react-panel-layout 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
  2. package/dist/FloatingPanelFrame-CEmXDvUA.cjs.map +1 -0
  3. package/dist/{FloatingPanelFrame-6W5OexYe.js → FloatingPanelFrame-SgYLc6Ud.js} +12 -15
  4. package/dist/FloatingPanelFrame-SgYLc6Ud.js.map +1 -0
  5. package/dist/FloatingWindow-BpdOpg_L.js +400 -0
  6. package/dist/FloatingWindow-BpdOpg_L.js.map +1 -0
  7. package/dist/FloatingWindow-TCDNY5gE.cjs +2 -0
  8. package/dist/FloatingWindow-TCDNY5gE.cjs.map +1 -0
  9. package/dist/GridLayout-B4VRsC0r.cjs +2 -0
  10. package/dist/GridLayout-B4VRsC0r.cjs.map +1 -0
  11. package/dist/GridLayout-BltqeCPK.js +927 -0
  12. package/dist/GridLayout-BltqeCPK.js.map +1 -0
  13. package/dist/HorizontalDivider-B5Z-KZLk.cjs +2 -0
  14. package/dist/HorizontalDivider-B5Z-KZLk.cjs.map +1 -0
  15. package/dist/HorizontalDivider-WF1k_qND.js +30 -0
  16. package/dist/HorizontalDivider-WF1k_qND.js.map +1 -0
  17. package/dist/PanelSystem-Bs8bQwQF.cjs +3 -0
  18. package/dist/PanelSystem-Bs8bQwQF.cjs.map +1 -0
  19. package/dist/PanelSystem-Dr1TBhxM.js +1946 -0
  20. package/dist/PanelSystem-Dr1TBhxM.js.map +1 -0
  21. package/dist/ResizeHandle-CScipO5l.cjs +2 -0
  22. package/dist/ResizeHandle-CScipO5l.cjs.map +1 -0
  23. package/dist/ResizeHandle-CdA_JYfN.js +120 -0
  24. package/dist/ResizeHandle-CdA_JYfN.js.map +1 -0
  25. package/dist/SwipePivotTabBar-BGO9X94m.js +407 -0
  26. package/dist/SwipePivotTabBar-BGO9X94m.js.map +1 -0
  27. package/dist/SwipePivotTabBar-BrQismcZ.cjs +2 -0
  28. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +1 -0
  29. package/dist/config.cjs +1 -1
  30. package/dist/config.cjs.map +1 -1
  31. package/dist/config.js +11 -9
  32. package/dist/config.js.map +1 -1
  33. package/dist/constants/styles.d.ts +18 -4
  34. package/dist/floating.cjs +1 -1
  35. package/dist/floating.js +1 -1
  36. package/dist/grid/index.d.ts +58 -0
  37. package/dist/grid.cjs +2 -0
  38. package/dist/grid.cjs.map +1 -0
  39. package/dist/grid.js +13 -0
  40. package/dist/grid.js.map +1 -0
  41. package/dist/hooks/gesture/presets.d.ts +33 -0
  42. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +110 -0
  43. package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
  44. package/dist/hooks/gesture/types.d.ts +254 -0
  45. package/dist/hooks/gesture/useDirectionalLock.d.ts +20 -0
  46. package/dist/hooks/gesture/useEdgeSwipeInput.d.ts +23 -0
  47. package/dist/hooks/gesture/useNativeGestureGuard.d.ts +23 -0
  48. package/dist/hooks/gesture/usePointerTracking.d.ts +22 -0
  49. package/dist/hooks/gesture/useScrollBoundary.d.ts +23 -0
  50. package/dist/hooks/gesture/useSwipeInput.d.ts +5 -0
  51. package/dist/hooks/gesture/utils.d.ts +40 -0
  52. package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
  53. package/dist/hooks/useAnimationFrame.d.ts +84 -0
  54. package/dist/hooks/useSnapAnimation.d.ts +54 -0
  55. package/dist/hooks/useSwipeContentTransform.d.ts +79 -0
  56. package/dist/index.cjs +1 -2
  57. package/dist/index.cjs.map +1 -1
  58. package/dist/index.d.ts +0 -1
  59. package/dist/index.js +25 -2006
  60. package/dist/index.js.map +1 -1
  61. package/dist/modules/pivot/PivotContent.d.ts +1 -1
  62. package/dist/modules/pivot/SwipePivotContent.d.ts +39 -0
  63. package/dist/modules/pivot/SwipePivotContent.debug.tmp.d.ts +25 -0
  64. package/dist/modules/pivot/SwipePivotContent.test.d.ts +1 -0
  65. package/dist/modules/pivot/SwipePivotTabBar.d.ts +89 -0
  66. package/dist/modules/pivot/index.d.ts +3 -0
  67. package/dist/modules/pivot/scaleInputState.d.ts +37 -0
  68. package/dist/modules/pivot/types.d.ts +73 -2
  69. package/dist/modules/pivot/usePivotSwipeInput.d.ts +68 -0
  70. package/dist/modules/stack/StackContent.d.ts +15 -0
  71. package/dist/modules/stack/SwipeStackContent.d.ts +63 -0
  72. package/dist/modules/stack/SwipeStackOutlet.d.ts +80 -0
  73. package/dist/modules/stack/computeStackContentState.d.ts +99 -0
  74. package/dist/modules/stack/computeSwipeStackTransform.d.ts +76 -0
  75. package/dist/modules/stack/types.d.ts +194 -0
  76. package/dist/modules/stack/useStackAnimationState.d.ts +32 -0
  77. package/dist/modules/stack/useStackNavigation.d.ts +23 -0
  78. package/dist/modules/stack/useStackSwipeInput.d.ts +27 -0
  79. package/dist/panels/index.d.ts +67 -0
  80. package/dist/panels.cjs +2 -0
  81. package/dist/panels.cjs.map +1 -0
  82. package/dist/panels.js +28 -0
  83. package/dist/panels.js.map +1 -0
  84. package/dist/pivot/index.d.ts +3 -0
  85. package/dist/pivot.cjs +1 -1
  86. package/dist/pivot.cjs.map +1 -1
  87. package/dist/pivot.js +20 -2
  88. package/dist/pivot.js.map +1 -1
  89. package/dist/resizer/index.d.ts +57 -0
  90. package/dist/resizer.cjs +2 -0
  91. package/dist/resizer.cjs.map +1 -0
  92. package/dist/resizer.js +8 -0
  93. package/dist/resizer.js.map +1 -0
  94. package/dist/stack/index.d.ts +72 -0
  95. package/dist/stack.cjs +2 -0
  96. package/dist/stack.cjs.map +1 -0
  97. package/dist/stack.js +980 -0
  98. package/dist/stack.js.map +1 -0
  99. package/dist/sticky-header/StickyArea.d.ts +38 -0
  100. package/dist/sticky-header/index.d.ts +4 -4
  101. package/dist/sticky-header/types.d.ts +35 -22
  102. package/dist/sticky-header.cjs +1 -1
  103. package/dist/sticky-header.cjs.map +1 -1
  104. package/dist/sticky-header.js +65 -174
  105. package/dist/sticky-header.js.map +1 -1
  106. package/dist/styles-DPPuJ0sf.js +57 -0
  107. package/dist/styles-DPPuJ0sf.js.map +1 -0
  108. package/dist/styles-qf6ptVLD.cjs +2 -0
  109. package/dist/styles-qf6ptVLD.cjs.map +1 -0
  110. package/dist/types.d.ts +12 -0
  111. package/dist/useContentCache-CO3LYNmz.js +24 -0
  112. package/dist/useContentCache-CO3LYNmz.js.map +1 -0
  113. package/dist/useContentCache-DqXtLrLs.cjs +2 -0
  114. package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
  115. package/dist/useDocumentPointerEvents-CKdhGXd0.js +46 -0
  116. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +1 -0
  117. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +2 -0
  118. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +1 -0
  119. package/dist/useEffectEvent-Dp7HLCf0.js +13 -0
  120. package/dist/useEffectEvent-Dp7HLCf0.js.map +1 -0
  121. package/dist/useEffectEvent-huSsGUnl.cjs +2 -0
  122. package/dist/useEffectEvent-huSsGUnl.cjs.map +1 -0
  123. package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
  124. package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
  125. package/dist/useFloatingState-tEfA_wbc.js +74 -0
  126. package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
  127. package/dist/window/index.d.ts +61 -0
  128. package/dist/window.cjs +2 -0
  129. package/dist/window.cjs.map +1 -0
  130. package/dist/window.js +149 -0
  131. package/dist/window.js.map +1 -0
  132. package/docs/design-tokens.md +405 -0
  133. package/package.json +29 -4
  134. package/src/PanelSystemContext.tsx +88 -0
  135. package/src/components/grid/GridLayerList.tsx +172 -0
  136. package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
  137. package/src/components/grid/GridLayout.spec.tsx +743 -0
  138. package/src/components/grid/GridLayout.tsx +130 -0
  139. package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
  140. package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
  141. package/src/components/panels/DropSuggestOverlay.tsx +131 -0
  142. package/src/components/panels/PanelGroupView.tsx +112 -0
  143. package/src/components/pivot/PivotLayer.tsx +27 -0
  144. package/src/components/resizer/HorizontalDivider.tsx +52 -0
  145. package/src/components/resizer/ResizeHandle.tsx +118 -0
  146. package/src/components/tabs/TabBar.tsx +223 -0
  147. package/src/components/tabs/TabBarTab.tsx +133 -0
  148. package/src/components/tabs/TabDragOverlay.tsx +92 -0
  149. package/src/components/window/DialogOverlay.tsx +180 -0
  150. package/src/components/window/Drawer.tsx +282 -0
  151. package/src/components/window/DrawerLayers.tsx +58 -0
  152. package/src/components/window/FloatingWindow.tsx +95 -0
  153. package/src/components/window/PopupLayerPortal.tsx +218 -0
  154. package/src/config/PanelContentDeclaration.tsx +427 -0
  155. package/src/config/index.tsx +52 -0
  156. package/src/config/panelJsx.spec.tsx +54 -0
  157. package/src/config/panelJsxConfig.spec.tsx +54 -0
  158. package/src/config/panelJsxDrawer.spec.tsx +33 -0
  159. package/src/config/panelRouter.spec.ts +68 -0
  160. package/src/config/panelRouter.tsx +155 -0
  161. package/src/constants/styles.ts +261 -0
  162. package/src/demo/Layout.module.css +258 -0
  163. package/src/demo/Layout.tsx +176 -0
  164. package/src/demo/components/CodeBlock.module.css +54 -0
  165. package/src/demo/components/CodeBlock.tsx +34 -0
  166. package/src/demo/components/CodePreview.module.css +37 -0
  167. package/src/demo/components/CodePreview.tsx +31 -0
  168. package/src/demo/components/DataPreview.module.css +177 -0
  169. package/src/demo/components/DataPreview.tsx +115 -0
  170. package/src/demo/components/Story.module.css +68 -0
  171. package/src/demo/components/Story.tsx +54 -0
  172. package/src/demo/components/layout/CodePanel.module.css +183 -0
  173. package/src/demo/components/layout/CodePanel.tsx +149 -0
  174. package/src/demo/components/layout/DemoPage.module.css +60 -0
  175. package/src/demo/components/layout/DemoPage.tsx +56 -0
  176. package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
  177. package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
  178. package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
  179. package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
  180. package/src/demo/components/layout/index.ts +11 -0
  181. package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
  182. package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
  183. package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
  184. package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
  185. package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
  186. package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
  187. package/src/demo/components/tab-styles/index.ts +6 -0
  188. package/src/demo/components/ui/DemoButton.module.css +63 -0
  189. package/src/demo/components/ui/DemoButton.tsx +32 -0
  190. package/src/demo/components/ui/DemoCard.module.css +15 -0
  191. package/src/demo/components/ui/DemoCard.tsx +30 -0
  192. package/src/demo/components/ui/DemoContainer.module.css +17 -0
  193. package/src/demo/components/ui/DemoContainer.tsx +30 -0
  194. package/src/demo/components/ui/DemoPanel.module.css +23 -0
  195. package/src/demo/components/ui/DemoPanel.tsx +33 -0
  196. package/src/demo/components/ui/PanelText.module.css +18 -0
  197. package/src/demo/components/ui/PanelText.tsx +29 -0
  198. package/src/demo/components/ui/PanelTitle.module.css +18 -0
  199. package/src/demo/components/ui/PanelTitle.tsx +31 -0
  200. package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
  201. package/src/demo/demo.css +172 -0
  202. package/src/demo/hooks/useMedia.ts +41 -0
  203. package/src/demo/hooks/useShikiHighlight.ts +55 -0
  204. package/src/demo/index.tsx +293 -0
  205. package/src/demo/pages/Drawer/animations/index.tsx +22 -0
  206. package/src/demo/pages/Drawer/basics/index.tsx +17 -0
  207. package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
  208. package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
  209. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
  210. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
  211. package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
  212. package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
  213. package/src/demo/pages/Drawer/menu/index.tsx +17 -0
  214. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
  215. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
  216. package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
  217. package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
  218. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
  219. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
  220. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
  221. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
  222. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
  223. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
  224. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
  225. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
  226. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
  227. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
  228. package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
  229. package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
  230. package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
  231. package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
  232. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
  233. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
  234. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
  235. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
  236. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
  237. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
  238. package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
  239. package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
  240. package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
  241. package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
  242. package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
  243. package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
  244. package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
  245. package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
  246. package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
  247. package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
  248. package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
  249. package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
  250. package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
  251. package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
  252. package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
  253. package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
  254. package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
  255. package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
  256. package/src/demo/pages/PanelLayout/index.tsx +94 -0
  257. package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
  258. package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
  259. package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
  260. package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
  261. package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
  262. package/src/demo/pages/Pivot/basics/index.tsx +17 -0
  263. package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
  264. package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
  265. package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
  266. package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
  267. package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
  268. package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
  269. package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
  270. package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
  271. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +289 -0
  272. package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
  273. package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
  274. package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
  275. package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
  276. package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
  277. package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
  278. package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
  279. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
  280. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
  281. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
  282. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
  283. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
  284. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
  285. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
  286. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
  287. package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
  288. package/src/demo/pages/ResizeHandle/index.module.css +11 -0
  289. package/src/demo/pages/ResizeHandle/index.tsx +71 -0
  290. package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
  291. package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
  292. package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
  293. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
  294. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
  295. package/src/demo/pages/Stack/basics/index.tsx +22 -0
  296. package/src/demo/pages/Stack/components/Stack.module.css +234 -0
  297. package/src/demo/pages/Stack/components/StackBasics.tsx +217 -0
  298. package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
  299. package/src/demo/pages/Stack/components/StackTablet.tsx +401 -0
  300. package/src/demo/pages/Stack/tablet/index.tsx +22 -0
  301. package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
  302. package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
  303. package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
  304. package/src/demo/routes.tsx +193 -0
  305. package/src/demo/styles/animations.css +68 -0
  306. package/src/demo/styles/stack-themes.css +35 -0
  307. package/src/demo/utils/createPanelView.tsx +58 -0
  308. package/src/floating/index.ts +24 -0
  309. package/src/grid/index.ts +75 -0
  310. package/src/hooks/ContentCacheContext.tsx +87 -0
  311. package/src/hooks/gesture/presets.spec.ts +86 -0
  312. package/src/hooks/gesture/presets.ts +95 -0
  313. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +237 -0
  314. package/src/hooks/gesture/testing/createGestureSimulator.ts +310 -0
  315. package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
  316. package/src/hooks/gesture/thresholdValue.ts +77 -0
  317. package/src/hooks/gesture/types.ts +290 -0
  318. package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
  319. package/src/hooks/gesture/useDirectionalLock.ts +115 -0
  320. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +454 -0
  321. package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
  322. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +413 -0
  323. package/src/hooks/gesture/useNativeGestureGuard.ts +133 -0
  324. package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
  325. package/src/hooks/gesture/usePointerTracking.ts +134 -0
  326. package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
  327. package/src/hooks/gesture/useScrollBoundary.ts +113 -0
  328. package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
  329. package/src/hooks/gesture/useSwipeInput.ts +310 -0
  330. package/src/hooks/gesture/utils.spec.ts +152 -0
  331. package/src/hooks/gesture/utils.ts +87 -0
  332. package/src/hooks/useAnimatedVisibility.spec.ts +257 -0
  333. package/src/hooks/useAnimatedVisibility.ts +146 -0
  334. package/src/hooks/useAnimationFrame.ts +200 -0
  335. package/src/hooks/useCSSMatrix.spec.ts +214 -0
  336. package/src/hooks/useCSSMatrix.ts +262 -0
  337. package/src/hooks/useClonedElementPreview.ts +28 -0
  338. package/src/hooks/useContainerScroll.ts +78 -0
  339. package/src/hooks/useContentCache.spec.tsx +232 -0
  340. package/src/hooks/useContentCache.tsx +127 -0
  341. package/src/hooks/useDocumentPointerEvents.ts +137 -0
  342. package/src/hooks/useDocumentScroll.ts +41 -0
  343. package/src/hooks/useEffectEvent.ts +40 -0
  344. package/src/hooks/useElementComponentWrapper.tsx +63 -0
  345. package/src/hooks/useIntersectionObserver.tsx +125 -0
  346. package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
  347. package/src/hooks/useResizeObserver.tsx +81 -0
  348. package/src/hooks/useScrollContainer.ts +79 -0
  349. package/src/hooks/useSnapAnimation.ts +128 -0
  350. package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
  351. package/src/hooks/useSwipeContentTransform.ts +235 -0
  352. package/src/hooks/useTransitionState.ts +95 -0
  353. package/src/index.tsx +88 -0
  354. package/src/modules/grid/GridLayoutContext.tsx +57 -0
  355. package/src/modules/grid/LayerInstanceContext.tsx +56 -0
  356. package/src/modules/grid/resizeHandles.ts +157 -0
  357. package/src/modules/grid/trackUtils.ts +146 -0
  358. package/src/modules/grid/useGridPlacements.ts +143 -0
  359. package/src/modules/grid/useGridTracks.ts +156 -0
  360. package/src/modules/grid/useLayerDragHandle.ts +16 -0
  361. package/src/modules/grid/useLayerInteractions.tsx +850 -0
  362. package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
  363. package/src/modules/panels/dom/DomRegistry.tsx +94 -0
  364. package/src/modules/panels/index.ts +45 -0
  365. package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
  366. package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
  367. package/src/modules/panels/interactions/dnd.ts +28 -0
  368. package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
  369. package/src/modules/panels/layout/adapter.ts +124 -0
  370. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +304 -0
  371. package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
  372. package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
  373. package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
  374. package/src/modules/panels/rendering/RenderContext.tsx +31 -0
  375. package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
  376. package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
  377. package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
  378. package/src/modules/panels/state/StateContext.tsx +12 -0
  379. package/src/modules/panels/state/cleanup.ts +37 -0
  380. package/src/modules/panels/state/commands.ts +53 -0
  381. package/src/modules/panels/state/focus/Context.tsx +25 -0
  382. package/src/modules/panels/state/focus/logic.ts +57 -0
  383. package/src/modules/panels/state/groups/Context.tsx +25 -0
  384. package/src/modules/panels/state/groups/logic.ts +105 -0
  385. package/src/modules/panels/state/splitLimits.spec.ts +46 -0
  386. package/src/modules/panels/state/splitLimits.ts +90 -0
  387. package/src/modules/panels/state/state.spec.ts +49 -0
  388. package/src/modules/panels/state/tree/Context.tsx +24 -0
  389. package/src/modules/panels/state/tree/logic.spec.ts +34 -0
  390. package/src/modules/panels/state/tree/logic.ts +138 -0
  391. package/src/modules/panels/state/types.ts +142 -0
  392. package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
  393. package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
  394. package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
  395. package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
  396. package/src/modules/panels/system/PanelSystem.tsx +206 -0
  397. package/src/modules/pivot/PivotContent.spec.tsx +179 -0
  398. package/src/modules/pivot/PivotContent.tsx +77 -0
  399. package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
  400. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +167 -0
  401. package/src/modules/pivot/SwipePivotContent.spec.tsx +464 -0
  402. package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
  403. package/src/modules/pivot/SwipePivotContent.tsx +197 -0
  404. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +865 -0
  405. package/src/modules/pivot/SwipePivotTabBar.tsx +523 -0
  406. package/src/modules/pivot/index.ts +8 -0
  407. package/src/modules/pivot/scaleInputState.spec.ts +210 -0
  408. package/src/modules/pivot/scaleInputState.ts +66 -0
  409. package/src/modules/pivot/types.ts +139 -0
  410. package/src/modules/pivot/usePivot.spec.ts +621 -0
  411. package/src/modules/pivot/usePivot.spec.tsx +186 -0
  412. package/src/modules/pivot/usePivot.tsx +345 -0
  413. package/src/modules/pivot/usePivotSwipeInput.spec.ts +649 -0
  414. package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
  415. package/src/modules/resizer/useResizeDrag.ts +94 -0
  416. package/src/modules/stack/StackContent.spec.tsx +264 -0
  417. package/src/modules/stack/StackContent.tsx +111 -0
  418. package/src/modules/stack/SwipeStackContent.spec.tsx +1277 -0
  419. package/src/modules/stack/SwipeStackContent.tsx +356 -0
  420. package/src/modules/stack/SwipeStackOutlet.spec.tsx +252 -0
  421. package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
  422. package/src/modules/stack/computeStackContentState.spec.ts +281 -0
  423. package/src/modules/stack/computeStackContentState.ts +304 -0
  424. package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
  425. package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
  426. package/src/modules/stack/types.ts +226 -0
  427. package/src/modules/stack/useStackAnimationState.spec.ts +186 -0
  428. package/src/modules/stack/useStackAnimationState.ts +138 -0
  429. package/src/modules/stack/useStackNavigation.spec.ts +477 -0
  430. package/src/modules/stack/useStackNavigation.tsx +336 -0
  431. package/src/modules/stack/useStackSwipeInput.spec.ts +276 -0
  432. package/src/modules/stack/useStackSwipeInput.ts +139 -0
  433. package/src/modules/window/useDrawerState.ts +81 -0
  434. package/src/modules/window/useFloatingState.spec.ts +252 -0
  435. package/src/modules/window/useFloatingState.ts +141 -0
  436. package/src/panels/index.ts +119 -0
  437. package/src/pivot/index.ts +19 -0
  438. package/src/resizer/index.ts +68 -0
  439. package/src/stack/index.ts +91 -0
  440. package/src/sticky-header/StickyArea.tsx +221 -0
  441. package/src/sticky-header/index.ts +18 -0
  442. package/src/sticky-header/types.ts +68 -0
  443. package/src/types.ts +323 -0
  444. package/src/utils/CSSMatrix.ts +321 -0
  445. package/src/utils/css.ts +65 -0
  446. package/src/utils/dialogUtils.ts +43 -0
  447. package/src/utils/math.ts +18 -0
  448. package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
  449. package/src/utils/typedActions.ts +103 -0
  450. package/src/vite-env.d.ts +6 -0
  451. package/src/window/index.ts +67 -0
  452. package/dist/FloatingPanelFrame-6W5OexYe.js.map +0 -1
  453. package/dist/FloatingPanelFrame-D9Cp2al1.cjs.map +0 -1
  454. package/dist/GridLayout-BzrIDrC9.js +0 -1465
  455. package/dist/GridLayout-BzrIDrC9.js.map +0 -1
  456. package/dist/GridLayout-ZrOhoLLB.cjs +0 -2
  457. package/dist/GridLayout-ZrOhoLLB.cjs.map +0 -1
  458. package/dist/sticky-header/StickyHeader.d.ts +0 -53
  459. package/dist/styles-CA2_zLZt.js +0 -52
  460. package/dist/styles-CA2_zLZt.js.map +0 -1
  461. package/dist/styles-PsqGOEJP.cjs +0 -2
  462. package/dist/styles-PsqGOEJP.cjs.map +0 -1
  463. package/dist/usePivot-BS-DGfwd.cjs +0 -2
  464. package/dist/usePivot-BS-DGfwd.cjs.map +0 -1
  465. package/dist/usePivot-BvOGxLQQ.js +0 -124
  466. package/dist/usePivot-BvOGxLQQ.js.map +0 -1
@@ -0,0 +1,865 @@
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 { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
11
+ import { render, screen, act } from "@testing-library/react";
12
+ import * as React from "react";
13
+ import { SwipePivotTabBar } from "./SwipePivotTabBar";
14
+ import type { IndicatorRenderProps } from "./SwipePivotTabBar";
15
+ import type { SwipeInputState } from "../../hooks/gesture/types";
16
+
17
+ // Mock requestAnimationFrame for animation testing
18
+ let rafCallbacks: FrameRequestCallback[] = [];
19
+ let rafId = 0;
20
+
21
+ const mockRAF = vi.fn((callback: FrameRequestCallback) => {
22
+ rafCallbacks.push(callback);
23
+ return ++rafId;
24
+ });
25
+
26
+ const mockCAF = vi.fn((id: number) => {
27
+ // Remove callback if needed
28
+ });
29
+
30
+ const flushRAF = () => {
31
+ const callbacks = rafCallbacks;
32
+ rafCallbacks = [];
33
+ callbacks.forEach(cb => cb(performance.now()));
34
+ };
35
+
36
+ beforeEach(() => {
37
+ rafCallbacks = [];
38
+ rafId = 0;
39
+ vi.stubGlobal("requestAnimationFrame", mockRAF);
40
+ vi.stubGlobal("cancelAnimationFrame", mockCAF);
41
+ });
42
+
43
+ afterEach(() => {
44
+ vi.unstubAllGlobals();
45
+ vi.clearAllMocks();
46
+ });
47
+
48
+ const createItems = () => [
49
+ { id: "tab1", label: "Tab 1" },
50
+ { id: "tab2", label: "Tab 2" },
51
+ { id: "tab3", label: "Tab 3" },
52
+ { id: "tab4", label: "Tab 4" },
53
+ { id: "tab5", label: "Tab 5" },
54
+ ];
55
+
56
+ const idleState: SwipeInputState = {
57
+ phase: "idle",
58
+ displacement: { x: 0, y: 0 },
59
+ velocity: { x: 0, y: 0 },
60
+ direction: 0,
61
+ };
62
+
63
+ const swipingLeftState = (displacement: number): SwipeInputState => ({
64
+ phase: "swiping",
65
+ displacement: { x: displacement, y: 0 },
66
+ velocity: { x: -0.5, y: 0 },
67
+ direction: -1,
68
+ });
69
+
70
+ const swipingRightState = (displacement: number): SwipeInputState => ({
71
+ phase: "swiping",
72
+ displacement: { x: displacement, y: 0 },
73
+ velocity: { x: 0.5, y: 0 },
74
+ direction: 1,
75
+ });
76
+
77
+ const endedState = (direction: -1 | 0 | 1): SwipeInputState => ({
78
+ phase: "ended",
79
+ displacement: { x: 0, y: 0 },
80
+ velocity: { x: 0, y: 0 },
81
+ direction,
82
+ });
83
+
84
+ const defaultProps = {
85
+ items: createItems(),
86
+ tabWidth: 100,
87
+ viewportWidth: 500,
88
+ navigationMode: "loop" as const,
89
+ renderTab: (item: { id: string; label?: string }, isActive: boolean) => (
90
+ <button data-testid={`tab-${item.id}`} data-active={isActive}>
91
+ {item.label}
92
+ </button>
93
+ ),
94
+ };
95
+
96
+ // Helper: centerX for default props
97
+ const centerX = (defaultProps.viewportWidth - defaultProps.tabWidth) / 2; // 200
98
+
99
+ // Helper to get slot element by position
100
+ const getSlot = (container: HTMLElement, slotPosition: number): HTMLElement | null => {
101
+ return container.querySelector(`[data-slot="${slotPosition}"]`);
102
+ };
103
+
104
+ // Helper to get all visible slots
105
+ const getVisibleSlots = (container: HTMLElement): HTMLElement[] => {
106
+ return Array.from(container.querySelectorAll('[data-slot][style*="visibility: visible"]'));
107
+ };
108
+
109
+ describe("SwipePivotTabBar", () => {
110
+ describe("Initial render", () => {
111
+ it("renders tabs at slot positions", () => {
112
+ const { container } = render(
113
+ <SwipePivotTabBar
114
+ {...defaultProps}
115
+ activeId="tab1"
116
+ activeIndex={0}
117
+ itemCount={5}
118
+ inputState={idleState}
119
+ />
120
+ );
121
+
122
+ // Slot 0 should have tab1 (active)
123
+ const slot0 = getSlot(container, 0);
124
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
125
+ expect(slot0).toHaveAttribute("data-active", "true");
126
+
127
+ // Slot 1 should have tab2
128
+ const slot1 = getSlot(container, 1);
129
+ expect(slot1).toHaveAttribute("data-pivot-tab", "tab2");
130
+
131
+ // Slot -1 should have tab5 (loop)
132
+ const slotMinus1 = getSlot(container, -1);
133
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "tab5");
134
+ });
135
+
136
+ it("centers active tab in viewport", () => {
137
+ const { container } = render(
138
+ <SwipePivotTabBar
139
+ {...defaultProps}
140
+ activeId="tab3"
141
+ activeIndex={2}
142
+ itemCount={5}
143
+ inputState={idleState}
144
+ />
145
+ );
146
+
147
+ // Slot 0 always has active tab, at center
148
+ const slot0 = getSlot(container, 0);
149
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab3");
150
+ expect(slot0).toHaveStyle({ left: `${centerX}px` });
151
+ expect(slot0).toHaveStyle({ transform: "translateX(0px)" });
152
+ });
153
+ });
154
+
155
+ describe("Pattern 1: Swipe to adjacent tab (threshold exceeded)", () => {
156
+ it("follows finger during swipe", () => {
157
+ const { container, rerender } = render(
158
+ <SwipePivotTabBar
159
+ {...defaultProps}
160
+ activeId="tab1"
161
+ activeIndex={0}
162
+ itemCount={5}
163
+ inputState={idleState}
164
+ />
165
+ );
166
+
167
+ // Start swiping left
168
+ rerender(
169
+ <SwipePivotTabBar
170
+ {...defaultProps}
171
+ activeId="tab1"
172
+ activeIndex={0}
173
+ itemCount={5}
174
+ inputState={swipingLeftState(-50)}
175
+ />
176
+ );
177
+
178
+ // All slots should be offset by displacement
179
+ const slot0 = getSlot(container, 0);
180
+ expect(slot0).toHaveStyle({ transform: "translateX(-50px)" });
181
+
182
+ const slot1 = getSlot(container, 1);
183
+ expect(slot1).toHaveStyle({ transform: "translateX(50px)" }); // 100 + (-50)
184
+ });
185
+
186
+ it("animates to new position after swipe ends", () => {
187
+ const { container, rerender } = render(
188
+ <SwipePivotTabBar
189
+ {...defaultProps}
190
+ activeId="tab1"
191
+ activeIndex={0}
192
+ itemCount={5}
193
+ inputState={swipingLeftState(-150)}
194
+ />
195
+ );
196
+
197
+ // Swipe ends, activeIndex changes to 1
198
+ rerender(
199
+ <SwipePivotTabBar
200
+ {...defaultProps}
201
+ activeId="tab2"
202
+ activeIndex={1}
203
+ itemCount={5}
204
+ inputState={endedState(-1)}
205
+ />
206
+ );
207
+
208
+ // Slot 0 should now have tab2 (new active)
209
+ const slot0 = getSlot(container, 0);
210
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab2");
211
+ expect(slot0).toHaveAttribute("data-active", "true");
212
+ });
213
+ });
214
+
215
+ describe("Pattern 2: Swipe cancel (threshold not met)", () => {
216
+ it("returns to original position when swipe is cancelled", () => {
217
+ const { container, rerender } = render(
218
+ <SwipePivotTabBar
219
+ {...defaultProps}
220
+ activeId="tab1"
221
+ activeIndex={0}
222
+ itemCount={5}
223
+ inputState={swipingLeftState(-30)}
224
+ />
225
+ );
226
+
227
+ // Swipe ends without threshold, activeIndex stays the same
228
+ rerender(
229
+ <SwipePivotTabBar
230
+ {...defaultProps}
231
+ activeId="tab1"
232
+ activeIndex={0}
233
+ itemCount={5}
234
+ inputState={idleState}
235
+ />
236
+ );
237
+
238
+ // Slot 0 should still have tab1
239
+ const slot0 = getSlot(container, 0);
240
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
241
+ });
242
+ });
243
+
244
+ describe("Pattern 3: Click adjacent tab", () => {
245
+ it("animates when adjacent tab is clicked", () => {
246
+ const { container, rerender } = render(
247
+ <SwipePivotTabBar
248
+ {...defaultProps}
249
+ activeId="tab1"
250
+ activeIndex={0}
251
+ itemCount={5}
252
+ inputState={idleState}
253
+ />
254
+ );
255
+
256
+ // Tab2 is clicked, activeIndex changes to 1
257
+ rerender(
258
+ <SwipePivotTabBar
259
+ {...defaultProps}
260
+ activeId="tab2"
261
+ activeIndex={1}
262
+ itemCount={5}
263
+ inputState={idleState}
264
+ />
265
+ );
266
+
267
+ // Slot 0 should now have tab2
268
+ const slot0 = getSlot(container, 0);
269
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab2");
270
+ });
271
+ });
272
+
273
+ describe("Pattern 4: Click non-adjacent tab", () => {
274
+ it("animates when distant tab is clicked (Tab 1 → Tab 5)", () => {
275
+ const { container, rerender } = render(
276
+ <SwipePivotTabBar
277
+ {...defaultProps}
278
+ activeId="tab1"
279
+ activeIndex={0}
280
+ itemCount={5}
281
+ inputState={idleState}
282
+ />
283
+ );
284
+
285
+ // Tab5 is clicked (which is at slot -1 in loop mode from tab1)
286
+ rerender(
287
+ <SwipePivotTabBar
288
+ {...defaultProps}
289
+ activeId="tab5"
290
+ activeIndex={4}
291
+ itemCount={5}
292
+ inputState={idleState}
293
+ />
294
+ );
295
+
296
+ // Slot 0 should now have tab5
297
+ const slot0 = getSlot(container, 0);
298
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab5");
299
+ });
300
+
301
+ it("calculates shortest path in loop mode (Tab 1 → Tab 5 = -1 step)", () => {
302
+ const { container, rerender } = render(
303
+ <SwipePivotTabBar
304
+ {...defaultProps}
305
+ activeId="tab1"
306
+ activeIndex={0}
307
+ itemCount={5}
308
+ inputState={idleState}
309
+ />
310
+ );
311
+
312
+ // In loop mode, Tab5 is at slot -1 relative to Tab1
313
+ // So going Tab1 → Tab5 should animate backward (shortest path)
314
+ rerender(
315
+ <SwipePivotTabBar
316
+ {...defaultProps}
317
+ activeId="tab5"
318
+ activeIndex={4}
319
+ itemCount={5}
320
+ inputState={idleState}
321
+ />
322
+ );
323
+
324
+ // After transition, slot configuration should be:
325
+ // Slot 0: tab5 (active)
326
+ // Slot 1: tab1
327
+ // Slot -1: tab4
328
+ const slot0 = getSlot(container, 0);
329
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab5");
330
+
331
+ const slot1 = getSlot(container, 1);
332
+ expect(slot1).toHaveAttribute("data-pivot-tab", "tab1");
333
+
334
+ const slotMinus1 = getSlot(container, -1);
335
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "tab4");
336
+ });
337
+ });
338
+
339
+ describe("Pattern 5: Previous/Next button click", () => {
340
+ it("animates forward when Next button is clicked", () => {
341
+ const { container, rerender } = render(
342
+ <SwipePivotTabBar
343
+ {...defaultProps}
344
+ activeId="tab2"
345
+ activeIndex={1}
346
+ itemCount={5}
347
+ inputState={idleState}
348
+ />
349
+ );
350
+
351
+ // Next button clicked, goes to tab3
352
+ rerender(
353
+ <SwipePivotTabBar
354
+ {...defaultProps}
355
+ activeId="tab3"
356
+ activeIndex={2}
357
+ itemCount={5}
358
+ inputState={idleState}
359
+ />
360
+ );
361
+
362
+ const slot0 = getSlot(container, 0);
363
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab3");
364
+ });
365
+
366
+ it("animates backward when Previous button is clicked", () => {
367
+ const { container, rerender } = render(
368
+ <SwipePivotTabBar
369
+ {...defaultProps}
370
+ activeId="tab3"
371
+ activeIndex={2}
372
+ itemCount={5}
373
+ inputState={idleState}
374
+ />
375
+ );
376
+
377
+ // Previous button clicked, goes to tab2
378
+ rerender(
379
+ <SwipePivotTabBar
380
+ {...defaultProps}
381
+ activeId="tab2"
382
+ activeIndex={1}
383
+ itemCount={5}
384
+ inputState={idleState}
385
+ />
386
+ );
387
+
388
+ const slot0 = getSlot(container, 0);
389
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab2");
390
+ });
391
+ });
392
+
393
+ describe("Pattern 6: Loop boundary swipe (Tab 5 → Tab 1)", () => {
394
+ it("swipes seamlessly from Tab 5 to Tab 1", () => {
395
+ const { container, rerender } = render(
396
+ <SwipePivotTabBar
397
+ {...defaultProps}
398
+ activeId="tab5"
399
+ activeIndex={4}
400
+ itemCount={5}
401
+ inputState={idleState}
402
+ />
403
+ );
404
+
405
+ // At Tab5, slot 1 should have Tab1 (loop)
406
+ const slot1Before = getSlot(container, 1);
407
+ expect(slot1Before).toHaveAttribute("data-pivot-tab", "tab1");
408
+
409
+ // Swipe left to go to Tab1
410
+ rerender(
411
+ <SwipePivotTabBar
412
+ {...defaultProps}
413
+ activeId="tab5"
414
+ activeIndex={4}
415
+ itemCount={5}
416
+ inputState={swipingLeftState(-100)}
417
+ />
418
+ );
419
+
420
+ // After swipe completes, Tab1 becomes active
421
+ rerender(
422
+ <SwipePivotTabBar
423
+ {...defaultProps}
424
+ activeId="tab1"
425
+ activeIndex={0}
426
+ itemCount={5}
427
+ inputState={endedState(-1)}
428
+ />
429
+ );
430
+
431
+ const slot0 = getSlot(container, 0);
432
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
433
+ });
434
+ });
435
+
436
+ describe("Pattern 7: Loop boundary click (Tab 5 → Tab 1)", () => {
437
+ it("clicking Tab 1 from Tab 5 animates correctly in loop mode", () => {
438
+ const { container, rerender } = render(
439
+ <SwipePivotTabBar
440
+ {...defaultProps}
441
+ activeId="tab5"
442
+ activeIndex={4}
443
+ itemCount={5}
444
+ inputState={idleState}
445
+ />
446
+ );
447
+
448
+ // Click Tab1
449
+ rerender(
450
+ <SwipePivotTabBar
451
+ {...defaultProps}
452
+ activeId="tab1"
453
+ activeIndex={0}
454
+ itemCount={5}
455
+ inputState={idleState}
456
+ />
457
+ );
458
+
459
+ // Tab1 should now be at slot 0
460
+ const slot0 = getSlot(container, 0);
461
+ expect(slot0).toHaveAttribute("data-pivot-tab", "tab1");
462
+
463
+ // Tab5 should be at slot -1
464
+ const slotMinus1 = getSlot(container, -1);
465
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "tab5");
466
+ });
467
+ });
468
+
469
+ describe("Pattern 8: Linear mode", () => {
470
+ it("does not wrap in linear mode", () => {
471
+ const { container } = render(
472
+ <SwipePivotTabBar
473
+ {...defaultProps}
474
+ navigationMode="linear"
475
+ activeId="tab1"
476
+ activeIndex={0}
477
+ itemCount={5}
478
+ inputState={idleState}
479
+ />
480
+ );
481
+
482
+ // In linear mode, slot -1 should be empty (no tab before tab1)
483
+ const slotMinus1 = getSlot(container, -1);
484
+ expect(slotMinus1).toBeNull();
485
+
486
+ // Slot 4 should have tab5
487
+ const slot4 = getSlot(container, 4);
488
+ expect(slot4).toHaveAttribute("data-pivot-tab", "tab5");
489
+ });
490
+ });
491
+
492
+ describe("Visibility", () => {
493
+ it("hides tabs outside viewport", () => {
494
+ const { container } = render(
495
+ <SwipePivotTabBar
496
+ {...defaultProps}
497
+ activeId="tab3"
498
+ activeIndex={2}
499
+ itemCount={5}
500
+ inputState={idleState}
501
+ />
502
+ );
503
+
504
+ // Slot 0 (center) should be visible
505
+ const slot0 = getSlot(container, 0);
506
+ expect(slot0).toHaveStyle({ visibility: "visible" });
507
+
508
+ // Distant slots should be hidden
509
+ const slot4 = getSlot(container, 4);
510
+ if (slot4) {
511
+ expect(slot4).toHaveStyle({ visibility: "hidden" });
512
+ }
513
+ });
514
+
515
+ it("shows tabs that enter viewport during swipe", () => {
516
+ const { container, rerender } = render(
517
+ <SwipePivotTabBar
518
+ {...defaultProps}
519
+ activeId="tab1"
520
+ activeIndex={0}
521
+ itemCount={5}
522
+ inputState={idleState}
523
+ />
524
+ );
525
+
526
+ // Swipe to reveal more tabs
527
+ rerender(
528
+ <SwipePivotTabBar
529
+ {...defaultProps}
530
+ activeId="tab1"
531
+ activeIndex={0}
532
+ itemCount={5}
533
+ inputState={swipingLeftState(-100)}
534
+ />
535
+ );
536
+
537
+ // Slot 1 should be visible (tab2)
538
+ const slot1 = getSlot(container, 1);
539
+ expect(slot1).toHaveStyle({ visibility: "visible" });
540
+ });
541
+ });
542
+
543
+ describe("Clone handling for small item counts", () => {
544
+ it("handles 2 items in loop mode", () => {
545
+ const twoItems = [
546
+ { id: "a", label: "A" },
547
+ { id: "b", label: "B" },
548
+ ];
549
+
550
+ const { container } = render(
551
+ <SwipePivotTabBar
552
+ {...defaultProps}
553
+ items={twoItems}
554
+ activeId="a"
555
+ activeIndex={0}
556
+ itemCount={2}
557
+ inputState={idleState}
558
+ />
559
+ );
560
+
561
+ // Slot 0: a
562
+ const slot0 = getSlot(container, 0);
563
+ expect(slot0).toHaveAttribute("data-pivot-tab", "a");
564
+
565
+ // Slot 1: b
566
+ const slot1 = getSlot(container, 1);
567
+ expect(slot1).toHaveAttribute("data-pivot-tab", "b");
568
+
569
+ // Slot -1: b (loop)
570
+ const slotMinus1 = getSlot(container, -1);
571
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "b");
572
+
573
+ // Slot 2: a (clone)
574
+ const slot2 = getSlot(container, 2);
575
+ expect(slot2).toHaveAttribute("data-pivot-tab", "a");
576
+ });
577
+
578
+ it("handles 3 items in loop mode", () => {
579
+ const threeItems = [
580
+ { id: "x", label: "X" },
581
+ { id: "y", label: "Y" },
582
+ { id: "z", label: "Z" },
583
+ ];
584
+
585
+ const { container } = render(
586
+ <SwipePivotTabBar
587
+ {...defaultProps}
588
+ items={threeItems}
589
+ activeId="x"
590
+ activeIndex={0}
591
+ itemCount={3}
592
+ inputState={idleState}
593
+ />
594
+ );
595
+
596
+ // Slot 0: x (active)
597
+ const slot0 = getSlot(container, 0);
598
+ expect(slot0).toHaveAttribute("data-pivot-tab", "x");
599
+
600
+ // Slot 1: y
601
+ const slot1 = getSlot(container, 1);
602
+ expect(slot1).toHaveAttribute("data-pivot-tab", "y");
603
+
604
+ // Slot -1: z
605
+ const slotMinus1 = getSlot(container, -1);
606
+ expect(slotMinus1).toHaveAttribute("data-pivot-tab", "z");
607
+ });
608
+ });
609
+
610
+ describe("Sliding indicator (iOS-style)", () => {
611
+ it("renders indicator with correct offset props", () => {
612
+ const indicatorFn = vi.fn(() => <div data-testid="indicator" />);
613
+
614
+ render(
615
+ <SwipePivotTabBar
616
+ {...defaultProps}
617
+ activeId="tab1"
618
+ activeIndex={0}
619
+ itemCount={5}
620
+ inputState={idleState}
621
+ renderIndicator={indicatorFn}
622
+ />
623
+ );
624
+
625
+ expect(indicatorFn).toHaveBeenCalledWith({
626
+ offsetPx: 0,
627
+ tabWidth: 100,
628
+ centerX: 200,
629
+ isSwiping: false,
630
+ isAnimating: false,
631
+ });
632
+
633
+ expect(screen.getByTestId("indicator")).toBeInTheDocument();
634
+ });
635
+
636
+ it("passes swipe displacement to indicator", () => {
637
+ const indicatorFn = vi.fn((_props: IndicatorRenderProps) => <div data-testid="indicator" />);
638
+
639
+ const { rerender } = render(
640
+ <SwipePivotTabBar
641
+ {...defaultProps}
642
+ activeId="tab1"
643
+ activeIndex={0}
644
+ itemCount={5}
645
+ inputState={idleState}
646
+ renderIndicator={indicatorFn}
647
+ />
648
+ );
649
+
650
+ rerender(
651
+ <SwipePivotTabBar
652
+ {...defaultProps}
653
+ activeId="tab1"
654
+ activeIndex={0}
655
+ itemCount={5}
656
+ inputState={swipingLeftState(-60)}
657
+ renderIndicator={indicatorFn}
658
+ />
659
+ );
660
+
661
+ // Last call should have the swipe offset
662
+ const calls = indicatorFn.mock.calls;
663
+ expect(calls.length).toBeGreaterThan(0);
664
+ const lastCall = calls[calls.length - 1]![0];
665
+ expect(lastCall.offsetPx).toBe(-60);
666
+ expect(lastCall.isSwiping).toBe(true);
667
+ });
668
+
669
+ it("indicator follows same offset as tabs", () => {
670
+ let indicatorOffset = 0;
671
+
672
+ const { container, rerender } = render(
673
+ <SwipePivotTabBar
674
+ {...defaultProps}
675
+ activeId="tab1"
676
+ activeIndex={0}
677
+ itemCount={5}
678
+ inputState={idleState}
679
+ renderIndicator={({ offsetPx }) => {
680
+ indicatorOffset = offsetPx;
681
+ return <div data-testid="indicator" />;
682
+ }}
683
+ />
684
+ );
685
+
686
+ rerender(
687
+ <SwipePivotTabBar
688
+ {...defaultProps}
689
+ activeId="tab1"
690
+ activeIndex={0}
691
+ itemCount={5}
692
+ inputState={swipingLeftState(-80)}
693
+ renderIndicator={({ offsetPx }) => {
694
+ indicatorOffset = offsetPx;
695
+ return <div data-testid="indicator" />;
696
+ }}
697
+ />
698
+ );
699
+
700
+ // Verify indicator offset matches tab offset
701
+ const slot0 = getSlot(container, 0);
702
+ expect(slot0).toHaveStyle({ transform: "translateX(-80px)" });
703
+ expect(indicatorOffset).toBe(-80);
704
+ });
705
+ });
706
+
707
+ describe("Continuous offset model", () => {
708
+ it("all slots move together during swipe", () => {
709
+ const { container, rerender } = render(
710
+ <SwipePivotTabBar
711
+ {...defaultProps}
712
+ activeId="tab1"
713
+ activeIndex={0}
714
+ itemCount={5}
715
+ inputState={idleState}
716
+ />
717
+ );
718
+
719
+ // Initial positions
720
+ const slot0Before = getSlot(container, 0);
721
+ const slot1Before = getSlot(container, 1);
722
+ expect(slot0Before).toHaveStyle({ transform: "translateX(0px)" });
723
+ expect(slot1Before).toHaveStyle({ transform: "translateX(100px)" });
724
+
725
+ // During swipe, all slots offset by same amount
726
+ rerender(
727
+ <SwipePivotTabBar
728
+ {...defaultProps}
729
+ activeId="tab1"
730
+ activeIndex={0}
731
+ itemCount={5}
732
+ inputState={swipingLeftState(-75)}
733
+ />
734
+ );
735
+
736
+ const slot0After = getSlot(container, 0);
737
+ const slot1After = getSlot(container, 1);
738
+ expect(slot0After).toHaveStyle({ transform: "translateX(-75px)" });
739
+ expect(slot1After).toHaveStyle({ transform: "translateX(25px)" }); // 100 - 75
740
+ });
741
+ });
742
+
743
+ describe("Fixed tabs mode (iOS segmented control style)", () => {
744
+ it("tabs stay fixed during swipe, only indicator moves", () => {
745
+ let indicatorOffset = 0;
746
+ let indicatorCenterX = 0;
747
+
748
+ const { container, rerender } = render(
749
+ <SwipePivotTabBar
750
+ {...defaultProps}
751
+ activeId="tab1"
752
+ activeIndex={0}
753
+ itemCount={5}
754
+ fixedTabs={true}
755
+ inputState={idleState}
756
+ renderIndicator={({ offsetPx, centerX }) => {
757
+ indicatorOffset = offsetPx;
758
+ indicatorCenterX = centerX;
759
+ return <div data-testid="indicator" />;
760
+ }}
761
+ />
762
+ );
763
+
764
+ // All tabs should be rendered (not slot-based)
765
+ const tabs = container.querySelectorAll("[data-pivot-tab]");
766
+ expect(tabs.length).toBe(5);
767
+
768
+ // Initial state: indicator at first tab position
769
+ // viewportWidth=500, 5 tabs * 100px = 500px, centeringOffset = (500-500)/2 = 0
770
+ // centerX is fixed at centeringOffset = 0
771
+ // offsetPx = activeIndex * tabWidth = 0 * 100 = 0
772
+ expect(indicatorCenterX).toBe(0);
773
+ expect(indicatorOffset).toBe(0);
774
+
775
+ // During swipe, tabs should NOT have transform (they're fixed)
776
+ rerender(
777
+ <SwipePivotTabBar
778
+ {...defaultProps}
779
+ activeId="tab1"
780
+ activeIndex={0}
781
+ itemCount={5}
782
+ fixedTabs={true}
783
+ inputState={swipingLeftState(-80)}
784
+ renderIndicator={({ offsetPx, centerX }) => {
785
+ indicatorOffset = offsetPx;
786
+ indicatorCenterX = centerX;
787
+ return <div data-testid="indicator" />;
788
+ }}
789
+ />
790
+ );
791
+
792
+ // Indicator should move OPPOSITE to swipe direction
793
+ // Swipe left (displacement = -80) → indicator moves right (+80)
794
+ expect(indicatorOffset).toBe(80);
795
+
796
+ // Tabs should not have any transform applied (position is relative, not absolute with transform)
797
+ const tabsAfterSwipe = container.querySelectorAll("[data-pivot-tab]");
798
+ tabsAfterSwipe.forEach((tab) => {
799
+ const style = window.getComputedStyle(tab as Element);
800
+ expect(style.position).toBe("relative");
801
+ // No translateX in transform (or transform is 'none')
802
+ expect(style.transform).not.toMatch(/translateX/);
803
+ });
804
+ });
805
+
806
+ it("indicator moves to new tab position when activeIndex changes", () => {
807
+ let indicatorOffsetPx = 0;
808
+ let indicatorCenterX = 0;
809
+
810
+ // Mock performance.now to control animation timing
811
+ let mockTime = 0;
812
+ vi.spyOn(performance, "now").mockImplementation(() => mockTime);
813
+
814
+ const { rerender } = render(
815
+ <SwipePivotTabBar
816
+ {...defaultProps}
817
+ activeId="tab1"
818
+ activeIndex={0}
819
+ itemCount={5}
820
+ fixedTabs={true}
821
+ inputState={idleState}
822
+ renderIndicator={({ offsetPx, centerX }) => {
823
+ indicatorOffsetPx = offsetPx;
824
+ indicatorCenterX = centerX;
825
+ return <div data-testid="indicator" />;
826
+ }}
827
+ />
828
+ );
829
+
830
+ // centerX is fixed at centering offset (0 for 5 tabs * 100px = 500px viewport)
831
+ expect(indicatorCenterX).toBe(0);
832
+ // offsetPx includes active tab position
833
+ expect(indicatorOffsetPx).toBe(0); // Tab 1 at position 0
834
+
835
+ rerender(
836
+ <SwipePivotTabBar
837
+ {...defaultProps}
838
+ activeId="tab3"
839
+ activeIndex={2}
840
+ itemCount={5}
841
+ fixedTabs={true}
842
+ inputState={idleState}
843
+ renderIndicator={({ offsetPx, centerX }) => {
844
+ indicatorOffsetPx = offsetPx;
845
+ indicatorCenterX = centerX;
846
+ return <div data-testid="indicator" />;
847
+ }}
848
+ />
849
+ );
850
+
851
+ // Animation starts - flush RAF callbacks until animation completes
852
+ mockTime = 500; // Advance past animation duration (300ms default)
853
+ act(() => {
854
+ for (let i = 0; i < 10; i++) {
855
+ flushRAF();
856
+ }
857
+ });
858
+
859
+ // centerX stays fixed
860
+ expect(indicatorCenterX).toBe(0);
861
+ // offsetPx now includes Tab 3 position (after animation completes)
862
+ expect(indicatorOffsetPx).toBe(200); // Tab 3 at position 2 * 100
863
+ });
864
+ });
865
+ });