@astryxdesign/core 0.7.3 → 0.7.4-canary.b25f95e
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 +18 -6
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +268 -58
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +41 -12
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +294 -165
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +51 -1
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -1
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +7 -11
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +46 -7
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +6 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +12 -0
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +6 -1
- package/dist/DateInput/NativeDateField.d.ts.map +1 -1
- package/dist/DateInput/NativeDateField.js +6 -1
- package/dist/DateInput/TouchDateField.d.ts.map +1 -1
- package/dist/DateInput/TouchDateField.js +9 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +6 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +6 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +6 -1
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +6 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +6 -1
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +9 -1
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +7 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +9 -1
- package/dist/astryx.css +2 -0
- package/dist/hooks/useBusyAnnouncement.d.ts +22 -0
- package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -0
- package/dist/hooks/useBusyAnnouncement.js +87 -0
- package/dist/hooks/useMenuPress.d.ts +2 -2
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +5 -1
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
- package/locales/en.json +6 -2
- package/locales/pseudo.json +3 -0
- package/package.json +4 -4
- package/src/BottomSheet/BottomSheet.doc.mjs +2 -2
- package/src/BottomSheet/BottomSheet.spec.md +2 -5
- package/src/BottomSheet/BottomSheet.tsx +260 -47
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -2
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +11 -10
- package/src/BottomSheet/BottomSheetSwitcher.spec.md +28 -35
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +830 -48
- package/src/BottomSheet/BottomSheetSwitcher.tsx +396 -215
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +57 -2
- package/src/Carousel/Carousel.test.tsx +8 -12
- package/src/Carousel/Carousel.tsx +6 -13
- package/src/Chat/ChatComposerInput.test.tsx +167 -1
- package/src/Chat/useTriggerMenu.tsx +55 -6
- package/src/ComplexSelector/ComplexSelector.tsx +5 -1
- package/src/ContextMenu/ContextMenu.test.tsx +34 -0
- package/src/ContextMenu/ContextMenu.tsx +13 -1
- package/src/DateInput/DateInput.tsx +5 -1
- package/src/DateInput/DateInputTouch.test.tsx +45 -0
- package/src/DateInput/NativeDateField.test.tsx +10 -2
- package/src/DateInput/NativeDateField.tsx +5 -1
- package/src/DateInput/TouchDateField.tsx +14 -2
- package/src/DateRangeInput/DateRangeInput.tsx +5 -1
- package/src/MultiSelector/MultiSelector.test.tsx +18 -4
- package/src/MultiSelector/MultiSelector.tsx +5 -1
- package/src/ProgressBar/ProgressBar.test.tsx +4 -21
- package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +114 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.known-failures.ts +12 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.renders.tsx +38 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.states.ts +123 -0
- package/src/ProgressBar/__tests__/ProgressBar.a11y.test.tsx +107 -0
- package/src/Selector/Selector.test.tsx +22 -5
- package/src/Selector/Selector.tsx +5 -1
- package/src/TextArea/TextArea.test.tsx +9 -1
- package/src/TextArea/TextArea.tsx +5 -1
- package/src/TextInput/TextInput.tsx +5 -1
- package/src/Tokenizer/Tokenizer.test.tsx +17 -12
- package/src/Tokenizer/Tokenizer.tsx +10 -1
- package/src/Typeahead/BaseTypeahead.tsx +14 -1
- package/src/Typeahead/Typeahead.test.tsx +32 -41
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/BusyOwnerAnnouncement.test.tsx +369 -0
- package/src/hooks/__tests__/MenuPressShadowRoot.a11y.chromium.spec.ts +50 -0
- package/src/hooks/useBusyAnnouncement.ts +100 -0
- package/src/hooks/useMenuPress.test.tsx +27 -1
- package/src/hooks/useMenuPress.ts +12 -3
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file BottomSheetSwitcher.tsx
|
|
7
|
-
* @input Uses React context, StyleX,
|
|
7
|
+
* @input Uses React context, StyleX, shared layer dismissal, BottomSheetSwitcherContext
|
|
8
8
|
* @output Exports BottomSheetSwitcher and BottomSheetSwitcherProps
|
|
9
9
|
* @position Core switcher for mutually exclusive BottomSheet flows
|
|
10
10
|
*
|
|
@@ -15,14 +15,37 @@
|
|
|
15
15
|
* down at the same time until their top edges align; otherwise it stays
|
|
16
16
|
* stationary. The previous sheet fades only after both motions complete.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
* scrim flow
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* Every child sheet owns a native `<dialog>`, exactly like a standalone
|
|
19
|
+
* sheet: a scrim flow presents each sheet with showModal(), a no-scrim flow
|
|
20
|
+
* with show(). The browser then does what a modal dialog does -- the page is
|
|
21
|
+
* inert, focus stays in the top-layer dialog, Escape and the platform close
|
|
22
|
+
* arrive as `cancel`. The switcher is the coordinator: it decides which
|
|
23
|
+
* sheet dialog is presented, retained, or hidden, keeps one scrim painted
|
|
24
|
+
* across a handoff (the retained sheet's backdrop until it fades, then the
|
|
25
|
+
* new sheet's, swapped without a fade), holds the flow's one page scroll
|
|
26
|
+
* lock (the shared useScrollLock, as Dialog uses, acquired once while the
|
|
27
|
+
* flow is presented and released after its exit, never per step) and the
|
|
28
|
+
* one layer-dismissal registration, and returns focus to the flow's opener
|
|
29
|
+
* when the flow ends, which no single dialog can know.
|
|
30
|
+
*
|
|
31
|
+
* The switcher's one element is a flow wrapper. In a scrim flow it has no box
|
|
32
|
+
* (`display: contents`): the dialogs sit in the top layer. In a no-scrim flow
|
|
33
|
+
* it is the isolated surface that orders the sheet dialogs locally, the way
|
|
34
|
+
* the shared shell once did, so no sheet needs a page-level value of its own.
|
|
35
|
+
*
|
|
36
|
+
* Its `ref` resolves to the host dialog: the one presenting the active sheet,
|
|
37
|
+
* the exiting sheet's through its exit, and the last host's (or the first
|
|
38
|
+
* mounted sheet's) while the flow is closed, so the ref is never null while a
|
|
39
|
+
* sheet is mounted. `id` and `data-testid` render on the host dialog only:
|
|
40
|
+
* a flow that mounts open carries them on the active sheet's dialog in server
|
|
41
|
+
* markup too; a flow that mounts closed carries them from the first commit's
|
|
42
|
+
* synchronous re-render on, and its server markup carries them on no element.
|
|
43
|
+
* Every other dialog prop and handler, `onCancel` included, applies to every
|
|
44
|
+
* sheet dialog; the browser raises `cancel` on the dialog it presents.
|
|
22
45
|
*
|
|
23
46
|
* SYNC: When modified, update these files to stay in sync:
|
|
24
47
|
* - /packages/core/src/BottomSheet/BottomSheet.tsx
|
|
25
|
-
* - /packages/core/src/BottomSheet/
|
|
48
|
+
* - /packages/core/src/BottomSheet/BottomSheetSwitcherContext.ts
|
|
26
49
|
* - /packages/core/src/BottomSheet/BottomSheetSwitcher.doc.mjs
|
|
27
50
|
* - /packages/core/src/BottomSheet/BottomSheetSwitcher.spec.md
|
|
28
51
|
* - /packages/core/src/BottomSheet/BottomSheetSwitcher.test.tsx
|
|
@@ -45,78 +68,42 @@ import {
|
|
|
45
68
|
import * as stylex from '@stylexjs/stylex';
|
|
46
69
|
import type {BaseProps} from '../BaseProps';
|
|
47
70
|
import type {DialogPurpose} from '../Dialog';
|
|
48
|
-
import {colorVars, durationVars, easeVars} from '../theme/tokens.stylex';
|
|
49
71
|
import {useScrollLock} from '../hooks';
|
|
50
|
-
import {
|
|
51
|
-
useFocusTrap,
|
|
52
|
-
useFocusTrapEscapeCompatibilitySignal,
|
|
53
|
-
} from '../hooks/useFocusTrap';
|
|
54
|
-
import {LayerDepthProvider} from '../Layer/LayerDepthContext';
|
|
72
|
+
import {useFocusTrapEscapeCompatibilitySignal} from '../hooks/useFocusTrap';
|
|
55
73
|
import {dispatchLayerEscapeKeyDown} from '../Layer/layerStack';
|
|
56
74
|
import {useLayerDismissal} from '../Layer/useLayerDismissal';
|
|
57
|
-
import {composeEventHandlers
|
|
58
|
-
import {
|
|
75
|
+
import {composeEventHandlers} from '../utils';
|
|
76
|
+
import {mergeRefs} from '../utils/mergeRefs';
|
|
59
77
|
import {
|
|
60
78
|
BottomSheetSwitcherContext,
|
|
61
79
|
type BottomSheetSwitcherContextValue,
|
|
80
|
+
type BottomSheetSwitcherDialogProps,
|
|
62
81
|
type BottomSheetSwitcherPhase,
|
|
63
82
|
type BottomSheetSwitcherTransitionEvent,
|
|
64
83
|
} from './BottomSheetSwitcherContext';
|
|
65
84
|
|
|
66
|
-
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
67
85
|
const styles = stylex.create({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
width: '100dvw',
|
|
72
|
-
height: '100dvh',
|
|
73
|
-
maxWidth: 'none',
|
|
74
|
-
maxHeight: 'none',
|
|
75
|
-
margin: 0,
|
|
76
|
-
padding: 0,
|
|
77
|
-
borderWidth: 0,
|
|
78
|
-
borderStyle: 'none',
|
|
79
|
-
backgroundColor: 'transparent',
|
|
80
|
-
overflow: 'visible',
|
|
81
|
-
display: 'none',
|
|
82
|
-
outline: 'none',
|
|
86
|
+
// A scrim flow's dialogs live in the top layer; the wrapper has no box.
|
|
87
|
+
flow: {
|
|
88
|
+
display: 'contents',
|
|
83
89
|
},
|
|
84
|
-
|
|
90
|
+
// show() leaves a no-scrim flow in the normal rendering tree, where DOM
|
|
91
|
+
// order would decide which sheet dialog paints in front during a handoff.
|
|
92
|
+
// The wrapper is then the flow's isolated surface: the sheet dialogs order
|
|
93
|
+
// themselves locally inside it (`spec:AST-027` FR2), and the wrapper itself
|
|
94
|
+
// keeps the one overlay value the shared shell carried before, which
|
|
95
|
+
// `spec:AST-027` records as the non-modal migration gap. It passes pointer
|
|
96
|
+
// input through to the live page; each sheet panel opts back in. Consumers
|
|
97
|
+
// of this non-modal mode must still avoid transformed or clipping ancestors.
|
|
98
|
+
flowNonModal: {
|
|
85
99
|
display: 'block',
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// pass pointer input through to the live page; each sheet panel opts back in.
|
|
89
|
-
// width/height 100% avoids overflowing a transformed containing block, but
|
|
90
|
-
// consumers of this non-modal mode must still avoid clipping ancestors.
|
|
91
|
-
dialogNonModal: {
|
|
92
|
-
pointerEvents: 'none',
|
|
93
|
-
zIndex: 1000,
|
|
100
|
+
position: 'fixed',
|
|
101
|
+
inset: 0,
|
|
94
102
|
width: '100%',
|
|
95
103
|
height: '100%',
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
backgroundColor: colorVars['--color-overlay'],
|
|
100
|
-
opacity: {
|
|
101
|
-
default: 'var(--_sheet-scrim-opacity, 1)',
|
|
102
|
-
'@starting-style': 0,
|
|
103
|
-
},
|
|
104
|
-
transitionProperty: 'opacity, display',
|
|
105
|
-
transitionDuration: durationVars['--duration-medium'],
|
|
106
|
-
transitionTimingFunction: easeVars['--ease-standard'],
|
|
107
|
-
transitionBehavior: 'allow-discrete',
|
|
108
|
-
'@media (prefers-reduced-motion: reduce)': {
|
|
109
|
-
transitionDuration: '0.01s',
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
// The flow's dim leaves with its last panel. Same rule, same reasoning, as
|
|
114
|
-
// a standalone sheet's -- see `scrimClosing` in BottomSheet.tsx. A handoff
|
|
115
|
-
// between two sheets is not a close, and keeps the entrance curve.
|
|
116
|
-
scrimClosing: {
|
|
117
|
-
'::backdrop': {
|
|
118
|
-
transitionTimingFunction: 'linear',
|
|
119
|
-
},
|
|
104
|
+
pointerEvents: 'none',
|
|
105
|
+
isolation: 'isolate',
|
|
106
|
+
zIndex: 1000,
|
|
120
107
|
},
|
|
121
108
|
});
|
|
122
109
|
|
|
@@ -179,11 +166,54 @@ function alignmentOffsetForElements(
|
|
|
179
166
|
return Math.max(0, enteringTop - retainedElement.getBoundingClientRect().top);
|
|
180
167
|
}
|
|
181
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Keeps one object identity while its own enumerable entries are unchanged, so
|
|
171
|
+
* the consumer's rest props can sit in a memoized context value without
|
|
172
|
+
* refreshing every sheet host on each switcher render.
|
|
173
|
+
*/
|
|
174
|
+
function useShallowStable<T extends object>(value: T): T {
|
|
175
|
+
const stableRef = useRef(value);
|
|
176
|
+
const previous = stableRef.current;
|
|
177
|
+
const previousKeys = Object.keys(previous) as (keyof T)[];
|
|
178
|
+
const nextKeys = Object.keys(value) as (keyof T)[];
|
|
179
|
+
const isSame =
|
|
180
|
+
previousKeys.length === nextKeys.length &&
|
|
181
|
+
nextKeys.every(key => Object.is(previous[key], value[key]));
|
|
182
|
+
if (!isSame) {
|
|
183
|
+
stableRef.current = value;
|
|
184
|
+
}
|
|
185
|
+
return stableRef.current;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Hands the flow's scrim to `dialog` without a fade. The backdrop's opacity
|
|
190
|
+
* transition would otherwise ease from the transparent value the entering
|
|
191
|
+
* dialog was presented with, dipping the page's dim for one motion token
|
|
192
|
+
* exactly when the previous dialog's backdrop disappears. Even a near-zero
|
|
193
|
+
* duration paints its first frame at the start value, so the swap disables
|
|
194
|
+
* the transition outright, flushes style so the new value is committed under
|
|
195
|
+
* that rule, then restores the token for later gesture and exit fades.
|
|
196
|
+
*/
|
|
197
|
+
function takeOverScrim(dialog: HTMLDialogElement, opacity: number): void {
|
|
198
|
+
dialog.style.setProperty('--_sheet-scrim-duration', '0s');
|
|
199
|
+
dialog.style.setProperty('--_sheet-scrim-opacity', String(opacity));
|
|
200
|
+
if (typeof getComputedStyle === 'function') {
|
|
201
|
+
void getComputedStyle(dialog, '::backdrop').opacity;
|
|
202
|
+
}
|
|
203
|
+
dialog.style.removeProperty('--_sheet-scrim-duration');
|
|
204
|
+
}
|
|
205
|
+
|
|
182
206
|
export interface BottomSheetSwitcherProps extends BaseProps<HTMLDialogElement> {
|
|
183
|
-
/**
|
|
207
|
+
/**
|
|
208
|
+
* Ref resolved to the native dialog hosting the flow: the active sheet's
|
|
209
|
+
* dialog, which changes on a handoff; the exiting sheet's through its exit;
|
|
210
|
+
* and, while the flow is closed, the dialog that hosted it last (or the
|
|
211
|
+
* first mounted sheet's), so it is never null while a sheet is mounted,
|
|
212
|
+
* from the first commit on.
|
|
213
|
+
*/
|
|
184
214
|
ref?: React.Ref<HTMLDialogElement>;
|
|
185
215
|
|
|
186
|
-
/** Called when the
|
|
216
|
+
/** Called when the active sheet's native dialog receives a cancel event. */
|
|
187
217
|
onCancel?: (event: SyntheticEvent<HTMLDialogElement>) => void;
|
|
188
218
|
|
|
189
219
|
/**
|
|
@@ -198,7 +228,7 @@ export interface BottomSheetSwitcherProps extends BaseProps<HTMLDialogElement> {
|
|
|
198
228
|
onActiveSheetChange: (sheetId: string | null) => void;
|
|
199
229
|
|
|
200
230
|
/**
|
|
201
|
-
* Whether
|
|
231
|
+
* Whether each sheet dialog opens modally with a native ::backdrop.
|
|
202
232
|
* Disable for a viewport-anchored, non-modal flow over an interactive page.
|
|
203
233
|
* @default true
|
|
204
234
|
*/
|
|
@@ -209,8 +239,8 @@ export interface BottomSheetSwitcherProps extends BaseProps<HTMLDialogElement> {
|
|
|
209
239
|
}
|
|
210
240
|
|
|
211
241
|
/**
|
|
212
|
-
* Coordinates a set of BottomSheets so zero or one is active at a time
|
|
213
|
-
*
|
|
242
|
+
* Coordinates a set of BottomSheets so zero or one is active at a time, each
|
|
243
|
+
* in its own native dialog. During a handoff the previous panel stays visible
|
|
214
244
|
* and inert beneath the entering panel, then fades after motion completes.
|
|
215
245
|
*
|
|
216
246
|
* @example
|
|
@@ -229,22 +259,40 @@ export function BottomSheetSwitcher({
|
|
|
229
259
|
hasScrim = true,
|
|
230
260
|
children,
|
|
231
261
|
ref,
|
|
232
|
-
xstyle,
|
|
233
|
-
className,
|
|
234
|
-
style,
|
|
235
262
|
onCancel,
|
|
236
263
|
onClick,
|
|
237
264
|
onKeyDown,
|
|
238
|
-
|
|
265
|
+
className,
|
|
266
|
+
style,
|
|
267
|
+
xstyle,
|
|
268
|
+
...restDialogProps
|
|
239
269
|
}: BottomSheetSwitcherProps) {
|
|
240
|
-
|
|
241
|
-
const
|
|
270
|
+
// Every sheet host applies the switcher's dialog surface to its own dialog.
|
|
271
|
+
const dialogProps = useShallowStable<BottomSheetSwitcherDialogProps>({
|
|
272
|
+
...restDialogProps,
|
|
273
|
+
className,
|
|
274
|
+
style,
|
|
275
|
+
xstyle,
|
|
276
|
+
});
|
|
277
|
+
const hostDialogRef = useRef<HTMLDialogElement | null>(null);
|
|
278
|
+
const sheetDialogsRef = useRef(new Map<string, HTMLDialogElement>());
|
|
279
|
+
// Sheet ids in the order their dialogs mounted (tree order on first mount).
|
|
280
|
+
// State, not a ref: the resting host below is rendered -- it carries the
|
|
281
|
+
// consumer's `id` -- so a sheet mounting or leaving must re-render.
|
|
282
|
+
const [sheetOrder, setSheetOrder] = useState<ReadonlyArray<string>>([]);
|
|
283
|
+
// The sheet that hosted the flow last, kept while the flow is closed so the
|
|
284
|
+
// ref and `id` stay on the dialog the person last used. State adjusted
|
|
285
|
+
// during render (not a ref read during render): it changes exactly when
|
|
286
|
+
// the presented host does, and the next render reads it.
|
|
287
|
+
const [lastHostSheet, setLastHostSheet] = useState<string | null>(null);
|
|
288
|
+
const presentationOrderRef = useRef(new Map<string, number>());
|
|
289
|
+
const presentationCounterRef = useRef(0);
|
|
290
|
+
const modalFlowRef = useRef(false);
|
|
242
291
|
const triggerRef = useRef<HTMLElement | null>(null);
|
|
243
292
|
const sheetElementsRef = useRef(new Map<string, HTMLElement>());
|
|
244
293
|
const committedActiveSheetRef = useRef(activeSheet);
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
);
|
|
294
|
+
const scrimOpacityRef = useRef(1);
|
|
295
|
+
const scrimOwnerRef = useRef<string | null>(null);
|
|
248
296
|
const [sheetPurposes, setSheetPurposes] = useState<
|
|
249
297
|
ReadonlyMap<string, DialogPurpose>
|
|
250
298
|
>(() => new Map());
|
|
@@ -261,11 +309,42 @@ export function BottomSheetSwitcher({
|
|
|
261
309
|
activeSheet,
|
|
262
310
|
)
|
|
263
311
|
: transition;
|
|
312
|
+
const presentedActiveSheet =
|
|
313
|
+
activeSheet != null && !unmountedSheetIds.has(activeSheet)
|
|
314
|
+
? activeSheet
|
|
315
|
+
: null;
|
|
316
|
+
const presentedRetainedSheet =
|
|
317
|
+
visibleTransition.retainedSheet != null &&
|
|
318
|
+
!unmountedSheetIds.has(visibleTransition.retainedSheet)
|
|
319
|
+
? visibleTransition.retainedSheet
|
|
320
|
+
: null;
|
|
264
321
|
const isFlowVisible =
|
|
265
|
-
|
|
266
|
-
(visibleTransition.retainedSheet != null &&
|
|
267
|
-
!unmountedSheetIds.has(visibleTransition.retainedSheet));
|
|
322
|
+
presentedActiveSheet != null || presentedRetainedSheet != null;
|
|
268
323
|
const isModal = hasScrim && isFlowVisible;
|
|
324
|
+
// The dialog that hosts the flow for its ref, its single-instance
|
|
325
|
+
// attributes, and its dismissal registration: the active sheet's, the
|
|
326
|
+
// retained sheet's while the flow closes through its exit, and while the
|
|
327
|
+
// flow is closed the dialog that hosted it last (or the first mounted
|
|
328
|
+
// sheet's before any open), so the ref resolves to a closed dialog element
|
|
329
|
+
// rather than to null, as the shared dialog did. Which sheet mounts first
|
|
330
|
+
// is known only once its dialog registers during the first commit: the
|
|
331
|
+
// ref is attached to it in that same commit (below), and the rendered
|
|
332
|
+
// `id` follows in the synchronous re-render that commit schedules, before
|
|
333
|
+
// the first paint. Server markup therefore carries the `id` on no element.
|
|
334
|
+
const presentedHostSheet = presentedActiveSheet ?? presentedRetainedSheet;
|
|
335
|
+
if (presentedHostSheet != null && presentedHostSheet !== lastHostSheet) {
|
|
336
|
+
setLastHostSheet(presentedHostSheet);
|
|
337
|
+
}
|
|
338
|
+
const restingHostSheet =
|
|
339
|
+
lastHostSheet != null && sheetOrder.includes(lastHostSheet)
|
|
340
|
+
? lastHostSheet
|
|
341
|
+
: (sheetOrder[0] ?? null);
|
|
342
|
+
const hostSheet = presentedHostSheet ?? restingHostSheet;
|
|
343
|
+
// The dialog whose backdrop paints the flow's one scrim. A retained sheet
|
|
344
|
+
// keeps it through a handoff so the entering dialog, presented above it in
|
|
345
|
+
// the top layer, does not dim the previous panel beneath its own backdrop.
|
|
346
|
+
const scrimOwnerSheet = presentedRetainedSheet ?? presentedActiveSheet;
|
|
347
|
+
const isFlowClosing = isFlowVisible && activeSheet == null;
|
|
269
348
|
const activeSheetPurpose =
|
|
270
349
|
(activeSheet == null ? null : sheetPurposes.get(activeSheet)) ?? 'info';
|
|
271
350
|
const allowsEscapeDismiss = activeSheetPurpose !== 'required';
|
|
@@ -299,59 +378,102 @@ export function BottomSheetSwitcher({
|
|
|
299
378
|
onActiveSheetChange(null);
|
|
300
379
|
}
|
|
301
380
|
}, [allowsLightDismiss, onActiveSheetChange]);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
// the released compatibility shim reported it as active. Keep that signal
|
|
307
|
-
// without registering the switcher twice in the one shared stack.
|
|
381
|
+
// Focus containment is the native modal dialog's: showModal() makes the
|
|
382
|
+
// page inert, so Tab cannot leave the active sheet. The released read-only
|
|
383
|
+
// `hasActiveFocusTrapEscape()` signal still reports a modal flow as a trap,
|
|
384
|
+
// as it did when a JavaScript trap backed it.
|
|
308
385
|
useFocusTrapEscapeCompatibilitySignal(isModal);
|
|
386
|
+
// One lock for the whole modal flow, as on a shared dialog: `isModal` stays
|
|
387
|
+
// true through a handoff (two dialogs open) and through the exit motion, so
|
|
388
|
+
// the shared counter is taken once and given back once.
|
|
389
|
+
useScrollLock(isModal);
|
|
309
390
|
const {shouldDismissOnCloseRequest} = useLayerDismissal({
|
|
310
391
|
isActive: isFlowVisible,
|
|
311
392
|
escapeBehavior: allowsEscapeDismiss ? 'close' : 'block',
|
|
312
393
|
onDismiss: dismissOnEscape,
|
|
313
|
-
getContainer: () =>
|
|
314
|
-
isPresent: () =>
|
|
394
|
+
getContainer: () => hostDialogRef.current,
|
|
395
|
+
isPresent: () => hostDialogRef.current?.open ?? false,
|
|
315
396
|
});
|
|
316
|
-
useScrollLock(isModal);
|
|
317
397
|
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
321
|
-
//
|
|
398
|
+
// Point the consumer ref and the dismissal registration at the host dialog.
|
|
399
|
+
// Sheet hosts register their dialogs during commit, so by the time this
|
|
400
|
+
// layout effect runs the map already names the dialog for `hostSheet` --
|
|
401
|
+
// and, in the first commit of a flow that mounts closed, the first
|
|
402
|
+
// registered dialog before `sheetOrder` has caught up, so a parent's own
|
|
403
|
+
// mount effects already find the ref resolved. The ref is attached the way
|
|
404
|
+
// React attaches one: a callback ref's returned cleanup runs on detach, and
|
|
405
|
+
// only a ref without one receives null. Attachment follows the resolved
|
|
406
|
+
// element, not the render that resolved it: the re-render that catches
|
|
407
|
+
// `sheetOrder` up resolves the same dialog and leaves the ref alone.
|
|
408
|
+
const attachedRef = useRef<{
|
|
409
|
+
ref: React.Ref<HTMLDialogElement> | undefined;
|
|
410
|
+
host: HTMLDialogElement;
|
|
411
|
+
detach: (() => void) | undefined;
|
|
412
|
+
} | null>(null);
|
|
322
413
|
useLayoutEffect(() => {
|
|
323
|
-
const
|
|
324
|
-
|
|
414
|
+
const host =
|
|
415
|
+
hostSheet == null
|
|
416
|
+
? (sheetDialogsRef.current.values().next().value ?? null)
|
|
417
|
+
: (sheetDialogsRef.current.get(hostSheet) ?? null);
|
|
418
|
+
hostDialogRef.current = host;
|
|
419
|
+
const attached = attachedRef.current;
|
|
420
|
+
if (attached?.host === host && attached.ref === ref) {
|
|
325
421
|
return;
|
|
326
422
|
}
|
|
423
|
+
attached?.detach?.();
|
|
424
|
+
if (host == null) {
|
|
425
|
+
attachedRef.current = null;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const detach = mergeRefs(ref)(host);
|
|
429
|
+
attachedRef.current = {
|
|
430
|
+
ref,
|
|
431
|
+
host,
|
|
432
|
+
detach: typeof detach === 'function' ? detach : undefined,
|
|
433
|
+
};
|
|
434
|
+
}, [hostSheet, ref, sheetOrder]);
|
|
435
|
+
useLayoutEffect(
|
|
436
|
+
() => () => {
|
|
437
|
+
hostDialogRef.current = null;
|
|
438
|
+
attachedRef.current?.detach?.();
|
|
439
|
+
attachedRef.current = null;
|
|
440
|
+
},
|
|
441
|
+
[],
|
|
442
|
+
);
|
|
327
443
|
|
|
444
|
+
// Focus return is native per dialog: each showModal() records the focused
|
|
445
|
+
// element and close() focuses it again. That serves a one-sheet flow, but
|
|
446
|
+
// the second sheet of a flow records a control inside the first, which is
|
|
447
|
+
// hidden by the time the flow ends, so the browser's return lands nowhere.
|
|
448
|
+
// The flow therefore remembers its own opener -- the page element focused
|
|
449
|
+
// before the first modal present -- and focuses it after the last dialog
|
|
450
|
+
// closes. Sheet hosts ask before presenting modally, so the capture sees
|
|
451
|
+
// the page element; hosts present in their own layout effects, which run
|
|
452
|
+
// before the one below, so the restore follows the last dialog's close.
|
|
453
|
+
const onBeforeSheetPresent = useCallback((sheetId: string) => {
|
|
454
|
+
if (triggerRef.current == null) {
|
|
455
|
+
triggerRef.current = document.activeElement as HTMLElement | null;
|
|
456
|
+
}
|
|
457
|
+
modalFlowRef.current = true;
|
|
458
|
+
presentationCounterRef.current += 1;
|
|
459
|
+
presentationOrderRef.current.set(sheetId, presentationCounterRef.current);
|
|
460
|
+
}, []);
|
|
461
|
+
useLayoutEffect(() => {
|
|
328
462
|
if (isFlowVisible) {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
dialog.close();
|
|
463
|
+
if (!hasScrim) {
|
|
464
|
+
modalFlowRef.current = false;
|
|
332
465
|
}
|
|
333
|
-
if (!dialog.open) {
|
|
334
|
-
if (hasScrim && triggerRef.current == null) {
|
|
335
|
-
triggerRef.current = document.activeElement as HTMLElement | null;
|
|
336
|
-
}
|
|
337
|
-
if (hasScrim) {
|
|
338
|
-
dialog.showModal();
|
|
339
|
-
} else {
|
|
340
|
-
dialog.show();
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
dialogModeRef.current = nextMode;
|
|
344
466
|
return;
|
|
345
467
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
triggerRef.current?.focus();
|
|
468
|
+
if (modalFlowRef.current) {
|
|
469
|
+
// Like the browser's own return on close(), without scrolling: the
|
|
470
|
+
// page kept its scroll offset under the lock, and an opener scrolled
|
|
471
|
+
// out of view must not drag the page back to itself.
|
|
472
|
+
triggerRef.current?.focus({preventScroll: true});
|
|
352
473
|
}
|
|
353
474
|
triggerRef.current = null;
|
|
354
|
-
|
|
475
|
+
modalFlowRef.current = false;
|
|
476
|
+
presentationOrderRef.current.clear();
|
|
355
477
|
}, [hasScrim, isFlowVisible]);
|
|
356
478
|
|
|
357
479
|
const getSheetPhase = useCallback(
|
|
@@ -377,6 +499,11 @@ export function BottomSheetSwitcher({
|
|
|
377
499
|
[visibleTransition],
|
|
378
500
|
);
|
|
379
501
|
|
|
502
|
+
const isScrimOwner = useCallback(
|
|
503
|
+
(sheetId: string) => sheetId === scrimOwnerSheet,
|
|
504
|
+
[scrimOwnerSheet],
|
|
505
|
+
);
|
|
506
|
+
|
|
380
507
|
const registerSheetElement = useCallback(
|
|
381
508
|
(sheetId: string, element: HTMLElement | null) => {
|
|
382
509
|
if (element == null) {
|
|
@@ -407,28 +534,44 @@ export function BottomSheetSwitcher({
|
|
|
407
534
|
[],
|
|
408
535
|
);
|
|
409
536
|
|
|
410
|
-
const
|
|
411
|
-
(sheetId: string,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return next;
|
|
427
|
-
});
|
|
537
|
+
const registerSheetDialog = useCallback(
|
|
538
|
+
(sheetId: string, dialog: HTMLDialogElement | null) => {
|
|
539
|
+
if (dialog == null) {
|
|
540
|
+
sheetDialogsRef.current.delete(sheetId);
|
|
541
|
+
presentationOrderRef.current.delete(sheetId);
|
|
542
|
+
setSheetOrder(current =>
|
|
543
|
+
current.includes(sheetId)
|
|
544
|
+
? current.filter(id => id !== sheetId)
|
|
545
|
+
: current,
|
|
546
|
+
);
|
|
547
|
+
} else {
|
|
548
|
+
sheetDialogsRef.current.set(sheetId, dialog);
|
|
549
|
+
setSheetOrder(current =>
|
|
550
|
+
current.includes(sheetId) ? current : [...current, sheetId],
|
|
551
|
+
);
|
|
552
|
+
}
|
|
428
553
|
},
|
|
429
554
|
[],
|
|
430
555
|
);
|
|
431
556
|
|
|
557
|
+
const isCoveredInTopLayer = useCallback((sheetId: string) => {
|
|
558
|
+
const order = presentationOrderRef.current;
|
|
559
|
+
const own = order.get(sheetId);
|
|
560
|
+
if (own == null) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
for (const [otherId, stamp] of order) {
|
|
564
|
+
if (
|
|
565
|
+
otherId !== sheetId &&
|
|
566
|
+
stamp > own &&
|
|
567
|
+
sheetDialogsRef.current.get(otherId)?.open
|
|
568
|
+
) {
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return false;
|
|
573
|
+
}, []);
|
|
574
|
+
|
|
432
575
|
const registerSheetPurpose = useCallback(
|
|
433
576
|
(sheetId: string, purpose: DialogPurpose | null) => {
|
|
434
577
|
setSheetPurposes(current => {
|
|
@@ -533,67 +676,64 @@ export function BottomSheetSwitcher({
|
|
|
533
676
|
[],
|
|
534
677
|
);
|
|
535
678
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
679
|
+
// One scrim for the flow. Only the owning dialog's backdrop carries the
|
|
680
|
+
// current opacity; every other presented dialog keeps its backdrop clear.
|
|
681
|
+
// When ownership moves — the retained dialog has faded and closes, or a
|
|
682
|
+
// rapid replacement hides it — the new owner takes the value without a
|
|
683
|
+
// fade, in the same layout pass that closes the old one.
|
|
684
|
+
const applyScrimOpacity = useCallback(() => {
|
|
685
|
+
const owner =
|
|
686
|
+
scrimOwnerSheet == null
|
|
687
|
+
? null
|
|
688
|
+
: (sheetDialogsRef.current.get(scrimOwnerSheet) ?? null);
|
|
689
|
+
// A fresh open keeps the backdrop's @starting-style fade; only a hand
|
|
690
|
+
// from one presented dialog to another swaps without one.
|
|
691
|
+
const ownerChanged =
|
|
692
|
+
scrimOwnerRef.current != null &&
|
|
693
|
+
scrimOwnerRef.current !== scrimOwnerSheet;
|
|
694
|
+
scrimOwnerRef.current = scrimOwnerSheet;
|
|
695
|
+
for (const [sheetId, dialog] of sheetDialogsRef.current) {
|
|
696
|
+
if (dialog === owner) {
|
|
697
|
+
if (ownerChanged && dialog.open) {
|
|
698
|
+
takeOverScrim(dialog, scrimOpacityRef.current);
|
|
699
|
+
} else {
|
|
700
|
+
dialog.style.setProperty(
|
|
701
|
+
'--_sheet-scrim-opacity',
|
|
702
|
+
String(scrimOpacityRef.current),
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
} else if (sheetId !== scrimOwnerSheet) {
|
|
706
|
+
dialog.style.setProperty('--_sheet-scrim-opacity', '0');
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}, [scrimOwnerSheet]);
|
|
542
710
|
const onSheetScrimOpacityChange = useCallback(
|
|
543
711
|
(sheetId: string, opacity: number) => {
|
|
544
712
|
// A pointer captured by the outgoing sheet can keep delivering gesture
|
|
545
713
|
// events after a handoff. Only the currently committed sheet owns the
|
|
546
|
-
//
|
|
714
|
+
// flow's scrim value, so stale gesture updates must not reach it.
|
|
547
715
|
if (sheetId !== committedActiveSheetRef.current) {
|
|
548
716
|
return;
|
|
549
717
|
}
|
|
550
|
-
|
|
718
|
+
scrimOpacityRef.current = opacity;
|
|
719
|
+
applyScrimOpacity();
|
|
551
720
|
},
|
|
552
|
-
[
|
|
721
|
+
[applyScrimOpacity],
|
|
553
722
|
);
|
|
554
723
|
|
|
724
|
+
// A committed selection resets the flow's scrim value -- 1 while a sheet
|
|
725
|
+
// is active, 0 for the exit fade -- and writes it to the owning backdrop in
|
|
726
|
+
// the same effect; a gesture then adjusts the value through
|
|
727
|
+
// `onSheetScrimOpacityChange` until the next selection.
|
|
555
728
|
useLayoutEffect(() => {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
getSheetPhase,
|
|
565
|
-
getSheetAlignmentOffset,
|
|
566
|
-
registerSheetElement,
|
|
567
|
-
registerSheetLabel,
|
|
568
|
-
registerSheetPurpose,
|
|
569
|
-
onSheetEnterStart,
|
|
570
|
-
onSheetTransitionComplete,
|
|
571
|
-
onSheetScrimOpacityChange,
|
|
572
|
-
}),
|
|
573
|
-
[
|
|
574
|
-
activeSheet,
|
|
575
|
-
getSheetAlignmentOffset,
|
|
576
|
-
getSheetPhase,
|
|
577
|
-
hasScrim,
|
|
578
|
-
onActiveSheetChange,
|
|
579
|
-
onSheetEnterStart,
|
|
580
|
-
onSheetScrimOpacityChange,
|
|
581
|
-
onSheetTransitionComplete,
|
|
582
|
-
registerSheetElement,
|
|
583
|
-
registerSheetLabel,
|
|
584
|
-
registerSheetPurpose,
|
|
585
|
-
],
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
const activeLabel =
|
|
589
|
-
(activeSheet == null ? null : sheetLabels.get(activeSheet)) ??
|
|
590
|
-
(visibleTransition.retainedSheet == null
|
|
591
|
-
? undefined
|
|
592
|
-
: sheetLabels.get(visibleTransition.retainedSheet));
|
|
593
|
-
|
|
594
|
-
// A `cancel` that bubbles up from a step is not a close request for the
|
|
595
|
-
// flow: a file input fires one when its picker is dismissed, and a step
|
|
596
|
-
// renders no dialog of its own, so it lands here. It is left alone.
|
|
729
|
+
scrimOpacityRef.current = activeSheet == null ? 0 : 1;
|
|
730
|
+
applyScrimOpacity();
|
|
731
|
+
}, [applyScrimOpacity, activeSheet, hasScrim, isFlowVisible]);
|
|
732
|
+
|
|
733
|
+
// A `cancel` that bubbles up from inside a step is not a close request for
|
|
734
|
+
// the flow: a file input fires one when its picker is dismissed, and it
|
|
735
|
+
// reaches the step's dialog. Only a `cancel` fired at a sheet dialog itself
|
|
736
|
+
// dismisses; the bubbled one is left alone.
|
|
597
737
|
const handleCancel = useCallback(
|
|
598
738
|
(event: SyntheticEvent<HTMLDialogElement>) => {
|
|
599
739
|
if (event.target !== event.currentTarget) {
|
|
@@ -618,47 +758,88 @@ export function BottomSheetSwitcher({
|
|
|
618
758
|
);
|
|
619
759
|
const handleClick = useCallback(
|
|
620
760
|
(event: ReactMouseEvent<HTMLDialogElement>) => {
|
|
621
|
-
|
|
761
|
+
// A `::backdrop` click targets its dialog element. Only the active
|
|
762
|
+
// sheet's backdrop is the flow's scrim; a retained dialog is inert and
|
|
763
|
+
// a hidden one closed, so neither can be clicked, but be explicit.
|
|
764
|
+
if (
|
|
765
|
+
hasScrim &&
|
|
766
|
+
event.target === event.currentTarget &&
|
|
767
|
+
event.currentTarget === hostDialogRef.current
|
|
768
|
+
) {
|
|
622
769
|
dismissOnLightInteraction();
|
|
623
770
|
}
|
|
624
771
|
},
|
|
625
772
|
[dismissOnLightInteraction, hasScrim],
|
|
626
773
|
);
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
isFlowVisible && styles.dialogOpen,
|
|
631
|
-
hasScrim && styles.scrim,
|
|
632
|
-
hasScrim && isFlowVisible && activeSheet == null && styles.scrimClosing,
|
|
633
|
-
!hasScrim && styles.dialogNonModal,
|
|
634
|
-
xstyle,
|
|
774
|
+
const onDialogCancel = useMemo(
|
|
775
|
+
() => composeEventHandlers(onCancel, handleCancel),
|
|
776
|
+
[handleCancel, onCancel],
|
|
635
777
|
);
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
778
|
+
const onDialogClick = useMemo(
|
|
779
|
+
() => composeEventHandlers(onClick, handleClick),
|
|
780
|
+
[handleClick, onClick],
|
|
781
|
+
);
|
|
782
|
+
const onDialogKeyDown = useMemo(
|
|
783
|
+
() => composeEventHandlers(onKeyDown, handleKeyDown),
|
|
784
|
+
[handleKeyDown, onKeyDown],
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
const contextValue = useMemo<BottomSheetSwitcherContextValue>(
|
|
788
|
+
() => ({
|
|
789
|
+
activeSheet,
|
|
790
|
+
hasScrim,
|
|
791
|
+
onActiveSheetChange,
|
|
792
|
+
getSheetPhase,
|
|
793
|
+
getSheetAlignmentOffset,
|
|
794
|
+
hostSheet,
|
|
795
|
+
isScrimOwner,
|
|
796
|
+
isFlowClosing,
|
|
797
|
+
registerSheetElement,
|
|
798
|
+
registerSheetDialog,
|
|
799
|
+
registerSheetPurpose,
|
|
800
|
+
onBeforeSheetPresent,
|
|
801
|
+
isCoveredInTopLayer,
|
|
802
|
+
onSheetEnterStart,
|
|
803
|
+
onSheetTransitionComplete,
|
|
804
|
+
onSheetScrimOpacityChange,
|
|
805
|
+
dialogProps,
|
|
806
|
+
onDialogCancel,
|
|
807
|
+
onDialogClick,
|
|
808
|
+
onDialogKeyDown,
|
|
809
|
+
}),
|
|
810
|
+
[
|
|
811
|
+
activeSheet,
|
|
812
|
+
dialogProps,
|
|
813
|
+
getSheetAlignmentOffset,
|
|
814
|
+
getSheetPhase,
|
|
815
|
+
hasScrim,
|
|
816
|
+
hostSheet,
|
|
817
|
+
isCoveredInTopLayer,
|
|
818
|
+
isFlowClosing,
|
|
819
|
+
isScrimOwner,
|
|
820
|
+
onActiveSheetChange,
|
|
821
|
+
onBeforeSheetPresent,
|
|
822
|
+
onDialogCancel,
|
|
823
|
+
onDialogClick,
|
|
824
|
+
onDialogKeyDown,
|
|
825
|
+
onSheetEnterStart,
|
|
826
|
+
onSheetScrimOpacityChange,
|
|
827
|
+
onSheetTransitionComplete,
|
|
828
|
+
registerSheetDialog,
|
|
829
|
+
registerSheetElement,
|
|
830
|
+
registerSheetPurpose,
|
|
831
|
+
],
|
|
640
832
|
);
|
|
641
|
-
const ariaLabel =
|
|
642
|
-
props['aria-label'] ??
|
|
643
|
-
(props['aria-labelledby'] == null ? activeLabel : undefined);
|
|
644
833
|
|
|
645
834
|
return (
|
|
646
835
|
<BottomSheetSwitcherContext value={contextValue}>
|
|
647
|
-
<
|
|
648
|
-
{...props
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
onClick={composeEventHandlers(onClick, handleClick)}
|
|
655
|
-
onKeyDown={composeEventHandlers(onKeyDown, handleKeyDown)}
|
|
656
|
-
{...(activeSheetPurpose === 'required'
|
|
657
|
-
? {role: 'alertdialog'}
|
|
658
|
-
: undefined)}>
|
|
659
|
-
<LayerDepthProvider>{children}</LayerDepthProvider>
|
|
660
|
-
<BottomSheetEdgeTint />
|
|
661
|
-
</dialog>
|
|
836
|
+
<div
|
|
837
|
+
{...stylex.props(
|
|
838
|
+
styles.flow,
|
|
839
|
+
!hasScrim && isFlowVisible && styles.flowNonModal,
|
|
840
|
+
)}>
|
|
841
|
+
{children}
|
|
842
|
+
</div>
|
|
662
843
|
</BottomSheetSwitcherContext>
|
|
663
844
|
);
|
|
664
845
|
}
|