@astryxdesign/lab 0.4.2-canary.b2057d1 → 0.4.2-canary.bb07062
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/BottomSheet/BottomSheet.d.ts +9 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +34 -12
- package/dist/BottomSheet/BottomSheetPanel.d.ts +2 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +2 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +44 -9
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +2 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +12 -2
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +100 -19
- package/package.json +3 -3
- package/src/BottomSheet/BottomSheet.doc.mjs +32 -4
- package/src/BottomSheet/BottomSheet.test.tsx +85 -1
- package/src/BottomSheet/BottomSheet.tsx +45 -12
- package/src/BottomSheet/BottomSheetPanel.tsx +3 -0
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +2 -2
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +112 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +53 -12
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +5 -0
- package/src/BottomSheet/useSheetGestures.test.ts +134 -0
- package/src/BottomSheet/useSheetGestures.ts +145 -22
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { type ReactNode } from 'react';
|
|
22
22
|
import type { BaseProps } from '@astryxdesign/core';
|
|
23
|
+
import type { DialogPurpose } from '@astryxdesign/core/Dialog';
|
|
23
24
|
export type { BottomSheetHeight } from './BottomSheetPanel';
|
|
24
25
|
import type { BottomSheetHeight } from './BottomSheetPanel';
|
|
25
26
|
interface BottomSheetSharedProps extends BaseProps<HTMLDivElement> {
|
|
@@ -31,6 +32,14 @@ interface BottomSheetSharedProps extends BaseProps<HTMLDivElement> {
|
|
|
31
32
|
children: ReactNode;
|
|
32
33
|
/** Height budget or custom CSS length. Only fully expanded Tall is keyboard-aware. @default 'capped' */
|
|
33
34
|
height?: BottomSheetHeight | number | string;
|
|
35
|
+
/**
|
|
36
|
+
* Configures implicit dismissal behavior, matching Dialog.
|
|
37
|
+
* - required: Blocks swipe, scrim click, and Escape
|
|
38
|
+
* - form: Blocks swipe and scrim click, allows Escape
|
|
39
|
+
* - info: Allows swipe, scrim click, and Escape
|
|
40
|
+
* @default 'info'
|
|
41
|
+
*/
|
|
42
|
+
purpose?: DialogPurpose;
|
|
34
43
|
}
|
|
35
44
|
interface StandaloneBottomSheetProps extends BottomSheetSharedProps {
|
|
36
45
|
isOpen: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"BottomSheet.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheet.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AAkB7D,YAAY,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AA2D1D,UAAU,sBAAuB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAChE,qDAAqD;IACrD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IAEd,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,CAAC;IAEpB,wGAAwG;IACxG,MAAM,CAAC,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAE7C;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,UAAU,0BAA2B,SAAQ,sBAAsB;IACjE,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC;CACjB;AAED,UAAU,wBAAyB,SAAQ,sBAAsB;IAC/D,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GAC1B,0BAA0B,GAAG,wBAAwB,CAAC;AA+VxD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BA6BlD;yBA7Be,WAAW"}
|
|
@@ -80,6 +80,7 @@ function StandaloneBottomSheet({
|
|
|
80
80
|
children,
|
|
81
81
|
height = 'capped',
|
|
82
82
|
hasScrim = true,
|
|
83
|
+
purpose = 'info',
|
|
83
84
|
xstyle,
|
|
84
85
|
...props
|
|
85
86
|
}) {
|
|
@@ -96,7 +97,16 @@ function StandaloneBottomSheet({
|
|
|
96
97
|
} : {
|
|
97
98
|
kind: 'hidden'
|
|
98
99
|
};
|
|
99
|
-
const
|
|
100
|
+
const dismissOnEscape = useCallback(() => {
|
|
101
|
+
if (purpose !== 'required') {
|
|
102
|
+
onOpenChange(false);
|
|
103
|
+
}
|
|
104
|
+
}, [onOpenChange, purpose]);
|
|
105
|
+
const dismissOnLightInteraction = useCallback(() => {
|
|
106
|
+
if (purpose === 'info') {
|
|
107
|
+
onOpenChange(false);
|
|
108
|
+
}
|
|
109
|
+
}, [onOpenChange, purpose]);
|
|
100
110
|
const handlePanelElementChange = useCallback(element => {
|
|
101
111
|
panelRef.current = element;
|
|
102
112
|
}, []);
|
|
@@ -142,19 +152,19 @@ function StandaloneBottomSheet({
|
|
|
142
152
|
useDevWarning('BottomSheet', 'requires a non-empty `label` for an accessible name; the open sheet ' + 'has no built-in heading to derive one from.', isOpen && !label);
|
|
143
153
|
const handleCancel = useCallback(event => {
|
|
144
154
|
event.preventDefault();
|
|
145
|
-
|
|
146
|
-
}, [
|
|
155
|
+
dismissOnEscape();
|
|
156
|
+
}, [dismissOnEscape]);
|
|
147
157
|
const handleKeyDown = useCallback(event => {
|
|
148
158
|
if (event.key === 'Escape') {
|
|
149
159
|
event.preventDefault();
|
|
150
|
-
|
|
160
|
+
dismissOnEscape();
|
|
151
161
|
}
|
|
152
|
-
}, [
|
|
162
|
+
}, [dismissOnEscape]);
|
|
153
163
|
const handleClick = useCallback(event => {
|
|
154
164
|
if (hasScrim && event.target === event.currentTarget) {
|
|
155
|
-
|
|
165
|
+
dismissOnLightInteraction();
|
|
156
166
|
}
|
|
157
|
-
}, [
|
|
167
|
+
}, [dismissOnLightInteraction, hasScrim]);
|
|
158
168
|
return /*#__PURE__*/_jsx("dialog", {
|
|
159
169
|
...{
|
|
160
170
|
0: {
|
|
@@ -186,6 +196,7 @@ function StandaloneBottomSheet({
|
|
|
186
196
|
"aria-label": label,
|
|
187
197
|
"aria-hidden": !isOpen && isPresented ? 'true' : undefined,
|
|
188
198
|
"aria-modal": hasScrim && isOpen ? 'true' : undefined,
|
|
199
|
+
role: purpose === 'required' ? 'alertdialog' : undefined,
|
|
189
200
|
inert: !isOpen && isPresented ? true : undefined,
|
|
190
201
|
onCancel: handleCancel,
|
|
191
202
|
onClick: handleClick,
|
|
@@ -199,8 +210,9 @@ function StandaloneBottomSheet({
|
|
|
199
210
|
ref: ref,
|
|
200
211
|
state: panelState,
|
|
201
212
|
height: height,
|
|
213
|
+
canSwipeDismiss: purpose === 'info',
|
|
202
214
|
xstyle: xstyle,
|
|
203
|
-
onDismiss:
|
|
215
|
+
onDismiss: dismissOnLightInteraction,
|
|
204
216
|
onScrimOpacity: handleScrimOpacity,
|
|
205
217
|
onElementChange: handlePanelElementChange,
|
|
206
218
|
onMotionComplete: handleMotionComplete,
|
|
@@ -216,6 +228,7 @@ function SwitcherBottomSheetItem({
|
|
|
216
228
|
label,
|
|
217
229
|
children,
|
|
218
230
|
height = 'capped',
|
|
231
|
+
purpose = 'info',
|
|
219
232
|
xstyle,
|
|
220
233
|
...props
|
|
221
234
|
}) {
|
|
@@ -227,6 +240,7 @@ function SwitcherBottomSheetItem({
|
|
|
227
240
|
getSheetAlignmentOffset,
|
|
228
241
|
registerSheetElement,
|
|
229
242
|
registerSheetLabel,
|
|
243
|
+
registerSheetPurpose,
|
|
230
244
|
onSheetEnterStart,
|
|
231
245
|
onSheetTransitionComplete,
|
|
232
246
|
onSheetScrimOpacityChange
|
|
@@ -246,11 +260,11 @@ function SwitcherBottomSheetItem({
|
|
|
246
260
|
useLayoutEffect(() => {
|
|
247
261
|
previousPhaseRef.current = phase;
|
|
248
262
|
}, [phase]);
|
|
249
|
-
const
|
|
250
|
-
if (hasValidSheetId && activeSheet === sheetId) {
|
|
263
|
+
const dismissOnSwipe = useCallback(() => {
|
|
264
|
+
if (purpose === 'info' && hasValidSheetId && activeSheet === sheetId) {
|
|
251
265
|
onActiveSheetChange(null);
|
|
252
266
|
}
|
|
253
|
-
}, [activeSheet, hasValidSheetId, onActiveSheetChange, sheetId]);
|
|
267
|
+
}, [activeSheet, hasValidSheetId, onActiveSheetChange, purpose, sheetId]);
|
|
254
268
|
const handlePanelElementChange = useCallback(element => {
|
|
255
269
|
panelRef.current = element;
|
|
256
270
|
if (hasValidSheetId) {
|
|
@@ -282,6 +296,13 @@ function SwitcherBottomSheetItem({
|
|
|
282
296
|
registerSheetLabel(sheetId, label);
|
|
283
297
|
return () => registerSheetLabel(sheetId, null);
|
|
284
298
|
}, [hasValidSheetId, label, registerSheetLabel, sheetId]);
|
|
299
|
+
useLayoutEffect(() => {
|
|
300
|
+
if (!hasValidSheetId) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
registerSheetPurpose(sheetId, purpose);
|
|
304
|
+
return () => registerSheetPurpose(sheetId, null);
|
|
305
|
+
}, [hasValidSheetId, purpose, registerSheetPurpose, sheetId]);
|
|
285
306
|
useEffect(() => {
|
|
286
307
|
if (isInteractive) {
|
|
287
308
|
const wasInteractive = previousPhase === 'active' || previousPhase === 'entering';
|
|
@@ -317,8 +338,9 @@ function SwitcherBottomSheetItem({
|
|
|
317
338
|
ref: ref,
|
|
318
339
|
state: panelState,
|
|
319
340
|
height: height,
|
|
341
|
+
canSwipeDismiss: purpose === 'info',
|
|
320
342
|
xstyle: xstyle,
|
|
321
|
-
onDismiss:
|
|
343
|
+
onDismiss: dismissOnSwipe,
|
|
322
344
|
onScrimOpacity: handleScrimOpacity,
|
|
323
345
|
onElementChange: handlePanelElementChange,
|
|
324
346
|
onMotionStart: handleMotionStart,
|
|
@@ -41,6 +41,7 @@ interface BottomSheetPanelProps extends BaseProps<HTMLDivElement> {
|
|
|
41
41
|
state: BottomSheetPanelState;
|
|
42
42
|
height: BottomSheetHeight | number | string;
|
|
43
43
|
children: ReactNode;
|
|
44
|
+
canSwipeDismiss?: boolean;
|
|
44
45
|
onDismiss: () => void;
|
|
45
46
|
onScrimOpacity: (opacity: number) => void;
|
|
46
47
|
onElementChange?: (element: HTMLDivElement | null) => void;
|
|
@@ -48,7 +49,7 @@ interface BottomSheetPanelProps extends BaseProps<HTMLDivElement> {
|
|
|
48
49
|
onMotionComplete?: (motion: BottomSheetPanelMotion) => void;
|
|
49
50
|
}
|
|
50
51
|
/** Internal visual and gesture surface shared by every BottomSheet host. */
|
|
51
|
-
export declare function BottomSheetPanel({ ref, state, height, children, className, style, tabIndex, xstyle, onDismiss, onScrimOpacity, onElementChange, onMotionStart, onMotionComplete, ...props }: BottomSheetPanelProps): import("react").JSX.Element;
|
|
52
|
+
export declare function BottomSheetPanel({ ref, state, height, children, className, style, tabIndex, xstyle, canSwipeDismiss, onDismiss, onScrimOpacity, onElementChange, onMotionStart, onMotionComplete, ...props }: BottomSheetPanelProps): import("react").JSX.Element;
|
|
52
53
|
export declare namespace BottomSheetPanel {
|
|
53
54
|
var displayName: string;
|
|
54
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheetPanel.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheetPanel.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAgBlD,QAAA,MAAM,cAAc;;;;CAIV,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,cAAc,CAAC;AA8F5D,MAAM,MAAM,sBAAsB,GAChC,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEjD,MAAM,MAAM,qBAAqB,GAC7B;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAC,GACjC;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC1C,eAAe,EAAE,MAAM,CAAC;CACzB,GACD;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC;AAEtB,UAAU,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC/D,+CAA+C;IAC/C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5C,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3D,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACzD,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC7D;AA+GD,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,cAAc,EACd,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,GAAG,KAAK,EACT,EAAE,qBAAqB,+
|
|
1
|
+
{"version":3,"file":"BottomSheetPanel.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheetPanel.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAgBlD,QAAA,MAAM,cAAc;;;;CAIV,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,cAAc,CAAC;AA8F5D,MAAM,MAAM,sBAAsB,GAChC,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEjD,MAAM,MAAM,qBAAqB,GAC7B;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAC,GACjC;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IAC1C,eAAe,EAAE,MAAM,CAAC;CACzB,GACD;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,CAAC;AAEtB,UAAU,qBAAsB,SAAQ,SAAS,CAAC,cAAc,CAAC;IAC/D,+CAA+C;IAC/C,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5C,QAAQ,EAAE,SAAS,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3D,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;IACzD,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC7D;AA+GD,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,EAC/B,GAAG,EACH,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,KAAK,EACL,QAAQ,EACR,MAAM,EACN,eAAsB,EACtB,SAAS,EACT,cAAc,EACd,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,GAAG,KAAK,EACT,EAAE,qBAAqB,+BAmLvB;yBAnMe,gBAAgB"}
|
|
@@ -188,6 +188,7 @@ export function BottomSheetPanel({
|
|
|
188
188
|
style,
|
|
189
189
|
tabIndex,
|
|
190
190
|
xstyle,
|
|
191
|
+
canSwipeDismiss = true,
|
|
191
192
|
onDismiss,
|
|
192
193
|
onScrimOpacity,
|
|
193
194
|
onElementChange,
|
|
@@ -233,6 +234,7 @@ export function BottomSheetPanel({
|
|
|
233
234
|
isDragging
|
|
234
235
|
} = useSheetGestures({
|
|
235
236
|
isOpen: isInteractive,
|
|
237
|
+
canDismiss: canSwipeDismiss,
|
|
236
238
|
onDismiss,
|
|
237
239
|
snapHeights: defaultSnapHeights,
|
|
238
240
|
onScrimOpacity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheetSwitcher.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheetSwitcher.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"BottomSheetSwitcher.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheetSwitcher.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAQL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAmIlD,MAAM,WAAW,wBAAyB,SAAQ,SAAS,CAAC,iBAAiB,CAAC;IAC5E,iDAAiD;IACjD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAEnC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAE9D;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B,iEAAiE;IACjE,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAEtD;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,0DAA0D;IAC1D,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,WAAW,EACX,mBAAmB,EACnB,QAAe,EACf,QAAQ,EACR,GAAG,EACH,MAAM,EACN,SAAS,EACT,KAAK,EACL,QAAQ,EACR,OAAO,EACP,SAAS,EACT,GAAG,KAAK,EACT,EAAE,wBAAwB,+BA2Z1B;yBAxae,mBAAmB"}
|
|
@@ -135,12 +135,16 @@ export function BottomSheetSwitcher({
|
|
|
135
135
|
const sheetElementsRef = useRef(new Map());
|
|
136
136
|
const committedActiveSheetRef = useRef(activeSheet);
|
|
137
137
|
const [sheetLabels, setSheetLabels] = useState(() => new Map());
|
|
138
|
+
const [sheetPurposes, setSheetPurposes] = useState(() => new Map());
|
|
138
139
|
const [unmountedSheetIds, setUnmountedSheetIds] = useState(() => new Set());
|
|
139
140
|
const [transition, setTransition] = useState(IDLE_TRANSITION);
|
|
140
141
|
const activeSheetChanged = committedActiveSheetRef.current !== activeSheet;
|
|
141
142
|
const visibleTransition = activeSheetChanged ? transitionForActiveSheetChange(committedActiveSheetRef.current, activeSheet) : transition;
|
|
142
143
|
const isFlowVisible = activeSheet != null && !unmountedSheetIds.has(activeSheet) || visibleTransition.retainedSheet != null && !unmountedSheetIds.has(visibleTransition.retainedSheet);
|
|
143
144
|
const isModal = hasScrim && isFlowVisible;
|
|
145
|
+
const activeSheetPurpose = (activeSheet == null ? null : sheetPurposes.get(activeSheet)) ?? 'info';
|
|
146
|
+
const allowsEscapeDismiss = activeSheetPurpose !== 'required';
|
|
147
|
+
const allowsLightDismiss = activeSheetPurpose === 'info';
|
|
144
148
|
useLayoutEffect(() => {
|
|
145
149
|
const previousActiveSheet = committedActiveSheetRef.current;
|
|
146
150
|
if (previousActiveSheet === activeSheet) {
|
|
@@ -150,12 +154,21 @@ export function BottomSheetSwitcher({
|
|
|
150
154
|
const nextTransition = transitionForActiveSheetChange(previousActiveSheet, activeSheet);
|
|
151
155
|
setTransition(nextTransition.retainedSheet != null && !sheetElementsRef.current.has(nextTransition.retainedSheet) ? IDLE_TRANSITION : nextTransition);
|
|
152
156
|
}, [activeSheet]);
|
|
153
|
-
const
|
|
157
|
+
const dismissOnEscape = useCallback(() => {
|
|
158
|
+
if (allowsEscapeDismiss) {
|
|
159
|
+
onActiveSheetChange(null);
|
|
160
|
+
}
|
|
161
|
+
}, [allowsEscapeDismiss, onActiveSheetChange]);
|
|
162
|
+
const dismissOnLightInteraction = useCallback(() => {
|
|
163
|
+
if (allowsLightDismiss) {
|
|
164
|
+
onActiveSheetChange(null);
|
|
165
|
+
}
|
|
166
|
+
}, [allowsLightDismiss, onActiveSheetChange]);
|
|
154
167
|
const {
|
|
155
168
|
containerRef
|
|
156
169
|
} = useFocusTrap({
|
|
157
170
|
isActive: isModal,
|
|
158
|
-
onEscape:
|
|
171
|
+
onEscape: dismissOnEscape
|
|
159
172
|
});
|
|
160
173
|
useScrollLock(isModal);
|
|
161
174
|
|
|
@@ -247,6 +260,24 @@ export function BottomSheetSwitcher({
|
|
|
247
260
|
return next;
|
|
248
261
|
});
|
|
249
262
|
}, []);
|
|
263
|
+
const registerSheetPurpose = useCallback((sheetId, purpose) => {
|
|
264
|
+
setSheetPurposes(current => {
|
|
265
|
+
if (purpose == null) {
|
|
266
|
+
if (!current.has(sheetId)) {
|
|
267
|
+
return current;
|
|
268
|
+
}
|
|
269
|
+
const next = new Map(current);
|
|
270
|
+
next.delete(sheetId);
|
|
271
|
+
return next;
|
|
272
|
+
}
|
|
273
|
+
if (current.get(sheetId) === purpose) {
|
|
274
|
+
return current;
|
|
275
|
+
}
|
|
276
|
+
const next = new Map(current);
|
|
277
|
+
next.set(sheetId, purpose);
|
|
278
|
+
return next;
|
|
279
|
+
});
|
|
280
|
+
}, []);
|
|
250
281
|
useLayoutEffect(() => {
|
|
251
282
|
setTransition(current => current.retainedSheet != null && unmountedSheetIds.has(current.retainedSheet) ? IDLE_TRANSITION : current);
|
|
252
283
|
}, [unmountedSheetIds]);
|
|
@@ -329,29 +360,30 @@ export function BottomSheetSwitcher({
|
|
|
329
360
|
getSheetAlignmentOffset,
|
|
330
361
|
registerSheetElement,
|
|
331
362
|
registerSheetLabel,
|
|
363
|
+
registerSheetPurpose,
|
|
332
364
|
onSheetEnterStart,
|
|
333
365
|
onSheetTransitionComplete,
|
|
334
366
|
onSheetScrimOpacityChange
|
|
335
|
-
}), [activeSheet, getSheetAlignmentOffset, getSheetPhase, hasScrim, onActiveSheetChange, onSheetEnterStart, onSheetScrimOpacityChange, onSheetTransitionComplete, registerSheetElement, registerSheetLabel]);
|
|
367
|
+
}), [activeSheet, getSheetAlignmentOffset, getSheetPhase, hasScrim, onActiveSheetChange, onSheetEnterStart, onSheetScrimOpacityChange, onSheetTransitionComplete, registerSheetElement, registerSheetLabel, registerSheetPurpose]);
|
|
336
368
|
const activeLabel = (activeSheet == null ? null : sheetLabels.get(activeSheet)) ?? (visibleTransition.retainedSheet == null ? undefined : sheetLabels.get(visibleTransition.retainedSheet));
|
|
337
369
|
const handleCancel = useCallback(event => {
|
|
338
370
|
event.preventDefault();
|
|
339
|
-
|
|
340
|
-
}, [
|
|
371
|
+
dismissOnEscape();
|
|
372
|
+
}, [dismissOnEscape]);
|
|
341
373
|
const handleKeyDown = useCallback(event => {
|
|
342
374
|
// Modal Escape is owned by useFocusTrap so nested traps can win. A
|
|
343
375
|
// non-modal switcher has no outer trap, so retain local dismissal while
|
|
344
376
|
// deferring to an active nested layer and ignoring IME cancellation.
|
|
345
377
|
if (!isModal && event.key === 'Escape' && !isImeKeyEvent(event.nativeEvent) && !hasActiveFocusTrapEscape()) {
|
|
346
378
|
event.preventDefault();
|
|
347
|
-
|
|
379
|
+
dismissOnEscape();
|
|
348
380
|
}
|
|
349
|
-
}, [
|
|
381
|
+
}, [dismissOnEscape, isModal]);
|
|
350
382
|
const handleClick = useCallback(event => {
|
|
351
383
|
if (hasScrim && event.target === event.currentTarget) {
|
|
352
|
-
|
|
384
|
+
dismissOnLightInteraction();
|
|
353
385
|
}
|
|
354
|
-
}, [
|
|
386
|
+
}, [dismissOnLightInteraction, hasScrim]);
|
|
355
387
|
const dialogStyleProps = stylex.props(styles.dialog, isFlowVisible && styles.dialogOpen, hasScrim && styles.scrim, !hasScrim && styles.dialogNonModal, xstyle);
|
|
356
388
|
const dialogPresentationProps = mergeProps(dialogStyleProps, className, style);
|
|
357
389
|
const ariaLabel = props['aria-label'] ?? (props['aria-labelledby'] == null ? activeLabel : undefined);
|
|
@@ -366,6 +398,9 @@ export function BottomSheetSwitcher({
|
|
|
366
398
|
onCancel: composeEventHandlers(onCancel, handleCancel),
|
|
367
399
|
onClick: composeEventHandlers(onClick, handleClick),
|
|
368
400
|
onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown),
|
|
401
|
+
...(activeSheetPurpose === 'required' ? {
|
|
402
|
+
role: 'alertdialog'
|
|
403
|
+
} : undefined),
|
|
369
404
|
children: children
|
|
370
405
|
})
|
|
371
406
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DialogPurpose } from '@astryxdesign/core/Dialog';
|
|
1
2
|
export type BottomSheetSwitcherPhase = 'entering' | 'active' | 'covered' | 'aligning' | 'fading' | 'exiting' | 'hidden';
|
|
2
3
|
export interface BottomSheetSwitcherTransitionEvent {
|
|
3
4
|
sheetId: string;
|
|
@@ -11,6 +12,7 @@ export interface BottomSheetSwitcherContextValue {
|
|
|
11
12
|
getSheetAlignmentOffset: (sheetId: string) => number;
|
|
12
13
|
registerSheetElement: (sheetId: string, element: HTMLElement | null) => void;
|
|
13
14
|
registerSheetLabel: (sheetId: string, label: string | null) => void;
|
|
15
|
+
registerSheetPurpose: (sheetId: string, purpose: DialogPurpose | null) => void;
|
|
14
16
|
onSheetEnterStart: (sheetId: string) => void;
|
|
15
17
|
onSheetTransitionComplete: (event: BottomSheetSwitcherTransitionEvent) => void;
|
|
16
18
|
onSheetScrimOpacityChange: (sheetId: string, opacity: number) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BottomSheetSwitcherContext.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheetSwitcherContext.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BottomSheetSwitcherContext.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/BottomSheetSwitcherContext.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AAE7D,MAAM,MAAM,wBAAwB,GAChC,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,QAAQ,GACR,SAAS,GACT,QAAQ,CAAC;AAEb,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;CACvD;AAED,MAAM,WAAW,+BAA+B;IAC9C,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACtD,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,wBAAwB,CAAC;IAC7D,uBAAuB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACrD,oBAAoB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7E,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACpE,oBAAoB,EAAE,CACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,GAAG,IAAI,KAC1B,IAAI,CAAC;IACV,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,yBAAyB,EAAE,CACzB,KAAK,EAAE,kCAAkC,KACtC,IAAI,CAAC;IACV,yBAAyB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACvE;AAED,eAAO,MAAM,0BAA0B,iEACsB,CAAC"}
|
|
@@ -22,10 +22,16 @@
|
|
|
22
22
|
* - /packages/lab/src/BottomSheet/useSheetGestures.doc.mjs
|
|
23
23
|
* - /packages/lab/src/BottomSheet/useSheetGestures.test.ts
|
|
24
24
|
*/
|
|
25
|
-
import { type CSSProperties, type PointerEvent as ReactPointerEvent } from 'react';
|
|
25
|
+
import { type CSSProperties, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent } from 'react';
|
|
26
26
|
export interface UseSheetGesturesOptions {
|
|
27
27
|
/** Whether the owning sheet is open. Drag state resets when it closes. */
|
|
28
28
|
isOpen: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a downward swipe may dismiss the sheet. When false, a gesture
|
|
31
|
+
* past the dismiss threshold settles at the shortest detent instead.
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
canDismiss?: boolean;
|
|
29
35
|
/** Called on a swipe-to-close (fast downward flick, or drag past the floor). */
|
|
30
36
|
onDismiss: () => void;
|
|
31
37
|
/**
|
|
@@ -53,6 +59,8 @@ export interface SheetContentProps {
|
|
|
53
59
|
}
|
|
54
60
|
export interface SheetHandleProps {
|
|
55
61
|
style: CSSProperties;
|
|
62
|
+
onContextMenu: (event: ReactMouseEvent) => void;
|
|
63
|
+
onLostPointerCapture: (event: ReactPointerEvent) => void;
|
|
56
64
|
onPointerDown: (event: ReactPointerEvent) => void;
|
|
57
65
|
onPointerMove: (event: ReactPointerEvent) => void;
|
|
58
66
|
onPointerUp: (event: ReactPointerEvent) => void;
|
|
@@ -60,6 +68,8 @@ export interface SheetHandleProps {
|
|
|
60
68
|
}
|
|
61
69
|
export interface SheetBodyProps {
|
|
62
70
|
ref: (node: HTMLElement | null) => void;
|
|
71
|
+
onContextMenu: (event: ReactMouseEvent) => void;
|
|
72
|
+
onLostPointerCapture: (event: ReactPointerEvent) => void;
|
|
63
73
|
onPointerDown: (event: ReactPointerEvent) => void;
|
|
64
74
|
onPointerMove: (event: ReactPointerEvent) => void;
|
|
65
75
|
onPointerUp: (event: ReactPointerEvent) => void;
|
|
@@ -108,5 +118,5 @@ export interface UseSheetGesturesResult {
|
|
|
108
118
|
* </div>
|
|
109
119
|
* ```
|
|
110
120
|
*/
|
|
111
|
-
export declare function useSheetGestures({ isOpen, onDismiss, snapHeights, onSnap, onScrimOpacity, }: UseSheetGesturesOptions): UseSheetGesturesResult;
|
|
121
|
+
export declare function useSheetGestures({ isOpen, canDismiss, onDismiss, snapHeights, onSnap, onScrimOpacity, }: UseSheetGesturesOptions): UseSheetGesturesResult;
|
|
112
122
|
//# sourceMappingURL=useSheetGestures.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSheetGestures.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/useSheetGestures.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,YAAY,IAAI,iBAAiB,EACvC,MAAM,OAAO,CAAC;AAgEf,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB,gFAAgF;IAChF,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IAC7B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,iBAAiB,CAAC;IAChC,gEAAgE;IAChE,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,SAAS,EAAE,cAAc,CAAC;IAC1B,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,SAAS,EACT,WAAW,EACX,MAAM,EACN,cAAc,GACf,EAAE,uBAAuB,GAAG,sBAAsB,
|
|
1
|
+
{"version":3,"file":"useSheetGestures.d.ts","sourceRoot":"","sources":["../../src/BottomSheet/useSheetGestures.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,UAAU,IAAI,eAAe,EAClC,KAAK,YAAY,IAAI,iBAAiB,EACvC,MAAM,OAAO,CAAC;AAgEf,MAAM,WAAW,uBAAuB;IACtC,0EAA0E;IAC1E,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gFAAgF;IAChF,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,EAAE,CAAC;IAC7B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,CAAC;IACrB,aAAa,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACzD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,0EAA0E;IAC1E,YAAY,EAAE,iBAAiB,CAAC;IAChC,gEAAgE;IAChE,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;;;OAIG;IACH,SAAS,EAAE,cAAc,CAAC;IAC1B,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,UAAU,EAAE,OAAO,CAAC;CACrB;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,UAAiB,EACjB,SAAS,EACT,WAAW,EACX,MAAM,EACN,cAAc,GACf,EAAE,uBAAuB,GAAG,sBAAsB,CAolBlD"}
|
|
@@ -110,6 +110,7 @@ function prefersReducedMotion() {
|
|
|
110
110
|
*/
|
|
111
111
|
export function useSheetGestures({
|
|
112
112
|
isOpen,
|
|
113
|
+
canDismiss = true,
|
|
113
114
|
onDismiss,
|
|
114
115
|
snapHeights,
|
|
115
116
|
onSnap,
|
|
@@ -119,11 +120,13 @@ export function useSheetGestures({
|
|
|
119
120
|
const [settledOffset, setSettledOffset] = useState(0);
|
|
120
121
|
const [isDragging, setIsDragging] = useState(false);
|
|
121
122
|
const onDismissRef = useRef(onDismiss);
|
|
123
|
+
const canDismissRef = useRef(canDismiss);
|
|
122
124
|
const onSnapRef = useRef(onSnap);
|
|
123
125
|
const onScrimOpacityRef = useRef(onScrimOpacity);
|
|
124
126
|
const snapHeightsRef = useRef(snapHeights);
|
|
125
127
|
useEffect(() => {
|
|
126
128
|
onDismissRef.current = onDismiss;
|
|
129
|
+
canDismissRef.current = canDismiss;
|
|
127
130
|
onSnapRef.current = onSnap;
|
|
128
131
|
onScrimOpacityRef.current = onScrimOpacity;
|
|
129
132
|
snapHeightsRef.current = snapHeights;
|
|
@@ -185,14 +188,65 @@ export function useSheetGestures({
|
|
|
185
188
|
const detentOffsets = useCallback(height => {
|
|
186
189
|
return computeDetentOffsets(height, snapHeightsRef.current?.() ?? []);
|
|
187
190
|
}, []);
|
|
191
|
+
const cancelDrag = useCallback(target => {
|
|
192
|
+
const state = dragStateRef.current;
|
|
193
|
+
if (state == null) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Clear first: releasePointerCapture() may synchronously dispatch
|
|
198
|
+
// lostpointercapture, which must observe that this drag is already done.
|
|
199
|
+
dragStateRef.current = null;
|
|
200
|
+
if (target?.hasPointerCapture?.(state.pointerId)) {
|
|
201
|
+
target.releasePointerCapture(state.pointerId);
|
|
202
|
+
}
|
|
203
|
+
setDragOffset(state.baseOffset);
|
|
204
|
+
setIsDragging(false);
|
|
205
|
+
|
|
206
|
+
// An interrupted drag returns to its previous resting detent. Restore
|
|
207
|
+
// the matching scrim opacity as well so the modal shell cannot remain
|
|
208
|
+
// dimmed with its sheet translated out of view.
|
|
209
|
+
const offsets = detentOffsets(state.height);
|
|
210
|
+
const maxOffset = offsets[offsets.length - 1];
|
|
211
|
+
const shortestDetentHeight = state.height - maxOffset;
|
|
212
|
+
const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
|
|
213
|
+
onScrimOpacityRef.current?.(scrimOpacityForOffset(state.baseOffset, offsets, dismissOffset));
|
|
214
|
+
}, [detentOffsets]);
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
const handleWindowBlur = () => cancelDrag();
|
|
217
|
+
const handleVisibilityChange = () => {
|
|
218
|
+
if (document.visibilityState === 'hidden') {
|
|
219
|
+
cancelDrag();
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
window.addEventListener('blur', handleWindowBlur);
|
|
223
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
224
|
+
return () => {
|
|
225
|
+
window.removeEventListener('blur', handleWindowBlur);
|
|
226
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
227
|
+
};
|
|
228
|
+
}, [cancelDrag]);
|
|
188
229
|
const settleFromDrag = useCallback((offset, velocity, height, dir, travel, baseOffset) => {
|
|
189
230
|
const offsets = detentOffsets(height);
|
|
190
231
|
const maxOffset = offsets[offsets.length - 1];
|
|
191
232
|
const shortestDetentHeight = height - maxOffset;
|
|
192
233
|
const speed = Math.abs(velocity);
|
|
193
234
|
const isFlick = speed > FLICK_VELOCITY && travel > FLICK_MIN_DISTANCE;
|
|
235
|
+
const settleAt = target => {
|
|
236
|
+
setSettledOffset(target);
|
|
237
|
+
onSnapRef.current?.(height - target);
|
|
238
|
+
const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
|
|
239
|
+
onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, dismissOffset));
|
|
240
|
+
if (target !== baseOffset) {
|
|
241
|
+
hapticTick();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
194
244
|
if (dir > 0 && isFlick) {
|
|
195
|
-
|
|
245
|
+
if (canDismissRef.current) {
|
|
246
|
+
onDismissRef.current();
|
|
247
|
+
} else {
|
|
248
|
+
settleAt(maxOffset);
|
|
249
|
+
}
|
|
196
250
|
return;
|
|
197
251
|
}
|
|
198
252
|
// Fast upward flick = expand to the tallest detent (the sheet's full
|
|
@@ -205,21 +259,17 @@ export function useSheetGestures({
|
|
|
205
259
|
return;
|
|
206
260
|
}
|
|
207
261
|
if (offset > maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO) {
|
|
208
|
-
|
|
262
|
+
if (canDismissRef.current) {
|
|
263
|
+
onDismissRef.current();
|
|
264
|
+
} else {
|
|
265
|
+
settleAt(maxOffset);
|
|
266
|
+
}
|
|
209
267
|
return;
|
|
210
268
|
}
|
|
211
269
|
// Settle to the nearest detent in the drag direction (never back past
|
|
212
270
|
// the starting detent), de-duped and direction-clamped by the util.
|
|
213
271
|
const target = resolveSettleOffset(offset, offsets, dir, baseOffset);
|
|
214
|
-
|
|
215
|
-
onSnapRef.current?.(height - target);
|
|
216
|
-
// Keep the scrim in sync with the resting detent (faint floor at the peek).
|
|
217
|
-
const dismissOffset = maxOffset + shortestDetentHeight * DISMISS_OVERSHOOT_RATIO;
|
|
218
|
-
onScrimOpacityRef.current?.(scrimOpacityForOffset(target, offsets, dismissOffset));
|
|
219
|
-
// Haptic "tick" when landing on a different detent (where supported).
|
|
220
|
-
if (target !== baseOffset) {
|
|
221
|
-
hapticTick();
|
|
222
|
-
}
|
|
272
|
+
settleAt(target);
|
|
223
273
|
}, [detentOffsets]);
|
|
224
274
|
const beginDrag = useCallback((event, sheetHeight, startCoord) => {
|
|
225
275
|
const target = event.currentTarget;
|
|
@@ -243,12 +293,27 @@ export function useSheetGestures({
|
|
|
243
293
|
setIsDragging(true);
|
|
244
294
|
}, [settledOffset]);
|
|
245
295
|
const handlePointerDown = useCallback(event => {
|
|
296
|
+
if (event.button !== 0 || !event.isPrimary) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
246
299
|
// The handle has no native focus action. Prevent pointer-down from
|
|
247
300
|
// moving focus off a form control on a tap; once the pointer actually
|
|
248
301
|
// moves, BottomSheet dismisses the keyboard as sheet travel begins.
|
|
249
302
|
event.preventDefault();
|
|
250
303
|
beginDrag(event, measureHeight());
|
|
251
304
|
}, [beginDrag, measureHeight]);
|
|
305
|
+
const handleContextMenu = useCallback(event => {
|
|
306
|
+
if (dragStateRef.current == null) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
cancelDrag(event.currentTarget);
|
|
311
|
+
}, [cancelDrag]);
|
|
312
|
+
const handleLostPointerCapture = useCallback(event => {
|
|
313
|
+
if (dragStateRef.current?.pointerId === event.pointerId) {
|
|
314
|
+
cancelDrag();
|
|
315
|
+
}
|
|
316
|
+
}, [cancelDrag]);
|
|
252
317
|
const handlePointerMove = useCallback(event => {
|
|
253
318
|
const state = dragStateRef.current;
|
|
254
319
|
if (!state || state.pointerId !== event.pointerId) {
|
|
@@ -291,11 +356,11 @@ export function useSheetGestures({
|
|
|
291
356
|
return;
|
|
292
357
|
}
|
|
293
358
|
const target = event.currentTarget;
|
|
294
|
-
target.releasePointerCapture?.(event.pointerId);
|
|
295
359
|
const delta = event.clientY - state.startCoord;
|
|
296
360
|
const offset = Math.max(0, state.baseOffset + delta);
|
|
297
361
|
const dir = delta === 0 ? 0 : delta > 0 ? 1 : -1;
|
|
298
362
|
dragStateRef.current = null;
|
|
363
|
+
target.releasePointerCapture?.(event.pointerId);
|
|
299
364
|
setIsDragging(false);
|
|
300
365
|
settleFromDrag(offset, state.velocity, state.height || 1, dir, Math.abs(delta), state.baseOffset);
|
|
301
366
|
}, [settleFromDrag]);
|
|
@@ -305,6 +370,9 @@ export function useSheetGestures({
|
|
|
305
370
|
// native pan starts.
|
|
306
371
|
const armedBodyRef = useRef(null);
|
|
307
372
|
const handleBodyPointerDown = useCallback(event => {
|
|
373
|
+
if (event.button !== 0 || !event.isPrimary) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
308
376
|
const scroller = event.currentTarget;
|
|
309
377
|
if (scroller.scrollTop > 0) {
|
|
310
378
|
armedBodyRef.current = null;
|
|
@@ -351,11 +419,13 @@ export function useSheetGestures({
|
|
|
351
419
|
const touchDragRef = useRef(null);
|
|
352
420
|
const previousTouchHandlersRef = useRef(null);
|
|
353
421
|
const beginDragRef = useRef(beginDrag);
|
|
422
|
+
const cancelDragRef = useRef(cancelDrag);
|
|
354
423
|
const pointerMoveRef = useRef(handlePointerMove);
|
|
355
424
|
const endDragRef = useRef(endDrag);
|
|
356
425
|
const measureHeightRef = useRef(measureHeight);
|
|
357
426
|
useEffect(() => {
|
|
358
427
|
beginDragRef.current = beginDrag;
|
|
428
|
+
cancelDragRef.current = cancelDrag;
|
|
359
429
|
pointerMoveRef.current = handlePointerMove;
|
|
360
430
|
endDragRef.current = endDrag;
|
|
361
431
|
measureHeightRef.current = measureHeight;
|
|
@@ -431,11 +501,18 @@ export function useSheetGestures({
|
|
|
431
501
|
};
|
|
432
502
|
const onTouchEnd = event => {
|
|
433
503
|
touchDragRef.current = null;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
504
|
+
const pointerId = dragStateRef.current?.pointerId;
|
|
505
|
+
if (pointerId == null) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const t = [...event.changedTouches].find(touch => touch.identifier === pointerId);
|
|
509
|
+
const target = event.currentTarget;
|
|
510
|
+
if (t) {
|
|
511
|
+
endDragRef.current(asPointer(t, target));
|
|
512
|
+
} else if (event.touches.length === 0) {
|
|
513
|
+
// Some interrupted multi-touch sequences omit the active touch from
|
|
514
|
+
// changedTouches. If no fingers remain, the drag cannot finish later.
|
|
515
|
+
cancelDragRef.current(target);
|
|
439
516
|
}
|
|
440
517
|
};
|
|
441
518
|
const prev = bodyNodeRef.current;
|
|
@@ -486,18 +563,22 @@ export function useSheetGestures({
|
|
|
486
563
|
touchAction: 'none',
|
|
487
564
|
cursor: 'grab'
|
|
488
565
|
},
|
|
566
|
+
onContextMenu: handleContextMenu,
|
|
567
|
+
onLostPointerCapture: handleLostPointerCapture,
|
|
489
568
|
onPointerDown: handlePointerDown,
|
|
490
569
|
onPointerMove: handlePointerMove,
|
|
491
570
|
onPointerUp: endDrag,
|
|
492
571
|
onPointerCancel: endDrag
|
|
493
|
-
}), [handlePointerDown, handlePointerMove
|
|
572
|
+
}), [endDrag, handleContextMenu, handleLostPointerCapture, handlePointerDown, handlePointerMove]);
|
|
494
573
|
const bodyProps = useMemo(() => ({
|
|
495
574
|
ref: bodyRef,
|
|
575
|
+
onContextMenu: handleContextMenu,
|
|
576
|
+
onLostPointerCapture: handleLostPointerCapture,
|
|
496
577
|
onPointerDown: handleBodyPointerDown,
|
|
497
578
|
onPointerMove: handleBodyPointerMove,
|
|
498
579
|
onPointerUp: handleBodyEnd,
|
|
499
580
|
onPointerCancel: handleBodyEnd
|
|
500
|
-
}), [bodyRef, handleBodyPointerDown, handleBodyPointerMove,
|
|
581
|
+
}), [bodyRef, handleBodyEnd, handleBodyPointerDown, handleBodyPointerMove, handleContextMenu, handleLostPointerCapture]);
|
|
501
582
|
return {
|
|
502
583
|
sheetRef,
|
|
503
584
|
contentProps,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/lab",
|
|
3
|
-
"version": "0.4.2-canary.
|
|
3
|
+
"version": "0.4.2-canary.bb07062",
|
|
4
4
|
"description": "Experimental Astryx components — published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
|
|
5
5
|
"author": "Meta Open Source",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@stylexjs/stylex": ">=0.10.0",
|
|
48
|
-
"@astryxdesign/core": "0.4.2-canary.
|
|
48
|
+
"@astryxdesign/core": "0.4.2-canary.bb07062",
|
|
49
49
|
"react": ">=19.0.0",
|
|
50
50
|
"react-dom": ">=19.0.0"
|
|
51
51
|
},
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"d3-shape": "^3.2.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@astryxdesign/cli": "0.4.2-canary.
|
|
58
|
+
"@astryxdesign/cli": "0.4.2-canary.bb07062",
|
|
59
59
|
"@types/d3-array": "^3.2.2",
|
|
60
60
|
"@types/d3-scale": "^4.0.9",
|
|
61
61
|
"@types/d3-shape": "^3.1.8",
|