react-panel-layout 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
- package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
- package/dist/FloatingWindow-Bw2djgpz.js +1542 -0
- package/dist/FloatingWindow-Bw2djgpz.js.map +1 -0
- package/dist/FloatingWindow-Cvyokf0m.cjs +2 -0
- package/dist/FloatingWindow-Cvyokf0m.cjs.map +1 -0
- package/dist/GridLayout-B4aCqSyd.js +947 -0
- package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-B4aCqSyd.js.map} +1 -1
- package/dist/GridLayout-DNOClFzz.cjs +2 -0
- package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DNOClFzz.cjs.map} +1 -1
- package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
- package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
- package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
- package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
- package/dist/PanelSystem-B8Igvnb2.cjs +3 -0
- package/dist/PanelSystem-B8Igvnb2.cjs.map +1 -0
- package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-DDUSFjXD.js} +209 -248
- package/dist/PanelSystem-DDUSFjXD.js.map +1 -0
- package/dist/ResizeHandle-CBcAS918.cjs +2 -0
- package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
- package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
- package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
- package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
- package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
- package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
- package/dist/components/window/Drawer.d.ts +4 -1
- package/dist/components/window/DrawerLayers.d.ts +1 -1
- package/dist/components/window/DrawerRevealContext.d.ts +61 -0
- package/dist/components/window/drawerRevealAnimationUtils.d.ts +212 -0
- package/dist/components/window/drawerStyles.d.ts +74 -0
- package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
- package/dist/components/window/useDrawerSwipeTransform.d.ts +29 -0
- package/dist/components/window/useDrawerTransform.d.ts +68 -0
- package/dist/components/window/useRevealDrawerTransform.d.ts +56 -0
- package/dist/config.cjs +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +9 -8
- package/dist/config.js.map +1 -1
- package/dist/constants/styles.d.ts +17 -0
- package/dist/dialog/index.d.ts +69 -0
- package/dist/floating.js +1 -1
- package/dist/grid.cjs +1 -1
- package/dist/grid.js +2 -2
- package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
- package/dist/hooks/gesture/types.d.ts +48 -5
- package/dist/hooks/gesture/utils.d.ts +19 -0
- package/dist/hooks/useAnimationFrame.d.ts +2 -0
- package/dist/hooks/useOperationContinuity.d.ts +64 -0
- package/dist/hooks/useResizeObserver.d.ts +33 -1
- package/dist/hooks/useSharedElementTransition.d.ts +112 -0
- package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +7 -7
- package/dist/modules/dialog/AlertDialog.d.ts +9 -0
- package/dist/modules/dialog/DialogContainer.d.ts +37 -0
- package/dist/modules/dialog/Modal.d.ts +26 -0
- package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
- package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
- package/dist/modules/dialog/types.d.ts +183 -0
- package/dist/modules/dialog/useDialog.d.ts +39 -0
- package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
- package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
- package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
- package/dist/modules/drawer/drawerStateMachine.d.ts +168 -0
- package/dist/modules/drawer/revealDrawerConstants.d.ts +33 -0
- package/dist/modules/drawer/revealDrawerStateMachine.d.ts +146 -0
- package/dist/modules/drawer/strategies/index.d.ts +8 -0
- package/dist/modules/drawer/strategies/overlayStrategy.d.ts +12 -0
- package/dist/modules/drawer/strategies/revealStrategy.d.ts +12 -0
- package/dist/modules/drawer/strategies/types.d.ts +116 -0
- package/dist/modules/drawer/types.d.ts +74 -0
- package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
- package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
- package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
- package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
- package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
- package/dist/panels.cjs +1 -1
- package/dist/panels.js +1 -1
- package/dist/pivot.cjs +1 -1
- package/dist/pivot.js +1 -1
- package/dist/resizer.cjs +1 -1
- package/dist/resizer.js +2 -2
- package/dist/stack.cjs +1 -1
- package/dist/stack.cjs.map +1 -1
- package/dist/stack.js +480 -780
- package/dist/stack.js.map +1 -1
- package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
- package/dist/sticky-header.cjs +1 -1
- package/dist/sticky-header.cjs.map +1 -1
- package/dist/sticky-header.js +59 -51
- package/dist/sticky-header.js.map +1 -1
- package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
- package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
- package/dist/styles-qf6ptVLD.cjs.map +1 -1
- package/dist/types.d.ts +30 -0
- package/dist/useAnimationFrame-BZ6D2lMq.cjs +2 -0
- package/dist/useAnimationFrame-BZ6D2lMq.cjs.map +1 -0
- package/dist/useAnimationFrame-Bg4e-H8O.js +394 -0
- package/dist/useAnimationFrame-Bg4e-H8O.js.map +1 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
- package/dist/window/index.d.ts +2 -0
- package/dist/window.cjs +1 -1
- package/dist/window.cjs.map +1 -1
- package/dist/window.js +114 -103
- package/dist/window.js.map +1 -1
- package/package.json +6 -1
- package/src/components/gesture/SwipeSafeZone.tsx +70 -0
- package/src/components/grid/GridLayout.tsx +110 -38
- package/src/components/window/Drawer.tsx +353 -162
- package/src/components/window/DrawerLayers.tsx +54 -11
- package/src/components/window/DrawerRevealContext.spec.ts +20 -0
- package/src/components/window/DrawerRevealContext.tsx +99 -0
- package/src/components/window/drawerRevealAnimationUtils.spec.ts +375 -0
- package/src/components/window/drawerRevealAnimationUtils.ts +415 -0
- package/src/components/window/drawerStyles.spec.ts +302 -0
- package/src/components/window/drawerStyles.ts +252 -0
- package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
- package/src/components/window/drawerSwipeConfig.ts +112 -0
- package/src/components/window/useDrawerSwipeTransform.ts +67 -0
- package/src/components/window/useDrawerTransform.ts +505 -0
- package/src/components/window/useRevealDrawerTransform.spec.ts +1936 -0
- package/src/components/window/useRevealDrawerTransform.ts +105 -0
- package/src/constants/styles.ts +19 -0
- package/src/demo/components/FullscreenDemoPage.tsx +47 -0
- package/src/demo/fullscreenRoutes.tsx +32 -0
- package/src/demo/index.tsx +5 -0
- package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
- package/src/demo/pages/Dialog/card/index.tsx +22 -0
- package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +219 -0
- package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
- package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
- package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
- package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
- package/src/demo/pages/Dialog/modal/index.tsx +17 -0
- package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
- package/src/demo/pages/Drawer/components/DrawerBasics.module.css +6 -1
- package/src/demo/pages/Drawer/components/DrawerBasics.tsx +14 -4
- package/src/demo/pages/Drawer/components/DrawerReveal.module.css +157 -0
- package/src/demo/pages/Drawer/components/DrawerReveal.tsx +128 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
- package/src/demo/pages/Drawer/reveal/index.tsx +17 -0
- package/src/demo/pages/Drawer/reveal-fullscreen/index.tsx +135 -0
- package/src/demo/pages/Drawer/reveal-fullscreen/styles.module.css +233 -0
- package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
- package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
- package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
- package/src/demo/pages/Stack/components/StackBasics.spec.tsx +156 -0
- package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
- package/src/demo/pages/Stack/components/StackTablet.spec.tsx +110 -0
- package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
- package/src/demo/routes.tsx +24 -1
- package/src/dialog/index.ts +85 -0
- package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
- package/src/hooks/gesture/testing/createGestureSimulator.ts +113 -37
- package/src/hooks/gesture/types.ts +83 -6
- package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +99 -31
- package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
- package/src/hooks/gesture/utils.ts +102 -0
- package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
- package/src/hooks/useAnimatedVisibility.ts +28 -2
- package/src/hooks/useAnimationFrame.ts +8 -0
- package/src/hooks/useOperationContinuity.spec.ts +394 -0
- package/src/hooks/useOperationContinuity.ts +135 -0
- package/src/hooks/useResizeObserver.spec.tsx +277 -0
- package/src/hooks/useResizeObserver.tsx +108 -39
- package/src/hooks/useScrollContainer.ts +4 -10
- package/src/hooks/useSharedElementTransition.ts +354 -0
- package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
- package/src/hooks/useSwipeContentTransform.ts +166 -28
- package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
- package/src/modules/dialog/AlertDialog.tsx +221 -0
- package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
- package/src/modules/dialog/DialogContainer.tsx +188 -0
- package/src/modules/dialog/Modal.spec.tsx +220 -0
- package/src/modules/dialog/Modal.tsx +182 -0
- package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
- package/src/modules/dialog/dialogAnimationUtils.spec.ts +252 -0
- package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
- package/src/modules/dialog/types.ts +186 -0
- package/src/modules/dialog/useDialog.spec.tsx +447 -0
- package/src/modules/dialog/useDialog.ts +214 -0
- package/src/modules/dialog/useDialogContainer.spec.ts +339 -0
- package/src/modules/dialog/useDialogContainer.ts +150 -0
- package/src/modules/dialog/useDialogSwipeInput.spec.ts +178 -0
- package/src/modules/dialog/useDialogSwipeInput.ts +350 -0
- package/src/modules/dialog/useDialogTransform.spec.ts +403 -0
- package/src/modules/dialog/useDialogTransform.ts +407 -0
- package/src/modules/drawer/drawerStateMachine.ts +500 -0
- package/src/modules/drawer/revealDrawerConstants.ts +38 -0
- package/src/modules/drawer/revealDrawerStateMachine.spec.ts +558 -0
- package/src/modules/drawer/revealDrawerStateMachine.ts +197 -0
- package/src/modules/drawer/strategies/index.ts +9 -0
- package/src/modules/drawer/strategies/overlayStrategy.ts +133 -0
- package/src/modules/drawer/strategies/revealStrategy.ts +111 -0
- package/src/modules/drawer/strategies/types.ts +160 -0
- package/src/modules/drawer/types.ts +102 -0
- package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
- package/src/modules/drawer/useDrawerSwipeInput.ts +402 -0
- package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
- package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
- package/src/modules/pivot/SwipePivotContent.spec.tsx +66 -25
- package/src/modules/pivot/SwipePivotContent.tsx +2 -2
- package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
- package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
- package/src/modules/pivot/scaleInputState.spec.ts +11 -2
- package/src/modules/pivot/usePivot.spec.ts +17 -3
- package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
- package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
- package/src/modules/stack/SwipeStackContent.tsx +43 -33
- package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
- package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
- package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
- package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
- package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
- package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
- package/src/modules/stack/useStackAnimationState.ts +18 -13
- package/src/modules/stack/useStackNavigation.spec.ts +198 -3
- package/src/modules/stack/useStackNavigation.tsx +113 -56
- package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
- package/src/modules/stack/useStackSwipeInput.ts +1 -1
- package/src/sticky-header/StickyArea.tsx +29 -57
- package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
- package/src/sticky-header/calculateStickyMetrics.ts +50 -0
- package/src/types.ts +33 -0
- package/src/window/index.ts +2 -0
- package/dist/FloatingWindow-BpdOpg_L.js +0 -400
- package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
- package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
- package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
- package/dist/GridLayout-B4VRsC0r.cjs +0 -2
- package/dist/GridLayout-BltqeCPK.js +0 -927
- package/dist/PanelSystem-Bs8bQwQF.cjs +0 -3
- package/dist/PanelSystem-Bs8bQwQF.cjs.map +0 -1
- package/dist/PanelSystem-Dr1TBhxM.js.map +0 -1
- package/dist/ResizeHandle-CScipO5l.cjs +0 -2
- package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
- package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
- package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
- package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
- package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
- package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
- package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
- package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
- package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
- package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
- package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
- 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,17 @@
|
|
|
2
2
|
* @file Drawer component
|
|
3
3
|
*
|
|
4
4
|
* Mobile-friendly slide-in panel with backdrop support.
|
|
5
|
+
* Supports swipe gestures for opening/closing.
|
|
6
|
+
* Supports reveal mode where content slides to show drawer behind.
|
|
5
7
|
*/
|
|
6
8
|
import * as React from "react";
|
|
7
|
-
import type { DrawerBehavior, WindowPosition } from "../../types";
|
|
9
|
+
import type { DrawerBehavior, WindowPosition } from "../../types.js";
|
|
8
10
|
export type DrawerProps = {
|
|
9
11
|
id: string;
|
|
10
12
|
config: DrawerBehavior;
|
|
11
13
|
isOpen: boolean;
|
|
12
14
|
onClose: () => void;
|
|
15
|
+
onOpen?: () => void;
|
|
13
16
|
children: React.ReactNode;
|
|
14
17
|
zIndex?: number;
|
|
15
18
|
width?: string | number;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Context for coordinating reveal-mode drawer animations with GridLayout.
|
|
3
|
+
*
|
|
4
|
+
* When a drawer uses animationMode="reveal", the main content needs to slide away
|
|
5
|
+
* to reveal the drawer behind it. This context provides the communication mechanism
|
|
6
|
+
* between DrawerLayers and GridLayout.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import type { DrawerAnimationMode } from "../../types.js";
|
|
10
|
+
import type { DrawerPlacement } from "./drawerStyles.js";
|
|
11
|
+
/**
|
|
12
|
+
* State for a reveal-mode drawer animation.
|
|
13
|
+
*/
|
|
14
|
+
export type DrawerRevealState = {
|
|
15
|
+
/** ID of the drawer layer */
|
|
16
|
+
layerId: string;
|
|
17
|
+
/** Drawer placement */
|
|
18
|
+
placement: DrawerPlacement;
|
|
19
|
+
/** Whether the drawer is currently open */
|
|
20
|
+
isOpen: boolean;
|
|
21
|
+
/** Drawer size in pixels (width for left/right, height for top/bottom) */
|
|
22
|
+
drawerSize: number;
|
|
23
|
+
/** Whether swipe operation is in progress */
|
|
24
|
+
isSwipeOperating: boolean;
|
|
25
|
+
/** Current swipe displacement during operation */
|
|
26
|
+
swipeDisplacement: number;
|
|
27
|
+
/** Whether currently opening via swipe */
|
|
28
|
+
isOpening: boolean;
|
|
29
|
+
/** Whether currently closing via swipe */
|
|
30
|
+
isClosing: boolean;
|
|
31
|
+
/** Whether the drawer is inline (container-relative) or fixed (viewport-relative) */
|
|
32
|
+
inline: boolean;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Context value provided to GridLayout for reveal-mode animations.
|
|
36
|
+
*/
|
|
37
|
+
export type DrawerRevealContextValue = {
|
|
38
|
+
/** Currently active reveal-mode drawer (only one at a time) */
|
|
39
|
+
activeRevealDrawer: DrawerRevealState | null;
|
|
40
|
+
/** Register/update a reveal-mode drawer state */
|
|
41
|
+
setRevealState: (state: DrawerRevealState | null) => void;
|
|
42
|
+
/** Grid container ref for inline mode transforms */
|
|
43
|
+
gridRef: React.RefObject<HTMLElement | null> | null;
|
|
44
|
+
/** Set the grid ref (called by GridLayout) */
|
|
45
|
+
setGridRef: (ref: React.RefObject<HTMLElement | null> | null) => void;
|
|
46
|
+
};
|
|
47
|
+
export declare const DrawerRevealContext: React.Context<DrawerRevealContextValue>;
|
|
48
|
+
/**
|
|
49
|
+
* Provider component for DrawerRevealContext.
|
|
50
|
+
*/
|
|
51
|
+
export declare const DrawerRevealProvider: React.FC<{
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Hook to consume DrawerRevealContext.
|
|
56
|
+
*/
|
|
57
|
+
export declare function useDrawerReveal(): DrawerRevealContextValue;
|
|
58
|
+
/**
|
|
59
|
+
* Check if the given animation mode is reveal mode.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isRevealMode(animationMode: DrawerAnimationMode | undefined): boolean;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Drawer reveal animation utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides transform calculations for reveal-mode drawer animations.
|
|
5
|
+
* The reveal mode creates a "pull out" effect where:
|
|
6
|
+
* - Drawer slides from partially hidden (-30%) to fully visible (0%)
|
|
7
|
+
* - Content slides away to reveal the drawer behind it
|
|
8
|
+
*/
|
|
9
|
+
import type * as React from "react";
|
|
10
|
+
import type { DrawerPlacement } from "./drawerStyles.js";
|
|
11
|
+
/**
|
|
12
|
+
* Percentage offset for drawer when closed (creates "pull out" effect).
|
|
13
|
+
*/
|
|
14
|
+
export declare const DRAWER_CLOSED_OFFSET_PERCENT = 30;
|
|
15
|
+
/**
|
|
16
|
+
* Default animation duration in milliseconds.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DEFAULT_ANIMATION_DURATION = 300;
|
|
19
|
+
/**
|
|
20
|
+
* Placement configuration for transforms.
|
|
21
|
+
*/
|
|
22
|
+
export type PlacementConfig = {
|
|
23
|
+
axis: "X" | "Y";
|
|
24
|
+
sign: 1 | -1;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get placement configuration for a drawer placement.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getPlacementConfig(placement: DrawerPlacement): PlacementConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Transform values for reveal drawer animation.
|
|
32
|
+
*/
|
|
33
|
+
export type RevealDrawerTransform = {
|
|
34
|
+
/** Drawer offset in percentage */
|
|
35
|
+
drawerOffsetPercent: number;
|
|
36
|
+
/** Content offset in pixels */
|
|
37
|
+
contentOffsetPx: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Compute progress from swipe displacement.
|
|
41
|
+
* Returns 0 (closed) to 1 (open).
|
|
42
|
+
*
|
|
43
|
+
* @param displacement - Current swipe displacement in pixels
|
|
44
|
+
* @param drawerSize - Size of the drawer in pixels
|
|
45
|
+
* @param isOpening - Whether the gesture is opening the drawer
|
|
46
|
+
* @param isClosing - Whether the gesture is closing the drawer
|
|
47
|
+
* @returns Progress value between 0 and 1
|
|
48
|
+
*/
|
|
49
|
+
export declare function computeSwipeProgress(displacement: number, drawerSize: number, isOpening: boolean, isClosing: boolean): number;
|
|
50
|
+
/**
|
|
51
|
+
* Compute reveal drawer transforms from progress.
|
|
52
|
+
*
|
|
53
|
+
* @param progress - Animation progress (0 = closed, 1 = open)
|
|
54
|
+
* @param drawerSize - Size of the drawer in pixels
|
|
55
|
+
* @param placement - Drawer placement
|
|
56
|
+
* @returns Transform values for drawer and content
|
|
57
|
+
*/
|
|
58
|
+
export declare function computeRevealTransform(progress: number, drawerSize: number, placement: DrawerPlacement): RevealDrawerTransform;
|
|
59
|
+
/**
|
|
60
|
+
* Build drawer transform string from progress.
|
|
61
|
+
*
|
|
62
|
+
* @param progress - Animation progress (0 = closed, 1 = open)
|
|
63
|
+
* @param placement - Drawer placement
|
|
64
|
+
* @returns CSS transform string
|
|
65
|
+
*/
|
|
66
|
+
export declare function buildDrawerTransformString(progress: number, placement: DrawerPlacement): string;
|
|
67
|
+
/**
|
|
68
|
+
* Build content transform string from progress.
|
|
69
|
+
*
|
|
70
|
+
* @param progress - Animation progress (0 = closed, 1 = open)
|
|
71
|
+
* @param drawerSize - Size of the drawer in pixels
|
|
72
|
+
* @param placement - Drawer placement
|
|
73
|
+
* @returns CSS transform string
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildContentTransformString(progress: number, drawerSize: number, placement: DrawerPlacement): string;
|
|
76
|
+
/**
|
|
77
|
+
* Stacking context styles for content element.
|
|
78
|
+
* Applied during reveal animation to ensure proper z-index layering.
|
|
79
|
+
*/
|
|
80
|
+
export type StackingContextStyles = {
|
|
81
|
+
position: string;
|
|
82
|
+
zIndex: string;
|
|
83
|
+
background: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Default stacking context styles.
|
|
87
|
+
*/
|
|
88
|
+
export declare const STACKING_CONTEXT_STYLES: StackingContextStyles;
|
|
89
|
+
/**
|
|
90
|
+
* Apply stacking context styles to an element.
|
|
91
|
+
*/
|
|
92
|
+
export declare function applyStackingContext(element: HTMLElement): void;
|
|
93
|
+
/**
|
|
94
|
+
* Clear stacking context styles from an element.
|
|
95
|
+
*/
|
|
96
|
+
export declare function clearStackingContext(element: HTMLElement): void;
|
|
97
|
+
/**
|
|
98
|
+
* Apply overflow hidden to body to prevent scrolling during drawer open.
|
|
99
|
+
* This prevents both horizontal and vertical scrolling of the body
|
|
100
|
+
* while the drawer is open or animating.
|
|
101
|
+
*/
|
|
102
|
+
export declare function applyOverflowHidden(): void;
|
|
103
|
+
/**
|
|
104
|
+
* Clear overflow hidden from body.
|
|
105
|
+
*/
|
|
106
|
+
export declare function clearOverflowHidden(): void;
|
|
107
|
+
/**
|
|
108
|
+
* Get the content element to transform.
|
|
109
|
+
*
|
|
110
|
+
* @param inline - Whether drawer is inline mode
|
|
111
|
+
* @param gridRef - Grid container ref for inline mode
|
|
112
|
+
* @returns The content element or null
|
|
113
|
+
*/
|
|
114
|
+
export declare function getContentElement(inline: boolean, gridRef?: React.RefObject<HTMLElement | null>): HTMLElement | null;
|
|
115
|
+
/**
|
|
116
|
+
* Tracked position for drawer and content elements in pixels.
|
|
117
|
+
*/
|
|
118
|
+
export type RevealPositionPx = {
|
|
119
|
+
/** Drawer offset in pixels */
|
|
120
|
+
drawerPx: number;
|
|
121
|
+
/** Content offset in pixels */
|
|
122
|
+
contentPx: number;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Compute drawer offset in pixels from progress.
|
|
126
|
+
*
|
|
127
|
+
* For reveal mode, the drawer animates from an offset position to 0.
|
|
128
|
+
* The offset is DRAWER_CLOSED_OFFSET_PERCENT of the drawer's own size.
|
|
129
|
+
*
|
|
130
|
+
* @param progress - Animation progress (0 = closed, 1 = open)
|
|
131
|
+
* @param drawerSize - Drawer size in pixels
|
|
132
|
+
* @param placement - Drawer placement
|
|
133
|
+
* @returns Drawer offset in pixels
|
|
134
|
+
*/
|
|
135
|
+
export declare function computeDrawerOffsetPx(progress: number, drawerSize: number, placement: DrawerPlacement): number;
|
|
136
|
+
/**
|
|
137
|
+
* Compute content offset in pixels from progress.
|
|
138
|
+
*
|
|
139
|
+
* Content moves from 0 (closed) to ±drawerSize (open).
|
|
140
|
+
*
|
|
141
|
+
* @param progress - Animation progress (0 = closed, 1 = open)
|
|
142
|
+
* @param drawerSize - Drawer size in pixels
|
|
143
|
+
* @param placement - Drawer placement
|
|
144
|
+
* @returns Content offset in pixels
|
|
145
|
+
*/
|
|
146
|
+
export declare function computeContentOffsetPx(progress: number, drawerSize: number, placement: DrawerPlacement): number;
|
|
147
|
+
/**
|
|
148
|
+
* Compute both drawer and content offsets from progress.
|
|
149
|
+
*
|
|
150
|
+
* @param progress - Animation progress (0 = closed, 1 = open)
|
|
151
|
+
* @param drawerSize - Drawer size in pixels
|
|
152
|
+
* @param placement - Drawer placement
|
|
153
|
+
* @returns Position in pixels for both elements
|
|
154
|
+
*/
|
|
155
|
+
export declare function computePositionFromProgress(progress: number, drawerSize: number, placement: DrawerPlacement): RevealPositionPx;
|
|
156
|
+
/**
|
|
157
|
+
* Compute target positions for open or closed state.
|
|
158
|
+
*
|
|
159
|
+
* @param isOpen - Whether target is the open state
|
|
160
|
+
* @param drawerSize - Drawer size in pixels
|
|
161
|
+
* @param placement - Drawer placement
|
|
162
|
+
* @returns Target positions in pixels
|
|
163
|
+
*/
|
|
164
|
+
export declare function computeTargetPosition(isOpen: boolean, drawerSize: number, placement: DrawerPlacement): RevealPositionPx;
|
|
165
|
+
/**
|
|
166
|
+
* Compute position from swipe displacement.
|
|
167
|
+
*
|
|
168
|
+
* This is the main function for tracking position during swipe.
|
|
169
|
+
*
|
|
170
|
+
* @param displacement - Current swipe displacement in pixels
|
|
171
|
+
* @param drawerSize - Drawer size in pixels
|
|
172
|
+
* @param placement - Drawer placement
|
|
173
|
+
* @param isOpening - Whether opening via swipe
|
|
174
|
+
* @param isClosing - Whether closing via swipe
|
|
175
|
+
* @returns Current position in pixels
|
|
176
|
+
*/
|
|
177
|
+
export declare function computePositionFromDisplacement(displacement: number, drawerSize: number, placement: DrawerPlacement, isOpening: boolean, isClosing: boolean): RevealPositionPx;
|
|
178
|
+
/**
|
|
179
|
+
* Build drawer transform string from pixel offset.
|
|
180
|
+
*
|
|
181
|
+
* @param drawerPx - Drawer offset in pixels
|
|
182
|
+
* @param placement - Drawer placement
|
|
183
|
+
* @returns CSS transform string
|
|
184
|
+
*/
|
|
185
|
+
export declare function buildDrawerTransformPx(drawerPx: number, placement: DrawerPlacement): string;
|
|
186
|
+
/**
|
|
187
|
+
* Build content transform string from pixel offset.
|
|
188
|
+
*
|
|
189
|
+
* @param contentPx - Content offset in pixels
|
|
190
|
+
* @param placement - Drawer placement
|
|
191
|
+
* @returns CSS transform string
|
|
192
|
+
*/
|
|
193
|
+
export declare function buildContentTransformPx(contentPx: number, placement: DrawerPlacement): string;
|
|
194
|
+
/**
|
|
195
|
+
* Show the drawer element.
|
|
196
|
+
*/
|
|
197
|
+
export declare function showDrawer(element: HTMLElement): void;
|
|
198
|
+
/**
|
|
199
|
+
* Hide the drawer element.
|
|
200
|
+
*/
|
|
201
|
+
export declare function hideDrawer(element: HTMLElement): void;
|
|
202
|
+
/**
|
|
203
|
+
* Clear drawer visibility styles.
|
|
204
|
+
*/
|
|
205
|
+
export declare function clearDrawerVisibility(element: HTMLElement): void;
|
|
206
|
+
/**
|
|
207
|
+
* Apply stacking context styles with configurable background.
|
|
208
|
+
*
|
|
209
|
+
* @param element - Content element
|
|
210
|
+
* @param background - Background color (defaults to #fff)
|
|
211
|
+
*/
|
|
212
|
+
export declare function applyStackingContextWithBackground(element: HTMLElement, background?: string): void;
|