@jobber/components 7.8.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/Autocomplete/index.mjs +1 -1
  2. package/dist/Button/buttonRenderAdapter.d.ts +304 -0
  3. package/dist/Card/index.cjs +7 -4
  4. package/dist/Card/index.mjs +7 -4
  5. package/dist/Countdown/index.cjs +1 -1
  6. package/dist/Countdown/index.mjs +1 -1
  7. package/dist/Countdown-cjs.js +1 -1
  8. package/dist/Countdown-es.js +1 -1
  9. package/dist/DataDump/index.cjs +7 -4
  10. package/dist/DataDump/index.mjs +7 -4
  11. package/dist/DrawerRoot-cjs.js +2634 -2142
  12. package/dist/DrawerRoot-es.js +2523 -2065
  13. package/dist/InputAvatar/index.cjs +1 -1
  14. package/dist/InputAvatar/index.mjs +1 -1
  15. package/dist/InputFile/index.cjs +1 -1
  16. package/dist/InputFile/index.mjs +1 -1
  17. package/dist/InputFile-cjs.js +1 -1
  18. package/dist/InputFile-es.js +1 -1
  19. package/dist/InputNumber/index.cjs +2441 -186
  20. package/dist/InputNumber/index.mjs +2361 -106
  21. package/dist/Menu/Menu.Legacy.d.ts +3 -0
  22. package/dist/Menu/Menu.d.ts +66 -24
  23. package/dist/Menu/Menu.types.d.ts +8 -108
  24. package/dist/Menu/MenuBottomSheet.d.ts +29 -0
  25. package/dist/Menu/MenuDropdown.d.ts +29 -0
  26. package/dist/Menu/index.cjs +7 -4
  27. package/dist/Menu/index.mjs +7 -4
  28. package/dist/Menu/menuComposableShared.d.ts +27 -0
  29. package/dist/Menu/menuComposableTypes.d.ts +99 -0
  30. package/dist/Menu-cjs.js +828 -8419
  31. package/dist/Menu-es.js +823 -8414
  32. package/dist/MenuSubmenuTrigger-cjs.js +5283 -0
  33. package/dist/MenuSubmenuTrigger-es.js +5224 -0
  34. package/dist/Modal/index.mjs +1 -1
  35. package/dist/Page/index.cjs +6 -3
  36. package/dist/Page/index.mjs +6 -3
  37. package/dist/Page-cjs.js +1 -1
  38. package/dist/Page-es.js +1 -1
  39. package/dist/RecurringSelect/index.cjs +0 -1
  40. package/dist/RecurringSelect/index.mjs +0 -1
  41. package/dist/docs/Menu/Menu.md +7 -62
  42. package/dist/floating-ui.react-dom-es.js +1 -1
  43. package/dist/floating-ui.react-es.js +2 -2
  44. package/dist/index-cjs.js +1161 -166
  45. package/dist/index-es.js +1160 -166
  46. package/dist/index.cjs +7 -3
  47. package/dist/index.esm-es.js +1 -1
  48. package/dist/index.mjs +7 -3
  49. package/dist/primitives/BottomSheet/index.cjs +0 -1
  50. package/dist/primitives/BottomSheet/index.mjs +0 -1
  51. package/dist/primitives/index.cjs +0 -1
  52. package/dist/primitives/index.mjs +0 -1
  53. package/dist/styles.css +540 -31
  54. package/dist/unstyledPrimitives/index.cjs +6662 -8039
  55. package/dist/unstyledPrimitives/index.d.ts +2 -1
  56. package/dist/unstyledPrimitives/index.mjs +6733 -8111
  57. package/dist/useRenderElement-cjs.js +212 -212
  58. package/dist/useRenderElement-es.js +213 -213
  59. package/dist/utils/meta/meta.json +9 -0
  60. package/package.json +2 -2
  61. package/dist/Text-cjs2.js +0 -2484
  62. package/dist/Text-es2.js +0 -2417
  63. package/dist/index-cjs2.js +0 -1189
  64. package/dist/index-es2.js +0 -1186
@@ -1,90 +1,22 @@
1
1
  import * as React from 'react';
2
2
  import React__default from 'react';
3
- import { a as useRefWithInit, E as EMPTY_OBJECT, N as NOOP, u as useRenderElement, b as useMergedRefs, f as formatErrorMessage, i as isReactVersionAtLeast } from './useRenderElement-es.js';
3
+ import { f as formatErrorMessage, u as useRenderElement, b as useRefWithInit, E as EMPTY_OBJECT, c as useMergedRefs, i as isReactVersionAtLeast, N as NOOP } from './useRenderElement-es.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
- import { i as isShadowRoot, a as isHTMLElement, l as getComputedStyle$1, f as floor, t as tabbable, I as getWindow, N as isOverflowElement, d as getNodeName, c as isNode, e as isTabbable, h as focusable, m as isWebKit$1, b as isElement, j as isLastTraversableNode, k as getParentNode } from './index.esm-es.js';
5
+ import { i as isShadowRoot, b as isElement, a as isHTMLElement, k as getComputedStyle$1, f as floor, t as tabbable, g as getNodeName, c as isNode, I as getWindow, d as isTabbable, e as focusable, m as isWebKit$1, h as isLastTraversableNode, j as getParentNode, N as isOverflowElement } from './index.esm-es.js';
6
6
  import * as ReactDOM from 'react-dom';
7
- import { s as shimExports } from './index-es.js';
8
7
 
9
- function useControlled({
10
- controlled,
11
- default: defaultProp,
12
- name,
13
- state = 'value'
14
- }) {
15
- // isControlled is ignored in the hook dependency lists as it should never change.
16
- const {
17
- current: isControlled
18
- } = React.useRef(controlled !== undefined);
19
- const [valueState, setValue] = React.useState(defaultProp);
20
- const value = isControlled ? controlled : valueState;
21
- if (process.env.NODE_ENV !== 'production') {
22
- React.useEffect(() => {
23
- if (isControlled !== (controlled !== undefined)) {
24
- console.error([`Base UI: A component is changing the ${isControlled ? '' : 'un'}controlled ${state} state of ${name} to be ${isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${name} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n'));
25
- }
26
- }, [state, name, controlled]);
27
- const {
28
- current: defaultValue
29
- } = React.useRef(defaultProp);
30
- React.useEffect(() => {
31
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
32
- if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
33
- console.error([`Base UI: A component is changing the default ${state} state of an uncontrolled ${name} after being initialized. ` + `To suppress this warning opt to use a controlled ${name}.`].join('\n'));
34
- }
35
- }, [JSON.stringify(defaultProp)]);
36
- }
37
- const setValueIfUncontrolled = React.useCallback(newValue => {
38
- if (!isControlled) {
39
- setValue(newValue);
40
- }
41
- }, []);
42
- return [value, setValueIfUncontrolled];
43
- }
44
-
45
- // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
46
- const useInsertionEffect = React[`useInsertionEffect${Math.random().toFixed(1)}`.slice(0, -3)];
47
- const useSafeInsertionEffect =
48
- // React 17 doesn't have useInsertionEffect.
49
- useInsertionEffect &&
50
- // Preact replaces useInsertionEffect with useLayoutEffect and fires too late.
51
- useInsertionEffect !== React.useLayoutEffect ? useInsertionEffect : fn => fn();
52
- /**
53
- * Stabilizes the function passed so it's always the same between renders.
54
- *
55
- * The function becomes non-reactive to any values it captures.
56
- * It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.
57
- *
58
- * The function must only be called inside effects and event handlers, never during render (which throws an error).
59
- *
60
- * This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.
61
- */
62
- function useStableCallback(callback) {
63
- const stable = useRefWithInit(createStableCallback).current;
64
- stable.next = callback;
65
- useSafeInsertionEffect(stable.effect);
66
- return stable.trampoline;
67
- }
68
- function createStableCallback() {
69
- const stable = {
70
- next: undefined,
71
- callback: assertNotCalled,
72
- trampoline: (...args) => stable.callback?.(...args),
73
- effect: () => {
74
- stable.callback = stable.next;
75
- }
76
- };
77
- return stable;
78
- }
79
- function assertNotCalled() {
80
- if (process.env.NODE_ENV !== 'production') {
81
- throw /* minify-error-disabled */new Error('Base UI: Cannot call an event handler while rendering.');
8
+ const DialogRootContext = /*#__PURE__*/React.createContext(undefined);
9
+ if (process.env.NODE_ENV !== "production") DialogRootContext.displayName = "DialogRootContext";
10
+ function useDialogRootContext(optional) {
11
+ const dialogRootContext = React.useContext(DialogRootContext);
12
+ if (optional === false && dialogRootContext === undefined) {
13
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.' : formatErrorMessage(27));
82
14
  }
15
+ return dialogRootContext;
83
16
  }
84
17
 
85
- const noop = () => {};
86
- const useIsoLayoutEffect = typeof document !== 'undefined' ? React.useLayoutEffect : noop;
87
-
18
+ const TYPEAHEAD_RESET_MS = 500;
19
+ const PATIENT_CLICK_THRESHOLD = 500;
88
20
  const DISABLED_TRANSITIONS_STYLE = {
89
21
  style: {
90
22
  transition: 'none'
@@ -102,6 +34,14 @@ const DROPDOWN_COLLISION_AVOIDANCE = {
102
34
  fallbackAxisSide: 'none'
103
35
  };
104
36
 
37
+ /**
38
+ * Used by regular popups that usually aren't scrollable and are allowed to
39
+ * freely flip to any axis of placement.
40
+ */
41
+ const POPUP_COLLISION_AVOIDANCE = {
42
+ fallbackAxisSide: 'end'
43
+ };
44
+
105
45
  /**
106
46
  * Special visually hidden styles for the aria-owns owner element to ensure owned element
107
47
  * accessibility in iOS/Safari/VoiceControl.
@@ -115,618 +55,691 @@ const ownerVisuallyHidden = {
115
55
  left: 0
116
56
  };
117
57
 
118
- const none = 'none';
119
- const triggerPress = 'trigger-press';
120
- const triggerHover = 'trigger-hover';
121
- const outsidePress = 'outside-press';
122
- const itemPress = 'item-press';
123
- const closePress = 'close-press';
124
- const clearPress = 'clear-press';
125
- const chipRemovePress = 'chip-remove-press';
126
- const incrementPress = 'increment-press';
127
- const decrementPress = 'decrement-press';
128
- const inputChange = 'input-change';
129
- const inputClear = 'input-clear';
130
- const inputBlur = 'input-blur';
131
- const inputPaste = 'input-paste';
132
- const inputPress = 'input-press';
133
- const focusOut = 'focus-out';
134
- const escapeKey = 'escape-key';
135
- const closeWatcher = 'close-watcher';
136
- const listNavigation = 'list-navigation';
137
- const keyboard = 'keyboard';
138
- const scrub = 'scrub';
139
- const imperativeAction = 'imperative-action';
140
- const swipe = 'swipe';
141
-
142
- /**
143
- * Maps a change `reason` string to the corresponding native event type.
144
- */
145
-
146
- /**
147
- * Details of custom change events emitted by Base UI components.
148
- */
149
-
150
- /**
151
- * Details of custom generic events emitted by Base UI components.
152
- */
153
-
154
- /**
155
- * Creates a Base UI event details object with the given reason and utilities
156
- * for preventing Base UI's internal event handling.
157
- */
158
- function createChangeEventDetails(reason, event, trigger, customProperties) {
159
- let canceled = false;
160
- let allowPropagation = false;
161
- const custom = customProperties ?? EMPTY_OBJECT;
162
- const details = {
163
- reason,
164
- event: event ?? new Event('base-ui'),
165
- cancel() {
166
- canceled = true;
167
- },
168
- allowPropagation() {
169
- allowPropagation = true;
170
- },
171
- get isCanceled() {
172
- return canceled;
173
- },
174
- get isPropagationAllowed() {
175
- return allowPropagation;
176
- },
177
- trigger,
178
- ...custom
179
- };
180
- return details;
181
- }
182
- function createGenericEventDetails(reason, event, customProperties) {
183
- const custom = customProperties ?? EMPTY_OBJECT;
184
- const details = {
185
- reason,
186
- event: event ?? new Event('base-ui'),
187
- ...custom
188
- };
189
- return details;
190
- }
191
-
192
- // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
193
- const SafeReact = {
194
- ...React
58
+ let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
59
+ /**
60
+ * Present when the component is animating in.
61
+ */
62
+ TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
63
+ /**
64
+ * Present when the component is animating out.
65
+ */
66
+ TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
67
+ return TransitionStatusDataAttributes;
68
+ }({});
69
+ const STARTING_HOOK = {
70
+ [TransitionStatusDataAttributes.startingStyle]: ''
195
71
  };
196
-
197
- let globalId = 0;
198
-
199
- // TODO React 17: Remove `useGlobalId` once React 17 support is removed
200
- function useGlobalId(idOverride, prefix = 'mui') {
201
- const [defaultId, setDefaultId] = React.useState(idOverride);
202
- const id = idOverride || defaultId;
203
- React.useEffect(() => {
204
- if (defaultId == null) {
205
- // Fallback to this default id when possible.
206
- // Use the incrementing value for client-side rendering only.
207
- // We can't use it server-side.
208
- // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
209
- globalId += 1;
210
- setDefaultId(`${prefix}-${globalId}`);
72
+ const ENDING_HOOK = {
73
+ [TransitionStatusDataAttributes.endingStyle]: ''
74
+ };
75
+ const transitionStatusMapping = {
76
+ transitionStatus(value) {
77
+ if (value === 'starting') {
78
+ return STARTING_HOOK;
211
79
  }
212
- }, [defaultId, prefix]);
213
- return id;
214
- }
215
- const maybeReactUseId = SafeReact.useId;
216
-
217
- /**
218
- *
219
- * @example <div id={useId()} />
220
- * @param idOverride
221
- * @returns {string}
222
- */
223
- function useId(idOverride, prefix) {
224
- // React.useId() is only available from React 17.0.0.
225
- if (maybeReactUseId !== undefined) {
226
- const reactId = maybeReactUseId();
227
- return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
80
+ if (value === 'ending') {
81
+ return ENDING_HOOK;
82
+ }
83
+ return null;
228
84
  }
85
+ };
229
86
 
230
- // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
231
- // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
232
- return useGlobalId(idOverride, prefix);
233
- }
234
-
235
- const EMPTY$2 = [];
236
-
237
- /**
238
- * A React.useEffect equivalent that runs once, when the component is mounted.
239
- */
240
- function useOnMount(fn) {
241
- // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- no need to put `fn` in the dependency array
242
- /* eslint-disable react-hooks/exhaustive-deps */
243
- React.useEffect(fn, EMPTY$2);
244
- /* eslint-enable react-hooks/exhaustive-deps */
245
- }
246
-
247
- /** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
248
- * a monomorphic `uint` type with `0` meaning empty.
249
- * See warning note at:
250
- * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
251
- const EMPTY$1 = null;
252
- let LAST_RAF = globalThis.requestAnimationFrame;
253
- class Scheduler {
254
- /* This implementation uses an array as a backing data-structure for frame callbacks.
255
- * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
256
- * never calls the native `cancelAnimationFrame` if there are no frames left. This can
257
- * be much more efficient if there is a call pattern that alterns as
258
- * "request-cancel-request-cancel-…".
259
- * But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
260
- * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
261
-
262
- callbacks = [];
263
- callbacksCount = 0;
264
- nextId = 1;
265
- startId = 1;
266
- isScheduled = false;
267
- tick = timestamp => {
268
- this.isScheduled = false;
269
- const currentCallbacks = this.callbacks;
270
- const currentCallbacksCount = this.callbacksCount;
271
-
272
- // Update these before iterating, callbacks could call `requestAnimationFrame` again.
273
- this.callbacks = [];
274
- this.callbacksCount = 0;
275
- this.startId = this.nextId;
276
- if (currentCallbacksCount > 0) {
277
- for (let i = 0; i < currentCallbacks.length; i += 1) {
278
- currentCallbacks[i]?.(timestamp);
279
- }
280
- }
281
- };
282
- request(fn) {
283
- const id = this.nextId;
284
- this.nextId += 1;
285
- this.callbacks.push(fn);
286
- this.callbacksCount += 1;
287
-
288
- /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
289
- * but there's no guarantee that the animation frame will actually run before the fake
290
- * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
291
- const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
292
- if (!this.isScheduled || didRAFChange) {
293
- requestAnimationFrame(this.tick);
294
- this.isScheduled = true;
295
- }
296
- return id;
297
- }
298
- cancel(id) {
299
- const index = id - this.startId;
300
- if (index < 0 || index >= this.callbacks.length) {
301
- return;
302
- }
303
- this.callbacks[index] = null;
304
- this.callbacksCount -= 1;
305
- }
306
- }
307
- const scheduler = new Scheduler();
308
- class AnimationFrame {
309
- static create() {
310
- return new AnimationFrame();
311
- }
312
- static request(fn) {
313
- return scheduler.request(fn);
314
- }
315
- static cancel(id) {
316
- return scheduler.cancel(id);
317
- }
318
- currentId = EMPTY$1;
319
-
87
+ let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
320
88
  /**
321
- * Executes `fn` after `delay`, clearing any previously scheduled call.
89
+ * Present when the popup is open.
322
90
  */
323
- request(fn) {
324
- this.cancel();
325
- this.currentId = scheduler.request(() => {
326
- this.currentId = EMPTY$1;
327
- fn();
328
- });
329
- }
330
- cancel = () => {
331
- if (this.currentId !== EMPTY$1) {
332
- scheduler.cancel(this.currentId);
333
- this.currentId = EMPTY$1;
334
- }
335
- };
336
- disposeEffect = () => {
337
- return this.cancel;
338
- };
339
- }
340
-
341
- /**
342
- * A `requestAnimationFrame` with automatic cleanup and guard.
343
- */
344
- function useAnimationFrame() {
345
- const timeout = useRefWithInit(AnimationFrame.create).current;
346
- useOnMount(timeout.disposeEffect);
347
- return timeout;
348
- }
349
-
350
- /**
351
- * If the provided argument is a ref object, returns its `current` value.
352
- * Otherwise, returns the argument itself.
353
- */
354
- function resolveRef(maybeRef) {
355
- if (maybeRef == null) {
356
- return maybeRef;
357
- }
358
- return 'current' in maybeRef ? maybeRef.current : maybeRef;
359
- }
360
-
361
- let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
91
+ CommonPopupDataAttributes["open"] = "data-open";
362
92
  /**
363
- * Present when the component is animating in.
93
+ * Present when the popup is closed.
364
94
  */
365
- TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
95
+ CommonPopupDataAttributes["closed"] = "data-closed";
366
96
  /**
367
- * Present when the component is animating out.
97
+ * Present when the popup is animating in.
368
98
  */
369
- TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
370
- return TransitionStatusDataAttributes;
99
+ CommonPopupDataAttributes[CommonPopupDataAttributes["startingStyle"] = TransitionStatusDataAttributes.startingStyle] = "startingStyle";
100
+ /**
101
+ * Present when the popup is animating out.
102
+ */
103
+ CommonPopupDataAttributes[CommonPopupDataAttributes["endingStyle"] = TransitionStatusDataAttributes.endingStyle] = "endingStyle";
104
+ /**
105
+ * Present when the anchor is hidden.
106
+ */
107
+ CommonPopupDataAttributes["anchorHidden"] = "data-anchor-hidden";
108
+ /**
109
+ * Indicates which side the popup is positioned relative to the trigger.
110
+ * @type { 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}
111
+ */
112
+ CommonPopupDataAttributes["side"] = "data-side";
113
+ /**
114
+ * Indicates how the popup is aligned relative to specified side.
115
+ * @type {'start' | 'center' | 'end'}
116
+ */
117
+ CommonPopupDataAttributes["align"] = "data-align";
118
+ return CommonPopupDataAttributes;
371
119
  }({});
372
- const STARTING_HOOK = {
373
- [TransitionStatusDataAttributes.startingStyle]: ''
120
+ let CommonTriggerDataAttributes = /*#__PURE__*/function (CommonTriggerDataAttributes) {
121
+ /**
122
+ * Present when the popup is open.
123
+ */
124
+ CommonTriggerDataAttributes["popupOpen"] = "data-popup-open";
125
+ /**
126
+ * Present when a pressable trigger is pressed.
127
+ */
128
+ CommonTriggerDataAttributes["pressed"] = "data-pressed";
129
+ return CommonTriggerDataAttributes;
130
+ }({});
131
+ const TRIGGER_HOOK = {
132
+ [CommonTriggerDataAttributes.popupOpen]: ''
374
133
  };
375
- const ENDING_HOOK = {
376
- [TransitionStatusDataAttributes.endingStyle]: ''
134
+ const PRESSABLE_TRIGGER_HOOK = {
135
+ [CommonTriggerDataAttributes.popupOpen]: '',
136
+ [CommonTriggerDataAttributes.pressed]: ''
377
137
  };
378
- const transitionStatusMapping = {
379
- transitionStatus(value) {
380
- if (value === 'starting') {
381
- return STARTING_HOOK;
138
+ const POPUP_OPEN_HOOK = {
139
+ [CommonPopupDataAttributes.open]: ''
140
+ };
141
+ const POPUP_CLOSED_HOOK = {
142
+ [CommonPopupDataAttributes.closed]: ''
143
+ };
144
+ const ANCHOR_HIDDEN_HOOK = {
145
+ [CommonPopupDataAttributes.anchorHidden]: ''
146
+ };
147
+ const triggerOpenStateMapping = {
148
+ open(value) {
149
+ if (value) {
150
+ return TRIGGER_HOOK;
382
151
  }
383
- if (value === 'ending') {
384
- return ENDING_HOOK;
152
+ return null;
153
+ }
154
+ };
155
+ const pressableTriggerOpenStateMapping = {
156
+ open(value) {
157
+ if (value) {
158
+ return PRESSABLE_TRIGGER_HOOK;
159
+ }
160
+ return null;
161
+ }
162
+ };
163
+ const popupStateMapping = {
164
+ open(value) {
165
+ if (value) {
166
+ return POPUP_OPEN_HOOK;
167
+ }
168
+ return POPUP_CLOSED_HOOK;
169
+ },
170
+ anchorHidden(value) {
171
+ if (value) {
172
+ return ANCHOR_HIDDEN_HOOK;
385
173
  }
386
174
  return null;
387
175
  }
388
176
  };
389
177
 
390
- /**
391
- * Executes a function once all animations have finished on the provided element.
392
- * @param elementOrRef - The element to watch for animations.
393
- * @param waitForStartingStyleRemoved - Whether to wait for [data-starting-style] to be removed before checking for animations.
394
- * @param treatAbortedAsFinished - Whether to treat aborted animations as finished. If `false`, and there are aborted animations,
395
- * the function will check again if any new animations have started and wait for them to finish.
396
- * @returns A function that takes a callback to execute once all animations have finished, and an optional AbortSignal to abort the callback
397
- */
398
- function useAnimationsFinished(elementOrRef, waitForStartingStyleRemoved = false, treatAbortedAsFinished = true) {
399
- const frame = useAnimationFrame();
400
- return useStableCallback((fnToExecute,
178
+ let DrawerPopupCssVars = /*#__PURE__*/function (DrawerPopupCssVars) {
401
179
  /**
402
- * An optional [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that
403
- * can be used to abort `fnToExecute` before all the animations have finished.
404
- * @default null
180
+ * The number of nested drawers that are currently open.
181
+ * @type {number}
405
182
  */
406
- signal = null) => {
407
- frame.cancel();
408
- function done() {
409
- // Synchronously flush the unmounting of the component so that the browser doesn't
410
- // paint: https://github.com/mui/base-ui/issues/979
411
- ReactDOM.flushSync(fnToExecute);
412
- }
413
- const element = resolveRef(elementOrRef);
414
- if (element == null) {
415
- return;
416
- }
417
- const resolvedElement = element;
418
- if (typeof resolvedElement.getAnimations !== 'function' || globalThis.BASE_UI_ANIMATIONS_DISABLED) {
419
- fnToExecute();
420
- } else {
421
- function execWaitForStartingStyleRemoved() {
422
- const startingStyleAttribute = TransitionStatusDataAttributes.startingStyle;
183
+ DrawerPopupCssVars["nestedDrawers"] = "--nested-drawers";
184
+ /**
185
+ * The height of the drawer popup.
186
+ * @type {CSS length}
187
+ */
188
+ DrawerPopupCssVars["height"] = "--drawer-height";
189
+ /**
190
+ * The height of the frontmost open drawer in the current nested drawer stack.
191
+ * @type {CSS length}
192
+ */
193
+ DrawerPopupCssVars["frontmostHeight"] = "--drawer-frontmost-height";
194
+ /**
195
+ * The swipe movement on the X axis.
196
+ * @type {CSS length}
197
+ */
198
+ DrawerPopupCssVars["swipeMovementX"] = "--drawer-swipe-movement-x";
199
+ /**
200
+ * The swipe movement on the Y axis.
201
+ * @type {CSS length}
202
+ */
203
+ DrawerPopupCssVars["swipeMovementY"] = "--drawer-swipe-movement-y";
204
+ /**
205
+ * The snap point offset used for translating the drawer.
206
+ * @type {CSS length}
207
+ */
208
+ DrawerPopupCssVars["snapPointOffset"] = "--drawer-snap-point-offset";
209
+ /**
210
+ * A scalar (0.1-1) used to scale the swipe release transition duration in CSS.
211
+ * @type {number}
212
+ */
213
+ DrawerPopupCssVars["swipeStrength"] = "--drawer-swipe-strength";
214
+ return DrawerPopupCssVars;
215
+ }({});
423
216
 
424
- // If `[data-starting-style]` isn't present, fall back to waiting one more frame
425
- // to give "open" animations a chance to be registered.
426
- if (!resolvedElement.hasAttribute(startingStyleAttribute)) {
427
- frame.request(exec);
428
- return;
429
- }
217
+ let DrawerBackdropCssVars = /*#__PURE__*/function (DrawerBackdropCssVars) {
218
+ /**
219
+ * The swipe progress of the drawer gesture.
220
+ * @type {number}
221
+ */
222
+ DrawerBackdropCssVars["swipeProgress"] = "--drawer-swipe-progress";
223
+ return DrawerBackdropCssVars;
224
+ }({});
430
225
 
431
- // Wait for `[data-starting-style]` to have been removed.
432
- const attributeObserver = new MutationObserver(() => {
433
- if (!resolvedElement.hasAttribute(startingStyleAttribute)) {
434
- attributeObserver.disconnect();
435
- exec();
436
- }
437
- });
438
- attributeObserver.observe(resolvedElement, {
439
- attributes: true,
440
- attributeFilter: [startingStyleAttribute]
441
- });
442
- signal?.addEventListener('abort', () => attributeObserver.disconnect(), {
443
- once: true
444
- });
445
- }
446
- function exec() {
447
- Promise.all(resolvedElement.getAnimations().map(anim => anim.finished)).then(() => {
448
- if (signal?.aborted) {
449
- return;
450
- }
451
- done();
452
- }).catch(() => {
453
- const currentAnimations = resolvedElement.getAnimations();
454
- if (treatAbortedAsFinished) {
455
- if (signal?.aborted) {
456
- return;
457
- }
458
- done();
459
- } else if (currentAnimations.length > 0 && currentAnimations.some(anim => anim.pending || anim.playState !== 'finished')) {
460
- // Sometimes animations can be aborted because a property they depend on changes while the animation plays.
461
- // In such cases, we need to re-check if any new animations have started.
462
- exec();
463
- }
464
- });
465
- }
466
- if (waitForStartingStyleRemoved) {
467
- execWaitForStartingStyleRemoved();
468
- return;
226
+ const stateAttributesMapping$1 = {
227
+ ...popupStateMapping,
228
+ ...transitionStatusMapping
229
+ };
230
+
231
+ /**
232
+ * An overlay displayed beneath the popup.
233
+ * Renders a `<div>` element.
234
+ *
235
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
236
+ */
237
+ const DrawerBackdrop = /*#__PURE__*/React.forwardRef(function DrawerBackdrop(componentProps, forwardedRef) {
238
+ const {
239
+ render,
240
+ className,
241
+ forceRender = false,
242
+ ...elementProps
243
+ } = componentProps;
244
+ const {
245
+ store
246
+ } = useDialogRootContext();
247
+ const open = store.useState('open');
248
+ const nested = store.useState('nested');
249
+ const mounted = store.useState('mounted');
250
+ const transitionStatus = store.useState('transitionStatus');
251
+ const state = {
252
+ open,
253
+ transitionStatus
254
+ };
255
+ return useRenderElement('div', componentProps, {
256
+ state,
257
+ ref: [store.context.backdropRef, forwardedRef],
258
+ stateAttributesMapping: stateAttributesMapping$1,
259
+ props: [{
260
+ role: 'presentation',
261
+ hidden: !mounted,
262
+ style: {
263
+ pointerEvents: !open ? 'none' : undefined,
264
+ userSelect: 'none',
265
+ WebkitUserSelect: 'none',
266
+ [DrawerBackdropCssVars.swipeProgress]: '0',
267
+ [DrawerPopupCssVars.swipeStrength]: '1'
469
268
  }
470
- frame.request(exec);
471
- }
269
+ }, elementProps],
270
+ enabled: forceRender || !nested
472
271
  });
473
- }
272
+ });
273
+ if (process.env.NODE_ENV !== "production") DrawerBackdrop.displayName = "DrawerBackdrop";
474
274
 
275
+ // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
276
+ const useInsertionEffect = React[`useInsertionEffect${Math.random().toFixed(1)}`.slice(0, -3)];
277
+ const useSafeInsertionEffect =
278
+ // React 17 doesn't have useInsertionEffect.
279
+ useInsertionEffect &&
280
+ // Preact replaces useInsertionEffect with useLayoutEffect and fires too late.
281
+ useInsertionEffect !== React.useLayoutEffect ? useInsertionEffect : fn => fn();
475
282
  /**
476
- * Provides a status string for CSS animations.
477
- * @param open - a boolean that determines if the element is open.
478
- * @param enableIdleState - a boolean that enables the `'idle'` state between `'starting'` and `'ending'`
283
+ * Stabilizes the function passed so it's always the same between renders.
284
+ *
285
+ * The function becomes non-reactive to any values it captures.
286
+ * It can safely be passed as a dependency of `React.useMemo` and `React.useEffect` without re-triggering them if its captured values change.
287
+ *
288
+ * The function must only be called inside effects and event handlers, never during render (which throws an error).
289
+ *
290
+ * This hook is a more permissive version of React 19.2's `React.useEffectEvent` in that it can be passed through contexts and called in event handler props, not just effects.
479
291
  */
480
- function useTransitionStatus(open, enableIdleState = false, deferEndingState = false) {
481
- const [transitionStatus, setTransitionStatus] = React.useState(open && enableIdleState ? 'idle' : undefined);
482
- const [mounted, setMounted] = React.useState(open);
483
- if (open && !mounted) {
484
- setMounted(true);
485
- setTransitionStatus('starting');
486
- }
487
- if (!open && mounted && transitionStatus !== 'ending' && !deferEndingState) {
488
- setTransitionStatus('ending');
489
- }
490
- if (!open && !mounted && transitionStatus === 'ending') {
491
- setTransitionStatus(undefined);
492
- }
493
- useIsoLayoutEffect(() => {
494
- if (!open && mounted && transitionStatus !== 'ending' && deferEndingState) {
495
- const frame = AnimationFrame.request(() => {
496
- setTransitionStatus('ending');
497
- });
498
- return () => {
499
- AnimationFrame.cancel(frame);
500
- };
501
- }
502
- return undefined;
503
- }, [open, mounted, transitionStatus, deferEndingState]);
504
- useIsoLayoutEffect(() => {
505
- if (!open || enableIdleState) {
506
- return undefined;
507
- }
508
- const frame = AnimationFrame.request(() => {
509
- // Avoid `flushSync` here due to Firefox.
510
- // See https://github.com/mui/base-ui/pull/3424
511
- setTransitionStatus(undefined);
512
- });
513
- return () => {
514
- AnimationFrame.cancel(frame);
515
- };
516
- }, [enableIdleState, open]);
517
- useIsoLayoutEffect(() => {
518
- if (!open || !enableIdleState) {
519
- return undefined;
520
- }
521
- if (open && mounted && transitionStatus !== 'idle') {
522
- setTransitionStatus('starting');
523
- }
524
- const frame = AnimationFrame.request(() => {
525
- setTransitionStatus('idle');
526
- });
527
- return () => {
528
- AnimationFrame.cancel(frame);
529
- };
530
- }, [enableIdleState, open, mounted, setTransitionStatus, transitionStatus]);
531
- return React.useMemo(() => ({
532
- mounted,
533
- setMounted,
534
- transitionStatus
535
- }), [mounted, transitionStatus]);
292
+ function useStableCallback(callback) {
293
+ const stable = useRefWithInit(createStableCallback).current;
294
+ stable.next = callback;
295
+ useSafeInsertionEffect(stable.effect);
296
+ return stable.trampoline;
536
297
  }
537
-
538
- const hasNavigator = typeof navigator !== 'undefined';
539
- const nav = getNavigatorData();
540
- const platform = getPlatform();
541
- const userAgent = getUserAgent();
542
- const isWebKit = typeof CSS === 'undefined' || !CSS.supports ? false : CSS.supports('-webkit-backdrop-filter:none');
543
- const isIOS =
544
- // iPads can claim to be MacIntel
545
- nav.platform === 'MacIntel' && nav.maxTouchPoints > 1 ? true : /iP(hone|ad|od)|iOS/.test(nav.platform);
546
- const isFirefox = hasNavigator && /firefox/i.test(userAgent);
547
- const isSafari = hasNavigator && /apple/i.test(navigator.vendor);
548
- const isAndroid = hasNavigator && /android/i.test(platform) || /android/i.test(userAgent);
549
- hasNavigator && platform.toLowerCase().startsWith('mac') && !navigator.maxTouchPoints;
550
- const isJSDOM = userAgent.includes('jsdom/');
551
-
552
- // Avoid Chrome DevTools blue warning.
553
- function getNavigatorData() {
554
- if (!hasNavigator) {
555
- return {
556
- platform: '',
557
- maxTouchPoints: -1
558
- };
559
- }
560
- const uaData = navigator.userAgentData;
561
- if (uaData?.platform) {
562
- return {
563
- platform: uaData.platform,
564
- maxTouchPoints: navigator.maxTouchPoints
565
- };
566
- }
567
- return {
568
- platform: navigator.platform ?? '',
569
- maxTouchPoints: navigator.maxTouchPoints ?? -1
298
+ function createStableCallback() {
299
+ const stable = {
300
+ next: undefined,
301
+ callback: assertNotCalled,
302
+ trampoline: (...args) => stable.callback?.(...args),
303
+ effect: () => {
304
+ stable.callback = stable.next;
305
+ }
570
306
  };
307
+ return stable;
571
308
  }
572
- function getUserAgent() {
573
- if (!hasNavigator) {
574
- return '';
575
- }
576
- const uaData = navigator.userAgentData;
577
- if (uaData && Array.isArray(uaData.brands)) {
578
- return uaData.brands.map(({
579
- brand,
580
- version
581
- }) => `${brand}/${version}`).join(' ');
582
- }
583
- return navigator.userAgent;
584
- }
585
- function getPlatform() {
586
- if (!hasNavigator) {
587
- return '';
588
- }
589
- const uaData = navigator.userAgentData;
590
- if (uaData?.platform) {
591
- return uaData.platform;
309
+ function assertNotCalled() {
310
+ if (process.env.NODE_ENV !== 'production') {
311
+ throw /* minify-error-disabled */new Error('Base UI: Cannot call an event handler while rendering.');
592
312
  }
593
- return navigator.platform ?? '';
594
313
  }
595
314
 
596
- const FOCUSABLE_ATTRIBUTE = 'data-base-ui-focusable';
597
- const ACTIVE_KEY = 'active';
598
- const SELECTED_KEY = 'selected';
599
- const TYPEABLE_SELECTOR = "input:not([type='hidden']):not([disabled])," + "[contenteditable]:not([contenteditable='false']),textarea:not([disabled])";
600
- const ARROW_LEFT$1 = 'ArrowLeft';
601
- const ARROW_RIGHT$1 = 'ArrowRight';
602
- const ARROW_UP$1 = 'ArrowUp';
603
- const ARROW_DOWN$1 = 'ArrowDown';
315
+ // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
316
+ const SafeReact = {
317
+ ...React
318
+ };
604
319
 
605
- function activeElement(doc) {
606
- let element = doc.activeElement;
607
- while (element?.shadowRoot?.activeElement != null) {
608
- element = element.shadowRoot.activeElement;
609
- }
610
- return element;
611
- }
612
- function contains(parent, child) {
613
- if (!parent || !child) {
614
- return false;
615
- }
616
- const rootNode = child.getRootNode?.();
320
+ const noop = () => {};
321
+ const useIsoLayoutEffect = typeof document !== 'undefined' ? React.useLayoutEffect : noop;
617
322
 
618
- // First, attempt with faster native method
619
- if (parent.contains(child)) {
620
- return true;
621
- }
323
+ const none = 'none';
324
+ const triggerPress = 'trigger-press';
325
+ const triggerHover = 'trigger-hover';
326
+ const triggerFocus = 'trigger-focus';
327
+ const outsidePress = 'outside-press';
328
+ const itemPress = 'item-press';
329
+ const closePress = 'close-press';
330
+ const clearPress = 'clear-press';
331
+ const chipRemovePress = 'chip-remove-press';
332
+ const incrementPress = 'increment-press';
333
+ const decrementPress = 'decrement-press';
334
+ const inputChange = 'input-change';
335
+ const inputClear = 'input-clear';
336
+ const inputBlur = 'input-blur';
337
+ const inputPaste = 'input-paste';
338
+ const inputPress = 'input-press';
339
+ const focusOut = 'focus-out';
340
+ const escapeKey = 'escape-key';
341
+ const closeWatcher = 'close-watcher';
342
+ const listNavigation = 'list-navigation';
343
+ const keyboard = 'keyboard';
344
+ const scrub = 'scrub';
345
+ const cancelOpen = 'cancel-open';
346
+ const siblingOpen = 'sibling-open';
347
+ const imperativeAction = 'imperative-action';
348
+ const swipe = 'swipe';
622
349
 
623
- // then fallback to custom implementation with Shadow DOM support
624
- if (rootNode && isShadowRoot(rootNode)) {
625
- let next = child;
626
- while (next) {
627
- if (parent === next) {
628
- return true;
629
- }
630
- next = next.parentNode || next.host;
631
- }
632
- }
350
+ /**
351
+ * Maps a change `reason` string to the corresponding native event type.
352
+ */
633
353
 
634
- // Give up, the result is false
635
- return false;
636
- }
637
- function getTarget(event) {
638
- if ('composedPath' in event) {
639
- return event.composedPath()[0];
640
- }
354
+ /**
355
+ * Details of custom change events emitted by Base UI components.
356
+ */
641
357
 
642
- // TS thinks `event` is of type never as it assumes all browsers support
643
- // `composedPath()`, but browsers without shadow DOM don't.
644
- return event.target;
645
- }
646
- function isEventTargetWithin(event, node) {
647
- if (node == null) {
648
- return false;
649
- }
650
- if ('composedPath' in event) {
651
- return event.composedPath().includes(node);
652
- }
358
+ /**
359
+ * Details of custom generic events emitted by Base UI components.
360
+ */
653
361
 
654
- // TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't
655
- const eventAgain = event;
656
- return eventAgain.target != null && node.contains(eventAgain.target);
657
- }
658
- function isRootElement(element) {
659
- return element.matches('html,body');
660
- }
661
- function isTypeableElement(element) {
662
- return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
663
- }
664
- function isTypeableCombobox(element) {
665
- if (!element) {
666
- return false;
667
- }
668
- return element.getAttribute('role') === 'combobox' && isTypeableElement(element);
362
+ /**
363
+ * Creates a Base UI event details object with the given reason and utilities
364
+ * for preventing Base UI's internal event handling.
365
+ */
366
+ function createChangeEventDetails(reason, event, trigger, customProperties) {
367
+ let canceled = false;
368
+ let allowPropagation = false;
369
+ const custom = customProperties ?? EMPTY_OBJECT;
370
+ const details = {
371
+ reason,
372
+ event: event ?? new Event('base-ui'),
373
+ cancel() {
374
+ canceled = true;
375
+ },
376
+ allowPropagation() {
377
+ allowPropagation = true;
378
+ },
379
+ get isCanceled() {
380
+ return canceled;
381
+ },
382
+ get isPropagationAllowed() {
383
+ return allowPropagation;
384
+ },
385
+ trigger,
386
+ ...custom
387
+ };
388
+ return details;
669
389
  }
670
- function getFloatingFocusElement(floatingElement) {
671
- if (!floatingElement) {
672
- return null;
673
- }
674
- // Try to find the element that has `{...getFloatingProps()}` spread on it.
675
- // This indicates the floating element is acting as a positioning wrapper, and
676
- // so focus should be managed on the child element with the event handlers and
677
- // aria props.
678
- return floatingElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? floatingElement : floatingElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`) || floatingElement;
390
+ function createGenericEventDetails(reason, event, customProperties) {
391
+ const custom = customProperties ?? EMPTY_OBJECT;
392
+ const details = {
393
+ reason,
394
+ event: event ?? new Event('base-ui'),
395
+ ...custom
396
+ };
397
+ return details;
679
398
  }
680
399
 
681
- /* eslint-disable @typescript-eslint/no-loop-func */
400
+ const DRAWER_CONTENT_ATTRIBUTE = 'data-drawer-content';
682
401
 
683
- function getNodeChildren(nodes, id, onlyOpenChildren = true) {
684
- const directChildren = nodes.filter(node => node.parentId === id && (!onlyOpenChildren || node.context?.open));
685
- return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
686
- }
687
- function getNodeAncestors(nodes, id) {
688
- let allAncestors = [];
689
- let currentParentId = nodes.find(node => node.id === id)?.parentId;
690
- while (currentParentId) {
691
- const currentNode = nodes.find(node => node.id === currentParentId);
692
- currentParentId = currentNode?.parentId;
693
- if (currentNode) {
694
- allAncestors = allAncestors.concat(currentNode);
402
+ /**
403
+ * A container for the drawer contents.
404
+ * Renders a `<div>` element.
405
+ *
406
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
407
+ */
408
+ const DrawerContent = /*#__PURE__*/React.forwardRef(function DrawerContent(componentProps, forwardedRef) {
409
+ const {
410
+ render,
411
+ className,
412
+ ...elementProps
413
+ } = componentProps;
414
+ useDialogRootContext();
415
+ return useRenderElement('div', componentProps, {
416
+ ref: forwardedRef,
417
+ props: [{
418
+ [DRAWER_CONTENT_ATTRIBUTE]: ''
419
+ }, elementProps]
420
+ });
421
+ });
422
+ if (process.env.NODE_ENV !== "production") DrawerContent.displayName = "DrawerContent";
423
+
424
+ let globalId = 0;
425
+
426
+ // TODO React 17: Remove `useGlobalId` once React 17 support is removed
427
+ function useGlobalId(idOverride, prefix = 'mui') {
428
+ const [defaultId, setDefaultId] = React.useState(idOverride);
429
+ const id = idOverride || defaultId;
430
+ React.useEffect(() => {
431
+ if (defaultId == null) {
432
+ // Fallback to this default id when possible.
433
+ // Use the incrementing value for client-side rendering only.
434
+ // We can't use it server-side.
435
+ // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
436
+ globalId += 1;
437
+ setDefaultId(`${prefix}-${globalId}`);
695
438
  }
439
+ }, [defaultId, prefix]);
440
+ return id;
441
+ }
442
+ const maybeReactUseId = SafeReact.useId;
443
+
444
+ /**
445
+ *
446
+ * @example <div id={useId()} />
447
+ * @param idOverride
448
+ * @returns {string}
449
+ */
450
+ function useId(idOverride, prefix) {
451
+ // React.useId() is only available from React 17.0.0.
452
+ if (maybeReactUseId !== undefined) {
453
+ const reactId = maybeReactUseId();
454
+ return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
696
455
  }
697
- return allAncestors;
456
+
457
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
458
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
459
+ return useGlobalId(idOverride, prefix);
698
460
  }
699
461
 
700
- function stopEvent(event) {
701
- event.preventDefault();
702
- event.stopPropagation();
462
+ const DrawerProviderContext = /*#__PURE__*/React.createContext(undefined);
463
+ if (process.env.NODE_ENV !== "production") DrawerProviderContext.displayName = "DrawerProviderContext";
464
+ function useDrawerProviderContext(optional) {
465
+ const context = React.useContext(DrawerProviderContext);
466
+ return context;
703
467
  }
704
- function isReactEvent(event) {
705
- return 'nativeEvent' in event;
468
+
469
+ const EMPTY$2 = [];
470
+
471
+ /**
472
+ * A React.useEffect equivalent that runs once, when the component is mounted.
473
+ */
474
+ function useOnMount(fn) {
475
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- no need to put `fn` in the dependency array
476
+ /* eslint-disable react-hooks/exhaustive-deps */
477
+ React.useEffect(fn, EMPTY$2);
478
+ /* eslint-enable react-hooks/exhaustive-deps */
706
479
  }
707
480
 
708
- // License: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/utils/src/isVirtualEvent.ts
709
- function isVirtualClick(event) {
710
- if (event.pointerType === '' && event.isTrusted) {
711
- return true;
481
+ const EMPTY$1 = 0;
482
+ class Timeout {
483
+ static create() {
484
+ return new Timeout();
712
485
  }
713
- if (isAndroid && event.pointerType) {
714
- return event.type === 'click' && event.buttons === 1;
486
+ currentId = EMPTY$1;
487
+
488
+ /**
489
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
490
+ */
491
+ start(delay, fn) {
492
+ this.clear();
493
+ this.currentId = setTimeout(() => {
494
+ this.currentId = EMPTY$1;
495
+ fn();
496
+ }, delay); /* Node.js types are enabled in development */
715
497
  }
716
- return event.detail === 0 && !event.pointerType;
717
- }
718
- function isVirtualPointerEvent(event) {
719
- if (isJSDOM) {
720
- return false;
498
+ isStarted() {
499
+ return this.currentId !== EMPTY$1;
721
500
  }
722
- return !isAndroid && event.width === 0 && event.height === 0 || isAndroid && event.width === 1 && event.height === 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'mouse' ||
723
- // iOS VoiceOver returns 0.333• for width/height.
724
- event.width < 1 && event.height < 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'touch';
725
- }
726
- function isMouseLikePointerType(pointerType, strict) {
501
+ clear = () => {
502
+ if (this.currentId !== EMPTY$1) {
503
+ clearTimeout(this.currentId);
504
+ this.currentId = EMPTY$1;
505
+ }
506
+ };
507
+ disposeEffect = () => {
508
+ return this.clear;
509
+ };
510
+ }
511
+
512
+ /**
513
+ * A `setTimeout` with automatic cleanup and guard.
514
+ */
515
+ function useTimeout() {
516
+ const timeout = useRefWithInit(Timeout.create).current;
517
+ useOnMount(timeout.disposeEffect);
518
+ return timeout;
519
+ }
520
+
521
+ const hasNavigator = typeof navigator !== 'undefined';
522
+ const nav = getNavigatorData();
523
+ const platform = getPlatform();
524
+ const userAgent = getUserAgent();
525
+ const isWebKit = typeof CSS === 'undefined' || !CSS.supports ? false : CSS.supports('-webkit-backdrop-filter:none');
526
+ const isIOS =
527
+ // iPads can claim to be MacIntel
528
+ nav.platform === 'MacIntel' && nav.maxTouchPoints > 1 ? true : /iP(hone|ad|od)|iOS/.test(nav.platform);
529
+ const isFirefox = hasNavigator && /firefox/i.test(userAgent);
530
+ const isSafari = hasNavigator && /apple/i.test(navigator.vendor);
531
+ const isAndroid = hasNavigator && /android/i.test(platform) || /android/i.test(userAgent);
532
+ const isMac = hasNavigator && platform.toLowerCase().startsWith('mac') && !navigator.maxTouchPoints;
533
+ const isJSDOM = userAgent.includes('jsdom/');
534
+
535
+ // Avoid Chrome DevTools blue warning.
536
+ function getNavigatorData() {
537
+ if (!hasNavigator) {
538
+ return {
539
+ platform: '',
540
+ maxTouchPoints: -1
541
+ };
542
+ }
543
+ const uaData = navigator.userAgentData;
544
+ if (uaData?.platform) {
545
+ return {
546
+ platform: uaData.platform,
547
+ maxTouchPoints: navigator.maxTouchPoints
548
+ };
549
+ }
550
+ return {
551
+ platform: navigator.platform ?? '',
552
+ maxTouchPoints: navigator.maxTouchPoints ?? -1
553
+ };
554
+ }
555
+ function getUserAgent() {
556
+ if (!hasNavigator) {
557
+ return '';
558
+ }
559
+ const uaData = navigator.userAgentData;
560
+ if (uaData && Array.isArray(uaData.brands)) {
561
+ return uaData.brands.map(({
562
+ brand,
563
+ version
564
+ }) => `${brand}/${version}`).join(' ');
565
+ }
566
+ return navigator.userAgent;
567
+ }
568
+ function getPlatform() {
569
+ if (!hasNavigator) {
570
+ return '';
571
+ }
572
+ const uaData = navigator.userAgentData;
573
+ if (uaData?.platform) {
574
+ return uaData.platform;
575
+ }
576
+ return navigator.platform ?? '';
577
+ }
578
+
579
+ const FOCUSABLE_ATTRIBUTE = 'data-base-ui-focusable';
580
+ const ACTIVE_KEY = 'active';
581
+ const SELECTED_KEY = 'selected';
582
+ const TYPEABLE_SELECTOR = "input:not([type='hidden']):not([disabled])," + "[contenteditable]:not([contenteditable='false']),textarea:not([disabled])";
583
+ const ARROW_LEFT$1 = 'ArrowLeft';
584
+ const ARROW_RIGHT$1 = 'ArrowRight';
585
+ const ARROW_UP$1 = 'ArrowUp';
586
+ const ARROW_DOWN$1 = 'ArrowDown';
587
+
588
+ function activeElement(doc) {
589
+ let element = doc.activeElement;
590
+ while (element?.shadowRoot?.activeElement != null) {
591
+ element = element.shadowRoot.activeElement;
592
+ }
593
+ return element;
594
+ }
595
+ function contains(parent, child) {
596
+ if (!parent || !child) {
597
+ return false;
598
+ }
599
+ const rootNode = child.getRootNode?.();
600
+
601
+ // First, attempt with faster native method
602
+ if (parent.contains(child)) {
603
+ return true;
604
+ }
605
+
606
+ // then fallback to custom implementation with Shadow DOM support
607
+ if (rootNode && isShadowRoot(rootNode)) {
608
+ let next = child;
609
+ while (next) {
610
+ if (parent === next) {
611
+ return true;
612
+ }
613
+ next = next.parentNode || next.host;
614
+ }
615
+ }
616
+
617
+ // Give up, the result is false
618
+ return false;
619
+ }
620
+ function isTargetInsideEnabledTrigger(target, triggerElements) {
621
+ if (!isElement(target)) {
622
+ return false;
623
+ }
624
+ const targetElement = target;
625
+ if (triggerElements.hasElement(targetElement)) {
626
+ return !targetElement.hasAttribute('data-trigger-disabled');
627
+ }
628
+ for (const [, trigger] of triggerElements.entries()) {
629
+ if (contains(trigger, targetElement)) {
630
+ return !trigger.hasAttribute('data-trigger-disabled');
631
+ }
632
+ }
633
+ return false;
634
+ }
635
+ function getTarget(event) {
636
+ if ('composedPath' in event) {
637
+ return event.composedPath()[0];
638
+ }
639
+
640
+ // TS thinks `event` is of type never as it assumes all browsers support
641
+ // `composedPath()`, but browsers without shadow DOM don't.
642
+ return event.target;
643
+ }
644
+ function isEventTargetWithin(event, node) {
645
+ if (node == null) {
646
+ return false;
647
+ }
648
+ if ('composedPath' in event) {
649
+ return event.composedPath().includes(node);
650
+ }
651
+
652
+ // TS thinks `event` is of type never as it assumes all browsers support composedPath, but browsers without shadow dom don't
653
+ const eventAgain = event;
654
+ return eventAgain.target != null && node.contains(eventAgain.target);
655
+ }
656
+ function isRootElement(element) {
657
+ return element.matches('html,body');
658
+ }
659
+ function isTypeableElement(element) {
660
+ return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR);
661
+ }
662
+ function isTypeableCombobox(element) {
663
+ if (!element) {
664
+ return false;
665
+ }
666
+ return element.getAttribute('role') === 'combobox' && isTypeableElement(element);
667
+ }
668
+ function matchesFocusVisible(element) {
669
+ // We don't want to block focus from working with `visibleOnly`
670
+ // (JSDOM doesn't match `:focus-visible` when the element has `:focus`)
671
+ if (!element || isJSDOM) {
672
+ return true;
673
+ }
674
+ try {
675
+ return element.matches(':focus-visible');
676
+ } catch (_e) {
677
+ return true;
678
+ }
679
+ }
680
+ function getFloatingFocusElement(floatingElement) {
681
+ if (!floatingElement) {
682
+ return null;
683
+ }
684
+ // Try to find the element that has `{...getFloatingProps()}` spread on it.
685
+ // This indicates the floating element is acting as a positioning wrapper, and
686
+ // so focus should be managed on the child element with the event handlers and
687
+ // aria props.
688
+ return floatingElement.hasAttribute(FOCUSABLE_ATTRIBUTE) ? floatingElement : floatingElement.querySelector(`[${FOCUSABLE_ATTRIBUTE}]`) || floatingElement;
689
+ }
690
+
691
+ /* eslint-disable @typescript-eslint/no-loop-func */
692
+
693
+ function getNodeChildren(nodes, id, onlyOpenChildren = true) {
694
+ const directChildren = nodes.filter(node => node.parentId === id && (!onlyOpenChildren || node.context?.open));
695
+ return directChildren.flatMap(child => [child, ...getNodeChildren(nodes, child.id, onlyOpenChildren)]);
696
+ }
697
+ function getNodeAncestors(nodes, id) {
698
+ let allAncestors = [];
699
+ let currentParentId = nodes.find(node => node.id === id)?.parentId;
700
+ while (currentParentId) {
701
+ const currentNode = nodes.find(node => node.id === currentParentId);
702
+ currentParentId = currentNode?.parentId;
703
+ if (currentNode) {
704
+ allAncestors = allAncestors.concat(currentNode);
705
+ }
706
+ }
707
+ return allAncestors;
708
+ }
709
+
710
+ function stopEvent(event) {
711
+ event.preventDefault();
712
+ event.stopPropagation();
713
+ }
714
+ function isReactEvent(event) {
715
+ return 'nativeEvent' in event;
716
+ }
717
+
718
+ // License: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/utils/src/isVirtualEvent.ts
719
+ function isVirtualClick(event) {
720
+ if (event.pointerType === '' && event.isTrusted) {
721
+ return true;
722
+ }
723
+ if (isAndroid && event.pointerType) {
724
+ return event.type === 'click' && event.buttons === 1;
725
+ }
726
+ return event.detail === 0 && !event.pointerType;
727
+ }
728
+ function isVirtualPointerEvent(event) {
729
+ if (isJSDOM) {
730
+ return false;
731
+ }
732
+ return !isAndroid && event.width === 0 && event.height === 0 || isAndroid && event.width === 1 && event.height === 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'mouse' ||
733
+ // iOS VoiceOver returns 0.333• for width/height.
734
+ event.width < 1 && event.height < 1 && event.pressure === 0 && event.detail === 0 && event.pointerType === 'touch';
735
+ }
736
+ function isMouseLikePointerType(pointerType, strict) {
727
737
  // On some Linux machines with Chromium, mouse inputs return a `pointerType`
728
738
  // of "pen": https://github.com/floating-ui/floating-ui/issues/2015
729
739
  const values = ['mouse', 'pen'];
740
+ if (!strict) {
741
+ values.push('', undefined);
742
+ }
730
743
  return values.includes(pointerType);
731
744
  }
732
745
  function isClickLikeEvent(event) {
@@ -1105,6 +1118,28 @@ function getNextTabbable(referenceElement) {
1105
1118
  function getPreviousTabbable(referenceElement) {
1106
1119
  return getTabbableIn(ownerDocument(referenceElement).body, -1) || referenceElement;
1107
1120
  }
1121
+ function getTabbableNearElement(referenceElement, dir) {
1122
+ if (!referenceElement) {
1123
+ return null;
1124
+ }
1125
+ const list = tabbable(ownerDocument(referenceElement).body, getTabbableOptions());
1126
+ const elementCount = list.length;
1127
+ if (elementCount === 0) {
1128
+ return null;
1129
+ }
1130
+ const index = list.indexOf(referenceElement);
1131
+ if (index === -1) {
1132
+ return null;
1133
+ }
1134
+ const nextIndex = (index + dir + elementCount) % elementCount;
1135
+ return list[nextIndex];
1136
+ }
1137
+ function getTabbableAfterElement(referenceElement) {
1138
+ return getTabbableNearElement(referenceElement, 1);
1139
+ }
1140
+ function getTabbableBeforeElement(referenceElement) {
1141
+ return getTabbableNearElement(referenceElement, -1);
1142
+ }
1108
1143
  function isOutsideEvent(event, container) {
1109
1144
  const containerElement = container || event.currentTarget;
1110
1145
  const relatedTarget = event.relatedTarget;
@@ -1130,412 +1165,203 @@ function enableFocusInside(container) {
1130
1165
  });
1131
1166
  }
1132
1167
 
1133
- const ARROW_UP = 'ArrowUp';
1134
- const ARROW_DOWN = 'ArrowDown';
1135
- const ARROW_LEFT = 'ArrowLeft';
1136
- const ARROW_RIGHT = 'ArrowRight';
1137
- const HOME = 'Home';
1138
- const END = 'End';
1139
- const HORIZONTAL_KEYS = new Set([ARROW_LEFT, ARROW_RIGHT]);
1140
- const VERTICAL_KEYS = new Set([ARROW_UP, ARROW_DOWN]);
1141
- const ARROW_KEYS = new Set([...HORIZONTAL_KEYS, ...VERTICAL_KEYS]);
1142
- new Set([...ARROW_KEYS, HOME, END]);
1143
- const COMPOSITE_KEYS = new Set([ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, HOME, END]);
1144
-
1145
1168
  /**
1146
- * Calls the provided function when the CSS open/close animation or transition completes.
1169
+ * Untracks the provided value by turning it into a ref to remove its reactivity.
1170
+ *
1171
+ * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
1147
1172
  */
1148
- function useOpenChangeComplete(parameters) {
1149
- const {
1150
- enabled = true,
1151
- open,
1152
- ref,
1153
- onComplete: onCompleteParam
1154
- } = parameters;
1155
- const onComplete = useStableCallback(onCompleteParam);
1156
- const runOnceAnimationsFinish = useAnimationsFinished(ref, open, false);
1157
- React.useEffect(() => {
1158
- if (!enabled) {
1159
- return undefined;
1173
+ function useValueAsRef(value) {
1174
+ const latest = useRefWithInit(createLatestRef, value).current;
1175
+ latest.next = value;
1176
+
1177
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1178
+ useIsoLayoutEffect(latest.effect);
1179
+ return latest;
1180
+ }
1181
+ function createLatestRef(value) {
1182
+ const latest = {
1183
+ current: value,
1184
+ next: value,
1185
+ effect: () => {
1186
+ latest.current = latest.next;
1160
1187
  }
1161
- const abortController = new AbortController();
1162
- runOnceAnimationsFinish(onComplete, abortController.signal);
1163
- return () => {
1164
- abortController.abort();
1165
- };
1166
- }, [enabled, open, onComplete, runOnceAnimationsFinish]);
1188
+ };
1189
+ return latest;
1167
1190
  }
1168
1191
 
1169
- const EMPTY = 0;
1170
- class Timeout {
1192
+ /** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
1193
+ * a monomorphic `uint` type with `0` meaning empty.
1194
+ * See warning note at:
1195
+ * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
1196
+ const EMPTY = null;
1197
+ let LAST_RAF = globalThis.requestAnimationFrame;
1198
+ class Scheduler {
1199
+ /* This implementation uses an array as a backing data-structure for frame callbacks.
1200
+ * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
1201
+ * never calls the native `cancelAnimationFrame` if there are no frames left. This can
1202
+ * be much more efficient if there is a call pattern that alterns as
1203
+ * "request-cancel-request-cancel-…".
1204
+ * But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
1205
+ * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
1206
+
1207
+ callbacks = [];
1208
+ callbacksCount = 0;
1209
+ nextId = 1;
1210
+ startId = 1;
1211
+ isScheduled = false;
1212
+ tick = timestamp => {
1213
+ this.isScheduled = false;
1214
+ const currentCallbacks = this.callbacks;
1215
+ const currentCallbacksCount = this.callbacksCount;
1216
+
1217
+ // Update these before iterating, callbacks could call `requestAnimationFrame` again.
1218
+ this.callbacks = [];
1219
+ this.callbacksCount = 0;
1220
+ this.startId = this.nextId;
1221
+ if (currentCallbacksCount > 0) {
1222
+ for (let i = 0; i < currentCallbacks.length; i += 1) {
1223
+ currentCallbacks[i]?.(timestamp);
1224
+ }
1225
+ }
1226
+ };
1227
+ request(fn) {
1228
+ const id = this.nextId;
1229
+ this.nextId += 1;
1230
+ this.callbacks.push(fn);
1231
+ this.callbacksCount += 1;
1232
+
1233
+ /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
1234
+ * but there's no guarantee that the animation frame will actually run before the fake
1235
+ * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
1236
+ const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
1237
+ if (!this.isScheduled || didRAFChange) {
1238
+ requestAnimationFrame(this.tick);
1239
+ this.isScheduled = true;
1240
+ }
1241
+ return id;
1242
+ }
1243
+ cancel(id) {
1244
+ const index = id - this.startId;
1245
+ if (index < 0 || index >= this.callbacks.length) {
1246
+ return;
1247
+ }
1248
+ this.callbacks[index] = null;
1249
+ this.callbacksCount -= 1;
1250
+ }
1251
+ }
1252
+ const scheduler = new Scheduler();
1253
+ class AnimationFrame {
1171
1254
  static create() {
1172
- return new Timeout();
1255
+ return new AnimationFrame();
1256
+ }
1257
+ static request(fn) {
1258
+ return scheduler.request(fn);
1259
+ }
1260
+ static cancel(id) {
1261
+ return scheduler.cancel(id);
1173
1262
  }
1174
1263
  currentId = EMPTY;
1175
1264
 
1176
1265
  /**
1177
1266
  * Executes `fn` after `delay`, clearing any previously scheduled call.
1178
1267
  */
1179
- start(delay, fn) {
1180
- this.clear();
1181
- this.currentId = setTimeout(() => {
1268
+ request(fn) {
1269
+ this.cancel();
1270
+ this.currentId = scheduler.request(() => {
1182
1271
  this.currentId = EMPTY;
1183
1272
  fn();
1184
- }, delay); /* Node.js types are enabled in development */
1185
- }
1186
- isStarted() {
1187
- return this.currentId !== EMPTY;
1273
+ });
1188
1274
  }
1189
- clear = () => {
1275
+ cancel = () => {
1190
1276
  if (this.currentId !== EMPTY) {
1191
- clearTimeout(this.currentId);
1277
+ scheduler.cancel(this.currentId);
1192
1278
  this.currentId = EMPTY;
1193
1279
  }
1194
1280
  };
1195
1281
  disposeEffect = () => {
1196
- return this.clear;
1282
+ return this.cancel;
1197
1283
  };
1198
1284
  }
1199
1285
 
1200
1286
  /**
1201
- * A `setTimeout` with automatic cleanup and guard.
1287
+ * A `requestAnimationFrame` with automatic cleanup and guard.
1202
1288
  */
1203
- function useTimeout() {
1204
- const timeout = useRefWithInit(Timeout.create).current;
1289
+ function useAnimationFrame() {
1290
+ const timeout = useRefWithInit(AnimationFrame.create).current;
1205
1291
  useOnMount(timeout.disposeEffect);
1206
1292
  return timeout;
1207
1293
  }
1208
1294
 
1209
- let originalHtmlStyles = {};
1210
- let originalBodyStyles = {};
1211
- let originalHtmlScrollBehavior = '';
1212
- function hasInsetScrollbars(referenceElement) {
1213
- if (typeof document === 'undefined') {
1214
- return false;
1215
- }
1216
- const doc = ownerDocument(referenceElement);
1217
- const win = getWindow(doc);
1218
- return win.innerWidth - doc.documentElement.clientWidth > 0;
1219
- }
1220
- function supportsStableScrollbarGutter(referenceElement) {
1221
- const supported = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('scrollbar-gutter', 'stable');
1222
- if (!supported || typeof document === 'undefined') {
1223
- return false;
1224
- }
1225
- const doc = ownerDocument(referenceElement);
1226
- const html = doc.documentElement;
1227
- const body = doc.body;
1228
- const scrollContainer = isOverflowElement(html) ? html : body;
1229
- const originalScrollContainerOverflowY = scrollContainer.style.overflowY;
1230
- const originalHtmlStyleGutter = html.style.scrollbarGutter;
1231
- html.style.scrollbarGutter = 'stable';
1232
- scrollContainer.style.overflowY = 'scroll';
1233
- const before = scrollContainer.offsetWidth;
1234
- scrollContainer.style.overflowY = 'hidden';
1235
- const after = scrollContainer.offsetWidth;
1236
- scrollContainer.style.overflowY = originalScrollContainerOverflowY;
1237
- html.style.scrollbarGutter = originalHtmlStyleGutter;
1238
- return before === after;
1239
- }
1240
- function preventScrollOverlayScrollbars(referenceElement) {
1241
- const doc = ownerDocument(referenceElement);
1242
- const html = doc.documentElement;
1243
- const body = doc.body;
1295
+ const visuallyHiddenBase = {
1296
+ clipPath: 'inset(50%)',
1297
+ overflow: 'hidden',
1298
+ whiteSpace: 'nowrap',
1299
+ border: 0,
1300
+ padding: 0,
1301
+ width: 1,
1302
+ height: 1,
1303
+ margin: -1
1304
+ };
1305
+ const visuallyHidden = {
1306
+ ...visuallyHiddenBase,
1307
+ position: 'fixed',
1308
+ top: 0,
1309
+ left: 0
1310
+ };
1311
+ const visuallyHiddenInput = {
1312
+ ...visuallyHiddenBase,
1313
+ position: 'absolute'
1314
+ };
1244
1315
 
1245
- // If an `overflow` style is present on <html>, we need to lock it, because a lock on <body>
1246
- // won't have any effect.
1247
- // But if <body> has an `overflow` style (like `overflow-x: hidden`), we need to lock it
1248
- // instead, as sticky elements shift otherwise.
1249
- const elementToLock = isOverflowElement(html) ? html : body;
1250
- const originalElementToLockStyles = {
1251
- overflowY: elementToLock.style.overflowY,
1252
- overflowX: elementToLock.style.overflowX
1316
+ const FocusGuard = /*#__PURE__*/React.forwardRef(function FocusGuard(props, ref) {
1317
+ const [role, setRole] = React.useState();
1318
+ useIsoLayoutEffect(() => {
1319
+ if (isSafari) {
1320
+ // Unlike other screen readers such as NVDA and JAWS, the virtual cursor
1321
+ // on VoiceOver does trigger the onFocus event, so we can use the focus
1322
+ // trap element. On Safari, only buttons trigger the onFocus event.
1323
+ setRole('button');
1324
+ }
1325
+ }, []);
1326
+ const restProps = {
1327
+ tabIndex: 0,
1328
+ // Role is only for VoiceOver
1329
+ role
1253
1330
  };
1254
- Object.assign(elementToLock.style, {
1255
- overflowY: 'hidden',
1256
- overflowX: 'hidden'
1331
+ return /*#__PURE__*/jsx("span", {
1332
+ ...props,
1333
+ ref: ref,
1334
+ style: visuallyHidden,
1335
+ "aria-hidden": role ? undefined : true,
1336
+ ...restProps,
1337
+ "data-base-ui-focus-guard": ""
1257
1338
  });
1258
- return () => {
1259
- Object.assign(elementToLock.style, originalElementToLockStyles);
1260
- };
1339
+ });
1340
+ if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1341
+
1342
+ function createAttribute(name) {
1343
+ return `data-base-ui-${name}`;
1261
1344
  }
1262
- function preventScrollInsetScrollbars(referenceElement) {
1263
- const doc = ownerDocument(referenceElement);
1264
- const html = doc.documentElement;
1265
- const body = doc.body;
1266
- const win = getWindow(html);
1267
- let scrollTop = 0;
1268
- let scrollLeft = 0;
1269
- let updateGutterOnly = false;
1270
- const resizeFrame = AnimationFrame.create();
1271
1345
 
1272
- // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom.
1273
- if (isWebKit && (win.visualViewport?.scale ?? 1) !== 1) {
1274
- return () => {};
1346
+ let rafId = 0;
1347
+ function enqueueFocus(el, options = {}) {
1348
+ const {
1349
+ preventScroll = false,
1350
+ cancelPrevious = true,
1351
+ sync = false
1352
+ } = options;
1353
+ if (cancelPrevious) {
1354
+ cancelAnimationFrame(rafId);
1275
1355
  }
1276
- function lockScroll() {
1277
- /* DOM reads: */
1278
-
1279
- const htmlStyles = win.getComputedStyle(html);
1280
- const bodyStyles = win.getComputedStyle(body);
1281
- const htmlScrollbarGutterValue = htmlStyles.scrollbarGutter || '';
1282
- const hasBothEdges = htmlScrollbarGutterValue.includes('both-edges');
1283
- const scrollbarGutterValue = hasBothEdges ? 'stable both-edges' : 'stable';
1284
- scrollTop = html.scrollTop;
1285
- scrollLeft = html.scrollLeft;
1286
- originalHtmlStyles = {
1287
- scrollbarGutter: html.style.scrollbarGutter,
1288
- overflowY: html.style.overflowY,
1289
- overflowX: html.style.overflowX
1290
- };
1291
- originalHtmlScrollBehavior = html.style.scrollBehavior;
1292
- originalBodyStyles = {
1293
- position: body.style.position,
1294
- height: body.style.height,
1295
- width: body.style.width,
1296
- boxSizing: body.style.boxSizing,
1297
- overflowY: body.style.overflowY,
1298
- overflowX: body.style.overflowX,
1299
- scrollBehavior: body.style.scrollBehavior
1300
- };
1301
- const isScrollableY = html.scrollHeight > html.clientHeight;
1302
- const isScrollableX = html.scrollWidth > html.clientWidth;
1303
- const hasConstantOverflowY = htmlStyles.overflowY === 'scroll' || bodyStyles.overflowY === 'scroll';
1304
- const hasConstantOverflowX = htmlStyles.overflowX === 'scroll' || bodyStyles.overflowX === 'scroll';
1305
-
1306
- // Values can be negative in Firefox
1307
- const scrollbarWidth = Math.max(0, win.innerWidth - body.clientWidth);
1308
- const scrollbarHeight = Math.max(0, win.innerHeight - body.clientHeight);
1309
-
1310
- // Avoid shift due to the default <body> margin. This does cause elements to be clipped
1311
- // with whitespace. Warn if <body> has margins?
1312
- const marginY = parseFloat(bodyStyles.marginTop) + parseFloat(bodyStyles.marginBottom);
1313
- const marginX = parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight);
1314
- const elementToLock = isOverflowElement(html) ? html : body;
1315
- updateGutterOnly = supportsStableScrollbarGutter(referenceElement);
1316
-
1317
- /*
1318
- * DOM writes:
1319
- * Do not read the DOM past this point!
1320
- */
1321
-
1322
- if (updateGutterOnly) {
1323
- html.style.scrollbarGutter = scrollbarGutterValue;
1324
- elementToLock.style.overflowY = 'hidden';
1325
- elementToLock.style.overflowX = 'hidden';
1326
- return;
1327
- }
1328
- Object.assign(html.style, {
1329
- scrollbarGutter: scrollbarGutterValue,
1330
- overflowY: 'hidden',
1331
- overflowX: 'hidden'
1332
- });
1333
- if (isScrollableY || hasConstantOverflowY) {
1334
- html.style.overflowY = 'scroll';
1335
- }
1336
- if (isScrollableX || hasConstantOverflowX) {
1337
- html.style.overflowX = 'scroll';
1338
- }
1339
- Object.assign(body.style, {
1340
- position: 'relative',
1341
- height: marginY || scrollbarHeight ? `calc(100dvh - ${marginY + scrollbarHeight}px)` : '100dvh',
1342
- width: marginX || scrollbarWidth ? `calc(100vw - ${marginX + scrollbarWidth}px)` : '100vw',
1343
- boxSizing: 'border-box',
1344
- overflow: 'hidden',
1345
- scrollBehavior: 'unset'
1346
- });
1347
- body.scrollTop = scrollTop;
1348
- body.scrollLeft = scrollLeft;
1349
- html.setAttribute('data-base-ui-scroll-locked', '');
1350
- html.style.scrollBehavior = 'unset';
1351
- }
1352
- function cleanup() {
1353
- Object.assign(html.style, originalHtmlStyles);
1354
- Object.assign(body.style, originalBodyStyles);
1355
- if (!updateGutterOnly) {
1356
- html.scrollTop = scrollTop;
1357
- html.scrollLeft = scrollLeft;
1358
- html.removeAttribute('data-base-ui-scroll-locked');
1359
- html.style.scrollBehavior = originalHtmlScrollBehavior;
1360
- }
1361
- }
1362
- function handleResize() {
1363
- cleanup();
1364
- resizeFrame.request(lockScroll);
1365
- }
1366
- lockScroll();
1367
- win.addEventListener('resize', handleResize);
1368
- return () => {
1369
- resizeFrame.cancel();
1370
- cleanup();
1371
- // Sometimes this cleanup can be run after test teardown
1372
- // because it is called in a `setTimeout(fn, 0)`,
1373
- // in which case `removeEventListener` wouldn't be available,
1374
- // so we check for it to avoid test failures.
1375
- if (typeof win.removeEventListener === 'function') {
1376
- win.removeEventListener('resize', handleResize);
1377
- }
1378
- };
1379
- }
1380
- class ScrollLocker {
1381
- lockCount = 0;
1382
- restore = null;
1383
- timeoutLock = Timeout.create();
1384
- timeoutUnlock = Timeout.create();
1385
- acquire(referenceElement) {
1386
- this.lockCount += 1;
1387
- if (this.lockCount === 1 && this.restore === null) {
1388
- this.timeoutLock.start(0, () => this.lock(referenceElement));
1389
- }
1390
- return this.release;
1391
- }
1392
- release = () => {
1393
- this.lockCount -= 1;
1394
- if (this.lockCount === 0 && this.restore) {
1395
- this.timeoutUnlock.start(0, this.unlock);
1396
- }
1397
- };
1398
- unlock = () => {
1399
- if (this.lockCount === 0 && this.restore) {
1400
- this.restore?.();
1401
- this.restore = null;
1402
- }
1403
- };
1404
- lock(referenceElement) {
1405
- if (this.lockCount === 0 || this.restore !== null) {
1406
- return;
1407
- }
1408
- const doc = ownerDocument(referenceElement);
1409
- const html = doc.documentElement;
1410
- const htmlOverflowY = getWindow(html).getComputedStyle(html).overflowY;
1411
-
1412
- // If the site author already hid overflow on <html>, respect it and bail out.
1413
- if (htmlOverflowY === 'hidden' || htmlOverflowY === 'clip') {
1414
- this.restore = NOOP;
1415
- return;
1416
- }
1417
- const hasOverlayScrollbars = isIOS || !hasInsetScrollbars(referenceElement);
1418
-
1419
- // On iOS, scroll locking does not work if the navbar is collapsed. Due to numerous
1420
- // side effects and bugs that arise on iOS, it must be researched extensively before
1421
- // being enabled to ensure it doesn't cause the following issues:
1422
- // - Textboxes must scroll into view when focused, nor cause a glitchy scroll animation.
1423
- // - The navbar must not force itself into view and cause layout shift.
1424
- // - Scroll containers must not flicker upon closing a popup when it has an exit animation.
1425
- this.restore = hasOverlayScrollbars ? preventScrollOverlayScrollbars(referenceElement) : preventScrollInsetScrollbars(referenceElement);
1426
- }
1427
- }
1428
- const SCROLL_LOCKER = new ScrollLocker();
1429
-
1430
- /**
1431
- * Locks the scroll of the document when enabled.
1432
- *
1433
- * @param enabled - Whether to enable the scroll lock.
1434
- * @param referenceElement - Element to use as a reference for lock calculations.
1435
- */
1436
- function useScrollLock(enabled = true, referenceElement = null) {
1437
- useIsoLayoutEffect(() => {
1438
- if (!enabled) {
1439
- return undefined;
1440
- }
1441
- return SCROLL_LOCKER.acquire(referenceElement);
1442
- }, [enabled, referenceElement]);
1443
- }
1444
-
1445
- /**
1446
- * Untracks the provided value by turning it into a ref to remove its reactivity.
1447
- *
1448
- * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
1449
- */
1450
- function useValueAsRef(value) {
1451
- const latest = useRefWithInit(createLatestRef, value).current;
1452
- latest.next = value;
1453
-
1454
- // eslint-disable-next-line react-hooks/exhaustive-deps
1455
- useIsoLayoutEffect(latest.effect);
1456
- return latest;
1457
- }
1458
- function createLatestRef(value) {
1459
- const latest = {
1460
- current: value,
1461
- next: value,
1462
- effect: () => {
1463
- latest.current = latest.next;
1464
- }
1465
- };
1466
- return latest;
1467
- }
1468
-
1469
- const visuallyHiddenBase = {
1470
- clipPath: 'inset(50%)',
1471
- overflow: 'hidden',
1472
- whiteSpace: 'nowrap',
1473
- border: 0,
1474
- padding: 0,
1475
- width: 1,
1476
- height: 1,
1477
- margin: -1
1478
- };
1479
- const visuallyHidden = {
1480
- ...visuallyHiddenBase,
1481
- position: 'fixed',
1482
- top: 0,
1483
- left: 0
1484
- };
1485
- const visuallyHiddenInput = {
1486
- ...visuallyHiddenBase,
1487
- position: 'absolute'
1488
- };
1489
-
1490
- const FocusGuard = /*#__PURE__*/React.forwardRef(function FocusGuard(props, ref) {
1491
- const [role, setRole] = React.useState();
1492
- useIsoLayoutEffect(() => {
1493
- if (isSafari) {
1494
- // Unlike other screen readers such as NVDA and JAWS, the virtual cursor
1495
- // on VoiceOver does trigger the onFocus event, so we can use the focus
1496
- // trap element. On Safari, only buttons trigger the onFocus event.
1497
- setRole('button');
1498
- }
1499
- }, []);
1500
- const restProps = {
1501
- tabIndex: 0,
1502
- // Role is only for VoiceOver
1503
- role
1504
- };
1505
- return /*#__PURE__*/jsx("span", {
1506
- ...props,
1507
- ref: ref,
1508
- style: visuallyHidden,
1509
- "aria-hidden": role ? undefined : true,
1510
- ...restProps,
1511
- "data-base-ui-focus-guard": ""
1512
- });
1513
- });
1514
- if (process.env.NODE_ENV !== "production") FocusGuard.displayName = "FocusGuard";
1515
-
1516
- function createAttribute(name) {
1517
- return `data-base-ui-${name}`;
1518
- }
1519
-
1520
- let rafId = 0;
1521
- function enqueueFocus(el, options = {}) {
1522
- const {
1523
- preventScroll = false,
1524
- cancelPrevious = true,
1525
- sync = false
1526
- } = options;
1527
- if (cancelPrevious) {
1528
- cancelAnimationFrame(rafId);
1529
- }
1530
- const exec = () => el?.focus({
1531
- preventScroll
1532
- });
1533
- if (sync) {
1534
- exec();
1535
- } else {
1536
- rafId = requestAnimationFrame(exec);
1537
- }
1538
- }
1356
+ const exec = () => el?.focus({
1357
+ preventScroll
1358
+ });
1359
+ if (sync) {
1360
+ exec();
1361
+ } else {
1362
+ rafId = requestAnimationFrame(exec);
1363
+ }
1364
+ }
1539
1365
 
1540
1366
  // Modified to add conditional `aria-hidden` support:
1541
1367
  // https://github.com/theKashey/aria-hidden/blob/9220c8f4a4fd35f63bee5510a9f41a37264382d4/src/index.ts
@@ -1899,13 +1725,33 @@ function createEventEmitter() {
1899
1725
  };
1900
1726
  }
1901
1727
 
1902
- const FloatingNodeContext = /*#__PURE__*/React.createContext(null);
1903
- if (process.env.NODE_ENV !== "production") FloatingNodeContext.displayName = "FloatingNodeContext";
1904
- const FloatingTreeContext = /*#__PURE__*/React.createContext(null);
1905
-
1906
1728
  /**
1907
- * Returns the parent node id for nested floating elements, if available.
1908
- * Returns `null` for top-level floating elements.
1729
+ * Stores and manages floating elements in a tree structure.
1730
+ * This is a backing store for the `FloatingTree` component.
1731
+ */
1732
+ class FloatingTreeStore {
1733
+ nodesRef = {
1734
+ current: []
1735
+ };
1736
+ events = createEventEmitter();
1737
+ addNode(node) {
1738
+ this.nodesRef.current.push(node);
1739
+ }
1740
+ removeNode(node) {
1741
+ const index = this.nodesRef.current.findIndex(n => n === node);
1742
+ if (index !== -1) {
1743
+ this.nodesRef.current.splice(index, 1);
1744
+ }
1745
+ }
1746
+ }
1747
+
1748
+ const FloatingNodeContext = /*#__PURE__*/React.createContext(null);
1749
+ if (process.env.NODE_ENV !== "production") FloatingNodeContext.displayName = "FloatingNodeContext";
1750
+ const FloatingTreeContext = /*#__PURE__*/React.createContext(null);
1751
+
1752
+ /**
1753
+ * Returns the parent node id for nested floating elements, if available.
1754
+ * Returns `null` for top-level floating elements.
1909
1755
  */
1910
1756
  if (process.env.NODE_ENV !== "production") FloatingTreeContext.displayName = "FloatingTreeContext";
1911
1757
  const useFloatingParentNodeId = () => React.useContext(FloatingNodeContext)?.id || null;
@@ -1918,6 +1764,82 @@ const useFloatingTree = externalTree => {
1918
1764
  return externalTree ?? contextTree;
1919
1765
  };
1920
1766
 
1767
+ /**
1768
+ * Registers a node into the `FloatingTree`, returning its id.
1769
+ * @see https://floating-ui.com/docs/FloatingTree
1770
+ */
1771
+ function useFloatingNodeId(externalTree) {
1772
+ const id = useId();
1773
+ const tree = useFloatingTree(externalTree);
1774
+ const parentId = useFloatingParentNodeId();
1775
+ useIsoLayoutEffect(() => {
1776
+ if (!id) {
1777
+ return undefined;
1778
+ }
1779
+ const node = {
1780
+ id,
1781
+ parentId
1782
+ };
1783
+ tree?.addNode(node);
1784
+ return () => {
1785
+ tree?.removeNode(node);
1786
+ };
1787
+ }, [tree, id, parentId]);
1788
+ return id;
1789
+ }
1790
+ /**
1791
+ * Provides parent node context for nested floating elements.
1792
+ * @see https://floating-ui.com/docs/FloatingTree
1793
+ * @internal
1794
+ */
1795
+ function FloatingNode(props) {
1796
+ const {
1797
+ children,
1798
+ id
1799
+ } = props;
1800
+ const parentId = useFloatingParentNodeId();
1801
+ return /*#__PURE__*/jsx(FloatingNodeContext.Provider, {
1802
+ value: React.useMemo(() => ({
1803
+ id,
1804
+ parentId
1805
+ }), [id, parentId]),
1806
+ children: children
1807
+ });
1808
+ }
1809
+ /**
1810
+ * Provides context for nested floating elements when they are not children of
1811
+ * each other on the DOM.
1812
+ * This is not necessary in all cases, except when there must be explicit communication between parent and child floating elements. It is necessary for:
1813
+ * - The `bubbles` option in the `useDismiss()` Hook
1814
+ * - Nested virtual list navigation
1815
+ * - Nested floating elements that each open on hover
1816
+ * - Custom communication between parent and child floating elements
1817
+ * @see https://floating-ui.com/docs/FloatingTree
1818
+ * @internal
1819
+ */
1820
+ function FloatingTree(props) {
1821
+ const {
1822
+ children,
1823
+ externalTree
1824
+ } = props;
1825
+ const tree = useRefWithInit(() => externalTree ?? new FloatingTreeStore()).current;
1826
+ return /*#__PURE__*/jsx(FloatingTreeContext.Provider, {
1827
+ value: tree,
1828
+ children: children
1829
+ });
1830
+ }
1831
+
1832
+ /**
1833
+ * If the provided argument is a ref object, returns its `current` value.
1834
+ * Otherwise, returns the argument itself.
1835
+ */
1836
+ function resolveRef(maybeRef) {
1837
+ if (maybeRef == null) {
1838
+ return maybeRef;
1839
+ }
1840
+ return 'current' in maybeRef ? maybeRef.current : maybeRef;
1841
+ }
1842
+
1921
1843
  function getEventType(event, lastInteractionType) {
1922
1844
  const win = getWindow(event.target);
1923
1845
  if (event instanceof win.KeyboardEvent) {
@@ -3476,6 +3398,195 @@ const createSelector = (a, b, c, d, e, f, ...other) => {
3476
3398
  return selector;
3477
3399
  };
3478
3400
 
3401
+ var shim = {exports: {}};
3402
+
3403
+ var useSyncExternalStoreShim_production = {};
3404
+
3405
+ /**
3406
+ * @license React
3407
+ * use-sync-external-store-shim.production.js
3408
+ *
3409
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3410
+ *
3411
+ * This source code is licensed under the MIT license found in the
3412
+ * LICENSE file in the root directory of this source tree.
3413
+ */
3414
+
3415
+ var hasRequiredUseSyncExternalStoreShim_production;
3416
+
3417
+ function requireUseSyncExternalStoreShim_production () {
3418
+ if (hasRequiredUseSyncExternalStoreShim_production) return useSyncExternalStoreShim_production;
3419
+ hasRequiredUseSyncExternalStoreShim_production = 1;
3420
+ var React = React__default;
3421
+ function is(x, y) {
3422
+ return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
3423
+ }
3424
+ var objectIs = "function" === typeof Object.is ? Object.is : is,
3425
+ useState = React.useState,
3426
+ useEffect = React.useEffect,
3427
+ useLayoutEffect = React.useLayoutEffect,
3428
+ useDebugValue = React.useDebugValue;
3429
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
3430
+ var value = getSnapshot(),
3431
+ _useState = useState({ inst: { value: value, getSnapshot: getSnapshot } }),
3432
+ inst = _useState[0].inst,
3433
+ forceUpdate = _useState[1];
3434
+ useLayoutEffect(
3435
+ function () {
3436
+ inst.value = value;
3437
+ inst.getSnapshot = getSnapshot;
3438
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
3439
+ },
3440
+ [subscribe, value, getSnapshot]
3441
+ );
3442
+ useEffect(
3443
+ function () {
3444
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
3445
+ return subscribe(function () {
3446
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
3447
+ });
3448
+ },
3449
+ [subscribe]
3450
+ );
3451
+ useDebugValue(value);
3452
+ return value;
3453
+ }
3454
+ function checkIfSnapshotChanged(inst) {
3455
+ var latestGetSnapshot = inst.getSnapshot;
3456
+ inst = inst.value;
3457
+ try {
3458
+ var nextValue = latestGetSnapshot();
3459
+ return !objectIs(inst, nextValue);
3460
+ } catch (error) {
3461
+ return !0;
3462
+ }
3463
+ }
3464
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
3465
+ return getSnapshot();
3466
+ }
3467
+ var shim =
3468
+ "undefined" === typeof window ||
3469
+ "undefined" === typeof window.document ||
3470
+ "undefined" === typeof window.document.createElement
3471
+ ? useSyncExternalStore$1
3472
+ : useSyncExternalStore$2;
3473
+ useSyncExternalStoreShim_production.useSyncExternalStore =
3474
+ void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;
3475
+ return useSyncExternalStoreShim_production;
3476
+ }
3477
+
3478
+ var useSyncExternalStoreShim_development = {};
3479
+
3480
+ /**
3481
+ * @license React
3482
+ * use-sync-external-store-shim.development.js
3483
+ *
3484
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3485
+ *
3486
+ * This source code is licensed under the MIT license found in the
3487
+ * LICENSE file in the root directory of this source tree.
3488
+ */
3489
+
3490
+ var hasRequiredUseSyncExternalStoreShim_development;
3491
+
3492
+ function requireUseSyncExternalStoreShim_development () {
3493
+ if (hasRequiredUseSyncExternalStoreShim_development) return useSyncExternalStoreShim_development;
3494
+ hasRequiredUseSyncExternalStoreShim_development = 1;
3495
+ "production" !== process.env.NODE_ENV &&
3496
+ (function () {
3497
+ function is(x, y) {
3498
+ return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
3499
+ }
3500
+ function useSyncExternalStore$2(subscribe, getSnapshot) {
3501
+ didWarnOld18Alpha ||
3502
+ void 0 === React.startTransition ||
3503
+ ((didWarnOld18Alpha = !0),
3504
+ console.error(
3505
+ "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
3506
+ ));
3507
+ var value = getSnapshot();
3508
+ if (!didWarnUncachedGetSnapshot) {
3509
+ var cachedValue = getSnapshot();
3510
+ objectIs(value, cachedValue) ||
3511
+ (console.error(
3512
+ "The result of getSnapshot should be cached to avoid an infinite loop"
3513
+ ),
3514
+ (didWarnUncachedGetSnapshot = !0));
3515
+ }
3516
+ cachedValue = useState({
3517
+ inst: { value: value, getSnapshot: getSnapshot }
3518
+ });
3519
+ var inst = cachedValue[0].inst,
3520
+ forceUpdate = cachedValue[1];
3521
+ useLayoutEffect(
3522
+ function () {
3523
+ inst.value = value;
3524
+ inst.getSnapshot = getSnapshot;
3525
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
3526
+ },
3527
+ [subscribe, value, getSnapshot]
3528
+ );
3529
+ useEffect(
3530
+ function () {
3531
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
3532
+ return subscribe(function () {
3533
+ checkIfSnapshotChanged(inst) && forceUpdate({ inst: inst });
3534
+ });
3535
+ },
3536
+ [subscribe]
3537
+ );
3538
+ useDebugValue(value);
3539
+ return value;
3540
+ }
3541
+ function checkIfSnapshotChanged(inst) {
3542
+ var latestGetSnapshot = inst.getSnapshot;
3543
+ inst = inst.value;
3544
+ try {
3545
+ var nextValue = latestGetSnapshot();
3546
+ return !objectIs(inst, nextValue);
3547
+ } catch (error) {
3548
+ return !0;
3549
+ }
3550
+ }
3551
+ function useSyncExternalStore$1(subscribe, getSnapshot) {
3552
+ return getSnapshot();
3553
+ }
3554
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
3555
+ "function" ===
3556
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart &&
3557
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
3558
+ var React = React__default,
3559
+ objectIs = "function" === typeof Object.is ? Object.is : is,
3560
+ useState = React.useState,
3561
+ useEffect = React.useEffect,
3562
+ useLayoutEffect = React.useLayoutEffect,
3563
+ useDebugValue = React.useDebugValue,
3564
+ didWarnOld18Alpha = !1,
3565
+ didWarnUncachedGetSnapshot = !1,
3566
+ shim =
3567
+ "undefined" === typeof window ||
3568
+ "undefined" === typeof window.document ||
3569
+ "undefined" === typeof window.document.createElement
3570
+ ? useSyncExternalStore$1
3571
+ : useSyncExternalStore$2;
3572
+ useSyncExternalStoreShim_development.useSyncExternalStore =
3573
+ void 0 !== React.useSyncExternalStore ? React.useSyncExternalStore : shim;
3574
+ "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
3575
+ "function" ===
3576
+ typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
3577
+ __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
3578
+ })();
3579
+ return useSyncExternalStoreShim_development;
3580
+ }
3581
+
3582
+ if (process.env.NODE_ENV === 'production') {
3583
+ shim.exports = requireUseSyncExternalStoreShim_production();
3584
+ } else {
3585
+ shim.exports = requireUseSyncExternalStoreShim_development();
3586
+ }
3587
+
3588
+ var shimExports = shim.exports;
3589
+
3479
3590
  var withSelector = {exports: {}};
3480
3591
 
3481
3592
  var withSelector_production = {};
@@ -3686,6 +3797,45 @@ if (process.env.NODE_ENV === 'production') {
3686
3797
 
3687
3798
  var withSelectorExports = withSelector.exports;
3688
3799
 
3800
+ const hooks = [];
3801
+ let currentInstance = undefined;
3802
+ function getInstance() {
3803
+ return currentInstance;
3804
+ }
3805
+ function register(hook) {
3806
+ hooks.push(hook);
3807
+ }
3808
+ function fastComponent(fn) {
3809
+ const FastComponent = (props, forwardedRef) => {
3810
+ const instance = useRefWithInit(createInstance).current;
3811
+ let result;
3812
+ try {
3813
+ currentInstance = instance;
3814
+ for (const hook of hooks) {
3815
+ hook.before(instance);
3816
+ }
3817
+ result = fn(props, forwardedRef);
3818
+ for (const hook of hooks) {
3819
+ hook.after(instance);
3820
+ }
3821
+ instance.didInitialize = true;
3822
+ } finally {
3823
+ currentInstance = undefined;
3824
+ }
3825
+ return result;
3826
+ };
3827
+ FastComponent.displayName = fn.displayName || fn.name;
3828
+ return FastComponent;
3829
+ }
3830
+ function fastComponentRef(fn) {
3831
+ return /*#__PURE__*/React.forwardRef(fastComponent(fn));
3832
+ }
3833
+ function createInstance() {
3834
+ return {
3835
+ didInitialize: false
3836
+ };
3837
+ }
3838
+
3689
3839
  /* Some tests fail in R18 with the raw useSyncExternalStore. It may be possible to make it work
3690
3840
  * but for now we only enable it for R19+. */
3691
3841
  const canUseRawUseSyncExternalStore = isReactVersionAtLeast(19);
@@ -3697,11 +3847,91 @@ function useStoreR19(store, selector, a1, a2, a3) {
3697
3847
  const getSelection = React.useCallback(() => selector(store.getSnapshot(), a1, a2, a3), [store, selector, a1, a2, a3]);
3698
3848
  return shimExports.useSyncExternalStore(store.subscribe, getSelection, getSelection);
3699
3849
  }
3850
+ register({
3851
+ before(instance) {
3852
+ instance.syncIndex = 0;
3853
+ if (!instance.didInitialize) {
3854
+ instance.syncTick = 1;
3855
+ instance.syncHooks = [];
3856
+ instance.didChangeStore = true;
3857
+ instance.getSnapshot = () => {
3858
+ let didChange = false;
3859
+ for (let i = 0; i < instance.syncHooks.length; i += 1) {
3860
+ const hook = instance.syncHooks[i];
3861
+ const value = hook.selector(hook.store.state, hook.a1, hook.a2, hook.a3);
3862
+ if (hook.didChange || !Object.is(hook.value, value)) {
3863
+ didChange = true;
3864
+ hook.value = value;
3865
+ hook.didChange = false;
3866
+ }
3867
+ }
3868
+ if (didChange) {
3869
+ instance.syncTick += 1;
3870
+ }
3871
+ return instance.syncTick;
3872
+ };
3873
+ }
3874
+ },
3875
+ after(instance) {
3876
+ if (instance.syncHooks.length > 0) {
3877
+ if (instance.didChangeStore) {
3878
+ instance.didChangeStore = false;
3879
+ instance.subscribe = onStoreChange => {
3880
+ const stores = new Set();
3881
+ for (const hook of instance.syncHooks) {
3882
+ stores.add(hook.store);
3883
+ }
3884
+ const unsubscribes = [];
3885
+ for (const store of stores) {
3886
+ unsubscribes.push(store.subscribe(onStoreChange));
3887
+ }
3888
+ return () => {
3889
+ for (const unsubscribe of unsubscribes) {
3890
+ unsubscribe();
3891
+ }
3892
+ };
3893
+ };
3894
+ }
3895
+ // eslint-disable-next-line react-hooks/rules-of-hooks
3896
+ shimExports.useSyncExternalStore(instance.subscribe, instance.getSnapshot, instance.getSnapshot);
3897
+ }
3898
+ }
3899
+ });
3700
3900
  function useStoreFast(store, selector, a1, a2, a3) {
3701
- {
3901
+ const instance = getInstance();
3902
+ if (!instance) {
3702
3903
  // eslint-disable-next-line react-hooks/rules-of-hooks
3703
3904
  return useStoreR19(store, selector, a1, a2, a3);
3704
3905
  }
3906
+ const index = instance.syncIndex;
3907
+ instance.syncIndex += 1;
3908
+ let hook;
3909
+ if (!instance.didInitialize) {
3910
+ hook = {
3911
+ store,
3912
+ selector,
3913
+ a1,
3914
+ a2,
3915
+ a3,
3916
+ value: selector(store.getSnapshot(), a1, a2, a3),
3917
+ didChange: false
3918
+ };
3919
+ instance.syncHooks.push(hook);
3920
+ } else {
3921
+ hook = instance.syncHooks[index];
3922
+ if (hook.store !== store || hook.selector !== selector || !Object.is(hook.a1, a1) || !Object.is(hook.a2, a2) || !Object.is(hook.a3, a3)) {
3923
+ if (hook.store !== store) {
3924
+ instance.didChangeStore = true;
3925
+ }
3926
+ hook.store = store;
3927
+ hook.selector = selector;
3928
+ hook.a1 = a1;
3929
+ hook.a2 = a2;
3930
+ hook.a3 = a3;
3931
+ hook.didChange = true;
3932
+ }
3933
+ }
3934
+ return hook.value;
3705
3935
  }
3706
3936
  function useStoreLegacy(store, selector, a1, a2, a3) {
3707
3937
  return withSelectorExports.useSyncExternalStoreWithSelector(store.subscribe, store.getSnapshot, store.getSnapshot, state => selector(state, a1, a2, a3));
@@ -4070,40 +4300,212 @@ class FloatingRootStore extends ReactStore {
4070
4300
  }
4071
4301
 
4072
4302
  /**
4073
- * Returns a callback ref that registers/unregisters the trigger element in the store.
4074
- *
4075
- * @param store The Store instance where the trigger should be registered.
4303
+ * Provides a status string for CSS animations.
4304
+ * @param open - a boolean that determines if the element is open.
4305
+ * @param enableIdleState - a boolean that enables the `'idle'` state between `'starting'` and `'ending'`
4076
4306
  */
4077
- function useTriggerRegistration(id, store) {
4078
- // Keep track of the currently registered element to unregister it on unmount or id change.
4079
- const registeredElementIdRef = React.useRef(null);
4080
- const registeredElementRef = React.useRef(null);
4081
- return React.useCallback(element => {
4082
- if (id === undefined) {
4083
- return;
4307
+ function useTransitionStatus(open, enableIdleState = false, deferEndingState = false) {
4308
+ const [transitionStatus, setTransitionStatus] = React.useState(open && enableIdleState ? 'idle' : undefined);
4309
+ const [mounted, setMounted] = React.useState(open);
4310
+ if (open && !mounted) {
4311
+ setMounted(true);
4312
+ setTransitionStatus('starting');
4313
+ }
4314
+ if (!open && mounted && transitionStatus !== 'ending' && !deferEndingState) {
4315
+ setTransitionStatus('ending');
4316
+ }
4317
+ if (!open && !mounted && transitionStatus === 'ending') {
4318
+ setTransitionStatus(undefined);
4319
+ }
4320
+ useIsoLayoutEffect(() => {
4321
+ if (!open && mounted && transitionStatus !== 'ending' && deferEndingState) {
4322
+ const frame = AnimationFrame.request(() => {
4323
+ setTransitionStatus('ending');
4324
+ });
4325
+ return () => {
4326
+ AnimationFrame.cancel(frame);
4327
+ };
4084
4328
  }
4085
- if (registeredElementIdRef.current !== null) {
4086
- const registeredId = registeredElementIdRef.current;
4087
- const registeredElement = registeredElementRef.current;
4088
- const currentElement = store.context.triggerElements.getById(registeredId);
4089
- if (registeredElement && currentElement === registeredElement) {
4090
- store.context.triggerElements.delete(registeredId);
4091
- }
4092
- registeredElementIdRef.current = null;
4093
- registeredElementRef.current = null;
4329
+ return undefined;
4330
+ }, [open, mounted, transitionStatus, deferEndingState]);
4331
+ useIsoLayoutEffect(() => {
4332
+ if (!open || enableIdleState) {
4333
+ return undefined;
4094
4334
  }
4095
- if (element !== null) {
4096
- registeredElementIdRef.current = id;
4097
- registeredElementRef.current = element;
4098
- store.context.triggerElements.add(id, element);
4335
+ const frame = AnimationFrame.request(() => {
4336
+ // Avoid `flushSync` here due to Firefox.
4337
+ // See https://github.com/mui/base-ui/pull/3424
4338
+ setTransitionStatus(undefined);
4339
+ });
4340
+ return () => {
4341
+ AnimationFrame.cancel(frame);
4342
+ };
4343
+ }, [enableIdleState, open]);
4344
+ useIsoLayoutEffect(() => {
4345
+ if (!open || !enableIdleState) {
4346
+ return undefined;
4099
4347
  }
4100
- }, [store, id]);
4348
+ if (open && mounted && transitionStatus !== 'idle') {
4349
+ setTransitionStatus('starting');
4350
+ }
4351
+ const frame = AnimationFrame.request(() => {
4352
+ setTransitionStatus('idle');
4353
+ });
4354
+ return () => {
4355
+ AnimationFrame.cancel(frame);
4356
+ };
4357
+ }, [enableIdleState, open, mounted, setTransitionStatus, transitionStatus]);
4358
+ return React.useMemo(() => ({
4359
+ mounted,
4360
+ setMounted,
4361
+ transitionStatus
4362
+ }), [mounted, transitionStatus]);
4101
4363
  }
4102
4364
 
4103
4365
  /**
4104
- * Sets up trigger data forwarding to the store.
4105
- *
4106
- * @param triggerId Id of the trigger.
4366
+ * Executes a function once all animations have finished on the provided element.
4367
+ * @param elementOrRef - The element to watch for animations.
4368
+ * @param waitForStartingStyleRemoved - Whether to wait for [data-starting-style] to be removed before checking for animations.
4369
+ * @param treatAbortedAsFinished - Whether to treat aborted animations as finished. If `false`, and there are aborted animations,
4370
+ * the function will check again if any new animations have started and wait for them to finish.
4371
+ * @returns A function that takes a callback to execute once all animations have finished, and an optional AbortSignal to abort the callback
4372
+ */
4373
+ function useAnimationsFinished(elementOrRef, waitForStartingStyleRemoved = false, treatAbortedAsFinished = true) {
4374
+ const frame = useAnimationFrame();
4375
+ return useStableCallback((fnToExecute,
4376
+ /**
4377
+ * An optional [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that
4378
+ * can be used to abort `fnToExecute` before all the animations have finished.
4379
+ * @default null
4380
+ */
4381
+ signal = null) => {
4382
+ frame.cancel();
4383
+ function done() {
4384
+ // Synchronously flush the unmounting of the component so that the browser doesn't
4385
+ // paint: https://github.com/mui/base-ui/issues/979
4386
+ ReactDOM.flushSync(fnToExecute);
4387
+ }
4388
+ const element = resolveRef(elementOrRef);
4389
+ if (element == null) {
4390
+ return;
4391
+ }
4392
+ const resolvedElement = element;
4393
+ if (typeof resolvedElement.getAnimations !== 'function' || globalThis.BASE_UI_ANIMATIONS_DISABLED) {
4394
+ fnToExecute();
4395
+ } else {
4396
+ function execWaitForStartingStyleRemoved() {
4397
+ const startingStyleAttribute = TransitionStatusDataAttributes.startingStyle;
4398
+
4399
+ // If `[data-starting-style]` isn't present, fall back to waiting one more frame
4400
+ // to give "open" animations a chance to be registered.
4401
+ if (!resolvedElement.hasAttribute(startingStyleAttribute)) {
4402
+ frame.request(exec);
4403
+ return;
4404
+ }
4405
+
4406
+ // Wait for `[data-starting-style]` to have been removed.
4407
+ const attributeObserver = new MutationObserver(() => {
4408
+ if (!resolvedElement.hasAttribute(startingStyleAttribute)) {
4409
+ attributeObserver.disconnect();
4410
+ exec();
4411
+ }
4412
+ });
4413
+ attributeObserver.observe(resolvedElement, {
4414
+ attributes: true,
4415
+ attributeFilter: [startingStyleAttribute]
4416
+ });
4417
+ signal?.addEventListener('abort', () => attributeObserver.disconnect(), {
4418
+ once: true
4419
+ });
4420
+ }
4421
+ function exec() {
4422
+ Promise.all(resolvedElement.getAnimations().map(anim => anim.finished)).then(() => {
4423
+ if (signal?.aborted) {
4424
+ return;
4425
+ }
4426
+ done();
4427
+ }).catch(() => {
4428
+ const currentAnimations = resolvedElement.getAnimations();
4429
+ if (treatAbortedAsFinished) {
4430
+ if (signal?.aborted) {
4431
+ return;
4432
+ }
4433
+ done();
4434
+ } else if (currentAnimations.length > 0 && currentAnimations.some(anim => anim.pending || anim.playState !== 'finished')) {
4435
+ // Sometimes animations can be aborted because a property they depend on changes while the animation plays.
4436
+ // In such cases, we need to re-check if any new animations have started.
4437
+ exec();
4438
+ }
4439
+ });
4440
+ }
4441
+ if (waitForStartingStyleRemoved) {
4442
+ execWaitForStartingStyleRemoved();
4443
+ return;
4444
+ }
4445
+ frame.request(exec);
4446
+ }
4447
+ });
4448
+ }
4449
+
4450
+ /**
4451
+ * Calls the provided function when the CSS open/close animation or transition completes.
4452
+ */
4453
+ function useOpenChangeComplete(parameters) {
4454
+ const {
4455
+ enabled = true,
4456
+ open,
4457
+ ref,
4458
+ onComplete: onCompleteParam
4459
+ } = parameters;
4460
+ const onComplete = useStableCallback(onCompleteParam);
4461
+ const runOnceAnimationsFinish = useAnimationsFinished(ref, open, false);
4462
+ React.useEffect(() => {
4463
+ if (!enabled) {
4464
+ return undefined;
4465
+ }
4466
+ const abortController = new AbortController();
4467
+ runOnceAnimationsFinish(onComplete, abortController.signal);
4468
+ return () => {
4469
+ abortController.abort();
4470
+ };
4471
+ }, [enabled, open, onComplete, runOnceAnimationsFinish]);
4472
+ }
4473
+
4474
+ /**
4475
+ * Returns a callback ref that registers/unregisters the trigger element in the store.
4476
+ *
4477
+ * @param store The Store instance where the trigger should be registered.
4478
+ */
4479
+ function useTriggerRegistration(id, store) {
4480
+ // Keep track of the currently registered element to unregister it on unmount or id change.
4481
+ const registeredElementIdRef = React.useRef(null);
4482
+ const registeredElementRef = React.useRef(null);
4483
+ return React.useCallback(element => {
4484
+ if (id === undefined) {
4485
+ return;
4486
+ }
4487
+ if (registeredElementIdRef.current !== null) {
4488
+ const registeredId = registeredElementIdRef.current;
4489
+ const registeredElement = registeredElementRef.current;
4490
+ const currentElement = store.context.triggerElements.getById(registeredId);
4491
+ if (registeredElement && currentElement === registeredElement) {
4492
+ store.context.triggerElements.delete(registeredId);
4493
+ }
4494
+ registeredElementIdRef.current = null;
4495
+ registeredElementRef.current = null;
4496
+ }
4497
+ if (element !== null) {
4498
+ registeredElementIdRef.current = id;
4499
+ registeredElementRef.current = element;
4500
+ store.context.triggerElements.add(id, element);
4501
+ }
4502
+ }, [store, id]);
4503
+ }
4504
+
4505
+ /**
4506
+ * Sets up trigger data forwarding to the store.
4507
+ *
4508
+ * @param triggerId Id of the trigger.
4107
4509
  * @param triggerElement The trigger DOM element.
4108
4510
  * @param store The Store instance managing the popup state.
4109
4511
  * @param stateUpdates An object with state updates to apply when the trigger is active.
@@ -4204,6 +4606,7 @@ function useOpenStateTransitions(open, store, onUnmount) {
4204
4606
  activeTriggerElement: null,
4205
4607
  mounted: false
4206
4608
  });
4609
+ onUnmount?.();
4207
4610
  store.context.onOpenChangeComplete?.(false);
4208
4611
  });
4209
4612
  const preventUnmountingOnClose = store.useState('preventUnmountingOnClose');
@@ -4625,1083 +5028,426 @@ function useRole(context, props = {}) {
4625
5028
  }), [reference, floating, trigger, item]);
4626
5029
  }
4627
5030
 
4628
- /**
4629
- * Provides a cross-browser way to determine the type of the pointer used to click.
4630
- * Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.
4631
- * Additionally, this implementation detects if the click was triggered by the keyboard.
4632
- *
4633
- * @param handler The function to be called when the button is clicked. The first parameter is the original event and the second parameter is the pointer type.
4634
- */
4635
- function useEnhancedClickHandler(handler) {
4636
- const lastClickInteractionTypeRef = React.useRef('');
4637
- const handlePointerDown = React.useCallback(event => {
4638
- if (event.defaultPrevented) {
4639
- return;
4640
- }
4641
- lastClickInteractionTypeRef.current = event.pointerType;
4642
- handler(event, event.pointerType);
4643
- }, [handler]);
4644
- const handleClick = React.useCallback(event => {
4645
- // event.detail has the number of clicks performed on the element. 0 means it was triggered by the keyboard.
4646
- if (event.detail === 0) {
4647
- handler(event, 'keyboard');
4648
- return;
4649
- }
4650
- if ('pointerType' in event) {
4651
- // Chrome and Edge correctly use PointerEvent
4652
- handler(event, event.pointerType);
4653
- } else {
4654
- handler(event, lastClickInteractionTypeRef.current);
4655
- }
4656
- lastClickInteractionTypeRef.current = '';
4657
- }, [handler]);
4658
- return {
4659
- onClick: handleClick,
4660
- onPointerDown: handlePointerDown
4661
- };
4662
- }
5031
+ let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
5032
+ /**
5033
+ * Present when the drawer is open.
5034
+ */
5035
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["open"] = CommonPopupDataAttributes.open] = "open";
5036
+ /**
5037
+ * Present when the drawer is closed.
5038
+ */
5039
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["closed"] = CommonPopupDataAttributes.closed] = "closed";
5040
+ /**
5041
+ * Present when the drawer is animating in.
5042
+ */
5043
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["startingStyle"] = CommonPopupDataAttributes.startingStyle] = "startingStyle";
5044
+ /**
5045
+ * Present when the drawer is animating out.
5046
+ */
5047
+ DrawerPopupDataAttributes[DrawerPopupDataAttributes["endingStyle"] = CommonPopupDataAttributes.endingStyle] = "endingStyle";
5048
+ /**
5049
+ * Present when the drawer is at the expanded (full-height) snap point.
5050
+ */
5051
+ DrawerPopupDataAttributes["expanded"] = "data-expanded";
5052
+ /**
5053
+ * Present when a nested drawer is open.
5054
+ */
5055
+ DrawerPopupDataAttributes["nestedDrawerOpen"] = "data-nested-drawer-open";
5056
+ /**
5057
+ * Present when a nested drawer is being swiped.
5058
+ */
5059
+ DrawerPopupDataAttributes["nestedDrawerSwiping"] = "data-nested-drawer-swiping";
5060
+ /**
5061
+ * Present when the drawer is dismissed by swiping.
5062
+ */
5063
+ DrawerPopupDataAttributes["swipeDismiss"] = "data-swipe-dismiss";
5064
+ /**
5065
+ * Indicates the swipe direction.
5066
+ * @type {'up' | 'down' | 'left' | 'right'}
5067
+ */
5068
+ DrawerPopupDataAttributes["swipeDirection"] = "data-swipe-direction";
5069
+ /**
5070
+ * Present when the drawer is being swiped.
5071
+ */
5072
+ DrawerPopupDataAttributes["swiping"] = "data-swiping";
5073
+ return DrawerPopupDataAttributes;
5074
+ }({});
4663
5075
 
4664
- function useValueChanged(value, onChange) {
4665
- const valueRef = React.useRef(value);
4666
- const onChangeCallback = useStableCallback(onChange);
4667
- useIsoLayoutEffect(() => {
4668
- if (valueRef.current === value) {
4669
- return;
4670
- }
4671
- onChangeCallback(valueRef.current);
4672
- }, [value, onChangeCallback]);
4673
- useIsoLayoutEffect(() => {
4674
- valueRef.current = value;
4675
- }, [value]);
5076
+ const DialogPortalContext = /*#__PURE__*/React.createContext(undefined);
5077
+ if (process.env.NODE_ENV !== "production") DialogPortalContext.displayName = "DialogPortalContext";
5078
+ function useDialogPortalContext() {
5079
+ const value = React.useContext(DialogPortalContext);
5080
+ if (value === undefined) {
5081
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Dialog.Portal> is missing.' : formatErrorMessage(26));
5082
+ }
5083
+ return value;
4676
5084
  }
4677
5085
 
4678
- /**
4679
- * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.
4680
- *
4681
- * @param open The open state of the component.
4682
- */
4683
- function useOpenInteractionType(open) {
4684
- const [openMethod, setOpenMethod] = React.useState(null);
4685
- const handleTriggerClick = useStableCallback((_, interactionType) => {
4686
- if (!open) {
4687
- setOpenMethod(interactionType || (
4688
- // On iOS Safari, the hitslop around touch targets means tapping outside an element's
4689
- // bounds does not fire `pointerdown` but does fire `mousedown`. The `interactionType`
4690
- // will be "" in that case.
4691
- isIOS ? 'touch' : ''));
5086
+ const ARROW_UP = 'ArrowUp';
5087
+ const ARROW_DOWN = 'ArrowDown';
5088
+ const ARROW_LEFT = 'ArrowLeft';
5089
+ const ARROW_RIGHT = 'ArrowRight';
5090
+ const HOME = 'Home';
5091
+ const END = 'End';
5092
+ const HORIZONTAL_KEYS = new Set([ARROW_LEFT, ARROW_RIGHT]);
5093
+ const VERTICAL_KEYS = new Set([ARROW_UP, ARROW_DOWN]);
5094
+ const ARROW_KEYS = new Set([...HORIZONTAL_KEYS, ...VERTICAL_KEYS]);
5095
+ new Set([...ARROW_KEYS, HOME, END]);
5096
+ const COMPOSITE_KEYS = new Set([ARROW_UP, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, HOME, END]);
5097
+
5098
+ const DrawerRootContext = /*#__PURE__*/React.createContext(undefined);
5099
+ if (process.env.NODE_ENV !== "production") DrawerRootContext.displayName = "DrawerRootContext";
5100
+ function useDrawerRootContext(optional) {
5101
+ const drawerRootContext = React.useContext(DrawerRootContext);
5102
+ if (optional === false && drawerRootContext === undefined) {
5103
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.' : formatErrorMessage(90));
5104
+ }
5105
+ return drawerRootContext;
5106
+ }
5107
+
5108
+ function clamp(val, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
5109
+ return Math.max(min, Math.min(val, max));
5110
+ }
5111
+
5112
+ function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
5113
+ if (!Number.isFinite(viewportHeight) || viewportHeight <= 0) {
5114
+ return null;
5115
+ }
5116
+ if (typeof snapPoint === 'number') {
5117
+ if (!Number.isFinite(snapPoint)) {
5118
+ return null;
4692
5119
  }
4693
- });
4694
- useValueChanged(open, previousOpen => {
4695
- if (previousOpen && !open) {
4696
- setOpenMethod(null);
5120
+ if (snapPoint <= 1) {
5121
+ return clamp(snapPoint, 0, 1) * viewportHeight;
4697
5122
  }
4698
- });
4699
- const {
4700
- onClick,
4701
- onPointerDown
4702
- } = useEnhancedClickHandler(handleTriggerClick);
4703
- return React.useMemo(() => ({
4704
- openMethod,
4705
- triggerProps: {
4706
- onClick,
4707
- onPointerDown
5123
+ return snapPoint;
5124
+ }
5125
+ const trimmed = snapPoint.trim();
5126
+ if (trimmed.endsWith('px')) {
5127
+ const value = Number.parseFloat(trimmed);
5128
+ return Number.isFinite(value) ? value : null;
5129
+ }
5130
+ if (trimmed.endsWith('rem')) {
5131
+ const value = Number.parseFloat(trimmed);
5132
+ return Number.isFinite(value) ? value * rootFontSize : null;
5133
+ }
5134
+ return null;
5135
+ }
5136
+ function findClosestSnapPoint(height, points) {
5137
+ let closest = null;
5138
+ let closestDistance = Infinity;
5139
+ for (const point of points) {
5140
+ const distance = Math.abs(point.height - height);
5141
+ if (distance < closestDistance) {
5142
+ closestDistance = distance;
5143
+ closest = point;
4708
5144
  }
4709
- }), [openMethod, onClick, onPointerDown]);
5145
+ }
5146
+ return closest;
4710
5147
  }
4711
-
4712
- function useDialogRoot(params) {
4713
- const {
4714
- store,
4715
- parentContext,
4716
- actionsRef
4717
- } = params;
4718
- const open = store.useState('open');
4719
- const disablePointerDismissal = store.useState('disablePointerDismissal');
4720
- const modal = store.useState('modal');
4721
- const popupElement = store.useState('popupElement');
5148
+ function useDrawerSnapPoints() {
4722
5149
  const {
4723
- openMethod,
4724
- triggerProps
4725
- } = useOpenInteractionType(open);
4726
- useImplicitActiveTrigger(store);
5150
+ store
5151
+ } = useDialogRootContext();
4727
5152
  const {
4728
- forceUnmount
4729
- } = useOpenStateTransitions(open, store);
4730
- const createDialogEventDetails = useStableCallback(reason => {
4731
- const details = createChangeEventDetails(reason);
4732
- details.preventUnmountOnClose = () => {
4733
- store.set('preventUnmountingOnClose', true);
4734
- };
4735
- return details;
4736
- });
4737
- const handleImperativeClose = React.useCallback(() => {
4738
- store.setOpen(false, createDialogEventDetails(imperativeAction));
4739
- }, [store, createDialogEventDetails]);
4740
- React.useImperativeHandle(actionsRef, () => ({
4741
- unmount: forceUnmount,
4742
- close: handleImperativeClose
4743
- }), [forceUnmount, handleImperativeClose]);
4744
- const floatingRootContext = useSyncedFloatingRootContext({
4745
- popupStore: store,
4746
- onOpenChange: store.setOpen,
4747
- treatPopupAsFloatingElement: true,
4748
- noEmit: true
5153
+ snapPoints,
5154
+ activeSnapPoint,
5155
+ setActiveSnapPoint,
5156
+ popupHeight
5157
+ } = useDrawerRootContext();
5158
+ const viewportElement = store.useState('viewportElement');
5159
+ const [viewportHeight, setViewportHeight] = React.useState(0);
5160
+ const [rootFontSize, setRootFontSize] = React.useState(16);
5161
+ const measureViewportHeight = useStableCallback(() => {
5162
+ const doc = ownerDocument(viewportElement);
5163
+ const html = doc.documentElement;
5164
+ if (viewportElement) {
5165
+ setViewportHeight(viewportElement.offsetHeight);
5166
+ }
5167
+ if (!viewportElement) {
5168
+ setViewportHeight(html.clientHeight);
5169
+ }
5170
+ const fontSize = parseFloat(getComputedStyle(html).fontSize);
5171
+ if (Number.isFinite(fontSize)) {
5172
+ setRootFontSize(fontSize);
5173
+ }
4749
5174
  });
4750
- const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React.useState(0);
4751
- const isTopmost = ownNestedOpenDialogs === 0;
4752
- const role = useRole(floatingRootContext);
4753
- const dismiss = useDismiss(floatingRootContext, {
4754
- outsidePressEvent() {
4755
- if (store.context.internalBackdropRef.current || store.context.backdropRef.current) {
4756
- return 'intentional';
5175
+ useIsoLayoutEffect(() => {
5176
+ measureViewportHeight();
5177
+ if (!viewportElement || typeof ResizeObserver !== 'function') {
5178
+ return undefined;
5179
+ }
5180
+ const resizeObserver = new ResizeObserver(measureViewportHeight);
5181
+ resizeObserver.observe(viewportElement);
5182
+ return () => {
5183
+ resizeObserver.disconnect();
5184
+ };
5185
+ }, [measureViewportHeight, viewportElement]);
5186
+ const resolvedSnapPoints = React.useMemo(() => {
5187
+ if (!snapPoints || snapPoints.length === 0 || viewportHeight <= 0 || popupHeight <= 0) {
5188
+ return [];
5189
+ }
5190
+ const maxHeight = Math.min(popupHeight, viewportHeight);
5191
+ if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
5192
+ return [];
5193
+ }
5194
+ const resolved = snapPoints.map(value => {
5195
+ const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);
5196
+ if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
5197
+ return null;
4757
5198
  }
4758
- // Ensure `aria-hidden` on outside elements is removed immediately
4759
- // on outside press when trapping focus.
5199
+ const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
4760
5200
  return {
4761
- mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',
4762
- touch: 'sloppy'
5201
+ value,
5202
+ height: clampedHeight,
5203
+ offset: Math.max(0, popupHeight - clampedHeight)
4763
5204
  };
4764
- },
4765
- outsidePress(event) {
4766
- if (!store.context.outsidePressEnabledRef.current) {
4767
- return false;
4768
- }
4769
-
4770
- // For mouse events, only accept left button (button 0)
4771
- // For touch events, a single touch is equivalent to left button
4772
- if ('button' in event && event.button !== 0) {
4773
- return false;
4774
- }
4775
- if ('touches' in event && event.touches.length !== 1) {
4776
- return false;
4777
- }
4778
- const target = getTarget(event);
4779
- if (isTopmost && !disablePointerDismissal) {
4780
- const eventTarget = target;
4781
- // Only close if the click occurred on the dialog's owning backdrop.
4782
- // This supports multiple modal dialogs that aren't nested in the React tree:
4783
- // https://github.com/mui/base-ui/issues/1320
4784
- if (modal) {
4785
- return store.context.internalBackdropRef.current || store.context.backdropRef.current ? store.context.internalBackdropRef.current === eventTarget || store.context.backdropRef.current === eventTarget || contains(eventTarget, popupElement) && !eventTarget?.hasAttribute('data-base-ui-portal') : true;
4786
- }
4787
- return true;
5205
+ }).filter(point => Boolean(point));
5206
+ if (resolved.length <= 1) {
5207
+ return resolved;
5208
+ }
5209
+ const deduped = [];
5210
+ const seenHeights = [];
5211
+ for (let index = resolved.length - 1; index >= 0; index -= 1) {
5212
+ const point = resolved[index];
5213
+ const isDuplicate = seenHeights.some(height => Math.abs(height - point.height) <= 1);
5214
+ if (isDuplicate) {
5215
+ continue;
4788
5216
  }
4789
- return false;
4790
- },
4791
- escapeKey: isTopmost
4792
- });
4793
- useScrollLock(open && modal === true, popupElement);
4794
- const {
4795
- getReferenceProps,
4796
- getFloatingProps,
4797
- getTriggerProps
4798
- } = useInteractions([role, dismiss]);
4799
-
4800
- // Listen for nested open/close events on this store to maintain the count
4801
- store.useContextCallback('onNestedDialogOpen', ownChildrenCount => {
4802
- setOwnNestedOpenDialogs(ownChildrenCount + 1);
4803
- });
4804
- store.useContextCallback('onNestedDialogClose', () => {
4805
- setOwnNestedOpenDialogs(0);
4806
- });
4807
-
4808
- // Notify parent of our open/close state using parent callbacks, if any
4809
- React.useEffect(() => {
4810
- if (parentContext?.onNestedDialogOpen && open) {
4811
- parentContext.onNestedDialogOpen(ownNestedOpenDialogs);
5217
+ seenHeights.push(point.height);
5218
+ deduped.push(point);
4812
5219
  }
4813
- if (parentContext?.onNestedDialogClose && !open) {
4814
- parentContext.onNestedDialogClose();
5220
+ deduped.reverse();
5221
+ return deduped;
5222
+ }, [popupHeight, rootFontSize, snapPoints, viewportHeight]);
5223
+ const resolvedActiveSnapPoint = React.useMemo(() => {
5224
+ if (activeSnapPoint === undefined) {
5225
+ return resolvedSnapPoints[0];
4815
5226
  }
4816
- return () => {
4817
- if (parentContext?.onNestedDialogClose && open) {
4818
- parentContext.onNestedDialogClose();
4819
- }
4820
- };
4821
- }, [open, parentContext, ownNestedOpenDialogs]);
4822
- const activeTriggerProps = React.useMemo(() => getReferenceProps(triggerProps), [getReferenceProps, triggerProps]);
4823
- const inactiveTriggerProps = React.useMemo(() => getTriggerProps(triggerProps), [getTriggerProps, triggerProps]);
4824
- const popupProps = React.useMemo(() => getFloatingProps(), [getFloatingProps]);
4825
- store.useSyncedValues({
4826
- openMethod,
4827
- activeTriggerProps,
4828
- inactiveTriggerProps,
4829
- popupProps,
4830
- floatingRootContext,
4831
- nestedOpenDialogCount: ownNestedOpenDialogs
4832
- });
4833
- }
4834
-
4835
- const DialogRootContext = /*#__PURE__*/React.createContext(undefined);
4836
- if (process.env.NODE_ENV !== "production") DialogRootContext.displayName = "DialogRootContext";
4837
- function useDialogRootContext(optional) {
4838
- const dialogRootContext = React.useContext(DialogRootContext);
4839
- if (optional === false && dialogRootContext === undefined) {
4840
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DialogRootContext is missing. Dialog parts must be placed within <Dialog.Root>.' : formatErrorMessage(27));
4841
- }
4842
- return dialogRootContext;
4843
- }
4844
-
4845
- const selectors = {
4846
- ...popupStoreSelectors,
4847
- modal: createSelector(state => state.modal),
4848
- nested: createSelector(state => state.nested),
4849
- nestedOpenDialogCount: createSelector(state => state.nestedOpenDialogCount),
4850
- disablePointerDismissal: createSelector(state => state.disablePointerDismissal),
4851
- openMethod: createSelector(state => state.openMethod),
4852
- descriptionElementId: createSelector(state => state.descriptionElementId),
4853
- titleElementId: createSelector(state => state.titleElementId),
4854
- viewportElement: createSelector(state => state.viewportElement),
4855
- role: createSelector(state => state.role)
4856
- };
4857
- class DialogStore extends ReactStore {
4858
- constructor(initialState) {
4859
- super(createInitialState(initialState), {
4860
- popupRef: /*#__PURE__*/React.createRef(),
4861
- backdropRef: /*#__PURE__*/React.createRef(),
4862
- internalBackdropRef: /*#__PURE__*/React.createRef(),
4863
- outsidePressEnabledRef: {
4864
- current: true
4865
- },
4866
- triggerElements: new PopupTriggerMap(),
4867
- onOpenChange: undefined,
4868
- onOpenChangeComplete: undefined
4869
- }, selectors);
4870
- }
4871
- setOpen = (nextOpen, eventDetails) => {
4872
- eventDetails.preventUnmountOnClose = () => {
4873
- this.set('preventUnmountingOnClose', true);
4874
- };
4875
- if (!nextOpen && eventDetails.trigger == null && this.state.activeTriggerId != null) {
4876
- // When closing the dialog, pass the old trigger to the onOpenChange event
4877
- // so it's not reset too early (potentially causing focus issues in controlled scenarios).
4878
- eventDetails.trigger = this.state.activeTriggerElement ?? undefined;
5227
+ if (activeSnapPoint === null) {
5228
+ return undefined;
4879
5229
  }
4880
- this.context.onOpenChange?.(nextOpen, eventDetails);
4881
- if (eventDetails.isCanceled) {
4882
- return;
5230
+ const exactMatch = resolvedSnapPoints.find(point => Object.is(point.value, activeSnapPoint));
5231
+ if (exactMatch) {
5232
+ return exactMatch;
4883
5233
  }
4884
- const details = {
4885
- open: nextOpen,
4886
- nativeEvent: eventDetails.event,
4887
- reason: eventDetails.reason,
4888
- nested: this.state.nested
4889
- };
4890
- this.state.floatingRootContext.context.events?.emit('openchange', details);
4891
- const updatedState = {
4892
- open: nextOpen
4893
- };
4894
-
4895
- // If a popup is closing, the `trigger` may be null.
4896
- // We want to keep the previous value so that exit animations are played and focus is returned correctly.
4897
- const newTriggerId = eventDetails.trigger?.id ?? null;
4898
- if (newTriggerId || nextOpen) {
4899
- updatedState.activeTriggerId = newTriggerId;
4900
- updatedState.activeTriggerElement = eventDetails.trigger ?? null;
5234
+ const maxHeight = Math.min(popupHeight, viewportHeight);
5235
+ const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);
5236
+ if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
5237
+ return undefined;
4901
5238
  }
4902
- this.update(updatedState);
4903
- };
4904
- }
4905
- function createInitialState(initialState = {}) {
5239
+ const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
5240
+ return findClosestSnapPoint(clampedHeight, resolvedSnapPoints) ?? undefined;
5241
+ }, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);
4906
5242
  return {
4907
- ...createInitialPopupStoreState(),
4908
- modal: true,
4909
- disablePointerDismissal: false,
4910
- popupElement: null,
4911
- viewportElement: null,
4912
- descriptionElementId: undefined,
4913
- titleElementId: undefined,
4914
- openMethod: null,
4915
- nested: false,
4916
- nestedOpenDialogCount: 0,
4917
- role: 'dialog',
4918
- ...initialState
5243
+ snapPoints,
5244
+ activeSnapPoint,
5245
+ setActiveSnapPoint,
5246
+ popupHeight,
5247
+ viewportHeight,
5248
+ resolvedSnapPoints,
5249
+ activeSnapPointOffset: resolvedActiveSnapPoint?.offset ?? null
4919
5250
  };
4920
5251
  }
4921
5252
 
4922
- let CommonPopupDataAttributes = function (CommonPopupDataAttributes) {
4923
- /**
4924
- * Present when the popup is open.
4925
- */
4926
- CommonPopupDataAttributes["open"] = "data-open";
4927
- /**
4928
- * Present when the popup is closed.
4929
- */
4930
- CommonPopupDataAttributes["closed"] = "data-closed";
4931
- /**
4932
- * Present when the popup is animating in.
4933
- */
4934
- CommonPopupDataAttributes[CommonPopupDataAttributes["startingStyle"] = TransitionStatusDataAttributes.startingStyle] = "startingStyle";
4935
- /**
4936
- * Present when the popup is animating out.
4937
- */
4938
- CommonPopupDataAttributes[CommonPopupDataAttributes["endingStyle"] = TransitionStatusDataAttributes.endingStyle] = "endingStyle";
4939
- /**
4940
- * Present when the anchor is hidden.
4941
- */
4942
- CommonPopupDataAttributes["anchorHidden"] = "data-anchor-hidden";
4943
- /**
4944
- * Indicates which side the popup is positioned relative to the trigger.
4945
- * @type { 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start'}
4946
- */
4947
- CommonPopupDataAttributes["side"] = "data-side";
4948
- /**
4949
- * Indicates how the popup is aligned relative to specified side.
4950
- * @type {'start' | 'center' | 'end'}
4951
- */
4952
- CommonPopupDataAttributes["align"] = "data-align";
4953
- return CommonPopupDataAttributes;
4954
- }({});
4955
- let CommonTriggerDataAttributes = /*#__PURE__*/function (CommonTriggerDataAttributes) {
4956
- /**
4957
- * Present when the popup is open.
4958
- */
4959
- CommonTriggerDataAttributes["popupOpen"] = "data-popup-open";
4960
- /**
4961
- * Present when a pressable trigger is pressed.
4962
- */
4963
- CommonTriggerDataAttributes["pressed"] = "data-pressed";
4964
- return CommonTriggerDataAttributes;
4965
- }({});
4966
- const TRIGGER_HOOK = {
4967
- [CommonTriggerDataAttributes.popupOpen]: ''
4968
- };
4969
- const PRESSABLE_TRIGGER_HOOK = {
4970
- [CommonTriggerDataAttributes.popupOpen]: '',
4971
- [CommonTriggerDataAttributes.pressed]: ''
4972
- };
4973
- const POPUP_OPEN_HOOK = {
4974
- [CommonPopupDataAttributes.open]: ''
4975
- };
4976
- const POPUP_CLOSED_HOOK = {
4977
- [CommonPopupDataAttributes.closed]: ''
4978
- };
4979
- const ANCHOR_HIDDEN_HOOK = {
4980
- [CommonPopupDataAttributes.anchorHidden]: ''
4981
- };
4982
- const triggerOpenStateMapping = {
4983
- open(value) {
4984
- if (value) {
4985
- return TRIGGER_HOOK;
4986
- }
4987
- return null;
4988
- }
4989
- };
4990
- const pressableTriggerOpenStateMapping = {
4991
- open(value) {
4992
- if (value) {
4993
- return PRESSABLE_TRIGGER_HOOK;
4994
- }
4995
- return null;
4996
- }
4997
- };
4998
- const popupStateMapping = {
4999
- open(value) {
5000
- if (value) {
5001
- return POPUP_OPEN_HOOK;
5002
- }
5003
- return POPUP_CLOSED_HOOK;
5004
- },
5005
- anchorHidden(value) {
5006
- if (value) {
5007
- return ANCHOR_HIDDEN_HOOK;
5008
- }
5009
- return null;
5010
- }
5011
- };
5012
-
5013
- const DialogPortalContext = /*#__PURE__*/React.createContext(undefined);
5014
- if (process.env.NODE_ENV !== "production") DialogPortalContext.displayName = "DialogPortalContext";
5015
- function useDialogPortalContext() {
5016
- const value = React.useContext(DialogPortalContext);
5017
- if (value === undefined) {
5018
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: <Dialog.Portal> is missing.' : formatErrorMessage(26));
5019
- }
5020
- return value;
5253
+ const DrawerViewportContext = /*#__PURE__*/React.createContext(null);
5254
+ if (process.env.NODE_ENV !== "production") DrawerViewportContext.displayName = "DrawerViewportContext";
5255
+ function useDrawerViewportContext(optional) {
5256
+ const context = React.useContext(DrawerViewportContext);
5257
+ return context;
5021
5258
  }
5022
5259
 
5023
- function inertValue(value) {
5024
- if (isReactVersionAtLeast(19)) {
5025
- return value;
5026
- }
5027
- // compatibility with React < 19
5028
- return value ? 'true' : undefined;
5029
- }
5260
+ let drawerSwipeVarsRegistered = false;
5030
5261
 
5031
- const InternalBackdrop = /*#__PURE__*/React.forwardRef(function InternalBackdrop(props, ref) {
5032
- const {
5033
- cutout,
5034
- ...otherProps
5035
- } = props;
5036
- let clipPath;
5037
- if (cutout) {
5038
- const rect = cutout?.getBoundingClientRect();
5039
- clipPath = `polygon(
5040
- 0% 0%,
5041
- 100% 0%,
5042
- 100% 100%,
5043
- 0% 100%,
5044
- 0% 0%,
5045
- ${rect.left}px ${rect.top}px,
5046
- ${rect.left}px ${rect.bottom}px,
5047
- ${rect.right}px ${rect.bottom}px,
5048
- ${rect.right}px ${rect.top}px,
5049
- ${rect.left}px ${rect.top}px
5050
- )`;
5262
+ /**
5263
+ * Removes inheritance of high-frequency drawer swipe CSS variables, which
5264
+ * reduces style recalculation cost in complex drawers with deep subtrees.
5265
+ * Child elements that need these values can still opt-in by using `inherit`.
5266
+ * See https://motion.dev/blog/web-animation-performance-tier-list
5267
+ * under the "Improving CSS variable performance" section.
5268
+ */
5269
+ function removeCSSVariableInheritance() {
5270
+ if (drawerSwipeVarsRegistered) {
5271
+ return;
5051
5272
  }
5052
- return /*#__PURE__*/jsx("div", {
5053
- ref: ref,
5054
- role: "presentation"
5055
- // Ensures Floating UI's outside press detection runs, as it considers
5056
- // it an element that existed when the popup rendered.
5057
- ,
5058
- "data-base-ui-inert": "",
5059
- ...otherProps,
5060
- style: {
5061
- position: 'fixed',
5062
- inset: 0,
5063
- userSelect: 'none',
5064
- WebkitUserSelect: 'none',
5065
- clipPath
5066
- }
5067
- });
5068
- });
5069
- if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "InternalBackdrop";
5070
-
5071
- const DialogPortal = /*#__PURE__*/React.forwardRef(function DialogPortal(props, forwardedRef) {
5072
- const {
5073
- keepMounted = false,
5074
- ...portalProps
5075
- } = props;
5076
- const {
5077
- store
5078
- } = useDialogRootContext();
5079
- const mounted = store.useState('mounted');
5080
- const modal = store.useState('modal');
5081
- const open = store.useState('open');
5082
- const shouldRender = mounted || keepMounted;
5083
- if (!shouldRender) {
5084
- return null;
5085
- }
5086
- return /*#__PURE__*/jsx(DialogPortalContext.Provider, {
5087
- value: keepMounted,
5088
- children: /*#__PURE__*/jsxs(FloatingPortal, {
5089
- ref: forwardedRef,
5090
- ...portalProps,
5091
- children: [mounted && modal === true && /*#__PURE__*/jsx(InternalBackdrop, {
5092
- ref: store.context.internalBackdropRef,
5093
- inert: inertValue(!open)
5094
- }), props.children]
5095
- })
5096
- });
5097
- });
5098
- if (process.env.NODE_ENV !== "production") DialogPortal.displayName = "DialogPortal";
5099
-
5100
- function useOnFirstRender(fn) {
5101
- const ref = React.useRef(true);
5102
- if (ref.current) {
5103
- ref.current = false;
5104
- fn();
5105
- }
5106
- }
5107
5273
 
5108
- /**
5109
- * Groups all parts of the dialog.
5110
- * Doesn’t render its own HTML element.
5111
- *
5112
- * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
5113
- */
5114
- function DialogRoot(props) {
5115
- const {
5116
- children,
5117
- open: openProp,
5118
- defaultOpen = false,
5119
- onOpenChange,
5120
- onOpenChangeComplete,
5121
- disablePointerDismissal = false,
5122
- modal = true,
5123
- actionsRef,
5124
- handle,
5125
- triggerId: triggerIdProp,
5126
- defaultTriggerId: defaultTriggerIdProp = null
5127
- } = props;
5128
- const parentDialogRootContext = useDialogRootContext(true);
5129
- const nested = Boolean(parentDialogRootContext);
5130
- const store = useRefWithInit(() => {
5131
- return handle?.store ?? new DialogStore({
5132
- open: defaultOpen,
5133
- openProp,
5134
- activeTriggerId: defaultTriggerIdProp,
5135
- triggerIdProp,
5136
- modal,
5137
- disablePointerDismissal,
5138
- nested
5274
+ // Intentionally keep inheritance disabled on WebKit as well. Safari doesn't support
5275
+ // opting descendants back in via `--var: inherit` for custom properties registered
5276
+ // with `inherits: false`, but Drawer does not rely on descendant access to these vars
5277
+ // (unlike ScrollArea), so we keep the performance optimization enabled.
5278
+ if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
5279
+ [DrawerPopupCssVars.swipeMovementX, DrawerPopupCssVars.swipeMovementY, DrawerPopupCssVars.snapPointOffset].forEach(name => {
5280
+ try {
5281
+ CSS.registerProperty({
5282
+ name,
5283
+ syntax: '<length>',
5284
+ inherits: false,
5285
+ initialValue: '0px'
5286
+ });
5287
+ } catch {
5288
+ /* ignore already-registered */
5289
+ }
5139
5290
  });
5140
- }).current;
5141
-
5142
- // Support initially open state when uncontrolled
5143
- useOnFirstRender(() => {
5144
- if (openProp === undefined && store.state.open === false && defaultOpen === true) {
5145
- store.update({
5146
- open: true,
5147
- activeTriggerId: defaultTriggerIdProp
5148
- });
5149
- }
5150
- });
5151
- store.useControlledProp('openProp', openProp);
5152
- store.useControlledProp('triggerIdProp', triggerIdProp);
5153
- store.useSyncedValues({
5154
- disablePointerDismissal,
5155
- nested,
5156
- modal
5157
- });
5158
- store.useContextCallback('onOpenChange', onOpenChange);
5159
- store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
5160
- const payload = store.useState('payload');
5161
- useDialogRoot({
5162
- store,
5163
- actionsRef,
5164
- parentContext: parentDialogRootContext?.store.context,
5165
- onOpenChange,
5166
- triggerIdProp
5167
- });
5168
- const contextValue = React.useMemo(() => ({
5169
- store
5170
- }), [store]);
5171
- return /*#__PURE__*/jsx(DialogRootContext.Provider, {
5172
- value: contextValue,
5173
- children: typeof children === 'function' ? children({
5174
- payload
5175
- }) : children
5176
- });
5291
+ [{
5292
+ name: DrawerBackdropCssVars.swipeProgress,
5293
+ initialValue: '0'
5294
+ }, {
5295
+ name: DrawerPopupCssVars.swipeStrength,
5296
+ initialValue: '1'
5297
+ }].forEach(({
5298
+ name,
5299
+ initialValue
5300
+ }) => {
5301
+ try {
5302
+ CSS.registerProperty({
5303
+ name,
5304
+ syntax: '<number>',
5305
+ inherits: false,
5306
+ initialValue
5307
+ });
5308
+ } catch {
5309
+ /* ignore already-registered */
5310
+ }
5311
+ });
5312
+ }
5313
+ drawerSwipeVarsRegistered = true;
5177
5314
  }
5178
-
5179
- let DrawerPopupCssVars = /*#__PURE__*/function (DrawerPopupCssVars) {
5180
- /**
5181
- * The number of nested drawers that are currently open.
5182
- * @type {number}
5183
- */
5184
- DrawerPopupCssVars["nestedDrawers"] = "--nested-drawers";
5185
- /**
5186
- * The height of the drawer popup.
5187
- * @type {CSS length}
5188
- */
5189
- DrawerPopupCssVars["height"] = "--drawer-height";
5190
- /**
5191
- * The height of the frontmost open drawer in the current nested drawer stack.
5192
- * @type {CSS length}
5193
- */
5194
- DrawerPopupCssVars["frontmostHeight"] = "--drawer-frontmost-height";
5195
- /**
5196
- * The swipe movement on the X axis.
5197
- * @type {CSS length}
5198
- */
5199
- DrawerPopupCssVars["swipeMovementX"] = "--drawer-swipe-movement-x";
5200
- /**
5201
- * The swipe movement on the Y axis.
5202
- * @type {CSS length}
5203
- */
5204
- DrawerPopupCssVars["swipeMovementY"] = "--drawer-swipe-movement-y";
5205
- /**
5206
- * The snap point offset used for translating the drawer.
5207
- * @type {CSS length}
5208
- */
5209
- DrawerPopupCssVars["snapPointOffset"] = "--drawer-snap-point-offset";
5210
- /**
5211
- * A scalar (0.1-1) used to scale the swipe release transition duration in CSS.
5212
- * @type {number}
5213
- */
5214
- DrawerPopupCssVars["swipeStrength"] = "--drawer-swipe-strength";
5215
- return DrawerPopupCssVars;
5216
- }({});
5217
-
5218
- let DrawerBackdropCssVars = /*#__PURE__*/function (DrawerBackdropCssVars) {
5219
- /**
5220
- * The swipe progress of the drawer gesture.
5221
- * @type {number}
5222
- */
5223
- DrawerBackdropCssVars["swipeProgress"] = "--drawer-swipe-progress";
5224
- return DrawerBackdropCssVars;
5225
- }({});
5226
-
5227
- const stateAttributesMapping$1 = {
5315
+ const stateAttributesMapping = {
5228
5316
  ...popupStateMapping,
5229
- ...transitionStatusMapping
5317
+ ...transitionStatusMapping,
5318
+ expanded(value) {
5319
+ return value ? {
5320
+ [DrawerPopupDataAttributes.expanded]: ''
5321
+ } : null;
5322
+ },
5323
+ nestedDrawerOpen(value) {
5324
+ return value ? {
5325
+ [DrawerPopupDataAttributes.nestedDrawerOpen]: ''
5326
+ } : null;
5327
+ },
5328
+ nestedDrawerSwiping(value) {
5329
+ return value ? {
5330
+ [DrawerPopupDataAttributes.nestedDrawerSwiping]: ''
5331
+ } : null;
5332
+ },
5333
+ swipeDirection(value) {
5334
+ return value ? {
5335
+ [DrawerPopupDataAttributes.swipeDirection]: value
5336
+ } : null;
5337
+ },
5338
+ swiping(value) {
5339
+ return value ? {
5340
+ [DrawerPopupDataAttributes.swiping]: ''
5341
+ } : null;
5342
+ }
5230
5343
  };
5231
5344
 
5232
5345
  /**
5233
- * An overlay displayed beneath the popup.
5346
+ * A container for the drawer contents.
5234
5347
  * Renders a `<div>` element.
5235
5348
  *
5236
5349
  * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
5237
5350
  */
5238
- const DrawerBackdrop = /*#__PURE__*/React.forwardRef(function DrawerBackdrop(componentProps, forwardedRef) {
5351
+ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(componentProps, forwardedRef) {
5239
5352
  const {
5240
- render,
5241
5353
  className,
5242
- forceRender = false,
5354
+ finalFocus,
5355
+ initialFocus,
5356
+ render,
5243
5357
  ...elementProps
5244
5358
  } = componentProps;
5245
5359
  const {
5246
5360
  store
5247
5361
  } = useDialogRootContext();
5248
- const open = store.useState('open');
5249
- const nested = store.useState('nested');
5362
+ const {
5363
+ swipeDirection,
5364
+ frontmostHeight,
5365
+ hasNestedDrawer,
5366
+ nestedSwiping,
5367
+ nestedSwipeProgressStore,
5368
+ onPopupHeightChange,
5369
+ notifyParentFrontmostHeight,
5370
+ notifyParentHasNestedDrawer
5371
+ } = useDrawerRootContext();
5372
+ const descriptionElementId = store.useState('descriptionElementId');
5373
+ const disablePointerDismissal = store.useState('disablePointerDismissal');
5374
+ const floatingRootContext = store.useState('floatingRootContext');
5375
+ const rootPopupProps = store.useState('popupProps');
5376
+ const modal = store.useState('modal');
5250
5377
  const mounted = store.useState('mounted');
5378
+ const nested = store.useState('nested');
5379
+ const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
5251
5380
  const transitionStatus = store.useState('transitionStatus');
5252
- const state = {
5253
- open,
5254
- transitionStatus
5255
- };
5256
- return useRenderElement('div', componentProps, {
5257
- state,
5258
- ref: [store.context.backdropRef, forwardedRef],
5259
- stateAttributesMapping: stateAttributesMapping$1,
5260
- props: [{
5261
- role: 'presentation',
5262
- hidden: !mounted,
5263
- style: {
5264
- pointerEvents: !open ? 'none' : undefined,
5265
- userSelect: 'none',
5266
- WebkitUserSelect: 'none',
5267
- [DrawerBackdropCssVars.swipeProgress]: '0',
5268
- [DrawerPopupCssVars.swipeStrength]: '1'
5269
- }
5270
- }, elementProps],
5271
- enabled: forceRender || !nested
5272
- });
5273
- });
5274
- if (process.env.NODE_ENV !== "production") DrawerBackdrop.displayName = "DrawerBackdrop";
5275
-
5276
- const DRAWER_CONTENT_ATTRIBUTE = 'data-drawer-content';
5277
-
5278
- /**
5279
- * A container for the drawer contents.
5280
- * Renders a `<div>` element.
5281
- *
5282
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
5283
- */
5284
- const DrawerContent = /*#__PURE__*/React.forwardRef(function DrawerContent(componentProps, forwardedRef) {
5285
- const {
5286
- render,
5287
- className,
5288
- ...elementProps
5289
- } = componentProps;
5290
- useDialogRootContext();
5291
- return useRenderElement('div', componentProps, {
5292
- ref: forwardedRef,
5293
- props: [{
5294
- [DRAWER_CONTENT_ATTRIBUTE]: ''
5295
- }, elementProps]
5296
- });
5297
- });
5298
- if (process.env.NODE_ENV !== "production") DrawerContent.displayName = "DrawerContent";
5299
-
5300
- const DrawerProviderContext = /*#__PURE__*/React.createContext(undefined);
5301
- if (process.env.NODE_ENV !== "production") DrawerProviderContext.displayName = "DrawerProviderContext";
5302
- function useDrawerProviderContext(optional) {
5303
- const context = React.useContext(DrawerProviderContext);
5304
- return context;
5305
- }
5306
-
5307
- let DrawerPopupDataAttributes = function (DrawerPopupDataAttributes) {
5308
- /**
5309
- * Present when the drawer is open.
5310
- */
5311
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["open"] = CommonPopupDataAttributes.open] = "open";
5312
- /**
5313
- * Present when the drawer is closed.
5314
- */
5315
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["closed"] = CommonPopupDataAttributes.closed] = "closed";
5316
- /**
5317
- * Present when the drawer is animating in.
5318
- */
5319
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["startingStyle"] = CommonPopupDataAttributes.startingStyle] = "startingStyle";
5320
- /**
5321
- * Present when the drawer is animating out.
5322
- */
5323
- DrawerPopupDataAttributes[DrawerPopupDataAttributes["endingStyle"] = CommonPopupDataAttributes.endingStyle] = "endingStyle";
5324
- /**
5325
- * Present when the drawer is at the expanded (full-height) snap point.
5326
- */
5327
- DrawerPopupDataAttributes["expanded"] = "data-expanded";
5328
- /**
5329
- * Present when a nested drawer is open.
5330
- */
5331
- DrawerPopupDataAttributes["nestedDrawerOpen"] = "data-nested-drawer-open";
5332
- /**
5333
- * Present when a nested drawer is being swiped.
5334
- */
5335
- DrawerPopupDataAttributes["nestedDrawerSwiping"] = "data-nested-drawer-swiping";
5336
- /**
5337
- * Present when the drawer is dismissed by swiping.
5338
- */
5339
- DrawerPopupDataAttributes["swipeDismiss"] = "data-swipe-dismiss";
5340
- /**
5341
- * Indicates the swipe direction.
5342
- * @type {'up' | 'down' | 'left' | 'right'}
5343
- */
5344
- DrawerPopupDataAttributes["swipeDirection"] = "data-swipe-direction";
5345
- /**
5346
- * Present when the drawer is being swiped.
5347
- */
5348
- DrawerPopupDataAttributes["swiping"] = "data-swiping";
5349
- return DrawerPopupDataAttributes;
5350
- }({});
5351
-
5352
- const DrawerRootContext = /*#__PURE__*/React.createContext(undefined);
5353
- if (process.env.NODE_ENV !== "production") DrawerRootContext.displayName = "DrawerRootContext";
5354
- function useDrawerRootContext(optional) {
5355
- const drawerRootContext = React.useContext(DrawerRootContext);
5356
- if (optional === false && drawerRootContext === undefined) {
5357
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: DrawerRootContext is missing. Drawer parts must be placed within <Drawer.Root>.' : formatErrorMessage(90));
5358
- }
5359
- return drawerRootContext;
5360
- }
5361
-
5362
- function clamp(val, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
5363
- return Math.max(min, Math.min(val, max));
5364
- }
5365
-
5366
- function resolveSnapPointValue(snapPoint, viewportHeight, rootFontSize) {
5367
- if (!Number.isFinite(viewportHeight) || viewportHeight <= 0) {
5368
- return null;
5369
- }
5370
- if (typeof snapPoint === 'number') {
5371
- if (!Number.isFinite(snapPoint)) {
5372
- return null;
5373
- }
5374
- if (snapPoint <= 1) {
5375
- return clamp(snapPoint, 0, 1) * viewportHeight;
5376
- }
5377
- return snapPoint;
5378
- }
5379
- const trimmed = snapPoint.trim();
5380
- if (trimmed.endsWith('px')) {
5381
- const value = Number.parseFloat(trimmed);
5382
- return Number.isFinite(value) ? value : null;
5383
- }
5384
- if (trimmed.endsWith('rem')) {
5385
- const value = Number.parseFloat(trimmed);
5386
- return Number.isFinite(value) ? value * rootFontSize : null;
5387
- }
5388
- return null;
5389
- }
5390
- function findClosestSnapPoint(height, points) {
5391
- let closest = null;
5392
- let closestDistance = Infinity;
5393
- for (const point of points) {
5394
- const distance = Math.abs(point.height - height);
5395
- if (distance < closestDistance) {
5396
- closestDistance = distance;
5397
- closest = point;
5398
- }
5399
- }
5400
- return closest;
5401
- }
5402
- function useDrawerSnapPoints() {
5403
- const {
5404
- store
5405
- } = useDialogRootContext();
5381
+ const open = store.useState('open');
5382
+ const openMethod = store.useState('openMethod');
5383
+ const titleElementId = store.useState('titleElementId');
5384
+ const role = store.useState('role');
5385
+ const nestedDrawerOpen = nestedOpenDialogCount > 0;
5386
+ const swipe = useDrawerViewportContext();
5387
+ const swiping = swipe?.swiping ?? false;
5388
+ const swipeStrength = swipe?.swipeStrength ?? null;
5406
5389
  const {
5407
5390
  snapPoints,
5408
5391
  activeSnapPoint,
5409
- setActiveSnapPoint,
5410
- popupHeight
5411
- } = useDrawerRootContext();
5412
- const viewportElement = store.useState('viewportElement');
5413
- const [viewportHeight, setViewportHeight] = React.useState(0);
5414
- const [rootFontSize, setRootFontSize] = React.useState(16);
5415
- const measureViewportHeight = useStableCallback(() => {
5416
- const doc = ownerDocument(viewportElement);
5417
- const html = doc.documentElement;
5418
- if (viewportElement) {
5419
- setViewportHeight(viewportElement.offsetHeight);
5392
+ activeSnapPointOffset
5393
+ } = useDrawerSnapPoints();
5394
+ useDialogPortalContext();
5395
+ const [popupHeight, setPopupHeight] = React.useState(0);
5396
+ const popupHeightRef = React.useRef(0);
5397
+ const measureHeight = useStableCallback(() => {
5398
+ const popupElement = store.context.popupRef.current;
5399
+ if (!popupElement) {
5400
+ return;
5420
5401
  }
5421
- if (!viewportElement) {
5422
- setViewportHeight(html.clientHeight);
5402
+ const offsetHeight = popupElement.offsetHeight;
5403
+
5404
+ // Only skip while the element is still actually stretched beyond its last measured height.
5405
+ if (popupHeightRef.current > 0 && frontmostHeight > popupHeightRef.current && offsetHeight > popupHeightRef.current) {
5406
+ return;
5423
5407
  }
5424
- const fontSize = parseFloat(getComputedStyle(html).fontSize);
5425
- if (Number.isFinite(fontSize)) {
5426
- setRootFontSize(fontSize);
5408
+ const keepHeightWhileNested = popupHeightRef.current > 0 && hasNestedDrawer;
5409
+ if (keepHeightWhileNested) {
5410
+ const oldHeight = popupHeightRef.current;
5411
+ setPopupHeight(oldHeight);
5412
+ onPopupHeightChange(oldHeight);
5413
+ return;
5414
+ }
5415
+ const nextHeight = offsetHeight;
5416
+ if (nextHeight === popupHeightRef.current) {
5417
+ return;
5427
5418
  }
5419
+ popupHeightRef.current = nextHeight;
5420
+ setPopupHeight(nextHeight);
5421
+ onPopupHeightChange(nextHeight);
5428
5422
  });
5429
5423
  useIsoLayoutEffect(() => {
5430
- measureViewportHeight();
5431
- if (!viewportElement || typeof ResizeObserver !== 'function') {
5424
+ if (!mounted) {
5425
+ popupHeightRef.current = 0;
5426
+ setPopupHeight(0);
5427
+ onPopupHeightChange(0);
5432
5428
  return undefined;
5433
5429
  }
5434
- const resizeObserver = new ResizeObserver(measureViewportHeight);
5435
- resizeObserver.observe(viewportElement);
5430
+ const popupElement = store.context.popupRef.current;
5431
+ if (!popupElement) {
5432
+ return undefined;
5433
+ }
5434
+ removeCSSVariableInheritance();
5435
+ measureHeight();
5436
+ if (typeof ResizeObserver !== 'function') {
5437
+ return undefined;
5438
+ }
5439
+ const resizeObserver = new ResizeObserver(measureHeight);
5440
+ resizeObserver.observe(popupElement);
5436
5441
  return () => {
5437
5442
  resizeObserver.disconnect();
5438
5443
  };
5439
- }, [measureViewportHeight, viewportElement]);
5440
- const resolvedSnapPoints = React.useMemo(() => {
5441
- if (!snapPoints || snapPoints.length === 0 || viewportHeight <= 0 || popupHeight <= 0) {
5442
- return [];
5443
- }
5444
- const maxHeight = Math.min(popupHeight, viewportHeight);
5445
- if (!Number.isFinite(maxHeight) || maxHeight <= 0) {
5446
- return [];
5447
- }
5448
- const resolved = snapPoints.map(value => {
5449
- const resolvedHeight = resolveSnapPointValue(value, viewportHeight, rootFontSize);
5450
- if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
5451
- return null;
5452
- }
5453
- const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
5454
- return {
5455
- value,
5456
- height: clampedHeight,
5457
- offset: Math.max(0, popupHeight - clampedHeight)
5458
- };
5459
- }).filter(point => Boolean(point));
5460
- if (resolved.length <= 1) {
5461
- return resolved;
5462
- }
5463
- const deduped = [];
5464
- const seenHeights = [];
5465
- for (let index = resolved.length - 1; index >= 0; index -= 1) {
5466
- const point = resolved[index];
5467
- const isDuplicate = seenHeights.some(height => Math.abs(height - point.height) <= 1);
5468
- if (isDuplicate) {
5469
- continue;
5470
- }
5471
- seenHeights.push(point.height);
5472
- deduped.push(point);
5473
- }
5474
- deduped.reverse();
5475
- return deduped;
5476
- }, [popupHeight, rootFontSize, snapPoints, viewportHeight]);
5477
- const resolvedActiveSnapPoint = React.useMemo(() => {
5478
- if (activeSnapPoint === undefined) {
5479
- return resolvedSnapPoints[0];
5480
- }
5481
- if (activeSnapPoint === null) {
5482
- return undefined;
5483
- }
5484
- const exactMatch = resolvedSnapPoints.find(point => Object.is(point.value, activeSnapPoint));
5485
- if (exactMatch) {
5486
- return exactMatch;
5487
- }
5488
- const maxHeight = Math.min(popupHeight, viewportHeight);
5489
- const resolvedHeight = resolveSnapPointValue(activeSnapPoint, viewportHeight, rootFontSize);
5490
- if (resolvedHeight === null || !Number.isFinite(resolvedHeight)) {
5491
- return undefined;
5492
- }
5493
- const clampedHeight = clamp(resolvedHeight, 0, maxHeight);
5494
- return findClosestSnapPoint(clampedHeight, resolvedSnapPoints) ?? undefined;
5495
- }, [activeSnapPoint, popupHeight, resolvedSnapPoints, rootFontSize, viewportHeight]);
5496
- return {
5497
- snapPoints,
5498
- activeSnapPoint,
5499
- setActiveSnapPoint,
5500
- popupHeight,
5501
- viewportHeight,
5502
- resolvedSnapPoints,
5503
- activeSnapPointOffset: resolvedActiveSnapPoint?.offset ?? null
5504
- };
5505
- }
5506
-
5507
- const DrawerViewportContext = /*#__PURE__*/React.createContext(null);
5508
- if (process.env.NODE_ENV !== "production") DrawerViewportContext.displayName = "DrawerViewportContext";
5509
- function useDrawerViewportContext(optional) {
5510
- const context = React.useContext(DrawerViewportContext);
5511
- return context;
5512
- }
5513
-
5514
- let drawerSwipeVarsRegistered = false;
5515
-
5516
- /**
5517
- * Removes inheritance of high-frequency drawer swipe CSS variables, which
5518
- * reduces style recalculation cost in complex drawers with deep subtrees.
5519
- * Child elements that need these values can still opt-in by using `inherit`.
5520
- * See https://motion.dev/blog/web-animation-performance-tier-list
5521
- * under the "Improving CSS variable performance" section.
5522
- */
5523
- function removeCSSVariableInheritance() {
5524
- if (drawerSwipeVarsRegistered) {
5525
- return;
5526
- }
5527
-
5528
- // Intentionally keep inheritance disabled on WebKit as well. Safari doesn't support
5529
- // opting descendants back in via `--var: inherit` for custom properties registered
5530
- // with `inherits: false`, but Drawer does not rely on descendant access to these vars
5531
- // (unlike ScrollArea), so we keep the performance optimization enabled.
5532
- if (typeof CSS !== 'undefined' && 'registerProperty' in CSS) {
5533
- [DrawerPopupCssVars.swipeMovementX, DrawerPopupCssVars.swipeMovementY, DrawerPopupCssVars.snapPointOffset].forEach(name => {
5534
- try {
5535
- CSS.registerProperty({
5536
- name,
5537
- syntax: '<length>',
5538
- inherits: false,
5539
- initialValue: '0px'
5540
- });
5541
- } catch {
5542
- /* ignore already-registered */
5543
- }
5544
- });
5545
- [{
5546
- name: DrawerBackdropCssVars.swipeProgress,
5547
- initialValue: '0'
5548
- }, {
5549
- name: DrawerPopupCssVars.swipeStrength,
5550
- initialValue: '1'
5551
- }].forEach(({
5552
- name,
5553
- initialValue
5554
- }) => {
5555
- try {
5556
- CSS.registerProperty({
5557
- name,
5558
- syntax: '<number>',
5559
- inherits: false,
5560
- initialValue
5561
- });
5562
- } catch {
5563
- /* ignore already-registered */
5564
- }
5565
- });
5566
- }
5567
- drawerSwipeVarsRegistered = true;
5568
- }
5569
- const stateAttributesMapping = {
5570
- ...popupStateMapping,
5571
- ...transitionStatusMapping,
5572
- expanded(value) {
5573
- return value ? {
5574
- [DrawerPopupDataAttributes.expanded]: ''
5575
- } : null;
5576
- },
5577
- nestedDrawerOpen(value) {
5578
- return value ? {
5579
- [DrawerPopupDataAttributes.nestedDrawerOpen]: ''
5580
- } : null;
5581
- },
5582
- nestedDrawerSwiping(value) {
5583
- return value ? {
5584
- [DrawerPopupDataAttributes.nestedDrawerSwiping]: ''
5585
- } : null;
5586
- },
5587
- swipeDirection(value) {
5588
- return value ? {
5589
- [DrawerPopupDataAttributes.swipeDirection]: value
5590
- } : null;
5591
- },
5592
- swiping(value) {
5593
- return value ? {
5594
- [DrawerPopupDataAttributes.swiping]: ''
5595
- } : null;
5596
- }
5597
- };
5598
-
5599
- /**
5600
- * A container for the drawer contents.
5601
- * Renders a `<div>` element.
5602
- *
5603
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
5604
- */
5605
- const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(componentProps, forwardedRef) {
5606
- const {
5607
- className,
5608
- finalFocus,
5609
- initialFocus,
5610
- render,
5611
- ...elementProps
5612
- } = componentProps;
5613
- const {
5614
- store
5615
- } = useDialogRootContext();
5616
- const {
5617
- swipeDirection,
5618
- frontmostHeight,
5619
- hasNestedDrawer,
5620
- nestedSwiping,
5621
- nestedSwipeProgressStore,
5622
- onPopupHeightChange,
5623
- notifyParentFrontmostHeight,
5624
- notifyParentHasNestedDrawer
5625
- } = useDrawerRootContext();
5626
- const descriptionElementId = store.useState('descriptionElementId');
5627
- const disablePointerDismissal = store.useState('disablePointerDismissal');
5628
- const floatingRootContext = store.useState('floatingRootContext');
5629
- const rootPopupProps = store.useState('popupProps');
5630
- const modal = store.useState('modal');
5631
- const mounted = store.useState('mounted');
5632
- const nested = store.useState('nested');
5633
- const nestedOpenDialogCount = store.useState('nestedOpenDialogCount');
5634
- const transitionStatus = store.useState('transitionStatus');
5635
- const open = store.useState('open');
5636
- const openMethod = store.useState('openMethod');
5637
- const titleElementId = store.useState('titleElementId');
5638
- const role = store.useState('role');
5639
- const nestedDrawerOpen = nestedOpenDialogCount > 0;
5640
- const swipe = useDrawerViewportContext();
5641
- const swiping = swipe?.swiping ?? false;
5642
- const swipeStrength = swipe?.swipeStrength ?? null;
5643
- const {
5644
- snapPoints,
5645
- activeSnapPoint,
5646
- activeSnapPointOffset
5647
- } = useDrawerSnapPoints();
5648
- useDialogPortalContext();
5649
- const [popupHeight, setPopupHeight] = React.useState(0);
5650
- const popupHeightRef = React.useRef(0);
5651
- const measureHeight = useStableCallback(() => {
5652
- const popupElement = store.context.popupRef.current;
5653
- if (!popupElement) {
5654
- return;
5655
- }
5656
- const offsetHeight = popupElement.offsetHeight;
5657
-
5658
- // Only skip while the element is still actually stretched beyond its last measured height.
5659
- if (popupHeightRef.current > 0 && frontmostHeight > popupHeightRef.current && offsetHeight > popupHeightRef.current) {
5660
- return;
5661
- }
5662
- const keepHeightWhileNested = popupHeightRef.current > 0 && hasNestedDrawer;
5663
- if (keepHeightWhileNested) {
5664
- const oldHeight = popupHeightRef.current;
5665
- setPopupHeight(oldHeight);
5666
- onPopupHeightChange(oldHeight);
5667
- return;
5668
- }
5669
- const nextHeight = offsetHeight;
5670
- if (nextHeight === popupHeightRef.current) {
5671
- return;
5672
- }
5673
- popupHeightRef.current = nextHeight;
5674
- setPopupHeight(nextHeight);
5675
- onPopupHeightChange(nextHeight);
5676
- });
5677
- useIsoLayoutEffect(() => {
5678
- if (!mounted) {
5679
- popupHeightRef.current = 0;
5680
- setPopupHeight(0);
5681
- onPopupHeightChange(0);
5682
- return undefined;
5683
- }
5684
- const popupElement = store.context.popupRef.current;
5685
- if (!popupElement) {
5686
- return undefined;
5687
- }
5688
- removeCSSVariableInheritance();
5689
- measureHeight();
5690
- if (typeof ResizeObserver !== 'function') {
5691
- return undefined;
5692
- }
5693
- const resizeObserver = new ResizeObserver(measureHeight);
5694
- resizeObserver.observe(popupElement);
5695
- return () => {
5696
- resizeObserver.disconnect();
5697
- };
5698
- }, [measureHeight, mounted, nestedDrawerOpen, onPopupHeightChange, store.context.popupRef]);
5699
- useIsoLayoutEffect(() => {
5700
- const popupRef = store.context.popupRef;
5701
- const syncNestedSwipeProgress = () => {
5702
- const popupElement = popupRef.current;
5703
- if (!popupElement) {
5704
- return;
5444
+ }, [measureHeight, mounted, nestedDrawerOpen, onPopupHeightChange, store.context.popupRef]);
5445
+ useIsoLayoutEffect(() => {
5446
+ const popupRef = store.context.popupRef;
5447
+ const syncNestedSwipeProgress = () => {
5448
+ const popupElement = popupRef.current;
5449
+ if (!popupElement) {
5450
+ return;
5705
5451
  }
5706
5452
  const progress = nestedSwipeProgressStore.getSnapshot();
5707
5453
  if (progress > 0) {
@@ -5831,14 +5577,726 @@ const DrawerPopup = /*#__PURE__*/React.forwardRef(function DrawerPopup(component
5831
5577
  });
5832
5578
  if (process.env.NODE_ENV !== "production") DrawerPopup.displayName = "DrawerPopup";
5833
5579
 
5834
- /**
5835
- * A portal element that moves the popup to a different part of the DOM.
5836
- * By default, the portal element is appended to `<body>`.
5837
- * Renders a `<div>` element.
5838
- *
5839
- * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
5840
- */
5841
- const DrawerPortal = DialogPortal;
5580
+ function inertValue(value) {
5581
+ if (isReactVersionAtLeast(19)) {
5582
+ return value;
5583
+ }
5584
+ // compatibility with React < 19
5585
+ return value ? 'true' : undefined;
5586
+ }
5587
+
5588
+ const InternalBackdrop = /*#__PURE__*/React.forwardRef(function InternalBackdrop(props, ref) {
5589
+ const {
5590
+ cutout,
5591
+ ...otherProps
5592
+ } = props;
5593
+ let clipPath;
5594
+ if (cutout) {
5595
+ const rect = cutout?.getBoundingClientRect();
5596
+ clipPath = `polygon(
5597
+ 0% 0%,
5598
+ 100% 0%,
5599
+ 100% 100%,
5600
+ 0% 100%,
5601
+ 0% 0%,
5602
+ ${rect.left}px ${rect.top}px,
5603
+ ${rect.left}px ${rect.bottom}px,
5604
+ ${rect.right}px ${rect.bottom}px,
5605
+ ${rect.right}px ${rect.top}px,
5606
+ ${rect.left}px ${rect.top}px
5607
+ )`;
5608
+ }
5609
+ return /*#__PURE__*/jsx("div", {
5610
+ ref: ref,
5611
+ role: "presentation"
5612
+ // Ensures Floating UI's outside press detection runs, as it considers
5613
+ // it an element that existed when the popup rendered.
5614
+ ,
5615
+ "data-base-ui-inert": "",
5616
+ ...otherProps,
5617
+ style: {
5618
+ position: 'fixed',
5619
+ inset: 0,
5620
+ userSelect: 'none',
5621
+ WebkitUserSelect: 'none',
5622
+ clipPath
5623
+ }
5624
+ });
5625
+ });
5626
+ if (process.env.NODE_ENV !== "production") InternalBackdrop.displayName = "InternalBackdrop";
5627
+
5628
+ const DialogPortal = /*#__PURE__*/React.forwardRef(function DialogPortal(props, forwardedRef) {
5629
+ const {
5630
+ keepMounted = false,
5631
+ ...portalProps
5632
+ } = props;
5633
+ const {
5634
+ store
5635
+ } = useDialogRootContext();
5636
+ const mounted = store.useState('mounted');
5637
+ const modal = store.useState('modal');
5638
+ const open = store.useState('open');
5639
+ const shouldRender = mounted || keepMounted;
5640
+ if (!shouldRender) {
5641
+ return null;
5642
+ }
5643
+ return /*#__PURE__*/jsx(DialogPortalContext.Provider, {
5644
+ value: keepMounted,
5645
+ children: /*#__PURE__*/jsxs(FloatingPortal, {
5646
+ ref: forwardedRef,
5647
+ ...portalProps,
5648
+ children: [mounted && modal === true && /*#__PURE__*/jsx(InternalBackdrop, {
5649
+ ref: store.context.internalBackdropRef,
5650
+ inert: inertValue(!open)
5651
+ }), props.children]
5652
+ })
5653
+ });
5654
+ });
5655
+ if (process.env.NODE_ENV !== "production") DialogPortal.displayName = "DialogPortal";
5656
+
5657
+ /**
5658
+ * A portal element that moves the popup to a different part of the DOM.
5659
+ * By default, the portal element is appended to `<body>`.
5660
+ * Renders a `<div>` element.
5661
+ *
5662
+ * Documentation: [Base UI Drawer](https://base-ui.com/react/components/drawer)
5663
+ */
5664
+ const DrawerPortal = DialogPortal;
5665
+
5666
+ function useControlled({
5667
+ controlled,
5668
+ default: defaultProp,
5669
+ name,
5670
+ state = 'value'
5671
+ }) {
5672
+ // isControlled is ignored in the hook dependency lists as it should never change.
5673
+ const {
5674
+ current: isControlled
5675
+ } = React.useRef(controlled !== undefined);
5676
+ const [valueState, setValue] = React.useState(defaultProp);
5677
+ const value = isControlled ? controlled : valueState;
5678
+ if (process.env.NODE_ENV !== 'production') {
5679
+ React.useEffect(() => {
5680
+ if (isControlled !== (controlled !== undefined)) {
5681
+ console.error([`Base UI: A component is changing the ${isControlled ? '' : 'un'}controlled ${state} state of ${name} to be ${isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled ${name} ` + 'element for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n'));
5682
+ }
5683
+ }, [state, name, controlled]);
5684
+ const {
5685
+ current: defaultValue
5686
+ } = React.useRef(defaultProp);
5687
+ React.useEffect(() => {
5688
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
5689
+ if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
5690
+ console.error([`Base UI: A component is changing the default ${state} state of an uncontrolled ${name} after being initialized. ` + `To suppress this warning opt to use a controlled ${name}.`].join('\n'));
5691
+ }
5692
+ }, [JSON.stringify(defaultProp)]);
5693
+ }
5694
+ const setValueIfUncontrolled = React.useCallback(newValue => {
5695
+ if (!isControlled) {
5696
+ setValue(newValue);
5697
+ }
5698
+ }, []);
5699
+ return [value, setValueIfUncontrolled];
5700
+ }
5701
+
5702
+ function useOnFirstRender(fn) {
5703
+ const ref = React.useRef(true);
5704
+ if (ref.current) {
5705
+ ref.current = false;
5706
+ fn();
5707
+ }
5708
+ }
5709
+
5710
+ let originalHtmlStyles = {};
5711
+ let originalBodyStyles = {};
5712
+ let originalHtmlScrollBehavior = '';
5713
+ function hasInsetScrollbars(referenceElement) {
5714
+ if (typeof document === 'undefined') {
5715
+ return false;
5716
+ }
5717
+ const doc = ownerDocument(referenceElement);
5718
+ const win = getWindow(doc);
5719
+ return win.innerWidth - doc.documentElement.clientWidth > 0;
5720
+ }
5721
+ function supportsStableScrollbarGutter(referenceElement) {
5722
+ const supported = typeof CSS !== 'undefined' && CSS.supports && CSS.supports('scrollbar-gutter', 'stable');
5723
+ if (!supported || typeof document === 'undefined') {
5724
+ return false;
5725
+ }
5726
+ const doc = ownerDocument(referenceElement);
5727
+ const html = doc.documentElement;
5728
+ const body = doc.body;
5729
+ const scrollContainer = isOverflowElement(html) ? html : body;
5730
+ const originalScrollContainerOverflowY = scrollContainer.style.overflowY;
5731
+ const originalHtmlStyleGutter = html.style.scrollbarGutter;
5732
+ html.style.scrollbarGutter = 'stable';
5733
+ scrollContainer.style.overflowY = 'scroll';
5734
+ const before = scrollContainer.offsetWidth;
5735
+ scrollContainer.style.overflowY = 'hidden';
5736
+ const after = scrollContainer.offsetWidth;
5737
+ scrollContainer.style.overflowY = originalScrollContainerOverflowY;
5738
+ html.style.scrollbarGutter = originalHtmlStyleGutter;
5739
+ return before === after;
5740
+ }
5741
+ function preventScrollOverlayScrollbars(referenceElement) {
5742
+ const doc = ownerDocument(referenceElement);
5743
+ const html = doc.documentElement;
5744
+ const body = doc.body;
5745
+
5746
+ // If an `overflow` style is present on <html>, we need to lock it, because a lock on <body>
5747
+ // won't have any effect.
5748
+ // But if <body> has an `overflow` style (like `overflow-x: hidden`), we need to lock it
5749
+ // instead, as sticky elements shift otherwise.
5750
+ const elementToLock = isOverflowElement(html) ? html : body;
5751
+ const originalElementToLockStyles = {
5752
+ overflowY: elementToLock.style.overflowY,
5753
+ overflowX: elementToLock.style.overflowX
5754
+ };
5755
+ Object.assign(elementToLock.style, {
5756
+ overflowY: 'hidden',
5757
+ overflowX: 'hidden'
5758
+ });
5759
+ return () => {
5760
+ Object.assign(elementToLock.style, originalElementToLockStyles);
5761
+ };
5762
+ }
5763
+ function preventScrollInsetScrollbars(referenceElement) {
5764
+ const doc = ownerDocument(referenceElement);
5765
+ const html = doc.documentElement;
5766
+ const body = doc.body;
5767
+ const win = getWindow(html);
5768
+ let scrollTop = 0;
5769
+ let scrollLeft = 0;
5770
+ let updateGutterOnly = false;
5771
+ const resizeFrame = AnimationFrame.create();
5772
+
5773
+ // Pinch-zoom in Safari causes a shift. Just don't lock scroll if there's any pinch-zoom.
5774
+ if (isWebKit && (win.visualViewport?.scale ?? 1) !== 1) {
5775
+ return () => {};
5776
+ }
5777
+ function lockScroll() {
5778
+ /* DOM reads: */
5779
+
5780
+ const htmlStyles = win.getComputedStyle(html);
5781
+ const bodyStyles = win.getComputedStyle(body);
5782
+ const htmlScrollbarGutterValue = htmlStyles.scrollbarGutter || '';
5783
+ const hasBothEdges = htmlScrollbarGutterValue.includes('both-edges');
5784
+ const scrollbarGutterValue = hasBothEdges ? 'stable both-edges' : 'stable';
5785
+ scrollTop = html.scrollTop;
5786
+ scrollLeft = html.scrollLeft;
5787
+ originalHtmlStyles = {
5788
+ scrollbarGutter: html.style.scrollbarGutter,
5789
+ overflowY: html.style.overflowY,
5790
+ overflowX: html.style.overflowX
5791
+ };
5792
+ originalHtmlScrollBehavior = html.style.scrollBehavior;
5793
+ originalBodyStyles = {
5794
+ position: body.style.position,
5795
+ height: body.style.height,
5796
+ width: body.style.width,
5797
+ boxSizing: body.style.boxSizing,
5798
+ overflowY: body.style.overflowY,
5799
+ overflowX: body.style.overflowX,
5800
+ scrollBehavior: body.style.scrollBehavior
5801
+ };
5802
+ const isScrollableY = html.scrollHeight > html.clientHeight;
5803
+ const isScrollableX = html.scrollWidth > html.clientWidth;
5804
+ const hasConstantOverflowY = htmlStyles.overflowY === 'scroll' || bodyStyles.overflowY === 'scroll';
5805
+ const hasConstantOverflowX = htmlStyles.overflowX === 'scroll' || bodyStyles.overflowX === 'scroll';
5806
+
5807
+ // Values can be negative in Firefox
5808
+ const scrollbarWidth = Math.max(0, win.innerWidth - body.clientWidth);
5809
+ const scrollbarHeight = Math.max(0, win.innerHeight - body.clientHeight);
5810
+
5811
+ // Avoid shift due to the default <body> margin. This does cause elements to be clipped
5812
+ // with whitespace. Warn if <body> has margins?
5813
+ const marginY = parseFloat(bodyStyles.marginTop) + parseFloat(bodyStyles.marginBottom);
5814
+ const marginX = parseFloat(bodyStyles.marginLeft) + parseFloat(bodyStyles.marginRight);
5815
+ const elementToLock = isOverflowElement(html) ? html : body;
5816
+ updateGutterOnly = supportsStableScrollbarGutter(referenceElement);
5817
+
5818
+ /*
5819
+ * DOM writes:
5820
+ * Do not read the DOM past this point!
5821
+ */
5822
+
5823
+ if (updateGutterOnly) {
5824
+ html.style.scrollbarGutter = scrollbarGutterValue;
5825
+ elementToLock.style.overflowY = 'hidden';
5826
+ elementToLock.style.overflowX = 'hidden';
5827
+ return;
5828
+ }
5829
+ Object.assign(html.style, {
5830
+ scrollbarGutter: scrollbarGutterValue,
5831
+ overflowY: 'hidden',
5832
+ overflowX: 'hidden'
5833
+ });
5834
+ if (isScrollableY || hasConstantOverflowY) {
5835
+ html.style.overflowY = 'scroll';
5836
+ }
5837
+ if (isScrollableX || hasConstantOverflowX) {
5838
+ html.style.overflowX = 'scroll';
5839
+ }
5840
+ Object.assign(body.style, {
5841
+ position: 'relative',
5842
+ height: marginY || scrollbarHeight ? `calc(100dvh - ${marginY + scrollbarHeight}px)` : '100dvh',
5843
+ width: marginX || scrollbarWidth ? `calc(100vw - ${marginX + scrollbarWidth}px)` : '100vw',
5844
+ boxSizing: 'border-box',
5845
+ overflow: 'hidden',
5846
+ scrollBehavior: 'unset'
5847
+ });
5848
+ body.scrollTop = scrollTop;
5849
+ body.scrollLeft = scrollLeft;
5850
+ html.setAttribute('data-base-ui-scroll-locked', '');
5851
+ html.style.scrollBehavior = 'unset';
5852
+ }
5853
+ function cleanup() {
5854
+ Object.assign(html.style, originalHtmlStyles);
5855
+ Object.assign(body.style, originalBodyStyles);
5856
+ if (!updateGutterOnly) {
5857
+ html.scrollTop = scrollTop;
5858
+ html.scrollLeft = scrollLeft;
5859
+ html.removeAttribute('data-base-ui-scroll-locked');
5860
+ html.style.scrollBehavior = originalHtmlScrollBehavior;
5861
+ }
5862
+ }
5863
+ function handleResize() {
5864
+ cleanup();
5865
+ resizeFrame.request(lockScroll);
5866
+ }
5867
+ lockScroll();
5868
+ win.addEventListener('resize', handleResize);
5869
+ return () => {
5870
+ resizeFrame.cancel();
5871
+ cleanup();
5872
+ // Sometimes this cleanup can be run after test teardown
5873
+ // because it is called in a `setTimeout(fn, 0)`,
5874
+ // in which case `removeEventListener` wouldn't be available,
5875
+ // so we check for it to avoid test failures.
5876
+ if (typeof win.removeEventListener === 'function') {
5877
+ win.removeEventListener('resize', handleResize);
5878
+ }
5879
+ };
5880
+ }
5881
+ class ScrollLocker {
5882
+ lockCount = 0;
5883
+ restore = null;
5884
+ timeoutLock = Timeout.create();
5885
+ timeoutUnlock = Timeout.create();
5886
+ acquire(referenceElement) {
5887
+ this.lockCount += 1;
5888
+ if (this.lockCount === 1 && this.restore === null) {
5889
+ this.timeoutLock.start(0, () => this.lock(referenceElement));
5890
+ }
5891
+ return this.release;
5892
+ }
5893
+ release = () => {
5894
+ this.lockCount -= 1;
5895
+ if (this.lockCount === 0 && this.restore) {
5896
+ this.timeoutUnlock.start(0, this.unlock);
5897
+ }
5898
+ };
5899
+ unlock = () => {
5900
+ if (this.lockCount === 0 && this.restore) {
5901
+ this.restore?.();
5902
+ this.restore = null;
5903
+ }
5904
+ };
5905
+ lock(referenceElement) {
5906
+ if (this.lockCount === 0 || this.restore !== null) {
5907
+ return;
5908
+ }
5909
+ const doc = ownerDocument(referenceElement);
5910
+ const html = doc.documentElement;
5911
+ const htmlOverflowY = getWindow(html).getComputedStyle(html).overflowY;
5912
+
5913
+ // If the site author already hid overflow on <html>, respect it and bail out.
5914
+ if (htmlOverflowY === 'hidden' || htmlOverflowY === 'clip') {
5915
+ this.restore = NOOP;
5916
+ return;
5917
+ }
5918
+ const hasOverlayScrollbars = isIOS || !hasInsetScrollbars(referenceElement);
5919
+
5920
+ // On iOS, scroll locking does not work if the navbar is collapsed. Due to numerous
5921
+ // side effects and bugs that arise on iOS, it must be researched extensively before
5922
+ // being enabled to ensure it doesn't cause the following issues:
5923
+ // - Textboxes must scroll into view when focused, nor cause a glitchy scroll animation.
5924
+ // - The navbar must not force itself into view and cause layout shift.
5925
+ // - Scroll containers must not flicker upon closing a popup when it has an exit animation.
5926
+ this.restore = hasOverlayScrollbars ? preventScrollOverlayScrollbars(referenceElement) : preventScrollInsetScrollbars(referenceElement);
5927
+ }
5928
+ }
5929
+ const SCROLL_LOCKER = new ScrollLocker();
5930
+
5931
+ /**
5932
+ * Locks the scroll of the document when enabled.
5933
+ *
5934
+ * @param enabled - Whether to enable the scroll lock.
5935
+ * @param referenceElement - Element to use as a reference for lock calculations.
5936
+ */
5937
+ function useScrollLock(enabled = true, referenceElement = null) {
5938
+ useIsoLayoutEffect(() => {
5939
+ if (!enabled) {
5940
+ return undefined;
5941
+ }
5942
+ return SCROLL_LOCKER.acquire(referenceElement);
5943
+ }, [enabled, referenceElement]);
5944
+ }
5945
+
5946
+ /**
5947
+ * Provides a cross-browser way to determine the type of the pointer used to click.
5948
+ * Safari and Firefox do not provide the PointerEvent to the click handler (they use MouseEvent) yet.
5949
+ * Additionally, this implementation detects if the click was triggered by the keyboard.
5950
+ *
5951
+ * @param handler The function to be called when the button is clicked. The first parameter is the original event and the second parameter is the pointer type.
5952
+ */
5953
+ function useEnhancedClickHandler(handler) {
5954
+ const lastClickInteractionTypeRef = React.useRef('');
5955
+ const handlePointerDown = React.useCallback(event => {
5956
+ if (event.defaultPrevented) {
5957
+ return;
5958
+ }
5959
+ lastClickInteractionTypeRef.current = event.pointerType;
5960
+ handler(event, event.pointerType);
5961
+ }, [handler]);
5962
+ const handleClick = React.useCallback(event => {
5963
+ // event.detail has the number of clicks performed on the element. 0 means it was triggered by the keyboard.
5964
+ if (event.detail === 0) {
5965
+ handler(event, 'keyboard');
5966
+ return;
5967
+ }
5968
+ if ('pointerType' in event) {
5969
+ // Chrome and Edge correctly use PointerEvent
5970
+ handler(event, event.pointerType);
5971
+ } else {
5972
+ handler(event, lastClickInteractionTypeRef.current);
5973
+ }
5974
+ lastClickInteractionTypeRef.current = '';
5975
+ }, [handler]);
5976
+ return {
5977
+ onClick: handleClick,
5978
+ onPointerDown: handlePointerDown
5979
+ };
5980
+ }
5981
+
5982
+ function useValueChanged(value, onChange) {
5983
+ const valueRef = React.useRef(value);
5984
+ const onChangeCallback = useStableCallback(onChange);
5985
+ useIsoLayoutEffect(() => {
5986
+ if (valueRef.current === value) {
5987
+ return;
5988
+ }
5989
+ onChangeCallback(valueRef.current);
5990
+ }, [value, onChangeCallback]);
5991
+ useIsoLayoutEffect(() => {
5992
+ valueRef.current = value;
5993
+ }, [value]);
5994
+ }
5995
+
5996
+ /**
5997
+ * Determines the interaction type (keyboard, mouse, touch, etc.) that opened the component.
5998
+ *
5999
+ * @param open The open state of the component.
6000
+ */
6001
+ function useOpenInteractionType(open) {
6002
+ const [openMethod, setOpenMethod] = React.useState(null);
6003
+ const handleTriggerClick = useStableCallback((_, interactionType) => {
6004
+ if (!open) {
6005
+ setOpenMethod(interactionType || (
6006
+ // On iOS Safari, the hitslop around touch targets means tapping outside an element's
6007
+ // bounds does not fire `pointerdown` but does fire `mousedown`. The `interactionType`
6008
+ // will be "" in that case.
6009
+ isIOS ? 'touch' : ''));
6010
+ }
6011
+ });
6012
+ useValueChanged(open, previousOpen => {
6013
+ if (previousOpen && !open) {
6014
+ setOpenMethod(null);
6015
+ }
6016
+ });
6017
+ const {
6018
+ onClick,
6019
+ onPointerDown
6020
+ } = useEnhancedClickHandler(handleTriggerClick);
6021
+ return React.useMemo(() => ({
6022
+ openMethod,
6023
+ triggerProps: {
6024
+ onClick,
6025
+ onPointerDown
6026
+ }
6027
+ }), [openMethod, onClick, onPointerDown]);
6028
+ }
6029
+
6030
+ function useDialogRoot(params) {
6031
+ const {
6032
+ store,
6033
+ parentContext,
6034
+ actionsRef
6035
+ } = params;
6036
+ const open = store.useState('open');
6037
+ const disablePointerDismissal = store.useState('disablePointerDismissal');
6038
+ const modal = store.useState('modal');
6039
+ const popupElement = store.useState('popupElement');
6040
+ const {
6041
+ openMethod,
6042
+ triggerProps
6043
+ } = useOpenInteractionType(open);
6044
+ useImplicitActiveTrigger(store);
6045
+ const {
6046
+ forceUnmount
6047
+ } = useOpenStateTransitions(open, store);
6048
+ const createDialogEventDetails = useStableCallback(reason => {
6049
+ const details = createChangeEventDetails(reason);
6050
+ details.preventUnmountOnClose = () => {
6051
+ store.set('preventUnmountingOnClose', true);
6052
+ };
6053
+ return details;
6054
+ });
6055
+ const handleImperativeClose = React.useCallback(() => {
6056
+ store.setOpen(false, createDialogEventDetails(imperativeAction));
6057
+ }, [store, createDialogEventDetails]);
6058
+ React.useImperativeHandle(actionsRef, () => ({
6059
+ unmount: forceUnmount,
6060
+ close: handleImperativeClose
6061
+ }), [forceUnmount, handleImperativeClose]);
6062
+ const floatingRootContext = useSyncedFloatingRootContext({
6063
+ popupStore: store,
6064
+ onOpenChange: store.setOpen,
6065
+ treatPopupAsFloatingElement: true,
6066
+ noEmit: true
6067
+ });
6068
+ const [ownNestedOpenDialogs, setOwnNestedOpenDialogs] = React.useState(0);
6069
+ const isTopmost = ownNestedOpenDialogs === 0;
6070
+ const role = useRole(floatingRootContext);
6071
+ const dismiss = useDismiss(floatingRootContext, {
6072
+ outsidePressEvent() {
6073
+ if (store.context.internalBackdropRef.current || store.context.backdropRef.current) {
6074
+ return 'intentional';
6075
+ }
6076
+ // Ensure `aria-hidden` on outside elements is removed immediately
6077
+ // on outside press when trapping focus.
6078
+ return {
6079
+ mouse: modal === 'trap-focus' ? 'sloppy' : 'intentional',
6080
+ touch: 'sloppy'
6081
+ };
6082
+ },
6083
+ outsidePress(event) {
6084
+ if (!store.context.outsidePressEnabledRef.current) {
6085
+ return false;
6086
+ }
6087
+
6088
+ // For mouse events, only accept left button (button 0)
6089
+ // For touch events, a single touch is equivalent to left button
6090
+ if ('button' in event && event.button !== 0) {
6091
+ return false;
6092
+ }
6093
+ if ('touches' in event && event.touches.length !== 1) {
6094
+ return false;
6095
+ }
6096
+ const target = getTarget(event);
6097
+ if (isTopmost && !disablePointerDismissal) {
6098
+ const eventTarget = target;
6099
+ // Only close if the click occurred on the dialog's owning backdrop.
6100
+ // This supports multiple modal dialogs that aren't nested in the React tree:
6101
+ // https://github.com/mui/base-ui/issues/1320
6102
+ if (modal) {
6103
+ return store.context.internalBackdropRef.current || store.context.backdropRef.current ? store.context.internalBackdropRef.current === eventTarget || store.context.backdropRef.current === eventTarget || contains(eventTarget, popupElement) && !eventTarget?.hasAttribute('data-base-ui-portal') : true;
6104
+ }
6105
+ return true;
6106
+ }
6107
+ return false;
6108
+ },
6109
+ escapeKey: isTopmost
6110
+ });
6111
+ useScrollLock(open && modal === true, popupElement);
6112
+ const {
6113
+ getReferenceProps,
6114
+ getFloatingProps,
6115
+ getTriggerProps
6116
+ } = useInteractions([role, dismiss]);
6117
+
6118
+ // Listen for nested open/close events on this store to maintain the count
6119
+ store.useContextCallback('onNestedDialogOpen', ownChildrenCount => {
6120
+ setOwnNestedOpenDialogs(ownChildrenCount + 1);
6121
+ });
6122
+ store.useContextCallback('onNestedDialogClose', () => {
6123
+ setOwnNestedOpenDialogs(0);
6124
+ });
6125
+
6126
+ // Notify parent of our open/close state using parent callbacks, if any
6127
+ React.useEffect(() => {
6128
+ if (parentContext?.onNestedDialogOpen && open) {
6129
+ parentContext.onNestedDialogOpen(ownNestedOpenDialogs);
6130
+ }
6131
+ if (parentContext?.onNestedDialogClose && !open) {
6132
+ parentContext.onNestedDialogClose();
6133
+ }
6134
+ return () => {
6135
+ if (parentContext?.onNestedDialogClose && open) {
6136
+ parentContext.onNestedDialogClose();
6137
+ }
6138
+ };
6139
+ }, [open, parentContext, ownNestedOpenDialogs]);
6140
+ const activeTriggerProps = React.useMemo(() => getReferenceProps(triggerProps), [getReferenceProps, triggerProps]);
6141
+ const inactiveTriggerProps = React.useMemo(() => getTriggerProps(triggerProps), [getTriggerProps, triggerProps]);
6142
+ const popupProps = React.useMemo(() => getFloatingProps(), [getFloatingProps]);
6143
+ store.useSyncedValues({
6144
+ openMethod,
6145
+ activeTriggerProps,
6146
+ inactiveTriggerProps,
6147
+ popupProps,
6148
+ floatingRootContext,
6149
+ nestedOpenDialogCount: ownNestedOpenDialogs
6150
+ });
6151
+ }
6152
+
6153
+ const selectors = {
6154
+ ...popupStoreSelectors,
6155
+ modal: createSelector(state => state.modal),
6156
+ nested: createSelector(state => state.nested),
6157
+ nestedOpenDialogCount: createSelector(state => state.nestedOpenDialogCount),
6158
+ disablePointerDismissal: createSelector(state => state.disablePointerDismissal),
6159
+ openMethod: createSelector(state => state.openMethod),
6160
+ descriptionElementId: createSelector(state => state.descriptionElementId),
6161
+ titleElementId: createSelector(state => state.titleElementId),
6162
+ viewportElement: createSelector(state => state.viewportElement),
6163
+ role: createSelector(state => state.role)
6164
+ };
6165
+ class DialogStore extends ReactStore {
6166
+ constructor(initialState) {
6167
+ super(createInitialState(initialState), {
6168
+ popupRef: /*#__PURE__*/React.createRef(),
6169
+ backdropRef: /*#__PURE__*/React.createRef(),
6170
+ internalBackdropRef: /*#__PURE__*/React.createRef(),
6171
+ outsidePressEnabledRef: {
6172
+ current: true
6173
+ },
6174
+ triggerElements: new PopupTriggerMap(),
6175
+ onOpenChange: undefined,
6176
+ onOpenChangeComplete: undefined
6177
+ }, selectors);
6178
+ }
6179
+ setOpen = (nextOpen, eventDetails) => {
6180
+ eventDetails.preventUnmountOnClose = () => {
6181
+ this.set('preventUnmountingOnClose', true);
6182
+ };
6183
+ if (!nextOpen && eventDetails.trigger == null && this.state.activeTriggerId != null) {
6184
+ // When closing the dialog, pass the old trigger to the onOpenChange event
6185
+ // so it's not reset too early (potentially causing focus issues in controlled scenarios).
6186
+ eventDetails.trigger = this.state.activeTriggerElement ?? undefined;
6187
+ }
6188
+ this.context.onOpenChange?.(nextOpen, eventDetails);
6189
+ if (eventDetails.isCanceled) {
6190
+ return;
6191
+ }
6192
+ const details = {
6193
+ open: nextOpen,
6194
+ nativeEvent: eventDetails.event,
6195
+ reason: eventDetails.reason,
6196
+ nested: this.state.nested
6197
+ };
6198
+ this.state.floatingRootContext.context.events?.emit('openchange', details);
6199
+ const updatedState = {
6200
+ open: nextOpen
6201
+ };
6202
+
6203
+ // If a popup is closing, the `trigger` may be null.
6204
+ // We want to keep the previous value so that exit animations are played and focus is returned correctly.
6205
+ const newTriggerId = eventDetails.trigger?.id ?? null;
6206
+ if (newTriggerId || nextOpen) {
6207
+ updatedState.activeTriggerId = newTriggerId;
6208
+ updatedState.activeTriggerElement = eventDetails.trigger ?? null;
6209
+ }
6210
+ this.update(updatedState);
6211
+ };
6212
+ }
6213
+ function createInitialState(initialState = {}) {
6214
+ return {
6215
+ ...createInitialPopupStoreState(),
6216
+ modal: true,
6217
+ disablePointerDismissal: false,
6218
+ popupElement: null,
6219
+ viewportElement: null,
6220
+ descriptionElementId: undefined,
6221
+ titleElementId: undefined,
6222
+ openMethod: null,
6223
+ nested: false,
6224
+ nestedOpenDialogCount: 0,
6225
+ role: 'dialog',
6226
+ ...initialState
6227
+ };
6228
+ }
6229
+
6230
+ /**
6231
+ * Groups all parts of the dialog.
6232
+ * Doesn’t render its own HTML element.
6233
+ *
6234
+ * Documentation: [Base UI Dialog](https://base-ui.com/react/components/dialog)
6235
+ */
6236
+ function DialogRoot(props) {
6237
+ const {
6238
+ children,
6239
+ open: openProp,
6240
+ defaultOpen = false,
6241
+ onOpenChange,
6242
+ onOpenChangeComplete,
6243
+ disablePointerDismissal = false,
6244
+ modal = true,
6245
+ actionsRef,
6246
+ handle,
6247
+ triggerId: triggerIdProp,
6248
+ defaultTriggerId: defaultTriggerIdProp = null
6249
+ } = props;
6250
+ const parentDialogRootContext = useDialogRootContext(true);
6251
+ const nested = Boolean(parentDialogRootContext);
6252
+ const store = useRefWithInit(() => {
6253
+ return handle?.store ?? new DialogStore({
6254
+ open: defaultOpen,
6255
+ openProp,
6256
+ activeTriggerId: defaultTriggerIdProp,
6257
+ triggerIdProp,
6258
+ modal,
6259
+ disablePointerDismissal,
6260
+ nested
6261
+ });
6262
+ }).current;
6263
+
6264
+ // Support initially open state when uncontrolled
6265
+ useOnFirstRender(() => {
6266
+ if (openProp === undefined && store.state.open === false && defaultOpen === true) {
6267
+ store.update({
6268
+ open: true,
6269
+ activeTriggerId: defaultTriggerIdProp
6270
+ });
6271
+ }
6272
+ });
6273
+ store.useControlledProp('openProp', openProp);
6274
+ store.useControlledProp('triggerIdProp', triggerIdProp);
6275
+ store.useSyncedValues({
6276
+ disablePointerDismissal,
6277
+ nested,
6278
+ modal
6279
+ });
6280
+ store.useContextCallback('onOpenChange', onOpenChange);
6281
+ store.useContextCallback('onOpenChangeComplete', onOpenChangeComplete);
6282
+ const payload = store.useState('payload');
6283
+ useDialogRoot({
6284
+ store,
6285
+ actionsRef,
6286
+ parentContext: parentDialogRootContext?.store.context,
6287
+ onOpenChange,
6288
+ triggerIdProp
6289
+ });
6290
+ const contextValue = React.useMemo(() => ({
6291
+ store
6292
+ }), [store]);
6293
+ return /*#__PURE__*/jsx(DialogRootContext.Provider, {
6294
+ value: contextValue,
6295
+ children: typeof children === 'function' ? children({
6296
+ payload
6297
+ }) : children
6298
+ });
6299
+ }
5842
6300
 
5843
6301
  var _DrawerProviderReport, _DrawerProviderReport2;
5844
6302
  /**
@@ -6059,4 +6517,4 @@ function DrawerProviderReporter() {
6059
6517
  return null;
6060
6518
  }
6061
6519
 
6062
- export { CommonPopupDataAttributes as $, listNavigation as A, createGridCellMap as B, isListIndexDisabled as C, DrawerPortal as D, getGridNavigatedIndex as E, FloatingRootStore as F, getGridCellIndices as G, getGridCellIndexOfCorner as H, ARROW_DOWN$1 as I, ARROW_LEFT$1 as J, ARROW_RIGHT$1 as K, findNonDisabledListIndex as L, getTarget as M, focusOut as N, enqueueFocus as O, PopupTriggerMap as P, isVirtualClick as Q, isVirtualPointerEvent as R, SafeReact as S, ARROW_UP$1 as T, isElementVisible as U, useDialogRootContext as V, closePress as W, useTriggerDataForwarding as X, useInteractions as Y, CLICK_TRIGGER_IDENTIFIER as Z, triggerOpenStateMapping as _, DrawerBackdrop as a, popupStateMapping as a0, transitionStatusMapping as a1, useDialogPortalContext as a2, DialogStore as a3, imperativeAction as a4, createSelector as a5, useControlled as a6, useStore as a7, useTransitionStatus as a8, useOpenInteractionType as a9, chipRemovePress as aA, useDrawerProviderContext as aB, DrawerBackdropCssVars as aC, DrawerPopupCssVars as aD, DrawerProviderContext as aE, clamp as aF, useDrawerRootContext as aG, useTriggerRegistration as aH, DrawerPopupDataAttributes as aI, swipe as aJ, useDrawerSnapPoints as aK, DrawerViewportContext as aL, BASE_UI_SWIPE_IGNORE_SELECTOR as aM, TransitionStatusDataAttributes as aN, DRAWER_CONTENT_ATTRIBUTE as aO, useOnMount as aP, Timeout as aQ, isIOS as aR, inputBlur as aS, inputPaste as aT, incrementPress as aU, decrementPress as aV, keyboard as aW, scrub as aX, isWebKit as aY, createGenericEventDetails as aa, inputClear as ab, inputChange as ac, outsidePress as ad, itemPress as ae, useOpenChangeComplete as af, useValueChanged as ag, none as ah, inputPress as ai, useDismiss as aj, useOnFirstRender as ak, visuallyHiddenInput as al, visuallyHidden as am, Store as an, pressableTriggerOpenStateMapping as ao, isAndroid as ap, isFirefox as aq, escapeKey as ar, clearPress as as, FloatingPortal as at, DISABLED_TRANSITIONS_STYLE as au, useScrollLock as av, InternalBackdrop as aw, inertValue as ax, DROPDOWN_COLLISION_AVOIDANCE as ay, FloatingFocusManager as az, DrawerPopup as b, DrawerContent as c, DrawerRoot as d, useIsoLayoutEffect as e, useId as f, useAnimationFrame as g, useTimeout as h, isMouseLikePointerType as i, isTypeableElement as j, createChangeEventDetails as k, isClickLikeEvent as l, useFloatingParentNodeId as m, useFloatingTree as n, getFloatingFocusElement as o, useValueAsRef as p, isTypeableCombobox as q, isIndexOutOfListBounds as r, getMinListIndex as s, triggerPress as t, useStableCallback as u, getMaxListIndex as v, activeElement as w, ownerDocument as x, contains as y, stopEvent as z };
6520
+ export { ARROW_RIGHT$1 as $, isSafari as A, ownerDocument as B, escapeKey as C, DrawerPortal as D, useOnMount as E, FloatingRootStore as F, Timeout as G, triggerHover as H, getNodeChildren as I, useValueAsRef as J, getFloatingFocusElement as K, isTypeableCombobox as L, isIndexOutOfListBounds as M, getMinListIndex as N, getMaxListIndex as O, PopupTriggerMap as P, stopEvent as Q, listNavigation as R, SafeReact as S, TYPEABLE_SELECTOR as T, createGridCellMap as U, isListIndexDisabled as V, getGridNavigatedIndex as W, getGridCellIndices as X, getGridCellIndexOfCorner as Y, ARROW_DOWN$1 as Z, ARROW_LEFT$1 as _, DrawerBackdrop as a, DialogStore as a$, findNonDisabledListIndex as a0, focusOut as a1, enqueueFocus as a2, isVirtualClick as a3, isVirtualPointerEvent as a4, ARROW_UP$1 as a5, isElementVisible as a6, useTriggerDataForwarding as a7, useInteractions as a8, CLICK_TRIGGER_IDENTIFIER as a9, useFloatingNodeId as aA, useOpenInteractionType as aB, useImplicitActiveTrigger as aC, useOpenStateTransitions as aD, useScrollLock as aE, imperativeAction as aF, useSyncedFloatingRootContext as aG, useDismiss as aH, useRole as aI, TYPEAHEAD_RESET_MS as aJ, FloatingTree as aK, fastComponentRef as aL, cancelOpen as aM, getTabbableBeforeElement as aN, isOutsideEvent as aO, getTabbableAfterElement as aP, getNextTabbable as aQ, pressableTriggerOpenStateMapping as aR, FocusGuard as aS, PATIENT_CLICK_THRESHOLD as aT, useTriggerRegistration as aU, useDrawerProviderContext as aV, DrawerBackdropCssVars as aW, DrawerPopupCssVars as aX, DrawerProviderContext as aY, CommonPopupDataAttributes as aZ, useDialogPortalContext as a_, triggerOpenStateMapping as aa, itemPress as ab, transitionStatusMapping as ac, DISABLED_TRANSITIONS_STYLE as ad, popupStateMapping as ae, useOpenChangeComplete as af, COMPOSITE_KEYS as ag, outsidePress as ah, FloatingFocusManager as ai, FloatingPortal as aj, useAnimationsFinished as ak, POPUP_COLLISION_AVOIDANCE as al, InternalBackdrop as am, inertValue as an, FloatingNode as ao, DROPDOWN_COLLISION_AVOIDANCE as ap, siblingOpen as aq, useControlled as ar, useTransitionStatus as as, popupStoreSelectors as at, createSelector as au, ReactStore as av, createInitialPopupStoreState as aw, FloatingTreeStore as ax, fastComponent as ay, useOnFirstRender as az, DrawerPopup as b, clamp as b0, useDrawerRootContext as b1, DrawerPopupDataAttributes as b2, swipe as b3, useDrawerSnapPoints as b4, DrawerViewportContext as b5, BASE_UI_SWIPE_IGNORE_SELECTOR as b6, TransitionStatusDataAttributes as b7, DRAWER_CONTENT_ATTRIBUTE as b8, useStore as b9, createGenericEventDetails as ba, inputClear as bb, inputChange as bc, useValueChanged as bd, none as be, inputPress as bf, visuallyHiddenInput as bg, visuallyHidden as bh, Store as bi, isAndroid as bj, isFirefox as bk, clearPress as bl, chipRemovePress as bm, isIOS as bn, inputBlur as bo, inputPaste as bp, incrementPress as bq, decrementPress as br, keyboard as bs, scrub as bt, isWebKit as bu, DrawerContent as c, DrawerRoot as d, useStableCallback as e, useDialogRootContext as f, createChangeEventDetails as g, closePress as h, useId as i, isMouseLikePointerType as j, useAnimationFrame as k, useTimeout as l, isTypeableElement as m, isClickLikeEvent as n, useFloatingParentNodeId as o, useFloatingTree as p, getTarget as q, matchesFocusVisible as r, isTargetInsideEnabledTrigger as s, triggerPress as t, useIsoLayoutEffect as u, triggerFocus as v, createAttribute as w, activeElement as x, contains as y, isMac as z };