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;