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,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for dialog animation utilities
|
|
3
|
+
*/
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
computeCloseTransform,
|
|
7
|
+
computeOpenTransform,
|
|
8
|
+
computeSwipeTransform,
|
|
9
|
+
shouldDismiss,
|
|
10
|
+
getAnimationAxis,
|
|
11
|
+
getDirectionSign,
|
|
12
|
+
getCloseDirectionFromSwipe,
|
|
13
|
+
supportsViewTransitions,
|
|
14
|
+
buildTransformString,
|
|
15
|
+
} from "./dialogAnimationUtils.js";
|
|
16
|
+
|
|
17
|
+
describe("dialogAnimationUtils", () => {
|
|
18
|
+
describe("getAnimationAxis", () => {
|
|
19
|
+
it("returns 'y' for center direction", () => {
|
|
20
|
+
expect(getAnimationAxis("center")).toBe("y");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("returns 'y' for bottom direction", () => {
|
|
24
|
+
expect(getAnimationAxis("bottom")).toBe("y");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns 'y' for top direction", () => {
|
|
28
|
+
expect(getAnimationAxis("top")).toBe("y");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns 'x' for left direction", () => {
|
|
32
|
+
expect(getAnimationAxis("left")).toBe("x");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("returns 'x' for right direction", () => {
|
|
36
|
+
expect(getAnimationAxis("right")).toBe("x");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("getDirectionSign", () => {
|
|
41
|
+
it("returns 1 for center (default down)", () => {
|
|
42
|
+
expect(getDirectionSign("center")).toBe(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns 1 for bottom", () => {
|
|
46
|
+
expect(getDirectionSign("bottom")).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns -1 for top", () => {
|
|
50
|
+
expect(getDirectionSign("top")).toBe(-1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns -1 for left", () => {
|
|
54
|
+
expect(getDirectionSign("left")).toBe(-1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns 1 for right", () => {
|
|
58
|
+
expect(getDirectionSign("right")).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("computeCloseTransform", () => {
|
|
63
|
+
const containerSize = 800;
|
|
64
|
+
|
|
65
|
+
it("returns identity transform at progress 0", () => {
|
|
66
|
+
const result = computeCloseTransform(0, containerSize, "bottom");
|
|
67
|
+
expect(result.translate).toBe(0);
|
|
68
|
+
expect(result.scale).toBe(1);
|
|
69
|
+
expect(result.backdropOpacity).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns final transform at progress 1", () => {
|
|
73
|
+
const result = computeCloseTransform(1, containerSize, "bottom");
|
|
74
|
+
// 50% of container = 400px translate
|
|
75
|
+
expect(result.translate).toBeCloseTo(400);
|
|
76
|
+
// Final scale is 0.85
|
|
77
|
+
expect(result.scale).toBeCloseTo(0.85);
|
|
78
|
+
// Backdrop should be faded
|
|
79
|
+
expect(result.backdropOpacity).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("phase 1 only applies translate", () => {
|
|
83
|
+
const result = computeCloseTransform(0.5, containerSize, "bottom");
|
|
84
|
+
// Still in phase 1 (before 0.7)
|
|
85
|
+
expect(result.scale).toBe(1);
|
|
86
|
+
expect(result.translate).toBeGreaterThan(0);
|
|
87
|
+
expect(result.translate).toBeLessThan(400);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("phase 2 applies scale reduction", () => {
|
|
91
|
+
const result = computeCloseTransform(0.85, containerSize, "bottom");
|
|
92
|
+
// In phase 2 (after 0.7)
|
|
93
|
+
expect(result.scale).toBeLessThan(1);
|
|
94
|
+
expect(result.scale).toBeGreaterThan(0.85);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("uses negative translate for top direction", () => {
|
|
98
|
+
const result = computeCloseTransform(1, containerSize, "top");
|
|
99
|
+
expect(result.translate).toBeLessThan(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("uses negative translate for left direction", () => {
|
|
103
|
+
const result = computeCloseTransform(1, containerSize, "left");
|
|
104
|
+
expect(result.translate).toBeLessThan(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("computeOpenTransform", () => {
|
|
109
|
+
const containerSize = 800;
|
|
110
|
+
|
|
111
|
+
it("returns closed state at progress 0", () => {
|
|
112
|
+
const result = computeOpenTransform(0, containerSize, "bottom");
|
|
113
|
+
expect(result.translate).toBeCloseTo(400);
|
|
114
|
+
expect(result.scale).toBeCloseTo(0.85);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns identity transform at progress 1", () => {
|
|
118
|
+
const result = computeOpenTransform(1, containerSize, "bottom");
|
|
119
|
+
expect(result.translate).toBe(0);
|
|
120
|
+
expect(result.scale).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("is the reverse of computeCloseTransform", () => {
|
|
124
|
+
const closeAt30 = computeCloseTransform(0.3, containerSize, "bottom");
|
|
125
|
+
const openAt70 = computeOpenTransform(0.7, containerSize, "bottom");
|
|
126
|
+
|
|
127
|
+
expect(closeAt30.translate).toBeCloseTo(openAt70.translate, 5);
|
|
128
|
+
expect(closeAt30.scale).toBeCloseTo(openAt70.scale, 5);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("computeSwipeTransform", () => {
|
|
133
|
+
const containerSize = 400;
|
|
134
|
+
|
|
135
|
+
it("returns identity transform at displacement 0", () => {
|
|
136
|
+
const result = computeSwipeTransform(0, containerSize);
|
|
137
|
+
expect(result.translate).toBe(0);
|
|
138
|
+
expect(result.scale).toBe(1);
|
|
139
|
+
expect(result.backdropOpacity).toBe(1);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("translates by displacement amount", () => {
|
|
143
|
+
const result = computeSwipeTransform(100, containerSize);
|
|
144
|
+
expect(result.translate).toBe(100);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("applies light scale feedback during swipe", () => {
|
|
148
|
+
const result = computeSwipeTransform(200, containerSize);
|
|
149
|
+
// 50% progress, 2% max reduction = 1% reduction
|
|
150
|
+
expect(result.scale).toBeCloseTo(0.99);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("fades backdrop with swipe progress", () => {
|
|
154
|
+
const result = computeSwipeTransform(200, containerSize);
|
|
155
|
+
// 50% progress, 80% max fade = 40% fade
|
|
156
|
+
expect(result.backdropOpacity).toBeCloseTo(0.6);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("clamps progress at 1", () => {
|
|
160
|
+
const result = computeSwipeTransform(800, containerSize);
|
|
161
|
+
expect(result.scale).toBeCloseTo(0.98); // Max 2% reduction
|
|
162
|
+
expect(result.backdropOpacity).toBeCloseTo(0.2); // 80% fade
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("shouldDismiss", () => {
|
|
167
|
+
const containerSize = 400;
|
|
168
|
+
|
|
169
|
+
it("returns true when displacement exceeds threshold", () => {
|
|
170
|
+
// 30% threshold = 120px
|
|
171
|
+
const result = shouldDismiss(130, 0, containerSize);
|
|
172
|
+
expect(result).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("returns false when displacement is below threshold", () => {
|
|
176
|
+
const result = shouldDismiss(100, 0, containerSize);
|
|
177
|
+
expect(result).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("returns true when velocity is high", () => {
|
|
181
|
+
// 0.5 px/ms threshold
|
|
182
|
+
const result = shouldDismiss(50, 0.6, containerSize);
|
|
183
|
+
expect(result).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("returns false when velocity is low", () => {
|
|
187
|
+
const result = shouldDismiss(50, 0.3, containerSize);
|
|
188
|
+
expect(result).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("respects custom threshold", () => {
|
|
192
|
+
// Custom 50% threshold = 200px
|
|
193
|
+
const belowThreshold = shouldDismiss(190, 0, containerSize, 0.5);
|
|
194
|
+
const aboveThreshold = shouldDismiss(210, 0, containerSize, 0.5);
|
|
195
|
+
|
|
196
|
+
expect(belowThreshold).toBe(false);
|
|
197
|
+
expect(aboveThreshold).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("getCloseDirectionFromSwipe", () => {
|
|
202
|
+
it("returns bottom for positive Y displacement", () => {
|
|
203
|
+
const result = getCloseDirectionFromSwipe(0, 100);
|
|
204
|
+
expect(result).toBe("bottom");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("returns top for negative Y displacement", () => {
|
|
208
|
+
const result = getCloseDirectionFromSwipe(0, -100);
|
|
209
|
+
expect(result).toBe("top");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("returns right for positive X displacement", () => {
|
|
213
|
+
const result = getCloseDirectionFromSwipe(100, 0);
|
|
214
|
+
expect(result).toBe("right");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("returns left for negative X displacement", () => {
|
|
218
|
+
const result = getCloseDirectionFromSwipe(-100, 0);
|
|
219
|
+
expect(result).toBe("left");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("prefers Y over X when equal", () => {
|
|
223
|
+
const result = getCloseDirectionFromSwipe(100, 100);
|
|
224
|
+
expect(result).toBe("bottom");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("returns default when both are zero", () => {
|
|
228
|
+
const result = getCloseDirectionFromSwipe(0, 0, "left");
|
|
229
|
+
expect(result).toBe("left");
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe("supportsViewTransitions", () => {
|
|
234
|
+
it("returns a boolean", () => {
|
|
235
|
+
const result = supportsViewTransitions();
|
|
236
|
+
expect(typeof result).toBe("boolean");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("buildTransformString", () => {
|
|
241
|
+
it("builds translateY for y axis", () => {
|
|
242
|
+
const transform = { translate: 100, scale: 0.9, backdropOpacity: 0.5 };
|
|
243
|
+
const result = buildTransformString(transform, "y");
|
|
244
|
+
expect(result).toBe("translateY(100px) scale(0.9)");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("builds translateX for x axis", () => {
|
|
248
|
+
const transform = { translate: -50, scale: 1, backdropOpacity: 1 };
|
|
249
|
+
const result = buildTransformString(transform, "x");
|
|
250
|
+
expect(result).toBe("translateX(-50px) scale(1)");
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Dialog animation utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides multi-phase animation calculations and viewTransition support
|
|
5
|
+
* for dialog open/close animations with "suck in" effect.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { easings } from "../../hooks/useAnimationFrame.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Direction from which the dialog opens.
|
|
12
|
+
* The close animation uses the opposite direction.
|
|
13
|
+
*/
|
|
14
|
+
export type DialogOpenDirection = "center" | "top" | "bottom" | "left" | "right";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Phase transition point for multi-phase animation.
|
|
18
|
+
* Phase 1: 0 to PHASE_TRANSITION (translate movement)
|
|
19
|
+
* Phase 2: PHASE_TRANSITION to 1 (scale + accelerated translate)
|
|
20
|
+
*/
|
|
21
|
+
const PHASE_TRANSITION = 0.7;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Final scale value for "suck in" effect.
|
|
25
|
+
*/
|
|
26
|
+
const FINAL_SCALE = 0.85;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Transform values for dialog animation.
|
|
30
|
+
*/
|
|
31
|
+
export type DialogTransform = {
|
|
32
|
+
/** Translate value in pixels (x or y depending on direction) */
|
|
33
|
+
translate: number;
|
|
34
|
+
/** Scale value (1 = normal, 0.85 = final suck-in) */
|
|
35
|
+
scale: number;
|
|
36
|
+
/** Backdrop opacity (1 = fully visible, 0 = transparent) */
|
|
37
|
+
backdropOpacity: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Axis for the dialog animation based on direction.
|
|
42
|
+
*/
|
|
43
|
+
export type DialogAnimationAxis = "x" | "y";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get the animation axis for a given direction.
|
|
47
|
+
*/
|
|
48
|
+
export function getAnimationAxis(direction: DialogOpenDirection): DialogAnimationAxis {
|
|
49
|
+
switch (direction) {
|
|
50
|
+
case "left":
|
|
51
|
+
case "right":
|
|
52
|
+
return "x";
|
|
53
|
+
case "top":
|
|
54
|
+
case "bottom":
|
|
55
|
+
case "center":
|
|
56
|
+
return "y";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the sign multiplier for translate based on direction.
|
|
62
|
+
* Positive means moving in the positive axis direction.
|
|
63
|
+
*/
|
|
64
|
+
export function getDirectionSign(direction: DialogOpenDirection): number {
|
|
65
|
+
switch (direction) {
|
|
66
|
+
case "top":
|
|
67
|
+
case "left":
|
|
68
|
+
return -1;
|
|
69
|
+
case "bottom":
|
|
70
|
+
case "right":
|
|
71
|
+
case "center":
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Compute the close animation transform values.
|
|
78
|
+
*
|
|
79
|
+
* The close animation has two phases:
|
|
80
|
+
* - Phase 1 (0-70%): Translate movement with easeOutExpo (natural deceleration)
|
|
81
|
+
* - Phase 2 (70-100%): Scale shrink + accelerated translate with easeInExpo ("suck in" effect)
|
|
82
|
+
*
|
|
83
|
+
* @param progress - Animation progress (0 = open, 1 = fully closed)
|
|
84
|
+
* @param containerSize - Size of the container in the animation direction (width or height)
|
|
85
|
+
* @param direction - Direction the dialog is closing towards
|
|
86
|
+
* @returns Transform values for the current progress
|
|
87
|
+
*/
|
|
88
|
+
export function computeCloseTransform(
|
|
89
|
+
progress: number,
|
|
90
|
+
containerSize: number,
|
|
91
|
+
direction: DialogOpenDirection = "bottom",
|
|
92
|
+
): DialogTransform {
|
|
93
|
+
const sign = getDirectionSign(direction);
|
|
94
|
+
const translateTarget = containerSize * 0.5; // Move 50% of container size
|
|
95
|
+
|
|
96
|
+
if (progress < PHASE_TRANSITION) {
|
|
97
|
+
// Phase 1: translate only, no scale change
|
|
98
|
+
const phase1Progress = progress / PHASE_TRANSITION;
|
|
99
|
+
const easedProgress = easings.easeOutExpo(phase1Progress);
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
translate: sign * translateTarget * easedProgress * 0.7, // 70% of translate in phase 1
|
|
103
|
+
scale: 1.0,
|
|
104
|
+
backdropOpacity: 1.0 - progress * 0.5, // Fade backdrop gradually
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Phase 2: scale + accelerated translate
|
|
109
|
+
const phase2Progress = (progress - PHASE_TRANSITION) / (1 - PHASE_TRANSITION);
|
|
110
|
+
const easedProgress = easeInExpo(phase2Progress);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
translate: sign * translateTarget * (0.7 + 0.3 * easedProgress),
|
|
114
|
+
scale: 1.0 - (1.0 - FINAL_SCALE) * easedProgress,
|
|
115
|
+
backdropOpacity: 0.5 - 0.5 * easedProgress, // Finish fading
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Compute the open animation transform values.
|
|
121
|
+
* This is the reverse of the close animation.
|
|
122
|
+
*
|
|
123
|
+
* @param progress - Animation progress (0 = closed, 1 = fully open)
|
|
124
|
+
* @param containerSize - Size of the container in the animation direction
|
|
125
|
+
* @param direction - Direction the dialog is opening from
|
|
126
|
+
* @returns Transform values for the current progress
|
|
127
|
+
*/
|
|
128
|
+
export function computeOpenTransform(
|
|
129
|
+
progress: number,
|
|
130
|
+
containerSize: number,
|
|
131
|
+
direction: DialogOpenDirection = "bottom",
|
|
132
|
+
): DialogTransform {
|
|
133
|
+
// Reverse the close animation
|
|
134
|
+
return computeCloseTransform(1 - progress, containerSize, direction);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Compute transform values during a swipe gesture.
|
|
139
|
+
* This provides real-time feedback as the user swipes.
|
|
140
|
+
*
|
|
141
|
+
* @param displacement - Current swipe displacement in pixels
|
|
142
|
+
* @param containerSize - Size of the container in the animation direction
|
|
143
|
+
* @returns Transform values for current swipe state
|
|
144
|
+
*/
|
|
145
|
+
export function computeSwipeTransform(
|
|
146
|
+
displacement: number,
|
|
147
|
+
containerSize: number,
|
|
148
|
+
): DialogTransform {
|
|
149
|
+
const progress = Math.min(Math.abs(displacement) / containerSize, 1);
|
|
150
|
+
|
|
151
|
+
// Light scale feedback during swipe (max 2% reduction)
|
|
152
|
+
const scale = 1.0 - 0.02 * progress;
|
|
153
|
+
|
|
154
|
+
// Backdrop fades with swipe progress
|
|
155
|
+
const backdropOpacity = 1.0 - progress * 0.8;
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
translate: displacement,
|
|
159
|
+
scale,
|
|
160
|
+
backdropOpacity,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if the swipe should trigger a dismiss action.
|
|
166
|
+
*
|
|
167
|
+
* @param displacement - Final swipe displacement in pixels
|
|
168
|
+
* @param velocity - Final swipe velocity in px/ms
|
|
169
|
+
* @param containerSize - Size of the container
|
|
170
|
+
* @param threshold - Threshold ratio (0-1) for dismiss (default: 0.3)
|
|
171
|
+
* @returns Whether the dialog should be dismissed
|
|
172
|
+
*/
|
|
173
|
+
export function shouldDismiss(
|
|
174
|
+
displacement: number,
|
|
175
|
+
velocity: number,
|
|
176
|
+
containerSize: number,
|
|
177
|
+
threshold: number = 0.3,
|
|
178
|
+
): boolean {
|
|
179
|
+
const absDisplacement = Math.abs(displacement);
|
|
180
|
+
const absVelocity = Math.abs(velocity);
|
|
181
|
+
|
|
182
|
+
// Dismiss if displacement exceeds threshold
|
|
183
|
+
if (absDisplacement >= containerSize * threshold) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Dismiss if velocity is high enough (quick flick)
|
|
188
|
+
if (absVelocity >= 0.5) {
|
|
189
|
+
// 0.5 px/ms = 500 px/s
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get the close direction based on swipe displacement.
|
|
198
|
+
* The dialog closes in the direction of the swipe.
|
|
199
|
+
*
|
|
200
|
+
* @param displacementX - Horizontal displacement
|
|
201
|
+
* @param displacementY - Vertical displacement
|
|
202
|
+
* @param defaultDirection - Default direction if no clear winner
|
|
203
|
+
* @returns The determined close direction
|
|
204
|
+
*/
|
|
205
|
+
export function getCloseDirectionFromSwipe(
|
|
206
|
+
displacementX: number,
|
|
207
|
+
displacementY: number,
|
|
208
|
+
defaultDirection: DialogOpenDirection = "bottom",
|
|
209
|
+
): DialogOpenDirection {
|
|
210
|
+
const absX = Math.abs(displacementX);
|
|
211
|
+
const absY = Math.abs(displacementY);
|
|
212
|
+
|
|
213
|
+
if (absY > absX) {
|
|
214
|
+
return displacementY > 0 ? "bottom" : "top";
|
|
215
|
+
}
|
|
216
|
+
if (absX > absY) {
|
|
217
|
+
return displacementX > 0 ? "right" : "left";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return defaultDirection;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check if View Transitions API is supported.
|
|
225
|
+
*/
|
|
226
|
+
export function supportsViewTransitions(): boolean {
|
|
227
|
+
if (typeof document === "undefined") {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return "startViewTransition" in document;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Execute a callback with View Transitions API, with fallback.
|
|
235
|
+
*
|
|
236
|
+
* @param callback - The DOM-changing callback to wrap
|
|
237
|
+
* @returns Promise<boolean> - true if viewTransition was used, false if fallback
|
|
238
|
+
*/
|
|
239
|
+
export async function safeViewTransition(callback: () => void): Promise<boolean> {
|
|
240
|
+
if (!supportsViewTransitions()) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const transition = (document as Document & { startViewTransition: (cb: () => void) => ViewTransition }).startViewTransition(callback);
|
|
246
|
+
await transition.finished;
|
|
247
|
+
return true;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.warn("viewTransition failed, using JS fallback:", error);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* ViewTransition type (for TypeScript).
|
|
256
|
+
*/
|
|
257
|
+
type ViewTransition = {
|
|
258
|
+
finished: Promise<void>;
|
|
259
|
+
ready: Promise<void>;
|
|
260
|
+
updateCallbackDone: Promise<void>;
|
|
261
|
+
skipTransition: () => void;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Ease-in exponential function for "suck in" effect.
|
|
266
|
+
* Accelerates towards the end.
|
|
267
|
+
*/
|
|
268
|
+
function easeInExpo(t: number): number {
|
|
269
|
+
if (t === 0) {
|
|
270
|
+
return 0;
|
|
271
|
+
}
|
|
272
|
+
return Math.pow(2, 10 * t - 10);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Build CSS transform string from transform values.
|
|
277
|
+
*
|
|
278
|
+
* @param transform - Transform values
|
|
279
|
+
* @param axis - Animation axis ('x' or 'y')
|
|
280
|
+
* @returns CSS transform string
|
|
281
|
+
*/
|
|
282
|
+
export function buildTransformString(transform: DialogTransform, axis: DialogAnimationAxis): string {
|
|
283
|
+
const translateFn = axis === "x" ? "translateX" : "translateY";
|
|
284
|
+
return `${translateFn}(${transform.translate}px) scale(${transform.scale})`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Build CSS for backdrop based on transform values.
|
|
289
|
+
*
|
|
290
|
+
* @param transform - Transform values
|
|
291
|
+
* @returns CSS properties for backdrop
|
|
292
|
+
*/
|
|
293
|
+
export function buildBackdropStyle(transform: DialogTransform): React.CSSProperties {
|
|
294
|
+
return {
|
|
295
|
+
opacity: transform.backdropOpacity,
|
|
296
|
+
};
|
|
297
|
+
}
|