1import React, { useState, useRef } from 'react'; 2 3import { ImageProps, ImageSource } from '../Image.types'; 4 5function findBestSourceForSize( 6 sources: ImageSource[] | undefined, 7 size: DOMRect | null 8): ImageSource | null { 9 return ( 10 [...(sources || [])] 11 // look for the smallest image that's still larger then a container 12 ?.map((source) => { 13 if (!size) { 14 return { source, penalty: 0, covers: false }; 15 } 16 const { width, height } = 17 typeof source === 'object' ? source : { width: null, height: null }; 18 if (width == null || height == null) { 19 return { source, penalty: 0, covers: false }; 20 } 21 if (width < size.width || height < size.height) { 22 return { 23 source, 24 penalty: Math.max(size.width - width, size.height - height), 25 covers: false, 26 }; 27 } 28 return { source, penalty: (width - size.width) * (height - size.height), covers: true }; 29 }) 30 .sort((a, b) => a.penalty - b.penalty) 31 .sort((a, b) => Number(b.covers) - Number(a.covers))[0]?.source ?? null 32 ); 33} 34 35export default function useSourceSelection( 36 sources?: ImageSource[], 37 sizeCalculation: ImageProps['responsivePolicy'] = 'live', 38 measurementCallback?: (target: HTMLElement, size: DOMRect) => void 39) { 40 const hasMoreThanOneSource = (sources?.length ?? 0) > 1; 41 // null - not calculated yet, DOMRect - size available 42 const [size, setSize] = useState<null | DOMRect>(null); 43 const resizeObserver = useRef<ResizeObserver | null>(null); 44 45 React.useEffect(() => { 46 return () => { 47 resizeObserver.current?.disconnect(); 48 }; 49 }, []); 50 51 const containerRef = React.useCallback( 52 (element: HTMLDivElement) => { 53 if (!hasMoreThanOneSource && !measurementCallback) { 54 return; 55 } 56 const rect = element?.getBoundingClientRect(); 57 measurementCallback?.(element, rect); 58 setSize(rect); 59 60 if (sizeCalculation === 'live') { 61 resizeObserver.current?.disconnect(); 62 if (!element) { 63 return; 64 } 65 resizeObserver.current = new ResizeObserver((entries) => { 66 setSize(entries[0].contentRect); 67 measurementCallback?.(entries[0].target as any, entries[0].contentRect); 68 }); 69 resizeObserver.current.observe(element); 70 } 71 }, 72 [hasMoreThanOneSource, sizeCalculation] 73 ); 74 75 const bestSourceForSize = size !== undefined ? findBestSourceForSize(sources, size) : null; 76 const source = (hasMoreThanOneSource ? bestSourceForSize : sources?.[0]) ?? null; 77 78 return React.useMemo( 79 () => ({ 80 containerRef, 81 source, 82 }), 83 [source] 84 ); 85} 86