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,
}
}