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
|
@@ -23,7 +23,7 @@ export type UseSwipeContentTransformOptions = {
|
|
|
23
23
|
/** Current swipe displacement in pixels */
|
|
24
24
|
displacement: number;
|
|
25
25
|
/** Whether swipe gesture is active */
|
|
26
|
-
|
|
26
|
+
isOperating: boolean;
|
|
27
27
|
/** Axis of transformation */
|
|
28
28
|
axis?: GestureAxis;
|
|
29
29
|
/** Duration of snap animation in ms */
|
|
@@ -42,6 +42,13 @@ export type UseSwipeContentTransformOptions = {
|
|
|
42
42
|
* Use this for push animations where new panel comes from off-screen.
|
|
43
43
|
*/
|
|
44
44
|
initialPx?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Skip animation when targetPx changes.
|
|
47
|
+
* Use this when the target changed during an operation (from useOperationContinuity).
|
|
48
|
+
* When true, target changes will snap instead of animate.
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
skipTargetChangeAnimation?: boolean;
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
/**
|
|
@@ -73,6 +80,119 @@ const getTransformFn = (axis: GestureAxis): "translateX" | "translateY" => {
|
|
|
73
80
|
return axis === "horizontal" ? "translateX" : "translateY";
|
|
74
81
|
};
|
|
75
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Check if initial mount animation should be scheduled.
|
|
85
|
+
* Returns animation info if conditions are met, null otherwise.
|
|
86
|
+
*
|
|
87
|
+
* Conditions for scheduling:
|
|
88
|
+
* 1. Animation not already consumed
|
|
89
|
+
* 2. containerSize is valid (> 0) - handles React effect execution order
|
|
90
|
+
* 3. initialPx is provided
|
|
91
|
+
* 4. initialPx differs from targetPx
|
|
92
|
+
*/
|
|
93
|
+
const computeInitialMountAnimation = (
|
|
94
|
+
hasConsumed: boolean,
|
|
95
|
+
containerSize: number | undefined,
|
|
96
|
+
initialPx: number | undefined,
|
|
97
|
+
targetPx: number,
|
|
98
|
+
): { from: number; to: number } | null => {
|
|
99
|
+
if (hasConsumed) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
if (containerSize === undefined) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (containerSize <= 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
if (initialPx === undefined) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (initialPx === targetPx) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return { from: initialPx, to: targetPx };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Result type for target change handling.
|
|
119
|
+
*/
|
|
120
|
+
type TargetChangeResult =
|
|
121
|
+
| { type: "animate"; animation: { from: number; to: number } }
|
|
122
|
+
| { type: "snap"; position: number }
|
|
123
|
+
| { type: "none" };
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Compute action for target position change when not swiping.
|
|
127
|
+
* Returns the appropriate action: animate, snap, or none.
|
|
128
|
+
*
|
|
129
|
+
* @param skipAnimation - If true, skip animation and snap directly.
|
|
130
|
+
* Use this when the target changed during an operation (from useOperationContinuity).
|
|
131
|
+
*/
|
|
132
|
+
const computeTargetChangeAction = (
|
|
133
|
+
targetPx: number,
|
|
134
|
+
prevTargetPx: number,
|
|
135
|
+
currentPx: number,
|
|
136
|
+
isOperating: boolean,
|
|
137
|
+
isAnimating: boolean,
|
|
138
|
+
animateOnTargetChange: boolean,
|
|
139
|
+
skipAnimation: boolean,
|
|
140
|
+
): TargetChangeResult => {
|
|
141
|
+
if (targetPx === prevTargetPx) {
|
|
142
|
+
return { type: "none" };
|
|
143
|
+
}
|
|
144
|
+
if (isOperating) {
|
|
145
|
+
return { type: "none" };
|
|
146
|
+
}
|
|
147
|
+
if (isAnimating) {
|
|
148
|
+
return { type: "none" };
|
|
149
|
+
}
|
|
150
|
+
if (!animateOnTargetChange) {
|
|
151
|
+
return { type: "snap", position: targetPx };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const distance = Math.abs(currentPx - targetPx);
|
|
155
|
+
if (distance <= 1) {
|
|
156
|
+
return { type: "snap", position: targetPx };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Skip animation if requested (e.g., role changed during operation)
|
|
160
|
+
// This prevents unwanted animations after an operation ends.
|
|
161
|
+
// However, allow forward animations (currentPx < targetPx) for normal swipe-to-complete.
|
|
162
|
+
// Only skip backward animations (currentPx > targetPx) which occur during over-swipe.
|
|
163
|
+
if (skipAnimation) {
|
|
164
|
+
// Backward direction (over-swipe): snap, don't animate backward
|
|
165
|
+
if (currentPx > targetPx) {
|
|
166
|
+
return { type: "snap", position: targetPx };
|
|
167
|
+
}
|
|
168
|
+
// Forward direction (normal swipe-to-complete): animate from current position
|
|
169
|
+
return { type: "animate", animation: { from: currentPx, to: targetPx } };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { type: "animate", animation: { from: currentPx, to: targetPx } };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if container size change requires position snap.
|
|
177
|
+
* Returns the new position to snap to, or null if no snap needed.
|
|
178
|
+
*/
|
|
179
|
+
const computeContainerResizeSnap = (
|
|
180
|
+
containerSize: number | undefined,
|
|
181
|
+
prevContainerSize: number | undefined,
|
|
182
|
+
targetPx: number,
|
|
183
|
+
): number | null => {
|
|
184
|
+
if (containerSize === undefined) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
if (containerSize === prevContainerSize) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (containerSize <= 0) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return targetPx;
|
|
194
|
+
};
|
|
195
|
+
|
|
76
196
|
/**
|
|
77
197
|
* Hook for DOM-based swipe content transform.
|
|
78
198
|
*
|
|
@@ -86,7 +206,7 @@ const getTransformFn = (axis: GestureAxis): "translateX" | "translateY" => {
|
|
|
86
206
|
* elementRef: containerRef,
|
|
87
207
|
* targetPx: 0,
|
|
88
208
|
* displacement: inputState.displacement.x,
|
|
89
|
-
*
|
|
209
|
+
* isOperating: inputState.phase === "swiping",
|
|
90
210
|
* });
|
|
91
211
|
* ```
|
|
92
212
|
*/
|
|
@@ -97,12 +217,13 @@ export function useSwipeContentTransform(
|
|
|
97
217
|
elementRef,
|
|
98
218
|
targetPx,
|
|
99
219
|
displacement,
|
|
100
|
-
|
|
220
|
+
isOperating,
|
|
101
221
|
axis = "horizontal",
|
|
102
222
|
animationDuration = DEFAULT_ANIMATION_DURATION,
|
|
103
223
|
containerSize,
|
|
104
224
|
animateOnTargetChange = false,
|
|
105
225
|
initialPx,
|
|
226
|
+
skipTargetChangeAnimation = false,
|
|
106
227
|
} = options;
|
|
107
228
|
|
|
108
229
|
// Use initialPx if provided, otherwise use targetPx
|
|
@@ -112,36 +233,47 @@ export function useSwipeContentTransform(
|
|
|
112
233
|
const prevTargetPxRef = React.useRef<number>(targetPx);
|
|
113
234
|
const prevContainerSizeRef = React.useRef<number | undefined>(containerSize);
|
|
114
235
|
const pendingAnimationRef = React.useRef<{ from: number; to: number } | null>(null);
|
|
115
|
-
|
|
236
|
+
// Track if initial mount animation has been consumed
|
|
237
|
+
const hasConsumedInitialMountRef = React.useRef<boolean>(false);
|
|
116
238
|
|
|
117
|
-
// Schedule animation on first mount if initialPx differs from targetPx
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
239
|
+
// Schedule animation on first mount if initialPx differs from targetPx.
|
|
240
|
+
const initialMountAnimation = computeInitialMountAnimation(
|
|
241
|
+
hasConsumedInitialMountRef.current,
|
|
242
|
+
containerSize,
|
|
243
|
+
initialPx,
|
|
244
|
+
targetPx,
|
|
245
|
+
);
|
|
246
|
+
if (initialMountAnimation !== null) {
|
|
247
|
+
pendingAnimationRef.current = initialMountAnimation;
|
|
248
|
+
hasConsumedInitialMountRef.current = true;
|
|
123
249
|
}
|
|
124
250
|
|
|
125
251
|
// Handle target changes when not swiping
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
252
|
+
const targetChangeAction = computeTargetChangeAction(
|
|
253
|
+
targetPx,
|
|
254
|
+
prevTargetPxRef.current,
|
|
255
|
+
currentPxRef.current,
|
|
256
|
+
isOperating,
|
|
257
|
+
animRef.current !== null,
|
|
258
|
+
animateOnTargetChange,
|
|
259
|
+
skipTargetChangeAnimation,
|
|
260
|
+
);
|
|
261
|
+
if (targetChangeAction.type === "animate") {
|
|
262
|
+
pendingAnimationRef.current = targetChangeAction.animation;
|
|
263
|
+
prevTargetPxRef.current = targetPx;
|
|
264
|
+
} else if (targetChangeAction.type === "snap") {
|
|
265
|
+
currentPxRef.current = targetChangeAction.position;
|
|
139
266
|
prevTargetPxRef.current = targetPx;
|
|
140
267
|
}
|
|
141
268
|
|
|
142
269
|
// Snap when container size changes (resize)
|
|
143
|
-
|
|
144
|
-
|
|
270
|
+
const resizeSnapPosition = computeContainerResizeSnap(
|
|
271
|
+
containerSize,
|
|
272
|
+
prevContainerSizeRef.current,
|
|
273
|
+
targetPx,
|
|
274
|
+
);
|
|
275
|
+
if (resizeSnapPosition !== null) {
|
|
276
|
+
currentPxRef.current = resizeSnapPosition;
|
|
145
277
|
prevContainerSizeRef.current = containerSize;
|
|
146
278
|
}
|
|
147
279
|
|
|
@@ -175,7 +307,7 @@ export function useSwipeContentTransform(
|
|
|
175
307
|
|
|
176
308
|
// When swipe ends or target changes with animateOnTargetChange, animate to target
|
|
177
309
|
React.useLayoutEffect(() => {
|
|
178
|
-
if (
|
|
310
|
+
if (isOperating) {
|
|
179
311
|
cancel();
|
|
180
312
|
animRef.current = null;
|
|
181
313
|
pendingAnimationRef.current = null;
|
|
@@ -208,7 +340,7 @@ export function useSwipeContentTransform(
|
|
|
208
340
|
currentPxRef.current = targetPx;
|
|
209
341
|
prevTargetPxRef.current = targetPx;
|
|
210
342
|
}
|
|
211
|
-
}, [
|
|
343
|
+
}, [isOperating, targetPx, start, cancel]);
|
|
212
344
|
|
|
213
345
|
// Direct DOM update during swipe
|
|
214
346
|
React.useLayoutEffect(() => {
|
|
@@ -218,7 +350,13 @@ export function useSwipeContentTransform(
|
|
|
218
350
|
}
|
|
219
351
|
|
|
220
352
|
// Skip if animation is running, about to start, or pending
|
|
221
|
-
if (isAnimating
|
|
353
|
+
if (isAnimating) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (animRef.current !== null) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (pendingAnimationRef.current !== null) {
|
|
222
360
|
return;
|
|
223
361
|
}
|
|
224
362
|
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for AlertDialog component
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
5
|
+
import { AlertDialog } from "./AlertDialog";
|
|
6
|
+
|
|
7
|
+
type CallTracker = {
|
|
8
|
+
calls: ReadonlyArray<ReadonlyArray<unknown>>;
|
|
9
|
+
fn: (...args: ReadonlyArray<unknown>) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const createCallTracker = (): CallTracker => {
|
|
13
|
+
const calls: Array<ReadonlyArray<unknown>> = [];
|
|
14
|
+
const fn = (...args: ReadonlyArray<unknown>): void => {
|
|
15
|
+
calls.push(args);
|
|
16
|
+
};
|
|
17
|
+
return { calls, fn };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("AlertDialog", () => {
|
|
21
|
+
const originalShowModal = HTMLDialogElement.prototype.showModal;
|
|
22
|
+
const originalClose = HTMLDialogElement.prototype.close;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
HTMLDialogElement.prototype.showModal = function (this: HTMLDialogElement) {
|
|
26
|
+
this.setAttribute("open", "");
|
|
27
|
+
};
|
|
28
|
+
HTMLDialogElement.prototype.close = function (this: HTMLDialogElement) {
|
|
29
|
+
this.removeAttribute("open");
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
HTMLDialogElement.prototype.showModal = originalShowModal;
|
|
35
|
+
HTMLDialogElement.prototype.close = originalClose;
|
|
36
|
+
document.body.style.overflow = "";
|
|
37
|
+
document.body.style.paddingRight = "";
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("alert type", () => {
|
|
41
|
+
it("should render message", () => {
|
|
42
|
+
render(
|
|
43
|
+
<AlertDialog
|
|
44
|
+
type="alert"
|
|
45
|
+
visible={true}
|
|
46
|
+
message="This is an alert message"
|
|
47
|
+
onConfirm={() => {}}
|
|
48
|
+
onCancel={() => {}}
|
|
49
|
+
/>,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
expect(screen.getByText("This is an alert message")).toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should render title when provided", () => {
|
|
56
|
+
render(
|
|
57
|
+
<AlertDialog
|
|
58
|
+
type="alert"
|
|
59
|
+
visible={true}
|
|
60
|
+
title="Alert Title"
|
|
61
|
+
message="Message"
|
|
62
|
+
onConfirm={() => {}}
|
|
63
|
+
onCancel={() => {}}
|
|
64
|
+
/>,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(screen.getByText("Alert Title")).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should only show OK button", () => {
|
|
71
|
+
render(
|
|
72
|
+
<AlertDialog type="alert" visible={true} message="Message" onConfirm={() => {}} onCancel={() => {}} />,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(screen.getByRole("button", { name: "OK" })).toBeInTheDocument();
|
|
76
|
+
expect(screen.queryByRole("button", { name: "Cancel" })).not.toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should call onConfirm when OK is clicked", () => {
|
|
80
|
+
const onConfirm = createCallTracker();
|
|
81
|
+
render(
|
|
82
|
+
<AlertDialog type="alert" visible={true} message="Message" onConfirm={onConfirm.fn} onCancel={() => {}} />,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
86
|
+
|
|
87
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should use custom confirmLabel", () => {
|
|
91
|
+
render(
|
|
92
|
+
<AlertDialog
|
|
93
|
+
type="alert"
|
|
94
|
+
visible={true}
|
|
95
|
+
message="Message"
|
|
96
|
+
confirmLabel="Got it"
|
|
97
|
+
onConfirm={() => {}}
|
|
98
|
+
onCancel={() => {}}
|
|
99
|
+
/>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(screen.getByRole("button", { name: "Got it" })).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("confirm type", () => {
|
|
107
|
+
it("should show OK and Cancel buttons", () => {
|
|
108
|
+
render(
|
|
109
|
+
<AlertDialog type="confirm" visible={true} message="Are you sure?" onConfirm={() => {}} onCancel={() => {}} />,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
expect(screen.getByRole("button", { name: "OK" })).toBeInTheDocument();
|
|
113
|
+
expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should call onConfirm when OK is clicked", () => {
|
|
117
|
+
const onConfirm = createCallTracker();
|
|
118
|
+
render(
|
|
119
|
+
<AlertDialog type="confirm" visible={true} message="Are you sure?" onConfirm={onConfirm.fn} onCancel={() => {}} />,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
123
|
+
|
|
124
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should call onCancel when Cancel is clicked", () => {
|
|
128
|
+
const onCancel = createCallTracker();
|
|
129
|
+
render(
|
|
130
|
+
<AlertDialog type="confirm" visible={true} message="Are you sure?" onConfirm={() => {}} onCancel={onCancel.fn} />,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
134
|
+
|
|
135
|
+
expect(onCancel.calls).toHaveLength(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should call onCancel when backdrop is clicked", () => {
|
|
139
|
+
const onCancel = createCallTracker();
|
|
140
|
+
render(
|
|
141
|
+
<AlertDialog
|
|
142
|
+
type="confirm"
|
|
143
|
+
visible={true}
|
|
144
|
+
message="Are you sure?"
|
|
145
|
+
onConfirm={() => {}}
|
|
146
|
+
onCancel={onCancel.fn}
|
|
147
|
+
/>,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const dialog = document.querySelector("dialog");
|
|
151
|
+
fireEvent.click(dialog!);
|
|
152
|
+
|
|
153
|
+
expect(onCancel.calls).toHaveLength(1);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should use custom button labels", () => {
|
|
157
|
+
render(
|
|
158
|
+
<AlertDialog
|
|
159
|
+
type="confirm"
|
|
160
|
+
visible={true}
|
|
161
|
+
message="Delete?"
|
|
162
|
+
confirmLabel="Delete"
|
|
163
|
+
cancelLabel="Keep"
|
|
164
|
+
onConfirm={() => {}}
|
|
165
|
+
onCancel={() => {}}
|
|
166
|
+
/>,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(screen.getByRole("button", { name: "Delete" })).toBeInTheDocument();
|
|
170
|
+
expect(screen.getByRole("button", { name: "Keep" })).toBeInTheDocument();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe("prompt type", () => {
|
|
175
|
+
it("should show input field", () => {
|
|
176
|
+
render(
|
|
177
|
+
<AlertDialog
|
|
178
|
+
type="prompt"
|
|
179
|
+
visible={true}
|
|
180
|
+
message="Enter your name:"
|
|
181
|
+
onConfirm={() => {}}
|
|
182
|
+
onCancel={() => {}}
|
|
183
|
+
/>,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("should show placeholder", () => {
|
|
190
|
+
render(
|
|
191
|
+
<AlertDialog
|
|
192
|
+
type="prompt"
|
|
193
|
+
visible={true}
|
|
194
|
+
message="Enter your name:"
|
|
195
|
+
placeholder="John Doe"
|
|
196
|
+
onConfirm={() => {}}
|
|
197
|
+
onCancel={() => {}}
|
|
198
|
+
/>,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(screen.getByRole("textbox")).toHaveAttribute("placeholder", "John Doe");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should show default value", () => {
|
|
205
|
+
render(
|
|
206
|
+
<AlertDialog
|
|
207
|
+
type="prompt"
|
|
208
|
+
visible={true}
|
|
209
|
+
message="Enter your name:"
|
|
210
|
+
defaultValue="Initial Value"
|
|
211
|
+
onConfirm={() => {}}
|
|
212
|
+
onCancel={() => {}}
|
|
213
|
+
/>,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
expect(screen.getByRole("textbox")).toHaveValue("Initial Value");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should update input value on change", () => {
|
|
220
|
+
render(
|
|
221
|
+
<AlertDialog
|
|
222
|
+
type="prompt"
|
|
223
|
+
visible={true}
|
|
224
|
+
message="Enter your name:"
|
|
225
|
+
onConfirm={() => {}}
|
|
226
|
+
onCancel={() => {}}
|
|
227
|
+
/>,
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
231
|
+
fireEvent.change(input, { target: { value: "Test Value" } });
|
|
232
|
+
|
|
233
|
+
expect(input.value).toBe("Test Value");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should call onConfirm with input value when OK is clicked", () => {
|
|
237
|
+
const onConfirm = createCallTracker();
|
|
238
|
+
render(
|
|
239
|
+
<AlertDialog
|
|
240
|
+
type="prompt"
|
|
241
|
+
visible={true}
|
|
242
|
+
message="Enter your name:"
|
|
243
|
+
defaultValue="Hello"
|
|
244
|
+
onConfirm={onConfirm.fn}
|
|
245
|
+
onCancel={() => {}}
|
|
246
|
+
/>,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
250
|
+
|
|
251
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
252
|
+
expect(onConfirm.calls[0]?.[0]).toBe("Hello");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should call onConfirm with changed input value", () => {
|
|
256
|
+
const onConfirm = createCallTracker();
|
|
257
|
+
render(
|
|
258
|
+
<AlertDialog
|
|
259
|
+
type="prompt"
|
|
260
|
+
visible={true}
|
|
261
|
+
message="Enter your name:"
|
|
262
|
+
onConfirm={onConfirm.fn}
|
|
263
|
+
onCancel={() => {}}
|
|
264
|
+
/>,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const input = screen.getByRole("textbox");
|
|
268
|
+
fireEvent.change(input, { target: { value: "Changed Value" } });
|
|
269
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
270
|
+
|
|
271
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
272
|
+
expect(onConfirm.calls[0]?.[0]).toBe("Changed Value");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should call onConfirm when Enter is pressed in input", () => {
|
|
276
|
+
const onConfirm = createCallTracker();
|
|
277
|
+
render(
|
|
278
|
+
<AlertDialog
|
|
279
|
+
type="prompt"
|
|
280
|
+
visible={true}
|
|
281
|
+
message="Enter your name:"
|
|
282
|
+
defaultValue="Test"
|
|
283
|
+
onConfirm={onConfirm.fn}
|
|
284
|
+
onCancel={() => {}}
|
|
285
|
+
/>,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const input = screen.getByRole("textbox");
|
|
289
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
290
|
+
|
|
291
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
292
|
+
expect(onConfirm.calls[0]?.[0]).toBe("Test");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should call onCancel when Cancel is clicked", () => {
|
|
296
|
+
const onCancel = createCallTracker();
|
|
297
|
+
render(
|
|
298
|
+
<AlertDialog
|
|
299
|
+
type="prompt"
|
|
300
|
+
visible={true}
|
|
301
|
+
message="Enter your name:"
|
|
302
|
+
onConfirm={() => {}}
|
|
303
|
+
onCancel={onCancel.fn}
|
|
304
|
+
/>,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
308
|
+
|
|
309
|
+
expect(onCancel.calls).toHaveLength(1);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should support password input type", () => {
|
|
313
|
+
render(
|
|
314
|
+
<AlertDialog
|
|
315
|
+
type="prompt"
|
|
316
|
+
visible={true}
|
|
317
|
+
message="Enter password:"
|
|
318
|
+
inputType="password"
|
|
319
|
+
onConfirm={() => {}}
|
|
320
|
+
onCancel={() => {}}
|
|
321
|
+
/>,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const input = screen.getByLabelText("Input");
|
|
325
|
+
expect(input).toHaveAttribute("type", "password");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("should focus input when dialog opens", async () => {
|
|
329
|
+
render(
|
|
330
|
+
<AlertDialog
|
|
331
|
+
type="prompt"
|
|
332
|
+
visible={true}
|
|
333
|
+
message="Enter your name:"
|
|
334
|
+
onConfirm={() => {}}
|
|
335
|
+
onCancel={() => {}}
|
|
336
|
+
/>,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const input = screen.getByRole("textbox");
|
|
340
|
+
await waitFor(() => {
|
|
341
|
+
expect(document.activeElement).toBe(input);
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("should reset input value when dialog reopens", () => {
|
|
346
|
+
const { rerender } = render(
|
|
347
|
+
<AlertDialog
|
|
348
|
+
type="prompt"
|
|
349
|
+
visible={true}
|
|
350
|
+
message="Enter your name:"
|
|
351
|
+
defaultValue="Initial"
|
|
352
|
+
onConfirm={() => {}}
|
|
353
|
+
onCancel={() => {}}
|
|
354
|
+
/>,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const input = screen.getByRole("textbox") as HTMLInputElement;
|
|
358
|
+
fireEvent.change(input, { target: { value: "Changed" } });
|
|
359
|
+
expect(input.value).toBe("Changed");
|
|
360
|
+
|
|
361
|
+
// Close and reopen
|
|
362
|
+
rerender(
|
|
363
|
+
<AlertDialog
|
|
364
|
+
type="prompt"
|
|
365
|
+
visible={false}
|
|
366
|
+
message="Enter your name:"
|
|
367
|
+
defaultValue="Initial"
|
|
368
|
+
onConfirm={() => {}}
|
|
369
|
+
onCancel={() => {}}
|
|
370
|
+
/>,
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
rerender(
|
|
374
|
+
<AlertDialog
|
|
375
|
+
type="prompt"
|
|
376
|
+
visible={true}
|
|
377
|
+
message="Enter your name:"
|
|
378
|
+
defaultValue="Initial"
|
|
379
|
+
onConfirm={() => {}}
|
|
380
|
+
onCancel={() => {}}
|
|
381
|
+
/>,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
expect(input.value).toBe("Initial");
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|