@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.
@@ -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;AAkBlD,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;CAC9C;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;AAwUxD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,+BA6BlD;yBA7Be,WAAW"}
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 close = useCallback(() => onOpenChange(false), [onOpenChange]);
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
- close();
146
- }, [close]);
155
+ dismissOnEscape();
156
+ }, [dismissOnEscape]);
147
157
  const handleKeyDown = useCallback(event => {
148
158
  if (event.key === 'Escape') {
149
159
  event.preventDefault();
150
- close();
160
+ dismissOnEscape();
151
161
  }
152
- }, [close]);
162
+ }, [dismissOnEscape]);
153
163
  const handleClick = useCallback(event => {
154
164
  if (hasScrim && event.target === event.currentTarget) {
155
- close();
165
+ dismissOnLightInteraction();
156
166
  }
157
- }, [close, hasScrim]);
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: close,
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 close = useCallback(() => {
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: close,
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,+BAkLvB;yBAjMe,gBAAgB"}
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;AAkIlD,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,+BAmX1B;yBAhYe,mBAAmB"}
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 close = useCallback(() => onActiveSheetChange(null), [onActiveSheetChange]);
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: close
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
- close();
340
- }, [close]);
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
- close();
379
+ dismissOnEscape();
348
380
  }
349
- }, [close, isModal]);
381
+ }, [dismissOnEscape, isModal]);
350
382
  const handleClick = useCallback(event => {
351
383
  if (hasScrim && event.target === event.currentTarget) {
352
- close();
384
+ dismissOnLightInteraction();
353
385
  }
354
- }, [close, hasScrim]);
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":"AAaA,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,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"}
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,CAqelD"}
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
- onDismissRef.current();
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
- onDismissRef.current();
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
- setSettledOffset(target);
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
- if (dragStateRef.current) {
435
- const t = event.changedTouches[0];
436
- if (t) {
437
- endDragRef.current(asPointer(t, event.currentTarget));
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, endDrag]);
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, handleBodyEnd]);
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.b2057d1",
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.b2057d1",
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.b2057d1",
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",