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
|
@@ -2,34 +2,60 @@
|
|
|
2
2
|
* @file DrawerLayers component
|
|
3
3
|
*/
|
|
4
4
|
import * as React from "react";
|
|
5
|
+
import * as ReactDOM from "react-dom";
|
|
5
6
|
import type { LayerDefinition } from "../../types.js";
|
|
6
7
|
import { Drawer } from "./Drawer.js";
|
|
7
8
|
import { useDrawerState } from "../../modules/window/useDrawerState.js";
|
|
9
|
+
import { isRevealMode } from "./DrawerRevealContext.js";
|
|
8
10
|
|
|
9
11
|
export type DrawerLayersProps = {
|
|
10
12
|
layers: LayerDefinition[];
|
|
11
13
|
};
|
|
12
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Check if a drawer should be rendered in a portal (outside #root).
|
|
17
|
+
* Reveal mode + non-inline drawers need to be outside #root so they
|
|
18
|
+
* don't move when #root is transformed.
|
|
19
|
+
*/
|
|
20
|
+
function shouldUsePortal(layer: LayerDefinition): boolean {
|
|
21
|
+
if (!layer.drawer) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const { animationMode, inline = false } = layer.drawer;
|
|
25
|
+
if (!isRevealMode(animationMode)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return !inline;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a map of layer ID to handler function.
|
|
33
|
+
*/
|
|
34
|
+
function createHandlerMap(
|
|
35
|
+
layers: LayerDefinition[],
|
|
36
|
+
createHandler: (layerId: string) => () => void,
|
|
37
|
+
): Map<string, () => void> {
|
|
38
|
+
const handlers = new Map<string, () => void>();
|
|
39
|
+
layers.forEach((layer) => {
|
|
40
|
+
handlers.set(layer.id, createHandler(layer.id));
|
|
41
|
+
});
|
|
42
|
+
return handlers;
|
|
43
|
+
}
|
|
44
|
+
|
|
13
45
|
export const DrawerLayers: React.FC<DrawerLayersProps> = ({ layers }) => {
|
|
14
46
|
const drawer = useDrawerState(layers);
|
|
15
47
|
|
|
16
48
|
const drawerLayers = React.useMemo(() => layers.filter((layer) => layer.drawer), [layers]);
|
|
17
49
|
|
|
18
|
-
const closeHandlers = React.useMemo(
|
|
19
|
-
|
|
20
|
-
drawerLayers.
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
return handlers;
|
|
24
|
-
}, [drawerLayers, drawer.close]);
|
|
50
|
+
const closeHandlers = React.useMemo(
|
|
51
|
+
() => createHandlerMap(drawerLayers, (id) => () => drawer.close(id)),
|
|
52
|
+
[drawerLayers, drawer.close],
|
|
53
|
+
);
|
|
25
54
|
|
|
26
|
-
const openHandlers = React.useMemo(
|
|
27
|
-
|
|
28
|
-
drawerLayers.
|
|
29
|
-
|
|
30
|
-
});
|
|
31
|
-
return handlers;
|
|
32
|
-
}, [drawerLayers, drawer.open]);
|
|
55
|
+
const openHandlers = React.useMemo(
|
|
56
|
+
() => createHandlerMap(drawerLayers, (id) => () => drawer.open(id)),
|
|
57
|
+
[drawerLayers, drawer.open],
|
|
58
|
+
);
|
|
33
59
|
|
|
34
60
|
return (
|
|
35
61
|
<>
|
|
@@ -46,7 +72,7 @@ export const DrawerLayers: React.FC<DrawerLayersProps> = ({ layers }) => {
|
|
|
46
72
|
return null;
|
|
47
73
|
}
|
|
48
74
|
|
|
49
|
-
|
|
75
|
+
const drawerElement = (
|
|
50
76
|
<Drawer
|
|
51
77
|
key={layer.id}
|
|
52
78
|
id={layer.id}
|
|
@@ -62,6 +88,13 @@ export const DrawerLayers: React.FC<DrawerLayersProps> = ({ layers }) => {
|
|
|
62
88
|
{layer.component}
|
|
63
89
|
</Drawer>
|
|
64
90
|
);
|
|
91
|
+
|
|
92
|
+
// Render reveal-mode (non-inline) drawers in a portal to document.body
|
|
93
|
+
if (shouldUsePortal(layer)) {
|
|
94
|
+
return ReactDOM.createPortal(drawerElement, document.body);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return drawerElement;
|
|
65
98
|
})}
|
|
66
99
|
</>
|
|
67
100
|
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for DrawerRevealContext utilities.
|
|
3
|
+
*/
|
|
4
|
+
import { isRevealMode } from "./DrawerRevealContext.js";
|
|
5
|
+
|
|
6
|
+
describe("DrawerRevealContext", () => {
|
|
7
|
+
describe("isRevealMode", () => {
|
|
8
|
+
it("returns true for reveal animation mode", () => {
|
|
9
|
+
expect(isRevealMode("reveal")).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns false for overlay animation mode", () => {
|
|
13
|
+
expect(isRevealMode("overlay")).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("returns false for undefined animation mode", () => {
|
|
17
|
+
expect(isRevealMode(undefined)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Context for coordinating reveal-mode drawer animations with GridLayout.
|
|
3
|
+
*
|
|
4
|
+
* When a drawer uses animationMode="reveal", the main content needs to slide away
|
|
5
|
+
* to reveal the drawer behind it. This context provides the communication mechanism
|
|
6
|
+
* between DrawerLayers and GridLayout.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import type { DrawerAnimationMode } from "../../types.js";
|
|
10
|
+
import type { DrawerPlacement } from "./drawerStyles.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* State for a reveal-mode drawer animation.
|
|
14
|
+
*/
|
|
15
|
+
export type DrawerRevealState = {
|
|
16
|
+
/** ID of the drawer layer */
|
|
17
|
+
layerId: string;
|
|
18
|
+
/** Drawer placement */
|
|
19
|
+
placement: DrawerPlacement;
|
|
20
|
+
/** Whether the drawer is currently open */
|
|
21
|
+
isOpen: boolean;
|
|
22
|
+
/** Drawer size in pixels (width for left/right, height for top/bottom) */
|
|
23
|
+
drawerSize: number;
|
|
24
|
+
/** Whether swipe operation is in progress */
|
|
25
|
+
isSwipeOperating: boolean;
|
|
26
|
+
/** Current swipe displacement during operation */
|
|
27
|
+
swipeDisplacement: number;
|
|
28
|
+
/** Whether currently opening via swipe */
|
|
29
|
+
isOpening: boolean;
|
|
30
|
+
/** Whether currently closing via swipe */
|
|
31
|
+
isClosing: boolean;
|
|
32
|
+
/** Whether the drawer is inline (container-relative) or fixed (viewport-relative) */
|
|
33
|
+
inline: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Context value provided to GridLayout for reveal-mode animations.
|
|
38
|
+
*/
|
|
39
|
+
export type DrawerRevealContextValue = {
|
|
40
|
+
/** Currently active reveal-mode drawer (only one at a time) */
|
|
41
|
+
activeRevealDrawer: DrawerRevealState | null;
|
|
42
|
+
/** Register/update a reveal-mode drawer state */
|
|
43
|
+
setRevealState: (state: DrawerRevealState | null) => void;
|
|
44
|
+
/** Grid container ref for inline mode transforms */
|
|
45
|
+
gridRef: React.RefObject<HTMLElement | null> | null;
|
|
46
|
+
/** Set the grid ref (called by GridLayout) */
|
|
47
|
+
setGridRef: (ref: React.RefObject<HTMLElement | null> | null) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const defaultContextValue: DrawerRevealContextValue = {
|
|
51
|
+
activeRevealDrawer: null,
|
|
52
|
+
setRevealState: () => {},
|
|
53
|
+
gridRef: null,
|
|
54
|
+
setGridRef: () => {},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const DrawerRevealContext = React.createContext<DrawerRevealContextValue>(defaultContextValue);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Provider component for DrawerRevealContext.
|
|
61
|
+
*/
|
|
62
|
+
export const DrawerRevealProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
63
|
+
const [activeRevealDrawer, setActiveRevealDrawer] = React.useState<DrawerRevealState | null>(null);
|
|
64
|
+
const [gridRef, setGridRefState] = React.useState<React.RefObject<HTMLElement | null> | null>(null);
|
|
65
|
+
|
|
66
|
+
const setRevealState = React.useCallback((state: DrawerRevealState | null) => {
|
|
67
|
+
setActiveRevealDrawer(state);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
const setGridRef = React.useCallback((ref: React.RefObject<HTMLElement | null> | null) => {
|
|
71
|
+
setGridRefState(ref);
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
const value = React.useMemo(
|
|
75
|
+
(): DrawerRevealContextValue => ({
|
|
76
|
+
activeRevealDrawer,
|
|
77
|
+
setRevealState,
|
|
78
|
+
gridRef,
|
|
79
|
+
setGridRef,
|
|
80
|
+
}),
|
|
81
|
+
[activeRevealDrawer, setRevealState, gridRef, setGridRef],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return <DrawerRevealContext.Provider value={value}>{children}</DrawerRevealContext.Provider>;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Hook to consume DrawerRevealContext.
|
|
89
|
+
*/
|
|
90
|
+
export function useDrawerReveal(): DrawerRevealContextValue {
|
|
91
|
+
return React.useContext(DrawerRevealContext);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if the given animation mode is reveal mode.
|
|
96
|
+
*/
|
|
97
|
+
export function isRevealMode(animationMode: DrawerAnimationMode | undefined): boolean {
|
|
98
|
+
return animationMode === "reveal";
|
|
99
|
+
}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for drawerRevealAnimationUtils.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
DRAWER_CLOSED_OFFSET_PERCENT,
|
|
6
|
+
DEFAULT_ANIMATION_DURATION,
|
|
7
|
+
getPlacementConfig,
|
|
8
|
+
computeSwipeProgress,
|
|
9
|
+
computeRevealTransform,
|
|
10
|
+
buildDrawerTransformString,
|
|
11
|
+
buildContentTransformString,
|
|
12
|
+
STACKING_CONTEXT_STYLES,
|
|
13
|
+
computeDrawerOffsetPx,
|
|
14
|
+
computeContentOffsetPx,
|
|
15
|
+
computePositionFromProgress,
|
|
16
|
+
computeTargetPosition,
|
|
17
|
+
computePositionFromDisplacement,
|
|
18
|
+
buildDrawerTransformPx,
|
|
19
|
+
buildContentTransformPx,
|
|
20
|
+
} from "./drawerRevealAnimationUtils.js";
|
|
21
|
+
|
|
22
|
+
describe("drawerRevealAnimationUtils", () => {
|
|
23
|
+
describe("constants", () => {
|
|
24
|
+
it("DRAWER_CLOSED_OFFSET_PERCENT is 30", () => {
|
|
25
|
+
expect(DRAWER_CLOSED_OFFSET_PERCENT).toBe(30);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("DEFAULT_ANIMATION_DURATION is 300ms", () => {
|
|
29
|
+
expect(DEFAULT_ANIMATION_DURATION).toBe(300);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("STACKING_CONTEXT_STYLES has expected values", () => {
|
|
33
|
+
expect(STACKING_CONTEXT_STYLES.position).toBe("relative");
|
|
34
|
+
expect(STACKING_CONTEXT_STYLES.zIndex).toBe("1");
|
|
35
|
+
expect(STACKING_CONTEXT_STYLES.background).toBe("#fff");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("getPlacementConfig", () => {
|
|
40
|
+
it("returns X axis for left placement with positive sign", () => {
|
|
41
|
+
const config = getPlacementConfig("left");
|
|
42
|
+
expect(config.axis).toBe("X");
|
|
43
|
+
expect(config.sign).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns X axis for right placement with negative sign", () => {
|
|
47
|
+
const config = getPlacementConfig("right");
|
|
48
|
+
expect(config.axis).toBe("X");
|
|
49
|
+
expect(config.sign).toBe(-1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns Y axis for top placement with positive sign", () => {
|
|
53
|
+
const config = getPlacementConfig("top");
|
|
54
|
+
expect(config.axis).toBe("Y");
|
|
55
|
+
expect(config.sign).toBe(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns Y axis for bottom placement with negative sign", () => {
|
|
59
|
+
const config = getPlacementConfig("bottom");
|
|
60
|
+
expect(config.axis).toBe("Y");
|
|
61
|
+
expect(config.sign).toBe(-1);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("computeSwipeProgress", () => {
|
|
66
|
+
it("returns 0 when drawerSize is 0", () => {
|
|
67
|
+
expect(computeSwipeProgress(100, 0, true, false)).toBe(0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns 0 when drawerSize is negative", () => {
|
|
71
|
+
expect(computeSwipeProgress(100, -100, true, false)).toBe(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns progress based on displacement when opening", () => {
|
|
75
|
+
expect(computeSwipeProgress(50, 200, true, false)).toBe(0.25);
|
|
76
|
+
expect(computeSwipeProgress(100, 200, true, false)).toBe(0.5);
|
|
77
|
+
expect(computeSwipeProgress(200, 200, true, false)).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("clamps progress to 0-1 range when opening", () => {
|
|
81
|
+
expect(computeSwipeProgress(-50, 200, true, false)).toBe(0);
|
|
82
|
+
expect(computeSwipeProgress(300, 200, true, false)).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns inverted progress when closing", () => {
|
|
86
|
+
expect(computeSwipeProgress(0, 200, false, true)).toBe(1);
|
|
87
|
+
expect(computeSwipeProgress(100, 200, false, true)).toBe(0.5);
|
|
88
|
+
expect(computeSwipeProgress(200, 200, false, true)).toBe(0);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("clamps progress to 0-1 range when closing", () => {
|
|
92
|
+
expect(computeSwipeProgress(-50, 200, false, true)).toBe(1);
|
|
93
|
+
expect(computeSwipeProgress(300, 200, false, true)).toBe(0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns 0 when neither opening nor closing", () => {
|
|
97
|
+
expect(computeSwipeProgress(100, 200, false, false)).toBe(0);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("computeRevealTransform", () => {
|
|
102
|
+
describe("left placement", () => {
|
|
103
|
+
it("returns -30% drawer offset when closed", () => {
|
|
104
|
+
const result = computeRevealTransform(0, 300, "left");
|
|
105
|
+
expect(result.drawerOffsetPercent).toBe(-30);
|
|
106
|
+
expect(result.contentOffsetPx).toBeCloseTo(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns 0% drawer offset when open", () => {
|
|
110
|
+
const result = computeRevealTransform(1, 300, "left");
|
|
111
|
+
expect(result.drawerOffsetPercent).toBeCloseTo(0);
|
|
112
|
+
expect(result.contentOffsetPx).toBe(300);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns -15% drawer offset at halfway", () => {
|
|
116
|
+
const result = computeRevealTransform(0.5, 300, "left");
|
|
117
|
+
expect(result.drawerOffsetPercent).toBe(-15);
|
|
118
|
+
expect(result.contentOffsetPx).toBe(150);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("right placement", () => {
|
|
123
|
+
it("returns +30% drawer offset when closed", () => {
|
|
124
|
+
const result = computeRevealTransform(0, 300, "right");
|
|
125
|
+
expect(result.drawerOffsetPercent).toBe(30);
|
|
126
|
+
expect(result.contentOffsetPx).toBeCloseTo(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns 0% drawer offset when open", () => {
|
|
130
|
+
const result = computeRevealTransform(1, 300, "right");
|
|
131
|
+
expect(result.drawerOffsetPercent).toBeCloseTo(0);
|
|
132
|
+
expect(result.contentOffsetPx).toBe(-300);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("top placement", () => {
|
|
137
|
+
it("returns -30% drawer offset when closed", () => {
|
|
138
|
+
const result = computeRevealTransform(0, 200, "top");
|
|
139
|
+
expect(result.drawerOffsetPercent).toBe(-30);
|
|
140
|
+
expect(result.contentOffsetPx).toBeCloseTo(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("returns 0% drawer offset when open", () => {
|
|
144
|
+
const result = computeRevealTransform(1, 200, "top");
|
|
145
|
+
expect(result.drawerOffsetPercent).toBeCloseTo(0);
|
|
146
|
+
expect(result.contentOffsetPx).toBe(200);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("bottom placement", () => {
|
|
151
|
+
it("returns +30% drawer offset when closed", () => {
|
|
152
|
+
const result = computeRevealTransform(0, 200, "bottom");
|
|
153
|
+
expect(result.drawerOffsetPercent).toBe(30);
|
|
154
|
+
expect(result.contentOffsetPx).toBeCloseTo(0);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("returns 0% drawer offset when open", () => {
|
|
158
|
+
const result = computeRevealTransform(1, 200, "bottom");
|
|
159
|
+
expect(result.drawerOffsetPercent).toBeCloseTo(0);
|
|
160
|
+
expect(result.contentOffsetPx).toBe(-200);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("buildDrawerTransformString", () => {
|
|
166
|
+
it("returns correct transform for left placement closed", () => {
|
|
167
|
+
expect(buildDrawerTransformString(0, "left")).toBe("translateX(-30%)");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("returns correct transform for left placement open", () => {
|
|
171
|
+
expect(buildDrawerTransformString(1, "left")).toBe("translateX(0%)");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("returns correct transform for right placement closed", () => {
|
|
175
|
+
expect(buildDrawerTransformString(0, "right")).toBe("translateX(30%)");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("returns correct transform for right placement open", () => {
|
|
179
|
+
expect(buildDrawerTransformString(1, "right")).toBe("translateX(0%)");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("returns correct transform for top placement closed", () => {
|
|
183
|
+
expect(buildDrawerTransformString(0, "top")).toBe("translateY(-30%)");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("returns correct transform for top placement open", () => {
|
|
187
|
+
expect(buildDrawerTransformString(1, "top")).toBe("translateY(0%)");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("returns correct transform for bottom placement closed", () => {
|
|
191
|
+
expect(buildDrawerTransformString(0, "bottom")).toBe("translateY(30%)");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("returns correct transform for bottom placement open", () => {
|
|
195
|
+
expect(buildDrawerTransformString(1, "bottom")).toBe("translateY(0%)");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("buildContentTransformString", () => {
|
|
200
|
+
it("returns 0px offset when closed for left placement", () => {
|
|
201
|
+
expect(buildContentTransformString(0, 300, "left")).toBe("translateX(0px)");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("returns +drawerSize offset when open for left placement", () => {
|
|
205
|
+
expect(buildContentTransformString(1, 300, "left")).toBe("translateX(300px)");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("returns 0px offset when closed for right placement", () => {
|
|
209
|
+
expect(buildContentTransformString(0, 300, "right")).toBe("translateX(0px)");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("returns -drawerSize offset when open for right placement", () => {
|
|
213
|
+
expect(buildContentTransformString(1, 300, "right")).toBe("translateX(-300px)");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("returns 0px offset when closed for top placement", () => {
|
|
217
|
+
expect(buildContentTransformString(0, 200, "top")).toBe("translateY(0px)");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("returns +drawerSize offset when open for top placement", () => {
|
|
221
|
+
expect(buildContentTransformString(1, 200, "top")).toBe("translateY(200px)");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("returns 0px offset when closed for bottom placement", () => {
|
|
225
|
+
expect(buildContentTransformString(0, 200, "bottom")).toBe("translateY(0px)");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("returns -drawerSize offset when open for bottom placement", () => {
|
|
229
|
+
expect(buildContentTransformString(1, 200, "bottom")).toBe("translateY(-200px)");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("returns halfway offset at 50% progress", () => {
|
|
233
|
+
expect(buildContentTransformString(0.5, 300, "left")).toBe("translateX(150px)");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("computeDrawerOffsetPx", () => {
|
|
238
|
+
it("returns -90px for left placement when closed (progress=0)", () => {
|
|
239
|
+
// 30% of 300px = 90px, negated for left
|
|
240
|
+
expect(computeDrawerOffsetPx(0, 300, "left")).toBe(-90);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("returns 0px for left placement when open (progress=1)", () => {
|
|
244
|
+
expect(computeDrawerOffsetPx(1, 300, "left")).toBeCloseTo(0);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("returns +90px for right placement when closed (progress=0)", () => {
|
|
248
|
+
expect(computeDrawerOffsetPx(0, 300, "right")).toBe(90);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("returns 0px for right placement when open (progress=1)", () => {
|
|
252
|
+
expect(computeDrawerOffsetPx(1, 300, "right")).toBeCloseTo(0);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("returns -45px at halfway progress for left placement", () => {
|
|
256
|
+
expect(computeDrawerOffsetPx(0.5, 300, "left")).toBe(-45);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe("computeContentOffsetPx", () => {
|
|
261
|
+
it("returns 0 for left placement when closed (progress=0)", () => {
|
|
262
|
+
expect(computeContentOffsetPx(0, 300, "left")).toBe(0);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("returns drawerSize for left placement when open (progress=1)", () => {
|
|
266
|
+
expect(computeContentOffsetPx(1, 300, "left")).toBe(300);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("returns -drawerSize for right placement when open (progress=1)", () => {
|
|
270
|
+
expect(computeContentOffsetPx(1, 300, "right")).toBe(-300);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("returns halfway offset at 50% progress", () => {
|
|
274
|
+
expect(computeContentOffsetPx(0.5, 300, "left")).toBe(150);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("computePositionFromProgress", () => {
|
|
279
|
+
it("returns both offsets for left placement", () => {
|
|
280
|
+
const result = computePositionFromProgress(0.5, 300, "left");
|
|
281
|
+
expect(result.drawerPx).toBe(-45);
|
|
282
|
+
expect(result.contentPx).toBe(150);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("returns closed state positions", () => {
|
|
286
|
+
const result = computePositionFromProgress(0, 300, "left");
|
|
287
|
+
expect(result.drawerPx).toBe(-90);
|
|
288
|
+
expect(result.contentPx).toBe(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("returns open state positions", () => {
|
|
292
|
+
const result = computePositionFromProgress(1, 300, "left");
|
|
293
|
+
expect(result.drawerPx).toBeCloseTo(0);
|
|
294
|
+
expect(result.contentPx).toBe(300);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe("computeTargetPosition", () => {
|
|
299
|
+
it("returns closed target positions", () => {
|
|
300
|
+
const result = computeTargetPosition(false, 300, "left");
|
|
301
|
+
expect(result.drawerPx).toBe(-90);
|
|
302
|
+
expect(result.contentPx).toBe(0);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("returns open target positions", () => {
|
|
306
|
+
const result = computeTargetPosition(true, 300, "left");
|
|
307
|
+
expect(result.drawerPx).toBeCloseTo(0);
|
|
308
|
+
expect(result.contentPx).toBe(300);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("handles right placement for open state", () => {
|
|
312
|
+
const result = computeTargetPosition(true, 300, "right");
|
|
313
|
+
expect(result.drawerPx).toBeCloseTo(0);
|
|
314
|
+
expect(result.contentPx).toBe(-300);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("computePositionFromDisplacement", () => {
|
|
319
|
+
it("returns closed position when not opening or closing", () => {
|
|
320
|
+
const result = computePositionFromDisplacement(100, 300, "left", false, false);
|
|
321
|
+
expect(result.drawerPx).toBe(-90);
|
|
322
|
+
expect(result.contentPx).toBe(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("returns position based on displacement when opening", () => {
|
|
326
|
+
// 150px displacement on 300px drawer = 50% progress
|
|
327
|
+
const result = computePositionFromDisplacement(150, 300, "left", true, false);
|
|
328
|
+
expect(result.drawerPx).toBe(-45);
|
|
329
|
+
expect(result.contentPx).toBe(150);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("returns position based on displacement when closing", () => {
|
|
333
|
+
// 150px displacement on 300px drawer = 50% progress (inverted)
|
|
334
|
+
const result = computePositionFromDisplacement(150, 300, "left", false, true);
|
|
335
|
+
expect(result.drawerPx).toBe(-45);
|
|
336
|
+
expect(result.contentPx).toBe(150);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe("buildDrawerTransformPx", () => {
|
|
341
|
+
it("returns correct transform for left placement", () => {
|
|
342
|
+
expect(buildDrawerTransformPx(-90, "left")).toBe("translateX(-90px)");
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("returns correct transform for right placement", () => {
|
|
346
|
+
expect(buildDrawerTransformPx(90, "right")).toBe("translateX(90px)");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("returns correct transform for top placement", () => {
|
|
350
|
+
expect(buildDrawerTransformPx(-60, "top")).toBe("translateY(-60px)");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("returns correct transform for bottom placement", () => {
|
|
354
|
+
expect(buildDrawerTransformPx(60, "bottom")).toBe("translateY(60px)");
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe("buildContentTransformPx", () => {
|
|
359
|
+
it("returns correct transform for left placement", () => {
|
|
360
|
+
expect(buildContentTransformPx(300, "left")).toBe("translateX(300px)");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("returns correct transform for right placement", () => {
|
|
364
|
+
expect(buildContentTransformPx(-300, "right")).toBe("translateX(-300px)");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("returns correct transform for top placement", () => {
|
|
368
|
+
expect(buildContentTransformPx(200, "top")).toBe("translateY(200px)");
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("returns correct transform for bottom placement", () => {
|
|
372
|
+
expect(buildContentTransformPx(-200, "bottom")).toBe("translateY(-200px)");
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
});
|