react-panel-layout 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FloatingWindow-Bw2djgpz.js +1542 -0
- package/dist/FloatingWindow-Bw2djgpz.js.map +1 -0
- package/dist/FloatingWindow-Cvyokf0m.cjs +2 -0
- package/dist/FloatingWindow-Cvyokf0m.cjs.map +1 -0
- package/dist/GridLayout-B4aCqSyd.js +947 -0
- package/dist/{GridLayout-UWNxXw77.js.map → GridLayout-B4aCqSyd.js.map} +1 -1
- package/dist/GridLayout-DNOClFzz.cjs +2 -0
- package/dist/{GridLayout-DKTg_N61.cjs.map → GridLayout-DNOClFzz.cjs.map} +1 -1
- package/dist/PanelSystem-B8Igvnb2.cjs +3 -0
- package/dist/PanelSystem-B8Igvnb2.cjs.map +1 -0
- package/dist/{PanelSystem-BqUzNtf2.js → PanelSystem-DDUSFjXD.js} +208 -247
- package/dist/PanelSystem-DDUSFjXD.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/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/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-BZ6D2lMq.cjs +2 -0
- package/dist/useAnimationFrame-BZ6D2lMq.cjs.map +1 -0
- package/dist/useAnimationFrame-Bg4e-H8O.js +394 -0
- package/dist/useAnimationFrame-Bg4e-H8O.js.map +1 -0
- package/dist/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/hooks/gesture/testing/createGestureSimulator.ts +1 -0
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +10 -2
- 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/dialogAnimationUtils.spec.ts +0 -1
- 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,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unified hook for drawer transform animations.
|
|
3
|
+
*
|
|
4
|
+
* This hook provides a single interface for both reveal and overlay drawer modes.
|
|
5
|
+
* It uses a strategy pattern internally to handle mode-specific position calculations
|
|
6
|
+
* and DOM operations while sharing the common state machine logic.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { useAnimationFrame, easings } from "../../hooks/useAnimationFrame.js";
|
|
10
|
+
import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
|
|
11
|
+
import type { DrawerPlacement } from "./drawerStyles.js";
|
|
12
|
+
import {
|
|
13
|
+
createDrawerReducer,
|
|
14
|
+
createDrawerInitialState,
|
|
15
|
+
drawerActions,
|
|
16
|
+
type DrawerPhase,
|
|
17
|
+
type DrawerAction,
|
|
18
|
+
} from "../../modules/drawer/drawerStateMachine.js";
|
|
19
|
+
import {
|
|
20
|
+
revealStrategy,
|
|
21
|
+
overlayStrategy,
|
|
22
|
+
type DrawerStrategyConfig,
|
|
23
|
+
type DrawerElements,
|
|
24
|
+
type DrawerTransformStrategy,
|
|
25
|
+
type RevealPosition,
|
|
26
|
+
type OverlayPosition,
|
|
27
|
+
} from "../../modules/drawer/strategies/index.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Union type for drawer positions (reveal or overlay mode).
|
|
31
|
+
*/
|
|
32
|
+
type DrawerPosition = RevealPosition | OverlayPosition;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Strategy type that works with either position type.
|
|
36
|
+
*/
|
|
37
|
+
type AnyDrawerStrategy = DrawerTransformStrategy<DrawerPosition>;
|
|
38
|
+
import { REVEAL_DRAWER_ANIMATION_DURATION } from "../../modules/drawer/revealDrawerConstants.js";
|
|
39
|
+
import {
|
|
40
|
+
applyOverflowHidden,
|
|
41
|
+
clearOverflowHidden,
|
|
42
|
+
getContentElement,
|
|
43
|
+
clearDrawerVisibility,
|
|
44
|
+
} from "./drawerRevealAnimationUtils.js";
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Types
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Drawer animation mode.
|
|
52
|
+
*/
|
|
53
|
+
export type DrawerMode = "reveal" | "overlay";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Phase of drawer animation lifecycle.
|
|
57
|
+
*/
|
|
58
|
+
export type DrawerAnimationPhase = "idle" | "opening" | "open" | "closing" | "closed";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Options for useDrawerTransform hook.
|
|
62
|
+
*/
|
|
63
|
+
export type UseDrawerTransformOptions = {
|
|
64
|
+
/** Animation mode */
|
|
65
|
+
mode: DrawerMode;
|
|
66
|
+
/** Ref to the drawer element */
|
|
67
|
+
drawerRef: React.RefObject<HTMLElement | null>;
|
|
68
|
+
/** Ref to the content element (reveal mode) */
|
|
69
|
+
contentRef?: React.RefObject<HTMLElement | null>;
|
|
70
|
+
/** Ref to the backdrop element (overlay mode) */
|
|
71
|
+
backdropRef?: React.RefObject<HTMLElement | null>;
|
|
72
|
+
/** Drawer placement */
|
|
73
|
+
placement: DrawerPlacement;
|
|
74
|
+
/** Drawer size in pixels */
|
|
75
|
+
drawerSize: number;
|
|
76
|
+
/** Whether the drawer is open */
|
|
77
|
+
isOpen: boolean;
|
|
78
|
+
/** Current swipe state */
|
|
79
|
+
swipeState: ContinuousOperationState;
|
|
80
|
+
/** Current swipe displacement */
|
|
81
|
+
displacement: number;
|
|
82
|
+
/** Whether opening via swipe */
|
|
83
|
+
isOpening: boolean;
|
|
84
|
+
/** Whether closing via swipe */
|
|
85
|
+
isClosing: boolean;
|
|
86
|
+
/** Whether this hook is enabled */
|
|
87
|
+
enabled: boolean;
|
|
88
|
+
/** Whether drawer is inline (affects content element for reveal mode) */
|
|
89
|
+
inline?: boolean;
|
|
90
|
+
/** Grid container ref for inline mode (reveal mode) */
|
|
91
|
+
gridRef?: React.RefObject<HTMLElement | null>;
|
|
92
|
+
/** Content background color (reveal mode) */
|
|
93
|
+
contentBackground?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Result from useDrawerTransform hook.
|
|
98
|
+
*/
|
|
99
|
+
export type UseDrawerTransformResult = {
|
|
100
|
+
/** Current animation phase */
|
|
101
|
+
phase: DrawerAnimationPhase;
|
|
102
|
+
/** Whether any animation is running */
|
|
103
|
+
isAnimating: boolean;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Implementation
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Select strategy based on drawer mode.
|
|
112
|
+
*/
|
|
113
|
+
function selectStrategy(mode: DrawerMode): AnyDrawerStrategy {
|
|
114
|
+
if (mode === "reveal") {
|
|
115
|
+
return revealStrategy as AnyDrawerStrategy;
|
|
116
|
+
}
|
|
117
|
+
return overlayStrategy as AnyDrawerStrategy;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert state machine phase to animation phase.
|
|
122
|
+
*/
|
|
123
|
+
function toAnimationPhase(phase: DrawerPhase): DrawerAnimationPhase {
|
|
124
|
+
return phase;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Unified hook for drawer transform animations.
|
|
129
|
+
*
|
|
130
|
+
* Supports both reveal and overlay modes through a strategy pattern.
|
|
131
|
+
*/
|
|
132
|
+
export function useDrawerTransform(
|
|
133
|
+
options: UseDrawerTransformOptions,
|
|
134
|
+
): UseDrawerTransformResult {
|
|
135
|
+
const {
|
|
136
|
+
mode,
|
|
137
|
+
drawerRef,
|
|
138
|
+
contentRef,
|
|
139
|
+
backdropRef,
|
|
140
|
+
placement,
|
|
141
|
+
drawerSize,
|
|
142
|
+
isOpen,
|
|
143
|
+
swipeState,
|
|
144
|
+
displacement,
|
|
145
|
+
isOpening,
|
|
146
|
+
isClosing,
|
|
147
|
+
enabled,
|
|
148
|
+
inline = false,
|
|
149
|
+
gridRef,
|
|
150
|
+
contentBackground = "#fff",
|
|
151
|
+
} = options;
|
|
152
|
+
|
|
153
|
+
const isOperating = swipeState.phase === "operating";
|
|
154
|
+
|
|
155
|
+
// Select strategy based on mode (cast to AnyDrawerStrategy for generic state machine)
|
|
156
|
+
const strategy: AnyDrawerStrategy = selectStrategy(mode);
|
|
157
|
+
|
|
158
|
+
// Config for strategy
|
|
159
|
+
const config: DrawerStrategyConfig = React.useMemo(
|
|
160
|
+
() => ({ placement, drawerSize, contentBackground }),
|
|
161
|
+
[placement, drawerSize, contentBackground],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Create reducer for the selected strategy
|
|
165
|
+
const reducer = React.useMemo(
|
|
166
|
+
() => createDrawerReducer(strategy),
|
|
167
|
+
[strategy],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// State machine state
|
|
171
|
+
const stateRef = React.useRef(
|
|
172
|
+
createDrawerInitialState(isOpen, strategy, config),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// React state for phase (for external API)
|
|
176
|
+
const [phase, setPhase] = React.useState<DrawerAnimationPhase>(
|
|
177
|
+
isOpen ? "open" : "closed",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Track previous values for detecting transitions
|
|
181
|
+
// Note: enabled starts as false to ensure initialization runs on first mount.
|
|
182
|
+
// This is critical for React Strict Mode compatibility where effects run twice.
|
|
183
|
+
const prevRef = React.useRef({
|
|
184
|
+
isOperating: false,
|
|
185
|
+
isOpening: false,
|
|
186
|
+
isClosing: false,
|
|
187
|
+
isOpen,
|
|
188
|
+
enabled: false,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Resolve elements based on mode
|
|
192
|
+
const resolveContentElement = (): HTMLElement | null => {
|
|
193
|
+
if (mode !== "reveal") {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return contentRef?.current ?? getContentElement(inline, gridRef);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const resolveBackdropElement = (): HTMLElement | null => {
|
|
200
|
+
if (mode !== "overlay") {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return backdropRef?.current ?? null;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const getElements = React.useCallback((): DrawerElements => {
|
|
207
|
+
return {
|
|
208
|
+
drawer: drawerRef.current,
|
|
209
|
+
content: resolveContentElement(),
|
|
210
|
+
backdrop: resolveBackdropElement(),
|
|
211
|
+
};
|
|
212
|
+
}, [mode, drawerRef, contentRef, backdropRef, inline, gridRef]);
|
|
213
|
+
|
|
214
|
+
// Dispatch helper
|
|
215
|
+
const dispatch = React.useCallback(
|
|
216
|
+
(action: DrawerAction) => {
|
|
217
|
+
const newState = reducer(stateRef.current, action, config);
|
|
218
|
+
stateRef.current = newState;
|
|
219
|
+
setPhase(toAnimationPhase(newState.phase));
|
|
220
|
+
return newState;
|
|
221
|
+
},
|
|
222
|
+
[reducer, config],
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// Animation frame handler
|
|
226
|
+
const handleFrame = React.useCallback(
|
|
227
|
+
({ easedProgress }: { easedProgress: number }) => {
|
|
228
|
+
const state = stateRef.current;
|
|
229
|
+
if (!state.animation) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const position = strategy.interpolatePosition(
|
|
234
|
+
state.animation.from,
|
|
235
|
+
state.animation.to,
|
|
236
|
+
easedProgress,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
stateRef.current = { ...state, position };
|
|
240
|
+
|
|
241
|
+
const elements = getElements();
|
|
242
|
+
strategy.applyToDOM(position, elements, config);
|
|
243
|
+
},
|
|
244
|
+
[strategy, getElements, config],
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Animation complete handler
|
|
248
|
+
const handleComplete = React.useCallback(() => {
|
|
249
|
+
const newState = dispatch(drawerActions.animationComplete());
|
|
250
|
+
const elements = getElements();
|
|
251
|
+
|
|
252
|
+
if (newState.phase === "closed") {
|
|
253
|
+
strategy.onClosingComplete?.(elements, config);
|
|
254
|
+
}
|
|
255
|
+
}, [dispatch, strategy, getElements, config]);
|
|
256
|
+
|
|
257
|
+
const { isAnimating, start, cancel } = useAnimationFrame({
|
|
258
|
+
duration: REVEAL_DRAWER_ANIMATION_DURATION,
|
|
259
|
+
easing: easings.easeOutExpo,
|
|
260
|
+
onFrame: handleFrame,
|
|
261
|
+
onComplete: handleComplete,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Initialize drawer state on mount/enable
|
|
265
|
+
React.useLayoutEffect(() => {
|
|
266
|
+
if (!enabled) {
|
|
267
|
+
prevRef.current.enabled = false;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (prevRef.current.enabled) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
prevRef.current.enabled = true;
|
|
275
|
+
|
|
276
|
+
// Re-initialize state machine
|
|
277
|
+
stateRef.current = createDrawerInitialState(isOpen, strategy, config);
|
|
278
|
+
setPhase(isOpen ? "open" : "closed");
|
|
279
|
+
|
|
280
|
+
const elements = getElements();
|
|
281
|
+
|
|
282
|
+
if (isOpen) {
|
|
283
|
+
strategy.onOpeningStart?.(elements, config);
|
|
284
|
+
strategy.applyToDOM(stateRef.current.position, elements, config);
|
|
285
|
+
} else {
|
|
286
|
+
strategy.onClosingComplete?.(elements, config);
|
|
287
|
+
}
|
|
288
|
+
}, [enabled, isOpen, strategy, config, getElements]);
|
|
289
|
+
|
|
290
|
+
// Handle swipe start/end
|
|
291
|
+
React.useLayoutEffect(() => {
|
|
292
|
+
if (!enabled) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const prev = prevRef.current;
|
|
297
|
+
const wasOperating = prev.isOperating;
|
|
298
|
+
const wasOpening = prev.isOpening;
|
|
299
|
+
const wasClosing = prev.isClosing;
|
|
300
|
+
|
|
301
|
+
prev.isOperating = isOperating;
|
|
302
|
+
prev.isOpening = isOpening;
|
|
303
|
+
prev.isClosing = isClosing;
|
|
304
|
+
|
|
305
|
+
// Swipe start
|
|
306
|
+
if (!wasOperating && isOperating) {
|
|
307
|
+
cancel();
|
|
308
|
+
const direction = isOpening ? "opening" : isClosing ? "closing" : null;
|
|
309
|
+
if (direction) {
|
|
310
|
+
dispatch(drawerActions.swipeStart(direction));
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Swipe end
|
|
316
|
+
if (wasOperating && !isOperating) {
|
|
317
|
+
const newState = dispatch(drawerActions.swipeEnd());
|
|
318
|
+
const elements = getElements();
|
|
319
|
+
|
|
320
|
+
if (newState.animation) {
|
|
321
|
+
strategy.onOpeningStart?.(elements, config);
|
|
322
|
+
start();
|
|
323
|
+
} else {
|
|
324
|
+
if (newState.phase === "open") {
|
|
325
|
+
strategy.onOpeningStart?.(elements, config);
|
|
326
|
+
strategy.applyToDOM(newState.position, elements, config);
|
|
327
|
+
} else if (newState.phase === "closed") {
|
|
328
|
+
strategy.onClosingComplete?.(elements, config);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Direction change during operation
|
|
335
|
+
if (isOperating) {
|
|
336
|
+
const currentDirection = isOpening ? "opening" : isClosing ? "closing" : null;
|
|
337
|
+
const prevDirection = wasOpening ? "opening" : wasClosing ? "closing" : null;
|
|
338
|
+
|
|
339
|
+
if (currentDirection !== prevDirection && currentDirection) {
|
|
340
|
+
dispatch(drawerActions.swipeStart(currentDirection));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}, [
|
|
344
|
+
enabled,
|
|
345
|
+
isOperating,
|
|
346
|
+
isOpening,
|
|
347
|
+
isClosing,
|
|
348
|
+
dispatch,
|
|
349
|
+
cancel,
|
|
350
|
+
start,
|
|
351
|
+
strategy,
|
|
352
|
+
config,
|
|
353
|
+
getElements,
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
// Handle displacement updates during swipe
|
|
357
|
+
React.useLayoutEffect(() => {
|
|
358
|
+
if (!enabled) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (!isOperating) {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (!isOpening && !isClosing) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const newState = dispatch(drawerActions.swipeUpdate(displacement));
|
|
369
|
+
const elements = getElements();
|
|
370
|
+
|
|
371
|
+
strategy.onOpeningStart?.(elements, config);
|
|
372
|
+
strategy.applyToDOM(newState.position, elements, config);
|
|
373
|
+
}, [
|
|
374
|
+
enabled,
|
|
375
|
+
isOperating,
|
|
376
|
+
isOpening,
|
|
377
|
+
isClosing,
|
|
378
|
+
displacement,
|
|
379
|
+
dispatch,
|
|
380
|
+
strategy,
|
|
381
|
+
config,
|
|
382
|
+
getElements,
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
// Handle external isOpen prop changes
|
|
386
|
+
React.useLayoutEffect(() => {
|
|
387
|
+
if (!enabled) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const prev = prevRef.current;
|
|
392
|
+
if (prev.isOpen === isOpen) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
prev.isOpen = isOpen;
|
|
396
|
+
|
|
397
|
+
if (isOperating) {
|
|
398
|
+
dispatch(drawerActions.syncOpenState(isOpen));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (isAnimating) {
|
|
403
|
+
const currentTarget = stateRef.current.animation?.type === "opening";
|
|
404
|
+
if (currentTarget === isOpen) {
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const newState = dispatch(drawerActions.syncOpenState(isOpen));
|
|
410
|
+
const elements = getElements();
|
|
411
|
+
|
|
412
|
+
if (newState.animation) {
|
|
413
|
+
strategy.onOpeningStart?.(elements, config);
|
|
414
|
+
strategy.applyToDOM(newState.animation.from, elements, config);
|
|
415
|
+
start();
|
|
416
|
+
} else if (!isAnimating) {
|
|
417
|
+
if (newState.phase === "open") {
|
|
418
|
+
strategy.onOpeningStart?.(elements, config);
|
|
419
|
+
strategy.applyToDOM(newState.position, elements, config);
|
|
420
|
+
} else if (newState.phase === "closed") {
|
|
421
|
+
strategy.onClosingComplete?.(elements, config);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}, [
|
|
425
|
+
enabled,
|
|
426
|
+
isOpen,
|
|
427
|
+
isOperating,
|
|
428
|
+
isAnimating,
|
|
429
|
+
dispatch,
|
|
430
|
+
start,
|
|
431
|
+
strategy,
|
|
432
|
+
config,
|
|
433
|
+
getElements,
|
|
434
|
+
]);
|
|
435
|
+
|
|
436
|
+
// Cleanup on disable
|
|
437
|
+
React.useLayoutEffect(() => {
|
|
438
|
+
if (enabled) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (!prevRef.current.enabled) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const elements = getElements();
|
|
446
|
+
strategy.clearFromDOM(elements, config);
|
|
447
|
+
|
|
448
|
+
if (elements.drawer) {
|
|
449
|
+
clearDrawerVisibility(elements.drawer);
|
|
450
|
+
}
|
|
451
|
+
}, [enabled, strategy, config, getElements]);
|
|
452
|
+
|
|
453
|
+
// Effect A: Manage body overflow based on drawer state (reveal mode only)
|
|
454
|
+
//
|
|
455
|
+
// This effect runs frequently as isOpen/isOperating/isAnimating change.
|
|
456
|
+
// Cleanup only clears overflow - safe to call repeatedly.
|
|
457
|
+
React.useLayoutEffect(() => {
|
|
458
|
+
if (!enabled || mode !== "reveal") {
|
|
459
|
+
if (mode === "reveal") {
|
|
460
|
+
clearOverflowHidden();
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const isActive = isOpen ? true : isOperating ? true : isAnimating;
|
|
466
|
+
|
|
467
|
+
if (isActive) {
|
|
468
|
+
applyOverflowHidden();
|
|
469
|
+
} else {
|
|
470
|
+
clearOverflowHidden();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return () => {
|
|
474
|
+
clearOverflowHidden();
|
|
475
|
+
};
|
|
476
|
+
}, [enabled, mode, isOpen, isOperating, isAnimating]);
|
|
477
|
+
|
|
478
|
+
// Effect B: Clear content element styles on unmount (reveal mode only)
|
|
479
|
+
//
|
|
480
|
+
// This effect has minimal dependencies (enabled, mode) so cleanup only runs:
|
|
481
|
+
// - When enabled changes to false (handled by "Cleanup on disable" effect above)
|
|
482
|
+
// - When mode changes (rare)
|
|
483
|
+
// - On unmount (the main case we care about)
|
|
484
|
+
//
|
|
485
|
+
// Content styles are applied by strategy.applyToDOM() during swipe/animation.
|
|
486
|
+
// Content styles are cleared by strategy.onClosingComplete() during NORMAL close.
|
|
487
|
+
// This effect handles UNMOUNT where onClosingComplete never runs.
|
|
488
|
+
React.useLayoutEffect(() => {
|
|
489
|
+
if (!enabled || mode !== "reveal") {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return () => {
|
|
494
|
+
const elements = getElements();
|
|
495
|
+
if (elements.content) {
|
|
496
|
+
strategy.clearFromDOM({ drawer: null, content: elements.content, backdrop: null }, config);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}, [enabled, mode, strategy, config, getElements]);
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
phase,
|
|
503
|
+
isAnimating,
|
|
504
|
+
};
|
|
505
|
+
}
|