1import React, { CSSProperties, SyntheticEvent, useEffect, Ref } from 'react'; 2 3import { 4 ImageContentPositionObject, 5 ImageContentPositionValue, 6 ImageNativeProps, 7 ImageSource, 8} from '../Image.types'; 9import { useBlurhash } from '../utils/blurhash/useBlurhash'; 10import { isBlurhashString } from '../utils/resolveSources'; 11 12function ensureUnit(value: string | number) { 13 const trimmedValue = String(value).trim(); 14 if (trimmedValue.endsWith('%')) { 15 return trimmedValue; 16 } 17 return `${trimmedValue}px`; 18} 19 20type KeysOfUnion<T> = T extends T ? keyof T : never; 21 22function getObjectPositionFromContentPositionObject( 23 contentPosition?: ImageContentPositionObject 24): string { 25 const resolvedPosition = { ...contentPosition } as Record< 26 KeysOfUnion<ImageContentPositionObject>, 27 ImageContentPositionValue 28 >; 29 if (!resolvedPosition) { 30 return '50% 50%'; 31 } 32 if (resolvedPosition.top == null && resolvedPosition.bottom == null) { 33 resolvedPosition.top = '50%'; 34 } 35 if (resolvedPosition.left == null && resolvedPosition.right == null) { 36 resolvedPosition.left = '50%'; 37 } 38 39 return ( 40 ['top', 'bottom', 'left', 'right'] 41 .map((key) => { 42 if (key in resolvedPosition) { 43 return `${key} ${ensureUnit(resolvedPosition[key])}`; 44 } 45 return ''; 46 }) 47 .join(' ') || '50% 50%' 48 ); 49} 50 51function getFetchPriorityFromImagePriority(priority: ImageNativeProps['priority'] = 'normal') { 52 return priority && ['low', 'high'].includes(priority) ? priority : 'auto'; 53} 54 55const ImageWrapper = React.forwardRef( 56 ( 57 { 58 source, 59 events, 60 contentPosition, 61 blurhashContentPosition, 62 priority, 63 style, 64 blurhashStyle, 65 className, 66 accessibilityLabel, 67 }: { 68 source?: ImageSource | null; 69 events?: { 70 onLoad?: (((event: SyntheticEvent<HTMLImageElement, Event>) => void) | undefined | null)[]; 71 onError?: ((({ source }: { source: ImageSource | null }) => void) | undefined | null)[]; 72 onTransitionEnd?: ((() => void) | undefined | null)[]; 73 onMount?: ((() => void) | undefined | null)[]; 74 }; 75 contentPosition?: ImageContentPositionObject; 76 blurhashContentPosition?: ImageContentPositionObject; 77 priority?: string | null; 78 style: CSSProperties; 79 blurhashStyle?: CSSProperties; 80 className?: string; 81 accessibilityLabel?: string; 82 }, 83 ref: Ref<HTMLImageElement> 84 ) => { 85 useEffect(() => { 86 events?.onMount?.forEach((e) => e?.()); 87 }, []); 88 const isBlurhash = isBlurhashString(source?.uri || ''); 89 const blurhashUri = useBlurhash(isBlurhash ? source?.uri : null, source?.width, source?.height); 90 const objectPosition = getObjectPositionFromContentPositionObject( 91 isBlurhash ? blurhashContentPosition : contentPosition 92 ); 93 const uri = isBlurhash ? blurhashUri : source?.uri; 94 if (!uri) return null; 95 return ( 96 <img 97 ref={ref} 98 alt={accessibilityLabel} 99 className={className} 100 src={uri || undefined} 101 key={source?.uri} 102 style={{ 103 width: '100%', 104 height: '100%', 105 position: 'absolute', 106 left: 0, 107 right: 0, 108 objectPosition, 109 ...style, 110 ...(isBlurhash ? blurhashStyle : {}), 111 }} 112 // @ts-ignore 113 // eslint-disable-next-line react/no-unknown-property 114 fetchpriority={getFetchPriorityFromImagePriority(priority || 'normal')} 115 onLoad={(event) => { 116 events?.onLoad?.forEach((e) => e?.(event)); 117 }} 118 onTransitionEnd={() => events?.onTransitionEnd?.forEach((e) => e?.())} 119 onError={() => events?.onError?.forEach((e) => e?.({ source: source || null }))} 120 /> 121 ); 122 } 123); 124export default ImageWrapper; 125