react-panel-layout 0.5.2 → 0.6.1
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-lLg-Lpg7.js → FloatingPanelFrame-3eU9AwPo.js} +11 -11
- package/dist/{FloatingPanelFrame-lLg-Lpg7.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
- package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
- package/dist/{FloatingPanelFrame-D9Cp2al1.cjs.map → FloatingPanelFrame-CEmXDvUA.cjs.map} +1 -1
- package/dist/FloatingWindow-CUXnEtrb.js +827 -0
- package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
- package/dist/GridLayout-DKTg_N61.cjs +2 -0
- package/dist/GridLayout-DKTg_N61.cjs.map +1 -0
- package/dist/GridLayout-UWNxXw77.js +926 -0
- package/dist/GridLayout-UWNxXw77.js.map +1 -0
- package/dist/HorizontalDivider-DdxzfV0l.js +30 -0
- package/dist/HorizontalDivider-DdxzfV0l.js.map +1 -0
- package/dist/HorizontalDivider-_pgV4Mcv.cjs +2 -0
- package/dist/HorizontalDivider-_pgV4Mcv.cjs.map +1 -0
- package/dist/PanelSystem-BqUzNtf2.js +1946 -0
- package/dist/PanelSystem-BqUzNtf2.js.map +1 -0
- package/dist/PanelSystem-D603LKKv.cjs +3 -0
- package/dist/PanelSystem-D603LKKv.cjs.map +1 -0
- package/dist/ResizeHandle-CBcAS918.cjs +2 -0
- package/dist/ResizeHandle-CBcAS918.cjs.map +1 -0
- package/dist/ResizeHandle-CXjc1meV.js +119 -0
- package/dist/ResizeHandle-CXjc1meV.js.map +1 -0
- package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
- package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
- package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
- package/dist/components/window/Drawer.d.ts +3 -1
- package/dist/components/window/DrawerLayers.d.ts +1 -1
- package/dist/components/window/drawerStyles.d.ts +69 -0
- package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
- package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -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 +35 -4
- package/dist/dialog/index.d.ts +69 -0
- 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 +117 -0
- package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
- package/dist/hooks/gesture/types.d.ts +297 -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 +59 -0
- package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
- package/dist/hooks/useAnimationFrame.d.ts +86 -0
- package/dist/hooks/useOperationContinuity.d.ts +64 -0
- package/dist/hooks/useResizeObserver.d.ts +33 -1
- package/dist/hooks/useSharedElementTransition.d.ts +112 -0
- package/dist/hooks/useSnapAnimation.d.ts +54 -0
- package/dist/hooks/useSwipeContentTransform.d.ts +86 -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/dialog/AlertDialog.d.ts +9 -0
- package/dist/modules/dialog/DialogContainer.d.ts +37 -0
- package/dist/modules/dialog/Modal.d.ts +26 -0
- package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
- package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
- package/dist/modules/dialog/types.d.ts +183 -0
- package/dist/modules/dialog/useDialog.d.ts +39 -0
- package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
- package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
- package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
- package/dist/modules/drawer/types.d.ts +74 -0
- package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
- 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 +92 -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 +67 -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 +66 -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 +721 -0
- package/dist/stack.js.map +1 -0
- package/dist/sticky-header/StickyArea.d.ts +38 -0
- package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -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 +73 -174
- package/dist/sticky-header.js.map +1 -1
- package/dist/styles-NkjuMOVS.js +57 -0
- package/dist/styles-NkjuMOVS.js.map +1 -0
- package/dist/styles-qf6ptVLD.cjs +2 -0
- package/dist/styles-qf6ptVLD.cjs.map +1 -0
- package/dist/types.d.ts +16 -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-DXxw3qWj.js +54 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.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/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
- package/dist/window/index.d.ts +63 -0
- package/dist/window.cjs +2 -0
- package/dist/window.cjs.map +1 -0
- package/dist/window.js +160 -0
- package/dist/window.js.map +1 -0
- package/docs/design-tokens.md +405 -0
- package/package.json +34 -4
- package/src/PanelSystemContext.tsx +88 -0
- package/src/components/gesture/SwipeSafeZone.tsx +69 -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 +369 -0
- package/src/components/window/DrawerLayers.tsx +68 -0
- package/src/components/window/FloatingWindow.tsx +95 -0
- package/src/components/window/PopupLayerPortal.tsx +218 -0
- package/src/components/window/drawerStyles.spec.ts +263 -0
- package/src/components/window/drawerStyles.ts +228 -0
- package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
- package/src/components/window/drawerSwipeConfig.ts +112 -0
- package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
- package/src/components/window/useDrawerSwipeTransform.ts +129 -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 +280 -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/Dialog/alerts/index.tsx +22 -0
- package/src/demo/pages/Dialog/card/index.tsx +22 -0
- package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
- package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
- package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
- package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
- package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
- package/src/demo/pages/Dialog/modal/index.tsx +17 -0
- package/src/demo/pages/Dialog/swipe/index.tsx +22 -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/components/DrawerSwipe.module.css +316 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
- package/src/demo/pages/Drawer/menu/index.tsx +17 -0
- package/src/demo/pages/Drawer/swipe/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 +320 -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.spec.tsx +152 -0
- package/src/demo/pages/Stack/components/StackBasics.tsx +301 -0
- package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
- package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
- package/src/demo/pages/Stack/components/StackTablet.tsx +422 -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 +214 -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/dialog/index.ts +85 -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 +241 -0
- package/src/hooks/gesture/testing/createGestureSimulator.ts +385 -0
- package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
- package/src/hooks/gesture/thresholdValue.ts +77 -0
- package/src/hooks/gesture/types.ts +367 -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 +462 -0
- package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +473 -0
- package/src/hooks/gesture/useNativeGestureGuard.ts +135 -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 +178 -0
- package/src/hooks/useAnimatedVisibility.spec.ts +277 -0
- package/src/hooks/useAnimatedVisibility.ts +172 -0
- package/src/hooks/useAnimationFrame.ts +208 -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/useOperationContinuity.spec.ts +387 -0
- package/src/hooks/useOperationContinuity.ts +135 -0
- package/src/hooks/useResizeObserver.spec.tsx +277 -0
- package/src/hooks/useResizeObserver.tsx +150 -0
- package/src/hooks/useScrollContainer.ts +73 -0
- package/src/hooks/useSharedElementTransition.ts +333 -0
- package/src/hooks/useSnapAnimation.ts +128 -0
- package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
- package/src/hooks/useSwipeContentTransform.ts +373 -0
- package/src/hooks/useTransitionState.ts +95 -0
- package/src/index.tsx +88 -0
- package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
- package/src/modules/dialog/AlertDialog.tsx +221 -0
- package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
- package/src/modules/dialog/DialogContainer.tsx +188 -0
- package/src/modules/dialog/Modal.spec.tsx +220 -0
- package/src/modules/dialog/Modal.tsx +182 -0
- package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
- package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
- package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
- package/src/modules/dialog/types.ts +186 -0
- package/src/modules/dialog/useDialog.spec.tsx +447 -0
- package/src/modules/dialog/useDialog.ts +214 -0
- package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
- package/src/modules/dialog/useDialogContainer.ts +150 -0
- package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
- package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
- package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
- package/src/modules/dialog/useDialogTransform.ts +407 -0
- package/src/modules/drawer/types.ts +102 -0
- package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
- package/src/modules/drawer/useDrawerSwipeInput.ts +399 -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 +311 -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 +171 -0
- package/src/modules/pivot/SwipePivotContent.spec.tsx +494 -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 +882 -0
- package/src/modules/pivot/SwipePivotTabBar.tsx +583 -0
- package/src/modules/pivot/index.ts +8 -0
- package/src/modules/pivot/scaleInputState.spec.ts +219 -0
- package/src/modules/pivot/scaleInputState.ts +66 -0
- package/src/modules/pivot/types.ts +139 -0
- package/src/modules/pivot/usePivot.spec.ts +635 -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 +708 -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 +1564 -0
- package/src/modules/stack/SwipeStackContent.tsx +366 -0
- package/src/modules/stack/SwipeStackOutlet.spec.tsx +250 -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/swipeTransitionContinuity.spec.tsx +1133 -0
- package/src/modules/stack/types.ts +226 -0
- package/src/modules/stack/useStackAnimationState.spec.ts +188 -0
- package/src/modules/stack/useStackAnimationState.ts +143 -0
- package/src/modules/stack/useStackNavigation.spec.ts +672 -0
- package/src/modules/stack/useStackNavigation.tsx +393 -0
- package/src/modules/stack/useStackSwipeInput.spec.ts +309 -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 +193 -0
- package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
- package/src/sticky-header/calculateStickyMetrics.ts +50 -0
- package/src/sticky-header/index.ts +18 -0
- package/src/sticky-header/types.ts +68 -0
- package/src/types.ts +341 -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 +69 -0
- package/dist/GridLayout-BQQ63eA1.cjs +0 -2
- package/dist/GridLayout-BQQ63eA1.cjs.map +0 -1
- package/dist/GridLayout-CJTKq7Mp.js +0 -1465
- package/dist/GridLayout-CJTKq7Mp.js.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-7ctin_P_.cjs +0 -2
- package/dist/usePivot-7ctin_P_.cjs.map +0 -1
- package/dist/usePivot-CgQxB8rc.js +0 -124
- package/dist/usePivot-CgQxB8rc.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/modules/stack/useStackSwipeInput.ts","../src/hooks/useAnimationFrame.ts","../src/hooks/useSwipeContentTransform.ts","../src/hooks/useOperationContinuity.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 * Navigation action types for centralized state management.\n * All navigation operations go through the reducer to avoid stale closure issues.\n */\ntype StackAction<TId extends string> =\n | { type: \"push\"; id: TId }\n | { type: \"go\"; direction: number }\n | { type: \"move\"; targetDepth: number }\n | { type: \"replace\"; id: TId };\n\n/**\n * Reducer for stack navigation state.\n * Centralizes all state transitions to ensure consistent behavior during rapid navigation.\n */\nfunction stackReducer<TId extends string>(\n state: ReadonlyArray<TId>,\n action: StackAction<TId>,\n): ReadonlyArray<TId> {\n switch (action.type) {\n case \"push\":\n return [...state, action.id];\n\n case \"go\": {\n if (action.direction >= 0) {\n return state;\n }\n const currentDepth = state.length - 1;\n const targetDepth = currentDepth + action.direction;\n if (targetDepth < 0) {\n return state;\n }\n return state.slice(0, targetDepth + 1);\n }\n\n case \"move\": {\n if (action.targetDepth < 0 || action.targetDepth >= state.length) {\n return state;\n }\n return state.slice(0, action.targetDepth + 1);\n }\n\n case \"replace\": {\n if (state.length === 0) {\n return [action.id];\n }\n return [...state.slice(0, -1), action.id];\n }\n }\n}\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 reducer for centralized state management\n const initialId = initialPanelId ?? (panels[0]?.id as TId);\n if (!initialId) {\n throw new Error(\"useStackNavigation: No panels provided\");\n }\n\n const [stack, dispatch] = React.useReducer(\n stackReducer<TId>,\n [initialId] as ReadonlyArray<TId>,\n );\n\n // Ref for accessing current stack in callbacks without stale closure\n const stackRef = React.useRef(stack);\n stackRef.current = stack;\n\n // Track previous stack for onPanelChange callback\n const prevStackRef = React.useRef(stack);\n React.useEffect(() => {\n const prevStack = prevStackRef.current;\n prevStackRef.current = stack;\n\n if (onPanelChange && stack !== prevStack) {\n const newDepth = stack.length - 1;\n const newPanelId = stack[newDepth];\n if (newPanelId !== undefined) {\n onPanelChange(newPanelId, newDepth);\n }\n }\n }, [stack, onPanelChange]);\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 // All navigation functions dispatch to reducer - no stale closure issues\n const push = React.useCallback((id: TId) => {\n const panel = panels.find((p) => p.id === id);\n if (!panel) {\n return;\n }\n dispatch({ type: \"push\", id });\n }, [panels]);\n\n const go = React.useCallback((direction: number) => {\n dispatch({ type: \"go\", direction });\n }, []);\n\n const move = React.useCallback((targetDepth: number) => {\n dispatch({ type: \"move\", targetDepth });\n }, []);\n\n const replace = React.useCallback((id: TId) => {\n const panel = panels.find((p) => p.id === id);\n if (!panel) {\n return;\n }\n dispatch({ type: \"replace\", id });\n }, [panels]);\n\n // canGo uses stackRef for current state\n const canGo = React.useCallback((direction: number): boolean => {\n if (direction >= 0) {\n return false;\n }\n const currentDepth = stackRef.current.length - 1;\n return currentDepth + direction >= 0;\n }, []);\n\n // Reveal functions use stackRef for current depth\n const revealParent = React.useCallback((targetDepth?: number) => {\n const currentDepth = stackRef.current.length - 1;\n const revealTo = targetDepth ?? currentDepth - 1;\n if (revealTo < 0 || revealTo >= currentDepth) {\n return;\n }\n setRevealState({ isRevealing: true, revealDepth: revealTo });\n }, []);\n\n const revealRoot = React.useCallback(() => {\n const currentDepth = stackRef.current.length - 1;\n if (currentDepth === 0) {\n return;\n }\n setRevealState({ isRevealing: true, revealDepth: 0 });\n }, []);\n\n const dismissReveal = React.useCallback(() => {\n setRevealState({ isRevealing: false, revealDepth: null });\n }, []);\n\n // getPanelProps uses stackRef for current state\n const getPanelProps = React.useCallback((id: TId): StackPanelProps => {\n const currentStack = stackRef.current;\n const panelIndex = currentStack.indexOf(id);\n const currentDepth = currentStack.length - 1;\n const isActive = panelIndex === currentDepth;\n\n return {\n \"data-stack-panel\": id,\n \"data-depth\": panelIndex,\n \"data-active\": isActive ? \"true\" : \"false\",\n \"aria-hidden\": !isActive,\n };\n }, []);\n\n // getBackButtonProps uses stackRef for current state\n const getBackButtonProps = React.useCallback((): StackBackButtonProps => {\n const currentStack = stackRef.current;\n const currentDepth = currentStack.length - 1;\n const canGoBack = currentDepth > 0;\n const prevPanelId = currentDepth > 0 ? currentStack[currentDepth - 1] : null;\n const prevPanel = prevPanelId ? panels.find((p) => p.id === prevPanelId) : 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 }, [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 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) : false, // 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\n /** Ease in expo (accelerating, for \"suck in\" effect) */\n easeInExpo: (t: number): number => {\n if (t === 0) {\n return 0;\n }\n return Math.pow(2, 10 * t - 10);\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 isOperating: 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 * Skip animation when targetPx changes.\n * Use this when the target changed during an operation (from useOperationContinuity).\n * When true, target changes will snap instead of animate.\n * @default false\n */\n skipTargetChangeAnimation?: boolean;\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 * Check if initial mount animation should be scheduled.\n * Returns animation info if conditions are met, null otherwise.\n *\n * Conditions for scheduling:\n * 1. Animation not already consumed\n * 2. containerSize is valid (> 0) - handles React effect execution order\n * 3. initialPx is provided\n * 4. initialPx differs from targetPx\n */\nconst computeInitialMountAnimation = (\n hasConsumed: boolean,\n containerSize: number | undefined,\n initialPx: number | undefined,\n targetPx: number,\n): { from: number; to: number } | null => {\n if (hasConsumed) {\n return null;\n }\n if (containerSize === undefined) {\n return null;\n }\n if (containerSize <= 0) {\n return null;\n }\n if (initialPx === undefined) {\n return null;\n }\n if (initialPx === targetPx) {\n return null;\n }\n return { from: initialPx, to: targetPx };\n};\n\n/**\n * Result type for target change handling.\n */\ntype TargetChangeResult =\n | { type: \"animate\"; animation: { from: number; to: number } }\n | { type: \"snap\"; position: number }\n | { type: \"none\" };\n\n/**\n * Compute action for target position change when not swiping.\n * Returns the appropriate action: animate, snap, or none.\n *\n * @param skipAnimation - If true, skip animation and snap directly.\n * Use this when the target changed during an operation (from useOperationContinuity).\n */\nconst computeTargetChangeAction = (\n targetPx: number,\n prevTargetPx: number,\n currentPx: number,\n isOperating: boolean,\n isAnimating: boolean,\n animateOnTargetChange: boolean,\n skipAnimation: boolean,\n): TargetChangeResult => {\n if (targetPx === prevTargetPx) {\n return { type: \"none\" };\n }\n if (isOperating) {\n return { type: \"none\" };\n }\n if (isAnimating) {\n return { type: \"none\" };\n }\n if (!animateOnTargetChange) {\n return { type: \"snap\", position: targetPx };\n }\n\n const distance = Math.abs(currentPx - targetPx);\n if (distance <= 1) {\n return { type: \"snap\", position: targetPx };\n }\n\n // Skip animation if requested (e.g., role changed during operation)\n // This prevents unwanted animations after an operation ends.\n // However, allow forward animations (currentPx < targetPx) for normal swipe-to-complete.\n // Only skip backward animations (currentPx > targetPx) which occur during over-swipe.\n if (skipAnimation) {\n // Backward direction (over-swipe): snap, don't animate backward\n if (currentPx > targetPx) {\n return { type: \"snap\", position: targetPx };\n }\n // Forward direction (normal swipe-to-complete): animate from current position\n return { type: \"animate\", animation: { from: currentPx, to: targetPx } };\n }\n\n return { type: \"animate\", animation: { from: currentPx, to: targetPx } };\n};\n\n/**\n * Check if container size change requires position snap.\n * Returns the new position to snap to, or null if no snap needed.\n */\nconst computeContainerResizeSnap = (\n containerSize: number | undefined,\n prevContainerSize: number | undefined,\n targetPx: number,\n): number | null => {\n if (containerSize === undefined) {\n return null;\n }\n if (containerSize === prevContainerSize) {\n return null;\n }\n if (containerSize <= 0) {\n return null;\n }\n return targetPx;\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 * isOperating: inputState.phase === \"swiping\",\n * });\n * ```\n */\nexport function useSwipeContentTransform(\n options: UseSwipeContentTransformOptions,\n): UseSwipeContentTransformResult {\n const {\n elementRef,\n targetPx,\n displacement,\n isOperating,\n axis = \"horizontal\",\n animationDuration = DEFAULT_ANIMATION_DURATION,\n containerSize,\n animateOnTargetChange = false,\n initialPx,\n skipTargetChangeAnimation = false,\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 // Track if initial mount animation has been consumed\n const hasConsumedInitialMountRef = React.useRef<boolean>(false);\n\n // Schedule animation on first mount if initialPx differs from targetPx.\n const initialMountAnimation = computeInitialMountAnimation(\n hasConsumedInitialMountRef.current,\n containerSize,\n initialPx,\n targetPx,\n );\n if (initialMountAnimation !== null) {\n pendingAnimationRef.current = initialMountAnimation;\n hasConsumedInitialMountRef.current = true;\n }\n\n // Handle target changes when not swiping\n const targetChangeAction = computeTargetChangeAction(\n targetPx,\n prevTargetPxRef.current,\n currentPxRef.current,\n isOperating,\n animRef.current !== null,\n animateOnTargetChange,\n skipTargetChangeAnimation,\n );\n if (targetChangeAction.type === \"animate\") {\n pendingAnimationRef.current = targetChangeAction.animation;\n prevTargetPxRef.current = targetPx;\n } else if (targetChangeAction.type === \"snap\") {\n currentPxRef.current = targetChangeAction.position;\n prevTargetPxRef.current = targetPx;\n }\n\n // Snap when container size changes (resize)\n const resizeSnapPosition = computeContainerResizeSnap(\n containerSize,\n prevContainerSizeRef.current,\n targetPx,\n );\n if (resizeSnapPosition !== null) {\n currentPxRef.current = resizeSnapPosition;\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 (isOperating) {\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 }, [isOperating, 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) {\n return;\n }\n if (animRef.current !== null) {\n return;\n }\n if (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 Hook for maintaining value continuity during continuous operations.\n *\n * During operations like swipe gestures, external state (navigation depth, panel roles)\n * may change before the gesture ends. This hook provides a pattern to:\n * - Retain the previous value during the operation for visual continuity\n * - Accept the new value when the operation ends\n * - Track whether the value changed during the operation\n *\n * This is a core primitive for the \"operation continuity\" pattern used throughout\n * the swipe gesture system.\n */\nimport * as React from \"react\";\n\n/**\n * Result from useOperationContinuity hook.\n */\nexport type UseOperationContinuityResult<T> = {\n /** The effective value (retained during operation, current after) */\n value: T;\n /**\n * True if the value changed during the operation.\n *\n * This is useful for determining how to handle the transition when the\n * operation ends. For example, if the role changed during a swipe,\n * the target position change at operation end should snap rather than animate.\n *\n * This flag is true on the render where shouldRetainPrevious becomes false\n * (operation end), allowing consumers to handle the transition appropriately.\n * It resets to false on subsequent renders.\n */\n changedDuringOperation: boolean;\n /**\n * True on the render where the operation just ended.\n *\n * This is true when shouldRetainPrevious transitions from true to false,\n * regardless of whether the value changed. Use this to detect the moment\n * when an operation completes and delay any immediate animations.\n *\n * In the over-swipe case, this helps prevent unwanted snap-back animation\n * in the intermediate render before navigation changes.\n */\n operationJustEnded: boolean;\n};\n\n/**\n * Hook for maintaining value continuity during continuous operations.\n *\n * When an operation is in progress, this hook retains the previous value\n * to prevent sudden visual changes from state updates. Once the operation\n * ends (shouldRetainPrevious becomes false), the new value is accepted.\n *\n * Additionally, this hook tracks whether the value changed during the operation,\n * which is useful for determining animation behavior at operation end.\n *\n * IMPORTANT: This hook is designed to be idempotent during render to work\n * correctly with React StrictMode, which calls the render function twice.\n * All ref mutations happen in useLayoutEffect, not during render.\n *\n * @param value - The current value from external state\n * @param shouldRetainPrevious - Whether to retain the previous value (true during operation)\n * @returns Object with effective value and whether it changed during operation\n *\n * @example\n * ```tsx\n * // Maintain role continuity during swipe\n * const { value: effectiveRole, changedDuringOperation } = useOperationContinuity(\n * role,\n * displacement > 0,\n * );\n *\n * // Use changedDuringOperation to skip animation on operation end\n * useSwipeContentTransform({\n * // ...\n * skipTargetChangeAnimation: changedDuringOperation,\n * });\n * ```\n */\nexport function useOperationContinuity<T>(\n value: T,\n shouldRetainPrevious: boolean,\n): UseOperationContinuityResult<T> {\n // Store previous shouldRetainPrevious to detect transitions\n const prevShouldRetainRef = React.useRef(shouldRetainPrevious);\n // Store retained value (the value at the start of retention)\n const retainedValueRef = React.useRef(value);\n // Track if value changed during retention\n const changedDuringRetentionRef = React.useRef(false);\n\n // Derive operationJustEnded from transition: true → false\n // This is idempotent - safe for StrictMode double-render\n const wasRetaining = prevShouldRetainRef.current;\n const operationJustEnded = wasRetaining && !shouldRetainPrevious;\n\n // Check if value diverged from retained value\n // This includes both current-render divergence and previously-tracked divergence\n const valueDiverged = value !== retainedValueRef.current;\n const currentlyDiverged = shouldRetainPrevious && valueDiverged;\n\n // Derive changedDuringOperation\n // True if:\n // 1. Value diverged during retention (tracked from previous renders via ref)\n // 2. Value diverges right now during retention (immediate comparison)\n // 3. Value diverged at the moment retention ends\n const changedDuringRetention = changedDuringRetentionRef.current || currentlyDiverged;\n const changedAtExit = operationJustEnded && valueDiverged;\n const changedDuringOperation = changedDuringRetention || changedAtExit;\n\n // Determine effective value\n // During retention: use retained value\n // After retention ends: use current value\n const effectiveValue = shouldRetainPrevious ? retainedValueRef.current : value;\n\n // Update refs in useLayoutEffect to ensure idempotency during render.\n // This runs once per commit, not per render in StrictMode.\n React.useLayoutEffect(() => {\n if (!shouldRetainPrevious) {\n // Retention ended or never started - reset state\n changedDuringRetentionRef.current = false;\n retainedValueRef.current = value;\n } else {\n // During retention - track if value diverged\n if (currentlyDiverged) {\n changedDuringRetentionRef.current = true;\n }\n }\n prevShouldRetainRef.current = shouldRetainPrevious;\n });\n\n return {\n value: effectiveValue,\n changedDuringOperation,\n operationJustEnded,\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 isOperating: 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, isOperating, 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 (isOperating) {\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 { useOperationContinuity } from \"../../hooks/useOperationContinuity.js\";\nimport type { ContinuousOperationState, 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 * 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 *\n * This component accepts ContinuousOperationState, meaning it responds uniformly\n * to any continuous operation (whether human gesture or system animation).\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 /** Continuous operation state (from gesture input or animation system) */\n operationState: ContinuousOperationState;\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 operation state for the given axis.\n */\nconst getAxisDisplacement = (state: ContinuousOperationState, axis: GestureAxis): number => {\n if (state.phase === \"idle\") {\n return 0;\n }\n return axis === \"horizontal\" ? state.displacement.x : state.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 operationState,\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\n const displacement = getAxisDisplacement(operationState, axis);\n const isOperating = operationState.phase === \"operating\";\n\n // Determine panel role\n const role = determineSwipePanelRole(depth, navigationDepth);\n\n // Maintain role continuity during swipe operations.\n // When navigation changes before the gesture ends (e.g., role changes from\n // \"behind\" to \"active\"), we keep using the previous role for position\n // calculations to prevent visual jumps.\n // changedDuringOperation tells us if the role changed during the operation,\n // which we use to skip backward target change animation (over-swipe case).\n const { value: effectiveRole, changedDuringOperation } = useOperationContinuity(\n role,\n displacement > 0,\n );\n\n // Compute target position based on effective role\n const targetPx = React.useMemo(() => {\n switch (effectiveRole) {\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 }, [effectiveRole, 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 (effectiveRole) {\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 }, [effectiveRole, 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 // Note: useSwipeContentTransform handles first-mount tracking internally,\n // so we just declare the initial position; the hook consumes it only once.\n const initialPx = React.useMemo(() => {\n if (!animateOnMount) {\n return undefined;\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 }, [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 isOperating,\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 // Skip backward animation if role changed during the operation.\n // This handles over-swipe where panel moves beyond 100% and needs to snap back.\n // useSwipeContentTransform allows forward animations (normal swipe-to-complete)\n // but skips backward animations (over-swipe snap).\n skipTargetChangeAnimation: changedDuringOperation,\n });\n\n // Compute visibility\n const visible = computeSwipeVisibility({\n depth,\n navigationDepth,\n isActive,\n isOperating,\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\" : false;\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 /> : null}\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 { ContinuousOperationState } 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 /** Continuous operation state (from gesture input or animation system) */\n operationState: ContinuousOperationState;\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 * operationState={toContinuousOperationState(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 operationState,\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 operationState={operationState}\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","stackReducer","state","action","targetDepth","StackOutletContext","StackOutletInner","ctx","forceUpdate","x","panels","visiblePanels","index","panel","p","useStackNavigation","options","initialPanelId","onPanelChange","initialId","stack","dispatch","stackRef","prevStackRef","prevStack","newDepth","newPanelId","revealState","setRevealState","currentPanelId","previousPanelId","push","go","direction","move","replace","canGo","revealParent","currentDepth","revealTo","revealRoot","dismissReveal","getPanelProps","currentStack","panelIndex","getBackButtonProps","canGoBack","prevPanelId","prevPanel","label","containerStyle","stateRef","subscribersRef","callback","resolveContent","panelId","validIds","getCachedContent","useContentCache","contextValue","Outlet","OutletComponent","useStackSwipeInput","containerRef","navigation","edge","edgeWidth","enabled","swipeState","setSwipeState","handleSwipeEnd","isEdgeGesture","inputState","swipeProps","useEdgeSwipeInput","guardProps","useNativeGestureGuard","progress","containerWidth","displacement","absDisplacement","containerProps","mergeGestureContainerProps","IDLE_SWIPE_INPUT_STATE","easings","DEFAULT_DURATION","useAnimationFrame","duration","easing","onFrame","onComplete","isAnimating","setIsAnimating","rafIdRef","startTimeRef","onFrameRef","onCompleteRef","cancel","start","step","timestamp","elapsed","easedProgress","isComplete","interpolate","from","to","DEFAULT_ANIMATION_DURATION","getTransformFn","axis","computeInitialMountAnimation","hasConsumed","containerSize","initialPx","targetPx","computeTargetChangeAction","prevTargetPx","currentPx","isOperating","animateOnTargetChange","skipAnimation","computeContainerResizeSnap","prevContainerSize","useSwipeContentTransform","elementRef","animationDuration","skipTargetChangeAnimation","effectiveInitialPx","currentPxRef","animRef","prevTargetPxRef","prevContainerSizeRef","pendingAnimationRef","hasConsumedInitialMountRef","initialMountAnimation","targetChangeAction","resizeSnapPosition","handleFrame","element","anim","value","handleComplete","pending","displayPx","useOperationContinuity","shouldRetainPrevious","prevShouldRetainRef","retainedValueRef","changedDuringRetentionRef","operationJustEnded","valueDiverged","currentlyDiverged","changedDuringOperation","effectiveValue","DEFAULT_BEHIND_OFFSET","computeActiveTargetPx","computeBehindTargetPx","behindOffset","clampedDisplacement","basePosition","parallaxOffset","computeSwipeVisibility","determineSwipePanelRole","STACK_SCALE_FACTOR","MAX_DIM_OPACITY","BASE_STYLE","getAxisDisplacement","ACTIVE_PANEL_SHADOW","SwipeStackContent","operationState","animateOnMount","showShadow","showDimming","role","effectiveRole","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","prevDepth","exitingId","timeoutId","isExiting"],"mappings":";;;;;;AA0CO,SAASA,GAAqBC,GAAsD;AACzF,QAAM,EAAE,WAAAC,GAAW,UAAAC,GAAU,gBAAAC,EAAA,IAAmBH;AAMhD,SAJIG,MAAmB,SAInBF,MAAcC,IACT,OAGFA,IAAW,SAAS;AAC7B;AAoBO,SAASE,GAAkBJ,GAAqD;AACrF,QAAM;AAAA,IACJ,aAAAK;AAAA,IACA,OAAAC;AAAA,IACA,iBAAAC;AAAA,IACA,UAAAL;AAAA,IACA,gBAAAM;AAAA,IACA,aAAAC;AAAA,IACA,aAAAC;AAAA,EAAA,IACEV;AAEJ,SAAIK,MAAgB,YAEdH,KAGAM,KAGAC,KAAeH,MAAUI,IACpB,YAEF,WAILJ,KAASC,KAGTC,IACK,YAEF;AACT;AAkBO,SAASG,GAAiBX,GAAsC;AACrE,QAAM,EAAE,OAAAM,GAAO,aAAAM,GAAa,aAAAP,GAAa,aAAAI,GAAa,aAAAC,MAAgBV,GAEhEE,IAAWI,MAAUM,GACrBC,IAAaP,IAAQM;AAG3B,MAAIH,KAAeP,KACbQ,MAAgB;AAElB,WAAO,cAAc,MAAiB,GAAG;AAI7C,MAAIR;AACF,WAAO;AAGT,MAAIW;AACF,YAAQR,GAAA;AAAA,MACN,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK,SAAS;AACZ,cAAMS,KAAUF,IAAcN,KAAS,IACjCS,IAAQ,KAAKH,IAAcN,KAAS;AAC1C,eAAO,cAAcQ,CAAM,YAAYC,CAAK;AAAA,MAC9C;AAAA,IAAA;AAKJ,SAAO;AACT;AAKA,SAASC,GACPC,GACAC,GACAhB,GACQ;AAOR,SANIgB,MAAkB,UAGlBA,KAAiB,KAGjB,CAAChB,IACIe,IAEF,cAAcC,IAAgB,GAAG;AAC1C;AAKA,SAASC,GAAqBhB,GAAyD;AACrF,MAAIA,MAAmB;AAGvB,WAAO,aAAaiB,CAAyB,IAAIC,CAAuB;AAC1E;AA0CO,SAASC,GAAyBtB,GAAwD;AAC/F,QAAM;AAAA,IACJ,OAAAM;AAAA,IACA,UAAAJ;AAAA,IACA,WAAAD;AAAA,IACA,sBAAAsB;AAAA,IACA,aAAAlB;AAAA,IACA,gBAAAF;AAAA,IACA,iBAAAqB;AAAA,IACA,eAAAN;AAAA,EAAA,IACElB,GAUEyB,IAP2B1B,GAAqB;AAAA,IACpD,WAAAE;AAAA,IACA,UAAAC;AAAA,IACA,gBAAAC;AAAA,EAAA,CACD,KAGqDoB,GAGhDf,IAAiBiB,MAAsB,OACvCC,IAAatB,GAAkB;AAAA,IACnC,aAAAC;AAAA,IACA,OAAAC;AAAA,IACA,iBAAiBkB,EAAgB;AAAA,IACjC,UAAAtB;AAAA,IACA,gBAAAM;AAAA,IACA,aAAagB,EAAgB;AAAA,IAC7B,aAAaA,EAAgB;AAAA,EAAA,CAC9B,GAGKP,IAAgBN,GAAiB;AAAA,IACrC,OAAAL;AAAA,IACA,aAAakB,EAAgB;AAAA,IAC7B,aAAAnB;AAAA,IACA,aAAamB,EAAgB;AAAA,IAC7B,aAAaA,EAAgB;AAAA,EAAA,CAC9B,GAGKG,IAAYX,GAAsBC,GAAeC,GAAehB,CAAQ,GAGxE0B,KAAa,MAAM;AACvB,QAAIzB,MAAmB,OAGvB;AAAA,UAAIsB,MAAsB;AACxB,eAAOI;AAET,UAAIJ,MAAsB;AACxB,eAAOK;AAAA;AAAA,EAGX,GAAA,GAGMC,IAAaZ,GAAqBhB,CAAc;AAEtD,SAAO;AAAA,IACL,mBAAAsB;AAAA,IACA,YAAAC;AAAA,IACA,WAAAC;AAAA,IACA,WAAAC;AAAA,IACA,YAAAG;AAAA,IACA,QAAQzB;AAAA,IACR,eAAeJ,IAAW,SAAS;AAAA,EAAA;AAEvC;AChSA,MAAM8B,KAAiC;AAAA,EACrC,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AACV,GAKaC,KAA4CC,EAAM;AAAA,EAC7D,CAAC,EAAE,IAAAC,GAAI,OAAA7B,GAAO,UAAAJ,GAAU,aAAAG,GAAa,gBAAAF,GAAgB,iBAAAqB,GAAiB,eAAAN,GAAe,UAAAkB,QAAe;AAClG,UAAMC,IAAMH,EAAM,OAAuB,IAAI,GACvCI,IAAgBJ,EAAM,OAAOhC,CAAQ,GAGrC,CAACqC,GAAeC,CAAgB,IAAIN,EAAM,SAA6B,IAAI,GAG3EO,IAAgBnB,GAAyB;AAAA,MAC7C,OAAAhB;AAAA,MACA,UAAAJ;AAAA,MACA,WAAWoC,EAAc;AAAA,MACzB,sBAAsBC;AAAA,MACtB,aAAAlC;AAAA,MACA,gBAAAF;AAAA,MACA,iBAAAqB;AAAA,MACA,eAAAN;AAAA,IAAA,CACD;AAGD,IAAAwB,GAA0B,MAAM;AAC9B,YAAMzC,IAAYqC,EAAc;AAChC,MAAAA,EAAc,UAAUpC,GAEpBD,MAAcC,KAChBsC,EAAiBC,EAAc,iBAAiB;AAAA,IAEpD,GAAG,CAACvC,GAAUuC,EAAc,iBAAiB,CAAC;AAI9C,UAAME,IAAqBT,EAAM,YAAY,CAACU,MAA4B;AACxE,MAAIA,EAAE,WAAWA,EAAE,iBACjBJ,EAAiB,IAAI;AAAA,IAEzB,GAAG,CAAA,CAAE,GAGCK,IAAQX,EAAM,QAA6B,MAAM;AACrD,YAAMY,IAAyB;AAAA,QAC7B,GAAGd;AAAA,QACH,WAAWS,EAAc;AAAA,QACzB,eAAeA,EAAc;AAAA,QAC7B,QAAQA,EAAc;AAAA,QACtB,YAAYA,EAAc;AAAA,MAAA;AAG5B,aAAIA,EAAc,cAAc,WAC9BK,EAAE,YAAYL,EAAc,YAG1BA,EAAc,eAAe,WAC/BK,EAAE,aAAaL,EAAc,aAGxBK;AAAA,IACT,GAAG;AAAA,MACDL,EAAc;AAAA,MACdA,EAAc;AAAA,MACdA,EAAc;AAAA,MACdA,EAAc;AAAA,MACdA,EAAc;AAAA,MACdA,EAAc;AAAA,IAAA,CACf,GAEKM,IACJ,gBAAAC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAAX;AAAA,QACA,sBAAoBF;AAAA,QACpB,cAAY7B;AAAA,QACZ,eAAaJ,IAAW,SAAS;AAAA,QACjC,OAAA2C;AAAA,QACA,gBAAgBF;AAAA,QAEf,UAAAP;AAAA,MAAA;AAAA,IAAA;AAIL,WAAIjC,MAAmB,SACd,gBAAA6C,EAACd,EAAM,UAAN,EAAe,MAAMhC,IAAW,YAAY,UAAW,UAAA6C,GAAQ,IAGlEA;AAAA,EACT;AACF;AC9EA,SAASE,GACPC,GACAC,GACoB;AACpB,UAAQA,EAAO,MAAA;AAAA,IACb,KAAK;AACH,aAAO,CAAC,GAAGD,GAAOC,EAAO,EAAE;AAAA,IAE7B,KAAK,MAAM;AACT,UAAIA,EAAO,aAAa;AACtB,eAAOD;AAGT,YAAME,IADeF,EAAM,SAAS,IACDC,EAAO;AAC1C,aAAIC,IAAc,IACTF,IAEFA,EAAM,MAAM,GAAGE,IAAc,CAAC;AAAA,IACvC;AAAA,IAEA,KAAK;AACH,aAAID,EAAO,cAAc,KAAKA,EAAO,eAAeD,EAAM,SACjDA,IAEFA,EAAM,MAAM,GAAGC,EAAO,cAAc,CAAC;AAAA,IAG9C,KAAK;AACH,aAAID,EAAM,WAAW,IACZ,CAACC,EAAO,EAAE,IAEZ,CAAC,GAAGD,EAAM,MAAM,GAAG,EAAE,GAAGC,EAAO,EAAE;AAAA,EAC1C;AAEJ;AAgBA,MAAME,IAAqBnB,EAAM,cAA8C,IAAI,GAK7EoB,KAA6BpB,EAAM,KAAK,MAAM;AAClD,QAAMqB,IAAMrB,EAAM,WAAWmB,CAAkB;AAC/C,MAAI,CAACE;AACH,UAAM,IAAI,MAAM,oDAAoD;AAGtE,QAAM,CAAA,EAAGC,CAAW,IAAItB,EAAM,WAAW,CAACuB,MAAMA,IAAI,GAAG,CAAC;AAExD,EAAAvB,EAAM,UAAU,MACPqB,EAAI,UAAUC,CAAW,GAC/B,CAACD,CAAG,CAAC;AAER,QAAM,EAAE,QAAAG,GAAQ,iBAAAlC,GAAiB,aAAAnB,GAAa,gBAAAF,EAAA,IAAmBoD,EAAI,SAAA,GAG/DI,IAAgBzB,EAAM,QAAQ,MAC3BV,EAAgB,MAAM,IAAI,CAACW,GAAIyB,MAAU;AAC9C,UAAMC,IAAQH,EAAO,KAAK,CAACI,MAAMA,EAAE,OAAO3B,CAAE;AAC5C,WAAO0B,IAAQ,EAAE,OAAAA,GAAO,OAAOD,MAAU;AAAA,EAC3C,CAAC,EAAE,OAAO,CAACE,MAAiDA,MAAM,IAAI,GACrE,CAACtC,EAAgB,OAAOkC,CAAM,CAAC;AAElC,gCAEK,UAAAC,EAAc,IAAI,CAAC,EAAE,OAAAE,GAAO,OAAAvD,QAC3B,gBAAA0C;AAAA,IAACf;AAAA,IAAA;AAAA,MAEC,IAAI4B,EAAM;AAAA,MACV,OAAAvD;AAAA,MACA,UAAUA,MAAUkB,EAAgB;AAAA,MACpC,aAAAnB;AAAA,MACA,gBAAAF;AAAA,MACA,iBAAAqB;AAAA,MAEC,YAAM,QAAQ+B,EAAI,iBAAiBM,EAAM,EAAE,IAAIA,EAAM;AAAA,IAAA;AAAA,IARjDA,EAAM;AAAA,EAAA,CAUd,GACH;AAEJ,CAAC;AAuBM,SAASE,GACdC,GAC+B;AAC/B,QAAM;AAAA,IACJ,QAAAN;AAAA,IACA,gBAAAO;AAAA,IACA,aAAA5D;AAAA,IACA,gBAAAF,IAAiB;AAAA,IACjB,eAAA+D;AAAA,EAAA,IACEF,GAGEG,IAAYF,KAAmBP,EAAO,CAAC,GAAG;AAChD,MAAI,CAACS;AACH,UAAM,IAAI,MAAM,wCAAwC;AAG1D,QAAM,CAACC,GAAOC,CAAQ,IAAInC,EAAM;AAAA,IAC9Be;AAAA,IACA,CAACkB,CAAS;AAAA,EAAA,GAING,IAAWpC,EAAM,OAAOkC,CAAK;AACnC,EAAAE,EAAS,UAAUF;AAGnB,QAAMG,IAAerC,EAAM,OAAOkC,CAAK;AACvC,EAAAlC,EAAM,UAAU,MAAM;AACpB,UAAMsC,IAAYD,EAAa;AAG/B,QAFAA,EAAa,UAAUH,GAEnBF,KAAiBE,MAAUI,GAAW;AACxC,YAAMC,IAAWL,EAAM,SAAS,GAC1BM,IAAaN,EAAMK,CAAQ;AACjC,MAAIC,MAAe,UACjBR,EAAcQ,GAAYD,CAAQ;AAAA,IAEtC;AAAA,EACF,GAAG,CAACL,GAAOF,CAAa,CAAC;AAGzB,QAAM,CAACS,GAAaC,CAAc,IAAI1C,EAAM,SAGzC,EAAE,aAAa,IAAO,aAAa,MAAM,GAGtC5B,IAAQ8D,EAAM,SAAS,GAGvBlB,IAAmChB,EAAM,QAAQ,OAAO;AAAA,IAC5D,OAAAkC;AAAA,IACA,OAAA9D;AAAA,IACA,aAAaqE,EAAY;AAAA,IACzB,aAAaA,EAAY;AAAA,EAAA,IACvB,CAACP,GAAO9D,GAAOqE,EAAY,aAAaA,EAAY,WAAW,CAAC,GAG9DE,IAAiBT,EAAM9D,CAAK,GAC5BwE,IAAkBxE,IAAQ,IAAI8D,EAAM9D,IAAQ,CAAC,IAAW,MAGxDyE,IAAO7C,EAAM,YAAY,CAACC,MAAY;AAE1C,IADcuB,EAAO,KAAK,CAACI,MAAMA,EAAE,OAAO3B,CAAE,KAI5CkC,EAAS,EAAE,MAAM,QAAQ,IAAAlC,EAAA,CAAI;AAAA,EAC/B,GAAG,CAACuB,CAAM,CAAC,GAELsB,IAAK9C,EAAM,YAAY,CAAC+C,MAAsB;AAClD,IAAAZ,EAAS,EAAE,MAAM,MAAM,WAAAY,EAAA,CAAW;AAAA,EACpC,GAAG,CAAA,CAAE,GAECC,IAAOhD,EAAM,YAAY,CAACkB,MAAwB;AACtD,IAAAiB,EAAS,EAAE,MAAM,QAAQ,aAAAjB,EAAA,CAAa;AAAA,EACxC,GAAG,CAAA,CAAE,GAEC+B,IAAUjD,EAAM,YAAY,CAACC,MAAY;AAE7C,IADcuB,EAAO,KAAK,CAACI,MAAMA,EAAE,OAAO3B,CAAE,KAI5CkC,EAAS,EAAE,MAAM,WAAW,IAAAlC,EAAA,CAAI;AAAA,EAClC,GAAG,CAACuB,CAAM,CAAC,GAGL0B,IAAQlD,EAAM,YAAY,CAAC+C,MAC3BA,KAAa,IACR,KAEYX,EAAS,QAAQ,SAAS,IACzBW,KAAa,GAClC,CAAA,CAAE,GAGCI,IAAenD,EAAM,YAAY,CAACkB,MAAyB;AAC/D,UAAMkC,IAAehB,EAAS,QAAQ,SAAS,GACzCiB,IAAWnC,KAAekC,IAAe;AAC/C,IAAIC,IAAW,KAAKA,KAAYD,KAGhCV,EAAe,EAAE,aAAa,IAAM,aAAaW,GAAU;AAAA,EAC7D,GAAG,CAAA,CAAE,GAECC,IAAatD,EAAM,YAAY,MAAM;AAEzC,IADqBoC,EAAS,QAAQ,SAAS,MAC1B,KAGrBM,EAAe,EAAE,aAAa,IAAM,aAAa,GAAG;AAAA,EACtD,GAAG,CAAA,CAAE,GAECa,IAAgBvD,EAAM,YAAY,MAAM;AAC5C,IAAA0C,EAAe,EAAE,aAAa,IAAO,aAAa,MAAM;AAAA,EAC1D,GAAG,CAAA,CAAE,GAGCc,IAAgBxD,EAAM,YAAY,CAACC,MAA6B;AACpE,UAAMwD,IAAerB,EAAS,SACxBsB,IAAaD,EAAa,QAAQxD,CAAE,GACpCmD,IAAeK,EAAa,SAAS,GACrCzF,IAAW0F,MAAeN;AAEhC,WAAO;AAAA,MACL,oBAAoBnD;AAAA,MACpB,cAAcyD;AAAA,MACd,eAAe1F,IAAW,SAAS;AAAA,MACnC,eAAe,CAACA;AAAA,IAAA;AAAA,EAEpB,GAAG,CAAA,CAAE,GAGC2F,IAAqB3D,EAAM,YAAY,MAA4B;AACvE,UAAMyD,IAAerB,EAAS,SACxBgB,IAAeK,EAAa,SAAS,GACrCG,IAAYR,IAAe,GAC3BS,IAAcT,IAAe,IAAIK,EAAaL,IAAe,CAAC,IAAI,MAClEU,IAAYD,IAAcrC,EAAO,KAAK,CAACI,MAAMA,EAAE,OAAOiC,CAAW,IAAI,MACrEE,IAAQD,GAAW,QAAQ,WAAWA,EAAU,KAAK,KAAK;AAEhE,WAAO;AAAA,MACL,SAAS,MAAMhB,EAAG,EAAE;AAAA,MACpB,UAAU,CAACc;AAAA,MACX,cAAcG;AAAA,IAAA;AAAA,EAElB,GAAG,CAACvC,GAAQsB,CAAE,CAAC,GAGTkB,IAAsChE,EAAM;AAAA,IAChD,OAAO;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU;AAAA,IAAA;AAAA,IAEZ,CAAA;AAAA,EAAC,GAIGiE,IAAWjE,EAAM,OAAO;AAAA,IAC5B,QAAAwB;AAAA,IACA,iBAAiBR;AAAA,IACjB,aAAA7C;AAAA,IACA,gBAAAF;AAAA,EAAA,CACD;AAED,EAAAgG,EAAS,UAAU;AAAA,IACjB,QAAAzC;AAAA,IACA,iBAAiBR;AAAA,IACjB,aAAA7C;AAAA,IACA,gBAAAF;AAAA,EAAA;AAIF,QAAMiG,IAAiBlE,EAAM,OAAO,oBAAI,KAAiB;AAGzD,EAAAA,EAAM,UAAU,MAAM;AACpB,IAAAkE,EAAe,QAAQ,QAAQ,CAACC,MAAaA,GAAU;AAAA,EACzD,GAAG,CAACnD,GAAO7C,GAAaF,CAAc,CAAC;AAGvC,QAAMmG,IAAiBpE,EAAM;AAAA,IAC3B,CAACqE,MACeJ,EAAS,QAAQ,OAAO,KAAK,CAACrC,MAAMA,EAAE,OAAOyC,CAAO,GACpD,WAAW;AAAA,IAE3B,CAAA;AAAA,EAAC,GAIGC,IAAWtE,EAAM,QAAQ,MAAyBwB,EAAO,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,CAACA,CAAM,CAAC,GAGnF,EAAE,kBAAA+C,EAAA,IAAqBC,GAAgB;AAAA,IAC3C,gBAAAJ;AAAA,IACA,UAAAE;AAAA,EAAA,CACD,GAGKG,IAAezE,EAAM;AAAA,IACzB,OAAO;AAAA,MACL,UAAU,MAAMiE,EAAS;AAAA,MACzB,WAAW,CAACE,OACVD,EAAe,QAAQ,IAAIC,CAAQ,GAC5B,MAAMD,EAAe,QAAQ,OAAOC,CAAQ;AAAA,MAErD,kBAAAI;AAAA,IAAA;AAAA,IAEF,CAACA,CAAgB;AAAA,EAAA,GAIbG,IAAS1E,EAAM,QAAQ,MAAM;AACjC,UAAM2E,IAA4B,MAChC,gBAAA7D,EAACK,EAAmB,UAAnB,EAA4B,OAAOsD,GAClC,UAAA,gBAAA3D,EAAC,OAAA,EAAI,OAAOkD,GAAgB,wBAAoB,IAC9C,UAAA,gBAAAlD,EAACM,IAAA,CAAA,CAAiB,GACpB,GACF;AAEF,WAAAuD,EAAgB,cAAc,eACvBA;AAAA,EACT,GAAG,CAACF,GAAcT,CAAc,CAAC;AAEjC,SAAO;AAAA,IACL,OAAAhD;AAAA,IACA,MAAA6B;AAAA,IACA,IAAAC;AAAA,IACA,MAAAE;AAAA,IACA,SAAAC;AAAA,IACA,cAAAE;AAAA,IACA,YAAAG;AAAA,IACA,eAAAC;AAAA,IACA,eAAAC;AAAA,IACA,oBAAAG;AAAA,IACA,OAAAT;AAAA,IACA,gBAAAP;AAAA,IACA,iBAAAC;AAAA,IACA,QAAA8B;AAAA,EAAA;AAEJ;ACjWO,SAASE,GAAmB9C,GAA8D;AAC/F,QAAM;AAAA,IACJ,cAAA+C;AAAA,IACA,YAAAC;AAAA,IACA,MAAAC,IAAO;AAAA,IACP,WAAAC,IAAY;AAAA,IACZ,SAAAC,IAAU;AAAA,EAAA,IACRnD,GAGE,CAACoD,GAAYC,CAAa,IAAInF,EAAM,SAAiC,IAAI,GAGzEoF,IAAiBpF,EAAM;AAAA,IAC3B,CAACgB,MAA2B;AAC1B,MAAAmE,EAAc,IAAI,GAGdJ,MAAS,UAAU/D,EAAM,cAAc,KACrC8D,EAAW,MAAM,EAAE,KACrBA,EAAW,GAAG,EAAE;AAAA,IAKtB;AAAA,IACA,CAACC,GAAMD,CAAU;AAAA,EAAA,GAIb,EAAE,eAAAO,GAAe,OAAOC,GAAY,gBAAgBC,EAAA,IAAeC,GAAkB;AAAA,IACzF,cAAAX;AAAA,IACA,MAAAE;AAAA,IACA,WAAAC;AAAA,IACA,SAASC,IAAUH,EAAW,MAAM,EAAE,IAAI;AAAA;AAAA,IAC1C,YAAYM;AAAA,EAAA,CACb;AAGD,EAAApF,EAAM,UAAU,MAAM;AACpB,IAAIqF,MAAkBC,EAAW,UAAU,aAAaA,EAAW,UAAU,cAC3EH,EAAcG,CAAU,IACfA,EAAW,UAAU,UAC9BH,EAAc,IAAI;AAAA,EAEtB,GAAG,CAACE,GAAeC,CAAU,CAAC;AAG9B,QAAM,EAAE,gBAAgBG,EAAA,IAAeC,GAAsB;AAAA,IAC3D,cAAAb;AAAA,IACA,QAAQQ;AAAA,IACR,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,WAAAL;AAAA,EAAA,CACD,GAGKW,IAAW3F,EAAM,QAAQ,MAAM;AACnC,QAAI,CAACkF,KAAc,CAACL,EAAa;AAC/B,aAAO;AAGT,UAAMe,IAAiBf,EAAa,QAAQ;AAC5C,QAAIe,MAAmB;AACrB,aAAO;AAIT,UAAMC,IAAeX,EAAW,aAAa;AAQ7C,QALIH,MAAS,UAAUc,KAAgB,KAKnCd,MAAS,WAAWc,KAAgB;AACtC,aAAO;AAGT,UAAMC,IAAkB,KAAK,IAAID,CAAY;AAC7C,WAAO,KAAK,IAAIC,IAAkBF,GAAgB,CAAC;AAAA,EACrD,GAAG,CAACV,GAAYL,GAAcE,CAAI,CAAC,GAG7BgB,IAAiB/F,EAAM;AAAA,IAC3B,MAAMgG,GAA2BT,GAAYE,CAAU;AAAA,IACvD,CAACF,GAAYE,CAAU;AAAA,EAAA;AAMzB,SAAO;AAAA,IACL,eAAeJ;AAAA,IACf,UAAAM;AAAA,IACA,YAL2CN,IAAgBC,IAAaW;AAAA,IAMxE,gBAAAF;AAAA,EAAA;AAEJ;ACzHO,MAAMG,IAAU;AAAA;AAAA,EAQrB,aAAa,CAAC,MACR,MAAM,IACD,IAEF,IAAI,KAAK,IAAI,GAAG,MAAM,CAAC;AAqBlC,GA2CMC,KAAmB;AAwBlB,SAASC,GAAkBtE,GAA4D;AAC5F,QAAM;AAAA,IACJ,UAAAuE,IAAWF;AAAA,IACX,QAAAG,IAASJ,EAAQ;AAAA,IACjB,SAAAK;AAAA,IACA,YAAAC;AAAA,EAAA,IACE1E,GAEE,CAAC2E,GAAaC,CAAc,IAAI1G,EAAM,SAAS,EAAK,GACpD2G,IAAW3G,EAAM,OAAsB,IAAI,GAC3C4G,IAAe5G,EAAM,OAAsB,IAAI,GAG/C6G,IAAa7G,EAAM,OAAOuG,CAAO,GACjCO,IAAgB9G,EAAM,OAAOwG,CAAU;AAC7C,EAAAxG,EAAM,UAAU,MAAM;AACpB,IAAA6G,EAAW,UAAUN,GACrBO,EAAc,UAAUN;AAAA,EAC1B,GAAG,CAACD,GAASC,CAAU,CAAC;AAExB,QAAMO,IAAS/G,EAAM,YAAY,MAAM;AACrC,IAAI2G,EAAS,YAAY,SACvB,qBAAqBA,EAAS,OAAO,GACrCA,EAAS,UAAU,OAErBC,EAAa,UAAU,MACvBF,EAAe,EAAK;AAAA,EACtB,GAAG,CAAA,CAAE,GAECM,IAAQhH,EAAM,YAAY,MAAM;AAEpC,IAAA+G,EAAA,GAEAL,EAAe,EAAI,GACnBE,EAAa,UAAU;AAEvB,UAAMK,IAAO,CAACC,MAAsB;AAClC,MAAIN,EAAa,YAAY,SAC3BA,EAAa,UAAUM;AAGzB,YAAMC,IAAUD,IAAYN,EAAa,SACnCjB,IAAW,KAAK,IAAIwB,IAAUd,GAAU,CAAC,GACzCe,IAAgBd,EAAOX,CAAQ,GAC/B0B,IAAa1B,KAAY,GAEzB3E,IAAwB;AAAA,QAC5B,UAAA2E;AAAA,QACA,eAAAyB;AAAA,QACA,SAAAD;AAAA,QACA,YAAAE;AAAA,MAAA;AAGF,MAAAR,EAAW,UAAU7F,CAAK,GAErBqG,KAIHV,EAAS,UAAU,MACnBC,EAAa,UAAU,MACvBF,EAAe,EAAK,GACpBI,EAAc,UAAA,KANdH,EAAS,UAAU,sBAAsBM,CAAI;AAAA,IAQjD;AAEA,IAAAN,EAAS,UAAU,sBAAsBM,CAAI;AAAA,EAC/C,GAAG,CAACZ,GAAUC,GAAQS,CAAM,CAAC;AAG7B,SAAA/G,EAAM,UAAU,MACP,MAAM;AACX,IAAI2G,EAAS,YAAY,QACvB,qBAAqBA,EAAS,OAAO;AAAA,EAEzC,GACC,CAAA,CAAE,GAEE;AAAA,IACL,aAAAF;AAAA,IACA,OAAAO;AAAA,IACA,QAAAD;AAAA,EAAA;AAEJ;AAKO,SAASO,GAAYC,GAAcC,GAAYJ,GAA+B;AACnF,SAAOG,KAAQC,IAAKD,KAAQH;AAC9B;ACnMA,MAAMK,KAA6B,KAkE7BC,IAAiB,CAACC,MACfA,MAAS,eAAe,eAAe,cAa1CC,KAA+B,CACnCC,GACAC,GACAC,GACAC,MAEIH,KAGAC,MAAkB,UAGlBA,KAAiB,KAGjBC,MAAc,UAGdA,MAAcC,IACT,OAEF,EAAE,MAAMD,GAAW,IAAIC,EAAA,GAkB1BC,KAA4B,CAChCD,GACAE,GACAC,GACAC,GACA3B,GACA4B,GACAC,MAEIN,MAAaE,IACR,EAAE,MAAM,OAAA,IAEbE,IACK,EAAE,MAAM,OAAA,IAEb3B,IACK,EAAE,MAAM,OAAA,IAEZ4B,IAIY,KAAK,IAAIF,IAAYH,CAAQ,KAC9B,IACP,EAAE,MAAM,QAAQ,UAAUA,EAAA,IAO/BM,IAEEH,IAAYH,IACP,EAAE,MAAM,QAAQ,UAAUA,EAAA,IAG5B,EAAE,MAAM,WAAW,WAAW,EAAE,MAAMG,GAAW,IAAIH,IAAS,IAGhE,EAAE,MAAM,WAAW,WAAW,EAAE,MAAMG,GAAW,IAAIH,IAAS,IArB5D,EAAE,MAAM,QAAQ,UAAUA,EAAA,GA4B/BO,KAA6B,CACjCT,GACAU,GACAR,MAEIF,MAAkB,UAGlBA,MAAkBU,KAGlBV,KAAiB,IACZ,OAEFE;AAoBF,SAASS,GACd3G,GACgC;AAChC,QAAM;AAAA,IACJ,YAAA4G;AAAA,IACA,UAAAV;AAAA,IACA,cAAAnC;AAAA,IACA,aAAAuC;AAAA,IACA,MAAAT,IAAO;AAAA,IACP,mBAAAgB,IAAoBlB;AAAAA,IACpB,eAAAK;AAAA,IACA,uBAAAO,IAAwB;AAAA,IACxB,WAAAN;AAAA,IACA,2BAAAa,IAA4B;AAAA,EAAA,IAC1B9G,GAGE+G,IAAqBd,KAAaC,GAClCc,IAAe9I,EAAM,OAAe6I,CAAkB,GACtDE,IAAU/I,EAAM,OAA4C,IAAI,GAChEgJ,IAAkBhJ,EAAM,OAAegI,CAAQ,GAC/CiB,IAAuBjJ,EAAM,OAA2B8H,CAAa,GACrEoB,IAAsBlJ,EAAM,OAA4C,IAAI,GAE5EmJ,IAA6BnJ,EAAM,OAAgB,EAAK,GAGxDoJ,IAAwBxB;AAAA,IAC5BuB,EAA2B;AAAA,IAC3BrB;AAAA,IACAC;AAAA,IACAC;AAAA,EAAA;AAEF,EAAIoB,MAA0B,SAC5BF,EAAoB,UAAUE,GAC9BD,EAA2B,UAAU;AAIvC,QAAME,IAAqBpB;AAAA,IACzBD;AAAA,IACAgB,EAAgB;AAAA,IAChBF,EAAa;AAAA,IACbV;AAAA,IACAW,EAAQ,YAAY;AAAA,IACpBV;AAAA,IACAO;AAAA,EAAA;AAEF,EAAIS,EAAmB,SAAS,aAC9BH,EAAoB,UAAUG,EAAmB,WACjDL,EAAgB,UAAUhB,KACjBqB,EAAmB,SAAS,WACrCP,EAAa,UAAUO,EAAmB,UAC1CL,EAAgB,UAAUhB;AAI5B,QAAMsB,IAAqBf;AAAA,IACzBT;AAAA,IACAmB,EAAqB;AAAA,IACrBjB;AAAA,EAAA;AAEF,EAAIsB,MAAuB,SACzBR,EAAa,UAAUQ,GACvBL,EAAqB,UAAUnB;AAIjC,QAAMyB,IAAcvJ,EAAM;AAAA,IACxB,CAAC,EAAE,eAAAoH,EAAA,MAA+C;AAChD,YAAMoC,IAAUd,EAAW,SACrBe,IAAOV,EAAQ;AACrB,UAAI,CAACS,KAAW,CAACC;AACf;AAEF,YAAMC,IAAQpC,GAAYmC,EAAK,MAAMA,EAAK,IAAIrC,CAAa;AAC3D,MAAA0B,EAAa,UAAUY,GACvBF,EAAQ,MAAM,YAAY,GAAG9B,EAAeC,CAAI,CAAC,IAAI+B,CAAK;AAAA,IAC5D;AAAA,IACA,CAAC/B,GAAMe,CAAU;AAAA,EAAA,GAGbiB,IAAiB3J,EAAM,YAAY,MAAM;AAC7C,IAAA+I,EAAQ,UAAU,MAClBD,EAAa,UAAUd,GACvBgB,EAAgB,UAAUhB;AAAA,EAC5B,GAAG,CAACA,CAAQ,CAAC,GAEP,EAAE,aAAAvB,GAAa,OAAAO,GAAO,QAAAD,EAAA,IAAWX,GAAkB;AAAA,IACvD,UAAUuC;AAAA,IACV,QAAQzC,EAAQ;AAAA,IAChB,SAASqD;AAAA,IACT,YAAYI;AAAA,EAAA,CACb;AAGD,SAAA3J,EAAM,gBAAgB,MAAM;AAC1B,QAAIoI,GAAa;AACf,MAAArB,EAAA,GACAgC,EAAQ,UAAU,MAClBG,EAAoB,UAAU;AAC9B;AAAA,IACF;AAGA,QAAIA,EAAoB,SAAS;AAC/B,YAAMU,IAAUV,EAAoB;AACpC,MAAAH,EAAQ,UAAUa,GAClBV,EAAoB,UAAU;AAE9B,YAAMM,IAAUd,EAAW;AAC3B,MAAIc,MACFA,EAAQ,MAAM,YAAY,GAAG9B,EAAeC,CAAI,CAAC,IAAIiC,EAAQ,IAAI,QAEnE5C,EAAA;AACA;AAAA,IACF;AAEA,UAAMmB,IAAYW,EAAa;AAG/B,IAFiB,KAAK,IAAIX,IAAYH,CAAQ,IAE/B,KAEbe,EAAQ,UAAU,EAAE,MAAMZ,GAAW,IAAIH,EAAA,GACzChB,EAAA,MAGA8B,EAAa,UAAUd,GACvBgB,EAAgB,UAAUhB;AAAA,EAE9B,GAAG,CAACI,GAAaJ,GAAUhB,GAAOD,CAAM,CAAC,GAGzC/G,EAAM,gBAAgB,MAAM;AAC1B,UAAMwJ,IAAUd,EAAW;AAY3B,QAXI,CAACc,KAKD/C,KAGAsC,EAAQ,YAAY,QAGpBG,EAAoB,YAAY;AAClC;AAGF,UAAMW,IAAY7B,IAAWnC;AAC7B,IAAAiD,EAAa,UAAUe,GACvBL,EAAQ,MAAM,YAAY,GAAG9B,EAAeC,CAAI,CAAC,IAAIkC,CAAS;AAAA,EAChE,GAAG,CAAC7B,GAAUnC,GAAc8B,GAAMlB,GAAaiC,CAAU,CAAC,GAEnD;AAAA,IACL,aAAAjC;AAAA,IACA,WAAWqC,EAAa;AAAA,IACxB,oBAAoBC,EAAQ;AAAA,EAAA;AAEhC;ACtSO,SAASe,GACdJ,GACAK,GACiC;AAEjC,QAAMC,IAAsBhK,EAAM,OAAO+J,CAAoB,GAEvDE,IAAmBjK,EAAM,OAAO0J,CAAK,GAErCQ,IAA4BlK,EAAM,OAAO,EAAK,GAK9CmK,IADeH,EAAoB,WACE,CAACD,GAItCK,IAAgBV,MAAUO,EAAiB,SAC3CI,IAAoBN,KAAwBK,GAS5CE,IAFyBJ,EAA0B,WAAWG,KAC9CF,KAAsBC,GAMtCG,IAAiBR,IAAuBE,EAAiB,UAAUP;AAIzE,SAAA1J,EAAM,gBAAgB,MAAM;AAC1B,IAAK+J,IAMCM,MACFH,EAA0B,UAAU,OALtCA,EAA0B,UAAU,IACpCD,EAAiB,UAAUP,IAO7BM,EAAoB,UAAUD;AAAA,EAChC,CAAC,GAEM;AAAA,IACL,OAAOQ;AAAA,IACP,wBAAAD;AAAA,IACA,oBAAAH;AAAA,EAAA;AAEJ;AC5HO,MAAMK,IAAwB;AAU9B,SAASC,GAAsB5E,GAA8B;AAGlE,SAAO,KAAK,IAAI,GAAGA,CAAY;AACjC;AAaO,SAAS6E,GACd7E,GACAiC,GACA6C,IAAuBH,GACf;AACR,MAAI1C,KAAiB;AACnB,WAAO;AAIT,QAAM8C,IAAsB,KAAK,IAAI,GAAG/E,CAAY,GAG9CF,IAAW,KAAK,IAAIiF,IAAsB9C,GAAe,CAAC,GAI1D+C,IAAeF,IAAe7C,GAC9BgD,IAAiB,KAAK,IAAIH,CAAY,IAAIhF,IAAWmC;AAE3D,SAAO+C,IAAeC;AACxB;AA2CO,SAASC,GAAuBjN,GAA6C;AAClF,QAAM,EAAE,OAAAM,GAAO,iBAAAC,GAAiB,UAAAL,GAAU,aAAAoK,GAAa,aAAA3B,MAAgB3I;AASvE,SANI,GAAAE,KAKkBI,MAAUC,IAAkB,MAE5C+J,KAGA3B;AAOR;AAcO,SAASuE,GAAwB5M,GAAeC,GAAyC;AAC9F,SAAID,MAAUC,IACL,WAELD,MAAUC,IAAkB,IACvB,WAEF;AACT;AC1HA,MAAMoJ,KAA6B,KAM7BwD,KAAqB,MAKrBC,KAAkB,KAwDlBC,KAAkC;AAAA,EACtC,UAAU;AAAA,EACV,OAAO;AAAA,EACP,OAAO;AAAA,EACP,QAAQ;AACV,GAKMC,KAAsB,CAACpK,GAAiC2G,MACxD3G,EAAM,UAAU,SACX,IAEF2G,MAAS,eAAe3G,EAAM,aAAa,IAAIA,EAAM,aAAa,GA0BrEqK,KAAsB,kCAEfC,KAAsDtL,EAAM;AAAA,EACvE,CAAC;AAAA,IACC,IAAAC;AAAA,IACA,OAAA7B;AAAA,IACA,iBAAAC;AAAA,IACA,UAAAL;AAAA,IACA,gBAAAuN;AAAA,IACA,eAAAzD;AAAA,IACA,MAAAH,IAAO;AAAA,IACP,cAAAgD,IAAeH;AAAA,IACf,mBAAA7B,IAAoBlB;AAAAA,IACpB,gBAAA+D,IAAiB;AAAA,IACjB,YAAAC,IAAa;AAAA,IACb,aAAAtN,IAAc;AAAA,IACd,aAAAuN,IAAc;AAAA,IACd,UAAAxL;AAAA,EAAA,MACI;AACJ,UAAMwI,IAAa1I,EAAM,OAAuB,IAAI,GAE9C6F,IAAeuF,GAAoBG,GAAgB5D,CAAI,GACvDS,IAAcmD,EAAe,UAAU,aAGvCI,IAAOX,GAAwB5M,GAAOC,CAAe,GAQrD,EAAE,OAAOuN,GAAe,wBAAAtB,EAAA,IAA2BR;AAAA,MACvD6B;AAAA,MACA9F,IAAe;AAAA,IAAA,GAIXmC,IAAWhI,EAAM,QAAQ,MAAM;AACnC,cAAQ4L,GAAA;AAAA,QACN,KAAK;AAEH,iBAAO;AAAA,QACT,KAAK;AAEH,iBAAOjB,IAAe7C;AAAA,QACxB,KAAK;AAEH,iBAAOA;AAAA,MAAA;AAAA,IAEb,GAAG,CAAC8D,GAAejB,GAAc7C,CAAa,CAAC,GAGzC+D,IAAoB7L,EAAM,QAAQ,MAAM;AAC5C,UAAI6F,KAAgB;AAClB,eAAO;AAGT,cAAQ+F,GAAA;AAAA,QACN,KAAK;AAEH,iBAAOnB,GAAsB5E,CAAY;AAAA,QAC3C,KAAK,UAAU;AAEb,gBAAMiG,IAAapB,GAAsB7E,GAAciC,GAAe6C,CAAY,GAC5EoB,IAAUpB,IAAe7C;AAC/B,iBAAOgE,IAAaC;AAAA,QACtB;AAAA,QACA,KAAK;AACH,iBAAO;AAAA,MAAA;AAAA,IAEb,GAAG,CAACH,GAAe/F,GAAciC,GAAe6C,CAAY,CAAC,GAQvD5C,IAAY/H,EAAM,QAAQ,MAAM;AACpC,UAAKwL,KAGDG,MAAS,YAAYvN,IAAQ;AAE/B,eAAO0J;AAAA,IAIX,GAAG,CAAC0D,GAAgBG,GAAMvN,GAAO0J,CAAa,CAAC,GAGzC,EAAE,aAAArB,EAAA,IAAgBgC,GAAyB;AAAA,MAC/C,YAAAC;AAAA,MACA,UAAAV;AAAA,MACA,cAAc6D;AAAA,MACd,aAAAzD;AAAA,MACA,MAAAT;AAAA,MACA,mBAAAgB;AAAA,MACA,eAAAb;AAAA;AAAA,MAEA,uBAAuB;AAAA;AAAA,MAEvB,WAAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,2BAA2BuC;AAAA,IAAA,CAC5B,GAGK0B,IAAUjB,GAAuB;AAAA,MACrC,OAAA3M;AAAA,MACA,iBAAAC;AAAA,MACA,UAAAL;AAAA,MACA,aAAAoK;AAAA,MACA,aAAA3B;AAAA,IAAA,CACD,GAGKzH,IAAgBgB,EAAM,QAAQ,MAC9B8H,KAAiB,KAAKjC,KAAgB,IACjC,IAEF,KAAK,IAAIA,IAAeiC,GAAe,CAAC,GAC9C,CAACjC,GAAciC,CAAa,CAAC,GAI1BjJ,IAAQmB,EAAM,QAAQ,MAAM;AAChC,UAAI7B,MAAgB;AAClB,eAAO;AAGT,YAAM8N,IAAY5N,IAAkBD;AAEpC,UAAIuN,MAAS;AACX,eAAO;AAGT,UAAIA,MAAS,UAAU;AAErB,cAAMO,IAAY,IAAID,IAAYhB;AAElC,eAAOiB,IAAYlN,KAAiB,IAAIkN;AAAA,MAC1C;AAEA,aAAO;AAAA,IACT,GAAG,CAAC/N,GAAawN,GAAMvN,GAAOC,GAAiBW,CAAa,CAAC,GAIvDmN,IAAiBnM,EAAM,QAAQ,MAC/B,CAAC0L,KAAeC,MAAS,WACpB,IAGFT,MAAmB,IAAIlM,IAC7B,CAAC0M,GAAaC,GAAM3M,CAAa,CAAC;AAGrC,IAAAgB,EAAM,gBAAgB,MAAM;AAC1B,YAAMwJ,IAAUd,EAAW;AAC3B,MAAIc,MACFA,EAAQ,MAAM,aAAawC,IAAU,YAAY;AAAA,IAErD,GAAG,CAACA,CAAO,CAAC,GAGZhM,EAAM,gBAAgB,MAAM;AAC1B,YAAMwJ,IAAUd,EAAW;AAC3B,UAAI,CAACc,KAAWrL,MAAgB;AAC9B;AAIF,YAAMiO,IAAmB5C,EAAQ,MAAM;AACvC,UAAI4C,EAAiB,SAAS,YAAY,GAAG;AAE3C,cAAMC,IAAiBD,EAAiB,MAAM,qBAAqB;AACnE,QAAIC,MACF7C,EAAQ,MAAM,YAAY,GAAG6C,EAAe,CAAC,CAAC,UAAUxN,CAAK;AAAA,MAEjE;AACE,QAAA2K,EAAQ,MAAM,YAAY,SAAS3K,CAAK;AAAA,IAE5C,GAAG,CAACA,GAAOV,CAAW,CAAC;AAIvB,UAAMmO,IAAmBb,IAAarN,IAAQ,KAAKuN,MAAS,WAAW,IAIjEY,IAAcvM,EAAM;AAAA,MACxB,OAAO;AAAA,QACL,GAAGmL;AAAA,QACH,eAAenN,IAAW,SAAS;AAAA,QACnC,YAAY;AAAA,QACZ,QAAQI;AAAA,QACR,YAAY4N,IAAU,YAAY;AAAA,QAClC,WAAWM,IAAmBjB,KAAsB;AAAA,MAAA;AAAA,MAEtD,CAACrN,GAAUI,GAAO4N,GAASM,CAAgB;AAAA,IAAA,GAIvCE,IAAexM,EAAM,QAAoC,MACzDmM,KAAkB,IACb,OAEF;AAAA,MACL,UAAU;AAAA,MACV,OAAO;AAAA,MACP,iBAAiB,iBAAiBA,CAAc;AAAA,MAChD,eAAe;AAAA,MACf,QAAQ;AAAA,IAAA,GAET,CAACA,CAAc,CAAC;AAEnB,WACE,gBAAAM;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK/D;AAAA,QACL,sBAAoBzI;AAAA,QACpB,cAAY7B;AAAA,QACZ,eAAaJ,IAAW,SAAS;AAAA,QACjC,aAAW2N;AAAA,QACX,OAAOY;AAAA,QAEN,UAAA;AAAA,UAAArM;AAAA,UACAsM,KAAgB,OAAO,gBAAA1L,EAAC,OAAA,EAAI,OAAO0L,GAAc,wBAAoB,IAAC,IAAK;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGlF;AACF,GClWM/E,KAA6B;AAsDnC,SAASiF,GACPlL,GACAlC,GACAqN,GACiE;AACjE,QAAM,EAAE,OAAAzK,GAAO,OAAA9D,EAAA,IAAUkB,GAGnBsN,IAAgB,CAACxO,CAAK;AAC5B,EAAIA,IAAQ,KACVwO,EAAc,KAAKxO,IAAQ,CAAC;AAG9B,QAAMyO,IAA0E,CAAA;AAGhF,aAAWC,KAAKF,GAAe;AAC7B,UAAM3M,IAAKiC,EAAM4K,CAAC,GACZnL,IAAQH,EAAO,KAAK,CAACI,MAAMA,EAAE,OAAO3B,CAAE;AAC5C,IAAI0B,KACFkL,EAAO,KAAK,EAAE,OAAAlL,GAAO,OAAOmL,GAAG,WAAW,IAAO;AAAA,EAErD;AAGA,MAAIH,KAAkB,QAEhB,CADoBE,EAAO,KAAK,CAACE,MAAMA,EAAE,MAAM,OAAOJ,CAAc,GAClD;AACpB,UAAMK,IAAexL,EAAO,KAAK,CAACI,MAAMA,EAAE,OAAO+K,CAAc;AAC/D,IAAIK,KAEFH,EAAO,KAAK,EAAE,OAAOG,GAAc,OAAO5O,IAAQ,GAAG,WAAW,IAAM;AAAA,EAE1E;AAIF,SAAOyO,EAAO,KAAK,CAACI,GAAGC,MAAMD,EAAE,QAAQC,EAAE,KAAK;AAChD;AA2BO,MAAMC,KAAoDnN,EAAM;AAAA,EACrE,CAAC;AAAA,IACC,QAAAwB;AAAA,IACA,iBAAAlC;AAAA,IACA,gBAAAiM;AAAA,IACA,eAAAzD;AAAA,IACA,kBAAAvD;AAAA,IACA,cAAAoG;AAAA,IACA,gBAAAa,IAAiB;AAAA,IACjB,mBAAA7C,IAAoBlB;AAAA,IACpB,YAAAgE;AAAA,IACA,aAAAtN;AAAA,IACA,aAAAuN;AAAA,EAAA,MACI;AAEJ,UAAM,CAACiB,GAAgBS,CAAiB,IAAIpN,EAAM,SAAwB,IAAI,GACxEqN,IAAerN,EAAM,OAAOV,EAAgB,KAAK,GACjD+C,IAAerC,EAAM,OAA8BV,EAAgB,KAAK;AAG9E,IAAAU,EAAM,gBAAgB,MAAM;AAC1B,YAAMsN,IAAYD,EAAa,SACzB/K,IAAYD,EAAa,SACzB,EAAE,OAAAjE,GAAO,OAAA8D,EAAA,IAAU5C;AAOzB,UAJA+N,EAAa,UAAUjP,GACvBiE,EAAa,UAAUH,GAGnB9D,IAAQkP,GAAW;AAErB,cAAMC,IAAYjL,EAAUgL,CAAS;AACrC,YAAIC,KAAa,MAAM;AACrB,UAAAH,EAAkBG,CAAS;AAG3B,gBAAMC,IAAY,WAAW,MAAM;AACjC,YAAAJ,EAAkB,IAAI;AAAA,UACxB,GAAGzE,CAAiB;AAEpB,iBAAO,MAAM,aAAa6E,CAAS;AAAA,QACrC;AAAA,MACF;AAAA,IACF,GAAG,CAAClO,EAAgB,OAAOA,EAAgB,OAAOqJ,CAAiB,CAAC;AAEpE,UAAMlH,IAAgBzB,EAAM;AAAA,MAC1B,MAAM0M,GAAiBlL,GAAQlC,GAAiBqN,CAAc;AAAA,MAC9D,CAACnL,GAAQlC,GAAiBqN,CAAc;AAAA,IAAA,GAGpC3I,IAAsChE,EAAM;AAAA,MAChD,OAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,UAAU;AAAA,MAAA;AAAA,MAEZ,CAAA;AAAA,IAAC;AAGH,WACE,gBAAAc,EAAC,OAAA,EAAI,OAAOkD,GAAgB,8BAA0B,IACnD,UAAAvC,EAAc,IAAI,CAAC,EAAE,OAAAE,GAAO,OAAAvD,GAAO,WAAAqP,QAAgB;AAClD,YAAMzP,IAAWI,MAAUkB,EAAgB,SAAS,CAACmO,GAC/C5M,IAAU0D,IAAmB5C,EAAM,EAAE,KAAKA,EAAM;AAEtD,aACE,gBAAAb;AAAA,QAACwK;AAAA,QAAA;AAAA,UAEC,IAAI3J,EAAM;AAAA,UACV,OAAAvD;AAAA,UACA,iBAAiBkB,EAAgB;AAAA,UACjC,UAAAtB;AAAA,UACA,gBAAAuN;AAAA,UACA,eAAAzD;AAAA,UACA,cAAA6C;AAAA,UACA,gBAAAa;AAAA,UACA,mBAAA7C;AAAA,UACA,YAAA8C;AAAA,UACA,aAAAtN;AAAA,UACA,aAAAuN;AAAA,UAEC,UAAA7K;AAAA,QAAA;AAAA,QAdIc,EAAM;AAAA,MAAA;AAAA,IAiBjB,CAAC,EAAA,CACH;AAAA,EAEJ;AACF;"}
|
|
@@ -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>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Pure function for calculating sticky area metrics.
|
|
3
|
+
*
|
|
4
|
+
* Extracts the position-dependent calculation logic from StickyArea component
|
|
5
|
+
* to enable unit testing and reduce code duplication.
|
|
6
|
+
*/
|
|
7
|
+
import type { StickyAreaPosition } from "./types";
|
|
8
|
+
/** Output metrics for sticky area layout. */
|
|
9
|
+
export type StickyMetrics = {
|
|
10
|
+
coverAreaHeight: number;
|
|
11
|
+
isStuck: boolean;
|
|
12
|
+
scrollOffset: number;
|
|
13
|
+
};
|
|
14
|
+
/** Lightweight element bounding rect used for calculations. */
|
|
15
|
+
export type ElementRect = {
|
|
16
|
+
top: number;
|
|
17
|
+
bottom: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Calculate sticky area metrics based on element position and viewport.
|
|
22
|
+
*
|
|
23
|
+
* @param position - Whether the sticky area is at "top" or "bottom"
|
|
24
|
+
* @param rect - Element bounding rect (top, bottom, height)
|
|
25
|
+
* @param viewportHeight - Current viewport height
|
|
26
|
+
* @returns Calculated metrics for cover area height, stuck state, and scroll offset
|
|
27
|
+
*/
|
|
28
|
+
export declare function calculateStickyMetrics(position: StickyAreaPosition, rect: ElementRect, viewportHeight: number): StickyMetrics;
|
|
@@ -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
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("react/jsx-runtime"),M=require("react"),H=require("./useIsomorphicLayoutEffect-DGRNF4Lf.cjs");function q(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const o in t)if(o!=="default"){const i=Object.getOwnPropertyDescriptor(t,o);Object.defineProperty(e,o,i.get?i:{enumerable:!0,get:()=>t[o]})}}return e.default=t,Object.freeze(e)}const a=q(M);function F(t,e,o){if(t==="top")return{coverAreaHeight:Math.max(0,e.height+e.top),isStuck:e.top<0,scrollOffset:Math.max(0,-e.top)};const i=o-e.bottom;return{coverAreaHeight:Math.max(0,e.height+i),isStuck:e.bottom>o,scrollOffset:Math.max(0,e.bottom-o)}}function I(t){return{position:"relative",paddingTop:t==="top"?"env(safe-area-inset-top)":void 0,paddingBottom:t==="bottom"?"env(safe-area-inset-bottom)":void 0,boxSizing:"border-box"}}const w={zIndex:1};function z(t){return{opacity:0,zIndex:0,userSelect:"none",pointerEvents:"none",transition:"none",willChange:"height, opacity",transform:"translateZ(0)",position:"fixed",top:t==="top"?0:void 0,bottom:t==="bottom"?0:void 0,left:0,right:0}}const C={position:"relative"},f=({position:t="top",cover:e,children:o,onStateChange:i})=>{const d=a.useRef(null),p=a.useRef(null),[m,A]=a.useState({isStuck:!1,scrollOffset:0}),y=a.useRef(m),h=a.useCallback(n=>{const s=y.current;(s.isStuck!==n.isStuck||s.scrollOffset!==n.scrollOffset)&&(y.current=n,A(n),i?.(n))},[i]);H.useIsomorphicLayoutEffect(()=>{const n=d.current,s=p.current;if(!s||!n)return;let v=Number.NaN,b=Number.NaN,g=Number.NaN,S=!1,x=!0;const R=()=>{const r=n.getBoundingClientRect(),N=window.innerHeight,{coverAreaHeight:c,isStuck:u,scrollOffset:j}=F(t,r,N);c!==v&&(s.style.opacity=c>0?"1":"0",s.style.height=`${c}px`,v=c),(r.left!==b||r.width!==g)&&(s.style.left=`${r.left}px`,s.style.width=`${r.width}px`,b=r.left,g=r.width),(x?!0:u!==S)&&(x=!1,S=u,h({isStuck:u,scrollOffset:j}))};let k=requestAnimationFrame(function r(){R(),k=requestAnimationFrame(r)});return()=>{cancelAnimationFrame(k)}},[t,h]);const O=typeof o=="function"?o(m):o;return l.jsxs("div",{style:C,children:[l.jsx("div",{ref:p,style:z(t),children:e}),l.jsx("div",{ref:d,style:I(t),children:l.jsx("div",{style:w,children:O})})]})};f.displayName="StickyArea";const B=f;exports.StickyArea=f;exports.StickyHeader=B;
|
|
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/calculateStickyMetrics.ts","../src/sticky-header/StickyArea.tsx"],"sourcesContent":["/**\n * @file Pure function for calculating sticky area metrics.\n *\n * Extracts the position-dependent calculation logic from StickyArea component\n * to enable unit testing and reduce code duplication.\n */\nimport type { StickyAreaPosition } from \"./types\";\n\n/** Output metrics for sticky area layout. */\nexport type StickyMetrics = {\n coverAreaHeight: number;\n isStuck: boolean;\n scrollOffset: number;\n};\n\n/** Lightweight element bounding rect used for calculations. */\nexport type ElementRect = {\n top: number;\n bottom: number;\n height: number;\n};\n\n/**\n * Calculate sticky area metrics based on element position and viewport.\n *\n * @param position - Whether the sticky area is at \"top\" or \"bottom\"\n * @param rect - Element bounding rect (top, bottom, height)\n * @param viewportHeight - Current viewport height\n * @returns Calculated metrics for cover area height, stuck state, and scroll offset\n */\nexport function calculateStickyMetrics(\n position: StickyAreaPosition,\n rect: ElementRect,\n viewportHeight: number\n): StickyMetrics {\n if (position === \"top\") {\n return {\n coverAreaHeight: Math.max(0, rect.height + rect.top),\n isStuck: rect.top < 0,\n scrollOffset: Math.max(0, -rect.top),\n };\n }\n // bottom\n const distanceFromBottom = viewportHeight - rect.bottom;\n return {\n coverAreaHeight: Math.max(0, rect.height + distanceFromBottom),\n isStuck: rect.bottom > viewportHeight,\n scrollOffset: Math.max(0, rect.bottom - viewportHeight),\n };\n}\n","/**\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 { calculateStickyMetrics } from \"./calculateStickyMetrics\";\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 // Calculate metrics using pure function\n const { coverAreaHeight, isStuck, scrollOffset } = calculateStickyMetrics(\n position,\n liveRect,\n viewportHeight\n );\n\n // Update height/opacity\n if (coverAreaHeight !== prevHeight) {\n coverArea.style.opacity = coverAreaHeight > 0 ? \"1\" : \"0\";\n coverArea.style.height = `${coverAreaHeight}px`;\n prevHeight = coverAreaHeight;\n }\n\n // Update left/width\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 // Update state\n const shouldUpdateState = isFirstRun ? true : isStuck !== prevIsStuck;\n if (shouldUpdateState) {\n isFirstRun = false;\n prevIsStuck = isStuck;\n updateState({ isStuck, scrollOffset });\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":["calculateStickyMetrics","position","rect","viewportHeight","distanceFromBottom","getAreaStyle","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","coverAreaHeight","isStuck","scrollOffset","id","animate","renderedChildren","jsxs","jsx","StickyHeader"],"mappings":"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"}
|