react-panel-layout 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-CUXnEtrb.js +827 -0
- package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
- package/dist/GridLayout-DKTg_N61.cjs +2 -0
- package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DKTg_N61.cjs.map} +1 -1
- package/dist/{GridLayout-BltqeCPK.js → GridLayout-UWNxXw77.js} +34 -35
- package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-UWNxXw77.js.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-Dr1TBhxM.js → PanelSystem-BqUzNtf2.js} +5 -5
- package/dist/{PanelSystem-Dr1TBhxM.js.map → PanelSystem-BqUzNtf2.js.map} +1 -1
- package/dist/{PanelSystem-Bs8bQwQF.cjs → PanelSystem-D603LKKv.cjs} +2 -2
- package/dist/{PanelSystem-Bs8bQwQF.cjs.map → PanelSystem-D603LKKv.cjs.map} +1 -1
- 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 +3 -1
- package/dist/components/window/DrawerLayers.d.ts +1 -1
- package/dist/components/window/drawerStyles.d.ts +69 -0
- package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
- package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
- package/dist/config.cjs +1 -1
- package/dist/config.js +3 -3
- 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/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 +503 -762
- 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 +16 -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/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js.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 +69 -0
- package/src/components/window/Drawer.tsx +249 -162
- package/src/components/window/DrawerLayers.tsx +13 -3
- package/src/components/window/drawerStyles.spec.ts +263 -0
- package/src/components/window/drawerStyles.ts +228 -0
- package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
- package/src/components/window/drawerSwipeConfig.ts +112 -0
- package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
- package/src/components/window/useDrawerSwipeTransform.ts +129 -0
- package/src/constants/styles.ts +19 -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 +204 -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/DrawerSwipe.module.css +316 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -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 +152 -0
- package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
- package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
- package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
- package/src/demo/routes.tsx +22 -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 +112 -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 +91 -31
- package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
- package/src/hooks/gesture/utils.ts +91 -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 +387 -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 +333 -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 +253 -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 +331 -0
- package/src/modules/dialog/useDialogContainer.ts +150 -0
- package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
- package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
- package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
- package/src/modules/dialog/useDialogTransform.ts +407 -0
- package/src/modules/drawer/types.ts +102 -0
- package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
- package/src/modules/drawer/useDrawerSwipeInput.ts +399 -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 +55 -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 +18 -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/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,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Hook for managing dialog transform during swipe and animation.
|
|
3
|
+
*
|
|
4
|
+
* This hook provides:
|
|
5
|
+
* - Real-time transform updates during swipe gestures
|
|
6
|
+
* - Smooth open/close animations with multi-phase easing
|
|
7
|
+
* - Backdrop opacity animation
|
|
8
|
+
* - Support for viewTransition API (optional)
|
|
9
|
+
*/
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
import { useAnimationFrame, easings } from "../../hooks/useAnimationFrame.js";
|
|
12
|
+
import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
|
|
13
|
+
import type { DialogOpenDirection, DialogTransform } from "./dialogAnimationUtils.js";
|
|
14
|
+
import {
|
|
15
|
+
computeSwipeTransform,
|
|
16
|
+
computeOpenTransform,
|
|
17
|
+
computeCloseTransform,
|
|
18
|
+
getAnimationAxis,
|
|
19
|
+
buildTransformString,
|
|
20
|
+
safeViewTransition,
|
|
21
|
+
} from "./dialogAnimationUtils.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default animation duration in milliseconds.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_ANIMATION_DURATION = 350;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Phase of dialog animation lifecycle.
|
|
30
|
+
*/
|
|
31
|
+
export type DialogAnimationPhase = "idle" | "opening" | "open" | "closing" | "closed";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 2D displacement for free movement.
|
|
35
|
+
*/
|
|
36
|
+
export type Displacement2D = {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Options for useDialogTransform hook.
|
|
43
|
+
*/
|
|
44
|
+
export type UseDialogTransformOptions = {
|
|
45
|
+
/** Ref to the dialog content element */
|
|
46
|
+
elementRef: React.RefObject<HTMLElement | null>;
|
|
47
|
+
/** Ref to the backdrop element */
|
|
48
|
+
backdropRef: React.RefObject<HTMLElement | null>;
|
|
49
|
+
/** Whether the dialog is visible */
|
|
50
|
+
visible: boolean;
|
|
51
|
+
/** Direction the dialog opens from */
|
|
52
|
+
openDirection: DialogOpenDirection;
|
|
53
|
+
/** Current swipe state */
|
|
54
|
+
swipeState: ContinuousOperationState;
|
|
55
|
+
/** Current swipe displacement in pixels (primary axis) */
|
|
56
|
+
displacement: number;
|
|
57
|
+
/** Full 2D displacement for free movement */
|
|
58
|
+
displacement2D: Displacement2D;
|
|
59
|
+
/** Animation duration in ms. @default 350 */
|
|
60
|
+
animationDuration?: number;
|
|
61
|
+
/** Whether to use viewTransition API for close animation */
|
|
62
|
+
useViewTransition?: boolean;
|
|
63
|
+
/** Callback when open animation completes */
|
|
64
|
+
onOpenComplete?: () => void;
|
|
65
|
+
/** Callback when close animation completes */
|
|
66
|
+
onCloseComplete?: () => void;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Result from useDialogTransform hook.
|
|
71
|
+
*/
|
|
72
|
+
export type UseDialogTransformResult = {
|
|
73
|
+
/** Current animation phase */
|
|
74
|
+
phase: DialogAnimationPhase;
|
|
75
|
+
/** Whether any animation is running */
|
|
76
|
+
isAnimating: boolean;
|
|
77
|
+
/** Trigger close animation (use instead of setting visible=false directly) */
|
|
78
|
+
triggerClose: () => Promise<void>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Apply transform values to elements.
|
|
83
|
+
*/
|
|
84
|
+
function applyTransform(
|
|
85
|
+
element: HTMLElement | null,
|
|
86
|
+
backdrop: HTMLElement | null,
|
|
87
|
+
transform: DialogTransform,
|
|
88
|
+
axis: "x" | "y",
|
|
89
|
+
): void {
|
|
90
|
+
if (element) {
|
|
91
|
+
element.style.transform = buildTransformString(transform, axis);
|
|
92
|
+
}
|
|
93
|
+
if (backdrop) {
|
|
94
|
+
backdrop.style.opacity = String(transform.backdropOpacity);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Hook for managing dialog transform during swipe and animation.
|
|
100
|
+
*
|
|
101
|
+
* During swipe: directly updates element transform based on displacement.
|
|
102
|
+
* After swipe: animates to target position (close or snap back).
|
|
103
|
+
* On visible change: animates open/close transition.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* const { phase, isAnimating, triggerClose } = useDialogTransform({
|
|
108
|
+
* elementRef,
|
|
109
|
+
* backdropRef,
|
|
110
|
+
* visible,
|
|
111
|
+
* openDirection: "bottom",
|
|
112
|
+
* swipeState: inputState,
|
|
113
|
+
* displacement: inputDisplacement,
|
|
114
|
+
* onCloseComplete: () => onClose(),
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function useDialogTransform(
|
|
119
|
+
options: UseDialogTransformOptions,
|
|
120
|
+
): UseDialogTransformResult {
|
|
121
|
+
const {
|
|
122
|
+
elementRef,
|
|
123
|
+
backdropRef,
|
|
124
|
+
visible,
|
|
125
|
+
openDirection,
|
|
126
|
+
swipeState,
|
|
127
|
+
displacement,
|
|
128
|
+
displacement2D,
|
|
129
|
+
animationDuration = DEFAULT_ANIMATION_DURATION,
|
|
130
|
+
useViewTransition = false,
|
|
131
|
+
onOpenComplete,
|
|
132
|
+
onCloseComplete,
|
|
133
|
+
} = options;
|
|
134
|
+
|
|
135
|
+
const axis = getAnimationAxis(openDirection);
|
|
136
|
+
const [phase, setPhase] = React.useState<DialogAnimationPhase>(visible ? "open" : "closed");
|
|
137
|
+
const containerSizeRef = React.useRef<number>(0);
|
|
138
|
+
const animationRef = React.useRef<{
|
|
139
|
+
type: "open" | "close" | "snapback";
|
|
140
|
+
startPx?: number;
|
|
141
|
+
start2D?: Displacement2D;
|
|
142
|
+
} | null>(null);
|
|
143
|
+
|
|
144
|
+
// Track container size - measure synchronously to ensure dimensions are available for animations
|
|
145
|
+
const measureContainerSize = React.useCallback((): number => {
|
|
146
|
+
const element = elementRef.current;
|
|
147
|
+
if (!element) {
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
const size = axis === "x" ? element.clientWidth : element.clientHeight;
|
|
151
|
+
containerSizeRef.current = size;
|
|
152
|
+
return size;
|
|
153
|
+
}, [elementRef, axis]);
|
|
154
|
+
|
|
155
|
+
// Initial measurement and ResizeObserver setup
|
|
156
|
+
React.useLayoutEffect(() => {
|
|
157
|
+
const element = elementRef.current;
|
|
158
|
+
if (!element) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
measureContainerSize();
|
|
163
|
+
|
|
164
|
+
const observer = new ResizeObserver(measureContainerSize);
|
|
165
|
+
observer.observe(element);
|
|
166
|
+
|
|
167
|
+
return () => observer.disconnect();
|
|
168
|
+
}, [elementRef, measureContainerSize]);
|
|
169
|
+
|
|
170
|
+
// Animation frame handlers
|
|
171
|
+
const handleFrame = React.useCallback(
|
|
172
|
+
({ easedProgress }: { easedProgress: number }) => {
|
|
173
|
+
const element = elementRef.current;
|
|
174
|
+
const backdrop = backdropRef.current;
|
|
175
|
+
const anim = animationRef.current;
|
|
176
|
+
|
|
177
|
+
if (!element || !anim) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Measure container size if not yet available (first frame after becoming visible)
|
|
182
|
+
const getContainerSize = (): number => {
|
|
183
|
+
const cachedSize = containerSizeRef.current;
|
|
184
|
+
if (cachedSize > 0) {
|
|
185
|
+
return cachedSize;
|
|
186
|
+
}
|
|
187
|
+
return measureContainerSize();
|
|
188
|
+
};
|
|
189
|
+
const containerSize = getContainerSize();
|
|
190
|
+
if (containerSize <= 0) {
|
|
191
|
+
return; // Still no size, skip this frame
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Handle snapback with 2D animation
|
|
195
|
+
if (anim.type === "snapback") {
|
|
196
|
+
const start2D = anim.start2D ?? { x: 0, y: 0 };
|
|
197
|
+
const startPx = Math.abs(anim.startPx ?? 0);
|
|
198
|
+
const startTransform = computeSwipeTransform(startPx, containerSize);
|
|
199
|
+
|
|
200
|
+
// Interpolate 2D position back to origin
|
|
201
|
+
const currentX = start2D.x * (1 - easedProgress);
|
|
202
|
+
const currentY = start2D.y * (1 - easedProgress);
|
|
203
|
+
const currentScale = startTransform.scale * (1 - easedProgress) + 1 * easedProgress;
|
|
204
|
+
const currentOpacity = startTransform.backdropOpacity * (1 - easedProgress) + 1 * easedProgress;
|
|
205
|
+
|
|
206
|
+
element.style.transform = `translate(${currentX}px, ${currentY}px) scale(${currentScale})`;
|
|
207
|
+
if (backdrop) {
|
|
208
|
+
backdrop.style.opacity = String(currentOpacity);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle open/close animations (1D based on direction)
|
|
214
|
+
const computeAnimTransform = (): DialogTransform => {
|
|
215
|
+
if (anim.type === "open") {
|
|
216
|
+
return computeOpenTransform(easedProgress, containerSize, openDirection);
|
|
217
|
+
}
|
|
218
|
+
return computeCloseTransform(easedProgress, containerSize, openDirection);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const transform = computeAnimTransform();
|
|
222
|
+
applyTransform(element, backdrop, transform, axis);
|
|
223
|
+
},
|
|
224
|
+
[elementRef, backdropRef, openDirection, axis, measureContainerSize],
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const handleComplete = React.useCallback(() => {
|
|
228
|
+
const anim = animationRef.current;
|
|
229
|
+
animationRef.current = null;
|
|
230
|
+
|
|
231
|
+
if (anim?.type === "open") {
|
|
232
|
+
setPhase("open");
|
|
233
|
+
onOpenComplete?.();
|
|
234
|
+
} else if (anim?.type === "close") {
|
|
235
|
+
setPhase("closed");
|
|
236
|
+
onCloseComplete?.();
|
|
237
|
+
} else {
|
|
238
|
+
// snapback
|
|
239
|
+
setPhase("open");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Reset transform to identity for open state
|
|
243
|
+
if (anim?.type !== "close") {
|
|
244
|
+
const element = elementRef.current;
|
|
245
|
+
const backdrop = backdropRef.current;
|
|
246
|
+
if (element) {
|
|
247
|
+
element.style.transform = "";
|
|
248
|
+
}
|
|
249
|
+
if (backdrop) {
|
|
250
|
+
backdrop.style.opacity = "";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}, [elementRef, backdropRef, onOpenComplete, onCloseComplete]);
|
|
254
|
+
|
|
255
|
+
const { isAnimating, start, cancel } = useAnimationFrame({
|
|
256
|
+
duration: animationDuration,
|
|
257
|
+
easing: easings.easeOutExpo,
|
|
258
|
+
onFrame: handleFrame,
|
|
259
|
+
onComplete: handleComplete,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Handle swipe displacement during operation - free 2D movement
|
|
263
|
+
React.useLayoutEffect(() => {
|
|
264
|
+
if (swipeState.phase !== "operating") {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Cancel any running animation
|
|
269
|
+
if (isAnimating) {
|
|
270
|
+
cancel();
|
|
271
|
+
animationRef.current = null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const element = elementRef.current;
|
|
275
|
+
const backdrop = backdropRef.current;
|
|
276
|
+
|
|
277
|
+
if (!element) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Measure container size if not yet available
|
|
282
|
+
const getContainerSize = (): number => {
|
|
283
|
+
const cachedSize = containerSizeRef.current;
|
|
284
|
+
if (cachedSize > 0) {
|
|
285
|
+
return cachedSize;
|
|
286
|
+
}
|
|
287
|
+
return measureContainerSize();
|
|
288
|
+
};
|
|
289
|
+
const containerSize = getContainerSize();
|
|
290
|
+
if (containerSize <= 0) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Use primary axis displacement for scale/opacity calculation
|
|
295
|
+
const transform = computeSwipeTransform(Math.abs(displacement), containerSize);
|
|
296
|
+
|
|
297
|
+
// Apply 2D translation with scale feedback
|
|
298
|
+
element.style.transform = `translate(${displacement2D.x}px, ${displacement2D.y}px) scale(${transform.scale})`;
|
|
299
|
+
if (backdrop) {
|
|
300
|
+
backdrop.style.opacity = String(transform.backdropOpacity);
|
|
301
|
+
}
|
|
302
|
+
}, [swipeState.phase, displacement, displacement2D, elementRef, backdropRef, isAnimating, cancel, measureContainerSize]);
|
|
303
|
+
|
|
304
|
+
// Handle swipe end - determine if we should close or snap back
|
|
305
|
+
React.useLayoutEffect(() => {
|
|
306
|
+
if (swipeState.phase !== "ended") {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// The actual dismiss decision is made in useDialogSwipeInput
|
|
311
|
+
// Here we just handle the snap-back animation if not dismissed
|
|
312
|
+
// (onSwipeDismiss callback will set visible=false if threshold was met)
|
|
313
|
+
|
|
314
|
+
// If still visible after swipe ended, snap back with 2D animation
|
|
315
|
+
const hasMovement = Math.abs(displacement2D.x) > 1 || Math.abs(displacement2D.y) > 1;
|
|
316
|
+
if (visible && hasMovement) {
|
|
317
|
+
animationRef.current = {
|
|
318
|
+
type: "snapback",
|
|
319
|
+
startPx: displacement,
|
|
320
|
+
start2D: { ...displacement2D },
|
|
321
|
+
};
|
|
322
|
+
setPhase("opening"); // Reuse opening phase for snapback
|
|
323
|
+
start();
|
|
324
|
+
}
|
|
325
|
+
}, [swipeState.phase, visible, displacement, displacement2D, start]);
|
|
326
|
+
|
|
327
|
+
// Handle visibility changes (external control)
|
|
328
|
+
const prevVisibleRef = React.useRef(visible);
|
|
329
|
+
|
|
330
|
+
React.useLayoutEffect(() => {
|
|
331
|
+
const wasVisible = prevVisibleRef.current;
|
|
332
|
+
prevVisibleRef.current = visible;
|
|
333
|
+
|
|
334
|
+
if (wasVisible === visible) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (visible) {
|
|
339
|
+
// Opening
|
|
340
|
+
animationRef.current = { type: "open" };
|
|
341
|
+
setPhase("opening");
|
|
342
|
+
start();
|
|
343
|
+
}
|
|
344
|
+
// Note: closing is handled by triggerClose for better control
|
|
345
|
+
}, [visible, start]);
|
|
346
|
+
|
|
347
|
+
// Trigger close animation
|
|
348
|
+
const triggerClose = React.useCallback(async (): Promise<void> => {
|
|
349
|
+
if (phase === "closing" || phase === "closed") {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
setPhase("closing");
|
|
354
|
+
|
|
355
|
+
const element = elementRef.current;
|
|
356
|
+
const containerSize = containerSizeRef.current;
|
|
357
|
+
|
|
358
|
+
// Try viewTransition first if enabled
|
|
359
|
+
const computeCanUseViewTransition = (): boolean => {
|
|
360
|
+
if (!useViewTransition) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
if (element === null) {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
if (containerSize <= 0) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
};
|
|
371
|
+
const canUseViewTransition = computeCanUseViewTransition();
|
|
372
|
+
if (canUseViewTransition) {
|
|
373
|
+
// TypeScript narrowing: element is guaranteed non-null here
|
|
374
|
+
const dialogElement = element as HTMLElement;
|
|
375
|
+
|
|
376
|
+
// Set CSS variable for current position
|
|
377
|
+
const currentDisplacement = displacement;
|
|
378
|
+
dialogElement.style.setProperty("--swipe-end-position", `${currentDisplacement}px`);
|
|
379
|
+
dialogElement.style.viewTransitionName = "dialog-content";
|
|
380
|
+
|
|
381
|
+
const used = await safeViewTransition(() => {
|
|
382
|
+
// The DOM change callback - this is where onCloseComplete would be called
|
|
383
|
+
// But we need to wait for animation to complete first
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (used) {
|
|
387
|
+
dialogElement.style.viewTransitionName = "";
|
|
388
|
+
setPhase("closed");
|
|
389
|
+
onCloseComplete?.();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Fallback to JS animation
|
|
394
|
+
dialogElement.style.viewTransitionName = "";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// JS-based close animation
|
|
398
|
+
animationRef.current = { type: "close" };
|
|
399
|
+
start();
|
|
400
|
+
}, [phase, elementRef, displacement, useViewTransition, start, onCloseComplete]);
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
phase,
|
|
404
|
+
isAnimating,
|
|
405
|
+
triggerClose,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Type definitions for Drawer swipe gesture handling.
|
|
3
|
+
*/
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Direction for drawer anchor (same as anchor edge).
|
|
9
|
+
*/
|
|
10
|
+
export type DrawerSwipeDirection = "left" | "right" | "top" | "bottom";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for useDrawerSwipeInput hook.
|
|
14
|
+
*/
|
|
15
|
+
export type UseDrawerSwipeInputOptions = {
|
|
16
|
+
/** Container ref for edge detection zone (e.g., GridLayout container) */
|
|
17
|
+
edgeContainerRef: React.RefObject<HTMLElement | null>;
|
|
18
|
+
/** Drawer content ref (for close gesture) */
|
|
19
|
+
drawerContentRef: React.RefObject<HTMLElement | null>;
|
|
20
|
+
/** Drawer direction (anchor edge) */
|
|
21
|
+
direction: DrawerSwipeDirection;
|
|
22
|
+
/** Whether the drawer is currently open */
|
|
23
|
+
isOpen: boolean;
|
|
24
|
+
/** Callback when swipe should open the drawer */
|
|
25
|
+
onSwipeOpen: () => void;
|
|
26
|
+
/** Callback when swipe should close the drawer */
|
|
27
|
+
onSwipeClose: () => void;
|
|
28
|
+
/** Whether edge swipe to open is enabled. @default true */
|
|
29
|
+
enableEdgeSwipeOpen?: boolean;
|
|
30
|
+
/** Whether swipe to close is enabled. @default true */
|
|
31
|
+
enableSwipeClose?: boolean;
|
|
32
|
+
/** Width of edge detection zone in pixels. @default 20 */
|
|
33
|
+
edgeWidth?: number;
|
|
34
|
+
/** Dismiss threshold ratio (0-1). @default 0.3 */
|
|
35
|
+
dismissThreshold?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Result from useDrawerSwipeInput hook.
|
|
40
|
+
*/
|
|
41
|
+
export type UseDrawerSwipeInputResult = {
|
|
42
|
+
/** Current operation state */
|
|
43
|
+
state: ContinuousOperationState;
|
|
44
|
+
/** Whether currently opening via edge swipe */
|
|
45
|
+
isOpening: boolean;
|
|
46
|
+
/** Whether currently closing via drag */
|
|
47
|
+
isClosing: boolean;
|
|
48
|
+
/** Progress (0-1) towards open/close threshold */
|
|
49
|
+
progress: number;
|
|
50
|
+
/** Displacement in pixels (primary axis) */
|
|
51
|
+
displacement: number;
|
|
52
|
+
/** Props for edge container (open gesture zone) */
|
|
53
|
+
edgeContainerProps: React.HTMLAttributes<HTMLElement> & {
|
|
54
|
+
style: React.CSSProperties;
|
|
55
|
+
};
|
|
56
|
+
/** Props for drawer content (close gesture zone) */
|
|
57
|
+
drawerContentProps: React.HTMLAttributes<HTMLElement> & {
|
|
58
|
+
style: React.CSSProperties;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get animation axis from direction.
|
|
64
|
+
*/
|
|
65
|
+
export function getDrawerAnimationAxis(direction: DrawerSwipeDirection): "x" | "y" {
|
|
66
|
+
switch (direction) {
|
|
67
|
+
case "left":
|
|
68
|
+
case "right":
|
|
69
|
+
return "x";
|
|
70
|
+
case "top":
|
|
71
|
+
case "bottom":
|
|
72
|
+
return "y";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the sign for the close swipe direction.
|
|
78
|
+
* Left drawer closes by swiping left (-1).
|
|
79
|
+
* Right drawer closes by swiping right (+1).
|
|
80
|
+
* Top drawer closes by swiping up (-1).
|
|
81
|
+
* Bottom drawer closes by swiping down (+1).
|
|
82
|
+
*/
|
|
83
|
+
export function getDrawerCloseSwipeSign(direction: DrawerSwipeDirection): 1 | -1 {
|
|
84
|
+
switch (direction) {
|
|
85
|
+
case "left":
|
|
86
|
+
return -1;
|
|
87
|
+
case "right":
|
|
88
|
+
return 1;
|
|
89
|
+
case "top":
|
|
90
|
+
return -1;
|
|
91
|
+
case "bottom":
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the sign for the open swipe direction.
|
|
98
|
+
* This is the opposite of the close direction.
|
|
99
|
+
*/
|
|
100
|
+
export function getDrawerOpenSwipeSign(direction: DrawerSwipeDirection): 1 | -1 {
|
|
101
|
+
return (getDrawerCloseSwipeSign(direction) * -1) as 1 | -1;
|
|
102
|
+
}
|