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.
Files changed (127) hide show
  1. package/dist/FloatingWindow-CE-WzkNv.js +1542 -0
  2. package/dist/FloatingWindow-CE-WzkNv.js.map +1 -0
  3. package/dist/FloatingWindow-DpFpmX1f.cjs +2 -0
  4. package/dist/FloatingWindow-DpFpmX1f.cjs.map +1 -0
  5. package/dist/GridLayout-EwKszYBy.cjs +2 -0
  6. package/dist/{GridLayout-DKTg_N61.cjs.map → GridLayout-EwKszYBy.cjs.map} +1 -1
  7. package/dist/GridLayout-kiWdpMLQ.js +947 -0
  8. package/dist/{GridLayout-UWNxXw77.js.map → GridLayout-kiWdpMLQ.js.map} +1 -1
  9. package/dist/PanelSystem-Dmy5YI_6.cjs +3 -0
  10. package/dist/PanelSystem-Dmy5YI_6.cjs.map +1 -0
  11. package/dist/{PanelSystem-BqUzNtf2.js → PanelSystem-DrYsYwuV.js} +208 -247
  12. package/dist/PanelSystem-DrYsYwuV.js.map +1 -0
  13. package/dist/components/window/Drawer.d.ts +1 -0
  14. package/dist/components/window/DrawerRevealContext.d.ts +61 -0
  15. package/dist/components/window/drawerRevealAnimationUtils.d.ts +212 -0
  16. package/dist/components/window/drawerStyles.d.ts +5 -0
  17. package/dist/components/window/useDrawerSwipeTransform.d.ts +8 -2
  18. package/dist/components/window/useDrawerTransform.d.ts +68 -0
  19. package/dist/components/window/useRevealDrawerTransform.d.ts +56 -0
  20. package/dist/config.cjs +1 -1
  21. package/dist/config.cjs.map +1 -1
  22. package/dist/config.js +8 -7
  23. package/dist/config.js.map +1 -1
  24. package/dist/dialog/index.d.ts +1 -1
  25. package/dist/grid.cjs +1 -1
  26. package/dist/grid.js +2 -2
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.js +4 -4
  29. package/dist/modules/dialog/DialogContainer.d.ts +22 -2
  30. package/dist/modules/dialog/Modal.d.ts +23 -2
  31. package/dist/modules/dialog/SwipeDialogContainer.d.ts +6 -2
  32. package/dist/modules/dialog/types.d.ts +12 -0
  33. package/dist/modules/drawer/drawerStateMachine.d.ts +168 -0
  34. package/dist/modules/drawer/revealDrawerConstants.d.ts +33 -0
  35. package/dist/modules/drawer/revealDrawerStateMachine.d.ts +146 -0
  36. package/dist/modules/drawer/strategies/index.d.ts +8 -0
  37. package/dist/modules/drawer/strategies/overlayStrategy.d.ts +12 -0
  38. package/dist/modules/drawer/strategies/revealStrategy.d.ts +12 -0
  39. package/dist/modules/drawer/strategies/types.d.ts +116 -0
  40. package/dist/panels.cjs +1 -1
  41. package/dist/panels.js +1 -1
  42. package/dist/stack.cjs +1 -1
  43. package/dist/stack.cjs.map +1 -1
  44. package/dist/stack.js +306 -347
  45. package/dist/stack.js.map +1 -1
  46. package/dist/types.d.ts +14 -0
  47. package/dist/useAnimationFrame-CRuFlk5t.js +394 -0
  48. package/dist/useAnimationFrame-CRuFlk5t.js.map +1 -0
  49. package/dist/useAnimationFrame-XRpDXkwV.cjs +2 -0
  50. package/dist/useAnimationFrame-XRpDXkwV.cjs.map +1 -0
  51. package/dist/window.cjs +1 -1
  52. package/dist/window.js +1 -1
  53. package/package.json +1 -1
  54. package/src/components/gesture/SwipeSafeZone.tsx +1 -0
  55. package/src/components/grid/GridLayout.tsx +110 -38
  56. package/src/components/window/Drawer.tsx +114 -10
  57. package/src/components/window/DrawerLayers.tsx +48 -15
  58. package/src/components/window/DrawerRevealContext.spec.ts +20 -0
  59. package/src/components/window/DrawerRevealContext.tsx +99 -0
  60. package/src/components/window/drawerRevealAnimationUtils.spec.ts +375 -0
  61. package/src/components/window/drawerRevealAnimationUtils.ts +415 -0
  62. package/src/components/window/drawerStyles.spec.ts +39 -0
  63. package/src/components/window/drawerStyles.ts +24 -0
  64. package/src/components/window/useDrawerSwipeTransform.ts +28 -90
  65. package/src/components/window/useDrawerTransform.ts +505 -0
  66. package/src/components/window/useRevealDrawerTransform.spec.ts +1936 -0
  67. package/src/components/window/useRevealDrawerTransform.ts +105 -0
  68. package/src/demo/components/FullscreenDemoPage.tsx +47 -0
  69. package/src/demo/fullscreenRoutes.tsx +32 -0
  70. package/src/demo/index.tsx +5 -0
  71. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +23 -8
  72. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +6 -1
  73. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +14 -4
  74. package/src/demo/pages/Drawer/components/DrawerReveal.module.css +157 -0
  75. package/src/demo/pages/Drawer/components/DrawerReveal.tsx +128 -0
  76. package/src/demo/pages/Drawer/reveal/index.tsx +17 -0
  77. package/src/demo/pages/Drawer/reveal-fullscreen/index.tsx +135 -0
  78. package/src/demo/pages/Drawer/reveal-fullscreen/styles.module.css +233 -0
  79. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +56 -52
  80. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +39 -49
  81. package/src/demo/routes.tsx +2 -0
  82. package/src/dialog/index.ts +2 -0
  83. package/src/hooks/gesture/testing/createGestureSimulator.ts +1 -0
  84. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +10 -2
  85. package/src/hooks/gesture/useSwipeInput.spec.ts +69 -0
  86. package/src/hooks/gesture/useSwipeInput.ts +2 -0
  87. package/src/hooks/gesture/utils.ts +15 -4
  88. package/src/hooks/useAnimatedVisibility.spec.ts +3 -3
  89. package/src/hooks/useOperationContinuity.spec.ts +17 -10
  90. package/src/hooks/useOperationContinuity.ts +5 -5
  91. package/src/hooks/useSharedElementTransition.ts +28 -7
  92. package/src/modules/dialog/DialogContainer.tsx +39 -5
  93. package/src/modules/dialog/Modal.tsx +46 -4
  94. package/src/modules/dialog/SwipeDialogContainer.tsx +12 -2
  95. package/src/modules/dialog/dialogAnimationUtils.spec.ts +0 -1
  96. package/src/modules/dialog/types.ts +14 -0
  97. package/src/modules/dialog/useDialogContainer.spec.ts +11 -3
  98. package/src/modules/dialog/useDialogSwipeInput.spec.ts +49 -28
  99. package/src/modules/dialog/useDialogSwipeInput.ts +37 -6
  100. package/src/modules/dialog/useDialogTransform.spec.ts +63 -30
  101. package/src/modules/drawer/drawerStateMachine.ts +500 -0
  102. package/src/modules/drawer/revealDrawerConstants.ts +38 -0
  103. package/src/modules/drawer/revealDrawerStateMachine.spec.ts +558 -0
  104. package/src/modules/drawer/revealDrawerStateMachine.ts +197 -0
  105. package/src/modules/drawer/strategies/index.ts +9 -0
  106. package/src/modules/drawer/strategies/overlayStrategy.ts +133 -0
  107. package/src/modules/drawer/strategies/revealStrategy.ts +111 -0
  108. package/src/modules/drawer/strategies/types.ts +160 -0
  109. package/src/modules/drawer/useDrawerSwipeInput.ts +7 -4
  110. package/src/modules/pivot/SwipePivotContent.spec.tsx +48 -37
  111. package/src/modules/pivot/usePivotSwipeInput.spec.ts +8 -8
  112. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1 -1
  113. package/src/types.ts +15 -0
  114. package/dist/FloatingWindow-CUXnEtrb.js +0 -827
  115. package/dist/FloatingWindow-CUXnEtrb.js.map +0 -1
  116. package/dist/FloatingWindow-DMwyK0eK.cjs +0 -2
  117. package/dist/FloatingWindow-DMwyK0eK.cjs.map +0 -1
  118. package/dist/GridLayout-DKTg_N61.cjs +0 -2
  119. package/dist/GridLayout-UWNxXw77.js +0 -926
  120. package/dist/PanelSystem-BqUzNtf2.js.map +0 -1
  121. package/dist/PanelSystem-D603LKKv.cjs +0 -3
  122. package/dist/PanelSystem-D603LKKv.cjs.map +0 -1
  123. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +0 -2
  124. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +0 -1
  125. package/dist/useNativeGestureGuard-CGYo6O0r.js +0 -347
  126. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +0 -1
  127. package/src/components/window/useDrawerSwipeTransform.spec.ts +0 -234
@@ -0,0 +1,135 @@
1
+ /**
2
+ * @file Drawer - Reveal Mode Fullscreen Demo
3
+ *
4
+ * Standalone page demonstrating reveal mode with body-level transform.
5
+ * No SingleSamplePage wrapper - this is the entire page.
6
+ */
7
+ import * as React from "react";
8
+ import { Link } from "react-router";
9
+ import { GridLayout } from "../../../../components/grid/GridLayout";
10
+ import type { LayerDefinition, PanelLayoutConfig } from "../../../../types";
11
+ import { FiArrowLeft, FiHome, FiSearch, FiBell, FiMail, FiUser, FiSettings } from "react-icons/fi";
12
+ import styles from "./styles.module.css";
13
+
14
+ const Page: React.FC = () => {
15
+ const [open, setOpen] = React.useState(false);
16
+
17
+ const config = React.useMemo<PanelLayoutConfig>(
18
+ () => ({
19
+ areas: [["content"]],
20
+ rows: [{ size: "1fr" }],
21
+ columns: [{ size: "1fr" }],
22
+ gap: "0",
23
+ }),
24
+ [],
25
+ );
26
+
27
+ const layers: LayerDefinition[] = [
28
+ {
29
+ id: "content",
30
+ gridArea: "content",
31
+ component: (
32
+ <div className={styles.container}>
33
+ <header className={styles.header}>
34
+ <button type="button" className={styles.menuButton} onClick={() => setOpen(true)}>
35
+ <span className={styles.menuIcon} />
36
+ </button>
37
+ <h1 className={styles.title}>Reveal Mode Demo</h1>
38
+ </header>
39
+
40
+ <main className={styles.main}>
41
+ <div className={styles.card}>
42
+ <h2>Full-screen Reveal Mode</h2>
43
+ <p>
44
+ This page demonstrates the reveal animation mode without any wrapper.
45
+ The entire page slides to reveal the drawer behind it.
46
+ </p>
47
+ <p>
48
+ Try swiping from the left edge, or tap the menu button above.
49
+ </p>
50
+ </div>
51
+
52
+ <div className={styles.card}>
53
+ <h3>How it works</h3>
54
+ <ul className={styles.list}>
55
+ <li>Drawer is positioned behind the main content</li>
56
+ <li>Body element receives transform during animation</li>
57
+ <li>Swipe gestures work from the left edge</li>
58
+ </ul>
59
+ </div>
60
+
61
+ <Link to="/components/drawer/reveal" className={styles.backLink}>
62
+ <FiArrowLeft />
63
+ Back to Drawer demos
64
+ </Link>
65
+ </main>
66
+ </div>
67
+ ),
68
+ },
69
+ {
70
+ id: "drawer-reveal-fullscreen",
71
+ component: (
72
+ <div className={styles.drawerContent}>
73
+ <div className={styles.profile}>
74
+ <div className={styles.avatar}>U</div>
75
+ <div className={styles.profileInfo}>
76
+ <div className={styles.profileName}>User Name</div>
77
+ <div className={styles.profileHandle}>@username</div>
78
+ </div>
79
+ </div>
80
+
81
+ <nav className={styles.nav}>
82
+ <Link to="/" className={styles.navItem}>
83
+ <FiHome className={styles.navIcon} />
84
+ Home
85
+ </Link>
86
+ <a href="#" className={styles.navItem}>
87
+ <FiSearch className={styles.navIcon} />
88
+ Explore
89
+ </a>
90
+ <a href="#" className={styles.navItem}>
91
+ <FiBell className={styles.navIcon} />
92
+ Notifications
93
+ </a>
94
+ <a href="#" className={styles.navItem}>
95
+ <FiMail className={styles.navIcon} />
96
+ Messages
97
+ </a>
98
+ <a href="#" className={styles.navItem}>
99
+ <FiUser className={styles.navIcon} />
100
+ Profile
101
+ </a>
102
+ <a href="#" className={styles.navItem}>
103
+ <FiSettings className={styles.navIcon} />
104
+ Settings
105
+ </a>
106
+ </nav>
107
+
108
+ <button type="button" className={styles.closeButton} onClick={() => setOpen(false)}>
109
+ Close Menu
110
+ </button>
111
+ </div>
112
+ ),
113
+ drawer: {
114
+ open,
115
+ onStateChange: setOpen,
116
+ dismissible: true,
117
+ animationMode: "reveal",
118
+ swipeGestures: {
119
+ edgeSwipeOpen: true,
120
+ swipeClose: true,
121
+ edgeWidth: 24,
122
+ dismissThreshold: 0.3,
123
+ },
124
+ },
125
+ position: { left: 0 },
126
+ width: 280,
127
+ height: "100%",
128
+ zIndex: 1200,
129
+ },
130
+ ];
131
+
132
+ return <GridLayout config={config} layers={layers} root />;
133
+ };
134
+
135
+ export default Page;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * @file Styles for Reveal Mode Fullscreen Demo
3
+ */
4
+
5
+ .container {
6
+ display: flex;
7
+ flex-direction: column;
8
+ height: 100vh;
9
+ height: 100dvh;
10
+ overflow-y: auto;
11
+ background: #fff;
12
+ color: #0f1419;
13
+ }
14
+
15
+ .header {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 16px;
19
+ padding: 12px 16px;
20
+ border-bottom: 1px solid #eff3f4;
21
+ position: sticky;
22
+ top: 0;
23
+ background: #fff;
24
+ z-index: 100;
25
+ }
26
+
27
+ .menuButton {
28
+ display: flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ width: 40px;
32
+ height: 40px;
33
+ border: none;
34
+ background: transparent;
35
+ border-radius: 50%;
36
+ cursor: pointer;
37
+ transition: background 0.15s ease;
38
+ }
39
+
40
+ .menuButton:hover {
41
+ background: rgba(15, 20, 25, 0.1);
42
+ }
43
+
44
+ .menuIcon {
45
+ position: relative;
46
+ width: 18px;
47
+ height: 2px;
48
+ background: #0f1419;
49
+ border-radius: 1px;
50
+ }
51
+
52
+ .menuIcon::before,
53
+ .menuIcon::after {
54
+ content: "";
55
+ position: absolute;
56
+ left: 0;
57
+ width: 100%;
58
+ height: 2px;
59
+ background: #0f1419;
60
+ border-radius: 1px;
61
+ }
62
+
63
+ .menuIcon::before {
64
+ top: -6px;
65
+ }
66
+
67
+ .menuIcon::after {
68
+ top: 6px;
69
+ }
70
+
71
+ .title {
72
+ font-size: 20px;
73
+ font-weight: 700;
74
+ margin: 0;
75
+ }
76
+
77
+ .main {
78
+ flex: 1;
79
+ padding: 20px 16px;
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 16px;
83
+ }
84
+
85
+ .card {
86
+ padding: 20px;
87
+ background: #f7f9f9;
88
+ border-radius: 16px;
89
+ }
90
+
91
+ .card h2 {
92
+ margin: 0 0 12px;
93
+ font-size: 18px;
94
+ font-weight: 700;
95
+ }
96
+
97
+ .card h3 {
98
+ margin: 0 0 12px;
99
+ font-size: 16px;
100
+ font-weight: 600;
101
+ }
102
+
103
+ .card p {
104
+ margin: 0 0 12px;
105
+ color: #536471;
106
+ line-height: 1.5;
107
+ }
108
+
109
+ .card p:last-child {
110
+ margin-bottom: 0;
111
+ }
112
+
113
+ .list {
114
+ margin: 0;
115
+ padding-left: 20px;
116
+ color: #536471;
117
+ line-height: 1.8;
118
+ }
119
+
120
+ .backLink {
121
+ display: inline-flex;
122
+ align-items: center;
123
+ gap: 8px;
124
+ margin-top: auto;
125
+ padding: 12px 20px;
126
+ background: #1d9bf0;
127
+ color: #fff;
128
+ border-radius: 9999px;
129
+ text-decoration: none;
130
+ font-weight: 600;
131
+ transition: background 0.15s ease;
132
+ }
133
+
134
+ .backLink:hover {
135
+ background: #1a8cd8;
136
+ }
137
+
138
+ /* Drawer content */
139
+ .drawerContent {
140
+ display: flex;
141
+ flex-direction: column;
142
+ height: 100%;
143
+ padding: 16px;
144
+ background: #fff;
145
+ border-right: 1px solid #eff3f4;
146
+ overflow-y: auto;
147
+ -webkit-overflow-scrolling: touch;
148
+ }
149
+
150
+ .profile {
151
+ display: flex;
152
+ align-items: center;
153
+ gap: 12px;
154
+ padding: 12px 0;
155
+ border-bottom: 1px solid #eff3f4;
156
+ margin-bottom: 8px;
157
+ }
158
+
159
+ .avatar {
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ width: 40px;
164
+ height: 40px;
165
+ background: #1d9bf0;
166
+ border-radius: 50%;
167
+ color: #fff;
168
+ font-size: 18px;
169
+ font-weight: 700;
170
+ }
171
+
172
+ .profileInfo {
173
+ display: flex;
174
+ flex-direction: column;
175
+ gap: 2px;
176
+ }
177
+
178
+ .profileName {
179
+ font-size: 15px;
180
+ font-weight: 700;
181
+ color: #0f1419;
182
+ }
183
+
184
+ .profileHandle {
185
+ font-size: 13px;
186
+ color: #536471;
187
+ }
188
+
189
+ .nav {
190
+ display: flex;
191
+ flex-direction: column;
192
+ gap: 4px;
193
+ flex: 1;
194
+ padding: 8px 0;
195
+ }
196
+
197
+ .navItem {
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 16px;
201
+ padding: 12px;
202
+ border-radius: 9999px;
203
+ color: #0f1419;
204
+ font-size: 20px;
205
+ font-weight: 500;
206
+ text-decoration: none;
207
+ transition: background 0.2s ease;
208
+ }
209
+
210
+ .navItem:hover {
211
+ background: #e7e7e8;
212
+ }
213
+
214
+ .navIcon {
215
+ width: 26px;
216
+ height: 26px;
217
+ }
218
+
219
+ .closeButton {
220
+ padding: 14px;
221
+ background: #eff3f4;
222
+ border: none;
223
+ border-radius: 9999px;
224
+ font-size: 15px;
225
+ font-weight: 700;
226
+ color: #0f1419;
227
+ cursor: pointer;
228
+ transition: background 0.15s ease;
229
+ }
230
+
231
+ .closeButton:hover {
232
+ background: #d7dbdc;
233
+ }
@@ -4,7 +4,9 @@
4
4
  * These tests focus on the panel visibility and depth calculation logic,
5
5
  * particularly around the exitingPanelId handling during rapid navigation.
6
6
  */
7
- import { describe, it, expect } from "vitest";
7
+
8
+ // Empty export to make this file a module (prevents global scope pollution)
9
+ export {};
8
10
 
9
11
  /**
10
12
  * Pure function version of the isExiting logic for testing.
@@ -83,69 +85,71 @@ describe("StackBasics panel calculation logic", () => {
83
85
 
84
86
  describe("rapid navigation scenarios", () => {
85
87
  it("handles push → back → push sequence correctly", () => {
86
- // Initial: list active
87
- let stack: readonly string[] = ["list"];
88
- let depth = 0;
89
- let exitingPanelId: string | null = null;
90
-
91
- // Push detail
92
- stack = ["list", "detail"];
93
- depth = 1;
94
-
95
- // Back to list (detail becomes exiting)
96
- stack = ["list"];
97
- depth = 0;
98
- exitingPanelId = "detail";
99
-
100
- // Verify detail is exiting
101
- let isExiting = computeIsExiting("detail", exitingPanelId, stack);
102
- expect(isExiting).toBe(true);
103
-
104
- // Push detail again BEFORE exitingPanelId clears
105
- stack = ["list", "detail"];
106
- depth = 1;
107
- // exitingPanelId is still "detail" (timeout hasn't fired yet)
108
-
109
- // Now detail should NOT be exiting because it's in the stack
110
- isExiting = computeIsExiting("detail", exitingPanelId, stack);
111
- expect(isExiting).toBe(false);
88
+ // Helper function to compute stack state for each step
89
+ const computeStackState = (step: number) => {
90
+ if (step === 0) {
91
+ // Initial: list active
92
+ return { stack: ["list"] as readonly string[], depth: 0, exitingPanelId: null as string | null };
93
+ }
94
+ if (step === 1) {
95
+ // Push detail
96
+ return { stack: ["list", "detail"] as readonly string[], depth: 1, exitingPanelId: null as string | null };
97
+ }
98
+ if (step === 2) {
99
+ // Back to list (detail becomes exiting)
100
+ return { stack: ["list"] as readonly string[], depth: 0, exitingPanelId: "detail" };
101
+ }
102
+ // step === 3: Push detail again BEFORE exitingPanelId clears
103
+ return { stack: ["list", "detail"] as readonly string[], depth: 1, exitingPanelId: "detail" };
104
+ };
105
+
106
+ // Verify detail is exiting at step 2
107
+ const step2 = computeStackState(2);
108
+ const isExitingStep2 = computeIsExiting("detail", step2.exitingPanelId, step2.stack);
109
+ expect(isExitingStep2).toBe(true);
110
+
111
+ // Now detail should NOT be exiting because it's in the stack at step 3
112
+ const step3 = computeStackState(3);
113
+ const isExitingStep3 = computeIsExiting("detail", step3.exitingPanelId, step3.stack);
114
+ expect(isExitingStep3).toBe(false);
112
115
 
113
116
  // Panel depth should be correct
114
- const panelDepth = computePanelDepth("detail", isExiting, stack, depth);
117
+ const panelDepth = computePanelDepth("detail", isExitingStep3, step3.stack, step3.depth);
115
118
  expect(panelDepth).toBe(1); // Not 2!
116
119
  });
117
120
 
118
121
  it("handles deep navigation with rapid back operations", () => {
119
- // Initial: list detail edit
120
- let stack: readonly string[] = ["list", "detail", "edit"];
121
- let depth = 2;
122
- let exitingPanelId: string | null = null;
123
-
124
- // Back to detail (edit becomes exiting)
125
- stack = ["list", "detail"];
126
- depth = 1;
127
- exitingPanelId = "edit";
128
-
129
- const editIsExiting = computeIsExiting("edit", exitingPanelId, stack);
122
+ // Helper function to compute stack state for each step
123
+ const computeDeepNavState = (step: number) => {
124
+ if (step === 0) {
125
+ // Initial: list detail edit
126
+ return { stack: ["list", "detail", "edit"] as readonly string[], depth: 2, exitingPanelId: null as string | null };
127
+ }
128
+ if (step === 1) {
129
+ // Back to detail (edit becomes exiting)
130
+ return { stack: ["list", "detail"] as readonly string[], depth: 1, exitingPanelId: "edit" };
131
+ }
132
+ if (step === 2) {
133
+ // Back to list immediately (detail becomes exiting)
134
+ return { stack: ["list"] as readonly string[], depth: 0, exitingPanelId: "detail" };
135
+ }
136
+ // step === 3: Push to detail again
137
+ return { stack: ["list", "detail"] as readonly string[], depth: 1, exitingPanelId: "detail" };
138
+ };
139
+
140
+ const step1 = computeDeepNavState(1);
141
+ const editIsExiting = computeIsExiting("edit", step1.exitingPanelId, step1.stack);
130
142
  expect(editIsExiting).toBe(true);
131
143
 
132
- // Back to list immediately (detail becomes exiting, edit timeout still pending)
133
- stack = ["list"];
134
- depth = 0;
135
- exitingPanelId = "detail"; // Updated to detail
136
-
137
- const detailIsExiting = computeIsExiting("detail", exitingPanelId, stack);
144
+ const step2 = computeDeepNavState(2);
145
+ const detailIsExiting = computeIsExiting("detail", step2.exitingPanelId, step2.stack);
138
146
  expect(detailIsExiting).toBe(true);
139
147
 
140
- // Push to detail again
141
- stack = ["list", "detail"];
142
- depth = 1;
143
- // exitingPanelId still "detail"
144
-
145
- const detailIsExitingAfterPush = computeIsExiting("detail", exitingPanelId, stack);
148
+ const step3 = computeDeepNavState(3);
149
+ const detailIsExitingAfterPush = computeIsExiting("detail", step3.exitingPanelId, step3.stack);
146
150
  expect(detailIsExitingAfterPush).toBe(false);
147
151
 
148
- const detailDepth = computePanelDepth("detail", detailIsExitingAfterPush, stack, depth);
152
+ const detailDepth = computePanelDepth("detail", detailIsExitingAfterPush, step3.stack, step3.depth);
149
153
  expect(detailDepth).toBe(1);
150
154
  });
151
155
  });
@@ -4,7 +4,9 @@
4
4
  * These tests focus on the panel visibility and depth calculation logic,
5
5
  * particularly around the exitingPanelId handling during rapid navigation.
6
6
  */
7
- import { describe, it, expect } from "vitest";
7
+
8
+ // Empty export to make this file a module (prevents global scope pollution)
9
+ export {};
8
10
 
9
11
  /**
10
12
  * Pure function version of the isExiting logic for testing.
@@ -53,67 +55,55 @@ describe("StackTablet panel calculation logic", () => {
53
55
 
54
56
  describe("rapid navigation scenarios", () => {
55
57
  it("handles push → back → push sequence correctly", () => {
56
- // Initial: root active
57
- let stack: readonly string[] = ["root"];
58
- let depth = 0;
59
- let exitingPanelId: string | null = null;
60
-
61
- // Push general
62
- stack = ["root", "general"];
63
- depth = 1;
64
-
65
- // Back to root (general becomes exiting)
66
- stack = ["root"];
67
- depth = 0;
68
- exitingPanelId = "general";
69
-
70
- // Push general again BEFORE exitingPanelId clears
71
- stack = ["root", "general"];
72
- depth = 1;
73
- // exitingPanelId is still "general" (timeout hasn't fired yet)
58
+ // Helper function to compute stack state for the final step
59
+ const computeFinalState = () => {
60
+ // Steps:
61
+ // 1. Initial: root active (stack: ["root"], depth: 0, exitingPanelId: null)
62
+ // 2. Push general (stack: ["root", "general"], depth: 1)
63
+ // 3. Back to root (stack: ["root"], depth: 0, exitingPanelId: "general")
64
+ // 4. Push general again BEFORE exitingPanelId clears
65
+ return {
66
+ stack: ["root", "general"] as readonly string[],
67
+ depth: 1,
68
+ exitingPanelId: "general",
69
+ };
70
+ };
71
+
72
+ const finalState = computeFinalState();
74
73
 
75
74
  // Now general should NOT be exiting because it's in the stack
76
- const isExiting = computeIsExiting("general", exitingPanelId, stack);
75
+ const isExiting = computeIsExiting("general", finalState.exitingPanelId, finalState.stack);
77
76
  expect(isExiting).toBe(false);
78
77
 
79
78
  // Panel depth should be correct
80
- const panelDepth = computePanelDepth("general", isExiting, stack, depth);
79
+ const panelDepth = computePanelDepth("general", isExiting, finalState.stack, finalState.depth);
81
80
  expect(panelDepth).toBe(1); // Not 2!
82
81
  });
83
82
 
84
83
  it("handles Settings menu rapid navigation", () => {
85
- // Simulating actual Settings menu navigation
86
- let stack: readonly string[] = ["root"];
87
- let depth = 0;
88
- let exitingPanelId: string | null = null;
89
-
90
- // Click "General"
91
- stack = ["root", "general"];
92
- depth = 1;
93
-
94
- // Click "About"
95
- stack = ["root", "general", "about"];
96
- depth = 2;
97
-
98
- // Back to General
99
- stack = ["root", "general"];
100
- depth = 1;
101
- exitingPanelId = "about";
102
-
103
- // Immediately back to root
104
- stack = ["root"];
105
- depth = 0;
106
- exitingPanelId = "general";
107
-
108
- // Immediately push General again
109
- stack = ["root", "general"];
110
- depth = 1;
84
+ // Helper function to compute final state after rapid navigation
85
+ const computeSettingsNavState = () => {
86
+ // Steps:
87
+ // 1. Initial: root (stack: ["root"], depth: 0)
88
+ // 2. Click "General" (stack: ["root", "general"], depth: 1)
89
+ // 3. Click "About" (stack: ["root", "general", "about"], depth: 2)
90
+ // 4. Back to General (stack: ["root", "general"], depth: 1, exitingPanelId: "about")
91
+ // 5. Immediately back to root (stack: ["root"], depth: 0, exitingPanelId: "general")
92
+ // 6. Immediately push General again
93
+ return {
94
+ stack: ["root", "general"] as readonly string[],
95
+ depth: 1,
96
+ exitingPanelId: "general",
97
+ };
98
+ };
99
+
100
+ const finalState = computeSettingsNavState();
111
101
 
112
102
  // general should NOT be exiting
113
- const isExiting = computeIsExiting("general", exitingPanelId, stack);
103
+ const isExiting = computeIsExiting("general", finalState.exitingPanelId, finalState.stack);
114
104
  expect(isExiting).toBe(false);
115
105
 
116
- const panelDepth = computePanelDepth("general", isExiting, stack, depth);
106
+ const panelDepth = computePanelDepth("general", isExiting, finalState.stack, finalState.depth);
117
107
  expect(panelDepth).toBe(1);
118
108
  });
119
109
  });
@@ -28,6 +28,7 @@ import DR_Basics from "./pages/Drawer/basics";
28
28
  import DR_Menu from "./pages/Drawer/menu";
29
29
  import DR_Animations from "./pages/Drawer/animations";
30
30
  import DR_Swipe from "./pages/Drawer/swipe";
31
+ import DR_Reveal from "./pages/Drawer/reveal";
31
32
 
32
33
  import DL_Modal from "./pages/Dialog/modal";
33
34
  import DL_Alerts from "./pages/Dialog/alerts";
@@ -93,6 +94,7 @@ export const demoCategories: DemoCategory[] = [
93
94
  { id: "menu", label: "Menu", path: "menu", element: <DR_Menu /> },
94
95
  { id: "animations", label: "Animations", path: "animations", element: <DR_Animations /> },
95
96
  { id: "swipe", label: "Swipe Gestures", path: "swipe", element: <DR_Swipe /> },
97
+ { id: "reveal", label: "Reveal Mode", path: "reveal", element: <DR_Reveal /> },
96
98
  ],
97
99
  },
98
100
  {
@@ -74,8 +74,10 @@ export { useDialogContainer } from "../modules/dialog/useDialogContainer.js";
74
74
  // Types
75
75
  export type {
76
76
  ModalProps,
77
+ ModalHandle,
77
78
  ModalHeader,
78
79
  DialogContainerProps,
80
+ DialogContainerHandle,
79
81
  DialogTransitionMode,
80
82
  AlertOptions,
81
83
  ConfirmOptions,
@@ -77,6 +77,7 @@ function createFullPointerEvent(
77
77
  width: 1,
78
78
  // UIEvent properties
79
79
  detail: 0,
80
+ // eslint-disable-next-line custom/no-as-outside-guard -- Required for React types compatibility
80
81
  view: window as unknown as React.AbstractView,
81
82
  };
82
83
  }