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,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Hook for View Transitions API shared element animations.
|
|
3
|
+
*
|
|
4
|
+
* Enables smooth morph animations between a source element (e.g., card)
|
|
5
|
+
* and a target element (e.g., expanded view) using CSS view-transition-name.
|
|
6
|
+
* Supports swipe-to-dismiss with the expanded view animating back to source.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { flushSync } from "react-dom";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if View Transitions API is supported.
|
|
13
|
+
*/
|
|
14
|
+
export function supportsViewTransitions(): boolean {
|
|
15
|
+
if (typeof document === "undefined") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
return "startViewTransition" in document;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type ViewTransitionHandle = {
|
|
22
|
+
finished: Promise<void>;
|
|
23
|
+
ready: Promise<void>;
|
|
24
|
+
updateCallbackDone: Promise<void>;
|
|
25
|
+
skipTransition: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Start a view transition with the given callback.
|
|
30
|
+
*/
|
|
31
|
+
export function startViewTransition(callback: () => void): ViewTransitionHandle | null {
|
|
32
|
+
if (supportsViewTransitions()) {
|
|
33
|
+
return (document as Document & { startViewTransition: (cb: () => void) => ViewTransitionHandle }).startViewTransition(callback);
|
|
34
|
+
}
|
|
35
|
+
callback();
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Default dismiss threshold (30% of viewport height) */
|
|
40
|
+
const DEFAULT_DISMISS_THRESHOLD = 0.3;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Compute transform string for swipe displacement.
|
|
44
|
+
*/
|
|
45
|
+
function computeSwipeTransform(
|
|
46
|
+
isSwiping: boolean,
|
|
47
|
+
displacement: { x: number; y: number },
|
|
48
|
+
): string | undefined {
|
|
49
|
+
if (isSwiping) {
|
|
50
|
+
return `translate(${displacement.x}px, ${displacement.y}px)`;
|
|
51
|
+
}
|
|
52
|
+
if (displacement.x !== 0) {
|
|
53
|
+
return `translate(${displacement.x}px, ${displacement.y}px)`;
|
|
54
|
+
}
|
|
55
|
+
if (displacement.y !== 0) {
|
|
56
|
+
return `translate(${displacement.x}px, ${displacement.y}px)`;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Velocity threshold for quick flick dismissal (px/ms) */
|
|
62
|
+
const VELOCITY_THRESHOLD = 0.5;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for useSharedElementTransition hook.
|
|
66
|
+
*/
|
|
67
|
+
export type UseSharedElementTransitionOptions<T> = {
|
|
68
|
+
/**
|
|
69
|
+
* Function to generate a unique transition name for an item.
|
|
70
|
+
* Multiple names can be returned for nested shared elements.
|
|
71
|
+
*/
|
|
72
|
+
getTransitionName: (item: T) => string | string[];
|
|
73
|
+
/**
|
|
74
|
+
* Function to get a unique key for comparison.
|
|
75
|
+
* Defaults to using getTransitionName result.
|
|
76
|
+
*/
|
|
77
|
+
getKey?: (item: T) => string;
|
|
78
|
+
/** Enable swipe to dismiss. @default true */
|
|
79
|
+
swipeDismissible?: boolean;
|
|
80
|
+
/** Threshold ratio (0-1) to trigger dismiss. @default 0.3 */
|
|
81
|
+
dismissThreshold?: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** 2D vector */
|
|
85
|
+
type Vector2 = { x: number; y: number };
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Result from useSharedElementTransition hook.
|
|
89
|
+
*/
|
|
90
|
+
export type UseSharedElementTransitionResult<T> = {
|
|
91
|
+
/** Currently expanded item, or null if none */
|
|
92
|
+
expandedItem: T | null;
|
|
93
|
+
/** Expand an item with view transition */
|
|
94
|
+
expand: (item: T) => void;
|
|
95
|
+
/** Collapse the expanded item with view transition */
|
|
96
|
+
collapse: () => void;
|
|
97
|
+
/**
|
|
98
|
+
* Get style props for a source element (e.g., card).
|
|
99
|
+
*/
|
|
100
|
+
getSourceProps: (item: T, nameIndex?: number) => { style: React.CSSProperties };
|
|
101
|
+
/**
|
|
102
|
+
* Get style props for the target element (e.g., expanded view).
|
|
103
|
+
* Includes transform for swipe tracking.
|
|
104
|
+
*/
|
|
105
|
+
getTargetProps: (nameIndex?: number) => { style: React.CSSProperties };
|
|
106
|
+
/**
|
|
107
|
+
* Get swipe container props (onPointerDown, style).
|
|
108
|
+
* Apply to the swipeable container element.
|
|
109
|
+
*/
|
|
110
|
+
getSwipeContainerProps: () => React.HTMLAttributes<HTMLElement> & { style: React.CSSProperties };
|
|
111
|
+
/** Whether currently being swiped */
|
|
112
|
+
isSwiping: boolean;
|
|
113
|
+
/** Current displacement during swipe */
|
|
114
|
+
displacement: Vector2;
|
|
115
|
+
/** Whether View Transitions API is supported */
|
|
116
|
+
isSupported: boolean;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Hook for managing shared element transitions using View Transitions API.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* const {
|
|
125
|
+
* expandedItem, expand, collapse,
|
|
126
|
+
* getSourceProps, getTargetProps, getSwipeContainerProps
|
|
127
|
+
* } = useSharedElementTransition({
|
|
128
|
+
* getTransitionName: (album) => [`album-${album.id}`, `album-art-${album.id}`],
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* // Source (card)
|
|
132
|
+
* <div {...getSourceProps(album, 0)}>
|
|
133
|
+
* <img {...getSourceProps(album, 1)} />
|
|
134
|
+
* </div>
|
|
135
|
+
*
|
|
136
|
+
* // Target (expanded view) - apply swipe props to container
|
|
137
|
+
* {expandedItem && (
|
|
138
|
+
* <div {...getSwipeContainerProps()}>
|
|
139
|
+
* <div {...getTargetProps(0)}>
|
|
140
|
+
* <img {...getTargetProps(1)} />
|
|
141
|
+
* </div>
|
|
142
|
+
* </div>
|
|
143
|
+
* )}
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export function useSharedElementTransition<T>(
|
|
147
|
+
options: UseSharedElementTransitionOptions<T>,
|
|
148
|
+
): UseSharedElementTransitionResult<T> {
|
|
149
|
+
const {
|
|
150
|
+
getTransitionName,
|
|
151
|
+
getKey,
|
|
152
|
+
swipeDismissible = true,
|
|
153
|
+
dismissThreshold = DEFAULT_DISMISS_THRESHOLD,
|
|
154
|
+
} = options;
|
|
155
|
+
|
|
156
|
+
const [expandedItem, setExpandedItem] = React.useState<T | null>(null);
|
|
157
|
+
// Track which item is about to be expanded (for view transition name assignment)
|
|
158
|
+
const [pendingExpandItem, setPendingExpandItem] = React.useState<T | null>(null);
|
|
159
|
+
// Track which item is collapsing (for view transition name assignment on the card)
|
|
160
|
+
const [collapsingItem, setCollapsingItem] = React.useState<T | null>(null);
|
|
161
|
+
const isSupported = React.useMemo(() => supportsViewTransitions(), []);
|
|
162
|
+
|
|
163
|
+
// Swipe tracking state
|
|
164
|
+
const [isSwiping, setIsSwiping] = React.useState(false);
|
|
165
|
+
const [displacement, setDisplacement] = React.useState<Vector2>({ x: 0, y: 0 });
|
|
166
|
+
const startPointRef = React.useRef<Vector2 | null>(null);
|
|
167
|
+
const startTimeRef = React.useRef<number>(0);
|
|
168
|
+
|
|
169
|
+
// Get item key for comparison
|
|
170
|
+
const getItemKey = React.useCallback((item: T): string => {
|
|
171
|
+
if (getKey) {
|
|
172
|
+
return getKey(item);
|
|
173
|
+
}
|
|
174
|
+
const names = getTransitionName(item);
|
|
175
|
+
return Array.isArray(names) ? names[0] : names;
|
|
176
|
+
}, [getKey, getTransitionName]);
|
|
177
|
+
|
|
178
|
+
const expand = React.useCallback((item: T) => {
|
|
179
|
+
// First, set pending item so only this card gets view-transition-name
|
|
180
|
+
setPendingExpandItem(item);
|
|
181
|
+
|
|
182
|
+
// Use requestAnimationFrame to ensure React re-renders before view transition
|
|
183
|
+
requestAnimationFrame(() => {
|
|
184
|
+
const transition = startViewTransition(() => {
|
|
185
|
+
flushSync(() => {
|
|
186
|
+
setExpandedItem(item);
|
|
187
|
+
setPendingExpandItem(null);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
// Fallback if viewTransition not supported
|
|
191
|
+
if (!transition) {
|
|
192
|
+
setExpandedItem(item);
|
|
193
|
+
setPendingExpandItem(null);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
const collapse = React.useCallback(() => {
|
|
199
|
+
if (!expandedItem) {return;}
|
|
200
|
+
|
|
201
|
+
setIsSwiping(false);
|
|
202
|
+
|
|
203
|
+
// For closing, expanded view already has view-transition-name
|
|
204
|
+
// We just need the card to also have it in the new state
|
|
205
|
+
// Use flushSync immediately since old state (expanded) already has the name
|
|
206
|
+
const itemToCollapse = expandedItem;
|
|
207
|
+
const transition = startViewTransition(() => {
|
|
208
|
+
flushSync(() => {
|
|
209
|
+
setExpandedItem(null);
|
|
210
|
+
setCollapsingItem(itemToCollapse);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Reset states after transition completes (or immediately if not supported)
|
|
215
|
+
if (transition) {
|
|
216
|
+
transition.finished.then(() => {
|
|
217
|
+
setDisplacement({ x: 0, y: 0 });
|
|
218
|
+
setCollapsingItem(null);
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
setExpandedItem(null);
|
|
222
|
+
setDisplacement({ x: 0, y: 0 });
|
|
223
|
+
setCollapsingItem(null);
|
|
224
|
+
}
|
|
225
|
+
}, [expandedItem]);
|
|
226
|
+
|
|
227
|
+
// Pointer event handlers for swipe
|
|
228
|
+
const handlePointerDown = React.useCallback((event: React.PointerEvent) => {
|
|
229
|
+
if (!swipeDismissible || !expandedItem) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
(event.target as HTMLElement).setPointerCapture(event.pointerId);
|
|
233
|
+
startPointRef.current = { x: event.clientX, y: event.clientY };
|
|
234
|
+
startTimeRef.current = Date.now();
|
|
235
|
+
setIsSwiping(true);
|
|
236
|
+
}, [swipeDismissible, expandedItem]);
|
|
237
|
+
|
|
238
|
+
const handlePointerMove = React.useCallback((event: React.PointerEvent) => {
|
|
239
|
+
if (!isSwiping || !startPointRef.current) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const dx = event.clientX - startPointRef.current.x;
|
|
243
|
+
const dy = event.clientY - startPointRef.current.y;
|
|
244
|
+
setDisplacement({ x: dx, y: dy });
|
|
245
|
+
}, [isSwiping]);
|
|
246
|
+
|
|
247
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- event parameter required by React handler signature
|
|
248
|
+
const handlePointerUp = React.useCallback((_event: React.PointerEvent) => {
|
|
249
|
+
if (!isSwiping || !startPointRef.current) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const duration = Date.now() - startTimeRef.current;
|
|
254
|
+
const velocity = Math.abs(displacement.y) / Math.max(1, duration);
|
|
255
|
+
const viewportHeight = window.innerHeight;
|
|
256
|
+
const ratio = Math.abs(displacement.y) / viewportHeight;
|
|
257
|
+
|
|
258
|
+
// Check if should dismiss (downward swipe)
|
|
259
|
+
const shouldDismiss = displacement.y > 0 && (ratio >= dismissThreshold || velocity >= VELOCITY_THRESHOLD);
|
|
260
|
+
|
|
261
|
+
if (shouldDismiss) {
|
|
262
|
+
collapse();
|
|
263
|
+
} else {
|
|
264
|
+
// Snap back
|
|
265
|
+
setDisplacement({ x: 0, y: 0 });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
setIsSwiping(false);
|
|
269
|
+
startPointRef.current = null;
|
|
270
|
+
}, [isSwiping, displacement, dismissThreshold, collapse]);
|
|
271
|
+
|
|
272
|
+
const handlePointerCancel = React.useCallback(() => {
|
|
273
|
+
setIsSwiping(false);
|
|
274
|
+
setDisplacement({ x: 0, y: 0 });
|
|
275
|
+
startPointRef.current = null;
|
|
276
|
+
}, []);
|
|
277
|
+
|
|
278
|
+
const getSourceProps = React.useCallback(
|
|
279
|
+
(item: T, nameIndex = 0): { style: React.CSSProperties } => {
|
|
280
|
+
const names = getTransitionName(item);
|
|
281
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
282
|
+
const name = nameArray[nameIndex];
|
|
283
|
+
|
|
284
|
+
const itemKey = getItemKey(item);
|
|
285
|
+
const isThisItemExpanded = expandedItem !== null && getItemKey(expandedItem) === itemKey;
|
|
286
|
+
const isThisItemPending = pendingExpandItem !== null && getItemKey(pendingExpandItem) === itemKey;
|
|
287
|
+
const isThisItemCollapsing = collapsingItem !== null && getItemKey(collapsingItem) === itemKey;
|
|
288
|
+
|
|
289
|
+
// Only give view-transition-name to:
|
|
290
|
+
// - The pending item (clicked card, for old state capture during expand)
|
|
291
|
+
// - The collapsing item (card returning to, for new state capture during collapse)
|
|
292
|
+
// - Never to other cards (they don't participate in the transition)
|
|
293
|
+
const shouldHaveTransitionName = isThisItemPending ? true : isThisItemCollapsing;
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
style: {
|
|
297
|
+
viewTransitionName: shouldHaveTransitionName ? name : undefined,
|
|
298
|
+
// Hide the source card when expanded (it's now represented by the target)
|
|
299
|
+
visibility: isThisItemExpanded ? "hidden" : undefined,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
[expandedItem, pendingExpandItem, collapsingItem, getTransitionName, getItemKey],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const getTargetProps = React.useCallback(
|
|
307
|
+
(nameIndex = 0): { style: React.CSSProperties } => {
|
|
308
|
+
if (expandedItem === null) {
|
|
309
|
+
return { style: {} };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const names = getTransitionName(expandedItem);
|
|
313
|
+
const nameArray = Array.isArray(names) ? names : [names];
|
|
314
|
+
const name = nameArray[nameIndex];
|
|
315
|
+
|
|
316
|
+
// Apply transform during swipe
|
|
317
|
+
const transform = computeSwipeTransform(isSwiping, displacement);
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
style: {
|
|
321
|
+
viewTransitionName: name,
|
|
322
|
+
transform,
|
|
323
|
+
transition: !isSwiping ? "transform 0.3s ease-out" : undefined,
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
},
|
|
327
|
+
[expandedItem, getTransitionName, isSwiping, displacement],
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const getSwipeContainerProps = React.useCallback((): React.HTMLAttributes<HTMLElement> & { style: React.CSSProperties } => {
|
|
331
|
+
return {
|
|
332
|
+
onPointerDown: handlePointerDown,
|
|
333
|
+
onPointerMove: handlePointerMove,
|
|
334
|
+
onPointerUp: handlePointerUp,
|
|
335
|
+
onPointerCancel: handlePointerCancel,
|
|
336
|
+
style: {
|
|
337
|
+
touchAction: "none",
|
|
338
|
+
userSelect: "none",
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}, [handlePointerDown, handlePointerMove, handlePointerUp, handlePointerCancel]);
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
expandedItem,
|
|
345
|
+
expand,
|
|
346
|
+
collapse,
|
|
347
|
+
getSourceProps,
|
|
348
|
+
getTargetProps,
|
|
349
|
+
getSwipeContainerProps,
|
|
350
|
+
isSwiping,
|
|
351
|
+
displacement,
|
|
352
|
+
isSupported,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
@@ -17,7 +17,7 @@ describe("useSwipeContentTransform", () => {
|
|
|
17
17
|
elementRef: ref,
|
|
18
18
|
targetPx: 0,
|
|
19
19
|
displacement: 0,
|
|
20
|
-
|
|
20
|
+
isOperating: false,
|
|
21
21
|
}),
|
|
22
22
|
);
|
|
23
23
|
|
|
@@ -28,20 +28,20 @@ describe("useSwipeContentTransform", () => {
|
|
|
28
28
|
it("updates element transform during swipe", () => {
|
|
29
29
|
const { element, ref } = createMockElementRef();
|
|
30
30
|
const { rerender } = renderHook(
|
|
31
|
-
({ displacement,
|
|
31
|
+
({ displacement, isOperating }) =>
|
|
32
32
|
useSwipeContentTransform({
|
|
33
33
|
elementRef: ref,
|
|
34
34
|
targetPx: 0,
|
|
35
35
|
displacement,
|
|
36
|
-
|
|
36
|
+
isOperating,
|
|
37
37
|
}),
|
|
38
38
|
{
|
|
39
|
-
initialProps: { displacement: 0,
|
|
39
|
+
initialProps: { displacement: 0, isOperating: true },
|
|
40
40
|
},
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
// Simulate swipe movement
|
|
44
|
-
rerender({ displacement: 100,
|
|
44
|
+
rerender({ displacement: 100, isOperating: true });
|
|
45
45
|
|
|
46
46
|
expect(element.style.transform).toBe("translateX(100px)");
|
|
47
47
|
});
|
|
@@ -49,20 +49,20 @@ describe("useSwipeContentTransform", () => {
|
|
|
49
49
|
it("uses correct transform function for vertical axis", () => {
|
|
50
50
|
const { element, ref } = createMockElementRef();
|
|
51
51
|
const { rerender } = renderHook(
|
|
52
|
-
({ displacement,
|
|
52
|
+
({ displacement, isOperating }) =>
|
|
53
53
|
useSwipeContentTransform({
|
|
54
54
|
elementRef: ref,
|
|
55
55
|
targetPx: 0,
|
|
56
56
|
displacement,
|
|
57
|
-
|
|
57
|
+
isOperating,
|
|
58
58
|
axis: "vertical",
|
|
59
59
|
}),
|
|
60
60
|
{
|
|
61
|
-
initialProps: { displacement: 0,
|
|
61
|
+
initialProps: { displacement: 0, isOperating: true },
|
|
62
62
|
},
|
|
63
63
|
);
|
|
64
64
|
|
|
65
|
-
rerender({ displacement: 50,
|
|
65
|
+
rerender({ displacement: 50, isOperating: true });
|
|
66
66
|
|
|
67
67
|
expect(element.style.transform).toBe("translateY(50px)");
|
|
68
68
|
});
|
|
@@ -70,20 +70,20 @@ describe("useSwipeContentTransform", () => {
|
|
|
70
70
|
it("applies target position with displacement", () => {
|
|
71
71
|
const { element, ref } = createMockElementRef();
|
|
72
72
|
const { rerender } = renderHook(
|
|
73
|
-
({ displacement,
|
|
73
|
+
({ displacement, isOperating }) =>
|
|
74
74
|
useSwipeContentTransform({
|
|
75
75
|
elementRef: ref,
|
|
76
76
|
targetPx: -300, // target is off-screen left
|
|
77
77
|
displacement,
|
|
78
|
-
|
|
78
|
+
isOperating,
|
|
79
79
|
}),
|
|
80
80
|
{
|
|
81
|
-
initialProps: { displacement: 0,
|
|
81
|
+
initialProps: { displacement: 0, isOperating: true },
|
|
82
82
|
},
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
// During swipe, position = targetPx + displacement
|
|
86
|
-
rerender({ displacement: 100,
|
|
86
|
+
rerender({ displacement: 100, isOperating: true });
|
|
87
87
|
|
|
88
88
|
expect(element.style.transform).toBe("translateX(-200px)");
|
|
89
89
|
});
|
|
@@ -92,21 +92,21 @@ describe("useSwipeContentTransform", () => {
|
|
|
92
92
|
const nullRef: React.RefObject<HTMLElement | null> = { current: null };
|
|
93
93
|
|
|
94
94
|
const { rerender } = renderHook(
|
|
95
|
-
({ displacement,
|
|
95
|
+
({ displacement, isOperating }) =>
|
|
96
96
|
useSwipeContentTransform({
|
|
97
97
|
elementRef: nullRef,
|
|
98
98
|
targetPx: 0,
|
|
99
99
|
displacement,
|
|
100
|
-
|
|
100
|
+
isOperating,
|
|
101
101
|
}),
|
|
102
102
|
{
|
|
103
|
-
initialProps: { displacement: 0,
|
|
103
|
+
initialProps: { displacement: 0, isOperating: true },
|
|
104
104
|
},
|
|
105
105
|
);
|
|
106
106
|
|
|
107
107
|
// Should not throw
|
|
108
108
|
expect(() => {
|
|
109
|
-
rerender({ displacement: 100,
|
|
109
|
+
rerender({ displacement: 100, isOperating: true });
|
|
110
110
|
}).not.toThrow();
|
|
111
111
|
});
|
|
112
112
|
|
|
@@ -118,7 +118,7 @@ describe("useSwipeContentTransform", () => {
|
|
|
118
118
|
elementRef: ref,
|
|
119
119
|
targetPx,
|
|
120
120
|
displacement: 0,
|
|
121
|
-
|
|
121
|
+
isOperating: false,
|
|
122
122
|
}),
|
|
123
123
|
{
|
|
124
124
|
initialProps: { targetPx: 0 },
|