import React from 'react' /** * Resolves the aspect ratio (width ÷ height) of a single-media bubble, * in order: * * 1. `suppliedRatio` — known at first paint, so the card never * resizes. `ImageItem.width` / `height` for images, and for video * `VideoItem.naturalAspectRatio`, taken from the dimensions Stream * stores on the attachment. * 2. the decoded ``'s `naturalWidth` / `naturalHeight`; * 3. `fallbackRatio`. * * Measurement reads both from `onLoad` **and** from the ref, because an * image already in the browser cache can be `complete` before React * attaches a `load` listener — a second card showing the same URL, a * remount while scrolling a thread, or a Storybook HMR update all hit * that path, and an `onLoad`-only hook silently keeps the fallback * forever. * * Shared by the image and video cards: a video's poster is an `` * on the bubble surface, so it measures identically (MES-1353). */ export const useSingleMediaRatio = ({ src, suppliedRatio, fallbackRatio, }: { /** Media source, used to discard a measurement from a previous item. */ src: string | undefined /** Ratio the caller already knows, if any. Wins over measuring. */ suppliedRatio?: number /** Used until something better is known. */ fallbackRatio: number }): { ratio: number imgRef: (img: HTMLImageElement | null) => void onLoad: React.ReactEventHandler } => { const [measured, setMeasured] = React.useState<{ src: string ratio: number }>() const measure = React.useCallback( (img: HTMLImageElement | null) => { if (!img || img.naturalWidth <= 0 || img.naturalHeight <= 0) return if (src === undefined) return const ratio = img.naturalWidth / img.naturalHeight // Returning `prev` unchanged matters: the ref callback runs on // every commit, and a fresh object each time would schedule an // endless render loop. setMeasured((prev) => prev && prev.src === src && prev.ratio === ratio ? prev : { src, ratio } ) }, [src] ) const handleLoad = React.useCallback( (event: React.SyntheticEvent) => measure(event.currentTarget), [measure] ) // Discard a measurement left over from a previous `src`. const measuredRatio = measured && measured.src === src ? measured.ratio : undefined return { ratio: suppliedRatio ?? measuredRatio ?? fallbackRatio, imgRef: measure, onLoad: handleLoad, } }