measureLayout instead of measureInWindow to get size relative to gallery bleed ancestor

This commit is contained in:
vineyardbovines
2026-04-14 09:27:27 -04:00
parent 11929c7379
commit 13ce9da116
+34 -40
View File
@@ -44,13 +44,16 @@ interface GalleryProps {
} }
const Context = createContext<{ const Context = createContext<{
ref: React.RefObject<View | null> bleedRef: React.RefObject<View | null>
bleedWidth: number
}>({ }>({
ref: {current: null}, bleedRef: {current: null},
bleedWidth: 0,
}) })
export function GalleryBleed({children}: {children: React.ReactNode}) { export function GalleryBleed({children}: {children: React.ReactNode}) {
const ref = useRef<View>(null) const ref = useRef<View>(null)
const [bleedWidth, setBleedWidth] = useState(0)
if (!isValidElement(children)) { if (!isValidElement(children)) {
throw new Error('GalleryBleed children must be a single React element') throw new Error('GalleryBleed children must be a single React element')
@@ -59,18 +62,19 @@ export function GalleryBleed({children}: {children: React.ReactNode}) {
const node = children as React.ReactElement<any> const node = children as React.ReactElement<any>
return ( return (
<Context.Provider value={{ref}}> <Context.Provider value={{bleedRef: ref, bleedWidth}}>
{cloneElement(node, { {cloneElement(node, {
ref: mergeRefs([ref, node?.props?.ref]), ref: mergeRefs([ref, node?.props?.ref]),
onLayout: (e: {nativeEvent: {layout: {width: number}}}) => {
setBleedWidth(e.nativeEvent.layout.width)
},
})} })}
</Context.Provider> </Context.Provider>
) )
} }
export function useGalleryBleedRef() { export function useGalleryBleed() {
const {ref} = useContext(Context) return useContext(Context)
// TODO throw?
return ref
} }
export function Gallery({ export function Gallery({
@@ -98,38 +102,31 @@ export function Gallery({
/* /*
* Container overflow styles * Container overflow styles
*
* Uses measureLayout to get the Gallery's offset relative to the GalleryBleed
* ancestor. This is a layout-relative measurement that doesn't depend on
* scroll position, so it works correctly for off-screen FlatList items.
*/ */
const bleedRef = useGalleryBleedRef() const {bleedRef, bleedWidth} = useGalleryBleed()
const [bleedDims, setBleedDims] = useState<{
left: number
right: number
width: number
}>()
const measureBleed = () => {
bleedRef?.current?.measureInWindow((x, _y, width) => {
setBleedDims({left: x, right: x + width, width})
})
}
const contentRef = useRef<View>(null) const contentRef = useRef<View>(null)
const [contentDims, setContentDims] = useState<{ const [insets, setInsets] = useState<{left: number; right: number}>()
left: number const measure = () => {
right: number if (contentRef.current && bleedRef.current && bleedWidth > 0) {
width: number contentRef.current.measureLayout(
}>() bleedRef.current,
const measureContent = () => { (x, _y, w) => {
contentRef?.current?.measureInWindow((x, _y, width) => { setInsets({
setContentDims({left: x, right: x + width, width}) left: x,
right: Math.max(0, bleedWidth - x - w),
}) })
},
() => {},
)
} }
const insetLeft = }
bleedDims && contentDims const insetLeft = insets?.left ?? 0
? Math.max(0, contentDims.left - bleedDims.left) const insetRight = insets?.right ?? 0
: 999 const width = bleedWidth || Math.min(600, window.width)
const insetRight =
bleedDims && contentDims
? Math.max(0, bleedDims.right - contentDims.right)
: 999
const width = bleedDims ? bleedDims.width : Math.min(600, window.width)
/* End container overflow styles */ /* End container overflow styles */
return ( return (
@@ -142,10 +139,7 @@ export function Gallery({
overflow: 'visible', overflow: 'visible',
}, },
]} ]}
onLayout={() => { onLayout={measure}>
measureBleed()
measureContent()
}}>
<BlockDrawerGesture> <BlockDrawerGesture>
<FlatList <FlatList
horizontal horizontal
@@ -165,7 +159,7 @@ export function Gallery({
height: contentHeight, height: contentHeight,
marginLeft: -insetLeft, marginLeft: -insetLeft,
width, width,
}, a.debug]} }]}
contentContainerStyle={{ contentContainerStyle={{
gap: ITEM_GAP, gap: ITEM_GAP,
paddingLeft: insetLeft, paddingLeft: insetLeft,