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.
Files changed (541) hide show
  1. package/dist/{FloatingPanelFrame-lLg-Lpg7.js → FloatingPanelFrame-3eU9AwPo.js} +11 -11
  2. package/dist/{FloatingPanelFrame-lLg-Lpg7.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
  4. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs.map → FloatingPanelFrame-CEmXDvUA.cjs.map} +1 -1
  5. package/dist/FloatingWindow-CUXnEtrb.js +827 -0
  6. package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
  7. package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
  8. package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
  9. package/dist/GridLayout-DKTg_N61.cjs +2 -0
  10. package/dist/GridLayout-DKTg_N61.cjs.map +1 -0
  11. package/dist/GridLayout-UWNxXw77.js +926 -0
  12. package/dist/GridLayout-UWNxXw77.js.map +1 -0
  13. package/dist/HorizontalDivider-DdxzfV0l.js +30 -0
  14. package/dist/HorizontalDivider-DdxzfV0l.js.map +1 -0
  15. package/dist/HorizontalDivider-_pgV4Mcv.cjs +2 -0
  16. package/dist/HorizontalDivider-_pgV4Mcv.cjs.map +1 -0
  17. package/dist/PanelSystem-BqUzNtf2.js +1946 -0
  18. package/dist/PanelSystem-BqUzNtf2.js.map +1 -0
  19. package/dist/PanelSystem-D603LKKv.cjs +3 -0
  20. package/dist/PanelSystem-D603LKKv.cjs.map +1 -0
  21. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  22. package/dist/ResizeHandle-CBcAS918.cjs.map +1 -0
  23. package/dist/ResizeHandle-CXjc1meV.js +119 -0
  24. package/dist/ResizeHandle-CXjc1meV.js.map +1 -0
  25. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  26. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  27. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  28. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  29. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  30. package/dist/components/window/Drawer.d.ts +3 -1
  31. package/dist/components/window/DrawerLayers.d.ts +1 -1
  32. package/dist/components/window/drawerStyles.d.ts +69 -0
  33. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  34. package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
  35. package/dist/config.cjs +1 -1
  36. package/dist/config.cjs.map +1 -1
  37. package/dist/config.js +11 -9
  38. package/dist/config.js.map +1 -1
  39. package/dist/constants/styles.d.ts +35 -4
  40. package/dist/dialog/index.d.ts +69 -0
  41. package/dist/floating.cjs +1 -1
  42. package/dist/floating.js +1 -1
  43. package/dist/grid/index.d.ts +58 -0
  44. package/dist/grid.cjs +2 -0
  45. package/dist/grid.cjs.map +1 -0
  46. package/dist/grid.js +13 -0
  47. package/dist/grid.js.map +1 -0
  48. package/dist/hooks/gesture/presets.d.ts +33 -0
  49. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +117 -0
  50. package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
  51. package/dist/hooks/gesture/types.d.ts +297 -0
  52. package/dist/hooks/gesture/useDirectionalLock.d.ts +20 -0
  53. package/dist/hooks/gesture/useEdgeSwipeInput.d.ts +23 -0
  54. package/dist/hooks/gesture/useNativeGestureGuard.d.ts +23 -0
  55. package/dist/hooks/gesture/usePointerTracking.d.ts +22 -0
  56. package/dist/hooks/gesture/useScrollBoundary.d.ts +23 -0
  57. package/dist/hooks/gesture/useSwipeInput.d.ts +5 -0
  58. package/dist/hooks/gesture/utils.d.ts +59 -0
  59. package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
  60. package/dist/hooks/useAnimationFrame.d.ts +86 -0
  61. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  62. package/dist/hooks/useResizeObserver.d.ts +33 -1
  63. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  64. package/dist/hooks/useSnapAnimation.d.ts +54 -0
  65. package/dist/hooks/useSwipeContentTransform.d.ts +86 -0
  66. package/dist/index.cjs +1 -2
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.ts +0 -1
  69. package/dist/index.js +25 -2006
  70. package/dist/index.js.map +1 -1
  71. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  72. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  73. package/dist/modules/dialog/Modal.d.ts +26 -0
  74. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  75. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  76. package/dist/modules/dialog/types.d.ts +183 -0
  77. package/dist/modules/dialog/useDialog.d.ts +39 -0
  78. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  79. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  80. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  81. package/dist/modules/drawer/types.d.ts +74 -0
  82. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  83. package/dist/modules/pivot/PivotContent.d.ts +1 -1
  84. package/dist/modules/pivot/SwipePivotContent.d.ts +39 -0
  85. package/dist/modules/pivot/SwipePivotContent.debug.tmp.d.ts +25 -0
  86. package/dist/modules/pivot/SwipePivotContent.test.d.ts +1 -0
  87. package/dist/modules/pivot/SwipePivotTabBar.d.ts +92 -0
  88. package/dist/modules/pivot/index.d.ts +3 -0
  89. package/dist/modules/pivot/scaleInputState.d.ts +37 -0
  90. package/dist/modules/pivot/types.d.ts +67 -2
  91. package/dist/modules/pivot/usePivotSwipeInput.d.ts +68 -0
  92. package/dist/modules/stack/StackContent.d.ts +15 -0
  93. package/dist/modules/stack/SwipeStackContent.d.ts +66 -0
  94. package/dist/modules/stack/SwipeStackOutlet.d.ts +80 -0
  95. package/dist/modules/stack/computeStackContentState.d.ts +99 -0
  96. package/dist/modules/stack/computeSwipeStackTransform.d.ts +76 -0
  97. package/dist/modules/stack/types.d.ts +194 -0
  98. package/dist/modules/stack/useStackAnimationState.d.ts +32 -0
  99. package/dist/modules/stack/useStackNavigation.d.ts +23 -0
  100. package/dist/modules/stack/useStackSwipeInput.d.ts +27 -0
  101. package/dist/panels/index.d.ts +67 -0
  102. package/dist/panels.cjs +2 -0
  103. package/dist/panels.cjs.map +1 -0
  104. package/dist/panels.js +28 -0
  105. package/dist/panels.js.map +1 -0
  106. package/dist/pivot/index.d.ts +3 -0
  107. package/dist/pivot.cjs +1 -1
  108. package/dist/pivot.cjs.map +1 -1
  109. package/dist/pivot.js +20 -2
  110. package/dist/pivot.js.map +1 -1
  111. package/dist/resizer/index.d.ts +57 -0
  112. package/dist/resizer.cjs +2 -0
  113. package/dist/resizer.cjs.map +1 -0
  114. package/dist/resizer.js +8 -0
  115. package/dist/resizer.js.map +1 -0
  116. package/dist/stack/index.d.ts +72 -0
  117. package/dist/stack.cjs +2 -0
  118. package/dist/stack.cjs.map +1 -0
  119. package/dist/stack.js +721 -0
  120. package/dist/stack.js.map +1 -0
  121. package/dist/sticky-header/StickyArea.d.ts +38 -0
  122. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  123. package/dist/sticky-header/index.d.ts +4 -4
  124. package/dist/sticky-header/types.d.ts +35 -22
  125. package/dist/sticky-header.cjs +1 -1
  126. package/dist/sticky-header.cjs.map +1 -1
  127. package/dist/sticky-header.js +73 -174
  128. package/dist/sticky-header.js.map +1 -1
  129. package/dist/styles-NkjuMOVS.js +57 -0
  130. package/dist/styles-NkjuMOVS.js.map +1 -0
  131. package/dist/styles-qf6ptVLD.cjs +2 -0
  132. package/dist/styles-qf6ptVLD.cjs.map +1 -0
  133. package/dist/types.d.ts +16 -0
  134. package/dist/useContentCache-CO3LYNmz.js +24 -0
  135. package/dist/useContentCache-CO3LYNmz.js.map +1 -0
  136. package/dist/useContentCache-DqXtLrLs.cjs +2 -0
  137. package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
  138. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  139. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  140. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  141. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  142. package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
  143. package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
  144. package/dist/useFloatingState-tEfA_wbc.js +74 -0
  145. package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
  146. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
  147. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
  148. package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
  149. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
  150. package/dist/window/index.d.ts +63 -0
  151. package/dist/window.cjs +2 -0
  152. package/dist/window.cjs.map +1 -0
  153. package/dist/window.js +160 -0
  154. package/dist/window.js.map +1 -0
  155. package/docs/design-tokens.md +405 -0
  156. package/package.json +34 -4
  157. package/src/PanelSystemContext.tsx +88 -0
  158. package/src/components/gesture/SwipeSafeZone.tsx +69 -0
  159. package/src/components/grid/GridLayerList.tsx +172 -0
  160. package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
  161. package/src/components/grid/GridLayout.spec.tsx +743 -0
  162. package/src/components/grid/GridLayout.tsx +130 -0
  163. package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
  164. package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
  165. package/src/components/panels/DropSuggestOverlay.tsx +131 -0
  166. package/src/components/panels/PanelGroupView.tsx +112 -0
  167. package/src/components/pivot/PivotLayer.tsx +27 -0
  168. package/src/components/resizer/HorizontalDivider.tsx +52 -0
  169. package/src/components/resizer/ResizeHandle.tsx +118 -0
  170. package/src/components/tabs/TabBar.tsx +223 -0
  171. package/src/components/tabs/TabBarTab.tsx +133 -0
  172. package/src/components/tabs/TabDragOverlay.tsx +92 -0
  173. package/src/components/window/DialogOverlay.tsx +180 -0
  174. package/src/components/window/Drawer.tsx +369 -0
  175. package/src/components/window/DrawerLayers.tsx +68 -0
  176. package/src/components/window/FloatingWindow.tsx +95 -0
  177. package/src/components/window/PopupLayerPortal.tsx +218 -0
  178. package/src/components/window/drawerStyles.spec.ts +263 -0
  179. package/src/components/window/drawerStyles.ts +228 -0
  180. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  181. package/src/components/window/drawerSwipeConfig.ts +112 -0
  182. package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
  183. package/src/components/window/useDrawerSwipeTransform.ts +129 -0
  184. package/src/config/PanelContentDeclaration.tsx +427 -0
  185. package/src/config/index.tsx +52 -0
  186. package/src/config/panelJsx.spec.tsx +54 -0
  187. package/src/config/panelJsxConfig.spec.tsx +54 -0
  188. package/src/config/panelJsxDrawer.spec.tsx +33 -0
  189. package/src/config/panelRouter.spec.ts +68 -0
  190. package/src/config/panelRouter.tsx +155 -0
  191. package/src/constants/styles.ts +280 -0
  192. package/src/demo/Layout.module.css +258 -0
  193. package/src/demo/Layout.tsx +176 -0
  194. package/src/demo/components/CodeBlock.module.css +54 -0
  195. package/src/demo/components/CodeBlock.tsx +34 -0
  196. package/src/demo/components/CodePreview.module.css +37 -0
  197. package/src/demo/components/CodePreview.tsx +31 -0
  198. package/src/demo/components/DataPreview.module.css +177 -0
  199. package/src/demo/components/DataPreview.tsx +115 -0
  200. package/src/demo/components/Story.module.css +68 -0
  201. package/src/demo/components/Story.tsx +54 -0
  202. package/src/demo/components/layout/CodePanel.module.css +183 -0
  203. package/src/demo/components/layout/CodePanel.tsx +149 -0
  204. package/src/demo/components/layout/DemoPage.module.css +60 -0
  205. package/src/demo/components/layout/DemoPage.tsx +56 -0
  206. package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
  207. package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
  208. package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
  209. package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
  210. package/src/demo/components/layout/index.ts +11 -0
  211. package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
  212. package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
  213. package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
  214. package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
  215. package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
  216. package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
  217. package/src/demo/components/tab-styles/index.ts +6 -0
  218. package/src/demo/components/ui/DemoButton.module.css +63 -0
  219. package/src/demo/components/ui/DemoButton.tsx +32 -0
  220. package/src/demo/components/ui/DemoCard.module.css +15 -0
  221. package/src/demo/components/ui/DemoCard.tsx +30 -0
  222. package/src/demo/components/ui/DemoContainer.module.css +17 -0
  223. package/src/demo/components/ui/DemoContainer.tsx +30 -0
  224. package/src/demo/components/ui/DemoPanel.module.css +23 -0
  225. package/src/demo/components/ui/DemoPanel.tsx +33 -0
  226. package/src/demo/components/ui/PanelText.module.css +18 -0
  227. package/src/demo/components/ui/PanelText.tsx +29 -0
  228. package/src/demo/components/ui/PanelTitle.module.css +18 -0
  229. package/src/demo/components/ui/PanelTitle.tsx +31 -0
  230. package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
  231. package/src/demo/demo.css +172 -0
  232. package/src/demo/hooks/useMedia.ts +41 -0
  233. package/src/demo/hooks/useShikiHighlight.ts +55 -0
  234. package/src/demo/index.tsx +293 -0
  235. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  236. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  237. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  238. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  239. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
  240. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  241. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  242. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  243. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  244. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  245. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  246. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  247. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  248. package/src/demo/pages/Drawer/animations/index.tsx +22 -0
  249. package/src/demo/pages/Drawer/basics/index.tsx +17 -0
  250. package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
  251. package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
  252. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
  253. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
  254. package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
  255. package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
  256. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  257. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  258. package/src/demo/pages/Drawer/menu/index.tsx +17 -0
  259. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  260. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
  261. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
  262. package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
  263. package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
  264. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
  265. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
  266. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
  267. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
  268. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
  269. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
  270. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
  271. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
  272. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
  273. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
  274. package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
  275. package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
  276. package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
  277. package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
  278. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
  279. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
  280. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
  281. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
  282. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
  283. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
  284. package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
  285. package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
  286. package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
  287. package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
  288. package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
  289. package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
  290. package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
  291. package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
  292. package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
  293. package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
  294. package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
  295. package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
  296. package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
  297. package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
  298. package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
  299. package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
  300. package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
  301. package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
  302. package/src/demo/pages/PanelLayout/index.tsx +94 -0
  303. package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
  304. package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
  305. package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
  306. package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
  307. package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
  308. package/src/demo/pages/Pivot/basics/index.tsx +17 -0
  309. package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
  310. package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
  311. package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
  312. package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
  313. package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
  314. package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
  315. package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
  316. package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
  317. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +320 -0
  318. package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
  319. package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
  320. package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
  321. package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
  322. package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
  323. package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
  324. package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
  325. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
  326. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
  327. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
  328. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
  329. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
  330. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
  331. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
  332. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
  333. package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
  334. package/src/demo/pages/ResizeHandle/index.module.css +11 -0
  335. package/src/demo/pages/ResizeHandle/index.tsx +71 -0
  336. package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
  337. package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
  338. package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
  339. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
  340. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
  341. package/src/demo/pages/Stack/basics/index.tsx +22 -0
  342. package/src/demo/pages/Stack/components/Stack.module.css +234 -0
  343. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
  344. package/src/demo/pages/Stack/components/StackBasics.tsx +301 -0
  345. package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
  346. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
  347. package/src/demo/pages/Stack/components/StackTablet.tsx +422 -0
  348. package/src/demo/pages/Stack/tablet/index.tsx +22 -0
  349. package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
  350. package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
  351. package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
  352. package/src/demo/routes.tsx +214 -0
  353. package/src/demo/styles/animations.css +68 -0
  354. package/src/demo/styles/stack-themes.css +35 -0
  355. package/src/demo/utils/createPanelView.tsx +58 -0
  356. package/src/dialog/index.ts +85 -0
  357. package/src/floating/index.ts +24 -0
  358. package/src/grid/index.ts +75 -0
  359. package/src/hooks/ContentCacheContext.tsx +87 -0
  360. package/src/hooks/gesture/presets.spec.ts +86 -0
  361. package/src/hooks/gesture/presets.ts +95 -0
  362. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +241 -0
  363. package/src/hooks/gesture/testing/createGestureSimulator.ts +385 -0
  364. package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
  365. package/src/hooks/gesture/thresholdValue.ts +77 -0
  366. package/src/hooks/gesture/types.ts +367 -0
  367. package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
  368. package/src/hooks/gesture/useDirectionalLock.ts +115 -0
  369. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +462 -0
  370. package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
  371. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +473 -0
  372. package/src/hooks/gesture/useNativeGestureGuard.ts +135 -0
  373. package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
  374. package/src/hooks/gesture/usePointerTracking.ts +134 -0
  375. package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
  376. package/src/hooks/gesture/useScrollBoundary.ts +113 -0
  377. package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
  378. package/src/hooks/gesture/useSwipeInput.ts +310 -0
  379. package/src/hooks/gesture/utils.spec.ts +152 -0
  380. package/src/hooks/gesture/utils.ts +178 -0
  381. package/src/hooks/useAnimatedVisibility.spec.ts +277 -0
  382. package/src/hooks/useAnimatedVisibility.ts +172 -0
  383. package/src/hooks/useAnimationFrame.ts +208 -0
  384. package/src/hooks/useCSSMatrix.spec.ts +214 -0
  385. package/src/hooks/useCSSMatrix.ts +262 -0
  386. package/src/hooks/useClonedElementPreview.ts +28 -0
  387. package/src/hooks/useContainerScroll.ts +78 -0
  388. package/src/hooks/useContentCache.spec.tsx +232 -0
  389. package/src/hooks/useContentCache.tsx +127 -0
  390. package/src/hooks/useDocumentPointerEvents.ts +137 -0
  391. package/src/hooks/useDocumentScroll.ts +41 -0
  392. package/src/hooks/useEffectEvent.ts +40 -0
  393. package/src/hooks/useElementComponentWrapper.tsx +63 -0
  394. package/src/hooks/useIntersectionObserver.tsx +125 -0
  395. package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
  396. package/src/hooks/useOperationContinuity.spec.ts +387 -0
  397. package/src/hooks/useOperationContinuity.ts +135 -0
  398. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  399. package/src/hooks/useResizeObserver.tsx +150 -0
  400. package/src/hooks/useScrollContainer.ts +73 -0
  401. package/src/hooks/useSharedElementTransition.ts +333 -0
  402. package/src/hooks/useSnapAnimation.ts +128 -0
  403. package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
  404. package/src/hooks/useSwipeContentTransform.ts +373 -0
  405. package/src/hooks/useTransitionState.ts +95 -0
  406. package/src/index.tsx +88 -0
  407. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  408. package/src/modules/dialog/AlertDialog.tsx +221 -0
  409. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  410. package/src/modules/dialog/DialogContainer.tsx +188 -0
  411. package/src/modules/dialog/Modal.spec.tsx +220 -0
  412. package/src/modules/dialog/Modal.tsx +182 -0
  413. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  414. package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
  415. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  416. package/src/modules/dialog/types.ts +186 -0
  417. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  418. package/src/modules/dialog/useDialog.ts +214 -0
  419. package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
  420. package/src/modules/dialog/useDialogContainer.ts +150 -0
  421. package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
  422. package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
  423. package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
  424. package/src/modules/dialog/useDialogTransform.ts +407 -0
  425. package/src/modules/drawer/types.ts +102 -0
  426. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  427. package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
  428. package/src/modules/grid/GridLayoutContext.tsx +57 -0
  429. package/src/modules/grid/LayerInstanceContext.tsx +56 -0
  430. package/src/modules/grid/resizeHandles.ts +157 -0
  431. package/src/modules/grid/trackUtils.ts +146 -0
  432. package/src/modules/grid/useGridPlacements.ts +143 -0
  433. package/src/modules/grid/useGridTracks.ts +156 -0
  434. package/src/modules/grid/useLayerDragHandle.ts +16 -0
  435. package/src/modules/grid/useLayerInteractions.tsx +850 -0
  436. package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
  437. package/src/modules/panels/dom/DomRegistry.tsx +94 -0
  438. package/src/modules/panels/index.ts +45 -0
  439. package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
  440. package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
  441. package/src/modules/panels/interactions/dnd.ts +28 -0
  442. package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
  443. package/src/modules/panels/layout/adapter.ts +124 -0
  444. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +311 -0
  445. package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
  446. package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
  447. package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
  448. package/src/modules/panels/rendering/RenderContext.tsx +31 -0
  449. package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
  450. package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
  451. package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
  452. package/src/modules/panels/state/StateContext.tsx +12 -0
  453. package/src/modules/panels/state/cleanup.ts +37 -0
  454. package/src/modules/panels/state/commands.ts +53 -0
  455. package/src/modules/panels/state/focus/Context.tsx +25 -0
  456. package/src/modules/panels/state/focus/logic.ts +57 -0
  457. package/src/modules/panels/state/groups/Context.tsx +25 -0
  458. package/src/modules/panels/state/groups/logic.ts +105 -0
  459. package/src/modules/panels/state/splitLimits.spec.ts +46 -0
  460. package/src/modules/panels/state/splitLimits.ts +90 -0
  461. package/src/modules/panels/state/state.spec.ts +49 -0
  462. package/src/modules/panels/state/tree/Context.tsx +24 -0
  463. package/src/modules/panels/state/tree/logic.spec.ts +34 -0
  464. package/src/modules/panels/state/tree/logic.ts +138 -0
  465. package/src/modules/panels/state/types.ts +142 -0
  466. package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
  467. package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
  468. package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
  469. package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
  470. package/src/modules/panels/system/PanelSystem.tsx +206 -0
  471. package/src/modules/pivot/PivotContent.spec.tsx +179 -0
  472. package/src/modules/pivot/PivotContent.tsx +77 -0
  473. package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
  474. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +171 -0
  475. package/src/modules/pivot/SwipePivotContent.spec.tsx +494 -0
  476. package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
  477. package/src/modules/pivot/SwipePivotContent.tsx +197 -0
  478. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +882 -0
  479. package/src/modules/pivot/SwipePivotTabBar.tsx +583 -0
  480. package/src/modules/pivot/index.ts +8 -0
  481. package/src/modules/pivot/scaleInputState.spec.ts +219 -0
  482. package/src/modules/pivot/scaleInputState.ts +66 -0
  483. package/src/modules/pivot/types.ts +139 -0
  484. package/src/modules/pivot/usePivot.spec.ts +635 -0
  485. package/src/modules/pivot/usePivot.spec.tsx +186 -0
  486. package/src/modules/pivot/usePivot.tsx +345 -0
  487. package/src/modules/pivot/usePivotSwipeInput.spec.ts +708 -0
  488. package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
  489. package/src/modules/resizer/useResizeDrag.ts +94 -0
  490. package/src/modules/stack/StackContent.spec.tsx +264 -0
  491. package/src/modules/stack/StackContent.tsx +111 -0
  492. package/src/modules/stack/SwipeStackContent.spec.tsx +1564 -0
  493. package/src/modules/stack/SwipeStackContent.tsx +366 -0
  494. package/src/modules/stack/SwipeStackOutlet.spec.tsx +250 -0
  495. package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
  496. package/src/modules/stack/computeStackContentState.spec.ts +281 -0
  497. package/src/modules/stack/computeStackContentState.ts +304 -0
  498. package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
  499. package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
  500. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  501. package/src/modules/stack/types.ts +226 -0
  502. package/src/modules/stack/useStackAnimationState.spec.ts +188 -0
  503. package/src/modules/stack/useStackAnimationState.ts +143 -0
  504. package/src/modules/stack/useStackNavigation.spec.ts +672 -0
  505. package/src/modules/stack/useStackNavigation.tsx +393 -0
  506. package/src/modules/stack/useStackSwipeInput.spec.ts +309 -0
  507. package/src/modules/stack/useStackSwipeInput.ts +139 -0
  508. package/src/modules/window/useDrawerState.ts +81 -0
  509. package/src/modules/window/useFloatingState.spec.ts +252 -0
  510. package/src/modules/window/useFloatingState.ts +141 -0
  511. package/src/panels/index.ts +119 -0
  512. package/src/pivot/index.ts +19 -0
  513. package/src/resizer/index.ts +68 -0
  514. package/src/stack/index.ts +91 -0
  515. package/src/sticky-header/StickyArea.tsx +193 -0
  516. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  517. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  518. package/src/sticky-header/index.ts +18 -0
  519. package/src/sticky-header/types.ts +68 -0
  520. package/src/types.ts +341 -0
  521. package/src/utils/CSSMatrix.ts +321 -0
  522. package/src/utils/css.ts +65 -0
  523. package/src/utils/dialogUtils.ts +43 -0
  524. package/src/utils/math.ts +18 -0
  525. package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
  526. package/src/utils/typedActions.ts +103 -0
  527. package/src/vite-env.d.ts +6 -0
  528. package/src/window/index.ts +69 -0
  529. package/dist/GridLayout-BQQ63eA1.cjs +0 -2
  530. package/dist/GridLayout-BQQ63eA1.cjs.map +0 -1
  531. package/dist/GridLayout-CJTKq7Mp.js +0 -1465
  532. package/dist/GridLayout-CJTKq7Mp.js.map +0 -1
  533. package/dist/sticky-header/StickyHeader.d.ts +0 -53
  534. package/dist/styles-CA2_zLZt.js +0 -52
  535. package/dist/styles-CA2_zLZt.js.map +0 -1
  536. package/dist/styles-PsqGOEJP.cjs +0 -2
  537. package/dist/styles-PsqGOEJP.cjs.map +0 -1
  538. package/dist/usePivot-7ctin_P_.cjs +0 -2
  539. package/dist/usePivot-7ctin_P_.cjs.map +0 -1
  540. package/dist/usePivot-CgQxB8rc.js +0 -124
  541. package/dist/usePivot-CgQxB8rc.js.map +0 -1
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @file Shared useIntersectionObserver hook with cached observer instances.
3
+ */
4
+ import * as React from "react";
5
+
6
+ const createIdGenerator = () => {
7
+ const map = new Map<object, number>();
8
+ return (ref: object | null | undefined) => {
9
+ if (!ref) {
10
+ return undefined;
11
+ }
12
+ const existing = map.get(ref);
13
+ if (existing !== undefined) {
14
+ return existing;
15
+ }
16
+ const nextId = map.size;
17
+ map.set(ref, nextId);
18
+ return nextId;
19
+ };
20
+ };
21
+
22
+ const getId = createIdGenerator();
23
+ type Unobserve = () => void;
24
+ type Callback = (entry: IntersectionObserverEntry) => void;
25
+ type SharedObserver = {
26
+ observe: (target: Element, callback: Callback) => Unobserve;
27
+ };
28
+ const observerCache = new Map<string, SharedObserver>();
29
+ const getSharedObserver = (options: IntersectionObserverInit) => {
30
+ const observerKey = `ovs-threshold:${options.threshold}-rootMargin:${options.rootMargin}-root:${getId(options.root)}`;
31
+
32
+ if (observerCache.has(observerKey)) {
33
+ return observerCache.get(observerKey)!;
34
+ }
35
+ const observer = new (class {
36
+ #callbackMap = new Map<Element, Callback>();
37
+ #intersectionObserver = new IntersectionObserver((entries) => {
38
+ entries.forEach((entry) => {
39
+ const callback = this.#callbackMap.get(entry.target);
40
+ if (callback) {
41
+ callback(entry);
42
+ }
43
+ });
44
+ }, options);
45
+ observe(target: Element, callback: Callback) {
46
+ this.#callbackMap.set(target, callback);
47
+ this.#intersectionObserver.observe(target);
48
+ return () => {
49
+ this.#callbackMap.delete(target);
50
+ this.#intersectionObserver.unobserve(target);
51
+ };
52
+ }
53
+ })();
54
+ observerCache.set(observerKey, observer);
55
+
56
+ return observer;
57
+ };
58
+ const voidClientRect = Object.freeze({
59
+ x: 0,
60
+ y: 0,
61
+ width: 0,
62
+ height: 0,
63
+ top: 0,
64
+ right: 0,
65
+ bottom: 0,
66
+ left: 0,
67
+ }) as DOMRectReadOnly;
68
+ /**
69
+ * Observe intersection changes for a given element reference using shared observers.
70
+ *
71
+ * @param ref - Ref holding the element to observe.
72
+ * @param options - Intersection observer configuration.
73
+ * @returns Latest intersection entry snapshot with sensible defaults.
74
+ */
75
+ export function useIntersectionObserver<T extends HTMLElement>(
76
+ ref: React.RefObject<T | null>,
77
+ { threshold = 0, rootMargin = "0px", root = null }: IntersectionObserverInit,
78
+ ): {
79
+ readonly boundingClientRect: DOMRectReadOnly;
80
+ readonly intersectionRatio: number;
81
+ readonly intersectionRect: DOMRectReadOnly;
82
+ readonly isIntersecting: boolean;
83
+ readonly rootBounds: DOMRectReadOnly | null;
84
+ readonly target: Element | null;
85
+ readonly time: DOMHighResTimeStamp;
86
+ } {
87
+ const [intersection, setIntersection] = React.useState<IntersectionObserverEntry | null>(null);
88
+
89
+ React.useEffect(() => {
90
+ const target = ref.current;
91
+ if (!target) {
92
+ return;
93
+ }
94
+
95
+ const observer = getSharedObserver({
96
+ threshold,
97
+ rootMargin,
98
+ root,
99
+ });
100
+
101
+ return observer.observe(target, (entry) => {
102
+ setIntersection({
103
+ isIntersecting: entry.isIntersecting,
104
+ boundingClientRect: entry.boundingClientRect,
105
+ intersectionRatio: entry.intersectionRatio,
106
+ intersectionRect: entry.intersectionRect,
107
+ rootBounds: entry.rootBounds,
108
+ target: entry.target,
109
+ time: entry.time,
110
+ });
111
+ });
112
+ }, [ref, threshold, rootMargin, root]);
113
+
114
+ return React.useMemo(() => {
115
+ return {
116
+ isIntersecting: intersection?.isIntersecting ?? false,
117
+ boundingClientRect: intersection?.boundingClientRect ?? voidClientRect,
118
+ intersectionRatio: intersection?.intersectionRatio ?? 0,
119
+ intersectionRect: intersection?.intersectionRect ?? voidClientRect,
120
+ rootBounds: intersection?.rootBounds ?? null,
121
+ target: intersection?.target ?? ref.current,
122
+ time: intersection?.time ?? 0,
123
+ };
124
+ }, [intersection, ref]);
125
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file useIsomorphicLayoutEffect - SSR-safe version of useLayoutEffect
3
+ *
4
+ * Uses useLayoutEffect on the client and useEffect on the server to avoid warnings
5
+ * during server-side rendering (e.g., with Next.js)
6
+ */
7
+ import * as React from "react";
8
+
9
+ /**
10
+ * Check if we're running in a browser environment
11
+ */
12
+ const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
13
+
14
+ /**
15
+ * SSR-safe version of useLayoutEffect
16
+ *
17
+ * - Client: Uses useLayoutEffect for synchronous layout updates
18
+ * - Server: Uses useEffect to avoid SSR warnings
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * useIsomorphicLayoutEffect(() => {
23
+ * // This runs synchronously after DOM mutations on the client
24
+ * // but safely falls back to useEffect on the server
25
+ * measureElement();
26
+ * }, [deps]);
27
+ * ```
28
+ */
29
+ export const useIsomorphicLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect;
@@ -0,0 +1,387 @@
1
+ /**
2
+ * @file Tests for useOperationContinuity hook.
3
+ */
4
+ import * as React from "react";
5
+ import { renderHook } from "@testing-library/react";
6
+ import { useOperationContinuity } from "./useOperationContinuity.js";
7
+
8
+ const StrictModeWrapper = ({ children }: { children: React.ReactNode }): React.ReactNode => {
9
+ return React.createElement(React.StrictMode, null, children);
10
+ };
11
+
12
+ describe("useOperationContinuity", () => {
13
+ describe("value continuity", () => {
14
+ it("returns current value when not retaining", () => {
15
+ const { result } = renderHook(() => useOperationContinuity("active", false));
16
+ expect(result.current.value).toBe("active");
17
+ });
18
+
19
+ it("returns current value when retaining but value unchanged", () => {
20
+ const { result } = renderHook(() => useOperationContinuity("active", true));
21
+ expect(result.current.value).toBe("active");
22
+ });
23
+
24
+ it("retains previous value when shouldRetainPrevious is true", () => {
25
+ const { result, rerender } = renderHook(
26
+ ({ value, shouldRetainPrevious }) => useOperationContinuity(value, shouldRetainPrevious),
27
+ { initialProps: { value: "behind", shouldRetainPrevious: true } },
28
+ );
29
+
30
+ expect(result.current.value).toBe("behind");
31
+
32
+ // Value changes but we're still retaining
33
+ rerender({ value: "active", shouldRetainPrevious: true });
34
+ expect(result.current.value).toBe("behind");
35
+ });
36
+
37
+ it("accepts new value when shouldRetainPrevious becomes false", () => {
38
+ const { result, rerender } = renderHook(
39
+ ({ value, shouldRetainPrevious }) => useOperationContinuity(value, shouldRetainPrevious),
40
+ { initialProps: { value: "behind", shouldRetainPrevious: true } },
41
+ );
42
+
43
+ // Value changes while retaining
44
+ rerender({ value: "active", shouldRetainPrevious: true });
45
+ expect(result.current.value).toBe("behind");
46
+
47
+ // Stop retaining - should accept new value
48
+ rerender({ value: "active", shouldRetainPrevious: false });
49
+ expect(result.current.value).toBe("active");
50
+ });
51
+
52
+ it("updates stored value when not retaining", () => {
53
+ const { result, rerender } = renderHook(
54
+ ({ value, shouldRetainPrevious }) => useOperationContinuity(value, shouldRetainPrevious),
55
+ { initialProps: { value: "behind", shouldRetainPrevious: false } },
56
+ );
57
+
58
+ rerender({ value: "active", shouldRetainPrevious: false });
59
+ expect(result.current.value).toBe("active");
60
+
61
+ // Start retaining - should keep "active"
62
+ rerender({ value: "hidden", shouldRetainPrevious: true });
63
+ expect(result.current.value).toBe("active");
64
+ });
65
+ });
66
+
67
+ describe("changedDuringOperation tracking", () => {
68
+ it("returns false when value never changed", () => {
69
+ const { result, rerender } = renderHook(
70
+ ({ value, retain }) => useOperationContinuity(value, retain),
71
+ { initialProps: { value: "active", retain: true } },
72
+ );
73
+
74
+ expect(result.current.changedDuringOperation).toBe(false);
75
+
76
+ // End retention without value change
77
+ rerender({ value: "active", retain: false });
78
+ expect(result.current.changedDuringOperation).toBe(false);
79
+ });
80
+
81
+ it("returns true when value changed during retention", () => {
82
+ const { result, rerender } = renderHook(
83
+ ({ value, retain }) => useOperationContinuity(value, retain),
84
+ { initialProps: { value: "behind", retain: true } },
85
+ );
86
+
87
+ expect(result.current.changedDuringOperation).toBe(false);
88
+
89
+ // Value changes while retaining
90
+ rerender({ value: "active", retain: true });
91
+ expect(result.current.changedDuringOperation).toBe(true);
92
+
93
+ // End retention - should still be true (for this render)
94
+ rerender({ value: "active", retain: false });
95
+ expect(result.current.changedDuringOperation).toBe(true);
96
+ });
97
+
98
+ it("resets changedDuringOperation after operation ends", () => {
99
+ const { result, rerender } = renderHook(
100
+ ({ value, retain }) => useOperationContinuity(value, retain),
101
+ { initialProps: { value: "behind", retain: true } },
102
+ );
103
+
104
+ // Value changes while retaining
105
+ rerender({ value: "active", retain: true });
106
+ expect(result.current.changedDuringOperation).toBe(true);
107
+
108
+ // End retention
109
+ rerender({ value: "active", retain: false });
110
+ expect(result.current.changedDuringOperation).toBe(true);
111
+
112
+ // Next render - should be reset
113
+ rerender({ value: "active", retain: false });
114
+ expect(result.current.changedDuringOperation).toBe(false);
115
+ });
116
+
117
+ it("tracks changes across multiple operations", () => {
118
+ const { result, rerender } = renderHook(
119
+ ({ value, retain }) => useOperationContinuity(value, retain),
120
+ { initialProps: { value: "behind", retain: true } },
121
+ );
122
+
123
+ // First operation: value changes
124
+ rerender({ value: "active", retain: true });
125
+ rerender({ value: "active", retain: false });
126
+ expect(result.current.changedDuringOperation).toBe(true);
127
+
128
+ // Reset
129
+ rerender({ value: "active", retain: false });
130
+ expect(result.current.changedDuringOperation).toBe(false);
131
+
132
+ // Second operation: no value change
133
+ rerender({ value: "active", retain: true });
134
+ rerender({ value: "active", retain: false });
135
+ expect(result.current.changedDuringOperation).toBe(false);
136
+
137
+ // Third operation: value changes again
138
+ rerender({ value: "active", retain: true });
139
+ rerender({ value: "hidden", retain: true });
140
+ expect(result.current.changedDuringOperation).toBe(true);
141
+ rerender({ value: "hidden", retain: false });
142
+ expect(result.current.changedDuringOperation).toBe(true);
143
+ });
144
+ });
145
+
146
+ describe("simultaneous value and retention change", () => {
147
+ /**
148
+ * CRITICAL: This tests the over-swipe bug scenario.
149
+ *
150
+ * In the real app, when user releases after over-swipe:
151
+ * - displacement becomes 0 (shouldRetainPrevious becomes false)
152
+ * - role changes from "active" to "hidden"
153
+ * Both happen in the same render!
154
+ *
155
+ * The hook should detect that the value changed even though
156
+ * the change happened at the exact moment retention ended.
157
+ */
158
+ it("detects value change when it happens simultaneously with retention ending", () => {
159
+ const { result, rerender } = renderHook(
160
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
161
+ { initialProps: { role: "active" as const, displacement: 500 } },
162
+ );
163
+
164
+ // During swipe: role="active", retaining
165
+ expect(result.current.value).toBe("active");
166
+ expect(result.current.changedDuringOperation).toBe(false);
167
+
168
+ // Simulate release: BOTH displacement becomes 0 AND role changes to "hidden"
169
+ // This is what happens in the real app during over-swipe
170
+ rerender({ role: "hidden" as const, displacement: 0 });
171
+
172
+ // value should now be "hidden" (retention ended)
173
+ expect(result.current.value).toBe("hidden");
174
+ // CRITICAL: changedDuringOperation should be TRUE because the value
175
+ // changed from "active" to "hidden" at the moment retention ended
176
+ expect(result.current.changedDuringOperation).toBe(true);
177
+ });
178
+
179
+ it("does not report change when value stays the same at retention end", () => {
180
+ const { result, rerender } = renderHook(
181
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
182
+ { initialProps: { role: "active" as const, displacement: 500 } },
183
+ );
184
+
185
+ expect(result.current.value).toBe("active");
186
+ expect(result.current.changedDuringOperation).toBe(false);
187
+
188
+ // Release but role stays "active" (e.g., partial swipe that didn't trigger navigation)
189
+ rerender({ role: "active" as const, displacement: 0 });
190
+
191
+ expect(result.current.value).toBe("active");
192
+ // No change occurred
193
+ expect(result.current.changedDuringOperation).toBe(false);
194
+ });
195
+
196
+ it("does NOT report change during button navigation (no operation)", () => {
197
+ // This is the button navigation case: value changes but there was never
198
+ // any retention (no swipe operation). We should NOT report changedDuringOperation
199
+ // because this is normal navigation, not an operation-related change.
200
+ const { result, rerender } = renderHook(
201
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
202
+ { initialProps: { role: "active" as const, displacement: 0 } },
203
+ );
204
+
205
+ expect(result.current.value).toBe("active");
206
+ expect(result.current.changedDuringOperation).toBe(false);
207
+
208
+ // Button navigation: role changes but there's no operation (displacement is always 0)
209
+ rerender({ role: "behind" as const, displacement: 0 });
210
+
211
+ expect(result.current.value).toBe("behind");
212
+ // CRITICAL: changedDuringOperation should be FALSE because there was no operation
213
+ // This allows the animation to happen normally for button navigation
214
+ expect(result.current.changedDuringOperation).toBe(false);
215
+ });
216
+ });
217
+
218
+ describe("role transition scenarios", () => {
219
+ it("maintains role continuity during swipe (behind -> active)", () => {
220
+ // Simulates: behind panel becomes active during swipe
221
+ // displacement > 0, so we should retain the previous role
222
+ const { result, rerender } = renderHook(
223
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
224
+ { initialProps: { role: "behind" as const, displacement: 100 } },
225
+ );
226
+
227
+ expect(result.current.value).toBe("behind");
228
+ expect(result.current.changedDuringOperation).toBe(false);
229
+
230
+ // Navigation changes role to "active" but displacement is still positive
231
+ rerender({ role: "active" as const, displacement: 100 });
232
+ expect(result.current.value).toBe("behind");
233
+ expect(result.current.changedDuringOperation).toBe(true);
234
+
235
+ // Swipe ends (displacement becomes 0)
236
+ rerender({ role: "active" as const, displacement: 0 });
237
+ expect(result.current.value).toBe("active");
238
+ expect(result.current.changedDuringOperation).toBe(true);
239
+ });
240
+
241
+ it("maintains role continuity during swipe (active -> hidden)", () => {
242
+ // Simulates: over-swipe where active panel becomes hidden
243
+ const { result, rerender } = renderHook(
244
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
245
+ { initialProps: { role: "active" as const, displacement: 400 } },
246
+ );
247
+
248
+ expect(result.current.value).toBe("active");
249
+
250
+ // Over-swipe triggers navigation change
251
+ rerender({ role: "hidden" as const, displacement: 500 });
252
+ expect(result.current.value).toBe("active");
253
+ expect(result.current.changedDuringOperation).toBe(true);
254
+
255
+ // Swipe ends
256
+ rerender({ role: "hidden" as const, displacement: 0 });
257
+ expect(result.current.value).toBe("hidden");
258
+ expect(result.current.changedDuringOperation).toBe(true);
259
+ });
260
+
261
+ it("provides changedDuringOperation for animation decision", () => {
262
+ // This test demonstrates the intended use case:
263
+ // Use changedDuringOperation to decide whether to animate on operation end
264
+ const { result, rerender } = renderHook(
265
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
266
+ { initialProps: { role: "behind" as const, displacement: 100 } },
267
+ );
268
+
269
+ // Simulate role change during swipe
270
+ rerender({ role: "active" as const, displacement: 100 });
271
+
272
+ // When swipe ends, changedDuringOperation tells us to skip animation
273
+ rerender({ role: "active" as const, displacement: 0 });
274
+ expect(result.current.changedDuringOperation).toBe(true);
275
+ // Consumer would use this to skip target change animation
276
+ });
277
+ });
278
+
279
+ describe("works with different value types", () => {
280
+ it("works with numbers", () => {
281
+ const { result, rerender } = renderHook(
282
+ ({ value, retain }) => useOperationContinuity(value, retain),
283
+ { initialProps: { value: 0, retain: true } },
284
+ );
285
+
286
+ rerender({ value: 1, retain: true });
287
+ expect(result.current.value).toBe(0);
288
+
289
+ rerender({ value: 1, retain: false });
290
+ expect(result.current.value).toBe(1);
291
+ });
292
+
293
+ it("works with objects (by reference)", () => {
294
+ const obj1 = { id: 1 };
295
+ const obj2 = { id: 2 };
296
+
297
+ const { result, rerender } = renderHook(
298
+ ({ value, retain }) => useOperationContinuity(value, retain),
299
+ { initialProps: { value: obj1, retain: true } },
300
+ );
301
+
302
+ rerender({ value: obj2, retain: true });
303
+ expect(result.current.value).toBe(obj1);
304
+
305
+ rerender({ value: obj2, retain: false });
306
+ expect(result.current.value).toBe(obj2);
307
+ });
308
+ });
309
+
310
+ describe("React StrictMode compatibility", () => {
311
+ /**
312
+ * CRITICAL: These tests verify the hook works correctly in StrictMode.
313
+ *
314
+ * In StrictMode, React calls the render function twice. Hooks that mutate
315
+ * refs during render will see the mutated value on the second call, which
316
+ * can cause bugs.
317
+ *
318
+ * This hook uses useLayoutEffect for ref mutations to avoid this issue.
319
+ */
320
+ it("operationJustEnded is correct in StrictMode", () => {
321
+ const { result, rerender } = renderHook(
322
+ ({ value, retain }) => useOperationContinuity(value, retain),
323
+ {
324
+ initialProps: { value: "active", retain: true },
325
+ wrapper: StrictModeWrapper,
326
+ },
327
+ );
328
+
329
+ // During retention
330
+ expect(result.current.operationJustEnded).toBe(false);
331
+
332
+ // End retention - operationJustEnded should be true
333
+ rerender({ value: "active", retain: false });
334
+ expect(result.current.operationJustEnded).toBe(true);
335
+
336
+ // Next render - should be false again
337
+ rerender({ value: "active", retain: false });
338
+ expect(result.current.operationJustEnded).toBe(false);
339
+ });
340
+
341
+ it("over-swipe scenario works in StrictMode", () => {
342
+ // This is the exact scenario that was broken before the fix:
343
+ // User swipes beyond 100%, releases, and we need operationJustEnded=true
344
+ // to prevent the visual jump.
345
+ const { result, rerender } = renderHook(
346
+ ({ role, displacement }) => useOperationContinuity(role, displacement > 0),
347
+ {
348
+ initialProps: { role: "active" as const, displacement: 500 },
349
+ wrapper: StrictModeWrapper,
350
+ },
351
+ );
352
+
353
+ // During over-swipe
354
+ expect(result.current.value).toBe("active");
355
+ expect(result.current.operationJustEnded).toBe(false);
356
+
357
+ // Release (displacement becomes 0)
358
+ rerender({ role: "active" as const, displacement: 0 });
359
+
360
+ // CRITICAL: operationJustEnded must be true even in StrictMode
361
+ // This is what was broken before the fix
362
+ expect(result.current.operationJustEnded).toBe(true);
363
+ expect(result.current.value).toBe("active");
364
+ });
365
+
366
+ it("changedDuringOperation is tracked correctly in StrictMode", () => {
367
+ const { result, rerender } = renderHook(
368
+ ({ value, retain }) => useOperationContinuity(value, retain),
369
+ {
370
+ initialProps: { value: "behind", retain: true },
371
+ wrapper: StrictModeWrapper,
372
+ },
373
+ );
374
+
375
+ expect(result.current.changedDuringOperation).toBe(false);
376
+
377
+ // Value changes during retention
378
+ rerender({ value: "active", retain: true });
379
+ expect(result.current.changedDuringOperation).toBe(true);
380
+
381
+ // End retention
382
+ rerender({ value: "active", retain: false });
383
+ expect(result.current.changedDuringOperation).toBe(true);
384
+ expect(result.current.operationJustEnded).toBe(true);
385
+ });
386
+ });
387
+ });
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @file Hook for maintaining value continuity during continuous operations.
3
+ *
4
+ * During operations like swipe gestures, external state (navigation depth, panel roles)
5
+ * may change before the gesture ends. This hook provides a pattern to:
6
+ * - Retain the previous value during the operation for visual continuity
7
+ * - Accept the new value when the operation ends
8
+ * - Track whether the value changed during the operation
9
+ *
10
+ * This is a core primitive for the "operation continuity" pattern used throughout
11
+ * the swipe gesture system.
12
+ */
13
+ import * as React from "react";
14
+
15
+ /**
16
+ * Result from useOperationContinuity hook.
17
+ */
18
+ export type UseOperationContinuityResult<T> = {
19
+ /** The effective value (retained during operation, current after) */
20
+ value: T;
21
+ /**
22
+ * True if the value changed during the operation.
23
+ *
24
+ * This is useful for determining how to handle the transition when the
25
+ * operation ends. For example, if the role changed during a swipe,
26
+ * the target position change at operation end should snap rather than animate.
27
+ *
28
+ * This flag is true on the render where shouldRetainPrevious becomes false
29
+ * (operation end), allowing consumers to handle the transition appropriately.
30
+ * It resets to false on subsequent renders.
31
+ */
32
+ changedDuringOperation: boolean;
33
+ /**
34
+ * True on the render where the operation just ended.
35
+ *
36
+ * This is true when shouldRetainPrevious transitions from true to false,
37
+ * regardless of whether the value changed. Use this to detect the moment
38
+ * when an operation completes and delay any immediate animations.
39
+ *
40
+ * In the over-swipe case, this helps prevent unwanted snap-back animation
41
+ * in the intermediate render before navigation changes.
42
+ */
43
+ operationJustEnded: boolean;
44
+ };
45
+
46
+ /**
47
+ * Hook for maintaining value continuity during continuous operations.
48
+ *
49
+ * When an operation is in progress, this hook retains the previous value
50
+ * to prevent sudden visual changes from state updates. Once the operation
51
+ * ends (shouldRetainPrevious becomes false), the new value is accepted.
52
+ *
53
+ * Additionally, this hook tracks whether the value changed during the operation,
54
+ * which is useful for determining animation behavior at operation end.
55
+ *
56
+ * IMPORTANT: This hook is designed to be idempotent during render to work
57
+ * correctly with React StrictMode, which calls the render function twice.
58
+ * All ref mutations happen in useLayoutEffect, not during render.
59
+ *
60
+ * @param value - The current value from external state
61
+ * @param shouldRetainPrevious - Whether to retain the previous value (true during operation)
62
+ * @returns Object with effective value and whether it changed during operation
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * // Maintain role continuity during swipe
67
+ * const { value: effectiveRole, changedDuringOperation } = useOperationContinuity(
68
+ * role,
69
+ * displacement > 0,
70
+ * );
71
+ *
72
+ * // Use changedDuringOperation to skip animation on operation end
73
+ * useSwipeContentTransform({
74
+ * // ...
75
+ * skipTargetChangeAnimation: changedDuringOperation,
76
+ * });
77
+ * ```
78
+ */
79
+ export function useOperationContinuity<T>(
80
+ value: T,
81
+ shouldRetainPrevious: boolean,
82
+ ): UseOperationContinuityResult<T> {
83
+ // Store previous shouldRetainPrevious to detect transitions
84
+ const prevShouldRetainRef = React.useRef(shouldRetainPrevious);
85
+ // Store retained value (the value at the start of retention)
86
+ const retainedValueRef = React.useRef(value);
87
+ // Track if value changed during retention
88
+ const changedDuringRetentionRef = React.useRef(false);
89
+
90
+ // Derive operationJustEnded from transition: true → false
91
+ // This is idempotent - safe for StrictMode double-render
92
+ const wasRetaining = prevShouldRetainRef.current;
93
+ const operationJustEnded = wasRetaining && !shouldRetainPrevious;
94
+
95
+ // Check if value diverged from retained value
96
+ // This includes both current-render divergence and previously-tracked divergence
97
+ const valueDiverged = value !== retainedValueRef.current;
98
+ const currentlyDiverged = shouldRetainPrevious && valueDiverged;
99
+
100
+ // Derive changedDuringOperation
101
+ // True if:
102
+ // 1. Value diverged during retention (tracked from previous renders via ref)
103
+ // 2. Value diverges right now during retention (immediate comparison)
104
+ // 3. Value diverged at the moment retention ends
105
+ const changedDuringRetention = changedDuringRetentionRef.current || currentlyDiverged;
106
+ const changedAtExit = operationJustEnded && valueDiverged;
107
+ const changedDuringOperation = changedDuringRetention || changedAtExit;
108
+
109
+ // Determine effective value
110
+ // During retention: use retained value
111
+ // After retention ends: use current value
112
+ const effectiveValue = shouldRetainPrevious ? retainedValueRef.current : value;
113
+
114
+ // Update refs in useLayoutEffect to ensure idempotency during render.
115
+ // This runs once per commit, not per render in StrictMode.
116
+ React.useLayoutEffect(() => {
117
+ if (!shouldRetainPrevious) {
118
+ // Retention ended or never started - reset state
119
+ changedDuringRetentionRef.current = false;
120
+ retainedValueRef.current = value;
121
+ } else {
122
+ // During retention - track if value diverged
123
+ if (currentlyDiverged) {
124
+ changedDuringRetentionRef.current = true;
125
+ }
126
+ }
127
+ prevShouldRetainRef.current = shouldRetainPrevious;
128
+ });
129
+
130
+ return {
131
+ value: effectiveValue,
132
+ changedDuringOperation,
133
+ operationJustEnded,
134
+ };
135
+ }