From a0e97a4e545a3edffd3a1c5483cd25e44412aa26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 15:35:41 +0200 Subject: [PATCH] fix(ui): attach the bottom sheet's touch listeners when the sheet mounts The non-passive touchmove listeners on the sheet and its backdrop were bound in an effect that ran while the portaled sheet was still unmounted, and never ran again, so they were never attached. The mounted element is now state, and the listeners follow it. --- src/components/ui/bottom-sheet.tsx | 3 +++ src/hooks/use-sheet-drag.ts | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/ui/bottom-sheet.tsx b/src/components/ui/bottom-sheet.tsx index 8c78808fb..e684707a1 100644 --- a/src/components/ui/bottom-sheet.tsx +++ b/src/components/ui/bottom-sheet.tsx @@ -148,6 +148,7 @@ export function BottomSheetContent({ const openerRef = React.useRef(null); const initial: SheetSnap = preview ? "preview" : "full"; const [snap, setSnap] = React.useState(initial); + const [node, setNode] = React.useState(null); const drag = useSheetDrag({ enabled: draggable, @@ -158,11 +159,13 @@ export function BottomSheetContent({ contentRef, overlayRef, probeRef, + node, }); const setContent = React.useCallback( (node: HTMLDivElement | null) => { contentRef.current = node; + setNode(node); if (typeof ref === "function") ref(node); else if (ref) ref.current = node; }, diff --git a/src/hooks/use-sheet-drag.ts b/src/hooks/use-sheet-drag.ts index 332a79b04..fe669b4ad 100644 --- a/src/hooks/use-sheet-drag.ts +++ b/src/hooks/use-sheet-drag.ts @@ -422,6 +422,14 @@ export interface UseSheetDragArgs { contentRef: React.RefObject; overlayRef: React.RefObject; probeRef: React.RefObject; + /** + * The mounted sheet element, as state rather than a ref. The sheet is + * portaled and mounts only while open, often long after the component + * that owns it (the More sheet's content is rendered, closed, from the + * first paint); the touch listeners below must be attached when the + * element appears, which a ref cannot announce. + */ + node: HTMLElement | null; } export interface SheetDragHandlers { @@ -441,6 +449,7 @@ export function useSheetDrag({ contentRef, overlayRef, probeRef, + node, }: UseSheetDragArgs): SheetDragHandlers { const latest = useRef({ hasPreview, snap, onSnap, onClose }); useEffect(() => { @@ -463,8 +472,8 @@ export function useSheetDrag({ }, [contentRef, overlayRef, probeRef]); useEffect(() => { - if (!enabled) return; - const content = contentRef.current; + if (!enabled || !node) return; + const content = node; const overlay = overlayRef.current; const drag = controller(); const onContentTouch = (event: TouchEvent) => drag.touchMove(event); @@ -472,14 +481,14 @@ export function useSheetDrag({ const onOverlayTouch = (event: TouchEvent) => { if (event.cancelable) event.preventDefault(); }; - content?.addEventListener("touchmove", onContentTouch, { passive: false }); + content.addEventListener("touchmove", onContentTouch, { passive: false }); overlay?.addEventListener("touchmove", onOverlayTouch, { passive: false }); return () => { - content?.removeEventListener("touchmove", onContentTouch); + content.removeEventListener("touchmove", onContentTouch); overlay?.removeEventListener("touchmove", onOverlayTouch); drag.dispose(); }; - }, [enabled, controller, contentRef, overlayRef]); + }, [enabled, node, controller, overlayRef]); if (!enabled) return {}; return {