import type { ImageProps } from 'expo-image'; import * as ExpoImage from 'expo-image'; import { Buffer } from 'buffer'; import React, { forwardRef, useState, useEffect, useCallback, useRef } from 'react'; import { Platform } from 'react-native'; function buildGridPlaceholder(w: number, h: number): string { const size = Math.max(w, h); const svg = ` `; const b64 = Buffer.from(svg).toString('base64'); return `data:image/svg+xml;base64,${b64}`; } type Src = ImageProps['source']; function computeSourceKey(src: Src): string { if (Array.isArray(src)) return src.map(computeSourceKey).join('|'); if (typeof src === 'number') return String(src); // require('./img.png') if (typeof src === 'string') return src; // remote on web if (src && typeof src === 'object' && 'uri' in src) return src.uri ?? ''; return ''; } const WrappedImage = forwardRef(function WrappedImage(props, ref) { const [fallbackSource, setFallbackSource] = useState(null); const source = props.source; const onError = props.onError; const style = props.style; const currentKey = computeSourceKey(props.source); const prevKeyRef = useRef(currentKey); useEffect(() => { if (prevKeyRef.current !== currentKey) { // parent really pointed to a different image: clear any old fallback setFallbackSource(null); prevKeyRef.current = currentKey; } }, [currentKey]); const handleError: ImageProps['onError'] = useCallback( (e: ExpoImage.ImageErrorEventData) => { onError?.(e); /* already swapped or dealing with a multi‑src array */ if (fallbackSource || Array.isArray(source)) return; // prevent it from recursing if ( source && typeof source === 'object' && 'uri' in source && source?.uri?.startsWith('data:') ) { return; } /* try to infer a sensible grid size */ const finalStyle = Array.isArray(style) ? Object.assign({}, ...style) : style; const width = finalStyle?.width ?? 128; const height = finalStyle?.height ?? 128; if (Platform.OS === 'web') { setFallbackSource({ uri: buildGridPlaceholder(width, height) }); } else { setFallbackSource(require('../../src/__create/placeholder.svg')); } }, [source, fallbackSource, onError, style] ); return ( ); }); /* expose static helpers so nothing breaks */ Object.assign(WrappedImage, ExpoImage); /* re‑export everything that expo-image provides */ export * from 'expo-image'; export const Image = WrappedImage; export default Image;