react-panel-layout 0.6.1 → 0.7.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/FloatingWindow-CE-WzkNv.js +1542 -0
- package/dist/FloatingWindow-CE-WzkNv.js.map +1 -0
- package/dist/FloatingWindow-DpFpmX1f.cjs +2 -0
- package/dist/FloatingWindow-DpFpmX1f.cjs.map +1 -0
- package/dist/GridLayout-EwKszYBy.cjs +2 -0
- package/dist/{GridLayout-DKTg_N61.cjs.map → GridLayout-EwKszYBy.cjs.map} +1 -1
- package/dist/GridLayout-kiWdpMLQ.js +947 -0
- package/dist/{GridLayout-UWNxXw77.js.map → GridLayout-kiWdpMLQ.js.map} +1 -1
- package/dist/PanelSystem-Dmy5YI_6.cjs +3 -0
- package/dist/PanelSystem-Dmy5YI_6.cjs.map +1 -0
- package/dist/{PanelSystem-BqUzNtf2.js → PanelSystem-DrYsYwuV.js} +208 -247
- package/dist/PanelSystem-DrYsYwuV.js.map +1 -0
- package/dist/components/window/Drawer.d.ts +1 -0
- package/dist/components/window/DrawerRevealContext.d.ts +61 -0
- package/dist/components/window/drawerRevealAnimationUtils.d.ts +212 -0
- package/dist/components/window/drawerStyles.d.ts +5 -0
- package/dist/components/window/useDrawerSwipeTransform.d.ts +8 -2
- package/dist/components/window/useDrawerTransform.d.ts +68 -0
- package/dist/components/window/useRevealDrawerTransform.d.ts +56 -0
- package/dist/config.cjs +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +8 -7
- package/dist/config.js.map +1 -1
- package/dist/dialog/index.d.ts +1 -1
- package/dist/grid.cjs +1 -1
- package/dist/grid.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +4 -4
- package/dist/modules/dialog/DialogContainer.d.ts +22 -2
- package/dist/modules/dialog/Modal.d.ts +23 -2
- package/dist/modules/dialog/SwipeDialogContainer.d.ts +6 -2
- package/dist/modules/dialog/types.d.ts +12 -0
- package/dist/modules/drawer/drawerStateMachine.d.ts +168 -0
- package/dist/modules/drawer/revealDrawerConstants.d.ts +33 -0
- package/dist/modules/drawer/revealDrawerStateMachine.d.ts +146 -0
- package/dist/modules/drawer/strategies/index.d.ts +8 -0
- package/dist/modules/drawer/strategies/overlayStrategy.d.ts +12 -0
- package/dist/modules/drawer/strategies/revealStrategy.d.ts +12 -0
- package/dist/modules/drawer/strategies/types.d.ts +116 -0
- package/dist/panels.cjs +1 -1
- package/dist/panels.js +1 -1
- package/dist/stack.cjs +1 -1
- package/dist/stack.cjs.map +1 -1
- package/dist/stack.js +306 -347
- package/dist/stack.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/useAnimationFrame-CRuFlk5t.js +394 -0
- package/dist/useAnimationFrame-CRuFlk5t.js.map +1 -0
- package/dist/useAnimationFrame-XRpDXkwV.cjs +2 -0
- package/dist/useAnimationFrame-XRpDXkwV.cjs.map +1 -0
- package/dist/window.cjs +1 -1
- package/dist/window.js +1 -1
- package/package.json +1 -1
- package/src/components/gesture/SwipeSafeZone.tsx +1 -0
- package/src/components/grid/GridLayout.tsx +110 -38
- package/src/components/window/Drawer.tsx +114 -10
- package/src/components/window/DrawerLayers.tsx +48 -15
- package/src/components/window/DrawerRevealContext.spec.ts +20 -0
- package/src/components/window/DrawerRevealContext.tsx +99 -0
- package/src/components/window/drawerRevealAnimationUtils.spec.ts +375 -0
- package/src/components/window/drawerRevealAnimationUtils.ts +415 -0
- package/src/components/window/drawerStyles.spec.ts +39 -0
- package/src/components/window/drawerStyles.ts +24 -0
- package/src/components/window/useDrawerSwipeTransform.ts +28 -90
- package/src/components/window/useDrawerTransform.ts +505 -0
- package/src/components/window/useRevealDrawerTransform.spec.ts +1936 -0
- package/src/components/window/useRevealDrawerTransform.ts +105 -0
- package/src/demo/components/FullscreenDemoPage.tsx +47 -0
- package/src/demo/fullscreenRoutes.tsx +32 -0
- package/src/demo/index.tsx +5 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +23 -8
- package/src/demo/pages/Drawer/components/DrawerBasics.module.css +6 -1
- package/src/demo/pages/Drawer/components/DrawerBasics.tsx +14 -4
- package/src/demo/pages/Drawer/components/DrawerReveal.module.css +157 -0
- package/src/demo/pages/Drawer/components/DrawerReveal.tsx +128 -0
- package/src/demo/pages/Drawer/reveal/index.tsx +17 -0
- package/src/demo/pages/Drawer/reveal-fullscreen/index.tsx +135 -0
- package/src/demo/pages/Drawer/reveal-fullscreen/styles.module.css +233 -0
- package/src/demo/pages/Stack/components/StackBasics.spec.tsx +56 -52
- package/src/demo/pages/Stack/components/StackTablet.spec.tsx +39 -49
- package/src/demo/routes.tsx +2 -0
- package/src/dialog/index.ts +2 -0
- package/src/hooks/gesture/testing/createGestureSimulator.ts +1 -0
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +10 -2
- package/src/hooks/gesture/useSwipeInput.spec.ts +69 -0
- package/src/hooks/gesture/useSwipeInput.ts +2 -0
- package/src/hooks/gesture/utils.ts +15 -4
- package/src/hooks/useAnimatedVisibility.spec.ts +3 -3
- package/src/hooks/useOperationContinuity.spec.ts +17 -10
- package/src/hooks/useOperationContinuity.ts +5 -5
- package/src/hooks/useSharedElementTransition.ts +28 -7
- package/src/modules/dialog/DialogContainer.tsx +39 -5
- package/src/modules/dialog/Modal.tsx +46 -4
- package/src/modules/dialog/SwipeDialogContainer.tsx +12 -2
- package/src/modules/dialog/dialogAnimationUtils.spec.ts +0 -1
- package/src/modules/dialog/types.ts +14 -0
- package/src/modules/dialog/useDialogContainer.spec.ts +11 -3
- package/src/modules/dialog/useDialogSwipeInput.spec.ts +49 -28
- package/src/modules/dialog/useDialogSwipeInput.ts +37 -6
- package/src/modules/dialog/useDialogTransform.spec.ts +63 -30
- package/src/modules/drawer/drawerStateMachine.ts +500 -0
- package/src/modules/drawer/revealDrawerConstants.ts +38 -0
- package/src/modules/drawer/revealDrawerStateMachine.spec.ts +558 -0
- package/src/modules/drawer/revealDrawerStateMachine.ts +197 -0
- package/src/modules/drawer/strategies/index.ts +9 -0
- package/src/modules/drawer/strategies/overlayStrategy.ts +133 -0
- package/src/modules/drawer/strategies/revealStrategy.ts +111 -0
- package/src/modules/drawer/strategies/types.ts +160 -0
- package/src/modules/drawer/useDrawerSwipeInput.ts +7 -4
- package/src/modules/pivot/SwipePivotContent.spec.tsx +48 -37
- package/src/modules/pivot/usePivotSwipeInput.spec.ts +8 -8
- package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1 -1
- package/src/types.ts +15 -0
- package/dist/FloatingWindow-CUXnEtrb.js +0 -827
- package/dist/FloatingWindow-CUXnEtrb.js.map +0 -1
- package/dist/FloatingWindow-DMwyK0eK.cjs +0 -2
- package/dist/FloatingWindow-DMwyK0eK.cjs.map +0 -1
- package/dist/GridLayout-DKTg_N61.cjs +0 -2
- package/dist/GridLayout-UWNxXw77.js +0 -926
- package/dist/PanelSystem-BqUzNtf2.js.map +0 -1
- package/dist/PanelSystem-D603LKKv.cjs +0 -3
- package/dist/PanelSystem-D603LKKv.cjs.map +0 -1
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs +0 -2
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +0 -1
- package/dist/useNativeGestureGuard-CGYo6O0r.js +0 -347
- package/dist/useNativeGestureGuard-CGYo6O0r.js.map +0 -1
- package/src/components/window/useDrawerSwipeTransform.spec.ts +0 -234
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Generic state machine for drawer animations.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a React-independent state machine for managing drawer
|
|
5
|
+
* transitions. It handles swipe gestures, button operations, and animations through
|
|
6
|
+
* a reducer pattern with typed actions.
|
|
7
|
+
*
|
|
8
|
+
* The position type is generic, allowing different drawer modes (reveal, overlay)
|
|
9
|
+
* to use the same state machine logic with mode-specific position calculations.
|
|
10
|
+
*
|
|
11
|
+
* Key design principles:
|
|
12
|
+
* - Pure functions: same input → same output
|
|
13
|
+
* - No React/DOM dependencies
|
|
14
|
+
* - Testable in isolation
|
|
15
|
+
* - Deterministic state transitions
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createAction, createActionHandlerMap } from "../../utils/typedActions.js";
|
|
19
|
+
import type { DrawerTransformStrategy, DrawerStrategyConfig } from "./strategies/types.js";
|
|
20
|
+
import {
|
|
21
|
+
REVEAL_DRAWER_OPEN_DISTANCE_THRESHOLD,
|
|
22
|
+
REVEAL_DRAWER_DISMISS_RATIO,
|
|
23
|
+
} from "./revealDrawerConstants.js";
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Types
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Phase of drawer animation lifecycle.
|
|
31
|
+
* - closed: Drawer is hidden
|
|
32
|
+
* - opening: Drawer is animating open
|
|
33
|
+
* - open: Drawer is fully visible
|
|
34
|
+
* - closing: Drawer is animating closed
|
|
35
|
+
*/
|
|
36
|
+
export type DrawerPhase = "closed" | "opening" | "open" | "closing";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Direction of swipe gesture.
|
|
40
|
+
*/
|
|
41
|
+
export type SwipeDirection = "opening" | "closing" | null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Animation state tracking from/to positions.
|
|
45
|
+
*/
|
|
46
|
+
export type AnimationState<TPosition> = {
|
|
47
|
+
/** Animation type */
|
|
48
|
+
type: "opening" | "closing";
|
|
49
|
+
/** Starting position */
|
|
50
|
+
from: TPosition;
|
|
51
|
+
/** Target position */
|
|
52
|
+
to: TPosition;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Complete state for drawer.
|
|
57
|
+
*/
|
|
58
|
+
export type DrawerState<TPosition> = {
|
|
59
|
+
/** Current animation phase */
|
|
60
|
+
phase: DrawerPhase;
|
|
61
|
+
/** Target open state (what the drawer should be after current operation) */
|
|
62
|
+
targetOpen: boolean;
|
|
63
|
+
/** Current position */
|
|
64
|
+
position: TPosition;
|
|
65
|
+
/** Whether a swipe operation is active */
|
|
66
|
+
isOperating: boolean;
|
|
67
|
+
/** Direction of current swipe */
|
|
68
|
+
swipeDirection: SwipeDirection;
|
|
69
|
+
/** Current swipe displacement in pixels */
|
|
70
|
+
displacement: number;
|
|
71
|
+
/** Animation state if animating */
|
|
72
|
+
animation: AnimationState<TPosition> | null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// Pure Computation Functions
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compute progress (0-1) from displacement.
|
|
81
|
+
*
|
|
82
|
+
* @param displacement - Current swipe displacement in pixels
|
|
83
|
+
* @param drawerSize - Size of the drawer in pixels
|
|
84
|
+
* @param direction - Swipe direction ("opening" or "closing")
|
|
85
|
+
* @returns Progress value between 0 and 1
|
|
86
|
+
*/
|
|
87
|
+
export function computeProgressFromDisplacement(
|
|
88
|
+
displacement: number,
|
|
89
|
+
drawerSize: number,
|
|
90
|
+
direction: SwipeDirection,
|
|
91
|
+
): number {
|
|
92
|
+
if (drawerSize <= 0 || direction === null) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ratio = Math.min(1, Math.max(0, displacement / drawerSize));
|
|
97
|
+
|
|
98
|
+
if (direction === "opening") {
|
|
99
|
+
return ratio;
|
|
100
|
+
}
|
|
101
|
+
return 1 - ratio;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Determine if drawer should open or close after swipe ends.
|
|
106
|
+
*
|
|
107
|
+
* @param displacement - Final swipe displacement in pixels
|
|
108
|
+
* @param drawerSize - Size of the drawer in pixels
|
|
109
|
+
* @param direction - Swipe direction
|
|
110
|
+
* @returns true if drawer should be open, false if closed
|
|
111
|
+
*/
|
|
112
|
+
export function computeSwipeEndTarget(
|
|
113
|
+
displacement: number,
|
|
114
|
+
drawerSize: number,
|
|
115
|
+
direction: SwipeDirection,
|
|
116
|
+
): boolean {
|
|
117
|
+
if (direction === "opening") {
|
|
118
|
+
return displacement >= REVEAL_DRAWER_OPEN_DISTANCE_THRESHOLD;
|
|
119
|
+
}
|
|
120
|
+
if (direction === "closing") {
|
|
121
|
+
const ratio = drawerSize > 0 ? displacement / drawerSize : 0;
|
|
122
|
+
return ratio < REVEAL_DRAWER_DISMISS_RATIO;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ============================================================================
|
|
128
|
+
// Actions
|
|
129
|
+
// ============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Action creators for drawer state machine.
|
|
133
|
+
*/
|
|
134
|
+
export const drawerActions = {
|
|
135
|
+
/** Start swipe operation */
|
|
136
|
+
swipeStart: createAction(
|
|
137
|
+
"SWIPE_START",
|
|
138
|
+
(direction: "opening" | "closing") => ({ direction }),
|
|
139
|
+
),
|
|
140
|
+
|
|
141
|
+
/** Update swipe displacement */
|
|
142
|
+
swipeUpdate: createAction(
|
|
143
|
+
"SWIPE_UPDATE",
|
|
144
|
+
(displacement: number) => ({ displacement }),
|
|
145
|
+
),
|
|
146
|
+
|
|
147
|
+
/** End swipe operation */
|
|
148
|
+
swipeEnd: createAction("SWIPE_END"),
|
|
149
|
+
|
|
150
|
+
/** Animation frame update */
|
|
151
|
+
animationFrame: createAction(
|
|
152
|
+
"ANIMATION_FRAME",
|
|
153
|
+
(easedProgress: number) => ({ easedProgress }),
|
|
154
|
+
),
|
|
155
|
+
|
|
156
|
+
/** Animation complete */
|
|
157
|
+
animationComplete: createAction("ANIMATION_COMPLETE"),
|
|
158
|
+
|
|
159
|
+
/** Button open */
|
|
160
|
+
buttonOpen: createAction("BUTTON_OPEN"),
|
|
161
|
+
|
|
162
|
+
/** Button close */
|
|
163
|
+
buttonClose: createAction("BUTTON_CLOSE"),
|
|
164
|
+
|
|
165
|
+
/** Sync open state from external prop */
|
|
166
|
+
syncOpenState: createAction(
|
|
167
|
+
"SYNC_OPEN_STATE",
|
|
168
|
+
(isOpen: boolean) => ({ isOpen }),
|
|
169
|
+
),
|
|
170
|
+
|
|
171
|
+
/** Initialize state */
|
|
172
|
+
initialize: createAction(
|
|
173
|
+
"INITIALIZE",
|
|
174
|
+
(isOpen: boolean) => ({ isOpen }),
|
|
175
|
+
),
|
|
176
|
+
} as const;
|
|
177
|
+
|
|
178
|
+
export type DrawerAction = ReturnType<
|
|
179
|
+
(typeof drawerActions)[keyof typeof drawerActions]
|
|
180
|
+
>;
|
|
181
|
+
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Initial State Factory
|
|
184
|
+
// ============================================================================
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get target position based on open state.
|
|
188
|
+
*/
|
|
189
|
+
function getTargetPositionFromOpenState<TPosition>(
|
|
190
|
+
isOpen: boolean,
|
|
191
|
+
strategy: DrawerTransformStrategy<TPosition>,
|
|
192
|
+
config: DrawerStrategyConfig,
|
|
193
|
+
): TPosition {
|
|
194
|
+
if (isOpen) {
|
|
195
|
+
return strategy.getOpenPosition(config);
|
|
196
|
+
}
|
|
197
|
+
return strategy.getClosedPosition(config);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Create initial state for drawer.
|
|
202
|
+
*
|
|
203
|
+
* @param isOpen - Initial open state
|
|
204
|
+
* @param strategy - Transform strategy
|
|
205
|
+
* @param config - Drawer configuration
|
|
206
|
+
* @returns Initial state
|
|
207
|
+
*/
|
|
208
|
+
export function createDrawerInitialState<TPosition>(
|
|
209
|
+
isOpen: boolean,
|
|
210
|
+
strategy: DrawerTransformStrategy<TPosition>,
|
|
211
|
+
config: DrawerStrategyConfig,
|
|
212
|
+
): DrawerState<TPosition> {
|
|
213
|
+
const position = getTargetPositionFromOpenState(isOpen, strategy, config);
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
phase: isOpen ? "open" : "closed",
|
|
217
|
+
targetOpen: isOpen,
|
|
218
|
+
position,
|
|
219
|
+
isOperating: false,
|
|
220
|
+
swipeDirection: null,
|
|
221
|
+
displacement: 0,
|
|
222
|
+
animation: null,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============================================================================
|
|
227
|
+
// Reducer Factory
|
|
228
|
+
// ============================================================================
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create a drawer reducer for a specific strategy.
|
|
232
|
+
*
|
|
233
|
+
* @param strategy - Transform strategy to use for position calculations
|
|
234
|
+
* @returns Reducer function for the drawer state machine
|
|
235
|
+
*/
|
|
236
|
+
export function createDrawerReducer<TPosition>(
|
|
237
|
+
strategy: DrawerTransformStrategy<TPosition>,
|
|
238
|
+
) {
|
|
239
|
+
type State = DrawerState<TPosition>;
|
|
240
|
+
type ReducerHandler = (
|
|
241
|
+
state: State,
|
|
242
|
+
action: DrawerAction,
|
|
243
|
+
config: DrawerStrategyConfig,
|
|
244
|
+
) => State;
|
|
245
|
+
|
|
246
|
+
const handlers = createActionHandlerMap<State, typeof drawerActions, DrawerStrategyConfig>(
|
|
247
|
+
drawerActions,
|
|
248
|
+
{
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- callback signature requires config
|
|
250
|
+
swipeStart: (state, action, _config) => {
|
|
251
|
+
if (!("payload" in action)) {return state;}
|
|
252
|
+
return {
|
|
253
|
+
...state,
|
|
254
|
+
isOperating: true,
|
|
255
|
+
swipeDirection: action.payload.direction,
|
|
256
|
+
displacement: 0,
|
|
257
|
+
animation: null,
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
swipeUpdate: (state, action, config) => {
|
|
262
|
+
if (!("payload" in action)) {return state;}
|
|
263
|
+
if (!state.isOperating) {return state;}
|
|
264
|
+
|
|
265
|
+
const progress = computeProgressFromDisplacement(
|
|
266
|
+
action.payload.displacement,
|
|
267
|
+
config.drawerSize,
|
|
268
|
+
state.swipeDirection,
|
|
269
|
+
);
|
|
270
|
+
const position = strategy.computePositionFromProgress(progress, config);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
...state,
|
|
274
|
+
displacement: action.payload.displacement,
|
|
275
|
+
position,
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
swipeEnd: (state, _action, config) => {
|
|
280
|
+
if (!state.isOperating) {return state;}
|
|
281
|
+
|
|
282
|
+
const targetOpen = computeSwipeEndTarget(
|
|
283
|
+
state.displacement,
|
|
284
|
+
config.drawerSize,
|
|
285
|
+
state.swipeDirection,
|
|
286
|
+
);
|
|
287
|
+
const targetPosition = getTargetPositionFromOpenState(
|
|
288
|
+
targetOpen,
|
|
289
|
+
strategy,
|
|
290
|
+
config
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
if (strategy.shouldAnimate(state.position, targetPosition)) {
|
|
294
|
+
const animType = targetOpen ? "opening" : "closing";
|
|
295
|
+
return {
|
|
296
|
+
...state,
|
|
297
|
+
isOperating: false,
|
|
298
|
+
swipeDirection: null,
|
|
299
|
+
displacement: 0,
|
|
300
|
+
targetOpen,
|
|
301
|
+
phase: animType,
|
|
302
|
+
animation: {
|
|
303
|
+
type: animType,
|
|
304
|
+
from: state.position,
|
|
305
|
+
to: targetPosition,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
...state,
|
|
312
|
+
isOperating: false,
|
|
313
|
+
swipeDirection: null,
|
|
314
|
+
displacement: 0,
|
|
315
|
+
targetOpen,
|
|
316
|
+
phase: targetOpen ? "open" : "closed",
|
|
317
|
+
position: targetPosition,
|
|
318
|
+
animation: null,
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- callback signature requires config
|
|
323
|
+
animationFrame: (state, action, _config) => {
|
|
324
|
+
if (!("payload" in action)) {return state;}
|
|
325
|
+
if (!state.animation) {return state;}
|
|
326
|
+
|
|
327
|
+
const position = strategy.interpolatePosition(
|
|
328
|
+
state.animation.from,
|
|
329
|
+
state.animation.to,
|
|
330
|
+
action.payload.easedProgress,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
...state,
|
|
335
|
+
position,
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- callback signature requires action and config
|
|
340
|
+
animationComplete: (state, _action, _config) => {
|
|
341
|
+
if (!state.animation) {return state;}
|
|
342
|
+
|
|
343
|
+
const isOpen = state.animation.type === "opening";
|
|
344
|
+
return {
|
|
345
|
+
...state,
|
|
346
|
+
phase: isOpen ? "open" : "closed",
|
|
347
|
+
position: state.animation.to,
|
|
348
|
+
animation: null,
|
|
349
|
+
};
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
buttonOpen: (state, _action, config) => {
|
|
353
|
+
if (state.phase === "open" || state.phase === "opening") {
|
|
354
|
+
return state;
|
|
355
|
+
}
|
|
356
|
+
if (state.isOperating) {
|
|
357
|
+
return state;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const targetPosition = strategy.getOpenPosition(config);
|
|
361
|
+
const fromPosition = state.position;
|
|
362
|
+
|
|
363
|
+
if (strategy.shouldAnimate(fromPosition, targetPosition)) {
|
|
364
|
+
return {
|
|
365
|
+
...state,
|
|
366
|
+
targetOpen: true,
|
|
367
|
+
phase: "opening",
|
|
368
|
+
animation: {
|
|
369
|
+
type: "opening",
|
|
370
|
+
from: fromPosition,
|
|
371
|
+
to: targetPosition,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
...state,
|
|
378
|
+
targetOpen: true,
|
|
379
|
+
phase: "open",
|
|
380
|
+
position: targetPosition,
|
|
381
|
+
animation: null,
|
|
382
|
+
};
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
buttonClose: (state, _action, config) => {
|
|
386
|
+
if (state.phase === "closed" || state.phase === "closing") {
|
|
387
|
+
return state;
|
|
388
|
+
}
|
|
389
|
+
if (state.isOperating) {
|
|
390
|
+
return state;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const targetPosition = strategy.getClosedPosition(config);
|
|
394
|
+
const fromPosition = state.position;
|
|
395
|
+
|
|
396
|
+
if (strategy.shouldAnimate(fromPosition, targetPosition)) {
|
|
397
|
+
return {
|
|
398
|
+
...state,
|
|
399
|
+
targetOpen: false,
|
|
400
|
+
phase: "closing",
|
|
401
|
+
animation: {
|
|
402
|
+
type: "closing",
|
|
403
|
+
from: fromPosition,
|
|
404
|
+
to: targetPosition,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
...state,
|
|
411
|
+
targetOpen: false,
|
|
412
|
+
phase: "closed",
|
|
413
|
+
position: targetPosition,
|
|
414
|
+
animation: null,
|
|
415
|
+
};
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
syncOpenState: (state, action, config) => {
|
|
419
|
+
if (!("payload" in action)) {return state;}
|
|
420
|
+
|
|
421
|
+
const { isOpen } = action.payload;
|
|
422
|
+
|
|
423
|
+
if (state.targetOpen === isOpen && !state.isOperating && !state.animation) {
|
|
424
|
+
return state;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (state.isOperating) {
|
|
428
|
+
return {
|
|
429
|
+
...state,
|
|
430
|
+
targetOpen: isOpen,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (state.animation) {
|
|
435
|
+
const animTarget = state.animation.type === "opening";
|
|
436
|
+
if (animTarget === isOpen) {
|
|
437
|
+
return {
|
|
438
|
+
...state,
|
|
439
|
+
targetOpen: isOpen,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const currentlyOpen = state.phase === "open" || state.phase === "opening";
|
|
445
|
+
if (currentlyOpen === isOpen) {
|
|
446
|
+
return {
|
|
447
|
+
...state,
|
|
448
|
+
targetOpen: isOpen,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const targetPosition = getTargetPositionFromOpenState(
|
|
453
|
+
isOpen,
|
|
454
|
+
strategy,
|
|
455
|
+
config
|
|
456
|
+
);
|
|
457
|
+
const fromPosition = state.position;
|
|
458
|
+
|
|
459
|
+
if (strategy.shouldAnimate(fromPosition, targetPosition)) {
|
|
460
|
+
const animType = isOpen ? "opening" : "closing";
|
|
461
|
+
return {
|
|
462
|
+
...state,
|
|
463
|
+
targetOpen: isOpen,
|
|
464
|
+
phase: animType,
|
|
465
|
+
animation: {
|
|
466
|
+
type: animType,
|
|
467
|
+
from: fromPosition,
|
|
468
|
+
to: targetPosition,
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
...state,
|
|
475
|
+
targetOpen: isOpen,
|
|
476
|
+
phase: isOpen ? "open" : "closed",
|
|
477
|
+
position: targetPosition,
|
|
478
|
+
animation: null,
|
|
479
|
+
};
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
initialize: (_state, action, config) => {
|
|
483
|
+
if (!("payload" in action)) {return _state;}
|
|
484
|
+
return createDrawerInitialState(action.payload.isOpen, strategy, config);
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
return function drawerReducer(
|
|
490
|
+
state: State,
|
|
491
|
+
action: DrawerAction,
|
|
492
|
+
config: DrawerStrategyConfig,
|
|
493
|
+
): State {
|
|
494
|
+
const handler = handlers[action.type] as ReducerHandler | undefined;
|
|
495
|
+
if (handler) {
|
|
496
|
+
return handler(state, action, config);
|
|
497
|
+
}
|
|
498
|
+
return state;
|
|
499
|
+
};
|
|
500
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Constants for reveal drawer state machine.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes all threshold values used in reveal drawer behavior.
|
|
5
|
+
* These values control swipe gestures, animations, and state transitions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimum swipe distance in pixels to trigger drawer open.
|
|
10
|
+
* Used when opening via edge swipe gesture.
|
|
11
|
+
* ~27% of a 375px mobile screen width.
|
|
12
|
+
*/
|
|
13
|
+
export const REVEAL_DRAWER_OPEN_DISTANCE_THRESHOLD = 100;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ratio of drawer size that must be swiped to dismiss (close) the drawer.
|
|
17
|
+
* Value between 0 and 1. When swiping to close, if displacement exceeds
|
|
18
|
+
* this ratio of the drawer size, the drawer will close.
|
|
19
|
+
*/
|
|
20
|
+
export const REVEAL_DRAWER_DISMISS_RATIO = 0.3;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Percentage offset for drawer when closed.
|
|
24
|
+
* The drawer is positioned at -30% (or +30% for right/bottom) when closed,
|
|
25
|
+
* creating a "pull out" parallax effect during the reveal animation.
|
|
26
|
+
*/
|
|
27
|
+
export const REVEAL_DRAWER_CLOSED_OFFSET_PERCENT = 30;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Animation duration in milliseconds for reveal transitions.
|
|
31
|
+
*/
|
|
32
|
+
export const REVEAL_DRAWER_ANIMATION_DURATION = 300;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Threshold in pixels below which positions are considered equal.
|
|
36
|
+
* Used to determine if animation is needed between two positions.
|
|
37
|
+
*/
|
|
38
|
+
export const REVEAL_DRAWER_SNAP_THRESHOLD = 1;
|