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,1564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for SwipeStackContent component.
|
|
3
|
+
*
|
|
4
|
+
* These tests ensure:
|
|
5
|
+
* 1. Swipe preview: active panel follows finger, behind panel reveals with parallax
|
|
6
|
+
* 2. Swipe commit: when swipe completes, navigation changes and panels animate to final positions
|
|
7
|
+
* 3. Swipe cancel: when swipe is released without threshold, panels snap back
|
|
8
|
+
* 4. Button navigation: when navigationDepth changes without swipe, panels should animate
|
|
9
|
+
*/
|
|
10
|
+
import { render, act } from "@testing-library/react";
|
|
11
|
+
import { SwipeStackContent } from "./SwipeStackContent.js";
|
|
12
|
+
import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
|
|
13
|
+
|
|
14
|
+
// Mock requestAnimationFrame for animation testing
|
|
15
|
+
const rafState = {
|
|
16
|
+
callbacks: [] as FrameRequestCallback[],
|
|
17
|
+
id: 0,
|
|
18
|
+
mockTimestamp: 0,
|
|
19
|
+
originalRAF: globalThis.requestAnimationFrame,
|
|
20
|
+
originalCAF: globalThis.cancelAnimationFrame,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const resetRafState = (): void => {
|
|
24
|
+
rafState.callbacks = [];
|
|
25
|
+
rafState.id = 0;
|
|
26
|
+
rafState.mockTimestamp = 0;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const mockRAF = (callback: FrameRequestCallback): number => {
|
|
30
|
+
rafState.callbacks = [...rafState.callbacks, callback];
|
|
31
|
+
rafState.id += 1;
|
|
32
|
+
return rafState.id;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const mockCAF = (): void => {};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Flush all pending RAF callbacks.
|
|
39
|
+
* @param advanceMs - How much to advance the mock timestamp (default: 400ms to complete 300ms animation)
|
|
40
|
+
*/
|
|
41
|
+
const flushRAF = (advanceMs = 400): void => {
|
|
42
|
+
rafState.mockTimestamp += advanceMs;
|
|
43
|
+
const callbacks = rafState.callbacks;
|
|
44
|
+
rafState.callbacks = [];
|
|
45
|
+
callbacks.forEach((cb) => cb(rafState.mockTimestamp));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
resetRafState();
|
|
50
|
+
globalThis.requestAnimationFrame = mockRAF;
|
|
51
|
+
globalThis.cancelAnimationFrame = mockCAF;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
globalThis.requestAnimationFrame = rafState.originalRAF;
|
|
56
|
+
globalThis.cancelAnimationFrame = rafState.originalCAF;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const IDLE_STATE: ContinuousOperationState = {
|
|
60
|
+
phase: "idle",
|
|
61
|
+
displacement: { x: 0, y: 0 },
|
|
62
|
+
velocity: { x: 0, y: 0 },
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const createOperatingState = (displacementX: number): ContinuousOperationState => ({
|
|
66
|
+
phase: "operating",
|
|
67
|
+
displacement: { x: displacementX, y: 0 },
|
|
68
|
+
velocity: { x: 0.5, y: 0 },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("SwipeStackContent", () => {
|
|
72
|
+
describe("rendering", () => {
|
|
73
|
+
it("renders children correctly", () => {
|
|
74
|
+
const { getByText } = render(
|
|
75
|
+
<SwipeStackContent
|
|
76
|
+
id="test"
|
|
77
|
+
depth={0}
|
|
78
|
+
navigationDepth={0}
|
|
79
|
+
isActive={true}
|
|
80
|
+
operationState={IDLE_STATE}
|
|
81
|
+
containerSize={400}
|
|
82
|
+
>
|
|
83
|
+
<div>Test Content</div>
|
|
84
|
+
</SwipeStackContent>,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(getByText("Test Content")).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("applies correct data attributes", () => {
|
|
91
|
+
const { container } = render(
|
|
92
|
+
<SwipeStackContent
|
|
93
|
+
id="panel-1"
|
|
94
|
+
depth={1}
|
|
95
|
+
navigationDepth={1}
|
|
96
|
+
isActive={true}
|
|
97
|
+
operationState={IDLE_STATE}
|
|
98
|
+
containerSize={400}
|
|
99
|
+
>
|
|
100
|
+
Content
|
|
101
|
+
</SwipeStackContent>,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const element = container.firstChild as HTMLElement;
|
|
105
|
+
expect(element.getAttribute("data-stack-content")).toBe("panel-1");
|
|
106
|
+
expect(element.getAttribute("data-depth")).toBe("1");
|
|
107
|
+
expect(element.getAttribute("data-active")).toBe("true");
|
|
108
|
+
expect(element.getAttribute("data-role")).toBe("active");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("applies correct role for behind panel", () => {
|
|
112
|
+
const { container } = render(
|
|
113
|
+
<SwipeStackContent
|
|
114
|
+
id="panel-0"
|
|
115
|
+
depth={0}
|
|
116
|
+
navigationDepth={1}
|
|
117
|
+
isActive={false}
|
|
118
|
+
operationState={IDLE_STATE}
|
|
119
|
+
containerSize={400}
|
|
120
|
+
>
|
|
121
|
+
Content
|
|
122
|
+
</SwipeStackContent>,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const element = container.firstChild as HTMLElement;
|
|
126
|
+
expect(element.getAttribute("data-role")).toBe("behind");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("visibility", () => {
|
|
131
|
+
it("active panel is always visible", () => {
|
|
132
|
+
const { container } = render(
|
|
133
|
+
<SwipeStackContent
|
|
134
|
+
id="test"
|
|
135
|
+
depth={1}
|
|
136
|
+
navigationDepth={1}
|
|
137
|
+
isActive={true}
|
|
138
|
+
operationState={IDLE_STATE}
|
|
139
|
+
containerSize={400}
|
|
140
|
+
>
|
|
141
|
+
Content
|
|
142
|
+
</SwipeStackContent>,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const element = container.firstChild as HTMLElement;
|
|
146
|
+
expect(element.style.visibility).toBe("visible");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("behind panel is hidden when idle", () => {
|
|
150
|
+
const { container } = render(
|
|
151
|
+
<SwipeStackContent
|
|
152
|
+
id="test"
|
|
153
|
+
depth={0}
|
|
154
|
+
navigationDepth={1}
|
|
155
|
+
isActive={false}
|
|
156
|
+
operationState={IDLE_STATE}
|
|
157
|
+
containerSize={400}
|
|
158
|
+
>
|
|
159
|
+
Content
|
|
160
|
+
</SwipeStackContent>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const element = container.firstChild as HTMLElement;
|
|
164
|
+
expect(element.style.visibility).toBe("hidden");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("behind panel is visible when swiping", () => {
|
|
168
|
+
const { container } = render(
|
|
169
|
+
<SwipeStackContent
|
|
170
|
+
id="test"
|
|
171
|
+
depth={0}
|
|
172
|
+
navigationDepth={1}
|
|
173
|
+
isActive={false}
|
|
174
|
+
operationState={createOperatingState(100)}
|
|
175
|
+
containerSize={400}
|
|
176
|
+
>
|
|
177
|
+
Content
|
|
178
|
+
</SwipeStackContent>,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const element = container.firstChild as HTMLElement;
|
|
182
|
+
expect(element.style.visibility).toBe("visible");
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("transform during swipe", () => {
|
|
187
|
+
it("active panel follows displacement", () => {
|
|
188
|
+
const { container, rerender } = render(
|
|
189
|
+
<SwipeStackContent
|
|
190
|
+
id="test"
|
|
191
|
+
depth={1}
|
|
192
|
+
navigationDepth={1}
|
|
193
|
+
isActive={true}
|
|
194
|
+
operationState={IDLE_STATE}
|
|
195
|
+
containerSize={400}
|
|
196
|
+
>
|
|
197
|
+
Content
|
|
198
|
+
</SwipeStackContent>,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Start swiping
|
|
202
|
+
rerender(
|
|
203
|
+
<SwipeStackContent
|
|
204
|
+
id="test"
|
|
205
|
+
depth={1}
|
|
206
|
+
navigationDepth={1}
|
|
207
|
+
isActive={true}
|
|
208
|
+
operationState={createOperatingState(150)}
|
|
209
|
+
containerSize={400}
|
|
210
|
+
>
|
|
211
|
+
Content
|
|
212
|
+
</SwipeStackContent>,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const element = container.firstChild as HTMLElement;
|
|
216
|
+
expect(element.style.transform).toBe("translateX(150px)");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("behind panel uses parallax effect", () => {
|
|
220
|
+
const { container, rerender } = render(
|
|
221
|
+
<SwipeStackContent
|
|
222
|
+
id="test"
|
|
223
|
+
depth={0}
|
|
224
|
+
navigationDepth={1}
|
|
225
|
+
isActive={false}
|
|
226
|
+
operationState={IDLE_STATE}
|
|
227
|
+
containerSize={400}
|
|
228
|
+
>
|
|
229
|
+
Content
|
|
230
|
+
</SwipeStackContent>,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Start swiping - at 50% progress (200px), behind should move from -120 to -60
|
|
234
|
+
rerender(
|
|
235
|
+
<SwipeStackContent
|
|
236
|
+
id="test"
|
|
237
|
+
depth={0}
|
|
238
|
+
navigationDepth={1}
|
|
239
|
+
isActive={false}
|
|
240
|
+
operationState={createOperatingState(200)}
|
|
241
|
+
containerSize={400}
|
|
242
|
+
>
|
|
243
|
+
Content
|
|
244
|
+
</SwipeStackContent>,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const element = container.firstChild as HTMLElement;
|
|
248
|
+
// Behind panel should be at -60px (halfway from -120 to 0)
|
|
249
|
+
expect(element.style.transform).toBe("translateX(-60px)");
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("z-index", () => {
|
|
254
|
+
it("applies z-index based on depth", () => {
|
|
255
|
+
const { container } = render(
|
|
256
|
+
<SwipeStackContent
|
|
257
|
+
id="test"
|
|
258
|
+
depth={3}
|
|
259
|
+
navigationDepth={3}
|
|
260
|
+
isActive={true}
|
|
261
|
+
operationState={IDLE_STATE}
|
|
262
|
+
containerSize={400}
|
|
263
|
+
>
|
|
264
|
+
Content
|
|
265
|
+
</SwipeStackContent>,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const element = container.firstChild as HTMLElement;
|
|
269
|
+
expect(element.style.zIndex).toBe("3");
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("pointer events", () => {
|
|
274
|
+
it("active panel has auto pointer events", () => {
|
|
275
|
+
const { container } = render(
|
|
276
|
+
<SwipeStackContent
|
|
277
|
+
id="test"
|
|
278
|
+
depth={1}
|
|
279
|
+
navigationDepth={1}
|
|
280
|
+
isActive={true}
|
|
281
|
+
operationState={IDLE_STATE}
|
|
282
|
+
containerSize={400}
|
|
283
|
+
>
|
|
284
|
+
Content
|
|
285
|
+
</SwipeStackContent>,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const element = container.firstChild as HTMLElement;
|
|
289
|
+
expect(element.style.pointerEvents).toBe("auto");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("inactive panel has none pointer events", () => {
|
|
293
|
+
const { container } = render(
|
|
294
|
+
<SwipeStackContent
|
|
295
|
+
id="test"
|
|
296
|
+
depth={0}
|
|
297
|
+
navigationDepth={1}
|
|
298
|
+
isActive={false}
|
|
299
|
+
operationState={IDLE_STATE}
|
|
300
|
+
containerSize={400}
|
|
301
|
+
>
|
|
302
|
+
Content
|
|
303
|
+
</SwipeStackContent>,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const element = container.firstChild as HTMLElement;
|
|
307
|
+
expect(element.style.pointerEvents).toBe("none");
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe("swipe preview behavior", () => {
|
|
312
|
+
it("active panel position changes immediately with displacement during swiping", () => {
|
|
313
|
+
const { container, rerender } = render(
|
|
314
|
+
<SwipeStackContent
|
|
315
|
+
id="active"
|
|
316
|
+
depth={1}
|
|
317
|
+
navigationDepth={1}
|
|
318
|
+
isActive={true}
|
|
319
|
+
operationState={IDLE_STATE}
|
|
320
|
+
containerSize={400}
|
|
321
|
+
>
|
|
322
|
+
Content
|
|
323
|
+
</SwipeStackContent>,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const element = container.firstChild as HTMLElement;
|
|
327
|
+
|
|
328
|
+
// Initial position
|
|
329
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
330
|
+
|
|
331
|
+
// Start tracking (before direction lock) - displacement is still applied
|
|
332
|
+
// because we want responsive feedback even before direction is confirmed
|
|
333
|
+
rerender(
|
|
334
|
+
<SwipeStackContent
|
|
335
|
+
id="active"
|
|
336
|
+
depth={1}
|
|
337
|
+
navigationDepth={1}
|
|
338
|
+
isActive={true}
|
|
339
|
+
operationState={createOperatingState(50)}
|
|
340
|
+
containerSize={400}
|
|
341
|
+
>
|
|
342
|
+
Content
|
|
343
|
+
</SwipeStackContent>,
|
|
344
|
+
);
|
|
345
|
+
// Tracking phase also applies displacement for responsive feedback
|
|
346
|
+
expect(element.style.transform).toBe("translateX(50px)");
|
|
347
|
+
|
|
348
|
+
// Swipe confirmed (direction locked)
|
|
349
|
+
rerender(
|
|
350
|
+
<SwipeStackContent
|
|
351
|
+
id="active"
|
|
352
|
+
depth={1}
|
|
353
|
+
navigationDepth={1}
|
|
354
|
+
isActive={true}
|
|
355
|
+
operationState={createOperatingState(100)}
|
|
356
|
+
containerSize={400}
|
|
357
|
+
>
|
|
358
|
+
Content
|
|
359
|
+
</SwipeStackContent>,
|
|
360
|
+
);
|
|
361
|
+
expect(element.style.transform).toBe("translateX(100px)");
|
|
362
|
+
|
|
363
|
+
// Continue swiping
|
|
364
|
+
rerender(
|
|
365
|
+
<SwipeStackContent
|
|
366
|
+
id="active"
|
|
367
|
+
depth={1}
|
|
368
|
+
navigationDepth={1}
|
|
369
|
+
isActive={true}
|
|
370
|
+
operationState={createOperatingState(250)}
|
|
371
|
+
containerSize={400}
|
|
372
|
+
>
|
|
373
|
+
Content
|
|
374
|
+
</SwipeStackContent>,
|
|
375
|
+
);
|
|
376
|
+
expect(element.style.transform).toBe("translateX(250px)");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it("behind panel reveals progressively during swipe with parallax effect", () => {
|
|
380
|
+
const { container, rerender } = render(
|
|
381
|
+
<SwipeStackContent
|
|
382
|
+
id="behind"
|
|
383
|
+
depth={0}
|
|
384
|
+
navigationDepth={1}
|
|
385
|
+
isActive={false}
|
|
386
|
+
operationState={IDLE_STATE}
|
|
387
|
+
containerSize={400}
|
|
388
|
+
>
|
|
389
|
+
Content
|
|
390
|
+
</SwipeStackContent>,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const element = container.firstChild as HTMLElement;
|
|
394
|
+
|
|
395
|
+
// Initial: behind panel at -30% = -120px
|
|
396
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
397
|
+
expect(element.style.visibility).toBe("hidden");
|
|
398
|
+
|
|
399
|
+
// Swipe 25% (100px of 400px)
|
|
400
|
+
rerender(
|
|
401
|
+
<SwipeStackContent
|
|
402
|
+
id="behind"
|
|
403
|
+
depth={0}
|
|
404
|
+
navigationDepth={1}
|
|
405
|
+
isActive={false}
|
|
406
|
+
operationState={createOperatingState(100)}
|
|
407
|
+
containerSize={400}
|
|
408
|
+
>
|
|
409
|
+
Content
|
|
410
|
+
</SwipeStackContent>,
|
|
411
|
+
);
|
|
412
|
+
// Behind moves 25% of 120px = 30px, so from -120 to -90
|
|
413
|
+
expect(element.style.transform).toBe("translateX(-90px)");
|
|
414
|
+
expect(element.style.visibility).toBe("visible");
|
|
415
|
+
|
|
416
|
+
// Swipe 50% (200px)
|
|
417
|
+
rerender(
|
|
418
|
+
<SwipeStackContent
|
|
419
|
+
id="behind"
|
|
420
|
+
depth={0}
|
|
421
|
+
navigationDepth={1}
|
|
422
|
+
isActive={false}
|
|
423
|
+
operationState={createOperatingState(200)}
|
|
424
|
+
containerSize={400}
|
|
425
|
+
>
|
|
426
|
+
Content
|
|
427
|
+
</SwipeStackContent>,
|
|
428
|
+
);
|
|
429
|
+
expect(element.style.transform).toBe("translateX(-60px)");
|
|
430
|
+
|
|
431
|
+
// Swipe 100% (400px) - behind should be at 0
|
|
432
|
+
rerender(
|
|
433
|
+
<SwipeStackContent
|
|
434
|
+
id="behind"
|
|
435
|
+
depth={0}
|
|
436
|
+
navigationDepth={1}
|
|
437
|
+
isActive={false}
|
|
438
|
+
operationState={createOperatingState(400)}
|
|
439
|
+
containerSize={400}
|
|
440
|
+
>
|
|
441
|
+
Content
|
|
442
|
+
</SwipeStackContent>,
|
|
443
|
+
);
|
|
444
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("does not respond to negative displacement (swiping wrong direction)", () => {
|
|
448
|
+
const { container, rerender } = render(
|
|
449
|
+
<SwipeStackContent
|
|
450
|
+
id="active"
|
|
451
|
+
depth={1}
|
|
452
|
+
navigationDepth={1}
|
|
453
|
+
isActive={true}
|
|
454
|
+
operationState={IDLE_STATE}
|
|
455
|
+
containerSize={400}
|
|
456
|
+
>
|
|
457
|
+
Content
|
|
458
|
+
</SwipeStackContent>,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const element = container.firstChild as HTMLElement;
|
|
462
|
+
|
|
463
|
+
// Swipe left (negative) should be clamped to 0
|
|
464
|
+
rerender(
|
|
465
|
+
<SwipeStackContent
|
|
466
|
+
id="active"
|
|
467
|
+
depth={1}
|
|
468
|
+
navigationDepth={1}
|
|
469
|
+
isActive={true}
|
|
470
|
+
operationState={createOperatingState(-100)}
|
|
471
|
+
containerSize={400}
|
|
472
|
+
>
|
|
473
|
+
Content
|
|
474
|
+
</SwipeStackContent>,
|
|
475
|
+
);
|
|
476
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
describe("swipe release and snap-back", () => {
|
|
481
|
+
it("returns to original position when swipe ends without navigation change", () => {
|
|
482
|
+
const { container, rerender } = render(
|
|
483
|
+
<SwipeStackContent
|
|
484
|
+
id="active"
|
|
485
|
+
depth={1}
|
|
486
|
+
navigationDepth={1}
|
|
487
|
+
isActive={true}
|
|
488
|
+
operationState={IDLE_STATE}
|
|
489
|
+
containerSize={400}
|
|
490
|
+
>
|
|
491
|
+
Content
|
|
492
|
+
</SwipeStackContent>,
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
const element = container.firstChild as HTMLElement;
|
|
496
|
+
|
|
497
|
+
// Swipe in progress
|
|
498
|
+
rerender(
|
|
499
|
+
<SwipeStackContent
|
|
500
|
+
id="active"
|
|
501
|
+
depth={1}
|
|
502
|
+
navigationDepth={1}
|
|
503
|
+
isActive={true}
|
|
504
|
+
operationState={createOperatingState(150)}
|
|
505
|
+
containerSize={400}
|
|
506
|
+
>
|
|
507
|
+
Content
|
|
508
|
+
</SwipeStackContent>,
|
|
509
|
+
);
|
|
510
|
+
expect(element.style.transform).toBe("translateX(150px)");
|
|
511
|
+
|
|
512
|
+
// Swipe released (back to idle) - animation starts
|
|
513
|
+
rerender(
|
|
514
|
+
<SwipeStackContent
|
|
515
|
+
id="active"
|
|
516
|
+
depth={1}
|
|
517
|
+
navigationDepth={1}
|
|
518
|
+
isActive={true}
|
|
519
|
+
operationState={IDLE_STATE}
|
|
520
|
+
containerSize={400}
|
|
521
|
+
>
|
|
522
|
+
Content
|
|
523
|
+
</SwipeStackContent>,
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Flush RAF within act() to complete the snap-back animation
|
|
527
|
+
act(() => {
|
|
528
|
+
flushRAF(0); // First flush starts the animation (sets startTime)
|
|
529
|
+
flushRAF(400); // Second flush completes the animation (elapsed > duration)
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// After animation completes, should be at 0
|
|
533
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it("behind panel returns to hidden position when swipe is canceled", () => {
|
|
537
|
+
const { container, rerender } = render(
|
|
538
|
+
<SwipeStackContent
|
|
539
|
+
id="behind"
|
|
540
|
+
depth={0}
|
|
541
|
+
navigationDepth={1}
|
|
542
|
+
isActive={false}
|
|
543
|
+
operationState={IDLE_STATE}
|
|
544
|
+
containerSize={400}
|
|
545
|
+
>
|
|
546
|
+
Content
|
|
547
|
+
</SwipeStackContent>,
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const element = container.firstChild as HTMLElement;
|
|
551
|
+
|
|
552
|
+
// Swipe in progress
|
|
553
|
+
rerender(
|
|
554
|
+
<SwipeStackContent
|
|
555
|
+
id="behind"
|
|
556
|
+
depth={0}
|
|
557
|
+
navigationDepth={1}
|
|
558
|
+
isActive={false}
|
|
559
|
+
operationState={createOperatingState(200)}
|
|
560
|
+
containerSize={400}
|
|
561
|
+
>
|
|
562
|
+
Content
|
|
563
|
+
</SwipeStackContent>,
|
|
564
|
+
);
|
|
565
|
+
expect(element.style.transform).toBe("translateX(-60px)");
|
|
566
|
+
expect(element.style.visibility).toBe("visible");
|
|
567
|
+
|
|
568
|
+
// Swipe canceled - animation starts
|
|
569
|
+
rerender(
|
|
570
|
+
<SwipeStackContent
|
|
571
|
+
id="behind"
|
|
572
|
+
depth={0}
|
|
573
|
+
navigationDepth={1}
|
|
574
|
+
isActive={false}
|
|
575
|
+
operationState={IDLE_STATE}
|
|
576
|
+
containerSize={400}
|
|
577
|
+
>
|
|
578
|
+
Content
|
|
579
|
+
</SwipeStackContent>,
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
// Flush RAF within act() to complete the snap-back animation
|
|
583
|
+
act(() => {
|
|
584
|
+
flushRAF(0); // First flush starts the animation (sets startTime)
|
|
585
|
+
flushRAF(400); // Second flush completes the animation (elapsed > duration)
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// After animation completes, should be at -120px
|
|
589
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe("button navigation (without swipe)", () => {
|
|
594
|
+
it("animates behind panel to center when going back via button", () => {
|
|
595
|
+
const { container, rerender } = render(
|
|
596
|
+
<SwipeStackContent
|
|
597
|
+
id="panel"
|
|
598
|
+
depth={0}
|
|
599
|
+
navigationDepth={1}
|
|
600
|
+
isActive={false}
|
|
601
|
+
operationState={IDLE_STATE}
|
|
602
|
+
containerSize={400}
|
|
603
|
+
>
|
|
604
|
+
Content
|
|
605
|
+
</SwipeStackContent>,
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
const element = container.firstChild as HTMLElement;
|
|
609
|
+
// Initial: behind panel at -30% = -120px
|
|
610
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
611
|
+
|
|
612
|
+
// Button click triggers navigation change: depth 0 becomes active
|
|
613
|
+
rerender(
|
|
614
|
+
<SwipeStackContent
|
|
615
|
+
id="panel"
|
|
616
|
+
depth={0}
|
|
617
|
+
navigationDepth={0}
|
|
618
|
+
isActive={true}
|
|
619
|
+
operationState={IDLE_STATE}
|
|
620
|
+
containerSize={400}
|
|
621
|
+
>
|
|
622
|
+
Content
|
|
623
|
+
</SwipeStackContent>,
|
|
624
|
+
);
|
|
625
|
+
|
|
626
|
+
// Animation should start: still at -120px immediately
|
|
627
|
+
// (not snapping directly to 0)
|
|
628
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
629
|
+
|
|
630
|
+
// Flush RAF to complete animation
|
|
631
|
+
act(() => {
|
|
632
|
+
flushRAF(0);
|
|
633
|
+
flushRAF(400);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// After animation completes, should be at 0
|
|
637
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("animates active panel off-screen when going back via button", () => {
|
|
641
|
+
const { container, rerender } = render(
|
|
642
|
+
<SwipeStackContent
|
|
643
|
+
id="panel"
|
|
644
|
+
depth={1}
|
|
645
|
+
navigationDepth={1}
|
|
646
|
+
isActive={true}
|
|
647
|
+
operationState={IDLE_STATE}
|
|
648
|
+
containerSize={400}
|
|
649
|
+
>
|
|
650
|
+
Content
|
|
651
|
+
</SwipeStackContent>,
|
|
652
|
+
);
|
|
653
|
+
|
|
654
|
+
const element = container.firstChild as HTMLElement;
|
|
655
|
+
// Initial: active panel at 0
|
|
656
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
657
|
+
|
|
658
|
+
// Button click triggers navigation change: depth 1 becomes hidden
|
|
659
|
+
rerender(
|
|
660
|
+
<SwipeStackContent
|
|
661
|
+
id="panel"
|
|
662
|
+
depth={1}
|
|
663
|
+
navigationDepth={0}
|
|
664
|
+
isActive={false}
|
|
665
|
+
operationState={IDLE_STATE}
|
|
666
|
+
containerSize={400}
|
|
667
|
+
>
|
|
668
|
+
Content
|
|
669
|
+
</SwipeStackContent>,
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
// Animation should start: still at 0px immediately
|
|
673
|
+
// (not snapping directly to 400)
|
|
674
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
675
|
+
|
|
676
|
+
// Flush RAF to complete animation
|
|
677
|
+
act(() => {
|
|
678
|
+
flushRAF(0);
|
|
679
|
+
flushRAF(400);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// After animation completes, should be at 400px (off-screen)
|
|
683
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
describe("forward navigation (push)", () => {
|
|
688
|
+
it("new panel animates in from off-screen when first mounted as active with animateOnMount", () => {
|
|
689
|
+
// Scenario: Panel is mounted for the first time as active (push navigation)
|
|
690
|
+
// This is what happens when visiblePanelIds changes to include the new panel
|
|
691
|
+
const { container } = render(
|
|
692
|
+
<SwipeStackContent
|
|
693
|
+
id="newPanel"
|
|
694
|
+
depth={1}
|
|
695
|
+
navigationDepth={1}
|
|
696
|
+
isActive={true}
|
|
697
|
+
operationState={IDLE_STATE}
|
|
698
|
+
containerSize={400}
|
|
699
|
+
animateOnMount={true}
|
|
700
|
+
>
|
|
701
|
+
Content
|
|
702
|
+
</SwipeStackContent>,
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
const element = container.firstChild as HTMLElement;
|
|
706
|
+
|
|
707
|
+
// On first mount as active with animateOnMount, panel should start at off-screen position
|
|
708
|
+
// (not at 0, which would cause a jarring pop-in)
|
|
709
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
710
|
+
|
|
711
|
+
// Flush RAF to complete the entrance animation
|
|
712
|
+
act(() => {
|
|
713
|
+
flushRAF(0);
|
|
714
|
+
flushRAF(400);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// After animation: at center (0)
|
|
718
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it("pre-existing hidden panel animates in when pushed", () => {
|
|
722
|
+
// Scenario: Panel already exists but was hidden, then becomes active
|
|
723
|
+
const { container, rerender } = render(
|
|
724
|
+
<SwipeStackContent
|
|
725
|
+
id="newPanel"
|
|
726
|
+
depth={1}
|
|
727
|
+
navigationDepth={0}
|
|
728
|
+
isActive={false}
|
|
729
|
+
operationState={IDLE_STATE}
|
|
730
|
+
containerSize={400}
|
|
731
|
+
>
|
|
732
|
+
Content
|
|
733
|
+
</SwipeStackContent>,
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const element = container.firstChild as HTMLElement;
|
|
737
|
+
// Hidden panel should be at containerSize (off-screen right)
|
|
738
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
739
|
+
|
|
740
|
+
// Push navigation: panel becomes active
|
|
741
|
+
rerender(
|
|
742
|
+
<SwipeStackContent
|
|
743
|
+
id="newPanel"
|
|
744
|
+
depth={1}
|
|
745
|
+
navigationDepth={1}
|
|
746
|
+
isActive={true}
|
|
747
|
+
operationState={IDLE_STATE}
|
|
748
|
+
containerSize={400}
|
|
749
|
+
>
|
|
750
|
+
Content
|
|
751
|
+
</SwipeStackContent>,
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
// Animation should start: still at 400px immediately (not snapping to 0)
|
|
755
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
756
|
+
|
|
757
|
+
// Flush RAF to complete animation
|
|
758
|
+
act(() => {
|
|
759
|
+
flushRAF(0);
|
|
760
|
+
flushRAF(400);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// After animation: at center (0)
|
|
764
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it("behind panel animates to offset position when new panel is pushed", () => {
|
|
768
|
+
// First render: panel at depth 0 is active
|
|
769
|
+
const { container, rerender } = render(
|
|
770
|
+
<SwipeStackContent
|
|
771
|
+
id="currentPanel"
|
|
772
|
+
depth={0}
|
|
773
|
+
navigationDepth={0}
|
|
774
|
+
isActive={true}
|
|
775
|
+
operationState={IDLE_STATE}
|
|
776
|
+
containerSize={400}
|
|
777
|
+
>
|
|
778
|
+
Content
|
|
779
|
+
</SwipeStackContent>,
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
const element = container.firstChild as HTMLElement;
|
|
783
|
+
// Active panel at center
|
|
784
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
785
|
+
|
|
786
|
+
// Push navigation: this panel becomes "behind"
|
|
787
|
+
rerender(
|
|
788
|
+
<SwipeStackContent
|
|
789
|
+
id="currentPanel"
|
|
790
|
+
depth={0}
|
|
791
|
+
navigationDepth={1}
|
|
792
|
+
isActive={false}
|
|
793
|
+
operationState={IDLE_STATE}
|
|
794
|
+
containerSize={400}
|
|
795
|
+
>
|
|
796
|
+
Content
|
|
797
|
+
</SwipeStackContent>,
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
// Animation should start: still at 0px immediately (not snapping to -120px)
|
|
801
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
802
|
+
|
|
803
|
+
// Flush RAF to complete animation
|
|
804
|
+
act(() => {
|
|
805
|
+
flushRAF(0);
|
|
806
|
+
flushRAF(400);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// After animation: at behind offset (-30% = -120px)
|
|
810
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
describe("mount → push → back sequence", () => {
|
|
815
|
+
it("panel animates from correct position after push then back", () => {
|
|
816
|
+
// This test covers the bug where:
|
|
817
|
+
// 1. Mount: root panel at depth 0, active
|
|
818
|
+
// 2. Push: new panel at depth 1 is mounted with animateOnMount
|
|
819
|
+
// 3. Back: the panel at depth 1 should animate from its current position (0) to off-screen
|
|
820
|
+
|
|
821
|
+
// Step 1: Mount panel at depth 1 as active (simulating push navigation)
|
|
822
|
+
const { container, rerender } = render(
|
|
823
|
+
<SwipeStackContent
|
|
824
|
+
id="panel1"
|
|
825
|
+
depth={1}
|
|
826
|
+
navigationDepth={1}
|
|
827
|
+
isActive={true}
|
|
828
|
+
operationState={IDLE_STATE}
|
|
829
|
+
containerSize={400}
|
|
830
|
+
animateOnMount={true}
|
|
831
|
+
>
|
|
832
|
+
Content
|
|
833
|
+
</SwipeStackContent>,
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
const element = container.firstChild as HTMLElement;
|
|
837
|
+
|
|
838
|
+
// Initially at off-screen (400px) due to animateOnMount
|
|
839
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
840
|
+
|
|
841
|
+
// Complete the push animation
|
|
842
|
+
act(() => {
|
|
843
|
+
flushRAF(0);
|
|
844
|
+
flushRAF(400);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// After push animation: should be at center (0)
|
|
848
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
849
|
+
|
|
850
|
+
// Step 2: Go back - navigation depth changes, panel becomes hidden
|
|
851
|
+
rerender(
|
|
852
|
+
<SwipeStackContent
|
|
853
|
+
id="panel1"
|
|
854
|
+
depth={1}
|
|
855
|
+
navigationDepth={0}
|
|
856
|
+
isActive={false}
|
|
857
|
+
operationState={IDLE_STATE}
|
|
858
|
+
containerSize={400}
|
|
859
|
+
animateOnMount={true}
|
|
860
|
+
>
|
|
861
|
+
Content
|
|
862
|
+
</SwipeStackContent>,
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
// Animation should start from current position (0px), not from initialPx (400px)
|
|
866
|
+
// The bug is that it might jump to 400px immediately because of animateOnMount logic
|
|
867
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
868
|
+
|
|
869
|
+
// Complete the back animation
|
|
870
|
+
act(() => {
|
|
871
|
+
flushRAF(0);
|
|
872
|
+
flushRAF(400);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// After back animation: should be off-screen (400px)
|
|
876
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
it("behind panel animates correctly in mount → push → back sequence", () => {
|
|
880
|
+
// Track the behind panel (depth 0) through the sequence
|
|
881
|
+
|
|
882
|
+
// Step 1: Mount as active
|
|
883
|
+
const { container, rerender } = render(
|
|
884
|
+
<SwipeStackContent
|
|
885
|
+
id="rootPanel"
|
|
886
|
+
depth={0}
|
|
887
|
+
navigationDepth={0}
|
|
888
|
+
isActive={true}
|
|
889
|
+
operationState={IDLE_STATE}
|
|
890
|
+
containerSize={400}
|
|
891
|
+
>
|
|
892
|
+
Content
|
|
893
|
+
</SwipeStackContent>,
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
const element = container.firstChild as HTMLElement;
|
|
897
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
898
|
+
|
|
899
|
+
// Step 2: Push - this panel becomes "behind"
|
|
900
|
+
rerender(
|
|
901
|
+
<SwipeStackContent
|
|
902
|
+
id="rootPanel"
|
|
903
|
+
depth={0}
|
|
904
|
+
navigationDepth={1}
|
|
905
|
+
isActive={false}
|
|
906
|
+
operationState={IDLE_STATE}
|
|
907
|
+
containerSize={400}
|
|
908
|
+
>
|
|
909
|
+
Content
|
|
910
|
+
</SwipeStackContent>,
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
// Animation starts from 0px
|
|
914
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
915
|
+
|
|
916
|
+
act(() => {
|
|
917
|
+
flushRAF(0);
|
|
918
|
+
flushRAF(400);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// After animation: at behind offset (-120px)
|
|
922
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
923
|
+
|
|
924
|
+
// Step 3: Back - this panel becomes active again
|
|
925
|
+
rerender(
|
|
926
|
+
<SwipeStackContent
|
|
927
|
+
id="rootPanel"
|
|
928
|
+
depth={0}
|
|
929
|
+
navigationDepth={0}
|
|
930
|
+
isActive={true}
|
|
931
|
+
operationState={IDLE_STATE}
|
|
932
|
+
containerSize={400}
|
|
933
|
+
>
|
|
934
|
+
Content
|
|
935
|
+
</SwipeStackContent>,
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
// Animation should start from current position (-120px)
|
|
939
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
940
|
+
|
|
941
|
+
act(() => {
|
|
942
|
+
flushRAF(0);
|
|
943
|
+
flushRAF(400);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// After animation: at center (0)
|
|
947
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
describe("shadow effects", () => {
|
|
952
|
+
it("shows shadow on active panel at depth > 0 by default", () => {
|
|
953
|
+
const { container } = render(
|
|
954
|
+
<SwipeStackContent
|
|
955
|
+
id="panel"
|
|
956
|
+
depth={1}
|
|
957
|
+
navigationDepth={1}
|
|
958
|
+
isActive={true}
|
|
959
|
+
operationState={IDLE_STATE}
|
|
960
|
+
containerSize={400}
|
|
961
|
+
>
|
|
962
|
+
Content
|
|
963
|
+
</SwipeStackContent>,
|
|
964
|
+
);
|
|
965
|
+
|
|
966
|
+
const element = container.firstChild as HTMLElement;
|
|
967
|
+
expect(element.style.boxShadow).toBe("-5px 0 15px rgba(0, 0, 0, 0.1)");
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it("does not show shadow on root panel (depth 0)", () => {
|
|
971
|
+
const { container } = render(
|
|
972
|
+
<SwipeStackContent
|
|
973
|
+
id="panel"
|
|
974
|
+
depth={0}
|
|
975
|
+
navigationDepth={0}
|
|
976
|
+
isActive={true}
|
|
977
|
+
operationState={IDLE_STATE}
|
|
978
|
+
containerSize={400}
|
|
979
|
+
>
|
|
980
|
+
Content
|
|
981
|
+
</SwipeStackContent>,
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
const element = container.firstChild as HTMLElement;
|
|
985
|
+
expect(element.style.boxShadow).toBe("");
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
it("does not show shadow on behind panel", () => {
|
|
989
|
+
const { container } = render(
|
|
990
|
+
<SwipeStackContent
|
|
991
|
+
id="panel"
|
|
992
|
+
depth={0}
|
|
993
|
+
navigationDepth={1}
|
|
994
|
+
isActive={false}
|
|
995
|
+
operationState={IDLE_STATE}
|
|
996
|
+
containerSize={400}
|
|
997
|
+
>
|
|
998
|
+
Content
|
|
999
|
+
</SwipeStackContent>,
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
const element = container.firstChild as HTMLElement;
|
|
1003
|
+
expect(element.style.boxShadow).toBe("");
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it("can disable shadow with showShadow=false", () => {
|
|
1007
|
+
const { container } = render(
|
|
1008
|
+
<SwipeStackContent
|
|
1009
|
+
id="panel"
|
|
1010
|
+
depth={1}
|
|
1011
|
+
navigationDepth={1}
|
|
1012
|
+
isActive={true}
|
|
1013
|
+
operationState={IDLE_STATE}
|
|
1014
|
+
containerSize={400}
|
|
1015
|
+
showShadow={false}
|
|
1016
|
+
>
|
|
1017
|
+
Content
|
|
1018
|
+
</SwipeStackContent>,
|
|
1019
|
+
);
|
|
1020
|
+
|
|
1021
|
+
const element = container.firstChild as HTMLElement;
|
|
1022
|
+
expect(element.style.boxShadow).toBe("");
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
describe("scale effects (stack displayMode)", () => {
|
|
1027
|
+
it("behind panel has scale < 1 in stack mode", () => {
|
|
1028
|
+
const { container } = render(
|
|
1029
|
+
<SwipeStackContent
|
|
1030
|
+
id="panel"
|
|
1031
|
+
depth={0}
|
|
1032
|
+
navigationDepth={1}
|
|
1033
|
+
isActive={false}
|
|
1034
|
+
operationState={IDLE_STATE}
|
|
1035
|
+
containerSize={400}
|
|
1036
|
+
displayMode="stack"
|
|
1037
|
+
>
|
|
1038
|
+
Content
|
|
1039
|
+
</SwipeStackContent>,
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
const element = container.firstChild as HTMLElement;
|
|
1043
|
+
// Transform should include scale(0.95) for 1 level behind
|
|
1044
|
+
expect(element.style.transform).toContain("scale(0.95)");
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it("active panel has no scale in stack mode", () => {
|
|
1048
|
+
const { container } = render(
|
|
1049
|
+
<SwipeStackContent
|
|
1050
|
+
id="panel"
|
|
1051
|
+
depth={1}
|
|
1052
|
+
navigationDepth={1}
|
|
1053
|
+
isActive={true}
|
|
1054
|
+
operationState={IDLE_STATE}
|
|
1055
|
+
containerSize={400}
|
|
1056
|
+
displayMode="stack"
|
|
1057
|
+
>
|
|
1058
|
+
Content
|
|
1059
|
+
</SwipeStackContent>,
|
|
1060
|
+
);
|
|
1061
|
+
|
|
1062
|
+
const element = container.firstChild as HTMLElement;
|
|
1063
|
+
// Active panel should not have scale transform (or scale(1))
|
|
1064
|
+
expect(element.style.transform).not.toContain("scale(0.");
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
it("no scale effect in overlay mode", () => {
|
|
1068
|
+
const { container } = render(
|
|
1069
|
+
<SwipeStackContent
|
|
1070
|
+
id="panel"
|
|
1071
|
+
depth={0}
|
|
1072
|
+
navigationDepth={1}
|
|
1073
|
+
isActive={false}
|
|
1074
|
+
operationState={IDLE_STATE}
|
|
1075
|
+
containerSize={400}
|
|
1076
|
+
displayMode="overlay"
|
|
1077
|
+
>
|
|
1078
|
+
Content
|
|
1079
|
+
</SwipeStackContent>,
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
const element = container.firstChild as HTMLElement;
|
|
1083
|
+
// No scale in overlay mode
|
|
1084
|
+
expect(element.style.transform).not.toContain("scale");
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
describe("dimming overlay", () => {
|
|
1089
|
+
it("shows dimming overlay on behind panel by default", () => {
|
|
1090
|
+
const { container } = render(
|
|
1091
|
+
<SwipeStackContent
|
|
1092
|
+
id="panel"
|
|
1093
|
+
depth={0}
|
|
1094
|
+
navigationDepth={1}
|
|
1095
|
+
isActive={false}
|
|
1096
|
+
operationState={IDLE_STATE}
|
|
1097
|
+
containerSize={400}
|
|
1098
|
+
>
|
|
1099
|
+
Content
|
|
1100
|
+
</SwipeStackContent>,
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
const overlay = container.querySelector("[data-dimming-overlay]");
|
|
1104
|
+
expect(overlay).toBeInTheDocument();
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it("no dimming overlay on active panel", () => {
|
|
1108
|
+
const { container } = render(
|
|
1109
|
+
<SwipeStackContent
|
|
1110
|
+
id="panel"
|
|
1111
|
+
depth={1}
|
|
1112
|
+
navigationDepth={1}
|
|
1113
|
+
isActive={true}
|
|
1114
|
+
operationState={IDLE_STATE}
|
|
1115
|
+
containerSize={400}
|
|
1116
|
+
>
|
|
1117
|
+
Content
|
|
1118
|
+
</SwipeStackContent>,
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
const overlay = container.querySelector("[data-dimming-overlay]");
|
|
1122
|
+
expect(overlay).not.toBeInTheDocument();
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
it("dimming fades during swipe", () => {
|
|
1126
|
+
const { container, rerender } = render(
|
|
1127
|
+
<SwipeStackContent
|
|
1128
|
+
id="panel"
|
|
1129
|
+
depth={0}
|
|
1130
|
+
navigationDepth={1}
|
|
1131
|
+
isActive={false}
|
|
1132
|
+
operationState={IDLE_STATE}
|
|
1133
|
+
containerSize={400}
|
|
1134
|
+
>
|
|
1135
|
+
Content
|
|
1136
|
+
</SwipeStackContent>,
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
// At rest, should have dimming
|
|
1140
|
+
const initialOverlay = container.querySelector("[data-dimming-overlay]") as HTMLElement;
|
|
1141
|
+
expect(initialOverlay).toBeInTheDocument();
|
|
1142
|
+
|
|
1143
|
+
// During swipe at 100% progress, dimming should be gone
|
|
1144
|
+
rerender(
|
|
1145
|
+
<SwipeStackContent
|
|
1146
|
+
id="panel"
|
|
1147
|
+
depth={0}
|
|
1148
|
+
navigationDepth={1}
|
|
1149
|
+
isActive={false}
|
|
1150
|
+
operationState={createOperatingState(400)}
|
|
1151
|
+
containerSize={400}
|
|
1152
|
+
>
|
|
1153
|
+
Content
|
|
1154
|
+
</SwipeStackContent>,
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
const overlayAfter = container.querySelector("[data-dimming-overlay]") as HTMLElement;
|
|
1158
|
+
// At 100% swipe, opacity should be 0 (no overlay)
|
|
1159
|
+
expect(overlayAfter).not.toBeInTheDocument();
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
it("can disable dimming with showDimming=false", () => {
|
|
1163
|
+
const { container } = render(
|
|
1164
|
+
<SwipeStackContent
|
|
1165
|
+
id="panel"
|
|
1166
|
+
depth={0}
|
|
1167
|
+
navigationDepth={1}
|
|
1168
|
+
isActive={false}
|
|
1169
|
+
operationState={IDLE_STATE}
|
|
1170
|
+
containerSize={400}
|
|
1171
|
+
showDimming={false}
|
|
1172
|
+
>
|
|
1173
|
+
Content
|
|
1174
|
+
</SwipeStackContent>,
|
|
1175
|
+
);
|
|
1176
|
+
|
|
1177
|
+
const overlay = container.querySelector("[data-dimming-overlay]");
|
|
1178
|
+
expect(overlay).not.toBeInTheDocument();
|
|
1179
|
+
});
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
describe("navigation during animation", () => {
|
|
1183
|
+
it("handles push during back animation", () => {
|
|
1184
|
+
// Scenario: Panel is animating back (going from active to hidden)
|
|
1185
|
+
// User quickly clicks to push forward again
|
|
1186
|
+
const { container, rerender } = render(
|
|
1187
|
+
<SwipeStackContent
|
|
1188
|
+
id="panel1"
|
|
1189
|
+
depth={1}
|
|
1190
|
+
navigationDepth={1}
|
|
1191
|
+
isActive={true}
|
|
1192
|
+
operationState={IDLE_STATE}
|
|
1193
|
+
containerSize={400}
|
|
1194
|
+
>
|
|
1195
|
+
Content
|
|
1196
|
+
</SwipeStackContent>,
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
const element = container.firstChild as HTMLElement;
|
|
1200
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
1201
|
+
|
|
1202
|
+
// Step 1: Go back - panel becomes hidden, starts animating to off-screen
|
|
1203
|
+
rerender(
|
|
1204
|
+
<SwipeStackContent
|
|
1205
|
+
id="panel1"
|
|
1206
|
+
depth={1}
|
|
1207
|
+
navigationDepth={0}
|
|
1208
|
+
isActive={false}
|
|
1209
|
+
operationState={IDLE_STATE}
|
|
1210
|
+
containerSize={400}
|
|
1211
|
+
>
|
|
1212
|
+
Content
|
|
1213
|
+
</SwipeStackContent>,
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1216
|
+
// Start animation but don't complete it
|
|
1217
|
+
act(() => {
|
|
1218
|
+
flushRAF(0);
|
|
1219
|
+
flushRAF(100); // Partial animation
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
// Panel should be somewhere between 0 and 400
|
|
1223
|
+
const midAnimationTransform = element.style.transform;
|
|
1224
|
+
expect(midAnimationTransform).not.toBe("translateX(0px)");
|
|
1225
|
+
expect(midAnimationTransform).not.toBe("translateX(400px)");
|
|
1226
|
+
|
|
1227
|
+
// Step 2: Push again - panel becomes active again DURING animation
|
|
1228
|
+
rerender(
|
|
1229
|
+
<SwipeStackContent
|
|
1230
|
+
id="panel1"
|
|
1231
|
+
depth={1}
|
|
1232
|
+
navigationDepth={1}
|
|
1233
|
+
isActive={true}
|
|
1234
|
+
operationState={IDLE_STATE}
|
|
1235
|
+
containerSize={400}
|
|
1236
|
+
>
|
|
1237
|
+
Content
|
|
1238
|
+
</SwipeStackContent>,
|
|
1239
|
+
);
|
|
1240
|
+
|
|
1241
|
+
// Complete all pending animations
|
|
1242
|
+
act(() => {
|
|
1243
|
+
flushRAF(0);
|
|
1244
|
+
flushRAF(400);
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
// Panel should end up at center (0), visible
|
|
1248
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
1249
|
+
expect(element.style.visibility).toBe("visible");
|
|
1250
|
+
});
|
|
1251
|
+
|
|
1252
|
+
it("handles back during push animation", () => {
|
|
1253
|
+
// Scenario: New panel is animating in from off-screen
|
|
1254
|
+
// User quickly clicks back before animation completes
|
|
1255
|
+
const { container, rerender } = render(
|
|
1256
|
+
<SwipeStackContent
|
|
1257
|
+
id="panel1"
|
|
1258
|
+
depth={1}
|
|
1259
|
+
navigationDepth={1}
|
|
1260
|
+
isActive={true}
|
|
1261
|
+
operationState={IDLE_STATE}
|
|
1262
|
+
containerSize={400}
|
|
1263
|
+
animateOnMount={true}
|
|
1264
|
+
>
|
|
1265
|
+
Content
|
|
1266
|
+
</SwipeStackContent>,
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
const element = container.firstChild as HTMLElement;
|
|
1270
|
+
|
|
1271
|
+
// Panel starts off-screen
|
|
1272
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
1273
|
+
|
|
1274
|
+
// Start animation but don't complete it
|
|
1275
|
+
act(() => {
|
|
1276
|
+
flushRAF(0);
|
|
1277
|
+
flushRAF(100); // Partial animation
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
// Panel should be somewhere between 400 and 0
|
|
1281
|
+
const midAnimationTransform = element.style.transform;
|
|
1282
|
+
expect(midAnimationTransform).not.toBe("translateX(400px)");
|
|
1283
|
+
expect(midAnimationTransform).not.toBe("translateX(0px)");
|
|
1284
|
+
|
|
1285
|
+
// Go back during animation
|
|
1286
|
+
rerender(
|
|
1287
|
+
<SwipeStackContent
|
|
1288
|
+
id="panel1"
|
|
1289
|
+
depth={1}
|
|
1290
|
+
navigationDepth={0}
|
|
1291
|
+
isActive={false}
|
|
1292
|
+
operationState={IDLE_STATE}
|
|
1293
|
+
containerSize={400}
|
|
1294
|
+
animateOnMount={true}
|
|
1295
|
+
>
|
|
1296
|
+
Content
|
|
1297
|
+
</SwipeStackContent>,
|
|
1298
|
+
);
|
|
1299
|
+
|
|
1300
|
+
// Complete all pending animations
|
|
1301
|
+
act(() => {
|
|
1302
|
+
flushRAF(0);
|
|
1303
|
+
flushRAF(400);
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// Panel should end up off-screen (400), hidden
|
|
1307
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
it("handles rapid back-push-back sequence during animations", () => {
|
|
1311
|
+
const { container, rerender } = render(
|
|
1312
|
+
<SwipeStackContent
|
|
1313
|
+
id="panel1"
|
|
1314
|
+
depth={1}
|
|
1315
|
+
navigationDepth={1}
|
|
1316
|
+
isActive={true}
|
|
1317
|
+
operationState={IDLE_STATE}
|
|
1318
|
+
containerSize={400}
|
|
1319
|
+
>
|
|
1320
|
+
Content
|
|
1321
|
+
</SwipeStackContent>,
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
const element = container.firstChild as HTMLElement;
|
|
1325
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
1326
|
+
|
|
1327
|
+
// Back
|
|
1328
|
+
rerender(
|
|
1329
|
+
<SwipeStackContent
|
|
1330
|
+
id="panel1"
|
|
1331
|
+
depth={1}
|
|
1332
|
+
navigationDepth={0}
|
|
1333
|
+
isActive={false}
|
|
1334
|
+
operationState={IDLE_STATE}
|
|
1335
|
+
containerSize={400}
|
|
1336
|
+
>
|
|
1337
|
+
Content
|
|
1338
|
+
</SwipeStackContent>,
|
|
1339
|
+
);
|
|
1340
|
+
|
|
1341
|
+
act(() => {
|
|
1342
|
+
flushRAF(0);
|
|
1343
|
+
flushRAF(50);
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
// Push (before back animation completes)
|
|
1347
|
+
rerender(
|
|
1348
|
+
<SwipeStackContent
|
|
1349
|
+
id="panel1"
|
|
1350
|
+
depth={1}
|
|
1351
|
+
navigationDepth={1}
|
|
1352
|
+
isActive={true}
|
|
1353
|
+
operationState={IDLE_STATE}
|
|
1354
|
+
containerSize={400}
|
|
1355
|
+
>
|
|
1356
|
+
Content
|
|
1357
|
+
</SwipeStackContent>,
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
act(() => {
|
|
1361
|
+
flushRAF(0);
|
|
1362
|
+
flushRAF(50);
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
// Back again (before push animation completes)
|
|
1366
|
+
rerender(
|
|
1367
|
+
<SwipeStackContent
|
|
1368
|
+
id="panel1"
|
|
1369
|
+
depth={1}
|
|
1370
|
+
navigationDepth={0}
|
|
1371
|
+
isActive={false}
|
|
1372
|
+
operationState={IDLE_STATE}
|
|
1373
|
+
containerSize={400}
|
|
1374
|
+
>
|
|
1375
|
+
Content
|
|
1376
|
+
</SwipeStackContent>,
|
|
1377
|
+
);
|
|
1378
|
+
|
|
1379
|
+
// Complete all animations
|
|
1380
|
+
act(() => {
|
|
1381
|
+
flushRAF(0);
|
|
1382
|
+
flushRAF(400);
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
// Final state: should be off-screen (hidden)
|
|
1386
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
it("panel remains visible and usable after rapid navigation", () => {
|
|
1390
|
+
// This is the bug scenario: after rapid back-and-forth, panel becomes permanently hidden
|
|
1391
|
+
const { container, rerender } = render(
|
|
1392
|
+
<SwipeStackContent
|
|
1393
|
+
id="panel1"
|
|
1394
|
+
depth={1}
|
|
1395
|
+
navigationDepth={1}
|
|
1396
|
+
isActive={true}
|
|
1397
|
+
operationState={IDLE_STATE}
|
|
1398
|
+
containerSize={400}
|
|
1399
|
+
>
|
|
1400
|
+
Content
|
|
1401
|
+
</SwipeStackContent>,
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
const element = container.firstChild as HTMLElement;
|
|
1405
|
+
|
|
1406
|
+
// Rapid sequence without waiting for animations
|
|
1407
|
+
for (let i = 0; i < 5; i++) {
|
|
1408
|
+
// Go back
|
|
1409
|
+
rerender(
|
|
1410
|
+
<SwipeStackContent
|
|
1411
|
+
id="panel1"
|
|
1412
|
+
depth={1}
|
|
1413
|
+
navigationDepth={0}
|
|
1414
|
+
isActive={false}
|
|
1415
|
+
operationState={IDLE_STATE}
|
|
1416
|
+
containerSize={400}
|
|
1417
|
+
>
|
|
1418
|
+
Content
|
|
1419
|
+
</SwipeStackContent>,
|
|
1420
|
+
);
|
|
1421
|
+
|
|
1422
|
+
act(() => {
|
|
1423
|
+
flushRAF(0);
|
|
1424
|
+
flushRAF(30);
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
// Push forward
|
|
1428
|
+
rerender(
|
|
1429
|
+
<SwipeStackContent
|
|
1430
|
+
id="panel1"
|
|
1431
|
+
depth={1}
|
|
1432
|
+
navigationDepth={1}
|
|
1433
|
+
isActive={true}
|
|
1434
|
+
operationState={IDLE_STATE}
|
|
1435
|
+
containerSize={400}
|
|
1436
|
+
>
|
|
1437
|
+
Content
|
|
1438
|
+
</SwipeStackContent>,
|
|
1439
|
+
);
|
|
1440
|
+
|
|
1441
|
+
act(() => {
|
|
1442
|
+
flushRAF(0);
|
|
1443
|
+
flushRAF(30);
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Final state: panel should be active
|
|
1448
|
+
rerender(
|
|
1449
|
+
<SwipeStackContent
|
|
1450
|
+
id="panel1"
|
|
1451
|
+
depth={1}
|
|
1452
|
+
navigationDepth={1}
|
|
1453
|
+
isActive={true}
|
|
1454
|
+
operationState={IDLE_STATE}
|
|
1455
|
+
containerSize={400}
|
|
1456
|
+
>
|
|
1457
|
+
Content
|
|
1458
|
+
</SwipeStackContent>,
|
|
1459
|
+
);
|
|
1460
|
+
|
|
1461
|
+
// Complete all animations
|
|
1462
|
+
act(() => {
|
|
1463
|
+
flushRAF(0);
|
|
1464
|
+
flushRAF(400);
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
// Panel should be visible at center
|
|
1468
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
1469
|
+
expect(element.style.visibility).toBe("visible");
|
|
1470
|
+
});
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
describe("swipe commit (navigation changes)", () => {
|
|
1474
|
+
it("active panel moves off-screen when navigation depth decreases", () => {
|
|
1475
|
+
const { container, rerender } = render(
|
|
1476
|
+
<SwipeStackContent
|
|
1477
|
+
id="panel"
|
|
1478
|
+
depth={1}
|
|
1479
|
+
navigationDepth={1}
|
|
1480
|
+
isActive={true}
|
|
1481
|
+
operationState={IDLE_STATE}
|
|
1482
|
+
containerSize={400}
|
|
1483
|
+
>
|
|
1484
|
+
Content
|
|
1485
|
+
</SwipeStackContent>,
|
|
1486
|
+
);
|
|
1487
|
+
|
|
1488
|
+
const element = container.firstChild as HTMLElement;
|
|
1489
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
1490
|
+
|
|
1491
|
+
// Navigation changed: depth 1 is no longer active (went back to depth 0)
|
|
1492
|
+
// Panel should animate to off-screen position (containerSize = 400)
|
|
1493
|
+
rerender(
|
|
1494
|
+
<SwipeStackContent
|
|
1495
|
+
id="panel"
|
|
1496
|
+
depth={1}
|
|
1497
|
+
navigationDepth={0}
|
|
1498
|
+
isActive={false}
|
|
1499
|
+
operationState={IDLE_STATE}
|
|
1500
|
+
containerSize={400}
|
|
1501
|
+
>
|
|
1502
|
+
Content
|
|
1503
|
+
</SwipeStackContent>,
|
|
1504
|
+
);
|
|
1505
|
+
|
|
1506
|
+
// Role changes to "hidden"
|
|
1507
|
+
expect(element.getAttribute("data-role")).toBe("hidden");
|
|
1508
|
+
|
|
1509
|
+
// Flush RAF to complete animation
|
|
1510
|
+
act(() => {
|
|
1511
|
+
flushRAF(0);
|
|
1512
|
+
flushRAF(400);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
// After animation: off-screen at containerSize
|
|
1516
|
+
expect(element.style.transform).toBe("translateX(400px)");
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
it("behind panel becomes active and moves to center when navigation commits", () => {
|
|
1520
|
+
const { container, rerender } = render(
|
|
1521
|
+
<SwipeStackContent
|
|
1522
|
+
id="panel"
|
|
1523
|
+
depth={0}
|
|
1524
|
+
navigationDepth={1}
|
|
1525
|
+
isActive={false}
|
|
1526
|
+
operationState={IDLE_STATE}
|
|
1527
|
+
containerSize={400}
|
|
1528
|
+
>
|
|
1529
|
+
Content
|
|
1530
|
+
</SwipeStackContent>,
|
|
1531
|
+
);
|
|
1532
|
+
|
|
1533
|
+
const element = container.firstChild as HTMLElement;
|
|
1534
|
+
expect(element.style.transform).toBe("translateX(-120px)");
|
|
1535
|
+
expect(element.getAttribute("data-role")).toBe("behind");
|
|
1536
|
+
|
|
1537
|
+
// Navigation commits: panel at depth 0 becomes active
|
|
1538
|
+
rerender(
|
|
1539
|
+
<SwipeStackContent
|
|
1540
|
+
id="panel"
|
|
1541
|
+
depth={0}
|
|
1542
|
+
navigationDepth={0}
|
|
1543
|
+
isActive={true}
|
|
1544
|
+
operationState={IDLE_STATE}
|
|
1545
|
+
containerSize={400}
|
|
1546
|
+
>
|
|
1547
|
+
Content
|
|
1548
|
+
</SwipeStackContent>,
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
// Role changes to "active"
|
|
1552
|
+
expect(element.getAttribute("data-role")).toBe("active");
|
|
1553
|
+
|
|
1554
|
+
// Flush RAF to complete animation
|
|
1555
|
+
act(() => {
|
|
1556
|
+
flushRAF(0);
|
|
1557
|
+
flushRAF(400);
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
// After animation: at center (0)
|
|
1561
|
+
expect(element.style.transform).toBe("translateX(0px)");
|
|
1562
|
+
});
|
|
1563
|
+
});
|
|
1564
|
+
});
|