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,882 @@
1
+ /**
2
+ * @file Tests for SwipePivotTabBar - covering all navigation patterns
3
+ *
4
+ * Slot-based rendering model:
5
+ * - Tabs are rendered at slot positions, not by item
6
+ * - Active tab is always at slot 0 (center)
7
+ * - Same tab may appear at multiple slots (clones for infinite loop)
8
+ * - Query by data-slot attribute for unique identification
9
+ */
10
+ import { render, screen, act } from "@testing-library/react";
11
+ import * as React from "react";
12
+ import { SwipePivotTabBar } from "./SwipePivotTabBar";
13
+ import type { IndicatorRenderProps } from "./SwipePivotTabBar";
14
+ import type { SwipeInputState } from "../../hooks/gesture/types";
15
+
16
+ // Mock requestAnimationFrame for animation testing
17
+ const rafState = {
18
+ callbacks: [] as FrameRequestCallback[],
19
+ id: 0,
20
+ originalRAF: globalThis.requestAnimationFrame,
21
+ originalCAF: globalThis.cancelAnimationFrame,
22
+ };
23
+
24
+ const resetRafState = (): void => {
25
+ rafState.callbacks = [];
26
+ rafState.id = 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 = (id: number): void => {
36
+ void id;
37
+ };
38
+
39
+ const flushRAF = (): void => {
40
+ const callbacks = rafState.callbacks;
41
+ rafState.callbacks = [];
42
+ callbacks.forEach((cb) => cb(performance.now()));
43
+ };
44
+
45
+ type RenderTracker<TArgs extends ReadonlyArray<unknown>, TResult> = {
46
+ calls: ReadonlyArray<TArgs>;
47
+ fn: (...args: TArgs) => TResult;
48
+ };
49
+
50
+ const createRenderTracker = <TArgs extends ReadonlyArray<unknown>, TResult>(
51
+ implementation: (...args: TArgs) => TResult,
52
+ ): RenderTracker<TArgs, TResult> => {
53
+ const calls: Array<TArgs> = [];
54
+ const fn = (...args: TArgs): TResult => {
55
+ calls.push(args);
56
+ return implementation(...args);
57
+ };
58
+ return { calls, fn };
59
+ };
60
+
61
+ beforeEach(() => {
62
+ resetRafState();
63
+ globalThis.requestAnimationFrame = mockRAF;
64
+ globalThis.cancelAnimationFrame = mockCAF;
65
+ });
66
+
67
+ afterEach(() => {
68
+ globalThis.requestAnimationFrame = rafState.originalRAF;
69
+ globalThis.cancelAnimationFrame = rafState.originalCAF;
70
+ });
71
+
72
+ const createItems = () => [
73
+ { id: "tab1", label: "Tab 1" },
74
+ { id: "tab2", label: "Tab 2" },
75
+ { id: "tab3", label: "Tab 3" },
76
+ { id: "tab4", label: "Tab 4" },
77
+ { id: "tab5", label: "Tab 5" },
78
+ ];
79
+
80
+ const idleState: SwipeInputState = {
81
+ phase: "idle",
82
+ displacement: { x: 0, y: 0 },
83
+ velocity: { x: 0, y: 0 },
84
+ direction: 0,
85
+ };
86
+
87
+ const swipingLeftState = (displacement: number): SwipeInputState => ({
88
+ phase: "swiping",
89
+ displacement: { x: displacement, y: 0 },
90
+ velocity: { x: -0.5, y: 0 },
91
+ direction: -1,
92
+ });
93
+
94
+ const endedState = (direction: -1 | 0 | 1): SwipeInputState => ({
95
+ phase: "ended",
96
+ displacement: { x: 0, y: 0 },
97
+ velocity: { x: 0, y: 0 },
98
+ direction,
99
+ });
100
+
101
+ const defaultProps = {
102
+ items: createItems(),
103
+ tabWidth: 100,
104
+ viewportWidth: 500,
105
+ navigationMode: "loop" as const,
106
+ renderTab: (item: { id: string; label?: string }, isActive: boolean) => (
107
+ <button data-testid={`tab-${item.id}`} data-active={isActive}>
108
+ {item.label}
109
+ </button>
110
+ ),
111
+ };
112
+
113
+ // Helper: centerX for default props
114
+ const centerX = (defaultProps.viewportWidth - defaultProps.tabWidth) / 2; // 200
115
+
116
+ // Helper to get slot element by position
117
+ const getSlot = (container: HTMLElement, slotPosition: number): HTMLElement | null => {
118
+ return container.querySelector(`[data-slot="${slotPosition}"]`);
119
+ };
120
+
121
+ // Helper to get all visible slots
122
+ describe("SwipePivotTabBar", () => {
123
+ describe("Initial render", () => {
124
+ it("renders tabs at slot positions", () => {
125
+ const { container } = render(
126
+ <SwipePivotTabBar
127
+ {...defaultProps}
128
+ activeId="tab1"
129
+ activeIndex={0}
130
+ itemCount={5}
131
+ inputState={idleState}
132
+ />
133
+ );
134
+
135
+ // Slot 0 should have tab1 (active)
136
+ const slot0 = getSlot(container, 0);
137
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
138
+ expect(slot0).toHaveAttribute("data-active", "true");
139
+
140
+ // Slot 1 should have tab2
141
+ const slot1 = getSlot(container, 1);
142
+ expect(slot1).toHaveAttribute("data-pivot-tab", "tab2");
143
+
144
+ // Slot -1 should have tab5 (loop)
145
+ const slotMinus1 = getSlot(container, -1);
146
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "tab5");
147
+ });
148
+
149
+ it("centers active tab in viewport", () => {
150
+ const { container } = render(
151
+ <SwipePivotTabBar
152
+ {...defaultProps}
153
+ activeId="tab3"
154
+ activeIndex={2}
155
+ itemCount={5}
156
+ inputState={idleState}
157
+ />
158
+ );
159
+
160
+ // Slot 0 always has active tab, at center
161
+ const slot0 = getSlot(container, 0);
162
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab3");
163
+ expect(slot0).toHaveStyle({ left: `${centerX}px` });
164
+ expect(slot0).toHaveStyle({ transform: "translateX(0px)" });
165
+ });
166
+ });
167
+
168
+ describe("Pattern 1: Swipe to adjacent tab (threshold exceeded)", () => {
169
+ it("follows finger during swipe", () => {
170
+ const { container, rerender } = render(
171
+ <SwipePivotTabBar
172
+ {...defaultProps}
173
+ activeId="tab1"
174
+ activeIndex={0}
175
+ itemCount={5}
176
+ inputState={idleState}
177
+ />
178
+ );
179
+
180
+ // Start swiping left
181
+ rerender(
182
+ <SwipePivotTabBar
183
+ {...defaultProps}
184
+ activeId="tab1"
185
+ activeIndex={0}
186
+ itemCount={5}
187
+ inputState={swipingLeftState(-50)}
188
+ />
189
+ );
190
+
191
+ // All slots should be offset by displacement
192
+ const slot0 = getSlot(container, 0);
193
+ expect(slot0).toHaveStyle({ transform: "translateX(-50px)" });
194
+
195
+ const slot1 = getSlot(container, 1);
196
+ expect(slot1).toHaveStyle({ transform: "translateX(50px)" }); // 100 + (-50)
197
+ });
198
+
199
+ it("animates to new position after swipe ends", () => {
200
+ const { container, rerender } = render(
201
+ <SwipePivotTabBar
202
+ {...defaultProps}
203
+ activeId="tab1"
204
+ activeIndex={0}
205
+ itemCount={5}
206
+ inputState={swipingLeftState(-150)}
207
+ />
208
+ );
209
+
210
+ // Swipe ends, activeIndex changes to 1
211
+ rerender(
212
+ <SwipePivotTabBar
213
+ {...defaultProps}
214
+ activeId="tab2"
215
+ activeIndex={1}
216
+ itemCount={5}
217
+ inputState={endedState(-1)}
218
+ />
219
+ );
220
+
221
+ // Slot 0 should now have tab2 (new active)
222
+ const slot0 = getSlot(container, 0);
223
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab2");
224
+ expect(slot0).toHaveAttribute("data-active", "true");
225
+ });
226
+ });
227
+
228
+ describe("Pattern 2: Swipe cancel (threshold not met)", () => {
229
+ it("returns to original position when swipe is cancelled", () => {
230
+ const { container, rerender } = render(
231
+ <SwipePivotTabBar
232
+ {...defaultProps}
233
+ activeId="tab1"
234
+ activeIndex={0}
235
+ itemCount={5}
236
+ inputState={swipingLeftState(-30)}
237
+ />
238
+ );
239
+
240
+ // Swipe ends without threshold, activeIndex stays the same
241
+ rerender(
242
+ <SwipePivotTabBar
243
+ {...defaultProps}
244
+ activeId="tab1"
245
+ activeIndex={0}
246
+ itemCount={5}
247
+ inputState={idleState}
248
+ />
249
+ );
250
+
251
+ // Slot 0 should still have tab1
252
+ const slot0 = getSlot(container, 0);
253
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
254
+ });
255
+ });
256
+
257
+ describe("Pattern 3: Click adjacent tab", () => {
258
+ it("animates when adjacent tab is clicked", () => {
259
+ const { container, rerender } = render(
260
+ <SwipePivotTabBar
261
+ {...defaultProps}
262
+ activeId="tab1"
263
+ activeIndex={0}
264
+ itemCount={5}
265
+ inputState={idleState}
266
+ />
267
+ );
268
+
269
+ // Tab2 is clicked, activeIndex changes to 1
270
+ rerender(
271
+ <SwipePivotTabBar
272
+ {...defaultProps}
273
+ activeId="tab2"
274
+ activeIndex={1}
275
+ itemCount={5}
276
+ inputState={idleState}
277
+ />
278
+ );
279
+
280
+ // Slot 0 should now have tab2
281
+ const slot0 = getSlot(container, 0);
282
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab2");
283
+ });
284
+ });
285
+
286
+ describe("Pattern 4: Click non-adjacent tab", () => {
287
+ it("animates when distant tab is clicked (Tab 1 → Tab 5)", () => {
288
+ const { container, rerender } = render(
289
+ <SwipePivotTabBar
290
+ {...defaultProps}
291
+ activeId="tab1"
292
+ activeIndex={0}
293
+ itemCount={5}
294
+ inputState={idleState}
295
+ />
296
+ );
297
+
298
+ // Tab5 is clicked (which is at slot -1 in loop mode from tab1)
299
+ rerender(
300
+ <SwipePivotTabBar
301
+ {...defaultProps}
302
+ activeId="tab5"
303
+ activeIndex={4}
304
+ itemCount={5}
305
+ inputState={idleState}
306
+ />
307
+ );
308
+
309
+ // Slot 0 should now have tab5
310
+ const slot0 = getSlot(container, 0);
311
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab5");
312
+ });
313
+
314
+ it("calculates shortest path in loop mode (Tab 1 → Tab 5 = -1 step)", () => {
315
+ const { container, rerender } = render(
316
+ <SwipePivotTabBar
317
+ {...defaultProps}
318
+ activeId="tab1"
319
+ activeIndex={0}
320
+ itemCount={5}
321
+ inputState={idleState}
322
+ />
323
+ );
324
+
325
+ // In loop mode, Tab5 is at slot -1 relative to Tab1
326
+ // So going Tab1 → Tab5 should animate backward (shortest path)
327
+ rerender(
328
+ <SwipePivotTabBar
329
+ {...defaultProps}
330
+ activeId="tab5"
331
+ activeIndex={4}
332
+ itemCount={5}
333
+ inputState={idleState}
334
+ />
335
+ );
336
+
337
+ // After transition, slot configuration should be:
338
+ // Slot 0: tab5 (active)
339
+ // Slot 1: tab1
340
+ // Slot -1: tab4
341
+ const slot0 = getSlot(container, 0);
342
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab5");
343
+
344
+ const slot1 = getSlot(container, 1);
345
+ expect(slot1).toHaveAttribute("data-pivot-tab", "tab1");
346
+
347
+ const slotMinus1 = getSlot(container, -1);
348
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "tab4");
349
+ });
350
+ });
351
+
352
+ describe("Pattern 5: Previous/Next button click", () => {
353
+ it("animates forward when Next button is clicked", () => {
354
+ const { container, rerender } = render(
355
+ <SwipePivotTabBar
356
+ {...defaultProps}
357
+ activeId="tab2"
358
+ activeIndex={1}
359
+ itemCount={5}
360
+ inputState={idleState}
361
+ />
362
+ );
363
+
364
+ // Next button clicked, goes to tab3
365
+ rerender(
366
+ <SwipePivotTabBar
367
+ {...defaultProps}
368
+ activeId="tab3"
369
+ activeIndex={2}
370
+ itemCount={5}
371
+ inputState={idleState}
372
+ />
373
+ );
374
+
375
+ const slot0 = getSlot(container, 0);
376
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab3");
377
+ });
378
+
379
+ it("animates backward when Previous button is clicked", () => {
380
+ const { container, rerender } = render(
381
+ <SwipePivotTabBar
382
+ {...defaultProps}
383
+ activeId="tab3"
384
+ activeIndex={2}
385
+ itemCount={5}
386
+ inputState={idleState}
387
+ />
388
+ );
389
+
390
+ // Previous button clicked, goes to tab2
391
+ rerender(
392
+ <SwipePivotTabBar
393
+ {...defaultProps}
394
+ activeId="tab2"
395
+ activeIndex={1}
396
+ itemCount={5}
397
+ inputState={idleState}
398
+ />
399
+ );
400
+
401
+ const slot0 = getSlot(container, 0);
402
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab2");
403
+ });
404
+ });
405
+
406
+ describe("Pattern 6: Loop boundary swipe (Tab 5 → Tab 1)", () => {
407
+ it("swipes seamlessly from Tab 5 to Tab 1", () => {
408
+ const { container, rerender } = render(
409
+ <SwipePivotTabBar
410
+ {...defaultProps}
411
+ activeId="tab5"
412
+ activeIndex={4}
413
+ itemCount={5}
414
+ inputState={idleState}
415
+ />
416
+ );
417
+
418
+ // At Tab5, slot 1 should have Tab1 (loop)
419
+ const slot1Before = getSlot(container, 1);
420
+ expect(slot1Before).toHaveAttribute("data-pivot-tab", "tab1");
421
+
422
+ // Swipe left to go to Tab1
423
+ rerender(
424
+ <SwipePivotTabBar
425
+ {...defaultProps}
426
+ activeId="tab5"
427
+ activeIndex={4}
428
+ itemCount={5}
429
+ inputState={swipingLeftState(-100)}
430
+ />
431
+ );
432
+
433
+ // After swipe completes, Tab1 becomes active
434
+ rerender(
435
+ <SwipePivotTabBar
436
+ {...defaultProps}
437
+ activeId="tab1"
438
+ activeIndex={0}
439
+ itemCount={5}
440
+ inputState={endedState(-1)}
441
+ />
442
+ );
443
+
444
+ const slot0 = getSlot(container, 0);
445
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
446
+ });
447
+ });
448
+
449
+ describe("Pattern 7: Loop boundary click (Tab 5 → Tab 1)", () => {
450
+ it("clicking Tab 1 from Tab 5 animates correctly in loop mode", () => {
451
+ const { container, rerender } = render(
452
+ <SwipePivotTabBar
453
+ {...defaultProps}
454
+ activeId="tab5"
455
+ activeIndex={4}
456
+ itemCount={5}
457
+ inputState={idleState}
458
+ />
459
+ );
460
+
461
+ // Click Tab1
462
+ rerender(
463
+ <SwipePivotTabBar
464
+ {...defaultProps}
465
+ activeId="tab1"
466
+ activeIndex={0}
467
+ itemCount={5}
468
+ inputState={idleState}
469
+ />
470
+ );
471
+
472
+ // Tab1 should now be at slot 0
473
+ const slot0 = getSlot(container, 0);
474
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
475
+
476
+ // Tab5 should be at slot -1
477
+ const slotMinus1 = getSlot(container, -1);
478
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "tab5");
479
+ });
480
+ });
481
+
482
+ describe("Pattern 8: Linear mode", () => {
483
+ it("does not wrap in linear mode", () => {
484
+ const { container } = render(
485
+ <SwipePivotTabBar
486
+ {...defaultProps}
487
+ navigationMode="linear"
488
+ activeId="tab1"
489
+ activeIndex={0}
490
+ itemCount={5}
491
+ inputState={idleState}
492
+ />
493
+ );
494
+
495
+ // In linear mode, slot -1 should be empty (no tab before tab1)
496
+ const slotMinus1 = getSlot(container, -1);
497
+ expect(slotMinus1).toBeNull();
498
+
499
+ // Slot 4 should have tab5
500
+ const slot4 = getSlot(container, 4);
501
+ expect(slot4).toHaveAttribute("data-pivot-tab", "tab5");
502
+ });
503
+ });
504
+
505
+ describe("Visibility", () => {
506
+ it("hides tabs outside viewport", () => {
507
+ const { container } = render(
508
+ <SwipePivotTabBar
509
+ {...defaultProps}
510
+ activeId="tab3"
511
+ activeIndex={2}
512
+ itemCount={5}
513
+ inputState={idleState}
514
+ />
515
+ );
516
+
517
+ // Slot 0 (center) should be visible
518
+ const slot0 = getSlot(container, 0);
519
+ expect(slot0).toHaveStyle({ visibility: "visible" });
520
+
521
+ // Distant slots should be hidden
522
+ const slot4 = getSlot(container, 4);
523
+ if (slot4) {
524
+ expect(slot4).toHaveStyle({ visibility: "hidden" });
525
+ }
526
+ });
527
+
528
+ it("shows tabs that enter viewport during swipe", () => {
529
+ const { container, rerender } = render(
530
+ <SwipePivotTabBar
531
+ {...defaultProps}
532
+ activeId="tab1"
533
+ activeIndex={0}
534
+ itemCount={5}
535
+ inputState={idleState}
536
+ />
537
+ );
538
+
539
+ // Swipe to reveal more tabs
540
+ rerender(
541
+ <SwipePivotTabBar
542
+ {...defaultProps}
543
+ activeId="tab1"
544
+ activeIndex={0}
545
+ itemCount={5}
546
+ inputState={swipingLeftState(-100)}
547
+ />
548
+ );
549
+
550
+ // Slot 1 should be visible (tab2)
551
+ const slot1 = getSlot(container, 1);
552
+ expect(slot1).toHaveStyle({ visibility: "visible" });
553
+ });
554
+ });
555
+
556
+ describe("Clone handling for small item counts", () => {
557
+ it("handles 2 items in loop mode", () => {
558
+ const twoItems = [
559
+ { id: "a", label: "A" },
560
+ { id: "b", label: "B" },
561
+ ];
562
+
563
+ const { container } = render(
564
+ <SwipePivotTabBar
565
+ {...defaultProps}
566
+ items={twoItems}
567
+ activeId="a"
568
+ activeIndex={0}
569
+ itemCount={2}
570
+ inputState={idleState}
571
+ />
572
+ );
573
+
574
+ // Slot 0: a
575
+ const slot0 = getSlot(container, 0);
576
+ expect(slot0).toHaveAttribute("data-pivot-tab", "a");
577
+
578
+ // Slot 1: b
579
+ const slot1 = getSlot(container, 1);
580
+ expect(slot1).toHaveAttribute("data-pivot-tab", "b");
581
+
582
+ // Slot -1: b (loop)
583
+ const slotMinus1 = getSlot(container, -1);
584
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "b");
585
+
586
+ // Slot 2: a (clone)
587
+ const slot2 = getSlot(container, 2);
588
+ expect(slot2).toHaveAttribute("data-pivot-tab", "a");
589
+ });
590
+
591
+ it("handles 3 items in loop mode", () => {
592
+ const threeItems = [
593
+ { id: "x", label: "X" },
594
+ { id: "y", label: "Y" },
595
+ { id: "z", label: "Z" },
596
+ ];
597
+
598
+ const { container } = render(
599
+ <SwipePivotTabBar
600
+ {...defaultProps}
601
+ items={threeItems}
602
+ activeId="x"
603
+ activeIndex={0}
604
+ itemCount={3}
605
+ inputState={idleState}
606
+ />
607
+ );
608
+
609
+ // Slot 0: x (active)
610
+ const slot0 = getSlot(container, 0);
611
+ expect(slot0).toHaveAttribute("data-pivot-tab", "x");
612
+
613
+ // Slot 1: y
614
+ const slot1 = getSlot(container, 1);
615
+ expect(slot1).toHaveAttribute("data-pivot-tab", "y");
616
+
617
+ // Slot -1: z
618
+ const slotMinus1 = getSlot(container, -1);
619
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "z");
620
+ });
621
+ });
622
+
623
+ describe("Sliding indicator (iOS-style)", () => {
624
+ it("renders indicator with correct offset props", () => {
625
+ const indicatorTracker = createRenderTracker<readonly [IndicatorRenderProps], React.ReactNode>(() => (
626
+ <div data-testid="indicator" />
627
+ ));
628
+
629
+ render(
630
+ <SwipePivotTabBar
631
+ {...defaultProps}
632
+ activeId="tab1"
633
+ activeIndex={0}
634
+ itemCount={5}
635
+ inputState={idleState}
636
+ renderIndicator={indicatorTracker.fn}
637
+ />
638
+ );
639
+
640
+ expect(indicatorTracker.calls).toHaveLength(1);
641
+ expect(indicatorTracker.calls[0]?.[0]).toEqual({
642
+ offsetPx: 0,
643
+ tabWidth: 100,
644
+ centerX: 200,
645
+ isSwiping: false,
646
+ isAnimating: false,
647
+ });
648
+
649
+ expect(screen.getByTestId("indicator")).toBeInTheDocument();
650
+ });
651
+
652
+ it("passes swipe displacement to indicator", () => {
653
+ const indicatorTracker = createRenderTracker<readonly [IndicatorRenderProps], React.ReactNode>(() => (
654
+ <div data-testid="indicator" />
655
+ ));
656
+
657
+ const { rerender } = render(
658
+ <SwipePivotTabBar
659
+ {...defaultProps}
660
+ activeId="tab1"
661
+ activeIndex={0}
662
+ itemCount={5}
663
+ inputState={idleState}
664
+ renderIndicator={indicatorTracker.fn}
665
+ />
666
+ );
667
+
668
+ rerender(
669
+ <SwipePivotTabBar
670
+ {...defaultProps}
671
+ activeId="tab1"
672
+ activeIndex={0}
673
+ itemCount={5}
674
+ inputState={swipingLeftState(-60)}
675
+ renderIndicator={indicatorTracker.fn}
676
+ />
677
+ );
678
+
679
+ // Last call should have the swipe offset
680
+ expect(indicatorTracker.calls.length).toBeGreaterThan(0);
681
+ const lastCall = indicatorTracker.calls[indicatorTracker.calls.length - 1]![0];
682
+ expect(lastCall.offsetPx).toBe(-60);
683
+ expect(lastCall.isSwiping).toBe(true);
684
+ });
685
+
686
+ it("indicator follows same offset as tabs", () => {
687
+ const indicatorState = { offset: 0 };
688
+
689
+ const { container, rerender } = render(
690
+ <SwipePivotTabBar
691
+ {...defaultProps}
692
+ activeId="tab1"
693
+ activeIndex={0}
694
+ itemCount={5}
695
+ inputState={idleState}
696
+ renderIndicator={({ offsetPx }) => {
697
+ indicatorState.offset = offsetPx;
698
+ return <div data-testid="indicator" />;
699
+ }}
700
+ />
701
+ );
702
+
703
+ rerender(
704
+ <SwipePivotTabBar
705
+ {...defaultProps}
706
+ activeId="tab1"
707
+ activeIndex={0}
708
+ itemCount={5}
709
+ inputState={swipingLeftState(-80)}
710
+ renderIndicator={({ offsetPx }) => {
711
+ indicatorState.offset = offsetPx;
712
+ return <div data-testid="indicator" />;
713
+ }}
714
+ />
715
+ );
716
+
717
+ // Verify indicator offset matches tab offset
718
+ const slot0 = getSlot(container, 0);
719
+ expect(slot0).toHaveStyle({ transform: "translateX(-80px)" });
720
+ expect(indicatorState.offset).toBe(-80);
721
+ });
722
+ });
723
+
724
+ describe("Continuous offset model", () => {
725
+ it("all slots move together during swipe", () => {
726
+ const { container, rerender } = render(
727
+ <SwipePivotTabBar
728
+ {...defaultProps}
729
+ activeId="tab1"
730
+ activeIndex={0}
731
+ itemCount={5}
732
+ inputState={idleState}
733
+ />
734
+ );
735
+
736
+ // Initial positions
737
+ const slot0Before = getSlot(container, 0);
738
+ const slot1Before = getSlot(container, 1);
739
+ expect(slot0Before).toHaveStyle({ transform: "translateX(0px)" });
740
+ expect(slot1Before).toHaveStyle({ transform: "translateX(100px)" });
741
+
742
+ // During swipe, all slots offset by same amount
743
+ rerender(
744
+ <SwipePivotTabBar
745
+ {...defaultProps}
746
+ activeId="tab1"
747
+ activeIndex={0}
748
+ itemCount={5}
749
+ inputState={swipingLeftState(-75)}
750
+ />
751
+ );
752
+
753
+ const slot0After = getSlot(container, 0);
754
+ const slot1After = getSlot(container, 1);
755
+ expect(slot0After).toHaveStyle({ transform: "translateX(-75px)" });
756
+ expect(slot1After).toHaveStyle({ transform: "translateX(25px)" }); // 100 - 75
757
+ });
758
+ });
759
+
760
+ describe("Fixed tabs mode (iOS segmented control style)", () => {
761
+ it("tabs stay fixed during swipe, only indicator moves", () => {
762
+ const indicatorState = { offset: 0, centerX: 0 };
763
+
764
+ const { container, rerender } = render(
765
+ <SwipePivotTabBar
766
+ {...defaultProps}
767
+ activeId="tab1"
768
+ activeIndex={0}
769
+ itemCount={5}
770
+ fixedTabs={true}
771
+ inputState={idleState}
772
+ renderIndicator={({ offsetPx, centerX }) => {
773
+ indicatorState.offset = offsetPx;
774
+ indicatorState.centerX = centerX;
775
+ return <div data-testid="indicator" />;
776
+ }}
777
+ />
778
+ );
779
+
780
+ // All tabs should be rendered (not slot-based)
781
+ const tabs = container.querySelectorAll("[data-pivot-tab]");
782
+ expect(tabs.length).toBe(5);
783
+
784
+ // Initial state: indicator at first tab position
785
+ // viewportWidth=500, 5 tabs * 100px = 500px, centeringOffset = (500-500)/2 = 0
786
+ // centerX is fixed at centeringOffset = 0
787
+ // offsetPx = activeIndex * tabWidth = 0 * 100 = 0
788
+ expect(indicatorState.centerX).toBe(0);
789
+ expect(indicatorState.offset).toBe(0);
790
+
791
+ // During swipe, tabs should NOT have transform (they're fixed)
792
+ rerender(
793
+ <SwipePivotTabBar
794
+ {...defaultProps}
795
+ activeId="tab1"
796
+ activeIndex={0}
797
+ itemCount={5}
798
+ fixedTabs={true}
799
+ inputState={swipingLeftState(-80)}
800
+ renderIndicator={({ offsetPx, centerX }) => {
801
+ indicatorState.offset = offsetPx;
802
+ indicatorState.centerX = centerX;
803
+ return <div data-testid="indicator" />;
804
+ }}
805
+ />
806
+ );
807
+
808
+ // Indicator should move OPPOSITE to swipe direction
809
+ // Swipe left (displacement = -80) → indicator moves right (+80)
810
+ expect(indicatorState.offset).toBe(80);
811
+
812
+ // Tabs should not have any transform applied (position is relative, not absolute with transform)
813
+ const tabsAfterSwipe = container.querySelectorAll("[data-pivot-tab]");
814
+ tabsAfterSwipe.forEach((tab) => {
815
+ const style = window.getComputedStyle(tab as Element);
816
+ expect(style.position).toBe("relative");
817
+ // No translateX in transform (or transform is 'none')
818
+ expect(style.transform).not.toMatch(/translateX/);
819
+ });
820
+ });
821
+
822
+ it("indicator moves to new tab position when activeIndex changes", () => {
823
+ const indicatorState = { offsetPx: 0, centerX: 0 };
824
+
825
+ // Mock performance.now to control animation timing
826
+ const originalNow = performance.now;
827
+ const timeState = { value: 0 };
828
+ performance.now = () => timeState.value;
829
+
830
+ const { rerender } = render(
831
+ <SwipePivotTabBar
832
+ {...defaultProps}
833
+ activeId="tab1"
834
+ activeIndex={0}
835
+ itemCount={5}
836
+ fixedTabs={true}
837
+ inputState={idleState}
838
+ renderIndicator={({ offsetPx, centerX }) => {
839
+ indicatorState.offsetPx = offsetPx;
840
+ indicatorState.centerX = centerX;
841
+ return <div data-testid="indicator" />;
842
+ }}
843
+ />
844
+ );
845
+
846
+ // centerX is fixed at centering offset (0 for 5 tabs * 100px = 500px viewport)
847
+ expect(indicatorState.centerX).toBe(0);
848
+ // offsetPx includes active tab position
849
+ expect(indicatorState.offsetPx).toBe(0); // Tab 1 at position 0
850
+
851
+ rerender(
852
+ <SwipePivotTabBar
853
+ {...defaultProps}
854
+ activeId="tab3"
855
+ activeIndex={2}
856
+ itemCount={5}
857
+ fixedTabs={true}
858
+ inputState={idleState}
859
+ renderIndicator={({ offsetPx, centerX }) => {
860
+ indicatorState.offsetPx = offsetPx;
861
+ indicatorState.centerX = centerX;
862
+ return <div data-testid="indicator" />;
863
+ }}
864
+ />
865
+ );
866
+
867
+ // Animation starts - flush RAF callbacks until animation completes
868
+ timeState.value = 500; // Advance past animation duration (300ms default)
869
+ act(() => {
870
+ Array.from({ length: 10 }).forEach(() => {
871
+ flushRAF();
872
+ });
873
+ });
874
+
875
+ // centerX stays fixed
876
+ expect(indicatorState.centerX).toBe(0);
877
+ // offsetPx now includes Tab 3 position (after animation completes)
878
+ expect(indicatorState.offsetPx).toBe(200); // Tab 3 at position 2 * 100
879
+ performance.now = originalNow;
880
+ });
881
+ });
882
+ });