react-panel-layout 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/dist/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
  2. package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/FloatingWindow-CUXnEtrb.js +827 -0
  4. package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
  5. package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
  6. package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
  7. package/dist/GridLayout-DKTg_N61.cjs +2 -0
  8. package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DKTg_N61.cjs.map} +1 -1
  9. package/dist/{GridLayout-BltqeCPK.js → GridLayout-UWNxXw77.js} +34 -35
  10. package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-UWNxXw77.js.map} +1 -1
  11. package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
  12. package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
  13. package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
  14. package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
  15. package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-BqUzNtf2.js} +5 -5
  16. package/dist/{PanelSystem-Dr1TBhxM.js.map → PanelSystem-BqUzNtf2.js.map} +1 -1
  17. package/dist/{PanelSystem-Bs8bQwQF.cjs → PanelSystem-D603LKKv.cjs} +2 -2
  18. package/dist/{PanelSystem-Bs8bQwQF.cjs.map → PanelSystem-D603LKKv.cjs.map} +1 -1
  19. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  20. package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
  21. package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
  22. package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
  23. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  24. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  25. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  26. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  27. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  28. package/dist/components/window/Drawer.d.ts +3 -1
  29. package/dist/components/window/DrawerLayers.d.ts +1 -1
  30. package/dist/components/window/drawerStyles.d.ts +69 -0
  31. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  32. package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
  33. package/dist/config.cjs +1 -1
  34. package/dist/config.js +3 -3
  35. package/dist/constants/styles.d.ts +17 -0
  36. package/dist/dialog/index.d.ts +69 -0
  37. package/dist/floating.js +1 -1
  38. package/dist/grid.cjs +1 -1
  39. package/dist/grid.js +2 -2
  40. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
  41. package/dist/hooks/gesture/types.d.ts +48 -5
  42. package/dist/hooks/gesture/utils.d.ts +19 -0
  43. package/dist/hooks/useAnimationFrame.d.ts +2 -0
  44. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  45. package/dist/hooks/useResizeObserver.d.ts +33 -1
  46. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  47. package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
  48. package/dist/index.cjs +1 -1
  49. package/dist/index.js +7 -7
  50. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  51. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  52. package/dist/modules/dialog/Modal.d.ts +26 -0
  53. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  54. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  55. package/dist/modules/dialog/types.d.ts +183 -0
  56. package/dist/modules/dialog/useDialog.d.ts +39 -0
  57. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  58. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  59. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  60. package/dist/modules/drawer/types.d.ts +74 -0
  61. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  62. package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
  63. package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
  64. package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
  65. package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
  66. package/dist/panels.cjs +1 -1
  67. package/dist/panels.js +1 -1
  68. package/dist/pivot.cjs +1 -1
  69. package/dist/pivot.js +1 -1
  70. package/dist/resizer.cjs +1 -1
  71. package/dist/resizer.js +2 -2
  72. package/dist/stack.cjs +1 -1
  73. package/dist/stack.cjs.map +1 -1
  74. package/dist/stack.js +503 -762
  75. package/dist/stack.js.map +1 -1
  76. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  77. package/dist/sticky-header.cjs +1 -1
  78. package/dist/sticky-header.cjs.map +1 -1
  79. package/dist/sticky-header.js +59 -51
  80. package/dist/sticky-header.js.map +1 -1
  81. package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
  82. package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
  83. package/dist/styles-qf6ptVLD.cjs.map +1 -1
  84. package/dist/types.d.ts +16 -0
  85. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  86. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  87. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  88. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  89. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
  90. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
  91. package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
  92. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
  93. package/dist/window/index.d.ts +2 -0
  94. package/dist/window.cjs +1 -1
  95. package/dist/window.cjs.map +1 -1
  96. package/dist/window.js +114 -103
  97. package/dist/window.js.map +1 -1
  98. package/package.json +6 -1
  99. package/src/components/gesture/SwipeSafeZone.tsx +69 -0
  100. package/src/components/window/Drawer.tsx +249 -162
  101. package/src/components/window/DrawerLayers.tsx +13 -3
  102. package/src/components/window/drawerStyles.spec.ts +263 -0
  103. package/src/components/window/drawerStyles.ts +228 -0
  104. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  105. package/src/components/window/drawerSwipeConfig.ts +112 -0
  106. package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
  107. package/src/components/window/useDrawerSwipeTransform.ts +129 -0
  108. package/src/constants/styles.ts +19 -0
  109. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  110. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  111. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  112. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  113. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
  114. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  115. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  116. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  117. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  118. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  119. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  120. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  121. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  122. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  123. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  124. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  125. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
  126. package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
  127. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
  128. package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
  129. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
  130. package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
  131. package/src/demo/routes.tsx +22 -1
  132. package/src/dialog/index.ts +85 -0
  133. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
  134. package/src/hooks/gesture/testing/createGestureSimulator.ts +112 -37
  135. package/src/hooks/gesture/types.ts +83 -6
  136. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
  137. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +91 -31
  138. package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
  139. package/src/hooks/gesture/utils.ts +91 -0
  140. package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
  141. package/src/hooks/useAnimatedVisibility.ts +28 -2
  142. package/src/hooks/useAnimationFrame.ts +8 -0
  143. package/src/hooks/useOperationContinuity.spec.ts +387 -0
  144. package/src/hooks/useOperationContinuity.ts +135 -0
  145. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  146. package/src/hooks/useResizeObserver.tsx +108 -39
  147. package/src/hooks/useScrollContainer.ts +4 -10
  148. package/src/hooks/useSharedElementTransition.ts +333 -0
  149. package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
  150. package/src/hooks/useSwipeContentTransform.ts +166 -28
  151. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  152. package/src/modules/dialog/AlertDialog.tsx +221 -0
  153. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  154. package/src/modules/dialog/DialogContainer.tsx +188 -0
  155. package/src/modules/dialog/Modal.spec.tsx +220 -0
  156. package/src/modules/dialog/Modal.tsx +182 -0
  157. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  158. package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
  159. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  160. package/src/modules/dialog/types.ts +186 -0
  161. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  162. package/src/modules/dialog/useDialog.ts +214 -0
  163. package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
  164. package/src/modules/dialog/useDialogContainer.ts +150 -0
  165. package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
  166. package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
  167. package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
  168. package/src/modules/dialog/useDialogTransform.ts +407 -0
  169. package/src/modules/drawer/types.ts +102 -0
  170. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  171. package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
  172. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
  173. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
  174. package/src/modules/pivot/SwipePivotContent.spec.tsx +55 -25
  175. package/src/modules/pivot/SwipePivotContent.tsx +2 -2
  176. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
  177. package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
  178. package/src/modules/pivot/scaleInputState.spec.ts +11 -2
  179. package/src/modules/pivot/usePivot.spec.ts +17 -3
  180. package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
  181. package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
  182. package/src/modules/stack/SwipeStackContent.tsx +43 -33
  183. package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
  184. package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
  185. package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
  186. package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
  187. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  188. package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
  189. package/src/modules/stack/useStackAnimationState.ts +18 -13
  190. package/src/modules/stack/useStackNavigation.spec.ts +198 -3
  191. package/src/modules/stack/useStackNavigation.tsx +113 -56
  192. package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
  193. package/src/modules/stack/useStackSwipeInput.ts +1 -1
  194. package/src/sticky-header/StickyArea.tsx +29 -57
  195. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  196. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  197. package/src/types.ts +18 -0
  198. package/src/window/index.ts +2 -0
  199. package/dist/FloatingWindow-BpdOpg_L.js +0 -400
  200. package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
  201. package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
  202. package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
  203. package/dist/GridLayout-B4VRsC0r.cjs +0 -2
  204. package/dist/ResizeHandle-CScipO5l.cjs +0 -2
  205. package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
  206. package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
  207. package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
  208. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
  209. package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
  210. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
  211. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
  212. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
  213. package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
  214. package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
  215. package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
  216. package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
@@ -0,0 +1,221 @@
1
+ /**
2
+ * @file AlertDialog component for alert, confirm, and prompt dialogs
3
+ */
4
+ import * as React from "react";
5
+ import type { AlertDialogProps } from "./types";
6
+ import { DialogContainer } from "./DialogContainer";
7
+ import {
8
+ FloatingPanelFrame,
9
+ FloatingPanelHeader,
10
+ FloatingPanelTitle,
11
+ FloatingPanelContent,
12
+ } from "../../components/paneling/FloatingPanelFrame";
13
+ import {
14
+ ALERT_DIALOG_WIDTH,
15
+ ALERT_DIALOG_BUTTON_GAP,
16
+ ALERT_DIALOG_ACTIONS_PADDING,
17
+ ALERT_DIALOG_MESSAGE_PADDING,
18
+ ALERT_DIALOG_INPUT_MARGIN_TOP,
19
+ FLOATING_PANEL_HEADER_PADDING_Y,
20
+ FLOATING_PANEL_HEADER_PADDING_X,
21
+ COLOR_PRIMARY,
22
+ COLOR_NODE_EDITOR_BORDER,
23
+ } from "../../constants/styles";
24
+
25
+ const alertDialogStyle: React.CSSProperties = {
26
+ width: ALERT_DIALOG_WIDTH,
27
+ maxWidth: "90vw",
28
+ };
29
+
30
+ const messageStyle: React.CSSProperties = {
31
+ padding: ALERT_DIALOG_MESSAGE_PADDING,
32
+ whiteSpace: "pre-wrap",
33
+ wordBreak: "break-word",
34
+ };
35
+
36
+ const actionsStyle: React.CSSProperties = {
37
+ display: "flex",
38
+ justifyContent: "flex-end",
39
+ gap: ALERT_DIALOG_BUTTON_GAP,
40
+ padding: ALERT_DIALOG_ACTIONS_PADDING,
41
+ borderTop: `1px solid ${COLOR_NODE_EDITOR_BORDER}`,
42
+ };
43
+
44
+ const inputStyle: React.CSSProperties = {
45
+ width: "100%",
46
+ padding: "8px 12px",
47
+ marginTop: ALERT_DIALOG_INPUT_MARGIN_TOP,
48
+ border: `1px solid ${COLOR_NODE_EDITOR_BORDER}`,
49
+ borderRadius: "4px",
50
+ fontSize: "14px",
51
+ boxSizing: "border-box",
52
+ };
53
+
54
+ const buttonBaseStyle: React.CSSProperties = {
55
+ padding: "8px 16px",
56
+ borderRadius: "4px",
57
+ fontSize: "14px",
58
+ fontWeight: 500,
59
+ cursor: "pointer",
60
+ border: "none",
61
+ transition: "background-color 0.15s ease",
62
+ };
63
+
64
+ const primaryButtonStyle: React.CSSProperties = {
65
+ ...buttonBaseStyle,
66
+ backgroundColor: COLOR_PRIMARY,
67
+ color: "#fff",
68
+ };
69
+
70
+ const secondaryButtonStyle: React.CSSProperties = {
71
+ ...buttonBaseStyle,
72
+ backgroundColor: "transparent",
73
+ border: `1px solid ${COLOR_NODE_EDITOR_BORDER}`,
74
+ color: "inherit",
75
+ };
76
+
77
+ /**
78
+ * Internal component for alert, confirm, and prompt dialogs
79
+ */
80
+ export const AlertDialog: React.FC<AlertDialogProps> = ({
81
+ type,
82
+ visible,
83
+ title,
84
+ message,
85
+ confirmLabel = "OK",
86
+ cancelLabel = "Cancel",
87
+ placeholder,
88
+ defaultValue = "",
89
+ inputType = "text",
90
+ onConfirm,
91
+ onCancel,
92
+ swipeDismissible,
93
+ }) => {
94
+ // Default swipeDismissible: false for alert (shouldn't be accidentally dismissed), true otherwise
95
+ const effectiveSwipeDismissible = swipeDismissible ?? (type !== "alert");
96
+ const [inputValue, setInputValue] = React.useState(defaultValue);
97
+ const inputRef = React.useRef<HTMLInputElement>(null);
98
+
99
+ // Reset input value when dialog opens
100
+ React.useEffect(() => {
101
+ if (visible) {
102
+ setInputValue(defaultValue);
103
+ }
104
+ }, [visible, defaultValue]);
105
+
106
+ // Focus input when prompt dialog opens
107
+ React.useEffect(() => {
108
+ if (!visible) {
109
+ return;
110
+ }
111
+ if (type !== "prompt") {
112
+ return;
113
+ }
114
+ if (!inputRef.current) {
115
+ return;
116
+ }
117
+ // Small delay to ensure dialog is rendered
118
+ const timeoutId = setTimeout(() => {
119
+ inputRef.current?.focus();
120
+ inputRef.current?.select();
121
+ }, 50);
122
+ return () => clearTimeout(timeoutId);
123
+ }, [visible, type]);
124
+
125
+ const handleConfirm = React.useCallback(() => {
126
+ if (type === "prompt") {
127
+ onConfirm(inputValue);
128
+ } else {
129
+ onConfirm();
130
+ }
131
+ }, [type, inputValue, onConfirm]);
132
+
133
+ const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
134
+ setInputValue(event.target.value);
135
+ }, []);
136
+
137
+ const handleInputKeyDown = React.useCallback(
138
+ (event: React.KeyboardEvent<HTMLInputElement>) => {
139
+ if (event.key === "Enter") {
140
+ event.preventDefault();
141
+ handleConfirm();
142
+ }
143
+ },
144
+ [handleConfirm],
145
+ );
146
+
147
+ // Prevent input events from bubbling up
148
+ const handleInputPointerDown = React.useCallback((event: React.PointerEvent) => {
149
+ event.stopPropagation();
150
+ }, []);
151
+
152
+ const computeDialogLabel = (): string => {
153
+ if (title) {
154
+ return title;
155
+ }
156
+ if (type === "prompt") {
157
+ return "Prompt";
158
+ }
159
+ if (type === "confirm") {
160
+ return "Confirm";
161
+ }
162
+ return "Alert";
163
+ };
164
+ const dialogLabel = computeDialogLabel();
165
+
166
+ return (
167
+ <DialogContainer
168
+ visible={visible}
169
+ onClose={onCancel}
170
+ position="center"
171
+ dismissible={type !== "alert"}
172
+ closeOnEscape={true}
173
+ ariaLabel={dialogLabel}
174
+ swipeDismissible={effectiveSwipeDismissible}
175
+ >
176
+ <div style={alertDialogStyle}>
177
+ <FloatingPanelFrame>
178
+ <React.Activity mode={title ? "visible" : "hidden"}>
179
+ <FloatingPanelHeader
180
+ style={{
181
+ padding: `${FLOATING_PANEL_HEADER_PADDING_Y} ${FLOATING_PANEL_HEADER_PADDING_X}`,
182
+ }}
183
+ >
184
+ <FloatingPanelTitle>{title}</FloatingPanelTitle>
185
+ </FloatingPanelHeader>
186
+ </React.Activity>
187
+ <FloatingPanelContent style={{ padding: 0 }}>
188
+ <div style={messageStyle}>
189
+ {message}
190
+ <React.Activity mode={type === "prompt" ? "visible" : "hidden"}>
191
+ <input
192
+ ref={inputRef}
193
+ type={inputType}
194
+ value={inputValue}
195
+ onChange={handleInputChange}
196
+ onKeyDown={handleInputKeyDown}
197
+ onPointerDown={handleInputPointerDown}
198
+ placeholder={placeholder}
199
+ style={inputStyle}
200
+ aria-label={placeholder ?? "Input"}
201
+ />
202
+ </React.Activity>
203
+ </div>
204
+ <div style={actionsStyle}>
205
+ <React.Activity mode={type !== "alert" ? "visible" : "hidden"}>
206
+ <button type="button" style={secondaryButtonStyle} onClick={onCancel}>
207
+ {cancelLabel}
208
+ </button>
209
+ </React.Activity>
210
+ <button type="button" style={primaryButtonStyle} onClick={handleConfirm}>
211
+ {confirmLabel}
212
+ </button>
213
+ </div>
214
+ </FloatingPanelContent>
215
+ </FloatingPanelFrame>
216
+ </div>
217
+ </DialogContainer>
218
+ );
219
+ };
220
+
221
+ AlertDialog.displayName = "AlertDialog";
@@ -0,0 +1,228 @@
1
+ /**
2
+ * @file Tests for DialogContainer component
3
+ */
4
+ import { render, screen, fireEvent } from "@testing-library/react";
5
+ import { DialogContainer } from "./DialogContainer";
6
+ import * as React from "react";
7
+
8
+ type CallTracker = {
9
+ calls: ReadonlyArray<ReadonlyArray<unknown>>;
10
+ fn: (...args: ReadonlyArray<unknown>) => void;
11
+ };
12
+
13
+ const createCallTracker = (): CallTracker => {
14
+ const calls: Array<ReadonlyArray<unknown>> = [];
15
+ const fn = (...args: ReadonlyArray<unknown>): void => {
16
+ calls.push(args);
17
+ };
18
+ return { calls, fn };
19
+ };
20
+
21
+ describe("DialogContainer", () => {
22
+ const originalShowModal = HTMLDialogElement.prototype.showModal;
23
+ const originalClose = HTMLDialogElement.prototype.close;
24
+ const dialogCallState = {
25
+ showModal: createCallTracker(),
26
+ close: createCallTracker(),
27
+ };
28
+
29
+ beforeEach(() => {
30
+ // Mock showModal and close for dialog element
31
+ dialogCallState.showModal = createCallTracker();
32
+ dialogCallState.close = createCallTracker();
33
+ HTMLDialogElement.prototype.showModal = function (this: HTMLDialogElement) {
34
+ dialogCallState.showModal.fn();
35
+ this.setAttribute("open", "");
36
+ };
37
+ HTMLDialogElement.prototype.close = function (this: HTMLDialogElement) {
38
+ dialogCallState.close.fn();
39
+ this.removeAttribute("open");
40
+ };
41
+ });
42
+
43
+ afterEach(() => {
44
+ HTMLDialogElement.prototype.showModal = originalShowModal;
45
+ HTMLDialogElement.prototype.close = originalClose;
46
+ document.body.style.overflow = "";
47
+ document.body.style.paddingRight = "";
48
+ });
49
+
50
+ it("should render children when visible", () => {
51
+ render(
52
+ <DialogContainer visible={true} onClose={() => {}}>
53
+ <div data-testid="content">Hello</div>
54
+ </DialogContainer>,
55
+ );
56
+
57
+ expect(screen.getByTestId("content")).toBeInTheDocument();
58
+ expect(screen.getByTestId("content")).toHaveTextContent("Hello");
59
+ });
60
+
61
+ it("should call showModal when visible is true", () => {
62
+ render(
63
+ <DialogContainer visible={true} onClose={() => {}}>
64
+ <div>Content</div>
65
+ </DialogContainer>,
66
+ );
67
+
68
+ expect(dialogCallState.showModal.calls).toHaveLength(1);
69
+ });
70
+
71
+ it("should call close when visible changes from true to false", () => {
72
+ const { rerender } = render(
73
+ <DialogContainer visible={true} onClose={() => {}}>
74
+ <div>Content</div>
75
+ </DialogContainer>,
76
+ );
77
+
78
+ rerender(
79
+ <DialogContainer visible={false} onClose={() => {}}>
80
+ <div>Content</div>
81
+ </DialogContainer>,
82
+ );
83
+
84
+ expect(dialogCallState.close.calls).toHaveLength(1);
85
+ });
86
+
87
+ it("should call onClose when Escape is pressed", () => {
88
+ const onClose = createCallTracker();
89
+ render(
90
+ <DialogContainer visible={true} onClose={onClose.fn}>
91
+ <div>Content</div>
92
+ </DialogContainer>,
93
+ );
94
+
95
+ const dialog = document.querySelector("dialog");
96
+ expect(dialog).not.toBeNull();
97
+
98
+ // Simulate cancel event (triggered by Escape key)
99
+ fireEvent(dialog!, new Event("cancel", { bubbles: true, cancelable: true }));
100
+
101
+ expect(onClose.calls).toHaveLength(1);
102
+ });
103
+
104
+ it("should NOT call onClose when Escape is pressed and closeOnEscape is false", () => {
105
+ const onClose = createCallTracker();
106
+ render(
107
+ <DialogContainer visible={true} onClose={onClose.fn} closeOnEscape={false}>
108
+ <div>Content</div>
109
+ </DialogContainer>,
110
+ );
111
+
112
+ const dialog = document.querySelector("dialog");
113
+ fireEvent(dialog!, new Event("cancel", { bubbles: true, cancelable: true }));
114
+
115
+ expect(onClose.calls).toHaveLength(0);
116
+ });
117
+
118
+ it("should call onClose when backdrop is clicked", () => {
119
+ const onClose = createCallTracker();
120
+ render(
121
+ <DialogContainer visible={true} onClose={onClose.fn}>
122
+ <div data-testid="content">Content</div>
123
+ </DialogContainer>,
124
+ );
125
+
126
+ const dialog = document.querySelector("dialog");
127
+ // Click directly on dialog (backdrop area)
128
+ fireEvent.click(dialog!);
129
+
130
+ expect(onClose.calls).toHaveLength(1);
131
+ });
132
+
133
+ it("should NOT call onClose when content is clicked", () => {
134
+ const onClose = createCallTracker();
135
+ render(
136
+ <DialogContainer visible={true} onClose={onClose.fn}>
137
+ <div data-testid="content">Content</div>
138
+ </DialogContainer>,
139
+ );
140
+
141
+ const content = screen.getByTestId("content");
142
+ fireEvent.click(content);
143
+
144
+ expect(onClose.calls).toHaveLength(0);
145
+ });
146
+
147
+ it("should NOT call onClose when backdrop is clicked and dismissible is false", () => {
148
+ const onClose = createCallTracker();
149
+ render(
150
+ <DialogContainer visible={true} onClose={onClose.fn} dismissible={false}>
151
+ <div>Content</div>
152
+ </DialogContainer>,
153
+ );
154
+
155
+ const dialog = document.querySelector("dialog");
156
+ fireEvent.click(dialog!);
157
+
158
+ expect(onClose.calls).toHaveLength(0);
159
+ });
160
+
161
+ it("should apply aria attributes", () => {
162
+ render(
163
+ <DialogContainer
164
+ visible={true}
165
+ onClose={() => {}}
166
+ ariaLabel="Test dialog"
167
+ ariaLabelledBy="title-id"
168
+ ariaDescribedBy="desc-id"
169
+ >
170
+ <div>Content</div>
171
+ </DialogContainer>,
172
+ );
173
+
174
+ const dialog = document.querySelector("dialog");
175
+ expect(dialog).toHaveAttribute("aria-label", "Test dialog");
176
+ expect(dialog).toHaveAttribute("aria-labelledby", "title-id");
177
+ expect(dialog).toHaveAttribute("aria-describedby", "desc-id");
178
+ });
179
+
180
+ it("should prevent body scroll when visible", () => {
181
+ render(
182
+ <DialogContainer visible={true} onClose={() => {}}>
183
+ <div>Content</div>
184
+ </DialogContainer>,
185
+ );
186
+
187
+ expect(document.body.style.overflow).toBe("hidden");
188
+ });
189
+
190
+ it("should restore body scroll on unmount", () => {
191
+ const { unmount } = render(
192
+ <DialogContainer visible={true} onClose={() => {}}>
193
+ <div>Content</div>
194
+ </DialogContainer>,
195
+ );
196
+
197
+ expect(document.body.style.overflow).toBe("hidden");
198
+
199
+ unmount();
200
+
201
+ expect(document.body.style.overflow).toBe("");
202
+ });
203
+
204
+ it("should NOT prevent body scroll when preventBodyScroll is false", () => {
205
+ render(
206
+ <DialogContainer visible={true} onClose={() => {}} preventBodyScroll={false}>
207
+ <div>Content</div>
208
+ </DialogContainer>,
209
+ );
210
+
211
+ expect(document.body.style.overflow).toBe("");
212
+ });
213
+
214
+ it("should stop propagation on content pointer down to prevent backdrop detection", () => {
215
+ const onClose = createCallTracker();
216
+ render(
217
+ <DialogContainer visible={true} onClose={onClose.fn}>
218
+ <button data-testid="button">Click me</button>
219
+ </DialogContainer>,
220
+ );
221
+
222
+ const button = screen.getByTestId("button");
223
+ fireEvent.pointerDown(button);
224
+
225
+ // The click should not close the dialog because propagation is stopped
226
+ expect(onClose.calls).toHaveLength(0);
227
+ });
228
+ });
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @file Base dialog container component using native <dialog> element
3
+ */
4
+ import * as React from "react";
5
+ import type { DialogContainerProps } from "./types.js";
6
+ import { useDialogContainer } from "./useDialogContainer.js";
7
+ import { SwipeDialogContainer } from "./SwipeDialogContainer.js";
8
+ import {
9
+ COLOR_MODAL_BACKDROP,
10
+ MODAL_TRANSITION_DURATION,
11
+ MODAL_TRANSITION_EASING,
12
+ } from "../../constants/styles.js";
13
+
14
+ const dialogBaseStyle: React.CSSProperties = {
15
+ border: "none",
16
+ padding: 0,
17
+ background: "transparent",
18
+ maxWidth: "none",
19
+ maxHeight: "none",
20
+ overflow: "visible",
21
+ };
22
+
23
+ const contentWrapperStyle: React.CSSProperties = {
24
+ display: "flex",
25
+ alignItems: "center",
26
+ justifyContent: "center",
27
+ position: "fixed",
28
+ inset: 0,
29
+ pointerEvents: "none",
30
+ };
31
+
32
+ const contentStyle: React.CSSProperties = {
33
+ pointerEvents: "auto",
34
+ };
35
+
36
+ const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
37
+
38
+ type DialogContainerImplProps = DialogContainerProps;
39
+
40
+ const DialogContainerImpl: React.FC<DialogContainerImplProps> = ({
41
+ visible,
42
+ onClose,
43
+ children,
44
+ position = "center",
45
+ dismissible = true,
46
+ closeOnEscape = true,
47
+ returnFocus = true,
48
+ preventBodyScroll = true,
49
+ ariaLabel,
50
+ ariaLabelledBy,
51
+ ariaDescribedBy,
52
+ transitionMode = "css",
53
+ transitionDuration = MODAL_TRANSITION_DURATION,
54
+ transitionEasing = MODAL_TRANSITION_EASING,
55
+ }) => {
56
+ const { dialogRef, dialogProps } = useDialogContainer({
57
+ visible,
58
+ onClose,
59
+ dismissible,
60
+ closeOnEscape,
61
+ returnFocus,
62
+ preventBodyScroll,
63
+ });
64
+
65
+ const backdropStyle = React.useMemo((): string => {
66
+ if (transitionMode === "none") {
67
+ return `
68
+ dialog::backdrop {
69
+ background: ${COLOR_MODAL_BACKDROP};
70
+ }
71
+ `;
72
+ }
73
+ return `
74
+ dialog::backdrop {
75
+ background: ${COLOR_MODAL_BACKDROP};
76
+ opacity: 0;
77
+ transition: opacity ${transitionDuration} ${transitionEasing};
78
+ }
79
+ dialog[open]::backdrop {
80
+ opacity: 1;
81
+ }
82
+ `;
83
+ }, [transitionMode, transitionDuration, transitionEasing]);
84
+
85
+ const computedContentWrapperStyle = React.useMemo((): React.CSSProperties => {
86
+ if (position === "center") {
87
+ return contentWrapperStyle;
88
+ }
89
+ // Absolute position
90
+ const style: React.CSSProperties = {
91
+ ...contentWrapperStyle,
92
+ alignItems: "flex-start",
93
+ justifyContent: "flex-start",
94
+ };
95
+ if (position.x !== undefined) {
96
+ style.left = position.x;
97
+ }
98
+ if (position.y !== undefined) {
99
+ style.top = position.y;
100
+ }
101
+ return style;
102
+ }, [position]);
103
+
104
+ const computedContentStyle = React.useMemo((): React.CSSProperties => {
105
+ if (transitionMode === "none") {
106
+ return contentStyle;
107
+ }
108
+ return {
109
+ ...contentStyle,
110
+ opacity: visible ? 1 : 0,
111
+ transform: visible ? "scale(1)" : "scale(0.95)",
112
+ transition: `opacity ${transitionDuration} ${transitionEasing}, transform ${transitionDuration} ${transitionEasing}`,
113
+ };
114
+ }, [visible, transitionMode, transitionDuration, transitionEasing]);
115
+
116
+ // Stop propagation on pointer down inside content to prevent backdrop click handling
117
+ const handleContentPointerDown = React.useCallback((event: React.PointerEvent) => {
118
+ event.stopPropagation();
119
+ }, []);
120
+
121
+ return (
122
+ <>
123
+ <style>{backdropStyle}</style>
124
+ <dialog
125
+ ref={dialogRef}
126
+ style={dialogBaseStyle}
127
+ aria-label={ariaLabel}
128
+ aria-labelledby={ariaLabelledBy}
129
+ aria-describedby={ariaDescribedBy}
130
+ {...dialogProps}
131
+ >
132
+ <div style={computedContentWrapperStyle}>
133
+ <React.Activity mode={visible ? "visible" : "hidden"}>
134
+ <div style={computedContentStyle} onPointerDown={handleContentPointerDown}>
135
+ {children}
136
+ </div>
137
+ </React.Activity>
138
+ </div>
139
+ </dialog>
140
+ </>
141
+ );
142
+ };
143
+
144
+ /**
145
+ * Base container for dialog-based overlays using native <dialog> element.
146
+ * Opens in the browser's top layer, ensuring it appears above all other content.
147
+ *
148
+ * Supports three transition modes:
149
+ * - "none": No animation
150
+ * - "css": CSS-based fade/scale animation (default)
151
+ * - "swipe": Swipeable with multi-phase animation
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * <DialogContainer
156
+ * visible={isOpen}
157
+ * onClose={() => setIsOpen(false)}
158
+ * >
159
+ * <div>Dialog content</div>
160
+ * </DialogContainer>
161
+ * ```
162
+ *
163
+ * @example Swipeable dialog
164
+ * ```tsx
165
+ * <DialogContainer
166
+ * visible={isOpen}
167
+ * onClose={() => setIsOpen(false)}
168
+ * transitionMode="swipe"
169
+ * openDirection="bottom"
170
+ * >
171
+ * <div>Swipe down to close</div>
172
+ * </DialogContainer>
173
+ * ```
174
+ */
175
+ export const DialogContainer: React.FC<DialogContainerProps> = (props) => {
176
+ if (!isBrowser) {
177
+ return null;
178
+ }
179
+
180
+ // Use SwipeDialogContainer for swipe mode
181
+ if (props.transitionMode === "swipe") {
182
+ return <SwipeDialogContainer {...props} />;
183
+ }
184
+
185
+ return <DialogContainerImpl {...props} />;
186
+ };
187
+
188
+ DialogContainer.displayName = "DialogContainer";