react-panel-layout 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
- package/dist/FloatingPanelFrame-CEmXDvUA.cjs.map +1 -0
- package/dist/{FloatingPanelFrame-6W5OexYe.js → FloatingPanelFrame-SgYLc6Ud.js} +12 -15
- package/dist/FloatingPanelFrame-SgYLc6Ud.js.map +1 -0
- package/dist/FloatingWindow-BpdOpg_L.js +400 -0
- package/dist/FloatingWindow-BpdOpg_L.js.map +1 -0
- package/dist/FloatingWindow-TCDNY5gE.cjs +2 -0
- package/dist/FloatingWindow-TCDNY5gE.cjs.map +1 -0
- package/dist/GridLayout-B4VRsC0r.cjs +2 -0
- package/dist/GridLayout-B4VRsC0r.cjs.map +1 -0
- package/dist/GridLayout-BltqeCPK.js +927 -0
- package/dist/GridLayout-BltqeCPK.js.map +1 -0
- package/dist/HorizontalDivider-B5Z-KZLk.cjs +2 -0
- package/dist/HorizontalDivider-B5Z-KZLk.cjs.map +1 -0
- package/dist/HorizontalDivider-WF1k_qND.js +30 -0
- package/dist/HorizontalDivider-WF1k_qND.js.map +1 -0
- package/dist/PanelSystem-Bs8bQwQF.cjs +3 -0
- package/dist/PanelSystem-Bs8bQwQF.cjs.map +1 -0
- package/dist/PanelSystem-Dr1TBhxM.js +1946 -0
- package/dist/PanelSystem-Dr1TBhxM.js.map +1 -0
- package/dist/ResizeHandle-CScipO5l.cjs +2 -0
- package/dist/ResizeHandle-CScipO5l.cjs.map +1 -0
- package/dist/ResizeHandle-CdA_JYfN.js +120 -0
- package/dist/ResizeHandle-CdA_JYfN.js.map +1 -0
- package/dist/SwipePivotTabBar-BGO9X94m.js +407 -0
- package/dist/SwipePivotTabBar-BGO9X94m.js.map +1 -0
- package/dist/SwipePivotTabBar-BrQismcZ.cjs +2 -0
- package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +11 -9
- package/dist/config.js.map +1 -1
- package/dist/constants/styles.d.ts +18 -4
- package/dist/floating.cjs +1 -1
- package/dist/floating.js +1 -1
- package/dist/grid/index.d.ts +58 -0
- package/dist/grid.cjs +2 -0
- package/dist/grid.cjs.map +1 -0
- package/dist/grid.js +13 -0
- package/dist/grid.js.map +1 -0
- package/dist/hooks/gesture/presets.d.ts +33 -0
- package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +110 -0
- package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
- package/dist/hooks/gesture/types.d.ts +254 -0
- package/dist/hooks/gesture/useDirectionalLock.d.ts +20 -0
- package/dist/hooks/gesture/useEdgeSwipeInput.d.ts +23 -0
- package/dist/hooks/gesture/useNativeGestureGuard.d.ts +23 -0
- package/dist/hooks/gesture/usePointerTracking.d.ts +22 -0
- package/dist/hooks/gesture/useScrollBoundary.d.ts +23 -0
- package/dist/hooks/gesture/useSwipeInput.d.ts +5 -0
- package/dist/hooks/gesture/utils.d.ts +40 -0
- package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
- package/dist/hooks/useAnimationFrame.d.ts +84 -0
- package/dist/hooks/useSnapAnimation.d.ts +54 -0
- package/dist/hooks/useSwipeContentTransform.d.ts +79 -0
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +25 -2006
- package/dist/index.js.map +1 -1
- package/dist/modules/pivot/PivotContent.d.ts +1 -1
- package/dist/modules/pivot/SwipePivotContent.d.ts +39 -0
- package/dist/modules/pivot/SwipePivotContent.debug.tmp.d.ts +25 -0
- package/dist/modules/pivot/SwipePivotContent.test.d.ts +1 -0
- package/dist/modules/pivot/SwipePivotTabBar.d.ts +89 -0
- package/dist/modules/pivot/index.d.ts +3 -0
- package/dist/modules/pivot/scaleInputState.d.ts +37 -0
- package/dist/modules/pivot/types.d.ts +73 -2
- package/dist/modules/pivot/usePivotSwipeInput.d.ts +68 -0
- package/dist/modules/stack/StackContent.d.ts +15 -0
- package/dist/modules/stack/SwipeStackContent.d.ts +63 -0
- package/dist/modules/stack/SwipeStackOutlet.d.ts +80 -0
- package/dist/modules/stack/computeStackContentState.d.ts +99 -0
- package/dist/modules/stack/computeSwipeStackTransform.d.ts +76 -0
- package/dist/modules/stack/types.d.ts +194 -0
- package/dist/modules/stack/useStackAnimationState.d.ts +32 -0
- package/dist/modules/stack/useStackNavigation.d.ts +23 -0
- package/dist/modules/stack/useStackSwipeInput.d.ts +27 -0
- package/dist/panels/index.d.ts +67 -0
- package/dist/panels.cjs +2 -0
- package/dist/panels.cjs.map +1 -0
- package/dist/panels.js +28 -0
- package/dist/panels.js.map +1 -0
- package/dist/pivot/index.d.ts +3 -0
- package/dist/pivot.cjs +1 -1
- package/dist/pivot.cjs.map +1 -1
- package/dist/pivot.js +20 -2
- package/dist/pivot.js.map +1 -1
- package/dist/resizer/index.d.ts +57 -0
- package/dist/resizer.cjs +2 -0
- package/dist/resizer.cjs.map +1 -0
- package/dist/resizer.js +8 -0
- package/dist/resizer.js.map +1 -0
- package/dist/stack/index.d.ts +72 -0
- package/dist/stack.cjs +2 -0
- package/dist/stack.cjs.map +1 -0
- package/dist/stack.js +980 -0
- package/dist/stack.js.map +1 -0
- package/dist/sticky-header/StickyArea.d.ts +38 -0
- package/dist/sticky-header/index.d.ts +4 -4
- package/dist/sticky-header/types.d.ts +35 -22
- package/dist/sticky-header.cjs +1 -1
- package/dist/sticky-header.cjs.map +1 -1
- package/dist/sticky-header.js +65 -174
- package/dist/sticky-header.js.map +1 -1
- package/dist/styles-DPPuJ0sf.js +57 -0
- package/dist/styles-DPPuJ0sf.js.map +1 -0
- package/dist/styles-qf6ptVLD.cjs +2 -0
- package/dist/styles-qf6ptVLD.cjs.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/useContentCache-CO3LYNmz.js +24 -0
- package/dist/useContentCache-CO3LYNmz.js.map +1 -0
- package/dist/useContentCache-DqXtLrLs.cjs +2 -0
- package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
- package/dist/useDocumentPointerEvents-CKdhGXd0.js +46 -0
- package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +1 -0
- package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +2 -0
- package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +1 -0
- package/dist/useEffectEvent-Dp7HLCf0.js +13 -0
- package/dist/useEffectEvent-Dp7HLCf0.js.map +1 -0
- package/dist/useEffectEvent-huSsGUnl.cjs +2 -0
- package/dist/useEffectEvent-huSsGUnl.cjs.map +1 -0
- package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
- package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
- package/dist/useFloatingState-tEfA_wbc.js +74 -0
- package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
- package/dist/window/index.d.ts +61 -0
- package/dist/window.cjs +2 -0
- package/dist/window.cjs.map +1 -0
- package/dist/window.js +149 -0
- package/dist/window.js.map +1 -0
- package/docs/design-tokens.md +405 -0
- package/package.json +29 -4
- package/src/PanelSystemContext.tsx +88 -0
- package/src/components/grid/GridLayerList.tsx +172 -0
- package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
- package/src/components/grid/GridLayout.spec.tsx +743 -0
- package/src/components/grid/GridLayout.tsx +130 -0
- package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
- package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
- package/src/components/panels/DropSuggestOverlay.tsx +131 -0
- package/src/components/panels/PanelGroupView.tsx +112 -0
- package/src/components/pivot/PivotLayer.tsx +27 -0
- package/src/components/resizer/HorizontalDivider.tsx +52 -0
- package/src/components/resizer/ResizeHandle.tsx +118 -0
- package/src/components/tabs/TabBar.tsx +223 -0
- package/src/components/tabs/TabBarTab.tsx +133 -0
- package/src/components/tabs/TabDragOverlay.tsx +92 -0
- package/src/components/window/DialogOverlay.tsx +180 -0
- package/src/components/window/Drawer.tsx +282 -0
- package/src/components/window/DrawerLayers.tsx +58 -0
- package/src/components/window/FloatingWindow.tsx +95 -0
- package/src/components/window/PopupLayerPortal.tsx +218 -0
- package/src/config/PanelContentDeclaration.tsx +427 -0
- package/src/config/index.tsx +52 -0
- package/src/config/panelJsx.spec.tsx +54 -0
- package/src/config/panelJsxConfig.spec.tsx +54 -0
- package/src/config/panelJsxDrawer.spec.tsx +33 -0
- package/src/config/panelRouter.spec.ts +68 -0
- package/src/config/panelRouter.tsx +155 -0
- package/src/constants/styles.ts +261 -0
- package/src/demo/Layout.module.css +258 -0
- package/src/demo/Layout.tsx +176 -0
- package/src/demo/components/CodeBlock.module.css +54 -0
- package/src/demo/components/CodeBlock.tsx +34 -0
- package/src/demo/components/CodePreview.module.css +37 -0
- package/src/demo/components/CodePreview.tsx +31 -0
- package/src/demo/components/DataPreview.module.css +177 -0
- package/src/demo/components/DataPreview.tsx +115 -0
- package/src/demo/components/Story.module.css +68 -0
- package/src/demo/components/Story.tsx +54 -0
- package/src/demo/components/layout/CodePanel.module.css +183 -0
- package/src/demo/components/layout/CodePanel.tsx +149 -0
- package/src/demo/components/layout/DemoPage.module.css +60 -0
- package/src/demo/components/layout/DemoPage.tsx +56 -0
- package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
- package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
- package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
- package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
- package/src/demo/components/layout/index.ts +11 -0
- package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
- package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
- package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
- package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
- package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
- package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
- package/src/demo/components/tab-styles/index.ts +6 -0
- package/src/demo/components/ui/DemoButton.module.css +63 -0
- package/src/demo/components/ui/DemoButton.tsx +32 -0
- package/src/demo/components/ui/DemoCard.module.css +15 -0
- package/src/demo/components/ui/DemoCard.tsx +30 -0
- package/src/demo/components/ui/DemoContainer.module.css +17 -0
- package/src/demo/components/ui/DemoContainer.tsx +30 -0
- package/src/demo/components/ui/DemoPanel.module.css +23 -0
- package/src/demo/components/ui/DemoPanel.tsx +33 -0
- package/src/demo/components/ui/PanelText.module.css +18 -0
- package/src/demo/components/ui/PanelText.tsx +29 -0
- package/src/demo/components/ui/PanelTitle.module.css +18 -0
- package/src/demo/components/ui/PanelTitle.tsx +31 -0
- package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
- package/src/demo/demo.css +172 -0
- package/src/demo/hooks/useMedia.ts +41 -0
- package/src/demo/hooks/useShikiHighlight.ts +55 -0
- package/src/demo/index.tsx +293 -0
- package/src/demo/pages/Drawer/animations/index.tsx +22 -0
- package/src/demo/pages/Drawer/basics/index.tsx +17 -0
- package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
- package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
- package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
- package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
- package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
- package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
- package/src/demo/pages/Drawer/menu/index.tsx +17 -0
- package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
- package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
- package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
- package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
- package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
- package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
- package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
- package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
- package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
- package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
- package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
- package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
- package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
- package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
- package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
- package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
- package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
- package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
- package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
- package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
- package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
- package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
- package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
- package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
- package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
- package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
- package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
- package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
- package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
- package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
- package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
- package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
- package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
- package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
- package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
- package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
- package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
- package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
- package/src/demo/pages/PanelLayout/index.tsx +94 -0
- package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
- package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
- package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
- package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
- package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
- package/src/demo/pages/Pivot/basics/index.tsx +17 -0
- package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
- package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
- package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
- package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
- package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
- package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
- package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
- package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
- package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +289 -0
- package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
- package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
- package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
- package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
- package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
- package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
- package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
- package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
- package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
- package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
- package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
- package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
- package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
- package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/index.module.css +11 -0
- package/src/demo/pages/ResizeHandle/index.tsx +71 -0
- package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
- package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
- package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
- package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
- package/src/demo/pages/Stack/basics/index.tsx +22 -0
- package/src/demo/pages/Stack/components/Stack.module.css +234 -0
- package/src/demo/pages/Stack/components/StackBasics.tsx +217 -0
- package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
- package/src/demo/pages/Stack/components/StackTablet.tsx +401 -0
- package/src/demo/pages/Stack/tablet/index.tsx +22 -0
- package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
- package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
- package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
- package/src/demo/routes.tsx +193 -0
- package/src/demo/styles/animations.css +68 -0
- package/src/demo/styles/stack-themes.css +35 -0
- package/src/demo/utils/createPanelView.tsx +58 -0
- package/src/floating/index.ts +24 -0
- package/src/grid/index.ts +75 -0
- package/src/hooks/ContentCacheContext.tsx +87 -0
- package/src/hooks/gesture/presets.spec.ts +86 -0
- package/src/hooks/gesture/presets.ts +95 -0
- package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +237 -0
- package/src/hooks/gesture/testing/createGestureSimulator.ts +310 -0
- package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
- package/src/hooks/gesture/thresholdValue.ts +77 -0
- package/src/hooks/gesture/types.ts +290 -0
- package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
- package/src/hooks/gesture/useDirectionalLock.ts +115 -0
- package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +454 -0
- package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +413 -0
- package/src/hooks/gesture/useNativeGestureGuard.ts +133 -0
- package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
- package/src/hooks/gesture/usePointerTracking.ts +134 -0
- package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
- package/src/hooks/gesture/useScrollBoundary.ts +113 -0
- package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
- package/src/hooks/gesture/useSwipeInput.ts +310 -0
- package/src/hooks/gesture/utils.spec.ts +152 -0
- package/src/hooks/gesture/utils.ts +87 -0
- package/src/hooks/useAnimatedVisibility.spec.ts +257 -0
- package/src/hooks/useAnimatedVisibility.ts +146 -0
- package/src/hooks/useAnimationFrame.ts +200 -0
- package/src/hooks/useCSSMatrix.spec.ts +214 -0
- package/src/hooks/useCSSMatrix.ts +262 -0
- package/src/hooks/useClonedElementPreview.ts +28 -0
- package/src/hooks/useContainerScroll.ts +78 -0
- package/src/hooks/useContentCache.spec.tsx +232 -0
- package/src/hooks/useContentCache.tsx +127 -0
- package/src/hooks/useDocumentPointerEvents.ts +137 -0
- package/src/hooks/useDocumentScroll.ts +41 -0
- package/src/hooks/useEffectEvent.ts +40 -0
- package/src/hooks/useElementComponentWrapper.tsx +63 -0
- package/src/hooks/useIntersectionObserver.tsx +125 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
- package/src/hooks/useResizeObserver.tsx +81 -0
- package/src/hooks/useScrollContainer.ts +79 -0
- package/src/hooks/useSnapAnimation.ts +128 -0
- package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
- package/src/hooks/useSwipeContentTransform.ts +235 -0
- package/src/hooks/useTransitionState.ts +95 -0
- package/src/index.tsx +88 -0
- package/src/modules/grid/GridLayoutContext.tsx +57 -0
- package/src/modules/grid/LayerInstanceContext.tsx +56 -0
- package/src/modules/grid/resizeHandles.ts +157 -0
- package/src/modules/grid/trackUtils.ts +146 -0
- package/src/modules/grid/useGridPlacements.ts +143 -0
- package/src/modules/grid/useGridTracks.ts +156 -0
- package/src/modules/grid/useLayerDragHandle.ts +16 -0
- package/src/modules/grid/useLayerInteractions.tsx +850 -0
- package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
- package/src/modules/panels/dom/DomRegistry.tsx +94 -0
- package/src/modules/panels/index.ts +45 -0
- package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
- package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
- package/src/modules/panels/interactions/dnd.ts +28 -0
- package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
- package/src/modules/panels/layout/adapter.ts +124 -0
- package/src/modules/panels/rendering/ContentRegistry.spec.tsx +304 -0
- package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
- package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
- package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
- package/src/modules/panels/rendering/RenderContext.tsx +31 -0
- package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
- package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
- package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
- package/src/modules/panels/state/StateContext.tsx +12 -0
- package/src/modules/panels/state/cleanup.ts +37 -0
- package/src/modules/panels/state/commands.ts +53 -0
- package/src/modules/panels/state/focus/Context.tsx +25 -0
- package/src/modules/panels/state/focus/logic.ts +57 -0
- package/src/modules/panels/state/groups/Context.tsx +25 -0
- package/src/modules/panels/state/groups/logic.ts +105 -0
- package/src/modules/panels/state/splitLimits.spec.ts +46 -0
- package/src/modules/panels/state/splitLimits.ts +90 -0
- package/src/modules/panels/state/state.spec.ts +49 -0
- package/src/modules/panels/state/tree/Context.tsx +24 -0
- package/src/modules/panels/state/tree/logic.spec.ts +34 -0
- package/src/modules/panels/state/tree/logic.ts +138 -0
- package/src/modules/panels/state/types.ts +142 -0
- package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
- package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
- package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
- package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
- package/src/modules/panels/system/PanelSystem.tsx +206 -0
- package/src/modules/pivot/PivotContent.spec.tsx +179 -0
- package/src/modules/pivot/PivotContent.tsx +77 -0
- package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
- package/src/modules/pivot/SwipePivotContent.position.spec.tsx +167 -0
- package/src/modules/pivot/SwipePivotContent.spec.tsx +464 -0
- package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
- package/src/modules/pivot/SwipePivotContent.tsx +197 -0
- package/src/modules/pivot/SwipePivotTabBar.spec.tsx +865 -0
- package/src/modules/pivot/SwipePivotTabBar.tsx +523 -0
- package/src/modules/pivot/index.ts +8 -0
- package/src/modules/pivot/scaleInputState.spec.ts +210 -0
- package/src/modules/pivot/scaleInputState.ts +66 -0
- package/src/modules/pivot/types.ts +139 -0
- package/src/modules/pivot/usePivot.spec.ts +621 -0
- package/src/modules/pivot/usePivot.spec.tsx +186 -0
- package/src/modules/pivot/usePivot.tsx +345 -0
- package/src/modules/pivot/usePivotSwipeInput.spec.ts +649 -0
- package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
- package/src/modules/resizer/useResizeDrag.ts +94 -0
- package/src/modules/stack/StackContent.spec.tsx +264 -0
- package/src/modules/stack/StackContent.tsx +111 -0
- package/src/modules/stack/SwipeStackContent.spec.tsx +1277 -0
- package/src/modules/stack/SwipeStackContent.tsx +356 -0
- package/src/modules/stack/SwipeStackOutlet.spec.tsx +252 -0
- package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
- package/src/modules/stack/computeStackContentState.spec.ts +281 -0
- package/src/modules/stack/computeStackContentState.ts +304 -0
- package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
- package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
- package/src/modules/stack/types.ts +226 -0
- package/src/modules/stack/useStackAnimationState.spec.ts +186 -0
- package/src/modules/stack/useStackAnimationState.ts +138 -0
- package/src/modules/stack/useStackNavigation.spec.ts +477 -0
- package/src/modules/stack/useStackNavigation.tsx +336 -0
- package/src/modules/stack/useStackSwipeInput.spec.ts +276 -0
- package/src/modules/stack/useStackSwipeInput.ts +139 -0
- package/src/modules/window/useDrawerState.ts +81 -0
- package/src/modules/window/useFloatingState.spec.ts +252 -0
- package/src/modules/window/useFloatingState.ts +141 -0
- package/src/panels/index.ts +119 -0
- package/src/pivot/index.ts +19 -0
- package/src/resizer/index.ts +68 -0
- package/src/stack/index.ts +91 -0
- package/src/sticky-header/StickyArea.tsx +221 -0
- package/src/sticky-header/index.ts +18 -0
- package/src/sticky-header/types.ts +68 -0
- package/src/types.ts +323 -0
- package/src/utils/CSSMatrix.ts +321 -0
- package/src/utils/css.ts +65 -0
- package/src/utils/dialogUtils.ts +43 -0
- package/src/utils/math.ts +18 -0
- package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
- package/src/utils/typedActions.ts +103 -0
- package/src/vite-env.d.ts +6 -0
- package/src/window/index.ts +67 -0
- package/dist/FloatingPanelFrame-6W5OexYe.js.map +0 -1
- package/dist/FloatingPanelFrame-D9Cp2al1.cjs.map +0 -1
- package/dist/GridLayout-BzrIDrC9.js +0 -1465
- package/dist/GridLayout-BzrIDrC9.js.map +0 -1
- package/dist/GridLayout-ZrOhoLLB.cjs +0 -2
- package/dist/GridLayout-ZrOhoLLB.cjs.map +0 -1
- package/dist/sticky-header/StickyHeader.d.ts +0 -53
- package/dist/styles-CA2_zLZt.js +0 -52
- package/dist/styles-CA2_zLZt.js.map +0 -1
- package/dist/styles-PsqGOEJP.cjs +0 -2
- package/dist/styles-PsqGOEJP.cjs.map +0 -1
- package/dist/usePivot-BS-DGfwd.cjs +0 -2
- package/dist/usePivot-BS-DGfwd.cjs.map +0 -1
- package/dist/usePivot-BvOGxLQQ.js +0 -124
- package/dist/usePivot-BvOGxLQQ.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack.js","sources":["../src/modules/stack/computeStackContentState.ts","../src/modules/stack/StackContent.tsx","../src/modules/stack/useStackNavigation.tsx","../src/hooks/gesture/usePointerTracking.ts","../src/hooks/gesture/useDirectionalLock.ts","../src/hooks/gesture/utils.ts","../src/hooks/gesture/types.ts","../src/hooks/gesture/useSwipeInput.ts","../src/hooks/gesture/useEdgeSwipeInput.ts","../src/hooks/gesture/useNativeGestureGuard.ts","../src/modules/stack/useStackSwipeInput.ts","../src/hooks/useAnimationFrame.ts","../src/hooks/useSwipeContentTransform.ts","../src/modules/stack/computeSwipeStackTransform.ts","../src/modules/stack/SwipeStackContent.tsx","../src/modules/stack/SwipeStackOutlet.tsx"],"sourcesContent":["/**\n * @file Pure functions for computing StackContent state.\n *\n * Separates state computation logic from React/CSS concerns for testability.\n * All functions are pure and deterministic.\n */\nimport {\n STACK_ANIMATION_PUSH,\n STACK_ANIMATION_POP,\n STACK_TRANSITION_DURATION,\n STACK_TRANSITION_EASING,\n} from \"../../constants/styles.js\";\n\n/**\n * Animation type for stack panel transitions.\n */\nexport type StackAnimationType = \"push\" | \"pop\" | null;\n\n/**\n * Display mode for stack panels.\n */\nexport type StackDisplayMode = \"overlay\" | \"slide\" | \"stack\";\n\n/**\n * Transition mode for animations.\n */\nexport type StackTransitionMode = \"css\" | \"none\";\n\n/**\n * Input for computing animation type.\n */\nexport type ComputeAnimationTypeInput = {\n wasActive: boolean;\n isActive: boolean;\n transitionMode: StackTransitionMode;\n};\n\n/**\n * Compute the animation type based on active state change.\n *\n * @returns The animation type to apply, or null if no animation needed.\n */\nexport function computeAnimationType(input: ComputeAnimationTypeInput): StackAnimationType {\n const { wasActive, isActive, transitionMode } = input;\n\n if (transitionMode !== \"css\") {\n return null;\n }\n\n if (wasActive === isActive) {\n return null;\n }\n\n return isActive ? \"push\" : \"pop\";\n}\n\n/**\n * Input for computing visibility.\n */\nexport type ComputeVisibilityInput = {\n displayMode: StackDisplayMode;\n depth: number;\n navigationDepth: number;\n isActive: boolean;\n isAnimatingOut: boolean;\n isRevealing: boolean;\n revealDepth: number | null;\n};\n\n/**\n * Compute panel visibility based on display mode and state.\n *\n * @returns \"visible\" or \"hidden\"\n */\nexport function computeVisibility(input: ComputeVisibilityInput): \"visible\" | \"hidden\" {\n const {\n displayMode,\n depth,\n navigationDepth,\n isActive,\n isAnimatingOut,\n isRevealing,\n revealDepth,\n } = input;\n\n if (displayMode === \"overlay\") {\n // In overlay mode, only show active, animating out, or revealing panel\n if (isActive) {\n return \"visible\";\n }\n if (isAnimatingOut) {\n return \"visible\";\n }\n if (isRevealing && depth === revealDepth) {\n return \"visible\";\n }\n return \"hidden\";\n }\n\n // In slide/stack mode, show all panels in stack or animating out\n if (depth <= navigationDepth) {\n return \"visible\";\n }\n if (isAnimatingOut) {\n return \"visible\";\n }\n return \"hidden\";\n}\n\n/**\n * Input for computing transform.\n */\nexport type ComputeTransformInput = {\n depth: number;\n activeDepth: number;\n displayMode: StackDisplayMode;\n isRevealing: boolean;\n revealDepth: number | null;\n};\n\n/**\n * Compute the transform value for a stack panel.\n *\n * @returns CSS transform string\n */\nexport function computeTransform(input: ComputeTransformInput): string {\n const { depth, activeDepth, displayMode, isRevealing, revealDepth } = input;\n\n const isActive = depth === activeDepth;\n const isPrevious = depth < activeDepth;\n\n // During reveal, shift active panel to show parent\n if (isRevealing && isActive) {\n if (revealDepth !== null) {\n const revealProgress = 0.3;\n return `translateX(${revealProgress * 100}%)`;\n }\n }\n\n if (isActive) {\n return \"translateX(0)\";\n }\n\n if (isPrevious) {\n switch (displayMode) {\n case \"overlay\":\n return \"translateX(0)\";\n case \"slide\":\n return \"translateX(-30%)\";\n case \"stack\": {\n const offset = (activeDepth - depth) * -5;\n const scale = 1 - (activeDepth - depth) * 0.05;\n return `translateX(${offset}%) scale(${scale})`;\n }\n }\n }\n\n // Future panels stay off-screen\n return \"translateX(100%)\";\n}\n\n/**\n * Compute the transform value considering swipe progress.\n */\nfunction computeSwipeTransform(\n baseTransform: string,\n swipeProgress: number | undefined,\n isActive: boolean,\n): string {\n if (swipeProgress === undefined) {\n return baseTransform;\n }\n if (swipeProgress <= 0) {\n return baseTransform;\n }\n if (!isActive) {\n return baseTransform;\n }\n return `translateX(${swipeProgress * 100}%)`;\n}\n\n/**\n * Compute the transition CSS value.\n */\nfunction computeTransitionCss(transitionMode: StackTransitionMode): string | undefined {\n if (transitionMode !== \"css\") {\n return undefined;\n }\n return `transform ${STACK_TRANSITION_DURATION} ${STACK_TRANSITION_EASING}`;\n}\n\n/**\n * Full input for computing stack content state.\n */\nexport type StackContentStateInput = {\n depth: number;\n isActive: boolean;\n wasActive: boolean;\n currentAnimationType: StackAnimationType;\n displayMode: StackDisplayMode;\n transitionMode: StackTransitionMode;\n navigationState: {\n depth: number;\n isRevealing: boolean;\n revealDepth: number | null;\n };\n swipeProgress: number | undefined;\n};\n\n/**\n * Computed state output for stack content.\n */\nexport type StackContentStateOutput = {\n nextAnimationType: StackAnimationType;\n visibility: \"visible\" | \"hidden\";\n transform: string;\n animation: string | undefined;\n transition: string | undefined;\n zIndex: number;\n pointerEvents: \"auto\" | \"none\";\n};\n\n/**\n * Compute the complete state for a stack content panel.\n *\n * This is the main entry point that combines all state computation.\n * Pure function with no side effects.\n *\n * @param input - All inputs needed to compute state\n * @returns Computed state for rendering\n */\nexport function computeStackContentState(input: StackContentStateInput): StackContentStateOutput {\n const {\n depth,\n isActive,\n wasActive,\n currentAnimationType,\n displayMode,\n transitionMode,\n navigationState,\n swipeProgress,\n } = input;\n\n // 1. Compute animation type\n const stateChangeAnimationType = computeAnimationType({\n wasActive,\n isActive,\n transitionMode,\n });\n\n // Use new animation type if state changed, otherwise preserve current\n const nextAnimationType = stateChangeAnimationType ?? currentAnimationType;\n\n // 2. Compute visibility\n const isAnimatingOut = nextAnimationType === \"pop\";\n const visibility = computeVisibility({\n displayMode,\n depth,\n navigationDepth: navigationState.depth,\n isActive,\n isAnimatingOut,\n isRevealing: navigationState.isRevealing,\n revealDepth: navigationState.revealDepth,\n });\n\n // 3. Compute transform\n const baseTransform = computeTransform({\n depth,\n activeDepth: navigationState.depth,\n displayMode,\n isRevealing: navigationState.isRevealing,\n revealDepth: navigationState.revealDepth,\n });\n\n // Apply swipe progress transform if swiping on active panel\n const transform = computeSwipeTransform(baseTransform, swipeProgress, isActive);\n\n // 4. Compute animation CSS\n const animation = (() => {\n if (transitionMode !== \"css\") {\n return undefined;\n }\n if (nextAnimationType === \"push\") {\n return STACK_ANIMATION_PUSH;\n }\n if (nextAnimationType === \"pop\") {\n return STACK_ANIMATION_POP;\n }\n return undefined;\n })();\n\n // 5. Compute transition CSS\n const transition = computeTransitionCss(transitionMode);\n\n return {\n nextAnimationType,\n visibility,\n transform,\n animation,\n transition,\n zIndex: depth,\n pointerEvents: isActive ? \"auto\" : \"none\",\n };\n}\n","/**\n * @file StackContent component for rendering stack panels with animations.\n *\n * Override via CSS custom properties:\n * - --rpl-stack-animation-push: Animation when panel is pushed\n * - --rpl-stack-animation-pop: Animation when panel is popped\n * - --rpl-stack-transition-duration: Duration of transitions\n * - --rpl-stack-transition-easing: Easing for transitions\n */\nimport * as React from \"react\";\nimport type { StackContentProps } from \"./types.js\";\nimport { computeStackContentState } from \"./computeStackContentState.js\";\nimport type { StackAnimationType } from \"./computeStackContentState.js\";\nimport { useIsomorphicLayoutEffect } from \"../../hooks/useIsomorphicLayoutEffect.js\";\n\nconst baseStyle: React.CSSProperties = {\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n};\n\n/**\n * Renders a stack panel with appropriate animation based on display mode.\n */\nexport const StackContent: React.FC<StackContentProps> = React.memo(\n ({ id, depth, isActive, displayMode, transitionMode, navigationState, swipeProgress, children }) => {\n const ref = React.useRef<HTMLDivElement>(null);\n const prevActiveRef = React.useRef(isActive);\n\n // Track current animation type\n const [animationType, setAnimationType] = React.useState<StackAnimationType>(null);\n\n // Compute state using pure function\n const computedState = computeStackContentState({\n depth,\n isActive,\n wasActive: prevActiveRef.current,\n currentAnimationType: animationType,\n displayMode,\n transitionMode,\n navigationState,\n swipeProgress,\n });\n\n // Update animation type synchronously before paint\n useIsomorphicLayoutEffect(() => {\n const wasActive = prevActiveRef.current;\n prevActiveRef.current = isActive;\n\n if (wasActive !== isActive) {\n setAnimationType(computedState.nextAnimationType);\n }\n }, [isActive, computedState.nextAnimationType]);\n\n // Clear animation type after animation ends\n // Only handle animation end for this element (not bubbled from children)\n const handleAnimationEnd = React.useCallback((e: React.AnimationEvent) => {\n if (e.target === e.currentTarget) {\n setAnimationType(null);\n }\n }, []);\n\n // Build style from computed state\n const style = React.useMemo<React.CSSProperties>(() => {\n const s: React.CSSProperties = {\n ...baseStyle,\n transform: computedState.transform,\n pointerEvents: computedState.pointerEvents,\n zIndex: computedState.zIndex,\n visibility: computedState.visibility,\n };\n\n if (computedState.animation !== undefined) {\n s.animation = computedState.animation;\n }\n\n if (computedState.transition !== undefined) {\n s.transition = computedState.transition;\n }\n\n return s;\n }, [\n computedState.transform,\n computedState.pointerEvents,\n computedState.zIndex,\n computedState.visibility,\n computedState.animation,\n computedState.transition,\n ]);\n\n const content = (\n <div\n ref={ref}\n data-stack-content={id}\n data-depth={depth}\n data-active={isActive ? \"true\" : \"false\"}\n style={style}\n onAnimationEnd={handleAnimationEnd}\n >\n {children}\n </div>\n );\n\n if (transitionMode === \"none\") {\n return <React.Activity mode={isActive ? \"visible\" : \"hidden\"}>{content}</React.Activity>;\n }\n\n return content;\n },\n);\n","/**\n * @file Headless hook for managing Stack (hierarchical) navigation.\n *\n * Provides navigation operations for a stack-based UI where panels\n * are pushed and popped as the user drills down into content.\n */\nimport * as React from \"react\";\nimport type {\n UseStackNavigationOptions,\n UseStackNavigationResult,\n StackNavigationState,\n StackPanelProps,\n StackBackButtonProps,\n StackPanel,\n} from \"./types.js\";\nimport { StackContent } from \"./StackContent.js\";\nimport { useContentCache } from \"../../hooks/useContentCache.js\";\n\n/**\n * Context for sharing stack state with Outlet component.\n */\ntype StackOutletContextValue = {\n getState: () => {\n panels: ReadonlyArray<StackPanel>;\n navigationState: StackNavigationState;\n displayMode: UseStackNavigationOptions[\"displayMode\"];\n transitionMode: NonNullable<UseStackNavigationOptions[\"transitionMode\"]>;\n };\n subscribe: (callback: () => void) => () => void;\n getCachedContent: (panelId: string) => React.ReactNode | null;\n};\n\nconst StackOutletContext = React.createContext<StackOutletContextValue | null>(null);\n\n/**\n * Outlet component that renders the stack panels.\n */\nconst StackOutletInner: React.FC = React.memo(() => {\n const ctx = React.useContext(StackOutletContext);\n if (!ctx) {\n throw new Error(\"StackOutlet must be used within useStackNavigation\");\n }\n\n const [, forceUpdate] = React.useReducer((x) => x + 1, 0);\n\n React.useEffect(() => {\n return ctx.subscribe(forceUpdate);\n }, [ctx]);\n\n const { panels, navigationState, displayMode, transitionMode } = ctx.getState();\n\n // Get panels that should be rendered (only those in the current stack)\n const visiblePanels = React.useMemo(() => {\n return navigationState.stack.map((id, index) => {\n const panel = panels.find((p) => p.id === id);\n return panel ? { panel, depth: index } : null;\n }).filter((p): p is { panel: StackPanel; depth: number } => p !== null);\n }, [navigationState.stack, panels]);\n\n return (\n <>\n {visiblePanels.map(({ panel, depth }) => (\n <StackContent\n key={panel.id}\n id={panel.id}\n depth={depth}\n isActive={depth === navigationState.depth}\n displayMode={displayMode}\n transitionMode={transitionMode}\n navigationState={navigationState}\n >\n {panel.cache ? ctx.getCachedContent(panel.id) : panel.content}\n </StackContent>\n ))}\n </>\n );\n});\n\n/**\n * Headless hook for managing hierarchical stack navigation.\n *\n * @example\n * ```tsx\n * const { state, push, go, Outlet } = useStackNavigation({\n * panels: [\n * { id: 'list', title: 'Items', content: <ListPage /> },\n * { id: 'detail', title: 'Detail', content: <DetailPage /> },\n * ],\n * displayMode: 'overlay',\n * });\n *\n * return (\n * <div>\n * <button onClick={() => push('detail')}>View Detail</button>\n * <Outlet />\n * </div>\n * );\n * ```\n */\nexport function useStackNavigation<TId extends string = string>(\n options: UseStackNavigationOptions<TId>,\n): UseStackNavigationResult<TId> {\n const {\n panels,\n initialPanelId,\n displayMode,\n transitionMode = \"css\",\n onPanelChange,\n } = options;\n\n // Initialize stack with initial panel\n const [stack, setStack] = React.useState<ReadonlyArray<TId>>(() => {\n const initialId = initialPanelId ?? (panels[0]?.id as TId);\n if (!initialId) {\n throw new Error(\"useStackNavigation: No panels provided\");\n }\n return [initialId];\n });\n\n // Reveal state for parent peeking\n const [revealState, setRevealState] = React.useState<{\n isRevealing: boolean;\n revealDepth: number | null;\n }>({ isRevealing: false, revealDepth: null });\n\n // Current depth (0-indexed)\n const depth = stack.length - 1;\n\n // Navigation state\n const state: StackNavigationState<TId> = React.useMemo(() => ({\n stack,\n depth,\n isRevealing: revealState.isRevealing,\n revealDepth: revealState.revealDepth,\n }), [stack, depth, revealState.isRevealing, revealState.revealDepth]);\n\n // Current and previous panel IDs\n const currentPanelId = stack[depth] as TId;\n const previousPanelId = depth > 0 ? stack[depth - 1] as TId : null;\n\n // Push a new panel onto the stack\n const push = React.useCallback((id: TId) => {\n const panel = panels.find((p) => p.id === id);\n if (!panel) {\n return;\n }\n setStack((prev) => [...prev, id]);\n onPanelChange?.(id, depth + 1);\n }, [panels, depth, onPanelChange]);\n\n // Navigate in a direction\n const go = React.useCallback((direction: number) => {\n if (direction >= 0) {\n return; // go is only for going back in stack navigation\n }\n const targetDepth = depth + direction;\n if (targetDepth < 0) {\n return;\n }\n setStack((prev) => prev.slice(0, targetDepth + 1));\n const targetId = stack[targetDepth] as TId;\n onPanelChange?.(targetId, targetDepth);\n }, [depth, stack, onPanelChange]);\n\n // Move to absolute depth\n const move = React.useCallback((targetDepth: number) => {\n if (targetDepth < 0 || targetDepth >= stack.length) {\n return;\n }\n setStack((prev) => prev.slice(0, targetDepth + 1));\n const targetId = stack[targetDepth] as TId;\n onPanelChange?.(targetId, targetDepth);\n }, [stack, onPanelChange]);\n\n // Replace current panel\n const replace = React.useCallback((id: TId) => {\n const panel = panels.find((p) => p.id === id);\n if (!panel) {\n return;\n }\n setStack((prev) => [...prev.slice(0, -1), id]);\n onPanelChange?.(id, depth);\n }, [panels, depth, onPanelChange]);\n\n // Check if navigation is possible\n const canGo = React.useCallback((direction: number): boolean => {\n if (direction >= 0) {\n return false; // canGo only checks backward navigation for stacks\n }\n const targetDepth = depth + direction;\n return targetDepth >= 0;\n }, [depth]);\n\n // Reveal parent panel\n const revealParent = React.useCallback((targetDepth?: number) => {\n const revealTo = targetDepth ?? depth - 1;\n if (revealTo < 0 || revealTo >= depth) {\n return;\n }\n setRevealState({ isRevealing: true, revealDepth: revealTo });\n }, [depth]);\n\n // Reveal root panel\n const revealRoot = React.useCallback(() => {\n if (depth === 0) {\n return;\n }\n setRevealState({ isRevealing: true, revealDepth: 0 });\n }, [depth]);\n\n // Dismiss reveal\n const dismissReveal = React.useCallback(() => {\n setRevealState({ isRevealing: false, revealDepth: null });\n }, []);\n\n // Get props for a panel element\n const getPanelProps = React.useCallback((id: TId): StackPanelProps => {\n const panelIndex = stack.indexOf(id);\n const isActive = panelIndex === depth;\n\n return {\n \"data-stack-panel\": id,\n \"data-depth\": panelIndex,\n \"data-active\": isActive ? \"true\" : \"false\",\n \"aria-hidden\": !isActive,\n };\n }, [stack, depth]);\n\n // Get props for back button\n const getBackButtonProps = React.useCallback((): StackBackButtonProps => {\n const canGoBack = depth > 0;\n const prevPanel = previousPanelId ? panels.find((p) => p.id === previousPanelId) : null;\n const label = prevPanel?.title ? `Back to ${prevPanel.title}` : \"Go back\";\n\n return {\n onClick: () => go(-1),\n disabled: !canGoBack,\n \"aria-label\": label,\n };\n }, [depth, previousPanelId, panels, go]);\n\n // Container style\n const containerStyle: React.CSSProperties = React.useMemo(\n () => ({\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n }),\n [],\n );\n\n // State ref for stable getState function\n const stateRef = React.useRef({\n panels,\n navigationState: state,\n displayMode,\n transitionMode,\n });\n\n stateRef.current = {\n panels,\n navigationState: state,\n displayMode,\n transitionMode,\n };\n\n // Subscribers for state changes\n const subscribersRef = React.useRef(new Set<() => void>());\n\n // Notify subscribers when state changes\n React.useEffect(() => {\n subscribersRef.current.forEach((callback) => callback());\n }, [state, displayMode, transitionMode]);\n\n // Content resolver for useContentCache\n const resolveContent = React.useCallback(\n (panelId: string): React.ReactNode | null => {\n const panel = stateRef.current.panels.find((p) => p.id === panelId);\n return panel?.content ?? null;\n },\n [],\n );\n\n // Valid IDs for cache cleanup\n const validIds = React.useMemo((): readonly string[] => panels.map((p) => p.id), [panels]);\n\n // Use shared content cache hook\n const { getCachedContent } = useContentCache({\n resolveContent,\n validIds,\n });\n\n // Stable context value\n const contextValue = React.useMemo<StackOutletContextValue>(\n () => ({\n getState: () => stateRef.current,\n subscribe: (callback) => {\n subscribersRef.current.add(callback);\n return () => subscribersRef.current.delete(callback);\n },\n getCachedContent,\n }),\n [getCachedContent],\n );\n\n // Stable Outlet component\n const Outlet = React.useMemo(() => {\n const OutletComponent: React.FC = () => (\n <StackOutletContext.Provider value={contextValue}>\n <div style={containerStyle} data-stack-container>\n <StackOutletInner />\n </div>\n </StackOutletContext.Provider>\n );\n OutletComponent.displayName = \"StackOutlet\";\n return OutletComponent;\n }, [contextValue, containerStyle]);\n\n return {\n state,\n push,\n go,\n move,\n replace,\n revealParent,\n revealRoot,\n dismissReveal,\n getPanelProps,\n getBackButtonProps,\n canGo,\n currentPanelId,\n previousPanelId,\n Outlet,\n };\n}\n","/**\n * @file Hook for tracking pointer state during gestures.\n *\n * Provides low-level pointer tracking with position, velocity, and timing.\n * This hook serves as the foundation for higher-level gesture hooks.\n */\nimport * as React from \"react\";\nimport { useDocumentPointerEvents } from \"../useDocumentPointerEvents.js\";\nimport { useEffectEvent } from \"../useEffectEvent.js\";\nimport type {\n PointerTrackingState,\n TimestampedPoint,\n UsePointerTrackingOptions,\n UsePointerTrackingResult,\n} from \"./types.js\";\n\n/**\n * Initial state for pointer tracking.\n */\nconst INITIAL_STATE: PointerTrackingState = {\n isDown: false,\n start: null,\n current: null,\n pointerId: null,\n wasCanceled: false,\n};\n\n/**\n * Creates a timestamped point from pointer event coordinates.\n */\nconst createTimestampedPoint = (clientX: number, clientY: number): TimestampedPoint => ({\n x: clientX,\n y: clientY,\n timestamp: performance.now(),\n});\n\n/**\n * Hook for tracking pointer state during gestures.\n *\n * Tracks pointer down/move/up events and provides position and timing data\n * that can be used by higher-level gesture detection hooks.\n *\n * @example\n * ```tsx\n * const { state, onPointerDown, reset } = usePointerTracking({\n * enabled: true,\n * primaryOnly: true,\n * });\n *\n * return (\n * <div onPointerDown={onPointerDown}>\n * {state.isDown && <span>Tracking...</span>}\n * </div>\n * );\n * ```\n */\nexport function usePointerTracking(options: UsePointerTrackingOptions): UsePointerTrackingResult {\n const { enabled, primaryOnly = true } = options;\n\n const [state, setState] = React.useState<PointerTrackingState>(INITIAL_STATE);\n\n const reset = React.useCallback(() => {\n setState(INITIAL_STATE);\n }, []);\n\n const handlePointerDown = useEffectEvent((event: React.PointerEvent) => {\n if (!enabled) {\n return;\n }\n\n // Filter non-primary pointers if primaryOnly is set\n if (primaryOnly && !event.isPrimary) {\n return;\n }\n\n // Only track left mouse button or touch/pen\n if (event.pointerType === \"mouse\" && event.button !== 0) {\n return;\n }\n\n const point = createTimestampedPoint(event.clientX, event.clientY);\n\n setState({\n isDown: true,\n start: point,\n current: point,\n pointerId: event.pointerId,\n wasCanceled: false,\n });\n });\n\n const handlePointerMove = useEffectEvent((event: PointerEvent) => {\n // Verify this is the tracked pointer\n if (state.pointerId !== event.pointerId) {\n return;\n }\n\n const point = createTimestampedPoint(event.clientX, event.clientY);\n\n setState((prev) => ({\n ...prev,\n current: point,\n }));\n });\n\n const handlePointerUp = useEffectEvent(() => {\n setState(INITIAL_STATE);\n });\n\n const handlePointerCancel = useEffectEvent(() => {\n setState({ ...INITIAL_STATE, wasCanceled: true });\n });\n\n // Use document-level pointer events for tracking after pointer down\n const shouldTrackDocument = state.isDown ? enabled : false;\n useDocumentPointerEvents(shouldTrackDocument, {\n onMove: handlePointerMove,\n onUp: handlePointerUp,\n onCancel: handlePointerCancel,\n });\n\n // Reset state when disabled\n React.useEffect(() => {\n if (!enabled && state.isDown) {\n reset();\n }\n }, [enabled, state.isDown, reset]);\n\n return {\n state,\n onPointerDown: handlePointerDown,\n reset,\n };\n}\n","/**\n * @file Hook for locking gesture direction after threshold is exceeded.\n *\n * Once the user moves beyond the lock threshold, the direction is locked\n * to either horizontal or vertical, preventing diagonal gestures from\n * triggering both scroll and swipe.\n */\nimport * as React from \"react\";\nimport type {\n GestureAxis,\n UseDirectionalLockOptions,\n UseDirectionalLockResult,\n} from \"./types.js\";\n\nconst DEFAULT_LOCK_THRESHOLD = 10;\n\n/**\n * Determines which axis the gesture is primarily moving along.\n *\n * @param deltaX - Horizontal displacement from start\n * @param deltaY - Vertical displacement from start\n * @returns The dominant axis, or null if movement is insufficient\n */\nconst determineAxis = (deltaX: number, deltaY: number): GestureAxis | null => {\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n\n // Require at least some movement in one direction\n if (absX === 0 && absY === 0) {\n return null;\n }\n\n // Use a 1.5x ratio to ensure clear direction before locking\n // This prevents near-diagonal gestures from locking prematurely\n if (absX > absY * 1.5) {\n return \"horizontal\";\n }\n\n if (absY > absX * 1.5) {\n return \"vertical\";\n }\n\n // Still ambiguous\n return null;\n};\n\n/**\n * Hook for locking gesture direction after threshold is exceeded.\n *\n * This hook tracks pointer movement and locks to horizontal or vertical\n * direction once the movement exceeds the configured threshold. This\n * prevents diagonal gestures from triggering both scroll and swipe behaviors.\n *\n * @example\n * ```tsx\n * const { state: tracking, onPointerDown } = usePointerTracking({ enabled: true });\n * const { lockedAxis, isLocked } = useDirectionalLock({\n * tracking,\n * lockThreshold: 10,\n * });\n *\n * // lockedAxis will be \"horizontal\" or \"vertical\" once determined\n * ```\n */\nexport function useDirectionalLock(options: UseDirectionalLockOptions): UseDirectionalLockResult {\n const { tracking, lockThreshold = DEFAULT_LOCK_THRESHOLD } = options;\n\n const [lockedAxis, setLockedAxis] = React.useState<GestureAxis | null>(null);\n\n const reset = React.useCallback(() => {\n setLockedAxis(null);\n }, []);\n\n // Determine direction when tracking is active\n React.useEffect(() => {\n // Reset lock when pointer is released\n if (!tracking.isDown) {\n if (lockedAxis !== null) {\n reset();\n }\n return;\n }\n\n // Already locked, no need to recalculate\n if (lockedAxis !== null) {\n return;\n }\n\n // Need start and current positions\n if (!tracking.start || !tracking.current) {\n return;\n }\n\n const deltaX = tracking.current.x - tracking.start.x;\n const deltaY = tracking.current.y - tracking.start.y;\n\n // Check if we've exceeded the lock threshold\n const distance = Math.max(Math.abs(deltaX), Math.abs(deltaY));\n if (distance < lockThreshold) {\n return;\n }\n\n // Try to determine axis\n const axis = determineAxis(deltaX, deltaY);\n if (axis !== null) {\n setLockedAxis(axis);\n }\n }, [tracking.isDown, tracking.start, tracking.current, lockedAxis, lockThreshold, reset]);\n\n return {\n lockedAxis,\n isLocked: lockedAxis !== null,\n reset,\n };\n}\n","/**\n * @file Utility functions for gesture detection hooks.\n *\n * Contains shared calculations and helper functions used across\n * gesture-related hooks to avoid code duplication.\n */\nimport type * as React from \"react\";\n\n/**\n * Calculate velocity from displacement and time elapsed.\n *\n * @param displacement - Distance traveled in pixels\n * @param startTime - Start timestamp in milliseconds\n * @param currentTime - Current timestamp in milliseconds\n * @returns Velocity in pixels per millisecond\n */\nexport const calculateVelocity = (\n displacement: number,\n startTime: number,\n currentTime: number,\n): number => {\n const elapsed = currentTime - startTime;\n if (elapsed <= 0) {\n return 0;\n }\n return displacement / elapsed;\n};\n\n/**\n * Determine direction from displacement.\n *\n * @param displacement - Distance from start position\n * @returns -1 for backward (left/up), 0 for no movement, 1 for forward (right/down)\n */\nexport const determineDirection = (displacement: number): -1 | 0 | 1 => {\n if (displacement > 0) {\n return 1;\n }\n if (displacement < 0) {\n return -1;\n }\n return 0;\n};\n\n/**\n * Container props type for gesture handling.\n */\nexport type GestureContainerProps = React.HTMLAttributes<HTMLElement> & {\n style: React.CSSProperties;\n};\n\n/**\n * Merge multiple container props objects for gesture handling.\n *\n * Combines style objects and chains onPointerDown handlers.\n * Useful when combining multiple gesture hooks that each provide\n * their own container props (e.g., swipe input + native gesture guard).\n *\n * @param propsArray - Array of container props to merge\n * @returns Merged container props with combined styles and handlers\n */\nexport const mergeGestureContainerProps = (\n ...propsArray: GestureContainerProps[]\n): GestureContainerProps => {\n const mergedStyle: React.CSSProperties = {};\n const pointerDownHandlers: Array<\n ((event: React.PointerEvent<HTMLElement>) => void) | undefined\n > = [];\n\n for (const props of propsArray) {\n Object.assign(mergedStyle, props.style);\n if (props.onPointerDown) {\n pointerDownHandlers.push(props.onPointerDown);\n }\n }\n\n const handlePointerDown = (event: React.PointerEvent<HTMLElement>) => {\n for (const handler of pointerDownHandlers) {\n handler?.(event);\n }\n };\n\n return {\n onPointerDown: handlePointerDown,\n style: mergedStyle,\n };\n};\n","/**\n * @file Type definitions for gesture input detection hooks.\n *\n * These types support the separation of concerns:\n * - Operation: what to do (navigate, push, pop)\n * - Input: how to command (swipe, click, keyboard)\n * - Presentation: how to show (animation, transition)\n *\n * This file defines types for the Input layer.\n */\nimport type * as React from \"react\";\n\n/**\n * Axis for gesture detection.\n */\nexport type GestureAxis = \"horizontal\" | \"vertical\";\n\n/**\n * Phase of swipe input lifecycle.\n */\nexport type SwipeInputPhase = \"idle\" | \"tracking\" | \"swiping\" | \"ended\";\n\n/**\n * 2D vector for displacement and velocity.\n */\nexport type Vector2 = {\n x: number;\n y: number;\n};\n\n/**\n * Point with timestamp for velocity calculation.\n */\nexport type TimestampedPoint = {\n x: number;\n y: number;\n timestamp: number;\n};\n\n/**\n * Swipe input state during gesture.\n */\nexport type SwipeInputState = {\n /** Current phase of the swipe input */\n phase: SwipeInputPhase;\n /** Displacement from start position in pixels */\n displacement: Vector2;\n /** Current velocity in pixels per millisecond */\n velocity: Vector2;\n /**\n * Direction of movement as a number.\n * -1 = backward (left/up), 0 = no movement, 1 = forward (right/down)\n */\n direction: -1 | 0 | 1;\n};\n\n/**\n * Thresholds for swipe input recognition.\n */\nexport type SwipeInputThresholds = {\n /** Minimum distance in pixels to trigger swipe. @default 50 */\n distanceThreshold: number;\n /** Minimum velocity in px/ms to trigger swipe. @default 0.3 */\n velocityThreshold: number;\n /** Distance threshold before direction is locked. @default 10 */\n lockThreshold: number;\n};\n\n/**\n * Options for usePointerTracking hook.\n */\nexport type UsePointerTrackingOptions = {\n /** Whether tracking is enabled */\n enabled: boolean;\n /** Restrict to primary pointer only (ignore multitouch). @default true */\n primaryOnly?: boolean;\n};\n\n/**\n * Result from usePointerTracking hook.\n */\nexport type UsePointerTrackingResult = {\n /** Current tracking state */\n state: PointerTrackingState;\n /** Handler to attach to onPointerDown */\n onPointerDown: (event: React.PointerEvent) => void;\n /** Reset tracking state */\n reset: () => void;\n};\n\n/**\n * Internal pointer tracking state.\n */\nexport type PointerTrackingState = {\n /** Whether pointer is currently down */\n isDown: boolean;\n /** Start position and timestamp */\n start: TimestampedPoint | null;\n /** Current position and timestamp */\n current: TimestampedPoint | null;\n /** Active pointer ID */\n pointerId: number | null;\n /** Whether tracking ended via pointercancel (not pointerup) */\n wasCanceled: boolean;\n};\n\n/**\n * Options for useDirectionalLock hook.\n */\nexport type UseDirectionalLockOptions = {\n /** Pointer tracking state from usePointerTracking */\n tracking: PointerTrackingState;\n /** Lock threshold in pixels. @default 10 */\n lockThreshold?: number;\n};\n\n/**\n * Result from useDirectionalLock hook.\n */\nexport type UseDirectionalLockResult = {\n /** Locked axis, or null if not yet locked */\n lockedAxis: GestureAxis | null;\n /** Whether lock has been determined */\n isLocked: boolean;\n /** Reset the lock state */\n reset: () => void;\n};\n\n/**\n * Options for useScrollBoundary hook.\n */\nexport type UseScrollBoundaryOptions = {\n /** Scroll container ref. If null, checks document scroll. */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Axis to monitor */\n axis: GestureAxis;\n /** Tolerance in pixels for \"at boundary\" detection. @default 1 */\n tolerance?: number;\n};\n\n/**\n * Result from useScrollBoundary hook.\n */\nexport type UseScrollBoundaryResult = {\n /** Whether at the start boundary (top/left) */\n atStart: boolean;\n /** Whether at the end boundary (bottom/right) */\n atEnd: boolean;\n /** Current scroll position */\n scrollPosition: number;\n /** Maximum scroll position */\n maxScrollPosition: number;\n};\n\n/**\n * Filter function to determine if a pointer event should start tracking.\n * Receives the pointer event and container element.\n * Return true to allow tracking, false to ignore the event.\n */\nexport type PointerStartFilter = (\n event: React.PointerEvent,\n container: HTMLElement,\n) => boolean;\n\n/**\n * Options for useSwipeInput hook.\n */\nexport type UseSwipeInputOptions = {\n /** Ref to the container element */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Axis to detect swipes on */\n axis: GestureAxis;\n /** Whether swipe detection is enabled. @default true */\n enabled?: boolean;\n /** Swipe thresholds configuration */\n thresholds?: Partial<SwipeInputThresholds>;\n /** Callback when swipe is completed */\n onSwipeEnd?: (state: SwipeInputState) => void;\n /** Whether to enable trackpad two-finger swipe (wheel events). @default true */\n enableWheel?: boolean;\n /**\n * Optional filter to determine if a pointer event should start tracking.\n * If provided, only events that pass this filter will be tracked.\n * Useful for edge-based swipe detection.\n */\n pointerStartFilter?: PointerStartFilter;\n};\n\n/**\n * Result from useSwipeInput hook.\n */\nexport type UseSwipeInputResult = {\n /** Current swipe input state */\n state: SwipeInputState;\n /** Props to spread on the container element */\n containerProps: React.HTMLAttributes<HTMLElement> & {\n style: React.CSSProperties;\n };\n};\n\n/**\n * Edge for edge-originated gestures.\n */\nexport type GestureEdge = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n/**\n * Options for useEdgeSwipeInput hook.\n */\nexport type UseEdgeSwipeInputOptions = {\n /** Ref to the container element */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Which edge to detect swipes from */\n edge: GestureEdge;\n /** Width of the edge detection zone in pixels. @default 20 */\n edgeWidth?: number;\n /** Whether edge swipe detection is enabled. @default true */\n enabled?: boolean;\n /** Swipe thresholds configuration */\n thresholds?: Partial<SwipeInputThresholds>;\n /** Callback when edge swipe is completed */\n onSwipeEnd?: (state: SwipeInputState) => void;\n};\n\n/**\n * Result from useEdgeSwipeInput hook.\n */\nexport type UseEdgeSwipeInputResult = {\n /** Whether the current gesture started from the edge */\n isEdgeGesture: boolean;\n /** Current swipe input state */\n state: SwipeInputState;\n /** Props to spread on the container element */\n containerProps: React.HTMLAttributes<HTMLElement> & {\n style: React.CSSProperties;\n };\n};\n\n/**\n * Options for useNativeGestureGuard hook.\n */\nexport type UseNativeGestureGuardOptions = {\n /** Ref to the container element */\n containerRef: React.RefObject<HTMLElement | null>;\n /** Whether the guard is active */\n active: boolean;\n /** Prevent iOS/macOS edge back gesture. @default true */\n preventEdgeBack?: boolean;\n /** Prevent overscroll bounce effect. @default true */\n preventOverscroll?: boolean;\n /** Width of edge zone where back gesture is prevented. @default 20 */\n edgeWidth?: number;\n};\n\n/**\n * Result from useNativeGestureGuard hook.\n */\nexport type UseNativeGestureGuardResult = {\n /** Props to spread on the container element */\n containerProps: React.HTMLAttributes<HTMLElement> & {\n style: React.CSSProperties;\n };\n};\n\n/**\n * Default swipe input thresholds.\n *\n * - distanceThreshold: 100px is ~27% of a 375px mobile screen\n * - velocityThreshold: 0.5px/ms = 500px/s, a moderate flick speed\n * - lockThreshold: 10px before direction is locked\n */\nexport const DEFAULT_SWIPE_THRESHOLDS: SwipeInputThresholds = {\n distanceThreshold: 100,\n velocityThreshold: 0.5,\n lockThreshold: 10,\n};\n\n/**\n * Default edge width for edge gesture detection.\n */\nexport const DEFAULT_EDGE_WIDTH = 20;\n\n/**\n * Initial idle state for SwipeInputState.\n */\nexport const IDLE_SWIPE_INPUT_STATE: SwipeInputState = {\n phase: \"idle\",\n displacement: { x: 0, y: 0 },\n velocity: { x: 0, y: 0 },\n direction: 0,\n};\n","/**\n * @file Hook for detecting swipe gestures on a container element.\n *\n * Combines pointer tracking and directional locking to detect swipe gestures.\n * Also supports trackpad two-finger swipe via wheel events.\n * Returns swipe state and container props for gesture handling.\n */\nimport * as React from \"react\";\nimport { usePointerTracking } from \"./usePointerTracking.js\";\nimport { useDirectionalLock } from \"./useDirectionalLock.js\";\nimport { useEffectEvent } from \"../useEffectEvent.js\";\nimport { calculateVelocity, determineDirection } from \"./utils.js\";\nimport type {\n GestureAxis,\n SwipeInputState,\n SwipeInputThresholds,\n UseSwipeInputOptions,\n UseSwipeInputResult,\n Vector2,\n} from \"./types.js\";\nimport { DEFAULT_SWIPE_THRESHOLDS, IDLE_SWIPE_INPUT_STATE } from \"./types.js\";\n\n/** Idle timeout to reset wheel state after swipe stops */\nconst WHEEL_RESET_TIMEOUT = 150;\n\n/**\n * Evaluate swipe end and call callback if threshold is met.\n */\nconst evaluateSwipeEnd = (\n displacement: Vector2,\n velocity: Vector2,\n axis: GestureAxis,\n thresholds: SwipeInputThresholds,\n onSwipeEnd: ((state: SwipeInputState) => void) | undefined,\n): void => {\n const axisDisplacement = axis === \"horizontal\" ? displacement.x : displacement.y;\n const axisVelocity = axis === \"horizontal\" ? velocity.x : velocity.y;\n\n const absDisplacement = Math.abs(axisDisplacement);\n const absVelocity = Math.abs(axisVelocity);\n\n const triggered = absDisplacement >= thresholds.distanceThreshold || absVelocity >= thresholds.velocityThreshold;\n\n if (triggered) {\n const direction = determineDirection(axisDisplacement);\n const endState: SwipeInputState = {\n phase: \"ended\",\n displacement,\n velocity,\n direction,\n };\n onSwipeEnd?.(endState);\n }\n};\n\n/**\n * Hook for detecting swipe gestures on a container element.\n */\nexport function useSwipeInput(options: UseSwipeInputOptions): UseSwipeInputResult {\n const {\n containerRef,\n axis,\n enabled = true,\n thresholds: customThresholds,\n onSwipeEnd,\n enableWheel = true,\n pointerStartFilter,\n } = options;\n\n const thresholds: SwipeInputThresholds = {\n ...DEFAULT_SWIPE_THRESHOLDS,\n ...customThresholds,\n };\n\n // Stable callback for swipe end\n const handleSwipeEnd = useEffectEvent(onSwipeEnd);\n\n // ===== Pointer-based swipe tracking =====\n const { state: tracking, onPointerDown: baseOnPointerDown } = usePointerTracking({\n enabled,\n });\n\n // Wrap pointer down handler with optional filter\n const onPointerDown = React.useCallback(\n (event: React.PointerEvent) => {\n if (!enabled) {\n return;\n }\n if (pointerStartFilter) {\n const container = containerRef.current;\n if (!container) {\n return;\n }\n if (!pointerStartFilter(event, container)) {\n return;\n }\n }\n baseOnPointerDown(event);\n },\n [enabled, pointerStartFilter, containerRef, baseOnPointerDown],\n );\n\n const { lockedAxis, isLocked } = useDirectionalLock({\n tracking,\n lockThreshold: thresholds.lockThreshold,\n });\n\n const lastActiveStateRef = React.useRef<SwipeInputState | null>(null);\n\n // Prevent native scroll when swiping on iOS\n const isLockedToSwipeAxisRef = React.useRef(false);\n\n React.useEffect(() => {\n isLockedToSwipeAxisRef.current = isLocked ? lockedAxis === axis : false;\n }, [isLocked, lockedAxis, axis]);\n\n React.useEffect(() => {\n const container = containerRef.current;\n if (!container || !enabled) {\n return;\n }\n const disableTouchMove = (event: TouchEvent) => {\n event.preventDefault();\n };\n const handleTouchStart = (event: TouchEvent) => {\n if (isLockedToSwipeAxisRef.current) {\n event.preventDefault();\n }\n document.addEventListener(\"touchmove\", disableTouchMove, { passive: false });\n };\n const handleTouchEnd = () => {\n document.removeEventListener(\"touchmove\", disableTouchMove);\n };\n document.addEventListener(\"touchend\", handleTouchEnd);\n document.addEventListener(\"touchcancel\", handleTouchEnd);\n container.addEventListener(\"touchstart\", handleTouchStart, { passive: false });\n\n return () => {\n container.removeEventListener(\"touchstart\", handleTouchStart);\n document.removeEventListener(\"touchend\", handleTouchEnd);\n document.removeEventListener(\"touchcancel\", handleTouchEnd);\n };\n }, [containerRef, enabled]);\n\n // ===== Wheel-based swipe tracking =====\n const [wheelState, setWheelState] = React.useState<SwipeInputState>(IDLE_SWIPE_INPUT_STATE);\n const wheelAccumulatedRef = React.useRef({ x: 0, y: 0 });\n const wheelIdleTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n const wheelLockedRef = React.useRef(false);\n const wheelLockedAxisRef = React.useRef<GestureAxis | null>(null);\n\n const resetWheelState = React.useCallback(() => {\n wheelAccumulatedRef.current = { x: 0, y: 0 };\n wheelLockedRef.current = false;\n wheelLockedAxisRef.current = null;\n setWheelState(IDLE_SWIPE_INPUT_STATE);\n }, []);\n\n const endWheelSwipe = React.useCallback(() => {\n const displacement = { ...wheelAccumulatedRef.current };\n evaluateSwipeEnd(displacement, { x: 0, y: 0 }, axis, thresholds, handleSwipeEnd);\n resetWheelState();\n }, [axis, thresholds, handleSwipeEnd, resetWheelState]);\n\n const handleWheel = useEffectEvent((event: WheelEvent) => {\n if (!enabled || !enableWheel || tracking.isDown) {\n return;\n }\n\n const { deltaX, deltaY } = event;\n\n // Direction lock\n if (!wheelLockedRef.current) {\n const absX = Math.abs(deltaX);\n const absY = Math.abs(deltaY);\n\n if (absX >= thresholds.lockThreshold || absY >= thresholds.lockThreshold) {\n wheelLockedRef.current = true;\n wheelLockedAxisRef.current = absX > absY ? \"horizontal\" : \"vertical\";\n }\n }\n\n // If locked to wrong axis, ignore\n if (wheelLockedRef.current && wheelLockedAxisRef.current !== axis) {\n return;\n }\n\n // Accumulate displacement (negate: wheel delta is scroll direction)\n wheelAccumulatedRef.current.x -= deltaX;\n wheelAccumulatedRef.current.y -= deltaY;\n\n const accumulated = wheelAccumulatedRef.current;\n const axisDisplacement = axis === \"horizontal\" ? accumulated.x : accumulated.y;\n\n setWheelState({\n phase: \"swiping\",\n displacement: { ...accumulated },\n velocity: { x: 0, y: 0 },\n direction: determineDirection(axisDisplacement),\n });\n\n // When wheel stops, treat as \"release\"\n if (wheelIdleTimerRef.current !== null) {\n clearTimeout(wheelIdleTimerRef.current);\n }\n wheelIdleTimerRef.current = setTimeout(endWheelSwipe, WHEEL_RESET_TIMEOUT);\n });\n\n // Set up wheel event listener\n React.useEffect(() => {\n const container = containerRef.current;\n if (!container || !enabled || !enableWheel) {\n return;\n }\n\n const listener = (event: WheelEvent) => {\n event.preventDefault();\n handleWheel(event);\n };\n\n container.addEventListener(\"wheel\", listener, { passive: false });\n\n return () => {\n container.removeEventListener(\"wheel\", listener);\n if (wheelIdleTimerRef.current !== null) {\n clearTimeout(wheelIdleTimerRef.current);\n }\n };\n }, [containerRef, enabled, enableWheel, handleWheel]);\n\n React.useEffect(() => {\n return () => {\n if (wheelIdleTimerRef.current !== null) {\n clearTimeout(wheelIdleTimerRef.current);\n }\n };\n }, []);\n\n // ===== Pointer swipe state =====\n const pointerState = React.useMemo<SwipeInputState>(() => {\n if (!tracking.isDown || !tracking.start || !tracking.current) {\n return IDLE_SWIPE_INPUT_STATE;\n }\n\n const deltaX = tracking.current.x - tracking.start.x;\n const deltaY = tracking.current.y - tracking.start.y;\n const displacement = { x: deltaX, y: deltaY };\n\n const velocity = {\n x: calculateVelocity(deltaX, tracking.start.timestamp, tracking.current.timestamp),\n y: calculateVelocity(deltaY, tracking.start.timestamp, tracking.current.timestamp),\n };\n\n if (!isLocked || lockedAxis !== axis) {\n return { phase: \"tracking\", displacement, velocity, direction: 0 };\n }\n\n const axisDisplacement = axis === \"horizontal\" ? deltaX : deltaY;\n return {\n phase: \"swiping\",\n displacement,\n velocity,\n direction: determineDirection(axisDisplacement),\n };\n }, [tracking.isDown, tracking.start, tracking.current, isLocked, lockedAxis, axis]);\n\n React.useEffect(() => {\n if (pointerState.phase !== \"idle\") {\n lastActiveStateRef.current = pointerState;\n }\n }, [pointerState]);\n\n // Handle pointer up (but not cancel)\n React.useEffect(() => {\n if (tracking.isDown) {\n return;\n }\n\n const lastState = lastActiveStateRef.current;\n if (!lastState || (lastState.phase !== \"swiping\" && lastState.phase !== \"tracking\")) {\n return;\n }\n\n lastActiveStateRef.current = null;\n\n // Skip navigation if the gesture was canceled (e.g., browser took over for native scroll)\n if (tracking.wasCanceled) {\n return;\n }\n\n evaluateSwipeEnd(lastState.displacement, lastState.velocity, axis, thresholds, handleSwipeEnd);\n }, [tracking.isDown, tracking.wasCanceled, axis, thresholds, handleSwipeEnd]);\n\n // Merge states\n const state = pointerState.phase !== \"idle\" ? pointerState : wheelState;\n\n const containerProps = React.useMemo(() => {\n const touchAction = axis === \"horizontal\" ? \"pan-y pinch-zoom\" : \"pan-x pinch-zoom\";\n return {\n onPointerDown,\n style: {\n touchAction,\n userSelect: \"none\" as const,\n WebkitUserSelect: \"none\" as const,\n },\n };\n }, [axis, onPointerDown]);\n\n return { state, containerProps };\n}\n","/**\n * @file Hook for detecting swipe gestures that originate from the edge of a container.\n *\n * Edge swipes are commonly used for \"swipe back\" navigation in mobile apps.\n * This hook detects swipes that start within a configurable edge zone.\n *\n * Built on top of useSwipeInput with edge zone filtering.\n */\nimport * as React from \"react\";\nimport { useSwipeInput } from \"./useSwipeInput.js\";\nimport type {\n GestureAxis,\n GestureEdge,\n SwipeInputState,\n UseEdgeSwipeInputOptions,\n UseEdgeSwipeInputResult,\n} from \"./types.js\";\nimport { DEFAULT_EDGE_WIDTH, DEFAULT_SWIPE_THRESHOLDS, IDLE_SWIPE_INPUT_STATE } from \"./types.js\";\n\n/**\n * Get the axis associated with an edge.\n */\nconst getAxisForEdge = (edge: GestureEdge): GestureAxis => {\n if (edge === \"left\" || edge === \"right\") {\n return \"horizontal\";\n }\n return \"vertical\";\n};\n\n/**\n * Check if a point is within the edge zone of a container.\n */\nconst isInEdgeZone = (\n clientX: number,\n clientY: number,\n container: HTMLElement,\n edge: GestureEdge,\n edgeWidth: number,\n): boolean => {\n const rect = container.getBoundingClientRect();\n\n switch (edge) {\n case \"left\":\n return clientX >= rect.left && clientX <= rect.left + edgeWidth;\n case \"right\":\n return clientX >= rect.right - edgeWidth && clientX <= rect.right;\n case \"top\":\n return clientY >= rect.top && clientY <= rect.top + edgeWidth;\n case \"bottom\":\n return clientY >= rect.bottom - edgeWidth && clientY <= rect.bottom;\n }\n};\n\n/**\n * Hook for detecting swipe gestures that originate from the edge of a container.\n *\n * This is useful for implementing \"swipe back\" navigation patterns where\n * the user must start their swipe from the edge of the screen.\n *\n * @example\n * ```tsx\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { isEdgeGesture, state, containerProps } = useEdgeSwipeInput({\n * containerRef,\n * edge: \"left\",\n * edgeWidth: 20,\n * onSwipeEnd: (state) => {\n * if (state.direction === 1) goBack();\n * },\n * });\n *\n * return <div ref={containerRef} {...containerProps}>{children}</div>;\n * ```\n */\nexport function useEdgeSwipeInput(options: UseEdgeSwipeInputOptions): UseEdgeSwipeInputResult {\n const {\n containerRef,\n edge,\n edgeWidth = DEFAULT_EDGE_WIDTH,\n enabled = true,\n thresholds: customThresholds,\n onSwipeEnd,\n } = options;\n\n const thresholds = {\n ...DEFAULT_SWIPE_THRESHOLDS,\n ...customThresholds,\n };\n\n const axis = getAxisForEdge(edge);\n\n // Track whether the current gesture started from the edge\n const [isEdgeGesture, setIsEdgeGesture] = React.useState(false);\n\n // Create edge zone filter for pointer events\n const pointerStartFilter = React.useCallback(\n (event: React.PointerEvent, container: HTMLElement): boolean => {\n const inEdge = isInEdgeZone(event.clientX, event.clientY, container, edge, edgeWidth);\n setIsEdgeGesture(inEdge);\n return inEdge;\n },\n [edge, edgeWidth],\n );\n\n // Use base swipe input with edge filtering\n const { state, containerProps } = useSwipeInput({\n containerRef,\n axis,\n enabled,\n thresholds,\n onSwipeEnd,\n enableWheel: false, // Edge swipe doesn't use wheel events\n pointerStartFilter,\n });\n\n // Reset edge gesture state when swipe ends\n React.useEffect(() => {\n if (state.phase === \"idle\") {\n setIsEdgeGesture(false);\n }\n }, [state.phase]);\n\n // If not an edge gesture, return idle state\n const effectiveState: SwipeInputState = isEdgeGesture ? state : IDLE_SWIPE_INPUT_STATE;\n\n return {\n isEdgeGesture,\n state: effectiveState,\n containerProps,\n };\n}\n","/**\n * @file Hook for preventing conflicts with native OS gestures.\n *\n * This hook helps prevent conflicts with:\n * - iOS/macOS edge swipe back navigation\n * - Overscroll bounce effects\n *\n * It applies appropriate CSS properties and event handlers to the container.\n */\nimport * as React from \"react\";\nimport type {\n UseNativeGestureGuardOptions,\n UseNativeGestureGuardResult,\n} from \"./types.js\";\nimport { DEFAULT_EDGE_WIDTH } from \"./types.js\";\n\n/**\n * Check if a pointer event is within the left edge zone.\n */\nconst isInLeftEdge = (clientX: number, container: HTMLElement, edgeWidth: number): boolean => {\n const rect = container.getBoundingClientRect();\n return clientX >= rect.left && clientX <= rect.left + edgeWidth;\n};\n\n/**\n * Hook for preventing conflicts with native OS gestures.\n *\n * When active, this hook:\n * - Prevents iOS/macOS edge back gesture by capturing pointerdown events in the edge zone\n * - Prevents overscroll bounce effect using CSS overscroll-behavior\n * - Dynamically applies overscroll-behavior: none to html element during gesture\n *\n * @example\n * ```tsx\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { containerProps } = useNativeGestureGuard({\n * containerRef,\n * active: isSwipeActive,\n * preventEdgeBack: true,\n * preventOverscroll: true,\n * });\n *\n * return <div ref={containerRef} {...containerProps}>{children}</div>;\n * ```\n */\nexport function useNativeGestureGuard(options: UseNativeGestureGuardOptions): UseNativeGestureGuardResult {\n const {\n containerRef,\n active,\n preventEdgeBack = true,\n preventOverscroll = true,\n edgeWidth = DEFAULT_EDGE_WIDTH,\n } = options;\n\n // Track previous html overscroll-behavior value for restoration\n const previousHtmlOverscrollRef = React.useRef<string | null>(null);\n\n // Apply overscroll-behavior to html synchronously (called from onPointerDown)\n const applyHtmlOverscroll = React.useCallback(() => {\n if (!preventOverscroll) return;\n\n const html = document.documentElement;\n if (previousHtmlOverscrollRef.current === null) {\n previousHtmlOverscrollRef.current = html.style.overscrollBehavior;\n }\n html.style.overscrollBehavior = \"none\";\n }, [preventOverscroll]);\n\n // Remove overscroll-behavior from html when gesture ends\n React.useEffect(() => {\n if (active || !preventOverscroll) {\n return;\n }\n\n // Cleanup: restore previous value when deactivated\n if (previousHtmlOverscrollRef.current !== null) {\n document.documentElement.style.overscrollBehavior = previousHtmlOverscrollRef.current;\n previousHtmlOverscrollRef.current = null;\n }\n }, [active, preventOverscroll]);\n\n // Cleanup on unmount\n React.useEffect(() => {\n return () => {\n if (previousHtmlOverscrollRef.current !== null) {\n document.documentElement.style.overscrollBehavior = previousHtmlOverscrollRef.current;\n previousHtmlOverscrollRef.current = null;\n }\n };\n }, []);\n\n // Pointer down handler that prevents edge back gesture\n // Note: This must run on EVERY pointerdown in edge zone, not just when active,\n // because browser gesture recognition starts immediately on first touch.\n const onPointerDown = React.useCallback((event: React.PointerEvent) => {\n if (!preventEdgeBack) {\n return;\n }\n\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n // Prevent for touch events in the left edge zone\n // This must happen immediately, before we know if it's \"our\" gesture\n if (event.pointerType === \"touch\" && isInLeftEdge(event.clientX, container, edgeWidth)) {\n // Apply html overscroll-behavior synchronously before browser can recognize gesture\n applyHtmlOverscroll();\n // Prevent the browser from handling this as a back gesture\n event.preventDefault();\n }\n }, [preventEdgeBack, containerRef, edgeWidth, applyHtmlOverscroll]);\n\n // Build container props\n // Styles are applied immediately (not waiting for active) to prevent browser gestures\n const containerProps = React.useMemo(() => {\n const style: React.CSSProperties = {\n // Always apply to prevent browser navigation gestures\n overscrollBehavior: preventOverscroll ? \"contain\" : undefined,\n WebkitOverflowScrolling: \"touch\",\n };\n\n return {\n onPointerDown: preventEdgeBack ? onPointerDown : undefined,\n style,\n };\n }, [preventOverscroll, preventEdgeBack, onPointerDown]);\n\n return {\n containerProps,\n };\n}\n","/**\n * @file Hook for binding edge swipe input to Stack navigation.\n *\n * This hook connects edge swipe gesture detection to Stack's navigation API,\n * enabling iOS-style \"swipe to go back\" navigation.\n */\nimport * as React from \"react\";\nimport { useEdgeSwipeInput } from \"../../hooks/gesture/useEdgeSwipeInput.js\";\nimport { useNativeGestureGuard } from \"../../hooks/gesture/useNativeGestureGuard.js\";\nimport { mergeGestureContainerProps } from \"../../hooks/gesture/utils.js\";\nimport { IDLE_SWIPE_INPUT_STATE } from \"../../hooks/gesture/types.js\";\nimport type { SwipeInputState } from \"../../hooks/gesture/types.js\";\nimport type { UseStackSwipeInputOptions, UseStackSwipeInputResult } from \"./types.js\";\n\n/**\n * Hook for binding edge swipe input to Stack navigation.\n *\n * Detects swipe gestures from the specified edge and triggers navigation:\n * - Left edge swipe → go(-1) (go back)\n * - Right edge swipe → reveals parent or custom action\n *\n * During a swipe, provides progress for animation.\n *\n * @example\n * ```tsx\n * const containerRef = useRef<HTMLDivElement>(null);\n * const navigation = useStackNavigation({ panels, displayMode: 'overlay' });\n * const { isEdgeSwiping, progress, containerProps } = useStackSwipeInput({\n * containerRef,\n * navigation,\n * });\n *\n * return (\n * <div ref={containerRef} {...containerProps}>\n * <navigation.Outlet />\n * </div>\n * );\n * ```\n */\nexport function useStackSwipeInput(options: UseStackSwipeInputOptions): UseStackSwipeInputResult {\n const {\n containerRef,\n navigation,\n edge = \"left\",\n edgeWidth = 20,\n enabled = true,\n } = options;\n\n // Track swipe state for progress calculation\n const [swipeState, setSwipeState] = React.useState<SwipeInputState | null>(null);\n\n // Handle swipe completion - navigate back\n const handleSwipeEnd = React.useCallback(\n (state: SwipeInputState) => {\n setSwipeState(null);\n\n // Left edge swipe going right = go back\n if (edge === \"left\" && state.direction === 1) {\n if (navigation.canGo(-1)) {\n navigation.go(-1);\n }\n }\n // Right edge swipe going left = custom action (optional)\n // Could be used for forward navigation if the app supports it\n },\n [edge, navigation],\n );\n\n // Use edge swipe detection\n const { isEdgeGesture, state: inputState, containerProps: swipeProps } = useEdgeSwipeInput({\n containerRef,\n edge,\n edgeWidth,\n enabled: enabled && navigation.canGo(-1), // Only enable if can go back\n onSwipeEnd: handleSwipeEnd,\n });\n\n // Update swipe state for progress tracking\n React.useEffect(() => {\n if (isEdgeGesture && (inputState.phase === \"swiping\" || inputState.phase === \"tracking\")) {\n setSwipeState(inputState);\n } else if (inputState.phase === \"idle\") {\n setSwipeState(null);\n }\n }, [isEdgeGesture, inputState]);\n\n // Use native gesture guard during swipe\n const { containerProps: guardProps } = useNativeGestureGuard({\n containerRef,\n active: isEdgeGesture,\n preventEdgeBack: true,\n preventOverscroll: true,\n edgeWidth,\n });\n\n // Calculate swipe progress (0-1)\n const progress = React.useMemo(() => {\n if (!swipeState || !containerRef.current) {\n return 0;\n }\n\n const containerWidth = containerRef.current.clientWidth;\n if (containerWidth === 0) {\n return 0;\n }\n\n // Use X displacement for horizontal swipe\n const displacement = swipeState.displacement.x;\n\n // Only count rightward movement for left edge swipe\n if (edge === \"left\" && displacement <= 0) {\n return 0;\n }\n\n // Only count leftward movement for right edge swipe\n if (edge === \"right\" && displacement >= 0) {\n return 0;\n }\n\n const absDisplacement = Math.abs(displacement);\n return Math.min(absDisplacement / containerWidth, 1);\n }, [swipeState, containerRef, edge]);\n\n // Merge container props\n const containerProps = React.useMemo(\n () => mergeGestureContainerProps(swipeProps, guardProps),\n [swipeProps, guardProps],\n );\n\n // Effective input state: only return actual state if it's an edge gesture\n const effectiveInputState: SwipeInputState = isEdgeGesture ? inputState : IDLE_SWIPE_INPUT_STATE;\n\n return {\n isEdgeSwiping: isEdgeGesture,\n progress,\n inputState: effectiveInputState,\n containerProps,\n };\n}\n","/**\n * @file Generic requestAnimationFrame-based animation hook.\n *\n * Provides a reusable animation loop with easing support.\n * This is the foundation for more specific animation hooks.\n */\nimport * as React from \"react\";\n\n/**\n * Easing function type.\n * Takes a progress value (0-1) and returns an eased value (0-1).\n */\nexport type EasingFunction = (t: number) => number;\n\n/**\n * Built-in easing functions.\n */\nexport const easings = {\n /** Linear (no easing) */\n linear: (t: number): number => t,\n\n /** Ease out cubic */\n easeOutCubic: (t: number): number => 1 - Math.pow(1 - t, 3),\n\n /** Ease out expo (similar to cubic-bezier(0.22, 1, 0.36, 1)) */\n easeOutExpo: (t: number): number => {\n if (t === 1) {\n return 1;\n }\n return 1 - Math.pow(2, -10 * t);\n },\n\n /** Ease out quart */\n easeOutQuart: (t: number): number => 1 - Math.pow(1 - t, 4),\n\n /** Ease in out cubic */\n easeInOutCubic: (t: number): number => {\n if (t < 0.5) {\n return 4 * t * t * t;\n }\n return 1 - Math.pow(-2 * t + 2, 3) / 2;\n },\n} as const;\n\n/**\n * Animation state passed to callbacks.\n */\nexport type AnimationState = {\n /** Raw progress (0-1) */\n progress: number;\n /** Eased progress (0-1) */\n easedProgress: number;\n /** Elapsed time in ms */\n elapsed: number;\n /** Whether animation is complete */\n isComplete: boolean;\n};\n\n/**\n * Options for useAnimationFrame hook.\n */\nexport type UseAnimationFrameOptions = {\n /** Duration of animation in milliseconds */\n duration?: number;\n /** Easing function for the animation */\n easing?: EasingFunction;\n /** Callback called every frame with animation state */\n onFrame?: (state: AnimationState) => void;\n /** Callback when animation completes */\n onComplete?: () => void;\n};\n\n/**\n * Result from useAnimationFrame hook.\n */\nexport type UseAnimationFrameResult = {\n /** Whether animation is currently running */\n isAnimating: boolean;\n /** Start the animation */\n start: () => void;\n /** Cancel the animation */\n cancel: () => void;\n};\n\n/** Default animation duration in ms */\nconst DEFAULT_DURATION = 300;\n\n/**\n * Generic requestAnimationFrame-based animation hook.\n *\n * Provides a reusable animation loop with progress calculation and easing.\n * Use this as a building block for specific animation behaviors.\n *\n * @example\n * ```tsx\n * const { start, isAnimating } = useAnimationFrame({\n * duration: 300,\n * easing: easings.easeOutExpo,\n * onFrame: ({ easedProgress }) => {\n * const value = fromValue + (toValue - fromValue) * easedProgress;\n * element.style.transform = `translateX(${value}px)`;\n * },\n * onComplete: () => console.log('Done!'),\n * });\n *\n * // Start animation\n * start();\n * ```\n */\nexport function useAnimationFrame(options: UseAnimationFrameOptions): UseAnimationFrameResult {\n const {\n duration = DEFAULT_DURATION,\n easing = easings.easeOutExpo,\n onFrame,\n onComplete,\n } = options;\n\n const [isAnimating, setIsAnimating] = React.useState(false);\n const rafIdRef = React.useRef<number | null>(null);\n const startTimeRef = React.useRef<number | null>(null);\n\n // Use refs for callbacks to avoid stale closures\n const onFrameRef = React.useRef(onFrame);\n const onCompleteRef = React.useRef(onComplete);\n React.useEffect(() => {\n onFrameRef.current = onFrame;\n onCompleteRef.current = onComplete;\n }, [onFrame, onComplete]);\n\n const cancel = React.useCallback(() => {\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n startTimeRef.current = null;\n setIsAnimating(false);\n }, []);\n\n const start = React.useCallback(() => {\n // Cancel any existing animation\n cancel();\n\n setIsAnimating(true);\n startTimeRef.current = null;\n\n const step = (timestamp: number) => {\n if (startTimeRef.current === null) {\n startTimeRef.current = timestamp;\n }\n\n const elapsed = timestamp - startTimeRef.current;\n const progress = Math.min(elapsed / duration, 1);\n const easedProgress = easing(progress);\n const isComplete = progress >= 1;\n\n const state: AnimationState = {\n progress,\n easedProgress,\n elapsed,\n isComplete,\n };\n\n onFrameRef.current?.(state);\n\n if (!isComplete) {\n rafIdRef.current = requestAnimationFrame(step);\n } else {\n // Animation complete\n rafIdRef.current = null;\n startTimeRef.current = null;\n setIsAnimating(false);\n onCompleteRef.current?.();\n }\n };\n\n rafIdRef.current = requestAnimationFrame(step);\n }, [duration, easing, cancel]);\n\n // Cleanup on unmount\n React.useEffect(() => {\n return () => {\n if (rafIdRef.current !== null) {\n cancelAnimationFrame(rafIdRef.current);\n }\n };\n }, []);\n\n return {\n isAnimating,\n start,\n cancel,\n };\n}\n\n/**\n * Interpolate between two values using eased progress.\n */\nexport function interpolate(from: number, to: number, easedProgress: number): number {\n return from + (to - from) * easedProgress;\n}\n","/**\n * @file Shared hook for DOM-based swipe content transform.\n *\n * This hook provides immediate DOM manipulation for swipe gestures,\n * with smooth snap-back animation when the swipe ends.\n *\n * Used by both Pivot and Stack for consistent swipe behavior.\n */\nimport * as React from \"react\";\nimport { useAnimationFrame, interpolate, easings } from \"./useAnimationFrame.js\";\nimport type { GestureAxis } from \"./gesture/types.js\";\n\nconst DEFAULT_ANIMATION_DURATION = 300;\n\n/**\n * Options for useSwipeContentTransform hook.\n */\nexport type UseSwipeContentTransformOptions = {\n /** Ref to the element to transform */\n elementRef: React.RefObject<HTMLElement | null>;\n /** Target position in pixels (where element should be at rest) */\n targetPx: number;\n /** Current swipe displacement in pixels */\n displacement: number;\n /** Whether swipe gesture is active */\n isSwiping: boolean;\n /** Axis of transformation */\n axis?: GestureAxis;\n /** Duration of snap animation in ms */\n animationDuration?: number;\n /** Container size in pixels (used for snap on resize) */\n containerSize?: number;\n /**\n * Whether to animate when targetPx changes (without swipe).\n * Use this for tab bar animations triggered by click/button.\n * @default false\n */\n animateOnTargetChange?: boolean;\n /**\n * Initial position in pixels when first mounted.\n * If different from targetPx, will animate from initialPx to targetPx.\n * Use this for push animations where new panel comes from off-screen.\n */\n initialPx?: number;\n};\n\n/**\n * Animation direction information.\n */\nexport type AnimationDirection = {\n /** Source position in pixels */\n from: number;\n /** Target position in pixels */\n to: number;\n};\n\n/**\n * Result from useSwipeContentTransform hook.\n */\nexport type UseSwipeContentTransformResult = {\n /** Whether snap animation is currently running */\n isAnimating: boolean;\n /** Current position in pixels (for visibility calculations) */\n currentPx: number;\n /** Animation direction info, or null if not animating */\n animationDirection: AnimationDirection | null;\n};\n\n/**\n * Get CSS transform function name for axis.\n */\nconst getTransformFn = (axis: GestureAxis): \"translateX\" | \"translateY\" => {\n return axis === \"horizontal\" ? \"translateX\" : \"translateY\";\n};\n\n/**\n * Hook for DOM-based swipe content transform.\n *\n * During swipe: immediately updates element.style.transform to follow finger.\n * After swipe: animates from current position to target position.\n *\n * @example\n * ```tsx\n * const containerRef = useRef<HTMLDivElement>(null);\n * const { isAnimating, currentPx } = useSwipeContentTransform({\n * elementRef: containerRef,\n * targetPx: 0,\n * displacement: inputState.displacement.x,\n * isSwiping: inputState.phase === \"swiping\",\n * });\n * ```\n */\nexport function useSwipeContentTransform(\n options: UseSwipeContentTransformOptions,\n): UseSwipeContentTransformResult {\n const {\n elementRef,\n targetPx,\n displacement,\n isSwiping,\n axis = \"horizontal\",\n animationDuration = DEFAULT_ANIMATION_DURATION,\n containerSize,\n animateOnTargetChange = false,\n initialPx,\n } = options;\n\n // Use initialPx if provided, otherwise use targetPx\n const effectiveInitialPx = initialPx ?? targetPx;\n const currentPxRef = React.useRef<number>(effectiveInitialPx);\n const animRef = React.useRef<{ from: number; to: number } | null>(null);\n const prevTargetPxRef = React.useRef<number>(targetPx);\n const prevContainerSizeRef = React.useRef<number | undefined>(containerSize);\n const pendingAnimationRef = React.useRef<{ from: number; to: number } | null>(null);\n const isFirstMountRef = React.useRef<boolean>(true);\n\n // Schedule animation on first mount if initialPx differs from targetPx\n if (isFirstMountRef.current && initialPx !== undefined && initialPx !== targetPx) {\n pendingAnimationRef.current = { from: initialPx, to: targetPx };\n isFirstMountRef.current = false;\n } else if (isFirstMountRef.current) {\n isFirstMountRef.current = false;\n }\n\n // Handle target changes when not swiping\n if (targetPx !== prevTargetPxRef.current && !isSwiping && animRef.current === null) {\n if (animateOnTargetChange) {\n // Schedule animation from current position to new target\n const distance = Math.abs(currentPxRef.current - targetPx);\n if (distance > 1) {\n pendingAnimationRef.current = { from: currentPxRef.current, to: targetPx };\n } else {\n currentPxRef.current = targetPx;\n }\n } else {\n // Snap immediately (default behavior for resize, etc.)\n currentPxRef.current = targetPx;\n }\n prevTargetPxRef.current = targetPx;\n }\n\n // Snap when container size changes (resize)\n if (containerSize !== undefined && containerSize !== prevContainerSizeRef.current && containerSize > 0) {\n currentPxRef.current = targetPx;\n prevContainerSizeRef.current = containerSize;\n }\n\n // Animation frame handler\n const handleFrame = React.useCallback(\n ({ easedProgress }: { easedProgress: number }) => {\n const element = elementRef.current;\n const anim = animRef.current;\n if (!element || !anim) {\n return;\n }\n const value = interpolate(anim.from, anim.to, easedProgress);\n currentPxRef.current = value;\n element.style.transform = `${getTransformFn(axis)}(${value}px)`;\n },\n [axis, elementRef],\n );\n\n const handleComplete = React.useCallback(() => {\n animRef.current = null;\n currentPxRef.current = targetPx;\n prevTargetPxRef.current = targetPx;\n }, [targetPx]);\n\n const { isAnimating, start, cancel } = useAnimationFrame({\n duration: animationDuration,\n easing: easings.easeOutExpo,\n onFrame: handleFrame,\n onComplete: handleComplete,\n });\n\n // When swipe ends or target changes with animateOnTargetChange, animate to target\n React.useLayoutEffect(() => {\n if (isSwiping) {\n cancel();\n animRef.current = null;\n pendingAnimationRef.current = null;\n return;\n }\n\n // Check for pending animation (from target change or initial mount)\n if (pendingAnimationRef.current) {\n const pending = pendingAnimationRef.current;\n animRef.current = pending;\n pendingAnimationRef.current = null;\n // Set initial position before animation starts\n const element = elementRef.current;\n if (element) {\n element.style.transform = `${getTransformFn(axis)}(${pending.from}px)`;\n }\n start();\n return;\n }\n\n const currentPx = currentPxRef.current;\n const distance = Math.abs(currentPx - targetPx);\n\n if (distance > 1) {\n // Need to animate from current to target\n animRef.current = { from: currentPx, to: targetPx };\n start();\n } else {\n // Close enough, snap directly\n currentPxRef.current = targetPx;\n prevTargetPxRef.current = targetPx;\n }\n }, [isSwiping, targetPx, start, cancel]);\n\n // Direct DOM update during swipe\n React.useLayoutEffect(() => {\n const element = elementRef.current;\n if (!element) {\n return;\n }\n\n // Skip if animation is running, about to start, or pending\n if (isAnimating || animRef.current !== null || pendingAnimationRef.current !== null) {\n return;\n }\n\n const displayPx = targetPx + displacement;\n currentPxRef.current = displayPx;\n element.style.transform = `${getTransformFn(axis)}(${displayPx}px)`;\n }, [targetPx, displacement, axis, isAnimating, elementRef]);\n\n return {\n isAnimating,\n currentPx: currentPxRef.current,\n animationDirection: animRef.current,\n };\n}\n","/**\n * @file Pure functions for computing Stack panel transforms during swipe.\n *\n * These functions calculate positions for iOS-style \"swipe to go back\" behavior\n * where the active panel follows the finger and the behind panel reveals from -30%.\n */\n\n/**\n * Default offset for behind panel (-30% of container width).\n */\nexport const DEFAULT_BEHIND_OFFSET = -0.3;\n\n/**\n * Compute the target position for the active panel.\n *\n * Active panel starts at 0 and moves right as the user swipes.\n *\n * @param displacement - Current swipe displacement in pixels\n * @returns Target position in pixels\n */\nexport function computeActiveTargetPx(displacement: number): number {\n // Active panel follows the finger directly\n // Only move right (positive displacement) for left-edge swipe\n return Math.max(0, displacement);\n}\n\n/**\n * Compute the target position for the behind panel.\n *\n * Behind panel starts at -30% and moves to 0% as swipe progresses.\n * Uses parallax effect: moves slower than the active panel.\n *\n * @param displacement - Current swipe displacement in pixels\n * @param containerSize - Container width/height in pixels\n * @param behindOffset - Starting offset ratio (default -0.3 for -30%)\n * @returns Target position in pixels\n */\nexport function computeBehindTargetPx(\n displacement: number,\n containerSize: number,\n behindOffset: number = DEFAULT_BEHIND_OFFSET,\n): number {\n if (containerSize <= 0) {\n return 0;\n }\n\n // Only respond to positive displacement (swipe right)\n const clampedDisplacement = Math.max(0, displacement);\n\n // Calculate progress (0 to 1)\n const progress = Math.min(clampedDisplacement / containerSize, 1);\n\n // Behind panel starts at behindOffset * containerSize and moves to 0\n // Parallax: moves |behindOffset| * progress * containerSize\n const basePosition = behindOffset * containerSize;\n const parallaxOffset = Math.abs(behindOffset) * progress * containerSize;\n\n return basePosition + parallaxOffset;\n}\n\n/**\n * Compute swipe progress as a ratio (0 to 1).\n *\n * @param displacement - Current swipe displacement in pixels\n * @param containerSize - Container width/height in pixels\n * @returns Progress ratio from 0 to 1\n */\nexport function computeSwipeProgress(displacement: number, containerSize: number): number {\n if (containerSize <= 0) {\n return 0;\n }\n\n const clampedDisplacement = Math.max(0, displacement);\n return Math.min(clampedDisplacement / containerSize, 1);\n}\n\n/**\n * Input for computing swipe visibility.\n */\nexport type ComputeSwipeVisibilityInput = {\n /** Panel depth in the stack */\n depth: number;\n /** Current navigation depth (active panel) */\n navigationDepth: number;\n /** Whether this panel is currently active */\n isActive: boolean;\n /** Whether swipe gesture is active */\n isSwiping: boolean;\n /** Whether snap-back animation is running */\n isAnimating: boolean;\n};\n\n/**\n * Compute whether a panel should be visible during swipe.\n *\n * During swipe:\n * - Active panel is always visible\n * - Behind panel (depth = navigationDepth - 1) is visible when swiping/animating\n *\n * @returns true if panel should be visible\n */\nexport function computeSwipeVisibility(input: ComputeSwipeVisibilityInput): boolean {\n const { depth, navigationDepth, isActive, isSwiping, isAnimating } = input;\n\n // Active panel is always visible\n if (isActive) {\n return true;\n }\n\n // Behind panel (one level back) is visible during swipe or animation\n const isBehindPanel = depth === navigationDepth - 1;\n if (isBehindPanel) {\n if (isSwiping) {\n return true;\n }\n if (isAnimating) {\n return true;\n }\n }\n\n // Other panels are hidden during swipe\n return false;\n}\n\n/**\n * Determine the role of a panel during swipe gesture.\n */\nexport type SwipePanelRole = \"active\" | \"behind\" | \"hidden\";\n\n/**\n * Determine the role of a panel during swipe.\n *\n * @param depth - Panel depth in the stack\n * @param navigationDepth - Current navigation depth (active panel)\n * @returns Panel role for swipe handling\n */\nexport function determineSwipePanelRole(depth: number, navigationDepth: number): SwipePanelRole {\n if (depth === navigationDepth) {\n return \"active\";\n }\n if (depth === navigationDepth - 1) {\n return \"behind\";\n }\n return \"hidden\";\n}\n","/**\n * @file SwipeStackContent component for Stack panels with direct DOM manipulation.\n *\n * Provides iOS-style swipe-to-go-back behavior:\n * - Active panel follows the finger directly\n * - Behind panel reveals from -30% with parallax effect\n *\n * Uses useSwipeContentTransform for immediate DOM updates.\n */\nimport * as React from \"react\";\nimport { useSwipeContentTransform } from \"../../hooks/useSwipeContentTransform.js\";\nimport type { SwipeInputState, GestureAxis } from \"../../hooks/gesture/types.js\";\nimport type { StackDisplayMode } from \"./types.js\";\nimport {\n computeActiveTargetPx,\n computeBehindTargetPx,\n computeSwipeVisibility,\n determineSwipePanelRole,\n DEFAULT_BEHIND_OFFSET,\n} from \"./computeSwipeStackTransform.js\";\n\nconst DEFAULT_ANIMATION_DURATION = 300;\n\n/**\n * Scale factor per depth level for \"stack\" display mode.\n * Each level behind reduces scale by this amount.\n */\nconst STACK_SCALE_FACTOR = 0.05;\n\n/**\n * Offset percentage per depth level for \"stack\" display mode.\n */\nconst STACK_OFFSET_PERCENT = 5;\n\n/**\n * Maximum dimming opacity for behind panels in iOS-style navigation.\n */\nconst MAX_DIM_OPACITY = 0.1;\n\n/**\n * Props for SwipeStackContent component.\n */\nexport type SwipeStackContentProps = {\n /** Panel ID */\n id: string;\n /** Panel depth in the stack */\n depth: number;\n /** Current navigation depth (active panel) */\n navigationDepth: number;\n /** Whether this panel is currently active */\n isActive: boolean;\n /** Swipe input state from useStackSwipeInput */\n inputState: SwipeInputState;\n /** Container size in pixels (width for horizontal, height for vertical) */\n containerSize: number;\n /** Gesture axis. @default \"horizontal\" */\n axis?: GestureAxis;\n /** Behind panel offset ratio. @default -0.3 */\n behindOffset?: number;\n /** Animation duration in ms. @default 300 */\n animationDuration?: number;\n /**\n * Whether to animate when first mounted as active.\n * Set to true for push navigation animations.\n * @default false\n */\n animateOnMount?: boolean;\n /**\n * Whether to show iOS-style edge shadow on active panel.\n * @default true\n */\n showShadow?: boolean;\n /**\n * Display mode for visual styling.\n * - \"overlay\": panels overlay, no scale (iOS style)\n * - \"slide\": panels slide with parallax\n * - \"stack\": panels scale down and offset (stacked cards style)\n * @default \"overlay\"\n */\n displayMode?: StackDisplayMode;\n /**\n * Whether to show dimming overlay on behind panels.\n * Creates iOS-style darkening effect that fades during swipe.\n * @default true\n */\n showDimming?: boolean;\n /** Content to render */\n children: React.ReactNode;\n};\n\nconst BASE_STYLE: React.CSSProperties = {\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n};\n\n/**\n * Get displacement from input state for the given axis.\n */\nconst getAxisDisplacement = (inputState: SwipeInputState, axis: GestureAxis): number => {\n if (inputState.phase === \"idle\") {\n return 0;\n }\n return axis === \"horizontal\" ? inputState.displacement.x : inputState.displacement.y;\n};\n\n/**\n * SwipeStackContent renders a single stack panel with swipe gesture support.\n *\n * Key behaviors:\n * - Active panel: follows finger directly (translateX = displacement)\n * - Behind panel: reveals from -30% with parallax (slower movement)\n * - Hidden panels: not rendered during swipe\n *\n * @example\n * ```tsx\n * <SwipeStackContent\n * id=\"detail\"\n * depth={1}\n * navigationDepth={1}\n * isActive={true}\n * inputState={swipeInput.inputState}\n * containerSize={containerWidth}\n * >\n * <DetailPanel />\n * </SwipeStackContent>\n * ```\n */\n// iOS-style left edge shadow for active panels\nconst ACTIVE_PANEL_SHADOW = \"-5px 0 15px rgba(0, 0, 0, 0.1)\";\n\nexport const SwipeStackContent: React.FC<SwipeStackContentProps> = React.memo(\n ({\n id,\n depth,\n navigationDepth,\n isActive,\n inputState,\n containerSize,\n axis = \"horizontal\",\n behindOffset = DEFAULT_BEHIND_OFFSET,\n animationDuration = DEFAULT_ANIMATION_DURATION,\n animateOnMount = false,\n showShadow = true,\n displayMode = \"overlay\",\n showDimming = true,\n children,\n }) => {\n const elementRef = React.useRef<HTMLDivElement>(null);\n const isFirstMountRef = React.useRef<boolean>(true);\n\n const displacement = getAxisDisplacement(inputState, axis);\n const isSwiping = inputState.phase === \"swiping\" || inputState.phase === \"tracking\";\n\n // Determine panel role\n const role = determineSwipePanelRole(depth, navigationDepth);\n\n // Track first mount for push animation\n const isFirstMount = isFirstMountRef.current;\n if (isFirstMountRef.current) {\n isFirstMountRef.current = false;\n }\n\n // Compute target position based on role\n const targetPx = React.useMemo(() => {\n switch (role) {\n case \"active\":\n // Active panel rests at 0\n return 0;\n case \"behind\":\n // Behind panel rests at offset position\n return behindOffset * containerSize;\n case \"hidden\":\n // Hidden panels are off-screen\n return containerSize;\n }\n }, [role, behindOffset, containerSize]);\n\n // Compute displacement for this panel\n const panelDisplacement = React.useMemo(() => {\n if (displacement <= 0) {\n return 0;\n }\n\n switch (role) {\n case \"active\":\n // Active panel follows finger directly\n return computeActiveTargetPx(displacement);\n case \"behind\": {\n // Behind panel uses parallax - compute offset from base position\n const currentPos = computeBehindTargetPx(displacement, containerSize, behindOffset);\n const basePos = behindOffset * containerSize;\n return currentPos - basePos;\n }\n case \"hidden\":\n return 0;\n }\n }, [role, displacement, containerSize, behindOffset]);\n\n // Compute initial position for push animation\n // When animateOnMount is true and panel is first mounted as \"active\",\n // it should animate in from off-screen\n // Root panel (depth=0) should not animate on mount\n const initialPx = React.useMemo(() => {\n if (!isFirstMount || !animateOnMount) {\n return undefined; // Only relevant on first mount with animateOnMount\n }\n if (role === \"active\" && depth > 0) {\n // New active panel (not root): start from off-screen right\n return containerSize;\n }\n // Root panel or other roles: start at their natural position\n return undefined;\n }, [isFirstMount, animateOnMount, role, depth, containerSize]);\n\n // Use shared transform hook for DOM manipulation\n const { isAnimating } = useSwipeContentTransform({\n elementRef,\n targetPx,\n displacement: panelDisplacement,\n isSwiping,\n axis,\n animationDuration,\n containerSize,\n // Animate when targetPx changes (button navigation)\n animateOnTargetChange: true,\n // For push animation: start from off-screen\n initialPx,\n });\n\n // Compute visibility\n const visible = computeSwipeVisibility({\n depth,\n navigationDepth,\n isActive,\n isSwiping,\n isAnimating,\n });\n\n // Compute swipe progress for scale and dimming interpolation\n const swipeProgress = React.useMemo(() => {\n if (containerSize <= 0 || displacement <= 0) {\n return 0;\n }\n return Math.min(displacement / containerSize, 1);\n }, [displacement, containerSize]);\n\n // Compute scale for \"stack\" display mode\n // Behind panels are scaled down, and scale interpolates during swipe\n const scale = React.useMemo(() => {\n if (displayMode !== \"stack\") {\n return 1; // No scale for overlay/slide modes\n }\n\n const depthDiff = navigationDepth - depth;\n\n if (role === \"active\") {\n return 1; // Active panel is always at full scale\n }\n\n if (role === \"behind\") {\n // Base scale for behind panel\n const baseScale = 1 - depthDiff * STACK_SCALE_FACTOR;\n // During swipe, interpolate toward 1\n return baseScale + swipeProgress * (1 - baseScale);\n }\n\n return 1;\n }, [displayMode, role, depth, navigationDepth, swipeProgress]);\n\n // Compute dimming opacity for behind panels\n // Full dimming at rest, fades to 0 during swipe\n const dimmingOpacity = React.useMemo(() => {\n if (!showDimming || role !== \"behind\") {\n return 0;\n }\n // Fade from MAX_DIM_OPACITY to 0 as swipe progresses\n return MAX_DIM_OPACITY * (1 - swipeProgress);\n }, [showDimming, role, swipeProgress]);\n\n // Update visibility via direct DOM manipulation\n React.useLayoutEffect(() => {\n const element = elementRef.current;\n if (element) {\n element.style.visibility = visible ? \"visible\" : \"hidden\";\n }\n }, [visible]);\n\n // Update scale via direct DOM manipulation for smooth animation\n React.useLayoutEffect(() => {\n const element = elementRef.current;\n if (!element || displayMode !== \"stack\") {\n return;\n }\n\n // Get current transform (translateX) and append scale\n const currentTransform = element.style.transform;\n if (currentTransform.includes(\"translateX\")) {\n // Extract translateX value and combine with scale\n const translateMatch = currentTransform.match(/translateX\\([^)]+\\)/);\n if (translateMatch) {\n element.style.transform = `${translateMatch[0]} scale(${scale})`;\n }\n } else {\n element.style.transform = `scale(${scale})`;\n }\n }, [scale, displayMode]);\n\n // Compute shadow for active panel\n // Shadow is shown on panels at depth > 0 when they're active or animating\n const shouldShowShadow = showShadow && depth > 0 && role === \"active\";\n\n // Static style - transform is handled entirely by useSwipeContentTransform\n // to ensure smooth animations\n const staticStyle = React.useMemo<React.CSSProperties>(\n () => ({\n ...BASE_STYLE,\n pointerEvents: isActive ? \"auto\" : \"none\",\n willChange: \"transform\",\n zIndex: depth,\n visibility: visible ? \"visible\" : \"hidden\",\n boxShadow: shouldShowShadow ? ACTIVE_PANEL_SHADOW : undefined,\n }),\n [isActive, depth, visible, shouldShowShadow],\n );\n\n // Dimming overlay style for behind panels\n const dimmingStyle = React.useMemo<React.CSSProperties | null>(() => {\n if (dimmingOpacity <= 0) {\n return null;\n }\n return {\n position: \"absolute\",\n inset: 0,\n backgroundColor: `rgba(0, 0, 0, ${dimmingOpacity})`,\n pointerEvents: \"none\",\n zIndex: 1,\n };\n }, [dimmingOpacity]);\n\n return (\n <div\n ref={elementRef}\n data-stack-content={id}\n data-depth={depth}\n data-active={isActive ? \"true\" : \"false\"}\n data-role={role}\n style={staticStyle}\n >\n {children}\n {dimmingStyle != null && <div style={dimmingStyle} data-dimming-overlay />}\n </div>\n );\n },\n);\n","/**\n * @file SwipeStackOutlet component for rendering stack with swipe support.\n *\n * Uses SwipeStackContent for direct DOM manipulation during swipe gestures,\n * providing iOS-style smooth swipe-to-go-back behavior.\n */\nimport * as React from \"react\";\nimport { SwipeStackContent } from \"./SwipeStackContent.js\";\nimport type { SwipeInputState } from \"../../hooks/gesture/types.js\";\nimport type { StackPanel, StackNavigationState } from \"./types.js\";\n\nconst DEFAULT_ANIMATION_DURATION = 300;\n\n/**\n * Props for SwipeStackOutlet component.\n */\nexport type SwipeStackOutletProps = {\n /** Array of panel definitions */\n panels: ReadonlyArray<StackPanel>;\n /** Current navigation state */\n navigationState: StackNavigationState;\n /** Swipe input state from useStackSwipeInput */\n inputState: SwipeInputState;\n /** Container size in pixels (width for horizontal swipe) */\n containerSize: number;\n /** Function to get cached content for a panel */\n getCachedContent?: (panelId: string) => React.ReactNode | null;\n /** Behind panel offset ratio. @default -0.3 */\n behindOffset?: number;\n /**\n * Whether to animate new panels on mount.\n * @default false\n */\n animateOnMount?: boolean;\n /**\n * Animation duration in ms.\n * @default 300\n */\n animationDuration?: number;\n /**\n * Whether to show iOS-style edge shadow on active panels.\n * @default true\n */\n showShadow?: boolean;\n /**\n * Display mode for visual styling.\n * - \"overlay\": panels overlay, no scale (iOS style)\n * - \"slide\": panels slide with parallax\n * - \"stack\": panels scale down and offset (stacked cards style)\n * @default \"overlay\"\n */\n displayMode?: \"overlay\" | \"slide\" | \"stack\";\n /**\n * Whether to show dimming overlay on behind panels.\n * @default true\n */\n showDimming?: boolean;\n};\n\n/**\n * Get visible panels for rendering during swipe.\n *\n * Only renders active panel and immediate behind panel for performance.\n * Also includes exiting panel when navigating back.\n */\nfunction getVisiblePanels(\n panels: ReadonlyArray<StackPanel>,\n navigationState: StackNavigationState,\n exitingPanelId: string | null,\n): Array<{ panel: StackPanel; depth: number; isExiting: boolean }> {\n const { stack, depth } = navigationState;\n\n // During swipe, we only need active and behind panels\n const visibleDepths = [depth];\n if (depth > 0) {\n visibleDepths.push(depth - 1);\n }\n\n const result: Array<{ panel: StackPanel; depth: number; isExiting: boolean }> = [];\n\n // Add panels at visible depths\n for (const d of visibleDepths) {\n const id = stack[d];\n const panel = panels.find((p) => p.id === id);\n if (panel) {\n result.push({ panel, depth: d, isExiting: false });\n }\n }\n\n // Add exiting panel if it's not already included\n if (exitingPanelId != null) {\n const alreadyIncluded = result.some((r) => r.panel.id === exitingPanelId);\n if (!alreadyIncluded) {\n const exitingPanel = panels.find((p) => p.id === exitingPanelId);\n if (exitingPanel) {\n // Exiting panel is at depth + 1 (was previously active)\n result.push({ panel: exitingPanel, depth: depth + 1, isExiting: true });\n }\n }\n }\n\n // Render in order: behind first, then active, then exiting\n return result.sort((a, b) => a.depth - b.depth);\n}\n\n/**\n * SwipeStackOutlet renders stack panels with swipe gesture support.\n *\n * Unlike the default StackOutlet, this component:\n * - Uses SwipeStackContent for direct DOM manipulation\n * - Only renders active and behind panels for performance\n * - Supports iOS-style parallax reveal animation\n *\n * @example\n * ```tsx\n * const navigation = useStackNavigation({ panels, displayMode: 'overlay' });\n * const swipeInput = useStackSwipeInput({ containerRef, navigation });\n *\n * return (\n * <div ref={containerRef} {...swipeInput.containerProps}>\n * <SwipeStackOutlet\n * panels={navigation.panels}\n * navigationState={navigation.state}\n * inputState={swipeInput.inputState}\n * containerSize={containerWidth}\n * />\n * </div>\n * );\n * ```\n */\nexport const SwipeStackOutlet: React.FC<SwipeStackOutletProps> = React.memo(\n ({\n panels,\n navigationState,\n inputState,\n containerSize,\n getCachedContent,\n behindOffset,\n animateOnMount = false,\n animationDuration = DEFAULT_ANIMATION_DURATION,\n showShadow,\n displayMode,\n showDimming,\n }) => {\n // Track the exiting panel ID when navigating back\n const [exitingPanelId, setExitingPanelId] = React.useState<string | null>(null);\n const prevDepthRef = React.useRef(navigationState.depth);\n const prevStackRef = React.useRef<ReadonlyArray<string>>(navigationState.stack);\n\n // Detect when we navigate back and need to animate out\n React.useLayoutEffect(() => {\n const prevDepth = prevDepthRef.current;\n const prevStack = prevStackRef.current;\n const { depth, stack } = navigationState;\n\n // Update refs\n prevDepthRef.current = depth;\n prevStackRef.current = stack;\n\n // Check if we went back (depth decreased)\n if (depth < prevDepth) {\n // The panel at prevDepth is exiting\n const exitingId = prevStack[prevDepth];\n if (exitingId != null) {\n setExitingPanelId(exitingId);\n\n // Clear exiting panel after animation completes\n const timeoutId = setTimeout(() => {\n setExitingPanelId(null);\n }, animationDuration);\n\n return () => clearTimeout(timeoutId);\n }\n }\n }, [navigationState.depth, navigationState.stack, animationDuration]);\n\n const visiblePanels = React.useMemo(\n () => getVisiblePanels(panels, navigationState, exitingPanelId),\n [panels, navigationState, exitingPanelId],\n );\n\n const containerStyle: React.CSSProperties = React.useMemo(\n () => ({\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n }),\n [],\n );\n\n return (\n <div style={containerStyle} data-swipe-stack-container>\n {visiblePanels.map(({ panel, depth, isExiting }) => {\n const isActive = depth === navigationState.depth && !isExiting;\n const content = getCachedContent?.(panel.id) ?? panel.content;\n\n return (\n <SwipeStackContent\n key={panel.id}\n id={panel.id}\n depth={depth}\n navigationDepth={navigationState.depth}\n isActive={isActive}\n inputState={inputState}\n containerSize={containerSize}\n behindOffset={behindOffset}\n animateOnMount={animateOnMount}\n animationDuration={animationDuration}\n showShadow={showShadow}\n displayMode={displayMode}\n showDimming={showDimming}\n >\n {content}\n </SwipeStackContent>\n );\n })}\n </div>\n );\n },\n);\n"],"names":["computeAnimationType","input","wasActive","isActive","transitionMode","computeVisibility","displayMode","depth","navigationDepth","isAnimatingOut","isRevealing","revealDepth","computeTransform","activeDepth","isPrevious","offset","scale","computeSwipeTransform","baseTransform","swipeProgress","computeTransitionCss","STACK_TRANSITION_DURATION","STACK_TRANSITION_EASING","computeStackContentState","currentAnimationType","navigationState","nextAnimationType","visibility","transform","animation","STACK_ANIMATION_PUSH","STACK_ANIMATION_POP","transition","baseStyle","StackContent","React","id","children","ref","prevActiveRef","animationType","setAnimationType","computedState","useIsomorphicLayoutEffect","handleAnimationEnd","e","style","s","content","jsx","StackOutletContext","StackOutletInner","ctx","forceUpdate","x","panels","visiblePanels","index","panel","p","useStackNavigation","options","initialPanelId","onPanelChange","stack","setStack","initialId","revealState","setRevealState","state","currentPanelId","previousPanelId","push","prev","go","direction","targetDepth","targetId","move","replace","canGo","revealParent","revealTo","revealRoot","dismissReveal","getPanelProps","panelIndex","getBackButtonProps","canGoBack","prevPanel","label","containerStyle","stateRef","subscribersRef","callback","resolveContent","panelId","validIds","getCachedContent","useContentCache","contextValue","Outlet","OutletComponent","INITIAL_STATE","createTimestampedPoint","clientX","clientY","usePointerTracking","enabled","primaryOnly","setState","reset","handlePointerDown","useEffectEvent","event","point","handlePointerMove","handlePointerUp","handlePointerCancel","shouldTrackDocument","useDocumentPointerEvents","DEFAULT_LOCK_THRESHOLD","determineAxis","deltaX","deltaY","absX","absY","useDirectionalLock","tracking","lockThreshold","lockedAxis","setLockedAxis","axis","calculateVelocity","displacement","startTime","currentTime","elapsed","determineDirection","mergeGestureContainerProps","propsArray","mergedStyle","pointerDownHandlers","props","handler","DEFAULT_SWIPE_THRESHOLDS","DEFAULT_EDGE_WIDTH","IDLE_SWIPE_INPUT_STATE","WHEEL_RESET_TIMEOUT","evaluateSwipeEnd","velocity","thresholds","onSwipeEnd","axisDisplacement","axisVelocity","absDisplacement","absVelocity","useSwipeInput","containerRef","customThresholds","enableWheel","pointerStartFilter","handleSwipeEnd","baseOnPointerDown","onPointerDown","container","isLocked","lastActiveStateRef","isLockedToSwipeAxisRef","disableTouchMove","handleTouchStart","handleTouchEnd","wheelState","setWheelState","wheelAccumulatedRef","wheelIdleTimerRef","wheelLockedRef","wheelLockedAxisRef","resetWheelState","endWheelSwipe","handleWheel","accumulated","listener","pointerState","lastState","containerProps","getAxisForEdge","edge","isInEdgeZone","edgeWidth","rect","useEdgeSwipeInput","isEdgeGesture","setIsEdgeGesture","inEdge","isInLeftEdge","useNativeGestureGuard","active","preventEdgeBack","preventOverscroll","previousHtmlOverscrollRef","applyHtmlOverscroll","html","useStackSwipeInput","navigation","swipeState","setSwipeState","inputState","swipeProps","guardProps","progress","containerWidth","easings","t","DEFAULT_DURATION","useAnimationFrame","duration","easing","onFrame","onComplete","isAnimating","setIsAnimating","rafIdRef","startTimeRef","onFrameRef","onCompleteRef","cancel","start","step","timestamp","easedProgress","isComplete","interpolate","from","to","DEFAULT_ANIMATION_DURATION","getTransformFn","useSwipeContentTransform","elementRef","targetPx","isSwiping","animationDuration","containerSize","animateOnTargetChange","initialPx","effectiveInitialPx","currentPxRef","animRef","prevTargetPxRef","prevContainerSizeRef","pendingAnimationRef","isFirstMountRef","handleFrame","element","anim","value","handleComplete","pending","currentPx","displayPx","DEFAULT_BEHIND_OFFSET","computeActiveTargetPx","computeBehindTargetPx","behindOffset","clampedDisplacement","basePosition","parallaxOffset","computeSwipeVisibility","determineSwipePanelRole","STACK_SCALE_FACTOR","MAX_DIM_OPACITY","BASE_STYLE","getAxisDisplacement","ACTIVE_PANEL_SHADOW","SwipeStackContent","animateOnMount","showShadow","showDimming","role","isFirstMount","panelDisplacement","currentPos","basePos","visible","depthDiff","baseScale","dimmingOpacity","currentTransform","translateMatch","shouldShowShadow","staticStyle","dimmingStyle","jsxs","getVisiblePanels","exitingPanelId","visibleDepths","result","d","r","exitingPanel","a","b","SwipeStackOutlet","setExitingPanelId","prevDepthRef","prevStackRef","prevDepth","prevStack","exitingId","timeoutId","isExiting"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file StickyArea component for native app-like overscroll experience.
|
|
3
|
+
*
|
|
4
|
+
* Displays cover content that expands during overscroll/bounce,
|
|
5
|
+
* providing a native app-like pull effect commonly seen in iOS apps.
|
|
6
|
+
*
|
|
7
|
+
* Supports both top (header) and bottom (footer) positions.
|
|
8
|
+
* Works with document-level scroll only.
|
|
9
|
+
*/
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import type { StickyAreaProps } from "./types";
|
|
12
|
+
/**
|
|
13
|
+
* StickyArea provides a native app-like overscroll experience.
|
|
14
|
+
*
|
|
15
|
+
* When the user overscrolls (pulls beyond the edge), the cover content
|
|
16
|
+
* expands to fill the visible area, similar to native iOS/Android app behavior.
|
|
17
|
+
*
|
|
18
|
+
* Supports both top (header) and bottom (footer) positions.
|
|
19
|
+
* Works with document-level scroll only.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // Header (top) - expands on pull-down
|
|
24
|
+
* <StickyArea position="top" cover={<img src="/hero.jpg" />}>
|
|
25
|
+
* <header><h1>My App</h1></header>
|
|
26
|
+
* </StickyArea>
|
|
27
|
+
*
|
|
28
|
+
* // Footer (bottom) - expands on pull-up
|
|
29
|
+
* <StickyArea position="bottom" cover={<div className="footer-bg" />}>
|
|
30
|
+
* <footer>Footer content</footer>
|
|
31
|
+
* </StickyArea>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare const StickyArea: React.FC<StickyAreaProps>;
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated Use StickyArea with position="top" instead
|
|
37
|
+
*/
|
|
38
|
+
export declare const StickyHeader: React.FC<StickyAreaProps>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file
|
|
2
|
+
* @file StickyArea entry point - Native app-like overscroll experience
|
|
3
3
|
*
|
|
4
|
-
* Import via: import {
|
|
4
|
+
* Import via: import { StickyArea } from "react-panel-layout/sticky-header";
|
|
5
5
|
*/
|
|
6
|
-
export { StickyHeader } from "./
|
|
7
|
-
export type { StickyHeaderProps, StickyHeaderState } from "./types";
|
|
6
|
+
export { StickyArea, StickyHeader } from "./StickyArea";
|
|
7
|
+
export type { StickyAreaPosition, StickyAreaProps, StickyAreaState, StickyHeaderProps, StickyHeaderState, } from "./types";
|
|
@@ -1,50 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @file Type definitions for the
|
|
2
|
+
* @file Type definitions for the StickyArea component.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* StickyArea provides a native app-like experience for SPAs/PWAs
|
|
5
5
|
* by displaying cover content during overscroll/bounce.
|
|
6
|
+
* Supports both top (header) and bottom (footer) positions.
|
|
7
|
+
* Works with document-level scroll only.
|
|
6
8
|
*/
|
|
7
9
|
import type * as React from "react";
|
|
8
10
|
/**
|
|
9
|
-
*
|
|
11
|
+
* Position of the sticky area.
|
|
12
|
+
* - "top": Header behavior - sticks to top, expands on pull-down overscroll
|
|
13
|
+
* - "bottom": Footer behavior - sticks to bottom, expands on pull-up overscroll
|
|
10
14
|
*/
|
|
11
|
-
export type
|
|
15
|
+
export type StickyAreaPosition = "top" | "bottom";
|
|
16
|
+
/**
|
|
17
|
+
* State information exposed by StickyArea.
|
|
18
|
+
*/
|
|
19
|
+
export type StickyAreaState = {
|
|
12
20
|
/**
|
|
13
|
-
* Whether the
|
|
14
|
-
*
|
|
21
|
+
* Whether the area is stuck at its edge of the viewport.
|
|
22
|
+
* - For "top": True when scrolled past the top edge
|
|
23
|
+
* - For "bottom": True when scrolled past the bottom edge
|
|
15
24
|
*/
|
|
16
25
|
isStuck: boolean;
|
|
17
26
|
/**
|
|
18
|
-
* The current scroll offset relative to the
|
|
19
|
-
*
|
|
20
|
-
* - Zero: Header is at the top
|
|
21
|
-
* - Negative: Overscroll/bounce (pulling down beyond top)
|
|
27
|
+
* The current scroll offset relative to the area.
|
|
28
|
+
* Positive when the area has scrolled past its edge.
|
|
22
29
|
*/
|
|
23
30
|
scrollOffset: number;
|
|
24
|
-
/**
|
|
25
|
-
* The detected scroll container type.
|
|
26
|
-
* - "document": Using window/document scroll
|
|
27
|
-
* - "container": Using a nested overflow:scroll/auto element
|
|
28
|
-
*/
|
|
29
|
-
containerType: "document" | "container";
|
|
30
31
|
};
|
|
31
32
|
/**
|
|
32
|
-
* Props for the
|
|
33
|
+
* Props for the StickyArea component.
|
|
33
34
|
*/
|
|
34
|
-
export type
|
|
35
|
+
export type StickyAreaProps = {
|
|
36
|
+
/**
|
|
37
|
+
* Position of the sticky area.
|
|
38
|
+
* - "top": Header behavior (default)
|
|
39
|
+
* - "bottom": Footer behavior
|
|
40
|
+
*
|
|
41
|
+
* @default "top"
|
|
42
|
+
*/
|
|
43
|
+
position?: StickyAreaPosition;
|
|
35
44
|
/**
|
|
36
|
-
* Cover content displayed behind the
|
|
45
|
+
* Cover content displayed behind the area.
|
|
37
46
|
* This content expands during overscroll/bounce to create
|
|
38
47
|
* a native app-like pull effect.
|
|
39
48
|
*/
|
|
40
49
|
cover: React.ReactNode;
|
|
41
50
|
/**
|
|
42
|
-
* Main
|
|
51
|
+
* Main content that scrolls over the cover.
|
|
43
52
|
* Can be a ReactNode or a render function receiving state.
|
|
44
53
|
*/
|
|
45
|
-
children: React.ReactNode | ((state:
|
|
54
|
+
children: React.ReactNode | ((state: StickyAreaState) => React.ReactNode);
|
|
46
55
|
/**
|
|
47
56
|
* Callback fired when sticky state changes.
|
|
48
57
|
*/
|
|
49
|
-
onStateChange?: (state:
|
|
58
|
+
onStateChange?: (state: StickyAreaState) => void;
|
|
50
59
|
};
|
|
60
|
+
/** @deprecated Use StickyAreaState instead */
|
|
61
|
+
export type StickyHeaderState = StickyAreaState;
|
|
62
|
+
/** @deprecated Use StickyAreaProps instead */
|
|
63
|
+
export type StickyHeaderProps = StickyAreaProps;
|
package/dist/sticky-header.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("react/jsx-runtime"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("react/jsx-runtime"),H=require("react"),M=require("./useIsomorphicLayoutEffect-DGRNF4Lf.cjs");function $(e){const c=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const a=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(c,r,a.get?a:{enumerable:!0,get:()=>e[r]})}}return c.default=e,Object.freeze(c)}const f=$(H);function q(e){return{position:"relative",paddingTop:e==="top"?"env(safe-area-inset-top)":void 0,paddingBottom:e==="bottom"?"env(safe-area-inset-bottom)":void 0,boxSizing:"border-box"}}const F={zIndex:1};function I(e){return{opacity:0,zIndex:0,userSelect:"none",pointerEvents:"none",transition:"none",willChange:"height, opacity",transform:"translateZ(0)",position:"fixed",top:e==="top"?0:void 0,bottom:e==="bottom"?0:void 0,left:0,right:0}}const z={position:"relative"},b=({position:e="top",cover:c,children:r,onStateChange:a})=>{const S=f.useRef(null),x=f.useRef(null),[k,O]=f.useState({isStuck:!1,scrollOffset:0}),R=f.useRef(k),v=f.useCallback(s=>{const o=R.current;(o.isStuck!==s.isStuck||o.scrollOffset!==s.scrollOffset)&&(R.current=s,O(s),a?.(s))},[a]);M.useIsomorphicLayoutEffect(()=>{const s=S.current,o=x.current;if(!o||!s)return;let u=Number.NaN,d=Number.NaN,p=Number.NaN,h=!1,y=!0;const w=()=>{const t=s.getBoundingClientRect(),g=window.innerHeight;if(e==="top"){const n=Math.max(0,t.height+t.top);n!==u&&(o.style.opacity=n>0?"1":"0",o.style.height=`${n}px`,u=n),(t.left!==d||t.width!==p)&&(o.style.left=`${t.left}px`,o.style.width=`${t.width}px`,d=t.left,p=t.width);const i=t.top<0,l=Math.max(0,-t.top);(y?!0:i!==h)&&(y=!1,h=i,v({isStuck:i,scrollOffset:l}))}else{const n=g-t.bottom,i=Math.max(0,t.height+n);i!==u&&(o.style.opacity=i>0?"1":"0",o.style.height=`${i}px`,u=i),(t.left!==d||t.width!==p)&&(o.style.left=`${t.left}px`,o.style.width=`${t.width}px`,d=t.left,p=t.width);const l=t.bottom>g,N=Math.max(0,t.bottom-g);(y?!0:l!==h)&&(y=!1,h=l,v({isStuck:l,scrollOffset:N}))}};let A=requestAnimationFrame(function t(){w(),A=requestAnimationFrame(t)});return()=>{cancelAnimationFrame(A)}},[e,v]);const j=typeof r=="function"?r(k):r;return m.jsxs("div",{style:z,children:[m.jsx("div",{ref:x,style:I(e),children:c}),m.jsx("div",{ref:S,style:q(e),children:m.jsx("div",{style:F,children:j})})]})};b.displayName="StickyArea";const C=b;exports.StickyArea=b;exports.StickyHeader=C;
|
|
2
2
|
//# sourceMappingURL=sticky-header.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sticky-header.cjs","sources":["../src/hooks/useContainerScroll.ts","../src/hooks/useResizeObserver.tsx","../src/hooks/useScrollContainer.ts","../src/sticky-header/StickyHeader.tsx"],"sourcesContent":["/**\n * @file useContainerScroll - Track scroll position of a container or document\n *\n * Tracks scroll position for either a specific scroll container element\n * or the document (when container is null).\n */\nimport * as React from \"react\";\n\nexport type ScrollPosition = {\n /** Vertical scroll position */\n scrollTop: number;\n /** Horizontal scroll position */\n scrollLeft: number;\n};\n\n/**\n * Track scroll position of a container element or the document.\n *\n * @param container - Scroll container element, or null for document scroll\n * @returns Current scroll position\n *\n * @example\n * ```tsx\n * // For document scroll\n * const scroll = useContainerScroll(null);\n *\n * // For container scroll\n * const containerRef = useRef<HTMLDivElement>(null);\n * const scroll = useContainerScroll(containerRef.current);\n * ```\n */\nexport function useContainerScroll(container: HTMLElement | null): ScrollPosition {\n const [position, setPosition] = React.useState<ScrollPosition>(() => {\n if (typeof window === \"undefined\") {\n return { scrollTop: 0, scrollLeft: 0 };\n }\n\n if (container) {\n return {\n scrollTop: container.scrollTop,\n scrollLeft: container.scrollLeft,\n };\n }\n\n return {\n scrollTop: window.scrollY,\n scrollLeft: window.scrollX,\n };\n });\n\n React.useEffect(() => {\n const handleScroll = () => {\n if (container) {\n setPosition({\n scrollTop: container.scrollTop,\n scrollLeft: container.scrollLeft,\n });\n } else {\n setPosition({\n scrollTop: window.scrollY,\n scrollLeft: window.scrollX,\n });\n }\n };\n\n // Initialize position\n handleScroll();\n\n const target = container ?? window;\n target.addEventListener(\"scroll\", handleScroll, { passive: true });\n\n return () => {\n target.removeEventListener(\"scroll\", handleScroll);\n };\n }, [container]);\n\n return position;\n}\n","/**\n * @file Shared useResizeObserver hook with cached observer instances.\n */\nimport * as React from \"react\";\n\ntype Unobserve = () => void;\ntype Callback = (entry: ResizeObserverEntry, observer: ResizeObserver) => void;\ntype SharedObserver = {\n observe: (target: Element, callback: Callback) => Unobserve;\n};\nconst observerCache = new Map<string, SharedObserver>();\nconst getSharedObserver = (options: ResizeObserverOptions) => {\n const { box = \"content-box\" } = options;\n const observerKey = `resize-box:${box}`;\n const cached = observerCache.get(observerKey);\n if (cached) {\n return cached;\n }\n const observer = new (class {\n #callbackMap = new Map<Element, Callback>();\n #resizeObserver = new ResizeObserver((entries, observer) => {\n entries.forEach((entry) => {\n const callback = this.#callbackMap.get(entry.target);\n if (callback) {\n callback(entry, observer);\n }\n });\n });\n observe(target: Element, callback: Callback) {\n this.#callbackMap.set(target, callback);\n this.#resizeObserver.observe(target, options);\n return () => {\n this.#callbackMap.delete(target);\n this.#resizeObserver.unobserve(target);\n };\n }\n })();\n observerCache.set(observerKey, observer);\n\n return observer;\n};\n/**\n * Observe size changes for a given element reference using shared resize observers.\n *\n * @param ref - Ref holding the element whose size to monitor.\n * @param options - Resize observer configuration.\n * @returns Latest resize entry and a derived DOMRect snapshot.\n */\nexport function useResizeObserver<T extends HTMLElement>(\n ref: React.RefObject<T | null>,\n { box }: ResizeObserverOptions,\n) {\n const [entry, setEntry] = React.useState<ResizeObserverEntry | null>(null);\n const target = ref.current;\n\n React.useEffect(() => {\n if (!target) {\n return;\n }\n\n const observer = getSharedObserver({ box });\n return observer.observe(target, (nextEntry) => {\n setEntry(nextEntry);\n });\n }, [box, target]);\n\n const rect = React.useMemo(() => {\n if (!entry) {\n return null;\n }\n\n if (entry.borderBoxSize?.length > 0) {\n const size = entry.borderBoxSize[0];\n return new DOMRect(0, 0, size.inlineSize, size.blockSize);\n }\n\n return entry.contentRect;\n }, [entry]);\n\n return { entry, rect };\n}\n","/**\n * @file useScrollContainer - Detect the nearest scrollable ancestor\n *\n * Traverses the DOM tree to find the nearest ancestor with overflow: scroll/auto.\n * Returns null if the document is the scroll container.\n */\nimport * as React from \"react\";\n\n/**\n * Check if an element is a scroll container.\n */\nfunction isScrollContainer(element: Element): boolean {\n const style = getComputedStyle(element);\n const overflowY = style.overflowY;\n const overflowX = style.overflowX;\n\n return (\n overflowY === \"scroll\" ||\n overflowY === \"auto\" ||\n overflowX === \"scroll\" ||\n overflowX === \"auto\"\n );\n}\n\n/**\n * Find the nearest scrollable ancestor of an element.\n *\n * @param element - Starting element to search from\n * @returns The nearest scrollable ancestor, or null if document is the container\n */\nfunction findScrollContainer(element: Element | null): HTMLElement | null {\n if (!element) {\n return null;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- DOM traversal requires let\n let current = element.parentElement;\n\n while (current) {\n if (isScrollContainer(current)) {\n return current;\n }\n current = current.parentElement;\n }\n\n return null;\n}\n\n/**\n * Hook to detect the nearest scrollable ancestor of a ref element.\n *\n * @param ref - Ref to the element to find scroll container for\n * @returns The nearest scrollable ancestor element, or null if document is the container\n *\n * @example\n * ```tsx\n * const elementRef = useRef<HTMLDivElement>(null);\n * const scrollContainer = useScrollContainer(elementRef);\n * // scrollContainer is HTMLElement if in nested scroll, null if document scroll\n * ```\n */\nexport function useScrollContainer<T extends HTMLElement>(\n ref: React.RefObject<T | null>,\n): HTMLElement | null {\n const [container, setContainer] = React.useState<HTMLElement | null>(null);\n\n React.useEffect(() => {\n const element = ref.current;\n if (!element) {\n setContainer(null);\n return;\n }\n\n const scrollContainer = findScrollContainer(element);\n setContainer(scrollContainer);\n }, [ref]);\n\n return container;\n}\n","/**\n * @file StickyHeader component for native app-like overscroll experience.\n *\n * Displays cover content that expands during overscroll/bounce,\n * providing a native app-like pull effect commonly seen in iOS apps.\n *\n * This component is designed for SPAs, PWAs, and hybrid apps where\n * the browser's pull-to-refresh is disabled and bounce effects\n * should display content rather than empty space.\n *\n * Supports both document-level scroll and nested scroll containers.\n */\nimport * as React from \"react\";\nimport { useContainerScroll } from \"../hooks/useContainerScroll\";\nimport { useIsomorphicLayoutEffect } from \"../hooks/useIsomorphicLayoutEffect\";\nimport { useResizeObserver } from \"../hooks/useResizeObserver\";\nimport { useScrollContainer } from \"../hooks/useScrollContainer\";\nimport type { StickyHeaderProps, StickyHeaderState } from \"./types\";\n\nconst baseStyle: React.CSSProperties = {\n position: \"relative\",\n};\n\nconst headerStyle: React.CSSProperties = {\n position: \"relative\",\n paddingTop: \"env(safe-area-inset-top)\",\n boxSizing: \"border-box\",\n};\n\nconst bodyStyle: React.CSSProperties = {\n zIndex: 1,\n};\n\n/**\n * Get cover styles based on container type.\n */\nfunction getCoverStyle(isDocumentScroll: boolean): React.CSSProperties {\n if (isDocumentScroll) {\n return {\n position: \"fixed\",\n top: 0,\n left: 0,\n right: 0,\n opacity: 0,\n zIndex: 0,\n userSelect: \"none\",\n pointerEvents: \"none\",\n };\n }\n\n // For nested scroll containers, use absolute positioning\n return {\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n opacity: 0,\n zIndex: 0,\n userSelect: \"none\",\n pointerEvents: \"none\",\n };\n}\n\n/**\n * Get wrapper styles for nested scroll containers.\n */\nfunction getWrapperStyle(isDocumentScroll: boolean): React.CSSProperties {\n if (isDocumentScroll) {\n return baseStyle;\n }\n\n return {\n ...baseStyle,\n overflow: \"hidden\",\n };\n}\n\n/**\n * StickyHeader provides a native app-like overscroll experience.\n *\n * When the user pulls down beyond the top of the page (overscroll/bounce),\n * the cover content expands to fill the visible area, similar to\n * native iOS/Android app behavior.\n *\n * Also supports nested scroll containers with overflow: scroll/auto.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <StickyHeader cover={<img src=\"/hero.jpg\" alt=\"Hero\" />}>\n * <header>\n * <h1>My App</h1>\n * </header>\n * </StickyHeader>\n *\n * // With state callback\n * <StickyHeader\n * cover={<img src=\"/hero.jpg\" alt=\"Hero\" />}\n * onStateChange={({ isStuck }) => console.log('Stuck:', isStuck)}\n * >\n * <header>\n * <h1>My App</h1>\n * </header>\n * </StickyHeader>\n *\n * // With render function\n * <StickyHeader cover={<img src=\"/hero.jpg\" alt=\"Hero\" />}>\n * {({ isStuck }) => (\n * <header style={{ background: isStuck ? 'white' : 'transparent' }}>\n * <h1>My App</h1>\n * </header>\n * )}\n * </StickyHeader>\n * ```\n */\nexport const StickyHeader: React.FC<StickyHeaderProps> = ({ cover, children, onStateChange }) => {\n const headerRef = React.useRef<HTMLDivElement>(null);\n const coverAreaRef = React.useRef<HTMLDivElement>(null);\n const wrapperRef = React.useRef<HTMLDivElement>(null);\n\n // Detect scroll container\n const scrollContainer = useScrollContainer(wrapperRef);\n const isDocumentScroll = scrollContainer === null;\n\n // Track scroll position\n const { scrollTop } = useContainerScroll(scrollContainer);\n const scrollTopRef = React.useRef(scrollTop);\n scrollTopRef.current = scrollTop;\n\n // Track header bounds\n const headerBoundRef = React.useRef<DOMRectReadOnly | null>(null);\n const { rect: headerRect } = useResizeObserver(headerRef, {});\n if (!Object.is(headerBoundRef.current, headerRect)) {\n headerBoundRef.current = headerRect;\n }\n\n // Track container bounds for nested scroll\n const containerBoundRef = React.useRef<DOMRect | null>(null);\n\n // State for render function and callback\n const [state, setState] = React.useState<StickyHeaderState>({\n isStuck: false,\n scrollOffset: 0,\n containerType: \"document\",\n });\n\n // Update state when values change\n const stateRef = React.useRef(state);\n const updateState = React.useCallback(\n (newState: StickyHeaderState) => {\n const prev = stateRef.current;\n if (prev.isStuck !== newState.isStuck || prev.scrollOffset !== newState.scrollOffset || prev.containerType !== newState.containerType) {\n stateRef.current = newState;\n setState(newState);\n onStateChange?.(newState);\n }\n },\n [onStateChange],\n );\n\n useIsomorphicLayoutEffect(() => {\n const header = headerRef.current;\n const coverArea = coverAreaRef.current;\n const wrapper = wrapperRef.current;\n if (!coverArea || !header || !wrapper) {\n return;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevHeight = Number.NaN;\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevHeaderBound: DOMRectReadOnly | null = null;\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevIsStuck = false;\n\n const loop = () => {\n const headerBound = headerBoundRef.current;\n if (!headerBound) {\n return;\n }\n\n const currentScrollTop = scrollTopRef.current;\n\n if (isDocumentScroll) {\n // Document scroll mode (original behavior)\n const coverAreaHeight = headerBound.height - currentScrollTop;\n if (coverAreaHeight !== prevHeight) {\n coverArea.style.opacity = \"1\";\n coverArea.style.height = `${coverAreaHeight}px`;\n prevHeight = coverAreaHeight;\n }\n\n if ((headerBound.x >= 0 || headerBound.y >= 0 || headerBound.width > 0) && prevHeaderBound !== headerBound) {\n coverArea.style.left = `${headerBound.x}px`;\n coverArea.style.top = `${headerBound.y}px`;\n coverArea.style.width = `${headerBound.width}px`;\n prevHeaderBound = headerBound;\n }\n\n // Calculate stuck state\n const isStuck = currentScrollTop > 0;\n if (isStuck !== prevIsStuck) {\n prevIsStuck = isStuck;\n updateState({\n isStuck,\n scrollOffset: currentScrollTop,\n containerType: \"document\",\n });\n }\n } else {\n // Nested container scroll mode\n const container = scrollContainer;\n if (!container) {\n return;\n }\n\n // Get container bounds\n const containerBound = container.getBoundingClientRect();\n containerBoundRef.current = containerBound;\n\n // Calculate header position relative to container\n const headerTop = header.getBoundingClientRect().top - containerBound.top + currentScrollTop;\n\n // Calculate cover height based on scroll position\n const scrollOffset = currentScrollTop - headerTop;\n const coverAreaHeight = Math.max(0, headerBound.height - scrollOffset);\n\n if (coverAreaHeight !== prevHeight) {\n coverArea.style.opacity = \"1\";\n coverArea.style.height = `${coverAreaHeight}px`;\n prevHeight = coverAreaHeight;\n }\n\n // Position cover relative to scroll\n if (prevHeaderBound !== headerBound) {\n coverArea.style.left = \"0\";\n coverArea.style.width = `${headerBound.width}px`;\n prevHeaderBound = headerBound;\n }\n\n // Calculate top position to follow scroll\n const coverTop = Math.max(0, scrollOffset);\n coverArea.style.top = `${coverTop}px`;\n\n // Calculate stuck state\n const isStuck = scrollOffset > 0;\n if (isStuck !== prevIsStuck) {\n prevIsStuck = isStuck;\n updateState({\n isStuck,\n scrollOffset,\n containerType: \"container\",\n });\n }\n }\n };\n\n // eslint-disable-next-line no-restricted-syntax -- Performance: RAF id needs reassignment\n let id = requestAnimationFrame(function animate() {\n loop();\n id = requestAnimationFrame(animate);\n });\n\n return () => {\n cancelAnimationFrame(id);\n };\n }, [isDocumentScroll, scrollContainer, updateState]);\n\n // Render children\n const renderedChildren = typeof children === \"function\" ? children(state) : children;\n\n return (\n <div ref={wrapperRef} style={getWrapperStyle(isDocumentScroll)}>\n <div ref={coverAreaRef} style={getCoverStyle(isDocumentScroll)}>\n {cover}\n </div>\n <div ref={headerRef} style={headerStyle}>\n <div style={bodyStyle}>{renderedChildren}</div>\n </div>\n </div>\n );\n};\n\nStickyHeader.displayName = \"StickyHeader\";\n"],"names":["useContainerScroll","container","position","setPosition","React","handleScroll","target","observerCache","getSharedObserver","options","box","observerKey","cached","observer","#callbackMap","#resizeObserver","entries","entry","callback","useResizeObserver","ref","setEntry","nextEntry","rect","size","isScrollContainer","element","style","overflowY","overflowX","findScrollContainer","current","useScrollContainer","setContainer","scrollContainer","baseStyle","headerStyle","bodyStyle","getCoverStyle","isDocumentScroll","getWrapperStyle","StickyHeader","cover","children","onStateChange","headerRef","coverAreaRef","wrapperRef","scrollTop","scrollTopRef","headerBoundRef","headerRect","containerBoundRef","state","setState","stateRef","updateState","newState","prev","useIsomorphicLayoutEffect","header","coverArea","wrapper","prevHeight","prevHeaderBound","prevIsStuck","loop","headerBound","currentScrollTop","coverAreaHeight","isStuck","containerBound","headerTop","scrollOffset","coverTop","id","animate","renderedChildren","jsx"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sticky-header.cjs","sources":["../src/sticky-header/StickyArea.tsx"],"sourcesContent":["/**\n * @file StickyArea component for native app-like overscroll experience.\n *\n * Displays cover content that expands during overscroll/bounce,\n * providing a native app-like pull effect commonly seen in iOS apps.\n *\n * Supports both top (header) and bottom (footer) positions.\n * Works with document-level scroll only.\n */\nimport * as React from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../hooks/useIsomorphicLayoutEffect\";\nimport type { StickyAreaProps, StickyAreaState } from \"./types\";\n\n/**\n * Get area styles based on position.\n */\nfunction getAreaStyle(position: \"top\" | \"bottom\"): React.CSSProperties {\n return {\n position: \"relative\",\n paddingTop: position === \"top\" ? \"env(safe-area-inset-top)\" : undefined,\n paddingBottom: position === \"bottom\" ? \"env(safe-area-inset-bottom)\" : undefined,\n boxSizing: \"border-box\",\n };\n}\n\nconst bodyStyle: React.CSSProperties = {\n zIndex: 1,\n};\n\n/**\n * Get cover styles based on position.\n * Explicitly disables transitions to prevent jank from inherited CSS.\n */\nfunction getCoverStyle(position: \"top\" | \"bottom\"): React.CSSProperties {\n return {\n opacity: 0,\n zIndex: 0,\n userSelect: \"none\",\n pointerEvents: \"none\",\n // Disable transitions to prevent jank - styles are updated via RAF\n transition: \"none\",\n // Enable GPU acceleration for smoother animations\n willChange: \"height, opacity\",\n transform: \"translateZ(0)\",\n position: \"fixed\",\n top: position === \"top\" ? 0 : undefined,\n bottom: position === \"bottom\" ? 0 : undefined,\n left: 0,\n right: 0,\n };\n}\n\nconst wrapperStyle: React.CSSProperties = {\n position: \"relative\",\n};\n\n/**\n * StickyArea provides a native app-like overscroll experience.\n *\n * When the user overscrolls (pulls beyond the edge), the cover content\n * expands to fill the visible area, similar to native iOS/Android app behavior.\n *\n * Supports both top (header) and bottom (footer) positions.\n * Works with document-level scroll only.\n *\n * @example\n * ```tsx\n * // Header (top) - expands on pull-down\n * <StickyArea position=\"top\" cover={<img src=\"/hero.jpg\" />}>\n * <header><h1>My App</h1></header>\n * </StickyArea>\n *\n * // Footer (bottom) - expands on pull-up\n * <StickyArea position=\"bottom\" cover={<div className=\"footer-bg\" />}>\n * <footer>Footer content</footer>\n * </StickyArea>\n * ```\n */\nexport const StickyArea: React.FC<StickyAreaProps> = ({\n position = \"top\",\n cover,\n children,\n onStateChange,\n}) => {\n const areaRef = React.useRef<HTMLDivElement>(null);\n const coverAreaRef = React.useRef<HTMLDivElement>(null);\n\n // State for render function and callback\n const [state, setState] = React.useState<StickyAreaState>({\n isStuck: false,\n scrollOffset: 0,\n });\n\n // Update state when values change\n const stateRef = React.useRef(state);\n const updateState = React.useCallback(\n (newState: StickyAreaState) => {\n const prev = stateRef.current;\n if (prev.isStuck !== newState.isStuck || prev.scrollOffset !== newState.scrollOffset) {\n stateRef.current = newState;\n setState(newState);\n onStateChange?.(newState);\n }\n },\n [onStateChange],\n );\n\n useIsomorphicLayoutEffect(() => {\n const area = areaRef.current;\n const coverArea = coverAreaRef.current;\n if (!coverArea || !area) {\n return;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevHeight = Number.NaN;\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevLeft = Number.NaN;\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevWidth = Number.NaN;\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let prevIsStuck = false;\n // eslint-disable-next-line no-restricted-syntax -- Performance: mutable state for RAF loop\n let isFirstRun = true;\n\n const loop = () => {\n const liveRect = area.getBoundingClientRect();\n const viewportHeight = window.innerHeight;\n\n if (position === \"top\") {\n // TOP: Cover expands upward during pull-down overscroll\n // liveRect.top > 0 means element moved down (overscroll)\n // liveRect.top < 0 means element scrolled up\n const coverAreaHeight = Math.max(0, liveRect.height + liveRect.top);\n\n if (coverAreaHeight !== prevHeight) {\n coverArea.style.opacity = coverAreaHeight > 0 ? \"1\" : \"0\";\n coverArea.style.height = `${coverAreaHeight}px`;\n prevHeight = coverAreaHeight;\n }\n\n if (liveRect.left !== prevLeft || liveRect.width !== prevWidth) {\n coverArea.style.left = `${liveRect.left}px`;\n coverArea.style.width = `${liveRect.width}px`;\n prevLeft = liveRect.left;\n prevWidth = liveRect.width;\n }\n\n const isStuck = liveRect.top < 0;\n const scrollOffset = Math.max(0, -liveRect.top);\n\n const shouldUpdateState = isFirstRun ? true : isStuck !== prevIsStuck;\n if (shouldUpdateState) {\n isFirstRun = false;\n prevIsStuck = isStuck;\n updateState({ isStuck, scrollOffset });\n }\n } else {\n // BOTTOM: Cover expands downward during pull-up overscroll\n // liveRect.bottom < viewportHeight means element moved up (overscroll at bottom)\n // liveRect.bottom > viewportHeight means element is below viewport\n const distanceFromBottom = viewportHeight - liveRect.bottom;\n const coverAreaHeight = Math.max(0, liveRect.height + distanceFromBottom);\n\n if (coverAreaHeight !== prevHeight) {\n coverArea.style.opacity = coverAreaHeight > 0 ? \"1\" : \"0\";\n coverArea.style.height = `${coverAreaHeight}px`;\n prevHeight = coverAreaHeight;\n }\n\n if (liveRect.left !== prevLeft || liveRect.width !== prevWidth) {\n coverArea.style.left = `${liveRect.left}px`;\n coverArea.style.width = `${liveRect.width}px`;\n prevLeft = liveRect.left;\n prevWidth = liveRect.width;\n }\n\n const isStuck = liveRect.bottom > viewportHeight;\n const scrollOffset = Math.max(0, liveRect.bottom - viewportHeight);\n\n const shouldUpdateState = isFirstRun ? true : isStuck !== prevIsStuck;\n if (shouldUpdateState) {\n isFirstRun = false;\n prevIsStuck = isStuck;\n updateState({ isStuck, scrollOffset });\n }\n }\n };\n\n // eslint-disable-next-line no-restricted-syntax -- Performance: RAF id needs reassignment\n let id = requestAnimationFrame(function animate() {\n loop();\n id = requestAnimationFrame(animate);\n });\n\n return () => {\n cancelAnimationFrame(id);\n };\n }, [position, updateState]);\n\n // Render children\n const renderedChildren = typeof children === \"function\" ? children(state) : children;\n\n return (\n <div style={wrapperStyle}>\n <div ref={coverAreaRef} style={getCoverStyle(position)}>\n {cover}\n </div>\n <div ref={areaRef} style={getAreaStyle(position)}>\n <div style={bodyStyle}>{renderedChildren}</div>\n </div>\n </div>\n );\n};\n\nStickyArea.displayName = \"StickyArea\";\n\n/**\n * @deprecated Use StickyArea with position=\"top\" instead\n */\nexport const StickyHeader = StickyArea;\n"],"names":["getAreaStyle","position","bodyStyle","getCoverStyle","wrapperStyle","StickyArea","cover","children","onStateChange","areaRef","React","coverAreaRef","state","setState","stateRef","updateState","newState","prev","useIsomorphicLayoutEffect","area","coverArea","prevHeight","prevLeft","prevWidth","prevIsStuck","isFirstRun","loop","liveRect","viewportHeight","coverAreaHeight","isStuck","scrollOffset","distanceFromBottom","id","animate","renderedChildren","jsxs","jsx","StickyHeader"],"mappings":"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"}
|