react-panel-layout 0.6.0 → 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 (216) hide show
  1. package/dist/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
  2. package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/FloatingWindow-CUXnEtrb.js +827 -0
  4. package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
  5. package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
  6. package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
  7. package/dist/GridLayout-DKTg_N61.cjs +2 -0
  8. package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DKTg_N61.cjs.map} +1 -1
  9. package/dist/{GridLayout-BltqeCPK.js → GridLayout-UWNxXw77.js} +34 -35
  10. package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-UWNxXw77.js.map} +1 -1
  11. package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
  12. package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
  13. package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
  14. package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
  15. package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-BqUzNtf2.js} +5 -5
  16. package/dist/{PanelSystem-Dr1TBhxM.js.map → PanelSystem-BqUzNtf2.js.map} +1 -1
  17. package/dist/{PanelSystem-Bs8bQwQF.cjs → PanelSystem-D603LKKv.cjs} +2 -2
  18. package/dist/{PanelSystem-Bs8bQwQF.cjs.map → PanelSystem-D603LKKv.cjs.map} +1 -1
  19. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  20. package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
  21. package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
  22. package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
  23. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  24. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  25. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  26. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  27. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  28. package/dist/components/window/Drawer.d.ts +3 -1
  29. package/dist/components/window/DrawerLayers.d.ts +1 -1
  30. package/dist/components/window/drawerStyles.d.ts +69 -0
  31. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  32. package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
  33. package/dist/config.cjs +1 -1
  34. package/dist/config.js +3 -3
  35. package/dist/constants/styles.d.ts +17 -0
  36. package/dist/dialog/index.d.ts +69 -0
  37. package/dist/floating.js +1 -1
  38. package/dist/grid.cjs +1 -1
  39. package/dist/grid.js +2 -2
  40. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
  41. package/dist/hooks/gesture/types.d.ts +48 -5
  42. package/dist/hooks/gesture/utils.d.ts +19 -0
  43. package/dist/hooks/useAnimationFrame.d.ts +2 -0
  44. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  45. package/dist/hooks/useResizeObserver.d.ts +33 -1
  46. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  47. package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
  48. package/dist/index.cjs +1 -1
  49. package/dist/index.js +7 -7
  50. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  51. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  52. package/dist/modules/dialog/Modal.d.ts +26 -0
  53. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  54. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  55. package/dist/modules/dialog/types.d.ts +183 -0
  56. package/dist/modules/dialog/useDialog.d.ts +39 -0
  57. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  58. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  59. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  60. package/dist/modules/drawer/types.d.ts +74 -0
  61. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  62. package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
  63. package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
  64. package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
  65. package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
  66. package/dist/panels.cjs +1 -1
  67. package/dist/panels.js +1 -1
  68. package/dist/pivot.cjs +1 -1
  69. package/dist/pivot.js +1 -1
  70. package/dist/resizer.cjs +1 -1
  71. package/dist/resizer.js +2 -2
  72. package/dist/stack.cjs +1 -1
  73. package/dist/stack.cjs.map +1 -1
  74. package/dist/stack.js +503 -762
  75. package/dist/stack.js.map +1 -1
  76. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  77. package/dist/sticky-header.cjs +1 -1
  78. package/dist/sticky-header.cjs.map +1 -1
  79. package/dist/sticky-header.js +59 -51
  80. package/dist/sticky-header.js.map +1 -1
  81. package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
  82. package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
  83. package/dist/styles-qf6ptVLD.cjs.map +1 -1
  84. package/dist/types.d.ts +16 -0
  85. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  86. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  87. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  88. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  89. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
  90. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
  91. package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
  92. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
  93. package/dist/window/index.d.ts +2 -0
  94. package/dist/window.cjs +1 -1
  95. package/dist/window.cjs.map +1 -1
  96. package/dist/window.js +114 -103
  97. package/dist/window.js.map +1 -1
  98. package/package.json +6 -1
  99. package/src/components/gesture/SwipeSafeZone.tsx +69 -0
  100. package/src/components/window/Drawer.tsx +249 -162
  101. package/src/components/window/DrawerLayers.tsx +13 -3
  102. package/src/components/window/drawerStyles.spec.ts +263 -0
  103. package/src/components/window/drawerStyles.ts +228 -0
  104. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  105. package/src/components/window/drawerSwipeConfig.ts +112 -0
  106. package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
  107. package/src/components/window/useDrawerSwipeTransform.ts +129 -0
  108. package/src/constants/styles.ts +19 -0
  109. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  110. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  111. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  112. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  113. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
  114. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  115. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  116. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  117. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  118. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  119. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  120. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  121. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  122. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  123. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  124. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  125. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
  126. package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
  127. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
  128. package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
  129. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
  130. package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
  131. package/src/demo/routes.tsx +22 -1
  132. package/src/dialog/index.ts +85 -0
  133. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
  134. package/src/hooks/gesture/testing/createGestureSimulator.ts +112 -37
  135. package/src/hooks/gesture/types.ts +83 -6
  136. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
  137. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +91 -31
  138. package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
  139. package/src/hooks/gesture/utils.ts +91 -0
  140. package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
  141. package/src/hooks/useAnimatedVisibility.ts +28 -2
  142. package/src/hooks/useAnimationFrame.ts +8 -0
  143. package/src/hooks/useOperationContinuity.spec.ts +387 -0
  144. package/src/hooks/useOperationContinuity.ts +135 -0
  145. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  146. package/src/hooks/useResizeObserver.tsx +108 -39
  147. package/src/hooks/useScrollContainer.ts +4 -10
  148. package/src/hooks/useSharedElementTransition.ts +333 -0
  149. package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
  150. package/src/hooks/useSwipeContentTransform.ts +166 -28
  151. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  152. package/src/modules/dialog/AlertDialog.tsx +221 -0
  153. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  154. package/src/modules/dialog/DialogContainer.tsx +188 -0
  155. package/src/modules/dialog/Modal.spec.tsx +220 -0
  156. package/src/modules/dialog/Modal.tsx +182 -0
  157. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  158. package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
  159. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  160. package/src/modules/dialog/types.ts +186 -0
  161. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  162. package/src/modules/dialog/useDialog.ts +214 -0
  163. package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
  164. package/src/modules/dialog/useDialogContainer.ts +150 -0
  165. package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
  166. package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
  167. package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
  168. package/src/modules/dialog/useDialogTransform.ts +407 -0
  169. package/src/modules/drawer/types.ts +102 -0
  170. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  171. package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
  172. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
  173. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
  174. package/src/modules/pivot/SwipePivotContent.spec.tsx +55 -25
  175. package/src/modules/pivot/SwipePivotContent.tsx +2 -2
  176. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
  177. package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
  178. package/src/modules/pivot/scaleInputState.spec.ts +11 -2
  179. package/src/modules/pivot/usePivot.spec.ts +17 -3
  180. package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
  181. package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
  182. package/src/modules/stack/SwipeStackContent.tsx +43 -33
  183. package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
  184. package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
  185. package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
  186. package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
  187. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  188. package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
  189. package/src/modules/stack/useStackAnimationState.ts +18 -13
  190. package/src/modules/stack/useStackNavigation.spec.ts +198 -3
  191. package/src/modules/stack/useStackNavigation.tsx +113 -56
  192. package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
  193. package/src/modules/stack/useStackSwipeInput.ts +1 -1
  194. package/src/sticky-header/StickyArea.tsx +29 -57
  195. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  196. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  197. package/src/types.ts +18 -0
  198. package/src/window/index.ts +2 -0
  199. package/dist/FloatingWindow-BpdOpg_L.js +0 -400
  200. package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
  201. package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
  202. package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
  203. package/dist/GridLayout-B4VRsC0r.cjs +0 -2
  204. package/dist/ResizeHandle-CScipO5l.cjs +0 -2
  205. package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
  206. package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
  207. package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
  208. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
  209. package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
  210. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
  211. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
  212. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
  213. package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
  214. package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
  215. package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
  216. package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwipePivotTabBar-DWrCuwEI.js","sources":["../src/hooks/useAnimatedVisibility.ts","../src/modules/pivot/PivotContent.tsx","../src/modules/pivot/usePivot.tsx","../src/modules/pivot/SwipePivotTabBar.tsx"],"sourcesContent":["/**\n * @file Hook for animation-aware visibility control.\n *\n * Common pattern for showing/hiding elements with CSS animations:\n * - If animation exists: wait for animationend before hiding\n * - If no animation: hide immediately\n * - Uses display:none for performance (removes from layout)\n * - Includes timeout fallback in case animationend doesn't fire\n */\nimport * as React from \"react\";\n\n/** Default timeout for animation fallback (ms) */\nconst DEFAULT_ANIMATION_TIMEOUT = 1000;\n\ntype AnimatedVisibilityState = {\n /** Whether element should be displayed (display: block/none) */\n shouldDisplay: boolean;\n /** Whether element is currently animating out */\n isAnimatingOut: boolean;\n};\n\ntype UseAnimatedVisibilityOptions = {\n /** Whether the element is logically visible */\n isVisible: boolean;\n /** CSS animation value for leave animation (e.g., CSS variable) */\n leaveAnimation?: string;\n /** Skip animation and hide immediately */\n skipAnimation?: boolean;\n /** Timeout for animation fallback in ms (default: 1000ms) */\n animationTimeout?: number;\n};\n\ntype UseAnimatedVisibilityResult = {\n /** Current visibility state */\n state: AnimatedVisibilityState;\n /** Props to spread on the animated element */\n props: {\n onAnimationEnd: (e: React.AnimationEvent) => void;\n };\n /** Style to apply for display control */\n style: {\n display: \"block\" | \"none\";\n };\n};\n\n/**\n * Hook for animation-aware visibility control.\n *\n * @example\n * const { state, props, style } = useAnimatedVisibility({\n * isVisible: isActive,\n * leaveAnimation: PIVOT_ANIMATION_LEAVE,\n * });\n *\n * return (\n * <div\n * style={{ ...baseStyle, ...style, animation: isActive ? enterAnim : leaveAnim }}\n * {...props}\n * >\n * {children}\n * </div>\n * );\n */\nexport function useAnimatedVisibility({\n isVisible,\n leaveAnimation,\n skipAnimation = false,\n animationTimeout = DEFAULT_ANIMATION_TIMEOUT,\n}: UseAnimatedVisibilityOptions): UseAnimatedVisibilityResult {\n const prevVisibleRef = React.useRef(isVisible);\n const [isAnimatingOut, setIsAnimatingOut] = React.useState(false);\n const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Clear timeout on unmount\n const shouldSkipLeaveAnimation = (\n isSkipped: boolean,\n animation: string | undefined,\n ): boolean => {\n if (isSkipped) {\n return true;\n }\n if (!animation) {\n return true;\n }\n if (animation === \"none\") {\n return true;\n }\n return false;\n };\n\n const getShouldDisplay = (visible: boolean, animatingOut: boolean): boolean => {\n if (visible) {\n return true;\n }\n if (animatingOut) {\n return true;\n }\n return false;\n };\n\n React.useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n React.useEffect(() => {\n const wasVisible = prevVisibleRef.current;\n prevVisibleRef.current = isVisible;\n\n // Clear any pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n if (wasVisible && !isVisible) {\n // Transitioning from visible to hidden\n if (shouldSkipLeaveAnimation(skipAnimation, leaveAnimation)) {\n // No animation, hide immediately\n setIsAnimatingOut(false);\n } else {\n // Has animation, mark as animating out\n setIsAnimatingOut(true);\n\n // Set timeout fallback in case animationend doesn't fire\n // (e.g., CSS variable resolves to \"none\", or animation is very short)\n timeoutRef.current = setTimeout(() => {\n setIsAnimatingOut(false);\n }, animationTimeout);\n }\n } else if (!wasVisible && isVisible) {\n // Transitioning from hidden to visible\n setIsAnimatingOut(false);\n }\n }, [isVisible, leaveAnimation, skipAnimation, animationTimeout]);\n\n const handleAnimationEnd = React.useCallback(\n (e: React.AnimationEvent) => {\n // Only handle animation end for this element (not bubbled from children)\n if (e.target === e.currentTarget && isAnimatingOut) {\n // Clear timeout since animation completed normally\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n setIsAnimatingOut(false);\n }\n },\n [isAnimatingOut],\n );\n\n // Element should be displayed if:\n // - It's visible, OR\n // - It's animating out (leave animation in progress)\n const shouldDisplay = getShouldDisplay(isVisible, isAnimatingOut);\n\n return {\n state: {\n shouldDisplay,\n isAnimatingOut,\n },\n props: {\n onAnimationEnd: handleAnimationEnd,\n },\n style: {\n display: shouldDisplay ? \"block\" : \"none\",\n },\n };\n}\n","/**\n * @file PivotContent component for rendering pivot items with CSS animations.\n *\n * Override via CSS custom properties:\n * - --rpl-pivot-animation-enter: Animation when becoming active\n * - --rpl-pivot-animation-leave: Animation when becoming inactive\n *\n * User defines @keyframes in their CSS and references via these tokens.\n * Example:\n * @keyframes pivotEnter {\n * from { opacity: 0; }\n * to { opacity: 1; }\n * }\n * :root { --rpl-pivot-animation-enter: pivotEnter 150ms ease-out forwards; }\n */\nimport * as React from \"react\";\nimport { PIVOT_ANIMATION_ENTER, PIVOT_ANIMATION_LEAVE } from \"../../constants/styles\";\nimport { useAnimatedVisibility } from \"../../hooks/useAnimatedVisibility\";\n\nexport type PivotContentProps = {\n id: string;\n isActive: boolean;\n transitionMode: \"css\" | \"none\";\n children: React.ReactNode;\n};\n\nconst baseStyle: React.CSSProperties = {\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n};\n\n/**\n * Renders pivot content with CSS animation support.\n *\n * When transitionMode=\"css\": Applies enter/leave animations with display:none when hidden.\n * When transitionMode=\"none\": Uses React.Activity for memory optimization.\n */\nexport const PivotContent: React.FC<PivotContentProps> = React.memo(({ id, isActive, transitionMode, children }) => {\n const visibility = useAnimatedVisibility({\n isVisible: isActive,\n leaveAnimation: transitionMode === \"css\" ? PIVOT_ANIMATION_LEAVE : undefined,\n skipAnimation: transitionMode !== \"css\",\n });\n\n const style = React.useMemo<React.CSSProperties>(() => {\n const s: React.CSSProperties = {\n ...baseStyle,\n ...visibility.style,\n pointerEvents: isActive ? \"auto\" : \"none\",\n };\n\n if (transitionMode === \"css\") {\n s.animation = isActive ? PIVOT_ANIMATION_ENTER : PIVOT_ANIMATION_LEAVE;\n }\n\n return s;\n }, [isActive, transitionMode, visibility.style]);\n\n const content = (\n <div\n data-pivot-content={id}\n data-active={isActive ? \"true\" : \"false\"}\n style={style}\n {...visibility.props}\n >\n {children}\n </div>\n );\n\n if (transitionMode === \"none\") {\n return <React.Activity mode={isActive ? \"visible\" : \"hidden\"}>{content}</React.Activity>;\n }\n\n return content;\n});\n","/**\n * @file Headless hook for managing Pivot (content switching) behavior.\n *\n * Includes content caching to preserve React component state across re-renders.\n * This is essential for maintaining internal state when parent components\n * re-create the items array.\n */\nimport * as React from \"react\";\nimport type { UsePivotOptions, UsePivotResult, PivotItemProps, PivotItem, PivotNavigationOptions } from \"./types\";\nimport { PivotContent } from \"./PivotContent\";\nimport { useContentCache } from \"../../hooks/useContentCache\";\n\n/**\n * Context for sharing pivot state with Outlet component.\n * Uses a ref-based approach to avoid re-creating the Outlet component.\n * Includes content cache to preserve component state.\n */\ntype PivotOutletContextValue = {\n getState: () => {\n items: ReadonlyArray<PivotItem>;\n activeId: string;\n transitionMode: \"css\" | \"none\";\n };\n subscribe: (callback: () => void) => () => void;\n /**\n * Get cached content for an item. Returns the same ReactNode reference\n * for the same item ID to prevent remounting on parent re-renders.\n */\n getCachedContent: (itemId: string) => React.ReactNode | null;\n};\n\nconst PivotOutletContext = React.createContext<PivotOutletContextValue | null>(null);\n\n/**\n * Stable Outlet component that subscribes to state changes.\n * This prevents remounting when activeId changes.\n * Uses cached content only when item.cache is true.\n */\nconst PivotOutletInner: React.FC = React.memo(() => {\n const ctx = React.useContext(PivotOutletContext);\n if (!ctx) {\n throw new Error(\"PivotOutlet must be used within usePivot\");\n }\n\n const [, forceUpdate] = React.useReducer((x) => x + 1, 0);\n\n React.useEffect(() => {\n return ctx.subscribe(forceUpdate);\n }, [ctx]);\n\n const { items, activeId, transitionMode } = ctx.getState();\n\n return (\n <>\n {items.map((item) => (\n <PivotContent key={item.id} id={item.id} isActive={item.id === activeId} transitionMode={transitionMode}>\n {item.cache ? ctx.getCachedContent(item.id) : item.content}\n </PivotContent>\n ))}\n </>\n );\n});\n\n/**\n * Headless hook for managing content switching within a scope.\n * Provides behavior only - UI is fully customizable.\n *\n * @example\n * ```tsx\n * const { activeId, getItemProps, Outlet } = usePivot({\n * items: [\n * { id: 'home', label: 'Home', content: <HomePage /> },\n * { id: 'settings', label: 'Settings', content: <SettingsPage /> }\n * ],\n * defaultActiveId: 'home'\n * });\n *\n * return (\n * <div>\n * <nav>\n * {items.map((item) => (\n * <button key={item.id} {...getItemProps(item.id)}>{item.label}</button>\n * ))}\n * </nav>\n * <Outlet />\n * </div>\n * );\n * ```\n */\nexport function usePivot<TId extends string = string>(options: UsePivotOptions<TId>): UsePivotResult<TId> {\n const { items, activeId: controlledActiveId, defaultActiveId, onActiveChange, transitionMode = \"css\", navigationMode = \"linear\" } = options;\n\n const isControlled = controlledActiveId !== undefined;\n\n const [uncontrolledActiveId, setUncontrolledActiveId] = React.useState<TId>(() => {\n if (defaultActiveId !== undefined) {\n return defaultActiveId;\n }\n const firstEnabled = items.find((item) => item.disabled !== true);\n if (!firstEnabled) {\n throw new Error(\"usePivot: No enabled items provided\");\n }\n return firstEnabled.id;\n });\n\n const activeId = isControlled ? controlledActiveId : uncontrolledActiveId;\n\n // Animation state\n const [isAnimating, setIsAnimating] = React.useState(false);\n\n const setActiveId = React.useCallback(\n (id: TId, options?: PivotNavigationOptions) => {\n const target = items.find((item) => item.id === id);\n if (!target) {\n return;\n }\n if (target.disabled) {\n return;\n }\n\n // Determine if we should animate\n const shouldAnimate = options?.animated ?? (transitionMode === \"css\");\n setIsAnimating(shouldAnimate);\n\n if (!isControlled) {\n setUncontrolledActiveId(id);\n }\n onActiveChange?.(id);\n },\n [items, isControlled, onActiveChange, transitionMode],\n );\n\n // End animation callback\n const endAnimation = React.useCallback(() => {\n setIsAnimating(false);\n }, []);\n\n const isActive = React.useCallback((id: TId): boolean => id === activeId, [activeId]);\n\n // Get only enabled items for navigation\n const enabledItems = React.useMemo(\n () => items.filter((item) => item.disabled !== true),\n [items],\n );\n\n // Current index in enabled items\n const activeIndex = React.useMemo(() => {\n const index = enabledItems.findIndex((item) => item.id === activeId);\n return index === -1 ? 0 : index;\n }, [enabledItems, activeId]);\n\n // Total count of enabled items\n const itemCount = enabledItems.length;\n\n // Check if navigation in a direction is possible\n const canGo = React.useCallback(\n (direction: number): boolean => {\n if (direction === 0) {\n return false;\n }\n // In loop mode, navigation is always possible if there are 2+ items\n if (navigationMode === \"loop\") {\n return itemCount >= 2;\n }\n // Linear mode: check bounds\n const targetIndex = activeIndex + direction;\n return targetIndex >= 0 && targetIndex < itemCount;\n },\n [activeIndex, itemCount, navigationMode],\n );\n\n // Compute target index with optional wrap-around\n const computeTargetIndex = React.useCallback(\n (direction: number): number => {\n const rawIndex = activeIndex + direction;\n if (navigationMode === \"loop\") {\n return ((rawIndex % itemCount) + itemCount) % itemCount;\n }\n return rawIndex;\n },\n [activeIndex, navigationMode, itemCount],\n );\n\n // Navigate in a direction\n const go = React.useCallback(\n (direction: number, options?: PivotNavigationOptions): void => {\n if (!canGo(direction)) {\n return;\n }\n const targetIndex = computeTargetIndex(direction);\n const targetItem = enabledItems[targetIndex];\n if (targetItem) {\n setActiveId(targetItem.id, options);\n }\n },\n [canGo, computeTargetIndex, enabledItems, setActiveId],\n );\n\n // Get virtual position for an item relative to active (for loop mode support)\n const getVirtualPosition = React.useCallback(\n (id: TId): -1 | 0 | 1 | null => {\n const itemIndex = enabledItems.findIndex((item) => item.id === id);\n if (itemIndex === -1) {\n return null;\n }\n\n if (navigationMode === \"linear\") {\n const rawOffset = itemIndex - activeIndex;\n if (Math.abs(rawOffset) > 1) {\n return null;\n }\n return rawOffset as -1 | 0 | 1;\n }\n\n // Loop mode: find shortest path\n const forwardDist = ((itemIndex - activeIndex) % itemCount + itemCount) % itemCount;\n if (forwardDist === 0) {\n return 0;\n }\n if (forwardDist === 1) {\n return 1; // Item is next\n }\n if (itemCount - forwardDist === 1) {\n return -1; // Item is previous\n }\n return null;\n },\n [enabledItems, activeIndex, navigationMode, itemCount],\n );\n\n // Get position for any item relative to active (for viewport mode)\n const getItemPosition = React.useCallback(\n (id: TId): number | null => {\n const itemIndex = enabledItems.findIndex((item) => item.id === id);\n if (itemIndex === -1) {\n return null;\n }\n\n if (navigationMode === \"linear\") {\n return itemIndex - activeIndex;\n }\n\n // Loop mode: find shortest path\n const forwardDist = ((itemIndex - activeIndex) % itemCount + itemCount) % itemCount;\n const backwardDist = itemCount - forwardDist;\n\n // Return the shorter path (prefer forward on tie)\n if (forwardDist <= backwardDist) {\n return forwardDist;\n }\n return -backwardDist;\n },\n [enabledItems, activeIndex, navigationMode, itemCount],\n );\n\n const getItemProps = React.useCallback(\n (id: TId): PivotItemProps => ({\n \"data-pivot-item\": id,\n \"data-active\": (id === activeId ? \"true\" : \"false\") as \"true\" | \"false\",\n \"aria-selected\": id === activeId,\n tabIndex: id === activeId ? 0 : -1,\n onClick: () => {\n setActiveId(id);\n },\n }),\n [activeId, setActiveId],\n );\n\n const containerStyle: React.CSSProperties = React.useMemo(\n () => ({\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n }),\n [],\n );\n\n // Store state in a ref for stable getState function\n const stateRef = React.useRef({\n items,\n activeId,\n transitionMode,\n });\n\n // Update ref when state changes\n stateRef.current = {\n items,\n activeId,\n transitionMode,\n };\n\n // Subscribers for state changes\n const subscribersRef = React.useRef(new Set<() => void>());\n\n // Notify subscribers when activeId changes\n React.useEffect(() => {\n subscribersRef.current.forEach((callback) => callback());\n }, [activeId, transitionMode]);\n\n // Content resolver for useContentCache\n const resolveContent = React.useCallback(\n (itemId: string): React.ReactNode | null => {\n const item = stateRef.current.items.find((i) => i.id === itemId);\n return item?.content ?? null;\n },\n [],\n );\n\n // Valid IDs for cache cleanup (cast to string[] for useContentCache compatibility)\n const validIds = React.useMemo((): readonly string[] => items.map((i) => i.id), [items]);\n\n // Use shared content cache hook\n const { getCachedContent } = useContentCache({\n resolveContent,\n validIds,\n });\n\n // Stable context value (never changes)\n const contextValue = React.useMemo<PivotOutletContextValue>(\n () => ({\n getState: () => stateRef.current,\n subscribe: (callback) => {\n subscribersRef.current.add(callback);\n return () => subscribersRef.current.delete(callback);\n },\n getCachedContent,\n }),\n [getCachedContent],\n );\n\n // Stable Outlet component (reference never changes)\n const Outlet = React.useMemo(() => {\n const OutletComponent: React.FC = () => (\n <PivotOutletContext.Provider value={contextValue}>\n <div style={containerStyle} data-pivot-container>\n <PivotOutletInner />\n </div>\n </PivotOutletContext.Provider>\n );\n OutletComponent.displayName = \"PivotOutlet\";\n return OutletComponent;\n }, [contextValue, containerStyle]);\n\n return { activeId, setActiveId, isActive, getItemProps, Outlet, go, canGo, activeIndex, itemCount, isAnimating, endAnimation, navigationMode, getVirtualPosition, getItemPosition };\n}\n","/**\n * @file SwipePivotTabBar - Swipeable tab bar for pivot navigation with proper looping.\n *\n * Infinite loop model:\n * - Uses continuous scroll offset (not discrete positions)\n * - Renders tab slots at fixed positions, content determined by scroll offset\n * - Clones tabs at boundaries for seamless looping\n * - Each slot has stable key (by position), preventing remount jumps\n */\nimport * as React from \"react\";\nimport type { SwipeInputState, GestureAxis } from \"../../hooks/gesture/types.js\";\n\n/**\n * Props passed to the indicator render function.\n * Use these to position a sliding indicator (iOS-style).\n */\nexport type IndicatorRenderProps = {\n /** Current offset in pixels (includes swipe displacement and animation) */\n offsetPx: number;\n /** Width of each tab */\n tabWidth: number;\n /** Center X position where active tab is located */\n centerX: number;\n /** Whether currently swiping */\n isSwiping: boolean;\n /** Whether animation is in progress */\n isAnimating: boolean;\n};\n\nexport type SwipePivotTabBarProps<TId extends string = string> = {\n /** Tab items to render */\n items: ReadonlyArray<{ id: TId; label?: string }>;\n /** Currently active tab ID */\n activeId: TId;\n /** Index of active tab */\n activeIndex: number;\n /** Total number of items */\n itemCount: number;\n /** Current swipe input state */\n inputState: SwipeInputState;\n /** Width of each tab */\n tabWidth: number;\n /** Width of the visible viewport */\n viewportWidth: number;\n /** Navigation mode */\n navigationMode?: \"linear\" | \"loop\";\n /** Axis for swipe (horizontal or vertical) */\n axis?: GestureAxis;\n /** Render function for each tab */\n renderTab: (item: { id: TId; label?: string }, isActive: boolean, index: number) => React.ReactNode;\n /** Animation duration in ms */\n animationDuration?: number;\n /**\n * When true, tabs stay at fixed positions and only the indicator moves.\n * Use this for iOS segmented control style where the \"window\" slides over fixed tabs.\n * @default false\n */\n fixedTabs?: boolean;\n /**\n * Optional render function for a sliding indicator (iOS-style).\n * The indicator follows the active tab position during swipe and animation.\n * Rendered behind the tabs.\n *\n * When used with fixedTabs=true, only the indicator moves while tabs stay fixed.\n *\n * @example\n * ```tsx\n * renderIndicator={({ offsetPx, tabWidth, centerX }) => (\n * <div\n * style={{\n * position: 'absolute',\n * left: centerX,\n * bottom: 0,\n * width: tabWidth,\n * height: 3,\n * backgroundColor: '#007AFF',\n * transform: `translateX(${offsetPx}px)`,\n * }}\n * />\n * )}\n * ```\n */\n renderIndicator?: (props: IndicatorRenderProps) => React.ReactNode;\n};\n\nconst DEFAULT_ANIMATION_DURATION = 300;\n\n/** Get displacement value for the given axis from input state */\nconst getAxisDisplacement = (inputState: SwipeInputState, axis: GestureAxis): number => {\n if (inputState.phase === \"idle\") {\n return 0;\n }\n return axis === \"horizontal\" ? inputState.displacement.x : inputState.displacement.y;\n};\n\n/**\n * Normalize index to valid range [0, count)\n */\nconst normalizeIndex = (index: number, count: number): number => {\n return ((index % count) + count) % count;\n};\n\n/**\n * Calculate which item should appear at a given slot position.\n * For loop mode, wraps around using modulo.\n * For linear mode, returns null if out of range.\n */\nconst getItemAtPosition = (\n slotPosition: number,\n activeIndex: number,\n itemCount: number,\n navigationMode: \"linear\" | \"loop\",\n): number | null => {\n const targetIndex = activeIndex + slotPosition;\n\n const isOutOfRange = (index: number, count: number): boolean => {\n if (index < 0) {\n return true;\n }\n if (index >= count) {\n return true;\n }\n return false;\n };\n\n if (navigationMode === \"linear\") {\n if (isOutOfRange(targetIndex, itemCount)) {\n return null;\n }\n return targetIndex;\n }\n\n // Loop mode: wrap around\n return normalizeIndex(targetIndex, itemCount);\n};\n\ntype TabSlotProps<TId extends string> = {\n slotPosition: number;\n item: { id: TId; label?: string };\n itemIndex: number;\n isActive: boolean;\n centerX: number;\n tabWidth: number;\n viewportWidth: number;\n offsetPx: number;\n axis: GestureAxis;\n renderTab: (item: { id: TId; label?: string }, isActive: boolean, index: number) => React.ReactNode;\n};\n\n/**\n * Tab slot component - renders a tab at a fixed slot position.\n * The slot position is stable; only the content changes based on scroll offset.\n */\nconst TabSlot = React.memo(<TId extends string>({\n slotPosition,\n item,\n itemIndex,\n isActive,\n centerX,\n tabWidth,\n viewportWidth,\n offsetPx,\n axis,\n renderTab,\n}: TabSlotProps<TId>) => {\n // Calculate visual position: centerX + slotPosition * tabWidth + offsetPx\n const basePx = slotPosition * tabWidth;\n const visualPx = centerX + basePx + offsetPx;\n\n // Check if visible in viewport\n const visible = visualPx + tabWidth > 0 && visualPx < viewportWidth;\n\n const transformFn = axis === \"horizontal\" ? \"translateX\" : \"translateY\";\n\n return (\n <div\n data-pivot-tab={item.id}\n data-slot={slotPosition}\n data-active={isActive ? \"true\" : \"false\"}\n style={{\n position: \"absolute\",\n left: centerX,\n top: 0,\n width: tabWidth,\n height: \"100%\",\n visibility: visible ? \"visible\" : \"hidden\",\n willChange: \"transform\",\n transform: `${transformFn}(${basePx + offsetPx}px)`,\n }}\n >\n {renderTab(item, isActive, itemIndex)}\n </div>\n );\n}) as <TId extends string>(props: TabSlotProps<TId>) => React.ReactElement;\n\n/**\n * Easing function for smooth animation\n */\nconst easeOutExpo = (t: number): number => {\n return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);\n};\n\n/**\n * Swipeable tab bar for pivot navigation.\n */\nexport function SwipePivotTabBar<TId extends string = string>({\n items,\n activeId,\n activeIndex,\n itemCount,\n inputState,\n tabWidth,\n viewportWidth,\n navigationMode = \"linear\",\n axis = \"horizontal\",\n renderTab,\n animationDuration = DEFAULT_ANIMATION_DURATION,\n fixedTabs = false,\n renderIndicator,\n}: SwipePivotTabBarProps<TId>): React.ReactElement {\n const isSwipePhase = (phase: SwipeInputState[\"phase\"]): boolean => {\n if (phase === \"swiping\") {\n return true;\n }\n if (phase === \"tracking\") {\n return true;\n }\n return false;\n };\n\n const getIsAnimating = (\n slotAnimation: typeof animationRef.current,\n fixedAnimation: typeof fixedAnimationRef.current,\n ): boolean => {\n if (slotAnimation !== null) {\n return true;\n }\n if (fixedAnimation !== null) {\n return true;\n }\n return false;\n };\n\n const getDelta = (\n mode: \"linear\" | \"loop\",\n nextIndex: number,\n previousIndex: number,\n totalItems: number,\n ): number => {\n if (mode === \"loop\") {\n // Use shortest path in loop mode\n const forwardDist = normalizeIndex(nextIndex - previousIndex, totalItems);\n const backwardDist = totalItems - forwardDist;\n if (forwardDist <= backwardDist) {\n return forwardDist;\n }\n return -backwardDist;\n }\n return nextIndex - previousIndex;\n };\n\n const displacement = getAxisDisplacement(inputState, axis);\n const isSwiping = isSwipePhase(inputState.phase);\n\n // ============================================================\n // Animation state for SLOT-BASED mode (scrolling tabs)\n // ============================================================\n const [animatedOffset, setAnimatedOffset] = React.useState(0);\n const animationRef = React.useRef<{\n startTime: number;\n startOffset: number;\n targetOffset: number;\n rafId: number;\n } | null>(null);\n\n // ============================================================\n // Animation state for FIXED TABS mode (iOS-style indicator)\n // Tracks the actual indicator position in pixels\n // ============================================================\n const [indicatorPosition, setIndicatorPosition] = React.useState(activeIndex * tabWidth);\n const indicatorPositionRef = React.useRef(activeIndex * tabWidth); // Track current position for animation start\n const fixedAnimationRef = React.useRef<{\n rafId: number;\n } | null>(null);\n const lastSwipePositionRef = React.useRef<number | null>(null);\n\n // Keep ref in sync with state\n React.useEffect(() => {\n indicatorPositionRef.current = indicatorPosition;\n }, [indicatorPosition]);\n\n const prevActiveIndexRef = React.useRef(activeIndex);\n\n // Calculate the range of slot positions to render\n const halfRange = Math.ceil(viewportWidth / tabWidth / 2) + 1;\n\n // Center position for active tab (for slot-based mode)\n const centerX = (viewportWidth - tabWidth) / 2;\n\n // ============================================================\n // Fixed tabs mode: track swipe position\n // ============================================================\n React.useEffect(() => {\n if (!fixedTabs) {\n return;\n }\n if (!isSwiping) {\n return;\n }\n\n // During swipe, track the visual position\n // Swipe direction is OPPOSITE to indicator movement\n const visualPosition = activeIndex * tabWidth - displacement;\n lastSwipePositionRef.current = visualPosition;\n setIndicatorPosition(visualPosition);\n }, [fixedTabs, isSwiping, activeIndex, tabWidth, displacement]);\n\n // ============================================================\n // Fixed tabs mode: animate when swipe ends or tab clicked\n // ============================================================\n React.useEffect(() => {\n if (!fixedTabs) {\n return;\n }\n if (isSwiping) {\n return;\n }\n\n // When swipe ends or tab changes via click\n const targetPosition = activeIndex * tabWidth;\n const startPosition = lastSwipePositionRef.current ?? indicatorPositionRef.current;\n lastSwipePositionRef.current = null;\n\n // Already at target\n if (Math.abs(startPosition - targetPosition) < 1) {\n setIndicatorPosition(targetPosition);\n return;\n }\n\n // Cancel existing animation\n if (fixedAnimationRef.current) {\n cancelAnimationFrame(fixedAnimationRef.current.rafId);\n }\n\n // Animate from current position to target\n const startTime = performance.now();\n const animationStartPosition = startPosition; // Capture for closure\n\n const animate = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / animationDuration, 1);\n const easedProgress = easeOutExpo(progress);\n\n const currentPosition = animationStartPosition + (targetPosition - animationStartPosition) * easedProgress;\n setIndicatorPosition(currentPosition);\n\n if (progress < 1) {\n fixedAnimationRef.current = {\n rafId: requestAnimationFrame(animate),\n };\n } else {\n fixedAnimationRef.current = null;\n setIndicatorPosition(targetPosition);\n }\n };\n\n fixedAnimationRef.current = {\n rafId: requestAnimationFrame(animate),\n };\n }, [fixedTabs, isSwiping, activeIndex, tabWidth, animationDuration]);\n\n // ============================================================\n // Slot-based mode animation: handle activeIndex changes\n // ============================================================\n React.useEffect(() => {\n if (fixedTabs) {\n return; // Skip for fixed tabs mode\n }\n\n if (prevActiveIndexRef.current === activeIndex) {\n return;\n }\n\n const prevIndex = prevActiveIndexRef.current;\n prevActiveIndexRef.current = activeIndex;\n\n // Calculate direction of movement\n const delta = getDelta(navigationMode, activeIndex, prevIndex, itemCount);\n\n // Target offset to animate to (then snap to 0)\n const targetOffsetPx = -delta * tabWidth;\n\n // Start from current visual position\n const startOffset = isSwiping ? displacement : animatedOffset;\n\n // Cancel any existing animation\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current.rafId);\n }\n\n // If already at target (within threshold), snap immediately\n if (Math.abs(startOffset - targetOffsetPx) < 1) {\n setAnimatedOffset(0);\n return;\n }\n\n // Start animation\n const startTime = performance.now();\n\n const animate = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / animationDuration, 1);\n const easedProgress = easeOutExpo(progress);\n\n // Interpolate from startOffset toward targetOffset, but we want to end at 0\n // So we animate: startOffset → targetOffset, but since we're rendering with\n // the NEW activeIndex, we need to compensate\n //\n // When activeIndex changes, slots now show different content.\n // We animate the offset from (startOffset - targetOffset) back to 0.\n const compensatedStart = startOffset - targetOffsetPx;\n const currentOffset = compensatedStart * (1 - easedProgress);\n\n setAnimatedOffset(currentOffset);\n\n if (progress < 1) {\n animationRef.current = {\n startTime,\n startOffset: compensatedStart,\n targetOffset: 0,\n rafId: requestAnimationFrame(animate),\n };\n } else {\n animationRef.current = null;\n setAnimatedOffset(0);\n }\n };\n\n animationRef.current = {\n startTime,\n startOffset: startOffset - targetOffsetPx,\n targetOffset: 0,\n rafId: requestAnimationFrame(animate),\n };\n }, [fixedTabs, activeIndex, itemCount, tabWidth, animationDuration, navigationMode, displacement, isSwiping, animatedOffset]);\n\n // Update prevActiveIndexRef for fixed tabs mode too\n React.useEffect(() => {\n if (fixedTabs) {\n prevActiveIndexRef.current = activeIndex;\n }\n }, [fixedTabs, activeIndex]);\n\n // Cleanup animation on unmount\n React.useEffect(() => {\n return () => {\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current.rafId);\n }\n if (fixedAnimationRef.current) {\n cancelAnimationFrame(fixedAnimationRef.current.rafId);\n }\n };\n }, []);\n\n // Current offset for slot-based mode\n const currentOffset = isSwiping ? displacement : animatedOffset;\n const isAnimating = getIsAnimating(animationRef.current, fixedAnimationRef.current);\n\n // Cancel slot animation when swiping starts\n React.useEffect(() => {\n if (isSwiping && animationRef.current) {\n cancelAnimationFrame(animationRef.current.rafId);\n animationRef.current = null;\n setAnimatedOffset(0);\n }\n }, [isSwiping]);\n\n // ============================================================\n // Fixed tabs mode: render fixed tabs with sliding indicator\n // ============================================================\n if (fixedTabs) {\n // Calculate total width and centering offset\n const totalTabsWidth = items.length * tabWidth;\n const centeringOffset = (viewportWidth - totalTabsWidth) / 2;\n\n return (\n <div\n data-active-id={activeId}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n display: \"flex\",\n justifyContent: \"center\",\n }}\n >\n {/* Sliding indicator (rendered behind tabs) */}\n {renderIndicator?.({\n offsetPx: indicatorPosition,\n tabWidth,\n centerX: centeringOffset,\n isSwiping,\n isAnimating,\n })}\n\n {/* Fixed tabs - each tab at its natural position */}\n {items.map((item, index) => (\n <div\n key={item.id}\n data-pivot-tab={item.id}\n data-active={index === activeIndex ? \"true\" : \"false\"}\n style={{\n position: \"relative\",\n width: tabWidth,\n height: \"100%\",\n flexShrink: 0,\n }}\n >\n {renderTab(item, index === activeIndex, index)}\n </div>\n ))}\n </div>\n );\n }\n\n // Slot-based rendering for scrolling tabs (infinite loop support)\n const slots: Array<{\n slotPosition: number;\n itemIndex: number;\n item: { id: TId; label?: string };\n }> = [];\n\n for (let pos = -halfRange; pos <= halfRange; pos++) {\n const itemIndex = getItemAtPosition(pos, activeIndex, itemCount, navigationMode);\n if (itemIndex !== null) {\n slots.push({\n slotPosition: pos,\n itemIndex,\n item: items[itemIndex],\n });\n }\n }\n\n return (\n <div\n data-active-id={activeId}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n }}\n >\n {/* Sliding indicator (rendered behind tabs) */}\n {renderIndicator?.({\n offsetPx: currentOffset,\n tabWidth,\n centerX,\n isSwiping,\n isAnimating,\n })}\n\n {/* Tab slots */}\n {slots.map(({ slotPosition, itemIndex, item }) => (\n <TabSlot\n key={slotPosition}\n slotPosition={slotPosition}\n item={item}\n itemIndex={itemIndex}\n isActive={itemIndex === activeIndex}\n centerX={centerX}\n tabWidth={tabWidth}\n viewportWidth={viewportWidth}\n offsetPx={currentOffset}\n axis={axis}\n renderTab={renderTab}\n />\n ))}\n </div>\n );\n}\n"],"names":["DEFAULT_ANIMATION_TIMEOUT","useAnimatedVisibility","isVisible","leaveAnimation","skipAnimation","animationTimeout","prevVisibleRef","React","isAnimatingOut","setIsAnimatingOut","timeoutRef","shouldSkipLeaveAnimation","isSkipped","animation","getShouldDisplay","visible","animatingOut","wasVisible","handleAnimationEnd","e","shouldDisplay","baseStyle","PivotContent","id","isActive","transitionMode","children","visibility","PIVOT_ANIMATION_LEAVE","style","s","PIVOT_ANIMATION_ENTER","content","jsx","PivotOutletContext","PivotOutletInner","ctx","forceUpdate","x","items","activeId","Fragment","item","usePivot","options","controlledActiveId","defaultActiveId","onActiveChange","navigationMode","isControlled","uncontrolledActiveId","setUncontrolledActiveId","firstEnabled","isAnimating","setIsAnimating","setActiveId","target","shouldAnimate","endAnimation","enabledItems","activeIndex","index","itemCount","canGo","direction","targetIndex","computeTargetIndex","rawIndex","go","targetItem","getVirtualPosition","itemIndex","rawOffset","forwardDist","getItemPosition","backwardDist","getItemProps","containerStyle","stateRef","subscribersRef","callback","resolveContent","itemId","i","validIds","getCachedContent","useContentCache","contextValue","Outlet","OutletComponent","DEFAULT_ANIMATION_DURATION","getAxisDisplacement","inputState","axis","normalizeIndex","count","getItemAtPosition","slotPosition","isOutOfRange","TabSlot","centerX","tabWidth","viewportWidth","offsetPx","renderTab","basePx","visualPx","transformFn","easeOutExpo","t","SwipePivotTabBar","animationDuration","fixedTabs","renderIndicator","isSwipePhase","phase","getIsAnimating","slotAnimation","fixedAnimation","getDelta","mode","nextIndex","previousIndex","totalItems","displacement","isSwiping","animatedOffset","setAnimatedOffset","animationRef","indicatorPosition","setIndicatorPosition","indicatorPositionRef","fixedAnimationRef","lastSwipePositionRef","prevActiveIndexRef","halfRange","visualPosition","targetPosition","startPosition","startTime","animationStartPosition","animate","currentTime","elapsed","progress","easedProgress","currentPosition","prevIndex","targetOffsetPx","startOffset","compensatedStart","currentOffset","totalTabsWidth","centeringOffset","jsxs","slots","pos"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";const w=require("react/jsx-runtime"),K=require("react"),X=require("./styles-qf6ptVLD.cjs"),Q=require("./useContentCache-DqXtLrLs.cjs");function Z(s){const o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const r in s)if(r!=="default"){const g=Object.getOwnPropertyDescriptor(s,r);Object.defineProperty(o,r,g.get?g:{enumerable:!0,get:()=>s[r]})}}return o.default=s,Object.freeze(o)}const e=Z(K),W=1e3;function tt({isVisible:s,leaveAnimation:o,skipAnimation:r=!1,animationTimeout:g=W}){const h=e.useRef(s),[n,i]=e.useState(!1),l=e.useRef(null),y=(v,I)=>!!(v||!I||I==="none"),R=(v,I)=>!!(v||I);e.useEffect(()=>()=>{l.current&&clearTimeout(l.current)},[]),e.useEffect(()=>{const v=h.current;h.current=s,l.current&&(clearTimeout(l.current),l.current=null),v&&!s?y(r,o)?i(!1):(i(!0),l.current=setTimeout(()=>{i(!1)},g)):!v&&s&&i(!1)},[s,o,r,g]);const m=e.useCallback(v=>{v.target===v.currentTarget&&n&&(l.current&&(clearTimeout(l.current),l.current=null),i(!1))},[n]),A=R(s,n);return{state:{shouldDisplay:A,isAnimatingOut:n},props:{onAnimationEnd:m},style:{display:A?"block":"none"}}}const et={position:"absolute",inset:0,width:"100%",height:"100%"},nt=e.memo(({id:s,isActive:o,transitionMode:r,children:g})=>{const h=tt({isVisible:o,leaveAnimation:r==="css"?X.PIVOT_ANIMATION_LEAVE:void 0,skipAnimation:r!=="css"}),n=e.useMemo(()=>{const l={...et,...h.style,pointerEvents:o?"auto":"none"};return r==="css"&&(l.animation=o?X.PIVOT_ANIMATION_ENTER:X.PIVOT_ANIMATION_LEAVE),l},[o,r,h.style]),i=w.jsx("div",{"data-pivot-content":s,"data-active":o?"true":"false",style:n,...h.props,children:g});return r==="none"?w.jsx(e.Activity,{mode:o?"visible":"hidden",children:i}):i}),Y=e.createContext(null),rt=e.memo(()=>{const s=e.useContext(Y);if(!s)throw new Error("PivotOutlet must be used within usePivot");const[,o]=e.useReducer(n=>n+1,0);e.useEffect(()=>s.subscribe(o),[s]);const{items:r,activeId:g,transitionMode:h}=s.getState();return w.jsx(w.Fragment,{children:r.map(n=>w.jsx(nt,{id:n.id,isActive:n.id===g,transitionMode:h,children:n.cache?s.getCachedContent(n.id):n.content},n.id))})});function st(s){const{items:o,activeId:r,defaultActiveId:g,onActiveChange:h,transitionMode:n="css",navigationMode:i="linear"}=s,l=r!==void 0,[y,R]=e.useState(()=>{if(g!==void 0)return g;const t=o.find(c=>c.disabled!==!0);if(!t)throw new Error("usePivot: No enabled items provided");return t.id}),m=l?r:y,[A,v]=e.useState(!1),I=e.useCallback((t,c)=>{const a=o.find(C=>C.id===t);if(!a||a.disabled)return;const d=c?.animated??n==="css";v(d),l||R(t),h?.(t)},[o,l,h,n]),V=e.useCallback(()=>{v(!1)},[]),z=e.useCallback(t=>t===m,[m]),O=e.useMemo(()=>o.filter(t=>t.disabled!==!0),[o]),f=e.useMemo(()=>{const t=O.findIndex(c=>c.id===m);return t===-1?0:t},[O,m]),p=O.length,b=e.useCallback(t=>{if(t===0)return!1;if(i==="loop")return p>=2;const c=f+t;return c>=0&&c<p},[f,p,i]),P=e.useCallback(t=>{const c=f+t;return i==="loop"?(c%p+p)%p:c},[f,i,p]),T=e.useCallback((t,c)=>{if(!b(t))return;const a=P(t),d=O[a];d&&I(d.id,c)},[b,P,O,I]),j=e.useCallback(t=>{const c=O.findIndex(d=>d.id===t);if(c===-1)return null;if(i==="linear"){const d=c-f;return Math.abs(d)>1?null:d}const a=((c-f)%p+p)%p;return a===0?0:a===1?1:p-a===1?-1:null},[O,f,i,p]),F=e.useCallback(t=>{const c=O.findIndex(C=>C.id===t);if(c===-1)return null;if(i==="linear")return c-f;const a=((c-f)%p+p)%p,d=p-a;return a<=d?a:-d},[O,f,i,p]),E=e.useCallback(t=>({"data-pivot-item":t,"data-active":t===m?"true":"false","aria-selected":t===m,tabIndex:t===m?0:-1,onClick:()=>{I(t)}}),[m,I]),k=e.useMemo(()=>({position:"relative",width:"100%",height:"100%"}),[]),S=e.useRef({items:o,activeId:m,transitionMode:n});S.current={items:o,activeId:m,transitionMode:n};const M=e.useRef(new Set);e.useEffect(()=>{M.current.forEach(t=>t())},[m,n]);const _=e.useCallback(t=>S.current.items.find(a=>a.id===t)?.content??null,[]),D=e.useMemo(()=>o.map(t=>t.id),[o]),{getCachedContent:x}=Q.useContentCache({resolveContent:_,validIds:D}),N=e.useMemo(()=>({getState:()=>S.current,subscribe:t=>(M.current.add(t),()=>M.current.delete(t)),getCachedContent:x}),[x]),u=e.useMemo(()=>{const t=()=>w.jsx(Y.Provider,{value:N,children:w.jsx("div",{style:k,"data-pivot-container":!0,children:w.jsx(rt,{})})});return t.displayName="PivotOutlet",t},[N,k]);return{activeId:m,setActiveId:I,isActive:z,getItemProps:E,Outlet:u,go:T,canGo:b,activeIndex:f,itemCount:p,isAnimating:A,endAnimation:V,navigationMode:i,getVirtualPosition:j,getItemPosition:F}}const ot=300,ct=(s,o)=>s.phase==="idle"?0:o==="horizontal"?s.displacement.x:s.displacement.y,H=(s,o)=>(s%o+o)%o,it=(s,o,r,g)=>{const h=o+s,n=(i,l)=>i<0||i>=l;return g==="linear"?n(h,r)?null:h:H(h,r)},ut=e.memo(({slotPosition:s,item:o,itemIndex:r,isActive:g,centerX:h,tabWidth:n,viewportWidth:i,offsetPx:l,axis:y,renderTab:R})=>{const m=s*n,A=h+m+l,v=A+n>0&&A<i,I=y==="horizontal"?"translateX":"translateY";return w.jsx("div",{"data-pivot-tab":o.id,"data-slot":s,"data-active":g?"true":"false",style:{position:"absolute",left:h,top:0,width:n,height:"100%",visibility:v?"visible":"hidden",willChange:"transform",transform:`${I}(${m+l}px)`},children:R(o,g,r)})}),G=s=>s===1?1:1-Math.pow(2,-10*s);function at({items:s,activeId:o,activeIndex:r,itemCount:g,inputState:h,tabWidth:n,viewportWidth:i,navigationMode:l="linear",axis:y="horizontal",renderTab:R,animationDuration:m=ot,fixedTabs:A=!1,renderIndicator:v}){const I=u=>u==="swiping"||u==="tracking",V=(u,t)=>u!==null||t!==null,z=(u,t,c,a)=>{if(u==="loop"){const d=H(t-c,a),C=a-d;return d<=C?d:-C}return t-c},O=ct(h,y),f=I(h.phase),[p,b]=e.useState(0),P=e.useRef(null),[T,j]=e.useState(r*n),F=e.useRef(r*n),E=e.useRef(null),k=e.useRef(null);e.useEffect(()=>{F.current=T},[T]);const S=e.useRef(r),M=Math.ceil(i/n/2)+1,_=(i-n)/2;e.useEffect(()=>{if(!A||!f)return;const u=r*n-O;k.current=u,j(u)},[A,f,r,n,O]),e.useEffect(()=>{if(!A||f)return;const u=r*n,t=k.current??F.current;if(k.current=null,Math.abs(t-u)<1){j(u);return}E.current&&cancelAnimationFrame(E.current.rafId);const c=performance.now(),a=t,d=C=>{const L=C-c,q=Math.min(L/m,1),U=G(q),B=a+(u-a)*U;j(B),q<1?E.current={rafId:requestAnimationFrame(d)}:(E.current=null,j(u))};E.current={rafId:requestAnimationFrame(d)}},[A,f,r,n,m]),e.useEffect(()=>{if(A||S.current===r)return;const u=S.current;S.current=r;const c=-z(l,r,u,g)*n,a=f?O:p;if(P.current&&cancelAnimationFrame(P.current.rafId),Math.abs(a-c)<1){b(0);return}const d=performance.now(),C=L=>{const q=L-d,U=Math.min(q/m,1),B=G(U),$=a-c,J=$*(1-B);b(J),U<1?P.current={startTime:d,startOffset:$,targetOffset:0,rafId:requestAnimationFrame(C)}:(P.current=null,b(0))};P.current={startTime:d,startOffset:a-c,targetOffset:0,rafId:requestAnimationFrame(C)}},[A,r,g,n,m,l,O,f,p]),e.useEffect(()=>{A&&(S.current=r)},[A,r]),e.useEffect(()=>()=>{P.current&&cancelAnimationFrame(P.current.rafId),E.current&&cancelAnimationFrame(E.current.rafId)},[]);const D=f?O:p,x=V(P.current,E.current);if(e.useEffect(()=>{f&&P.current&&(cancelAnimationFrame(P.current.rafId),P.current=null,b(0))},[f]),A){const u=s.length*n,t=(i-u)/2;return w.jsxs("div",{"data-active-id":o,style:{position:"relative",width:"100%",height:"100%",overflow:"hidden",display:"flex",justifyContent:"center"},children:[v?.({offsetPx:T,tabWidth:n,centerX:t,isSwiping:f,isAnimating:x}),s.map((c,a)=>w.jsx("div",{"data-pivot-tab":c.id,"data-active":a===r?"true":"false",style:{position:"relative",width:n,height:"100%",flexShrink:0},children:R(c,a===r,a)},c.id))]})}const N=[];for(let u=-M;u<=M;u++){const t=it(u,r,g,l);t!==null&&N.push({slotPosition:u,itemIndex:t,item:s[t]})}return w.jsxs("div",{"data-active-id":o,style:{position:"relative",width:"100%",height:"100%",overflow:"hidden"},children:[v?.({offsetPx:D,tabWidth:n,centerX:_,isSwiping:f,isAnimating:x}),N.map(({slotPosition:u,itemIndex:t,item:c})=>w.jsx(ut,{slotPosition:u,item:c,itemIndex:t,isActive:t===r,centerX:_,tabWidth:n,viewportWidth:i,offsetPx:D,axis:y,renderTab:R},u))]})}exports.SwipePivotTabBar=at;exports.usePivot=st;
2
+ //# sourceMappingURL=SwipePivotTabBar-fjjXkpj7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwipePivotTabBar-fjjXkpj7.cjs","sources":["../src/hooks/useAnimatedVisibility.ts","../src/modules/pivot/PivotContent.tsx","../src/modules/pivot/usePivot.tsx","../src/modules/pivot/SwipePivotTabBar.tsx"],"sourcesContent":["/**\n * @file Hook for animation-aware visibility control.\n *\n * Common pattern for showing/hiding elements with CSS animations:\n * - If animation exists: wait for animationend before hiding\n * - If no animation: hide immediately\n * - Uses display:none for performance (removes from layout)\n * - Includes timeout fallback in case animationend doesn't fire\n */\nimport * as React from \"react\";\n\n/** Default timeout for animation fallback (ms) */\nconst DEFAULT_ANIMATION_TIMEOUT = 1000;\n\ntype AnimatedVisibilityState = {\n /** Whether element should be displayed (display: block/none) */\n shouldDisplay: boolean;\n /** Whether element is currently animating out */\n isAnimatingOut: boolean;\n};\n\ntype UseAnimatedVisibilityOptions = {\n /** Whether the element is logically visible */\n isVisible: boolean;\n /** CSS animation value for leave animation (e.g., CSS variable) */\n leaveAnimation?: string;\n /** Skip animation and hide immediately */\n skipAnimation?: boolean;\n /** Timeout for animation fallback in ms (default: 1000ms) */\n animationTimeout?: number;\n};\n\ntype UseAnimatedVisibilityResult = {\n /** Current visibility state */\n state: AnimatedVisibilityState;\n /** Props to spread on the animated element */\n props: {\n onAnimationEnd: (e: React.AnimationEvent) => void;\n };\n /** Style to apply for display control */\n style: {\n display: \"block\" | \"none\";\n };\n};\n\n/**\n * Hook for animation-aware visibility control.\n *\n * @example\n * const { state, props, style } = useAnimatedVisibility({\n * isVisible: isActive,\n * leaveAnimation: PIVOT_ANIMATION_LEAVE,\n * });\n *\n * return (\n * <div\n * style={{ ...baseStyle, ...style, animation: isActive ? enterAnim : leaveAnim }}\n * {...props}\n * >\n * {children}\n * </div>\n * );\n */\nexport function useAnimatedVisibility({\n isVisible,\n leaveAnimation,\n skipAnimation = false,\n animationTimeout = DEFAULT_ANIMATION_TIMEOUT,\n}: UseAnimatedVisibilityOptions): UseAnimatedVisibilityResult {\n const prevVisibleRef = React.useRef(isVisible);\n const [isAnimatingOut, setIsAnimatingOut] = React.useState(false);\n const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Clear timeout on unmount\n const shouldSkipLeaveAnimation = (\n isSkipped: boolean,\n animation: string | undefined,\n ): boolean => {\n if (isSkipped) {\n return true;\n }\n if (!animation) {\n return true;\n }\n if (animation === \"none\") {\n return true;\n }\n return false;\n };\n\n const getShouldDisplay = (visible: boolean, animatingOut: boolean): boolean => {\n if (visible) {\n return true;\n }\n if (animatingOut) {\n return true;\n }\n return false;\n };\n\n React.useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n React.useEffect(() => {\n const wasVisible = prevVisibleRef.current;\n prevVisibleRef.current = isVisible;\n\n // Clear any pending timeout\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n\n if (wasVisible && !isVisible) {\n // Transitioning from visible to hidden\n if (shouldSkipLeaveAnimation(skipAnimation, leaveAnimation)) {\n // No animation, hide immediately\n setIsAnimatingOut(false);\n } else {\n // Has animation, mark as animating out\n setIsAnimatingOut(true);\n\n // Set timeout fallback in case animationend doesn't fire\n // (e.g., CSS variable resolves to \"none\", or animation is very short)\n timeoutRef.current = setTimeout(() => {\n setIsAnimatingOut(false);\n }, animationTimeout);\n }\n } else if (!wasVisible && isVisible) {\n // Transitioning from hidden to visible\n setIsAnimatingOut(false);\n }\n }, [isVisible, leaveAnimation, skipAnimation, animationTimeout]);\n\n const handleAnimationEnd = React.useCallback(\n (e: React.AnimationEvent) => {\n // Only handle animation end for this element (not bubbled from children)\n if (e.target === e.currentTarget && isAnimatingOut) {\n // Clear timeout since animation completed normally\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = null;\n }\n setIsAnimatingOut(false);\n }\n },\n [isAnimatingOut],\n );\n\n // Element should be displayed if:\n // - It's visible, OR\n // - It's animating out (leave animation in progress)\n const shouldDisplay = getShouldDisplay(isVisible, isAnimatingOut);\n\n return {\n state: {\n shouldDisplay,\n isAnimatingOut,\n },\n props: {\n onAnimationEnd: handleAnimationEnd,\n },\n style: {\n display: shouldDisplay ? \"block\" : \"none\",\n },\n };\n}\n","/**\n * @file PivotContent component for rendering pivot items with CSS animations.\n *\n * Override via CSS custom properties:\n * - --rpl-pivot-animation-enter: Animation when becoming active\n * - --rpl-pivot-animation-leave: Animation when becoming inactive\n *\n * User defines @keyframes in their CSS and references via these tokens.\n * Example:\n * @keyframes pivotEnter {\n * from { opacity: 0; }\n * to { opacity: 1; }\n * }\n * :root { --rpl-pivot-animation-enter: pivotEnter 150ms ease-out forwards; }\n */\nimport * as React from \"react\";\nimport { PIVOT_ANIMATION_ENTER, PIVOT_ANIMATION_LEAVE } from \"../../constants/styles\";\nimport { useAnimatedVisibility } from \"../../hooks/useAnimatedVisibility\";\n\nexport type PivotContentProps = {\n id: string;\n isActive: boolean;\n transitionMode: \"css\" | \"none\";\n children: React.ReactNode;\n};\n\nconst baseStyle: React.CSSProperties = {\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n};\n\n/**\n * Renders pivot content with CSS animation support.\n *\n * When transitionMode=\"css\": Applies enter/leave animations with display:none when hidden.\n * When transitionMode=\"none\": Uses React.Activity for memory optimization.\n */\nexport const PivotContent: React.FC<PivotContentProps> = React.memo(({ id, isActive, transitionMode, children }) => {\n const visibility = useAnimatedVisibility({\n isVisible: isActive,\n leaveAnimation: transitionMode === \"css\" ? PIVOT_ANIMATION_LEAVE : undefined,\n skipAnimation: transitionMode !== \"css\",\n });\n\n const style = React.useMemo<React.CSSProperties>(() => {\n const s: React.CSSProperties = {\n ...baseStyle,\n ...visibility.style,\n pointerEvents: isActive ? \"auto\" : \"none\",\n };\n\n if (transitionMode === \"css\") {\n s.animation = isActive ? PIVOT_ANIMATION_ENTER : PIVOT_ANIMATION_LEAVE;\n }\n\n return s;\n }, [isActive, transitionMode, visibility.style]);\n\n const content = (\n <div\n data-pivot-content={id}\n data-active={isActive ? \"true\" : \"false\"}\n style={style}\n {...visibility.props}\n >\n {children}\n </div>\n );\n\n if (transitionMode === \"none\") {\n return <React.Activity mode={isActive ? \"visible\" : \"hidden\"}>{content}</React.Activity>;\n }\n\n return content;\n});\n","/**\n * @file Headless hook for managing Pivot (content switching) behavior.\n *\n * Includes content caching to preserve React component state across re-renders.\n * This is essential for maintaining internal state when parent components\n * re-create the items array.\n */\nimport * as React from \"react\";\nimport type { UsePivotOptions, UsePivotResult, PivotItemProps, PivotItem, PivotNavigationOptions } from \"./types\";\nimport { PivotContent } from \"./PivotContent\";\nimport { useContentCache } from \"../../hooks/useContentCache\";\n\n/**\n * Context for sharing pivot state with Outlet component.\n * Uses a ref-based approach to avoid re-creating the Outlet component.\n * Includes content cache to preserve component state.\n */\ntype PivotOutletContextValue = {\n getState: () => {\n items: ReadonlyArray<PivotItem>;\n activeId: string;\n transitionMode: \"css\" | \"none\";\n };\n subscribe: (callback: () => void) => () => void;\n /**\n * Get cached content for an item. Returns the same ReactNode reference\n * for the same item ID to prevent remounting on parent re-renders.\n */\n getCachedContent: (itemId: string) => React.ReactNode | null;\n};\n\nconst PivotOutletContext = React.createContext<PivotOutletContextValue | null>(null);\n\n/**\n * Stable Outlet component that subscribes to state changes.\n * This prevents remounting when activeId changes.\n * Uses cached content only when item.cache is true.\n */\nconst PivotOutletInner: React.FC = React.memo(() => {\n const ctx = React.useContext(PivotOutletContext);\n if (!ctx) {\n throw new Error(\"PivotOutlet must be used within usePivot\");\n }\n\n const [, forceUpdate] = React.useReducer((x) => x + 1, 0);\n\n React.useEffect(() => {\n return ctx.subscribe(forceUpdate);\n }, [ctx]);\n\n const { items, activeId, transitionMode } = ctx.getState();\n\n return (\n <>\n {items.map((item) => (\n <PivotContent key={item.id} id={item.id} isActive={item.id === activeId} transitionMode={transitionMode}>\n {item.cache ? ctx.getCachedContent(item.id) : item.content}\n </PivotContent>\n ))}\n </>\n );\n});\n\n/**\n * Headless hook for managing content switching within a scope.\n * Provides behavior only - UI is fully customizable.\n *\n * @example\n * ```tsx\n * const { activeId, getItemProps, Outlet } = usePivot({\n * items: [\n * { id: 'home', label: 'Home', content: <HomePage /> },\n * { id: 'settings', label: 'Settings', content: <SettingsPage /> }\n * ],\n * defaultActiveId: 'home'\n * });\n *\n * return (\n * <div>\n * <nav>\n * {items.map((item) => (\n * <button key={item.id} {...getItemProps(item.id)}>{item.label}</button>\n * ))}\n * </nav>\n * <Outlet />\n * </div>\n * );\n * ```\n */\nexport function usePivot<TId extends string = string>(options: UsePivotOptions<TId>): UsePivotResult<TId> {\n const { items, activeId: controlledActiveId, defaultActiveId, onActiveChange, transitionMode = \"css\", navigationMode = \"linear\" } = options;\n\n const isControlled = controlledActiveId !== undefined;\n\n const [uncontrolledActiveId, setUncontrolledActiveId] = React.useState<TId>(() => {\n if (defaultActiveId !== undefined) {\n return defaultActiveId;\n }\n const firstEnabled = items.find((item) => item.disabled !== true);\n if (!firstEnabled) {\n throw new Error(\"usePivot: No enabled items provided\");\n }\n return firstEnabled.id;\n });\n\n const activeId = isControlled ? controlledActiveId : uncontrolledActiveId;\n\n // Animation state\n const [isAnimating, setIsAnimating] = React.useState(false);\n\n const setActiveId = React.useCallback(\n (id: TId, options?: PivotNavigationOptions) => {\n const target = items.find((item) => item.id === id);\n if (!target) {\n return;\n }\n if (target.disabled) {\n return;\n }\n\n // Determine if we should animate\n const shouldAnimate = options?.animated ?? (transitionMode === \"css\");\n setIsAnimating(shouldAnimate);\n\n if (!isControlled) {\n setUncontrolledActiveId(id);\n }\n onActiveChange?.(id);\n },\n [items, isControlled, onActiveChange, transitionMode],\n );\n\n // End animation callback\n const endAnimation = React.useCallback(() => {\n setIsAnimating(false);\n }, []);\n\n const isActive = React.useCallback((id: TId): boolean => id === activeId, [activeId]);\n\n // Get only enabled items for navigation\n const enabledItems = React.useMemo(\n () => items.filter((item) => item.disabled !== true),\n [items],\n );\n\n // Current index in enabled items\n const activeIndex = React.useMemo(() => {\n const index = enabledItems.findIndex((item) => item.id === activeId);\n return index === -1 ? 0 : index;\n }, [enabledItems, activeId]);\n\n // Total count of enabled items\n const itemCount = enabledItems.length;\n\n // Check if navigation in a direction is possible\n const canGo = React.useCallback(\n (direction: number): boolean => {\n if (direction === 0) {\n return false;\n }\n // In loop mode, navigation is always possible if there are 2+ items\n if (navigationMode === \"loop\") {\n return itemCount >= 2;\n }\n // Linear mode: check bounds\n const targetIndex = activeIndex + direction;\n return targetIndex >= 0 && targetIndex < itemCount;\n },\n [activeIndex, itemCount, navigationMode],\n );\n\n // Compute target index with optional wrap-around\n const computeTargetIndex = React.useCallback(\n (direction: number): number => {\n const rawIndex = activeIndex + direction;\n if (navigationMode === \"loop\") {\n return ((rawIndex % itemCount) + itemCount) % itemCount;\n }\n return rawIndex;\n },\n [activeIndex, navigationMode, itemCount],\n );\n\n // Navigate in a direction\n const go = React.useCallback(\n (direction: number, options?: PivotNavigationOptions): void => {\n if (!canGo(direction)) {\n return;\n }\n const targetIndex = computeTargetIndex(direction);\n const targetItem = enabledItems[targetIndex];\n if (targetItem) {\n setActiveId(targetItem.id, options);\n }\n },\n [canGo, computeTargetIndex, enabledItems, setActiveId],\n );\n\n // Get virtual position for an item relative to active (for loop mode support)\n const getVirtualPosition = React.useCallback(\n (id: TId): -1 | 0 | 1 | null => {\n const itemIndex = enabledItems.findIndex((item) => item.id === id);\n if (itemIndex === -1) {\n return null;\n }\n\n if (navigationMode === \"linear\") {\n const rawOffset = itemIndex - activeIndex;\n if (Math.abs(rawOffset) > 1) {\n return null;\n }\n return rawOffset as -1 | 0 | 1;\n }\n\n // Loop mode: find shortest path\n const forwardDist = ((itemIndex - activeIndex) % itemCount + itemCount) % itemCount;\n if (forwardDist === 0) {\n return 0;\n }\n if (forwardDist === 1) {\n return 1; // Item is next\n }\n if (itemCount - forwardDist === 1) {\n return -1; // Item is previous\n }\n return null;\n },\n [enabledItems, activeIndex, navigationMode, itemCount],\n );\n\n // Get position for any item relative to active (for viewport mode)\n const getItemPosition = React.useCallback(\n (id: TId): number | null => {\n const itemIndex = enabledItems.findIndex((item) => item.id === id);\n if (itemIndex === -1) {\n return null;\n }\n\n if (navigationMode === \"linear\") {\n return itemIndex - activeIndex;\n }\n\n // Loop mode: find shortest path\n const forwardDist = ((itemIndex - activeIndex) % itemCount + itemCount) % itemCount;\n const backwardDist = itemCount - forwardDist;\n\n // Return the shorter path (prefer forward on tie)\n if (forwardDist <= backwardDist) {\n return forwardDist;\n }\n return -backwardDist;\n },\n [enabledItems, activeIndex, navigationMode, itemCount],\n );\n\n const getItemProps = React.useCallback(\n (id: TId): PivotItemProps => ({\n \"data-pivot-item\": id,\n \"data-active\": (id === activeId ? \"true\" : \"false\") as \"true\" | \"false\",\n \"aria-selected\": id === activeId,\n tabIndex: id === activeId ? 0 : -1,\n onClick: () => {\n setActiveId(id);\n },\n }),\n [activeId, setActiveId],\n );\n\n const containerStyle: React.CSSProperties = React.useMemo(\n () => ({\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n }),\n [],\n );\n\n // Store state in a ref for stable getState function\n const stateRef = React.useRef({\n items,\n activeId,\n transitionMode,\n });\n\n // Update ref when state changes\n stateRef.current = {\n items,\n activeId,\n transitionMode,\n };\n\n // Subscribers for state changes\n const subscribersRef = React.useRef(new Set<() => void>());\n\n // Notify subscribers when activeId changes\n React.useEffect(() => {\n subscribersRef.current.forEach((callback) => callback());\n }, [activeId, transitionMode]);\n\n // Content resolver for useContentCache\n const resolveContent = React.useCallback(\n (itemId: string): React.ReactNode | null => {\n const item = stateRef.current.items.find((i) => i.id === itemId);\n return item?.content ?? null;\n },\n [],\n );\n\n // Valid IDs for cache cleanup (cast to string[] for useContentCache compatibility)\n const validIds = React.useMemo((): readonly string[] => items.map((i) => i.id), [items]);\n\n // Use shared content cache hook\n const { getCachedContent } = useContentCache({\n resolveContent,\n validIds,\n });\n\n // Stable context value (never changes)\n const contextValue = React.useMemo<PivotOutletContextValue>(\n () => ({\n getState: () => stateRef.current,\n subscribe: (callback) => {\n subscribersRef.current.add(callback);\n return () => subscribersRef.current.delete(callback);\n },\n getCachedContent,\n }),\n [getCachedContent],\n );\n\n // Stable Outlet component (reference never changes)\n const Outlet = React.useMemo(() => {\n const OutletComponent: React.FC = () => (\n <PivotOutletContext.Provider value={contextValue}>\n <div style={containerStyle} data-pivot-container>\n <PivotOutletInner />\n </div>\n </PivotOutletContext.Provider>\n );\n OutletComponent.displayName = \"PivotOutlet\";\n return OutletComponent;\n }, [contextValue, containerStyle]);\n\n return { activeId, setActiveId, isActive, getItemProps, Outlet, go, canGo, activeIndex, itemCount, isAnimating, endAnimation, navigationMode, getVirtualPosition, getItemPosition };\n}\n","/**\n * @file SwipePivotTabBar - Swipeable tab bar for pivot navigation with proper looping.\n *\n * Infinite loop model:\n * - Uses continuous scroll offset (not discrete positions)\n * - Renders tab slots at fixed positions, content determined by scroll offset\n * - Clones tabs at boundaries for seamless looping\n * - Each slot has stable key (by position), preventing remount jumps\n */\nimport * as React from \"react\";\nimport type { SwipeInputState, GestureAxis } from \"../../hooks/gesture/types.js\";\n\n/**\n * Props passed to the indicator render function.\n * Use these to position a sliding indicator (iOS-style).\n */\nexport type IndicatorRenderProps = {\n /** Current offset in pixels (includes swipe displacement and animation) */\n offsetPx: number;\n /** Width of each tab */\n tabWidth: number;\n /** Center X position where active tab is located */\n centerX: number;\n /** Whether currently swiping */\n isSwiping: boolean;\n /** Whether animation is in progress */\n isAnimating: boolean;\n};\n\nexport type SwipePivotTabBarProps<TId extends string = string> = {\n /** Tab items to render */\n items: ReadonlyArray<{ id: TId; label?: string }>;\n /** Currently active tab ID */\n activeId: TId;\n /** Index of active tab */\n activeIndex: number;\n /** Total number of items */\n itemCount: number;\n /** Current swipe input state */\n inputState: SwipeInputState;\n /** Width of each tab */\n tabWidth: number;\n /** Width of the visible viewport */\n viewportWidth: number;\n /** Navigation mode */\n navigationMode?: \"linear\" | \"loop\";\n /** Axis for swipe (horizontal or vertical) */\n axis?: GestureAxis;\n /** Render function for each tab */\n renderTab: (item: { id: TId; label?: string }, isActive: boolean, index: number) => React.ReactNode;\n /** Animation duration in ms */\n animationDuration?: number;\n /**\n * When true, tabs stay at fixed positions and only the indicator moves.\n * Use this for iOS segmented control style where the \"window\" slides over fixed tabs.\n * @default false\n */\n fixedTabs?: boolean;\n /**\n * Optional render function for a sliding indicator (iOS-style).\n * The indicator follows the active tab position during swipe and animation.\n * Rendered behind the tabs.\n *\n * When used with fixedTabs=true, only the indicator moves while tabs stay fixed.\n *\n * @example\n * ```tsx\n * renderIndicator={({ offsetPx, tabWidth, centerX }) => (\n * <div\n * style={{\n * position: 'absolute',\n * left: centerX,\n * bottom: 0,\n * width: tabWidth,\n * height: 3,\n * backgroundColor: '#007AFF',\n * transform: `translateX(${offsetPx}px)`,\n * }}\n * />\n * )}\n * ```\n */\n renderIndicator?: (props: IndicatorRenderProps) => React.ReactNode;\n};\n\nconst DEFAULT_ANIMATION_DURATION = 300;\n\n/** Get displacement value for the given axis from input state */\nconst getAxisDisplacement = (inputState: SwipeInputState, axis: GestureAxis): number => {\n if (inputState.phase === \"idle\") {\n return 0;\n }\n return axis === \"horizontal\" ? inputState.displacement.x : inputState.displacement.y;\n};\n\n/**\n * Normalize index to valid range [0, count)\n */\nconst normalizeIndex = (index: number, count: number): number => {\n return ((index % count) + count) % count;\n};\n\n/**\n * Calculate which item should appear at a given slot position.\n * For loop mode, wraps around using modulo.\n * For linear mode, returns null if out of range.\n */\nconst getItemAtPosition = (\n slotPosition: number,\n activeIndex: number,\n itemCount: number,\n navigationMode: \"linear\" | \"loop\",\n): number | null => {\n const targetIndex = activeIndex + slotPosition;\n\n const isOutOfRange = (index: number, count: number): boolean => {\n if (index < 0) {\n return true;\n }\n if (index >= count) {\n return true;\n }\n return false;\n };\n\n if (navigationMode === \"linear\") {\n if (isOutOfRange(targetIndex, itemCount)) {\n return null;\n }\n return targetIndex;\n }\n\n // Loop mode: wrap around\n return normalizeIndex(targetIndex, itemCount);\n};\n\ntype TabSlotProps<TId extends string> = {\n slotPosition: number;\n item: { id: TId; label?: string };\n itemIndex: number;\n isActive: boolean;\n centerX: number;\n tabWidth: number;\n viewportWidth: number;\n offsetPx: number;\n axis: GestureAxis;\n renderTab: (item: { id: TId; label?: string }, isActive: boolean, index: number) => React.ReactNode;\n};\n\n/**\n * Tab slot component - renders a tab at a fixed slot position.\n * The slot position is stable; only the content changes based on scroll offset.\n */\nconst TabSlot = React.memo(<TId extends string>({\n slotPosition,\n item,\n itemIndex,\n isActive,\n centerX,\n tabWidth,\n viewportWidth,\n offsetPx,\n axis,\n renderTab,\n}: TabSlotProps<TId>) => {\n // Calculate visual position: centerX + slotPosition * tabWidth + offsetPx\n const basePx = slotPosition * tabWidth;\n const visualPx = centerX + basePx + offsetPx;\n\n // Check if visible in viewport\n const visible = visualPx + tabWidth > 0 && visualPx < viewportWidth;\n\n const transformFn = axis === \"horizontal\" ? \"translateX\" : \"translateY\";\n\n return (\n <div\n data-pivot-tab={item.id}\n data-slot={slotPosition}\n data-active={isActive ? \"true\" : \"false\"}\n style={{\n position: \"absolute\",\n left: centerX,\n top: 0,\n width: tabWidth,\n height: \"100%\",\n visibility: visible ? \"visible\" : \"hidden\",\n willChange: \"transform\",\n transform: `${transformFn}(${basePx + offsetPx}px)`,\n }}\n >\n {renderTab(item, isActive, itemIndex)}\n </div>\n );\n}) as <TId extends string>(props: TabSlotProps<TId>) => React.ReactElement;\n\n/**\n * Easing function for smooth animation\n */\nconst easeOutExpo = (t: number): number => {\n return t === 1 ? 1 : 1 - Math.pow(2, -10 * t);\n};\n\n/**\n * Swipeable tab bar for pivot navigation.\n */\nexport function SwipePivotTabBar<TId extends string = string>({\n items,\n activeId,\n activeIndex,\n itemCount,\n inputState,\n tabWidth,\n viewportWidth,\n navigationMode = \"linear\",\n axis = \"horizontal\",\n renderTab,\n animationDuration = DEFAULT_ANIMATION_DURATION,\n fixedTabs = false,\n renderIndicator,\n}: SwipePivotTabBarProps<TId>): React.ReactElement {\n const isSwipePhase = (phase: SwipeInputState[\"phase\"]): boolean => {\n if (phase === \"swiping\") {\n return true;\n }\n if (phase === \"tracking\") {\n return true;\n }\n return false;\n };\n\n const getIsAnimating = (\n slotAnimation: typeof animationRef.current,\n fixedAnimation: typeof fixedAnimationRef.current,\n ): boolean => {\n if (slotAnimation !== null) {\n return true;\n }\n if (fixedAnimation !== null) {\n return true;\n }\n return false;\n };\n\n const getDelta = (\n mode: \"linear\" | \"loop\",\n nextIndex: number,\n previousIndex: number,\n totalItems: number,\n ): number => {\n if (mode === \"loop\") {\n // Use shortest path in loop mode\n const forwardDist = normalizeIndex(nextIndex - previousIndex, totalItems);\n const backwardDist = totalItems - forwardDist;\n if (forwardDist <= backwardDist) {\n return forwardDist;\n }\n return -backwardDist;\n }\n return nextIndex - previousIndex;\n };\n\n const displacement = getAxisDisplacement(inputState, axis);\n const isSwiping = isSwipePhase(inputState.phase);\n\n // ============================================================\n // Animation state for SLOT-BASED mode (scrolling tabs)\n // ============================================================\n const [animatedOffset, setAnimatedOffset] = React.useState(0);\n const animationRef = React.useRef<{\n startTime: number;\n startOffset: number;\n targetOffset: number;\n rafId: number;\n } | null>(null);\n\n // ============================================================\n // Animation state for FIXED TABS mode (iOS-style indicator)\n // Tracks the actual indicator position in pixels\n // ============================================================\n const [indicatorPosition, setIndicatorPosition] = React.useState(activeIndex * tabWidth);\n const indicatorPositionRef = React.useRef(activeIndex * tabWidth); // Track current position for animation start\n const fixedAnimationRef = React.useRef<{\n rafId: number;\n } | null>(null);\n const lastSwipePositionRef = React.useRef<number | null>(null);\n\n // Keep ref in sync with state\n React.useEffect(() => {\n indicatorPositionRef.current = indicatorPosition;\n }, [indicatorPosition]);\n\n const prevActiveIndexRef = React.useRef(activeIndex);\n\n // Calculate the range of slot positions to render\n const halfRange = Math.ceil(viewportWidth / tabWidth / 2) + 1;\n\n // Center position for active tab (for slot-based mode)\n const centerX = (viewportWidth - tabWidth) / 2;\n\n // ============================================================\n // Fixed tabs mode: track swipe position\n // ============================================================\n React.useEffect(() => {\n if (!fixedTabs) {\n return;\n }\n if (!isSwiping) {\n return;\n }\n\n // During swipe, track the visual position\n // Swipe direction is OPPOSITE to indicator movement\n const visualPosition = activeIndex * tabWidth - displacement;\n lastSwipePositionRef.current = visualPosition;\n setIndicatorPosition(visualPosition);\n }, [fixedTabs, isSwiping, activeIndex, tabWidth, displacement]);\n\n // ============================================================\n // Fixed tabs mode: animate when swipe ends or tab clicked\n // ============================================================\n React.useEffect(() => {\n if (!fixedTabs) {\n return;\n }\n if (isSwiping) {\n return;\n }\n\n // When swipe ends or tab changes via click\n const targetPosition = activeIndex * tabWidth;\n const startPosition = lastSwipePositionRef.current ?? indicatorPositionRef.current;\n lastSwipePositionRef.current = null;\n\n // Already at target\n if (Math.abs(startPosition - targetPosition) < 1) {\n setIndicatorPosition(targetPosition);\n return;\n }\n\n // Cancel existing animation\n if (fixedAnimationRef.current) {\n cancelAnimationFrame(fixedAnimationRef.current.rafId);\n }\n\n // Animate from current position to target\n const startTime = performance.now();\n const animationStartPosition = startPosition; // Capture for closure\n\n const animate = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / animationDuration, 1);\n const easedProgress = easeOutExpo(progress);\n\n const currentPosition = animationStartPosition + (targetPosition - animationStartPosition) * easedProgress;\n setIndicatorPosition(currentPosition);\n\n if (progress < 1) {\n fixedAnimationRef.current = {\n rafId: requestAnimationFrame(animate),\n };\n } else {\n fixedAnimationRef.current = null;\n setIndicatorPosition(targetPosition);\n }\n };\n\n fixedAnimationRef.current = {\n rafId: requestAnimationFrame(animate),\n };\n }, [fixedTabs, isSwiping, activeIndex, tabWidth, animationDuration]);\n\n // ============================================================\n // Slot-based mode animation: handle activeIndex changes\n // ============================================================\n React.useEffect(() => {\n if (fixedTabs) {\n return; // Skip for fixed tabs mode\n }\n\n if (prevActiveIndexRef.current === activeIndex) {\n return;\n }\n\n const prevIndex = prevActiveIndexRef.current;\n prevActiveIndexRef.current = activeIndex;\n\n // Calculate direction of movement\n const delta = getDelta(navigationMode, activeIndex, prevIndex, itemCount);\n\n // Target offset to animate to (then snap to 0)\n const targetOffsetPx = -delta * tabWidth;\n\n // Start from current visual position\n const startOffset = isSwiping ? displacement : animatedOffset;\n\n // Cancel any existing animation\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current.rafId);\n }\n\n // If already at target (within threshold), snap immediately\n if (Math.abs(startOffset - targetOffsetPx) < 1) {\n setAnimatedOffset(0);\n return;\n }\n\n // Start animation\n const startTime = performance.now();\n\n const animate = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / animationDuration, 1);\n const easedProgress = easeOutExpo(progress);\n\n // Interpolate from startOffset toward targetOffset, but we want to end at 0\n // So we animate: startOffset → targetOffset, but since we're rendering with\n // the NEW activeIndex, we need to compensate\n //\n // When activeIndex changes, slots now show different content.\n // We animate the offset from (startOffset - targetOffset) back to 0.\n const compensatedStart = startOffset - targetOffsetPx;\n const currentOffset = compensatedStart * (1 - easedProgress);\n\n setAnimatedOffset(currentOffset);\n\n if (progress < 1) {\n animationRef.current = {\n startTime,\n startOffset: compensatedStart,\n targetOffset: 0,\n rafId: requestAnimationFrame(animate),\n };\n } else {\n animationRef.current = null;\n setAnimatedOffset(0);\n }\n };\n\n animationRef.current = {\n startTime,\n startOffset: startOffset - targetOffsetPx,\n targetOffset: 0,\n rafId: requestAnimationFrame(animate),\n };\n }, [fixedTabs, activeIndex, itemCount, tabWidth, animationDuration, navigationMode, displacement, isSwiping, animatedOffset]);\n\n // Update prevActiveIndexRef for fixed tabs mode too\n React.useEffect(() => {\n if (fixedTabs) {\n prevActiveIndexRef.current = activeIndex;\n }\n }, [fixedTabs, activeIndex]);\n\n // Cleanup animation on unmount\n React.useEffect(() => {\n return () => {\n if (animationRef.current) {\n cancelAnimationFrame(animationRef.current.rafId);\n }\n if (fixedAnimationRef.current) {\n cancelAnimationFrame(fixedAnimationRef.current.rafId);\n }\n };\n }, []);\n\n // Current offset for slot-based mode\n const currentOffset = isSwiping ? displacement : animatedOffset;\n const isAnimating = getIsAnimating(animationRef.current, fixedAnimationRef.current);\n\n // Cancel slot animation when swiping starts\n React.useEffect(() => {\n if (isSwiping && animationRef.current) {\n cancelAnimationFrame(animationRef.current.rafId);\n animationRef.current = null;\n setAnimatedOffset(0);\n }\n }, [isSwiping]);\n\n // ============================================================\n // Fixed tabs mode: render fixed tabs with sliding indicator\n // ============================================================\n if (fixedTabs) {\n // Calculate total width and centering offset\n const totalTabsWidth = items.length * tabWidth;\n const centeringOffset = (viewportWidth - totalTabsWidth) / 2;\n\n return (\n <div\n data-active-id={activeId}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n display: \"flex\",\n justifyContent: \"center\",\n }}\n >\n {/* Sliding indicator (rendered behind tabs) */}\n {renderIndicator?.({\n offsetPx: indicatorPosition,\n tabWidth,\n centerX: centeringOffset,\n isSwiping,\n isAnimating,\n })}\n\n {/* Fixed tabs - each tab at its natural position */}\n {items.map((item, index) => (\n <div\n key={item.id}\n data-pivot-tab={item.id}\n data-active={index === activeIndex ? \"true\" : \"false\"}\n style={{\n position: \"relative\",\n width: tabWidth,\n height: \"100%\",\n flexShrink: 0,\n }}\n >\n {renderTab(item, index === activeIndex, index)}\n </div>\n ))}\n </div>\n );\n }\n\n // Slot-based rendering for scrolling tabs (infinite loop support)\n const slots: Array<{\n slotPosition: number;\n itemIndex: number;\n item: { id: TId; label?: string };\n }> = [];\n\n for (let pos = -halfRange; pos <= halfRange; pos++) {\n const itemIndex = getItemAtPosition(pos, activeIndex, itemCount, navigationMode);\n if (itemIndex !== null) {\n slots.push({\n slotPosition: pos,\n itemIndex,\n item: items[itemIndex],\n });\n }\n }\n\n return (\n <div\n data-active-id={activeId}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n }}\n >\n {/* Sliding indicator (rendered behind tabs) */}\n {renderIndicator?.({\n offsetPx: currentOffset,\n tabWidth,\n centerX,\n isSwiping,\n isAnimating,\n })}\n\n {/* Tab slots */}\n {slots.map(({ slotPosition, itemIndex, item }) => (\n <TabSlot\n key={slotPosition}\n slotPosition={slotPosition}\n item={item}\n itemIndex={itemIndex}\n isActive={itemIndex === activeIndex}\n centerX={centerX}\n tabWidth={tabWidth}\n viewportWidth={viewportWidth}\n offsetPx={currentOffset}\n axis={axis}\n renderTab={renderTab}\n />\n ))}\n </div>\n );\n}\n"],"names":["DEFAULT_ANIMATION_TIMEOUT","useAnimatedVisibility","isVisible","leaveAnimation","skipAnimation","animationTimeout","prevVisibleRef","React","isAnimatingOut","setIsAnimatingOut","timeoutRef","shouldSkipLeaveAnimation","isSkipped","animation","getShouldDisplay","visible","animatingOut","wasVisible","handleAnimationEnd","e","shouldDisplay","baseStyle","PivotContent","id","isActive","transitionMode","children","visibility","PIVOT_ANIMATION_LEAVE","style","s","PIVOT_ANIMATION_ENTER","content","jsx","PivotOutletContext","PivotOutletInner","ctx","forceUpdate","x","items","activeId","Fragment","item","usePivot","options","controlledActiveId","defaultActiveId","onActiveChange","navigationMode","isControlled","uncontrolledActiveId","setUncontrolledActiveId","firstEnabled","isAnimating","setIsAnimating","setActiveId","target","shouldAnimate","endAnimation","enabledItems","activeIndex","index","itemCount","canGo","direction","targetIndex","computeTargetIndex","rawIndex","go","targetItem","getVirtualPosition","itemIndex","rawOffset","forwardDist","getItemPosition","backwardDist","getItemProps","containerStyle","stateRef","subscribersRef","callback","resolveContent","itemId","i","validIds","getCachedContent","useContentCache","contextValue","Outlet","OutletComponent","DEFAULT_ANIMATION_DURATION","getAxisDisplacement","inputState","axis","normalizeIndex","count","getItemAtPosition","slotPosition","isOutOfRange","TabSlot","centerX","tabWidth","viewportWidth","offsetPx","renderTab","basePx","visualPx","transformFn","easeOutExpo","t","SwipePivotTabBar","animationDuration","fixedTabs","renderIndicator","isSwipePhase","phase","getIsAnimating","slotAnimation","fixedAnimation","getDelta","mode","nextIndex","previousIndex","totalItems","displacement","isSwiping","animatedOffset","setAnimatedOffset","animationRef","indicatorPosition","setIndicatorPosition","indicatorPositionRef","fixedAnimationRef","lastSwipePositionRef","prevActiveIndexRef","halfRange","visualPosition","targetPosition","startPosition","startTime","animationStartPosition","animate","currentTime","elapsed","progress","easedProgress","currentPosition","prevIndex","targetOffsetPx","startOffset","compensatedStart","currentOffset","totalTabsWidth","centeringOffset","jsxs","slots","pos"],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @file SwipeSafeZone component
3
+ *
4
+ * A wrapper component that marks an area as exempt from swipe gesture detection.
5
+ * Content inside this zone will not trigger swipe-to-close or other swipe gestures.
6
+ *
7
+ * Use this for:
8
+ * - Scrollable content areas
9
+ * - Input fields and text areas
10
+ * - Interactive elements that need drag/swipe for their own purposes
11
+ */
12
+ import * as React from "react";
13
+ /**
14
+ * Data attribute used to identify swipe-safe zones.
15
+ * Swipe gesture handlers should check for this attribute on target elements.
16
+ */
17
+ export declare const SWIPE_SAFE_ZONE_ATTR = "data-swipe-safe-zone";
18
+ export type SwipeSafeZoneProps = {
19
+ /** Content to render inside the safe zone */
20
+ children: React.ReactNode;
21
+ /** Additional CSS class name */
22
+ className?: string;
23
+ /** Additional inline styles */
24
+ style?: React.CSSProperties;
25
+ };
26
+ /**
27
+ * SwipeSafeZone marks an area where swipe gestures should not be triggered.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * <SwipeSafeZone>
32
+ * <ScrollableList items={items} />
33
+ * </SwipeSafeZone>
34
+ * ```
35
+ */
36
+ export declare const SwipeSafeZone: React.FC<SwipeSafeZoneProps>;
37
+ /**
38
+ * Check if an element is inside a SwipeSafeZone.
39
+ */
40
+ export declare function isInSwipeSafeZone(element: HTMLElement, container: HTMLElement): boolean;
@@ -2,14 +2,16 @@
2
2
  * @file Drawer component
3
3
  *
4
4
  * Mobile-friendly slide-in panel with backdrop support.
5
+ * Supports swipe gestures for opening/closing.
5
6
  */
6
7
  import * as React from "react";
7
- import type { DrawerBehavior, WindowPosition } from "../../types";
8
+ import type { DrawerBehavior, WindowPosition } from "../../types.js";
8
9
  export type DrawerProps = {
9
10
  id: string;
10
11
  config: DrawerBehavior;
11
12
  isOpen: boolean;
12
13
  onClose: () => void;
14
+ onOpen?: () => void;
13
15
  children: React.ReactNode;
14
16
  zIndex?: number;
15
17
  width?: string | number;
@@ -2,7 +2,7 @@
2
2
  * @file DrawerLayers component
3
3
  */
4
4
  import * as React from "react";
5
- import type { LayerDefinition } from "../../types";
5
+ import type { LayerDefinition } from "../../types.js";
6
6
  export type DrawerLayersProps = {
7
7
  layers: LayerDefinition[];
8
8
  };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @file Drawer style computation utilities.
3
+ *
4
+ * Provides pure functions for computing drawer styles based on configuration.
5
+ */
6
+ import type * as React from "react";
7
+ import type { DrawerBehavior } from "../../types.js";
8
+ /**
9
+ * Base backdrop style shared by backdrop and edge zone.
10
+ */
11
+ export declare const DRAWER_BACKDROP_BASE_STYLE: React.CSSProperties;
12
+ /**
13
+ * Base drawer panel style.
14
+ */
15
+ export declare const DRAWER_PANEL_BASE_STYLE: React.CSSProperties;
16
+ /**
17
+ * Placement-specific styles for drawer positioning.
18
+ */
19
+ export type DrawerPlacement = "left" | "right" | "top" | "bottom";
20
+ /**
21
+ * Get placement-specific style.
22
+ */
23
+ export declare function getPlacementStyle(placement: DrawerPlacement): React.CSSProperties;
24
+ /**
25
+ * Get transform value for open state.
26
+ */
27
+ export declare function getOpenTransform(placement: DrawerPlacement): string;
28
+ /**
29
+ * Get closed transform value.
30
+ */
31
+ export declare function getClosedTransform(placement: DrawerPlacement): string;
32
+ /**
33
+ * Compute CSS transition value.
34
+ */
35
+ export declare function computeTransitionValue(mode: DrawerBehavior["transitionMode"] | undefined, duration: DrawerBehavior["transitionDuration"], easing: DrawerBehavior["transitionEasing"]): string | undefined;
36
+ /**
37
+ * Compute backdrop transition value.
38
+ */
39
+ export declare function computeBackdropTransition(mode: DrawerBehavior["transitionMode"] | undefined, duration: DrawerBehavior["transitionDuration"]): string | undefined;
40
+ /**
41
+ * Check if placement is horizontal (left/right).
42
+ */
43
+ export declare function isHorizontalPlacement(placement: DrawerPlacement): boolean;
44
+ /**
45
+ * Format dimension value to CSS string.
46
+ */
47
+ export declare function formatDimension(value: string | number | undefined): string | undefined;
48
+ /**
49
+ * Options for computing edge zone style.
50
+ */
51
+ export type EdgeZoneStyleOptions = {
52
+ placement: DrawerPlacement;
53
+ inline: boolean;
54
+ edgeWidth: number;
55
+ zIndex: number | undefined;
56
+ };
57
+ /**
58
+ * Compute edge zone style based on placement and positioning context.
59
+ *
60
+ * The edge zone is positioned at the edge where the drawer appears from:
61
+ * - left drawer: left edge of the container
62
+ * - right drawer: right edge of the container
63
+ * - top drawer: top edge of the container
64
+ * - bottom drawer: bottom edge of the container
65
+ *
66
+ * When inline=true, uses absolute positioning (relative to parent container).
67
+ * When inline=false, uses fixed positioning (relative to viewport).
68
+ */
69
+ export declare function computeEdgeZoneStyle(options: EdgeZoneStyleOptions): React.CSSProperties;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @file Drawer swipe gesture configuration parsing.
3
+ *
4
+ * Normalizes swipeGestures config from DrawerBehavior into a consistent shape.
5
+ */
6
+ import type { DrawerBehavior, WindowPosition } from "../../types.js";
7
+ import type { DrawerPlacement } from "./drawerStyles.js";
8
+ /**
9
+ * Normalized swipe gesture configuration.
10
+ */
11
+ export type NormalizedSwipeConfig = {
12
+ enabled: boolean;
13
+ edgeSwipeOpen: boolean;
14
+ swipeClose: boolean;
15
+ edgeWidth: number;
16
+ dismissThreshold: number;
17
+ };
18
+ /**
19
+ * Parse swipeGestures config into normalized options.
20
+ */
21
+ export declare function parseSwipeGesturesConfig(swipeGestures: DrawerBehavior["swipeGestures"]): NormalizedSwipeConfig;
22
+ /**
23
+ * Resolve drawer placement from anchor and position.
24
+ */
25
+ export declare function resolvePlacement(anchor: DrawerBehavior["anchor"], position: WindowPosition | undefined): DrawerPlacement;
26
+ /**
27
+ * Determine if edge zone should be visible.
28
+ */
29
+ export declare function shouldShowEdgeZone(config: NormalizedSwipeConfig, isOpen: boolean, isOpening: boolean): boolean;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @file Hook for applying real-time transform during drawer swipe gestures.
3
+ *
4
+ * Handles DOM manipulation for smooth swipe animations.
5
+ */
6
+ import * as React from "react";
7
+ import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
8
+ import type { DrawerSwipeDirection } from "../../modules/drawer/types.js";
9
+ type UseDrawerSwipeTransformOptions = {
10
+ drawerRef: React.RefObject<HTMLDivElement | null>;
11
+ backdropRef: React.RefObject<HTMLDivElement | null>;
12
+ placement: DrawerSwipeDirection;
13
+ swipeState: ContinuousOperationState;
14
+ displacement: number;
15
+ isOpening: boolean;
16
+ isClosing: boolean;
17
+ enabled: boolean;
18
+ };
19
+ /**
20
+ * Apply real-time transform to drawer and backdrop during swipe.
21
+ */
22
+ export declare function useDrawerSwipeTransform(options: UseDrawerSwipeTransformOptions): void;
23
+ export {};
package/dist/config.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("react/jsx-runtime"),v=require("react"),b=require("./GridLayout-B4VRsC0r.cjs");require("./FloatingPanelFrame-CEmXDvUA.cjs");require("./FloatingWindow-TCDNY5gE.cjs");function E(r){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const n in r)if(n!=="default"){const i=Object.getOwnPropertyDescriptor(r,n);Object.defineProperty(t,n,i.get?i:{enumerable:!0,get:()=>r[n]})}}return t.default=r,Object.freeze(t)}const s=E(v),C=r=>{const t=z(r);if(t==="grid"&&!r.area)throw new Error(`PanelRoute ${r.id} must specify 'area' for grid placement.`);return{id:r.id,component:r.element,visible:r.visible,gridArea:r.area,positionMode:t,position:r.position,zIndex:r.zIndex,width:r.width,height:r.height,pointerEvents:r.pointerEvents,style:r.style,drawer:r.drawer,floating:r.floating,pivot:r.pivot,backdropStyle:r.backdropStyle}},z=r=>r.positionMode?r.positionMode:r.floating?"absolute":(r.drawer,"grid"),P=r=>{const t=[],n=i=>{t.push(i),i.children&&i.children.forEach(e=>n(e))};return r.forEach(i=>n(i)),t},I=r=>{const t=new Set;r.forEach(n=>{if(t.has(n.id))throw new Error(`Duplicate PanelRoute id detected: ${n.id}`);t.add(n.id)})},A=r=>{const t=P(r);return I(t),t.map(n=>C(n))},p=()=>null,x=()=>null,a=(r,t)=>s.isValidElement(r)?r.type===t:!1,d=r=>{const t=[],n=i=>{if(!(i==null||typeof i=="boolean")){if(Array.isArray(i)){i.forEach(n);return}if(a(i,p)){const e=i.props;if(!e.id)throw new Error("<Panel> requires an 'id' prop.");if(e.type==="grid"){if(!e.area)throw new Error(`<Panel id="${e.id}"> requires an explicit 'area' prop when type="grid".`);t.push({id:e.id,area:e.area,element:e.children??null,visible:e.visible,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style});return}if(e.type==="floating"){if(!e.position)throw new Error(`<Panel id="${e.id}"> requires a 'position' prop when type="floating".`);if(e.width===void 0||e.height===void 0)throw new Error(`<Panel id="${e.id}"> requires 'width' and 'height' when type="floating".`);t.push({id:e.id,element:e.children??null,visible:e.visible??!0,positionMode:"absolute",position:e.position,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style,floating:{mode:"embedded",draggable:e.draggable,resizable:e.resizable}});return}if(e.type==="drawer"){t.push({id:e.id,element:e.children??null,visible:e.visible??!0,positionMode:"relative",position:e.position,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style,drawer:e.drawer,backdropStyle:e.backdropStyle});return}if(e.type==="pivot"){if(!e.area)throw new Error(`<Panel id="${e.id}"> requires an explicit 'area' prop when type="pivot".`);const o=$(e.children);if(o.length===0)throw new Error(`<Panel id="${e.id}"> requires at least one <PivotItem> child when type="pivot".`);t.push({id:e.id,area:e.area,element:null,visible:e.visible,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style,pivot:{items:o,activeId:e.activeId,defaultActiveId:e.defaultActiveId,onActiveChange:e.onActiveChange}});return}throw new Error("<Panel> has unsupported type.")}if(s.isValidElement(i)){if(i.type===s.Fragment){n(i.props.children);return}return}}};return n(r),t},R=({config:r,style:t,children:n})=>{const i=s.useMemo(()=>d(n),[n]),e=s.useMemo(()=>A(i),[i]),o=s.useMemo(()=>{if(r)return r;const l=m(n);if(!l)throw new Error("Panel requires either 'config' prop or a JSX config (<Config><Rows/><Columns/><Areas/></Config>). ");return l},[n,r]);return y.jsx(b.GridLayout,{config:o,layers:e,style:t})},h=()=>null,q=()=>null,S=()=>null,f=()=>null,w=()=>null,g=()=>null,u=(r,t)=>{const n=[],i=e=>{if(!(e==null||typeof e=="boolean")){if(Array.isArray(e)){e.forEach(i);return}if(a(e,t)){const o=e.props;if(!o.size)throw new Error("Row/Col requires 'size' property.");n.push({size:o.size,resizable:o.resizable,minSize:o.minSize,maxSize:o.maxSize});return}s.isValidElement(e)&&i(e.props.children)}};return i(r),n},M=r=>{const t=c(r,h);if(!t)return null;const n=t.props,i=u(t.props.children,f),e=u(t.props.children,w),o=c(t.props.children,g),l=o?o.props.matrix:void 0;return{gap:n.gap,style:n.style,rows:i,columns:e,areas:l}},c=(r,t)=>{const n=i=>{if(i==null||typeof i=="boolean")return null;if(Array.isArray(i)){for(const e of i){const o=n(e);if(o)return o}return null}return a(i,t)?i:s.isValidElement(i)?n(i.props.children):null};return n(r)},$=r=>{const t=[],n=i=>{if(!(i==null||typeof i=="boolean")){if(Array.isArray(i)){i.forEach(n);return}if(a(i,x)){const e=i.props;if(!e.id)throw new Error("<PivotItem> requires an 'id' prop.");t.push({id:e.id,label:e.label,content:e.children??null,disabled:e.disabled});return}s.isValidElement(i)&&i.type===s.Fragment&&n(i.props.children)}};return n(r),t},m=r=>{const t=M(r);if(!t)return null;if(!t.rows||t.rows.length===0)throw new Error("Config must include at least one <Row size=...> inside <Config>.");if(!t.columns||t.columns.length===0)throw new Error("Config must include at least one <Col size=...> inside <Config>.");if(!t.areas||t.areas.length===0)throw new Error("Config must include <Areas matrix={...}> inside <Config>.");const n=t.areas.length,i=t.areas[0]?.length??0;if(n!==t.rows.length)throw new Error(`Areas row count (${n}) must match Rows count (${t.rows.length}).`);if(i!==t.columns.length)throw new Error(`Areas column count (${i}) must match Columns count (${t.columns.length}).`);return{areas:t.areas,rows:t.rows,columns:t.columns,gap:t.gap,style:t.style}};exports.Areas=g;exports.Col=w;exports.Columns=S;exports.Config=h;exports.Panel=p;exports.PanelLayout=R;exports.Row=f;exports.Rows=q;exports.buildConfigFromChildren=m;exports.buildRoutesFromChildren=d;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const y=require("react/jsx-runtime"),v=require("react"),b=require("./GridLayout-DKTg_N61.cjs");require("./FloatingPanelFrame-CEmXDvUA.cjs");require("./FloatingWindow-DMwyK0eK.cjs");function E(r){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const n in r)if(n!=="default"){const i=Object.getOwnPropertyDescriptor(r,n);Object.defineProperty(t,n,i.get?i:{enumerable:!0,get:()=>r[n]})}}return t.default=r,Object.freeze(t)}const s=E(v),C=r=>{const t=z(r);if(t==="grid"&&!r.area)throw new Error(`PanelRoute ${r.id} must specify 'area' for grid placement.`);return{id:r.id,component:r.element,visible:r.visible,gridArea:r.area,positionMode:t,position:r.position,zIndex:r.zIndex,width:r.width,height:r.height,pointerEvents:r.pointerEvents,style:r.style,drawer:r.drawer,floating:r.floating,pivot:r.pivot,backdropStyle:r.backdropStyle}},z=r=>r.positionMode?r.positionMode:r.floating?"absolute":(r.drawer,"grid"),P=r=>{const t=[],n=i=>{t.push(i),i.children&&i.children.forEach(e=>n(e))};return r.forEach(i=>n(i)),t},I=r=>{const t=new Set;r.forEach(n=>{if(t.has(n.id))throw new Error(`Duplicate PanelRoute id detected: ${n.id}`);t.add(n.id)})},A=r=>{const t=P(r);return I(t),t.map(n=>C(n))},p=()=>null,x=()=>null,a=(r,t)=>s.isValidElement(r)?r.type===t:!1,d=r=>{const t=[],n=i=>{if(!(i==null||typeof i=="boolean")){if(Array.isArray(i)){i.forEach(n);return}if(a(i,p)){const e=i.props;if(!e.id)throw new Error("<Panel> requires an 'id' prop.");if(e.type==="grid"){if(!e.area)throw new Error(`<Panel id="${e.id}"> requires an explicit 'area' prop when type="grid".`);t.push({id:e.id,area:e.area,element:e.children??null,visible:e.visible,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style});return}if(e.type==="floating"){if(!e.position)throw new Error(`<Panel id="${e.id}"> requires a 'position' prop when type="floating".`);if(e.width===void 0||e.height===void 0)throw new Error(`<Panel id="${e.id}"> requires 'width' and 'height' when type="floating".`);t.push({id:e.id,element:e.children??null,visible:e.visible??!0,positionMode:"absolute",position:e.position,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style,floating:{mode:"embedded",draggable:e.draggable,resizable:e.resizable}});return}if(e.type==="drawer"){t.push({id:e.id,element:e.children??null,visible:e.visible??!0,positionMode:"relative",position:e.position,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style,drawer:e.drawer,backdropStyle:e.backdropStyle});return}if(e.type==="pivot"){if(!e.area)throw new Error(`<Panel id="${e.id}"> requires an explicit 'area' prop when type="pivot".`);const o=$(e.children);if(o.length===0)throw new Error(`<Panel id="${e.id}"> requires at least one <PivotItem> child when type="pivot".`);t.push({id:e.id,area:e.area,element:null,visible:e.visible,zIndex:e.zIndex,width:e.width,height:e.height,pointerEvents:e.pointerEvents,style:e.style,pivot:{items:o,activeId:e.activeId,defaultActiveId:e.defaultActiveId,onActiveChange:e.onActiveChange}});return}throw new Error("<Panel> has unsupported type.")}if(s.isValidElement(i)){if(i.type===s.Fragment){n(i.props.children);return}return}}};return n(r),t},R=({config:r,style:t,children:n})=>{const i=s.useMemo(()=>d(n),[n]),e=s.useMemo(()=>A(i),[i]),o=s.useMemo(()=>{if(r)return r;const l=m(n);if(!l)throw new Error("Panel requires either 'config' prop or a JSX config (<Config><Rows/><Columns/><Areas/></Config>). ");return l},[n,r]);return y.jsx(b.GridLayout,{config:o,layers:e,style:t})},h=()=>null,q=()=>null,S=()=>null,f=()=>null,w=()=>null,g=()=>null,u=(r,t)=>{const n=[],i=e=>{if(!(e==null||typeof e=="boolean")){if(Array.isArray(e)){e.forEach(i);return}if(a(e,t)){const o=e.props;if(!o.size)throw new Error("Row/Col requires 'size' property.");n.push({size:o.size,resizable:o.resizable,minSize:o.minSize,maxSize:o.maxSize});return}s.isValidElement(e)&&i(e.props.children)}};return i(r),n},M=r=>{const t=c(r,h);if(!t)return null;const n=t.props,i=u(t.props.children,f),e=u(t.props.children,w),o=c(t.props.children,g),l=o?o.props.matrix:void 0;return{gap:n.gap,style:n.style,rows:i,columns:e,areas:l}},c=(r,t)=>{const n=i=>{if(i==null||typeof i=="boolean")return null;if(Array.isArray(i)){for(const e of i){const o=n(e);if(o)return o}return null}return a(i,t)?i:s.isValidElement(i)?n(i.props.children):null};return n(r)},$=r=>{const t=[],n=i=>{if(!(i==null||typeof i=="boolean")){if(Array.isArray(i)){i.forEach(n);return}if(a(i,x)){const e=i.props;if(!e.id)throw new Error("<PivotItem> requires an 'id' prop.");t.push({id:e.id,label:e.label,content:e.children??null,disabled:e.disabled});return}s.isValidElement(i)&&i.type===s.Fragment&&n(i.props.children)}};return n(r),t},m=r=>{const t=M(r);if(!t)return null;if(!t.rows||t.rows.length===0)throw new Error("Config must include at least one <Row size=...> inside <Config>.");if(!t.columns||t.columns.length===0)throw new Error("Config must include at least one <Col size=...> inside <Config>.");if(!t.areas||t.areas.length===0)throw new Error("Config must include <Areas matrix={...}> inside <Config>.");const n=t.areas.length,i=t.areas[0]?.length??0;if(n!==t.rows.length)throw new Error(`Areas row count (${n}) must match Rows count (${t.rows.length}).`);if(i!==t.columns.length)throw new Error(`Areas column count (${i}) must match Columns count (${t.columns.length}).`);return{areas:t.areas,rows:t.rows,columns:t.columns,gap:t.gap,style:t.style}};exports.Areas=g;exports.Col=w;exports.Columns=S;exports.Config=h;exports.Panel=p;exports.PanelLayout=R;exports.Row=f;exports.Rows=q;exports.buildConfigFromChildren=m;exports.buildRoutesFromChildren=d;
2
2
  //# sourceMappingURL=config.cjs.map
package/dist/config.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { jsx as u } from "react/jsx-runtime";
2
2
  import * as s from "react";
3
- import { G as d } from "./GridLayout-BltqeCPK.js";
4
- import "./FloatingPanelFrame-SgYLc6Ud.js";
5
- import "./FloatingWindow-BpdOpg_L.js";
3
+ import { G as d } from "./GridLayout-UWNxXw77.js";
4
+ import "./FloatingPanelFrame-3eU9AwPo.js";
5
+ import "./FloatingWindow-CUXnEtrb.js";
6
6
  const h = (i) => {
7
7
  const r = f(i);
8
8
  if (r === "grid" && !i.area)
@@ -209,3 +209,20 @@ export declare const SPLIT_HANDLE_THICKNESS = "var(--rpl-size-split-handle-thick
209
209
  */
210
210
  export declare const HORIZONTAL_DIVIDER_WIDTH = "var(--rpl-size-horizontal-divider-width, 4px)";
211
211
  export declare const HORIZONTAL_DIVIDER_HIT_AREA_OFFSET = "var(--rpl-space-horizontal-divider-hit-area-offset, 4px)";
212
+ /**
213
+ * Modal
214
+ */
215
+ export declare const COLOR_MODAL_BACKDROP = "var(--rpl-color-modal-backdrop, rgba(0, 0, 0, 0.5))";
216
+ export declare const MODAL_TRANSITION_DURATION = "var(--rpl-modal-transition-duration, 200ms)";
217
+ export declare const MODAL_TRANSITION_EASING = "var(--rpl-modal-transition-easing, ease-out)";
218
+ export declare const MODAL_MIN_WIDTH = "var(--rpl-modal-min-width, 280px)";
219
+ export declare const MODAL_MAX_WIDTH = "var(--rpl-modal-max-width, 90vw)";
220
+ export declare const MODAL_MAX_HEIGHT = "var(--rpl-modal-max-height, 85vh)";
221
+ /**
222
+ * Alert Dialog
223
+ */
224
+ export declare const ALERT_DIALOG_WIDTH = "var(--rpl-alert-dialog-width, 320px)";
225
+ export declare const ALERT_DIALOG_BUTTON_GAP = "var(--rpl-alert-dialog-button-gap, 8px)";
226
+ export declare const ALERT_DIALOG_ACTIONS_PADDING = "var(--rpl-alert-dialog-actions-padding, 12px)";
227
+ export declare const ALERT_DIALOG_MESSAGE_PADDING = "var(--rpl-alert-dialog-message-padding, 16px)";
228
+ export declare const ALERT_DIALOG_INPUT_MARGIN_TOP = "var(--rpl-alert-dialog-input-margin-top, 12px)";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @file Dialog entry point - Modal and alert/confirm/prompt dialogs
3
+ * @packageDocumentation
4
+ *
5
+ * This is a subpath export entry point for `react-panel-layout/dialog`.
6
+ *
7
+ * ## Overview
8
+ * Dialog provides modal dialogs and imperative alert/confirm/prompt APIs
9
+ * using native HTML dialog element for proper accessibility and top-layer rendering.
10
+ *
11
+ * ## Installation
12
+ * ```ts
13
+ * import { Modal, useDialog, DialogContainer } from "react-panel-layout/dialog";
14
+ * ```
15
+ *
16
+ * ## Modal Usage
17
+ * ```tsx
18
+ * const [isOpen, setIsOpen] = useState(false);
19
+ *
20
+ * <Modal
21
+ * visible={isOpen}
22
+ * onClose={() => setIsOpen(false)}
23
+ * header={{ title: "Settings" }}
24
+ * >
25
+ * <form>
26
+ * <input type="text" placeholder="Name" />
27
+ * <button type="submit">Save</button>
28
+ * </form>
29
+ * </Modal>
30
+ * ```
31
+ *
32
+ * ## useDialog Hook Usage
33
+ * ```tsx
34
+ * function MyComponent() {
35
+ * const { alert, confirm, prompt, Outlet } = useDialog();
36
+ *
37
+ * const handleClick = async () => {
38
+ * await alert("Hello!");
39
+ *
40
+ * const confirmed = await confirm({
41
+ * message: "Are you sure?",
42
+ * confirmLabel: "Yes",
43
+ * cancelLabel: "No",
44
+ * });
45
+ *
46
+ * if (confirmed) {
47
+ * const name = await prompt({
48
+ * message: "Enter your name:",
49
+ * defaultValue: "Anonymous",
50
+ * });
51
+ * console.log("Name:", name);
52
+ * }
53
+ * };
54
+ *
55
+ * return (
56
+ * <>
57
+ * <button onClick={handleClick}>Show dialogs</button>
58
+ * <Outlet />
59
+ * </>
60
+ * );
61
+ * }
62
+ * ```
63
+ */
64
+ export { Modal } from "../modules/dialog/Modal.js";
65
+ export { DialogContainer } from "../modules/dialog/DialogContainer.js";
66
+ export { AlertDialog } from "../modules/dialog/AlertDialog.js";
67
+ export { useDialog } from "../modules/dialog/useDialog.js";
68
+ export { useDialogContainer } from "../modules/dialog/useDialogContainer.js";
69
+ export type { ModalProps, ModalHeader, DialogContainerProps, DialogTransitionMode, AlertOptions, ConfirmOptions, PromptOptions, AlertDialogProps, UseDialogReturn, } from "../modules/dialog/types.js";
package/dist/floating.js CHANGED
@@ -1,4 +1,4 @@
1
- import { e, d as l, F as t, a as o, c as F, b as i } from "./FloatingPanelFrame-SgYLc6Ud.js";
1
+ import { e, d as l, F as t, a as o, c as F, b as i } from "./FloatingPanelFrame-3eU9AwPo.js";
2
2
  export {
3
3
  e as FloatingPanelContent,
4
4
  l as FloatingPanelControls,