@jobber/components 8.14.1 → 8.16.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 (46) hide show
  1. package/dist/Autocomplete-es.js +1 -1
  2. package/dist/BottomSheet-cjs.js +109 -77
  3. package/dist/BottomSheet-es.js +103 -75
  4. package/dist/Card/index.cjs +2 -3
  5. package/dist/Card/index.mjs +2 -3
  6. package/dist/DataDump/index.cjs +2 -3
  7. package/dist/DataDump/index.mjs +2 -3
  8. package/dist/InputNumberExperimental-cjs.js +16 -17
  9. package/dist/InputNumberExperimental-es.js +2 -3
  10. package/dist/Menu/index.cjs +2 -3
  11. package/dist/Menu/index.mjs +2 -3
  12. package/dist/Menu-cjs.js +11 -56
  13. package/dist/Menu-es.js +4 -49
  14. package/dist/MenuSubmenuTrigger-cjs.js +1608 -1793
  15. package/dist/MenuSubmenuTrigger-es.js +1340 -1522
  16. package/dist/Modal/index.mjs +1 -1
  17. package/dist/NumberFieldInput-cjs.js +53 -54
  18. package/dist/NumberFieldInput-es.js +2 -3
  19. package/dist/Page/index.cjs +2 -3
  20. package/dist/Page/index.mjs +2 -3
  21. package/dist/{DrawerRoot-cjs.js → ScrollAreaViewport-cjs.js} +5488 -4449
  22. package/dist/{DrawerRoot-es.js → ScrollAreaViewport-es.js} +5423 -4393
  23. package/dist/{useValueChanged-cjs.js → clamp-cjs.js} +573 -321
  24. package/dist/{useValueChanged-es.js → clamp-es.js} +569 -322
  25. package/dist/floating-ui.react-es.js +1 -1
  26. package/dist/index.cjs +2 -3
  27. package/dist/index.mjs +2 -3
  28. package/dist/primitives/BottomSheet/BottomSheet.d.ts +15 -16
  29. package/dist/primitives/BottomSheet/BottomSheet.types.d.ts +27 -0
  30. package/dist/primitives/BottomSheet/BottomSheet.utils.d.ts +6 -0
  31. package/dist/primitives/BottomSheet/index.cjs +8 -3
  32. package/dist/primitives/BottomSheet/index.d.ts +1 -0
  33. package/dist/primitives/BottomSheet/index.mjs +8 -3
  34. package/dist/primitives/InputNumberExperimental/index.cjs +2 -3
  35. package/dist/primitives/InputNumberExperimental/index.mjs +2 -3
  36. package/dist/primitives/index.cjs +8 -9
  37. package/dist/primitives/index.mjs +8 -9
  38. package/dist/styles.css +62 -1
  39. package/dist/unstyledPrimitives/index.cjs +6373 -5868
  40. package/dist/unstyledPrimitives/index.d.ts +2 -0
  41. package/dist/unstyledPrimitives/index.mjs +6325 -5822
  42. package/dist/useRenderElement-cjs.js +30 -30
  43. package/dist/useRenderElement-es.js +31 -31
  44. package/package.json +2 -2
  45. package/dist/useBaseUiId-cjs.js +0 -275
  46. package/dist/useBaseUiId-es.js +0 -251
@@ -1,35 +1,7 @@
1
- import { b as useRefWithInit, E as EMPTY_OBJECT } from './useRenderElement-es.js';
2
1
  import * as React from 'react';
2
+ import { a as useRefWithInit, E as EMPTY_OBJECT, f as formatErrorMessage, m as mergeProps, c as makeEventPreventable } from './useRenderElement-es.js';
3
3
  import * as ReactDOM from 'react-dom';
4
-
5
- let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
6
- /**
7
- * Present when the component is animating in.
8
- */
9
- TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
10
- /**
11
- * Present when the component is animating out.
12
- */
13
- TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
14
- return TransitionStatusDataAttributes;
15
- }({});
16
- const STARTING_HOOK = {
17
- [TransitionStatusDataAttributes.startingStyle]: ''
18
- };
19
- const ENDING_HOOK = {
20
- [TransitionStatusDataAttributes.endingStyle]: ''
21
- };
22
- const transitionStatusMapping = {
23
- transitionStatus(value) {
24
- if (value === 'starting') {
25
- return STARTING_HOOK;
26
- }
27
- if (value === 'ending') {
28
- return ENDING_HOOK;
29
- }
30
- return null;
31
- }
32
- };
4
+ import { a as isHTMLElement } from './floating-ui.utils.dom-es.js';
33
5
 
34
6
  // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
35
7
  const useInsertionEffect = React[`useInsertionEffect${Math.random().toFixed(1)}`.slice(0, -3)];
@@ -71,147 +43,187 @@ function assertNotCalled() {
71
43
  }
72
44
  }
73
45
 
74
- // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
75
- const SafeReact = {
76
- ...React
77
- };
46
+ function useControlled({
47
+ controlled,
48
+ default: defaultProp,
49
+ name,
50
+ state = 'value'
51
+ }) {
52
+ // isControlled is ignored in the hook dependency lists as it should never change.
53
+ const {
54
+ current: isControlled
55
+ } = React.useRef(controlled !== undefined);
56
+ const [valueState, setValue] = React.useState(defaultProp);
57
+ const value = isControlled ? controlled : valueState;
58
+ if (process.env.NODE_ENV !== 'production') {
59
+ React.useEffect(() => {
60
+ if (isControlled !== (controlled !== undefined)) {
61
+ 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'));
62
+ }
63
+ }, [state, name, controlled]);
64
+ const {
65
+ current: defaultValue
66
+ } = React.useRef(defaultProp);
67
+ React.useEffect(() => {
68
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
69
+ if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
70
+ 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'));
71
+ }
72
+ }, [JSON.stringify(defaultProp)]);
73
+ }
74
+ const setValueIfUncontrolled = React.useCallback(newValue => {
75
+ if (!isControlled) {
76
+ setValue(newValue);
77
+ }
78
+ }, []);
79
+ return [value, setValueIfUncontrolled];
80
+ }
78
81
 
79
82
  const noop = () => {};
80
83
  const useIsoLayoutEffect = typeof document !== 'undefined' ? React.useLayoutEffect : noop;
81
84
 
82
- const none = 'none';
83
- const triggerPress = 'trigger-press';
84
- const triggerHover = 'trigger-hover';
85
- const triggerFocus = 'trigger-focus';
86
- const outsidePress = 'outside-press';
87
- const itemPress = 'item-press';
88
- const closePress = 'close-press';
89
- const clearPress = 'clear-press';
90
- const chipRemovePress = 'chip-remove-press';
91
- const incrementPress = 'increment-press';
92
- const decrementPress = 'decrement-press';
93
- const inputChange = 'input-change';
94
- const inputClear = 'input-clear';
95
- const inputBlur = 'input-blur';
96
- const inputPaste = 'input-paste';
97
- const inputPress = 'input-press';
98
- const focusOut = 'focus-out';
99
- const escapeKey = 'escape-key';
100
- const closeWatcher = 'close-watcher';
101
- const listNavigation = 'list-navigation';
102
- const keyboard = 'keyboard';
103
- const scrub = 'scrub';
104
- const cancelOpen = 'cancel-open';
105
- const siblingOpen = 'sibling-open';
106
- const imperativeAction = 'imperative-action';
107
- const swipe = 'swipe';
85
+ const visuallyHiddenBase = {
86
+ clipPath: 'inset(50%)',
87
+ overflow: 'hidden',
88
+ whiteSpace: 'nowrap',
89
+ border: 0,
90
+ padding: 0,
91
+ width: 1,
92
+ height: 1,
93
+ margin: -1
94
+ };
95
+ const visuallyHidden = {
96
+ ...visuallyHiddenBase,
97
+ position: 'fixed',
98
+ top: 0,
99
+ left: 0
100
+ };
101
+ const visuallyHiddenInput = {
102
+ ...visuallyHiddenBase,
103
+ position: 'absolute'
104
+ };
108
105
 
109
- /**
110
- * Maps a change `reason` string to the corresponding native event type.
111
- */
106
+ const EMPTY$2 = [];
112
107
 
113
108
  /**
114
- * Details of custom change events emitted by Base UI components.
109
+ * A React.useEffect equivalent that runs once, when the component is mounted.
115
110
  */
111
+ function useOnMount(fn) {
112
+ // 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
113
+ /* eslint-disable react-hooks/exhaustive-deps */
114
+ React.useEffect(fn, EMPTY$2);
115
+ /* eslint-enable react-hooks/exhaustive-deps */
116
+ }
116
117
 
117
- /**
118
- * Details of custom generic events emitted by Base UI components.
119
- */
118
+ /** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
119
+ * a monomorphic `uint` type with `0` meaning empty.
120
+ * See warning note at:
121
+ * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
122
+ const EMPTY$1 = null;
123
+ let LAST_RAF = globalThis.requestAnimationFrame;
124
+ class Scheduler {
125
+ /* This implementation uses an array as a backing data-structure for frame callbacks.
126
+ * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
127
+ * never calls the native `cancelAnimationFrame` if there are no frames left. This can
128
+ * be much more efficient if there is a call pattern that alterns as
129
+ * "request-cancel-request-cancel-…".
130
+ * But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
131
+ * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
120
132
 
121
- /**
122
- * Creates a Base UI event details object with the given reason and utilities
123
- * for preventing Base UI's internal event handling.
124
- */
125
- function createChangeEventDetails(reason, event, trigger, customProperties) {
126
- let canceled = false;
127
- let allowPropagation = false;
128
- const custom = customProperties ?? EMPTY_OBJECT;
129
- const details = {
130
- reason,
131
- event: event ?? new Event('base-ui'),
132
- cancel() {
133
- canceled = true;
134
- },
135
- allowPropagation() {
136
- allowPropagation = true;
137
- },
138
- get isCanceled() {
139
- return canceled;
140
- },
141
- get isPropagationAllowed() {
142
- return allowPropagation;
143
- },
144
- trigger,
145
- ...custom
146
- };
147
- return details;
148
- }
149
- function createGenericEventDetails(reason, event, customProperties) {
150
- const custom = customProperties ?? EMPTY_OBJECT;
151
- const details = {
152
- reason,
153
- event: event ?? new Event('base-ui'),
154
- ...custom
155
- };
156
- return details;
157
- }
133
+ callbacks = [];
134
+ callbacksCount = 0;
135
+ nextId = 1;
136
+ startId = 1;
137
+ isScheduled = false;
138
+ tick = timestamp => {
139
+ this.isScheduled = false;
140
+ const currentCallbacks = this.callbacks;
141
+ const currentCallbacksCount = this.callbacksCount;
158
142
 
159
- let globalId = 0;
143
+ // Update these before iterating, callbacks could call `requestAnimationFrame` again.
144
+ this.callbacks = [];
145
+ this.callbacksCount = 0;
146
+ this.startId = this.nextId;
147
+ if (currentCallbacksCount > 0) {
148
+ for (let i = 0; i < currentCallbacks.length; i += 1) {
149
+ currentCallbacks[i]?.(timestamp);
150
+ }
151
+ }
152
+ };
153
+ request(fn) {
154
+ const id = this.nextId;
155
+ this.nextId += 1;
156
+ this.callbacks.push(fn);
157
+ this.callbacksCount += 1;
160
158
 
161
- // TODO React 17: Remove `useGlobalId` once React 17 support is removed
162
- function useGlobalId(idOverride, prefix = 'mui') {
163
- const [defaultId, setDefaultId] = React.useState(idOverride);
164
- const id = idOverride || defaultId;
165
- React.useEffect(() => {
166
- if (defaultId == null) {
167
- // Fallback to this default id when possible.
168
- // Use the incrementing value for client-side rendering only.
169
- // We can't use it server-side.
170
- // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
171
- globalId += 1;
172
- setDefaultId(`${prefix}-${globalId}`);
159
+ /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
160
+ * but there's no guarantee that the animation frame will actually run before the fake
161
+ * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
162
+ const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
163
+ if (!this.isScheduled || didRAFChange) {
164
+ requestAnimationFrame(this.tick);
165
+ this.isScheduled = true;
173
166
  }
174
- }, [defaultId, prefix]);
175
- return id;
167
+ return id;
168
+ }
169
+ cancel(id) {
170
+ const index = id - this.startId;
171
+ if (index < 0 || index >= this.callbacks.length) {
172
+ return;
173
+ }
174
+ this.callbacks[index] = null;
175
+ this.callbacksCount -= 1;
176
+ }
176
177
  }
177
- const maybeReactUseId = SafeReact.useId;
178
-
179
- /**
180
- *
181
- * @example <div id={useId()} />
182
- * @param idOverride
183
- * @returns {string}
184
- */
185
- function useId(idOverride, prefix) {
186
- // React.useId() is only available from React 17.0.0.
187
- if (maybeReactUseId !== undefined) {
188
- const reactId = maybeReactUseId();
189
- return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
178
+ const scheduler = new Scheduler();
179
+ class AnimationFrame {
180
+ static create() {
181
+ return new AnimationFrame();
190
182
  }
183
+ static request(fn) {
184
+ return scheduler.request(fn);
185
+ }
186
+ static cancel(id) {
187
+ return scheduler.cancel(id);
188
+ }
189
+ currentId = EMPTY$1;
191
190
 
192
- // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
193
- // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
194
- return useGlobalId(idOverride, prefix);
191
+ /**
192
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
193
+ */
194
+ request(fn) {
195
+ this.cancel();
196
+ this.currentId = scheduler.request(() => {
197
+ this.currentId = EMPTY$1;
198
+ fn();
199
+ });
200
+ }
201
+ cancel = () => {
202
+ if (this.currentId !== EMPTY$1) {
203
+ scheduler.cancel(this.currentId);
204
+ this.currentId = EMPTY$1;
205
+ }
206
+ };
207
+ disposeEffect = () => {
208
+ return this.cancel;
209
+ };
195
210
  }
196
211
 
197
- const EMPTY$2 = [];
198
-
199
212
  /**
200
- * A React.useEffect equivalent that runs once, when the component is mounted.
213
+ * A `requestAnimationFrame` with automatic cleanup and guard.
201
214
  */
202
- function useOnMount(fn) {
203
- // 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
204
- /* eslint-disable react-hooks/exhaustive-deps */
205
- React.useEffect(fn, EMPTY$2);
206
- /* eslint-enable react-hooks/exhaustive-deps */
215
+ function useAnimationFrame() {
216
+ const timeout = useRefWithInit(AnimationFrame.create).current;
217
+ useOnMount(timeout.disposeEffect);
218
+ return timeout;
207
219
  }
208
220
 
209
- const EMPTY$1 = 0;
221
+ const EMPTY = 0;
210
222
  class Timeout {
211
223
  static create() {
212
224
  return new Timeout();
213
225
  }
214
- currentId = EMPTY$1;
226
+ currentId = EMPTY;
215
227
 
216
228
  /**
217
229
  * Executes `fn` after `delay`, clearing any previously scheduled call.
@@ -219,17 +231,17 @@ class Timeout {
219
231
  start(delay, fn) {
220
232
  this.clear();
221
233
  this.currentId = setTimeout(() => {
222
- this.currentId = EMPTY$1;
234
+ this.currentId = EMPTY;
223
235
  fn();
224
236
  }, delay); /* Node.js types are enabled in development */
225
237
  }
226
238
  isStarted() {
227
- return this.currentId !== EMPTY$1;
239
+ return this.currentId !== EMPTY;
228
240
  }
229
241
  clear = () => {
230
- if (this.currentId !== EMPTY$1) {
242
+ if (this.currentId !== EMPTY) {
231
243
  clearTimeout(this.currentId);
232
- this.currentId = EMPTY$1;
244
+ this.currentId = EMPTY;
233
245
  }
234
246
  };
235
247
  disposeEffect = () => {
@@ -246,6 +258,30 @@ function useTimeout() {
246
258
  return timeout;
247
259
  }
248
260
 
261
+ /**
262
+ * Untracks the provided value by turning it into a ref to remove its reactivity.
263
+ *
264
+ * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
265
+ */
266
+ function useValueAsRef(value) {
267
+ const latest = useRefWithInit(createLatestRef, value).current;
268
+ latest.next = value;
269
+
270
+ // eslint-disable-next-line react-hooks/exhaustive-deps
271
+ useIsoLayoutEffect(latest.effect);
272
+ return latest;
273
+ }
274
+ function createLatestRef(value) {
275
+ const latest = {
276
+ current: value,
277
+ next: value,
278
+ effect: () => {
279
+ latest.current = latest.next;
280
+ }
281
+ };
282
+ return latest;
283
+ }
284
+
249
285
  const hasNavigator = typeof navigator !== 'undefined';
250
286
  const nav = getNavigatorData();
251
287
  const platform = getPlatform();
@@ -348,153 +384,126 @@ function ownerDocument(node) {
348
384
  return node?.ownerDocument || document;
349
385
  }
350
386
 
387
+ const none = 'none';
388
+ const triggerPress = 'trigger-press';
389
+ const triggerHover = 'trigger-hover';
390
+ const triggerFocus = 'trigger-focus';
391
+ const outsidePress = 'outside-press';
392
+ const itemPress = 'item-press';
393
+ const closePress = 'close-press';
394
+ const clearPress = 'clear-press';
395
+ const chipRemovePress = 'chip-remove-press';
396
+ const incrementPress = 'increment-press';
397
+ const decrementPress = 'decrement-press';
398
+ const inputChange = 'input-change';
399
+ const inputClear = 'input-clear';
400
+ const inputBlur = 'input-blur';
401
+ const inputPaste = 'input-paste';
402
+ const inputPress = 'input-press';
403
+ const focusOut = 'focus-out';
404
+ const escapeKey = 'escape-key';
405
+ const closeWatcher = 'close-watcher';
406
+ const listNavigation = 'list-navigation';
407
+ const keyboard = 'keyboard';
408
+ const pointer = 'pointer';
409
+ const scrub = 'scrub';
410
+ const cancelOpen = 'cancel-open';
411
+ const siblingOpen = 'sibling-open';
412
+ const imperativeAction = 'imperative-action';
413
+ const swipe = 'swipe';
414
+
351
415
  /**
352
- * Untracks the provided value by turning it into a ref to remove its reactivity.
353
- *
354
- * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
416
+ * Maps a change `reason` string to the corresponding native event type.
355
417
  */
356
- function useValueAsRef(value) {
357
- const latest = useRefWithInit(createLatestRef, value).current;
358
- latest.next = value;
359
418
 
360
- // eslint-disable-next-line react-hooks/exhaustive-deps
361
- useIsoLayoutEffect(latest.effect);
362
- return latest;
363
- }
364
- function createLatestRef(value) {
365
- const latest = {
366
- current: value,
367
- next: value,
368
- effect: () => {
369
- latest.current = latest.next;
370
- }
371
- };
372
- return latest;
373
- }
374
-
375
- /** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
376
- * a monomorphic `uint` type with `0` meaning empty.
377
- * See warning note at:
378
- * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
379
- const EMPTY = null;
380
- let LAST_RAF = globalThis.requestAnimationFrame;
381
- class Scheduler {
382
- /* This implementation uses an array as a backing data-structure for frame callbacks.
383
- * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
384
- * never calls the native `cancelAnimationFrame` if there are no frames left. This can
385
- * be much more efficient if there is a call pattern that alterns as
386
- * "request-cancel-request-cancel-…".
387
- * But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
388
- * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
389
-
390
- callbacks = [];
391
- callbacksCount = 0;
392
- nextId = 1;
393
- startId = 1;
394
- isScheduled = false;
395
- tick = timestamp => {
396
- this.isScheduled = false;
397
- const currentCallbacks = this.callbacks;
398
- const currentCallbacksCount = this.callbacksCount;
419
+ /**
420
+ * Details of custom change events emitted by Base UI components.
421
+ */
399
422
 
400
- // Update these before iterating, callbacks could call `requestAnimationFrame` again.
401
- this.callbacks = [];
402
- this.callbacksCount = 0;
403
- this.startId = this.nextId;
404
- if (currentCallbacksCount > 0) {
405
- for (let i = 0; i < currentCallbacks.length; i += 1) {
406
- currentCallbacks[i]?.(timestamp);
407
- }
408
- }
409
- };
410
- request(fn) {
411
- const id = this.nextId;
412
- this.nextId += 1;
413
- this.callbacks.push(fn);
414
- this.callbacksCount += 1;
423
+ /**
424
+ * Details of custom generic events emitted by Base UI components.
425
+ */
415
426
 
416
- /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
417
- * but there's no guarantee that the animation frame will actually run before the fake
418
- * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
419
- const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
420
- if (!this.isScheduled || didRAFChange) {
421
- requestAnimationFrame(this.tick);
422
- this.isScheduled = true;
423
- }
424
- return id;
425
- }
426
- cancel(id) {
427
- const index = id - this.startId;
428
- if (index < 0 || index >= this.callbacks.length) {
429
- return;
430
- }
431
- this.callbacks[index] = null;
432
- this.callbacksCount -= 1;
433
- }
427
+ /**
428
+ * Creates a Base UI event details object with the given reason and utilities
429
+ * for preventing Base UI's internal event handling.
430
+ */
431
+ function createChangeEventDetails(reason, event, trigger, customProperties) {
432
+ let canceled = false;
433
+ let allowPropagation = false;
434
+ const custom = customProperties ?? EMPTY_OBJECT;
435
+ const details = {
436
+ reason,
437
+ event: event ?? new Event('base-ui'),
438
+ cancel() {
439
+ canceled = true;
440
+ },
441
+ allowPropagation() {
442
+ allowPropagation = true;
443
+ },
444
+ get isCanceled() {
445
+ return canceled;
446
+ },
447
+ get isPropagationAllowed() {
448
+ return allowPropagation;
449
+ },
450
+ trigger,
451
+ ...custom
452
+ };
453
+ return details;
454
+ }
455
+ function createGenericEventDetails(reason, event, customProperties) {
456
+ const custom = customProperties ?? EMPTY_OBJECT;
457
+ const details = {
458
+ reason,
459
+ event: event ?? new Event('base-ui'),
460
+ ...custom
461
+ };
462
+ return details;
434
463
  }
435
- const scheduler = new Scheduler();
436
- class AnimationFrame {
437
- static create() {
438
- return new AnimationFrame();
439
- }
440
- static request(fn) {
441
- return scheduler.request(fn);
442
- }
443
- static cancel(id) {
444
- return scheduler.cancel(id);
445
- }
446
- currentId = EMPTY;
447
464
 
448
- /**
449
- * Executes `fn` after `delay`, clearing any previously scheduled call.
450
- */
451
- request(fn) {
452
- this.cancel();
453
- this.currentId = scheduler.request(() => {
454
- this.currentId = EMPTY;
455
- fn();
456
- });
457
- }
458
- cancel = () => {
459
- if (this.currentId !== EMPTY) {
460
- scheduler.cancel(this.currentId);
461
- this.currentId = EMPTY;
465
+ // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
466
+ const SafeReact = {
467
+ ...React
468
+ };
469
+
470
+ let globalId = 0;
471
+
472
+ // TODO React 17: Remove `useGlobalId` once React 17 support is removed
473
+ function useGlobalId(idOverride, prefix = 'mui') {
474
+ const [defaultId, setDefaultId] = React.useState(idOverride);
475
+ const id = idOverride || defaultId;
476
+ React.useEffect(() => {
477
+ if (defaultId == null) {
478
+ // Fallback to this default id when possible.
479
+ // Use the incrementing value for client-side rendering only.
480
+ // We can't use it server-side.
481
+ // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
482
+ globalId += 1;
483
+ setDefaultId(`${prefix}-${globalId}`);
462
484
  }
463
- };
464
- disposeEffect = () => {
465
- return this.cancel;
466
- };
485
+ }, [defaultId, prefix]);
486
+ return id;
467
487
  }
488
+ const maybeReactUseId = SafeReact.useId;
468
489
 
469
490
  /**
470
- * A `requestAnimationFrame` with automatic cleanup and guard.
491
+ *
492
+ * @example <div id={useId()} />
493
+ * @param idOverride
494
+ * @returns {string}
471
495
  */
472
- function useAnimationFrame() {
473
- const timeout = useRefWithInit(AnimationFrame.create).current;
474
- useOnMount(timeout.disposeEffect);
475
- return timeout;
476
- }
496
+ function useId(idOverride, prefix) {
497
+ // React.useId() is only available from React 17.0.0.
498
+ if (maybeReactUseId !== undefined) {
499
+ const reactId = maybeReactUseId();
500
+ return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
501
+ }
477
502
 
478
- const visuallyHiddenBase = {
479
- clipPath: 'inset(50%)',
480
- overflow: 'hidden',
481
- whiteSpace: 'nowrap',
482
- border: 0,
483
- padding: 0,
484
- width: 1,
485
- height: 1,
486
- margin: -1
487
- };
488
- const visuallyHidden = {
489
- ...visuallyHiddenBase,
490
- position: 'fixed',
491
- top: 0,
492
- left: 0
493
- };
494
- const visuallyHiddenInput = {
495
- ...visuallyHiddenBase,
496
- position: 'absolute'
497
- };
503
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
504
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
505
+ return useGlobalId(idOverride, prefix);
506
+ }
498
507
 
499
508
  /**
500
509
  * If the provided argument is a ref object, returns its `current` value.
@@ -570,6 +579,35 @@ function useTransitionStatus(open, enableIdleState = false, deferEndingState = f
570
579
  }), [mounted, transitionStatus]);
571
580
  }
572
581
 
582
+ let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
583
+ /**
584
+ * Present when the component is animating in.
585
+ */
586
+ TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
587
+ /**
588
+ * Present when the component is animating out.
589
+ */
590
+ TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
591
+ return TransitionStatusDataAttributes;
592
+ }({});
593
+ const STARTING_HOOK = {
594
+ [TransitionStatusDataAttributes.startingStyle]: ''
595
+ };
596
+ const ENDING_HOOK = {
597
+ [TransitionStatusDataAttributes.endingStyle]: ''
598
+ };
599
+ const transitionStatusMapping = {
600
+ transitionStatus(value) {
601
+ if (value === 'starting') {
602
+ return STARTING_HOOK;
603
+ }
604
+ if (value === 'ending') {
605
+ return ENDING_HOOK;
606
+ }
607
+ return null;
608
+ }
609
+ };
610
+
573
611
  /**
574
612
  * Executes a function once all animations have finished on the provided element.
575
613
  * @param elementOrRef - The element to watch for animations.
@@ -679,44 +717,13 @@ function useOpenChangeComplete(parameters) {
679
717
  }, [enabled, open, onComplete, runOnceAnimationsFinish]);
680
718
  }
681
719
 
682
- function clamp(val, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
683
- return Math.max(min, Math.min(val, max));
684
- }
685
-
686
- function useControlled({
687
- controlled,
688
- default: defaultProp,
689
- name,
690
- state = 'value'
691
- }) {
692
- // isControlled is ignored in the hook dependency lists as it should never change.
693
- const {
694
- current: isControlled
695
- } = React.useRef(controlled !== undefined);
696
- const [valueState, setValue] = React.useState(defaultProp);
697
- const value = isControlled ? controlled : valueState;
698
- if (process.env.NODE_ENV !== 'production') {
699
- React.useEffect(() => {
700
- if (isControlled !== (controlled !== undefined)) {
701
- 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'));
702
- }
703
- }, [state, name, controlled]);
704
- const {
705
- current: defaultValue
706
- } = React.useRef(defaultProp);
707
- React.useEffect(() => {
708
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
709
- if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
710
- 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'));
711
- }
712
- }, [JSON.stringify(defaultProp)]);
713
- }
714
- const setValueIfUncontrolled = React.useCallback(newValue => {
715
- if (!isControlled) {
716
- setValue(newValue);
717
- }
718
- }, []);
719
- return [value, setValueIfUncontrolled];
720
+ /**
721
+ * Wraps `useId` and prefixes generated `id`s with `base-ui-`
722
+ * @param {string | undefined} idOverride overrides the generated id when provided
723
+ * @returns {string | undefined}
724
+ */
725
+ function useBaseUiId(idOverride) {
726
+ return useId(idOverride, 'base-ui');
720
727
  }
721
728
 
722
729
  function useValueChanged(value, onChange) {
@@ -733,4 +740,244 @@ function useValueChanged(value, onChange) {
733
740
  }, [value]);
734
741
  }
735
742
 
736
- export { isReactEvent as $, keyboard as A, closePress as B, isMouseLikePointerType as C, useAnimationFrame as D, isClickLikeEvent as E, triggerPress as F, triggerFocus as G, isMac as H, isSafari as I, escapeKey as J, triggerHover as K, listNavigation as L, focusOut as M, isVirtualClick as N, isVirtualPointerEvent as O, itemPress as P, outsidePress as Q, useAnimationsFinished as R, SafeReact as S, Timeout as T, siblingOpen as U, imperativeAction as V, cancelOpen as W, TransitionStatusDataAttributes as X, isJSDOM as Y, resolveRef as Z, isWebKit as _, useStableCallback as a, AnimationFrame as a0, isAndroid as a1, closeWatcher as a2, swipe as a3, inputPress as a4, isFirefox as a5, clearPress as a6, chipRemovePress as a7, scrub as a8, useId as b, useTimeout as c, useTransitionStatus as d, useOpenChangeComplete as e, useOnMount as f, clamp as g, useControlled as h, useValueAsRef as i, isIOS as j, createChangeEventDetails as k, visuallyHidden as l, inputChange as m, none as n, inputClear as o, inputBlur as p, inputPaste as q, createGenericEventDetails as r, ownerDocument as s, transitionStatusMapping as t, useIsoLayoutEffect as u, visuallyHiddenInput as v, incrementPress as w, decrementPress as x, useValueChanged as y, stopEvent as z };
743
+ let set;
744
+ if (process.env.NODE_ENV !== 'production') {
745
+ set = new Set();
746
+ }
747
+ function error(...messages) {
748
+ if (process.env.NODE_ENV !== 'production') {
749
+ const messageKey = messages.join(' ');
750
+ if (!set.has(messageKey)) {
751
+ set.add(messageKey);
752
+ console.error(`Base UI: ${messageKey}`);
753
+ }
754
+ }
755
+ }
756
+
757
+ const CompositeRootContext = /*#__PURE__*/React.createContext(undefined);
758
+ if (process.env.NODE_ENV !== "production") CompositeRootContext.displayName = "CompositeRootContext";
759
+ function useCompositeRootContext(optional = false) {
760
+ const context = React.useContext(CompositeRootContext);
761
+ if (context === undefined && !optional) {
762
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.' : formatErrorMessage(16));
763
+ }
764
+ return context;
765
+ }
766
+
767
+ function useFocusableWhenDisabled(parameters) {
768
+ const {
769
+ focusableWhenDisabled,
770
+ disabled,
771
+ composite = false,
772
+ tabIndex: tabIndexProp = 0,
773
+ isNativeButton
774
+ } = parameters;
775
+ const isFocusableComposite = composite && focusableWhenDisabled !== false;
776
+ const isNonFocusableComposite = composite && focusableWhenDisabled === false;
777
+
778
+ // we can't explicitly assign `undefined` to any of these props because it
779
+ // would otherwise prevent subsequently merged props from setting them
780
+ const props = React.useMemo(() => {
781
+ const additionalProps = {
782
+ // allow Tabbing away from focusableWhenDisabled elements
783
+ onKeyDown(event) {
784
+ if (disabled && focusableWhenDisabled && event.key !== 'Tab') {
785
+ event.preventDefault();
786
+ }
787
+ }
788
+ };
789
+ if (!composite) {
790
+ additionalProps.tabIndex = tabIndexProp;
791
+ if (!isNativeButton && disabled) {
792
+ additionalProps.tabIndex = focusableWhenDisabled ? tabIndexProp : -1;
793
+ }
794
+ }
795
+ if (isNativeButton && (focusableWhenDisabled || isFocusableComposite) || !isNativeButton && disabled) {
796
+ additionalProps['aria-disabled'] = disabled;
797
+ }
798
+ if (isNativeButton && (!focusableWhenDisabled || isNonFocusableComposite)) {
799
+ additionalProps.disabled = disabled;
800
+ }
801
+ return additionalProps;
802
+ }, [composite, disabled, focusableWhenDisabled, isFocusableComposite, isNonFocusableComposite, isNativeButton, tabIndexProp]);
803
+ return {
804
+ props
805
+ };
806
+ }
807
+
808
+ function useButton(parameters = {}) {
809
+ const {
810
+ disabled = false,
811
+ focusableWhenDisabled,
812
+ tabIndex = 0,
813
+ native: isNativeButton = true,
814
+ composite: compositeProp
815
+ } = parameters;
816
+ const elementRef = React.useRef(null);
817
+ const compositeRootContext = useCompositeRootContext(true);
818
+ const isCompositeItem = compositeProp ?? compositeRootContext !== undefined;
819
+ const {
820
+ props: focusableWhenDisabledProps
821
+ } = useFocusableWhenDisabled({
822
+ focusableWhenDisabled,
823
+ disabled,
824
+ composite: isCompositeItem,
825
+ tabIndex,
826
+ isNativeButton
827
+ });
828
+ if (process.env.NODE_ENV !== 'production') {
829
+ // eslint-disable-next-line react-hooks/rules-of-hooks
830
+ React.useEffect(() => {
831
+ if (!elementRef.current) {
832
+ return;
833
+ }
834
+ const isButtonTag = isButtonElement(elementRef.current);
835
+ if (isNativeButton) {
836
+ if (!isButtonTag) {
837
+ const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
838
+ const message = 'A component that acts as a button expected a native <button> because the ' + '`nativeButton` prop is true. Rendering a non-<button> removes native button ' + 'semantics, which can impact forms and accessibility. Use a real <button> in the ' + '`render` prop, or set `nativeButton` to `false`.';
839
+ error(`${message}${ownerStackMessage}`);
840
+ }
841
+ } else if (isButtonTag) {
842
+ const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
843
+ const message = 'A component that acts as a button expected a non-<button> because the `nativeButton` ' + 'prop is false. Rendering a <button> keeps native behavior while Base UI applies ' + 'non-native attributes and handlers, which can add unintended extra attributes (such ' + 'as `role` or `aria-disabled`). Use a non-<button> in the `render` prop, or set ' + '`nativeButton` to `true`.';
844
+ error(`${message}${ownerStackMessage}`);
845
+ }
846
+ }, [isNativeButton]);
847
+ }
848
+
849
+ // handles a disabled composite button rendering another button, e.g.
850
+ // <Toolbar.Button disabled render={<Menu.Trigger />} />
851
+ // the `disabled` prop needs to pass through 2 `useButton`s then finally
852
+ // delete the `disabled` attribute from DOM
853
+ const updateDisabled = React.useCallback(() => {
854
+ const element = elementRef.current;
855
+ if (!isButtonElement(element)) {
856
+ return;
857
+ }
858
+ if (isCompositeItem && disabled && focusableWhenDisabledProps.disabled === undefined && element.disabled) {
859
+ element.disabled = false;
860
+ }
861
+ }, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]);
862
+ useIsoLayoutEffect(updateDisabled, [updateDisabled]);
863
+ const getButtonProps = React.useCallback((externalProps = {}) => {
864
+ const {
865
+ onClick: externalOnClick,
866
+ onMouseDown: externalOnMouseDown,
867
+ onKeyUp: externalOnKeyUp,
868
+ onKeyDown: externalOnKeyDown,
869
+ onPointerDown: externalOnPointerDown,
870
+ ...otherExternalProps
871
+ } = externalProps;
872
+ const type = isNativeButton ? 'button' : undefined;
873
+ return mergeProps({
874
+ type,
875
+ onClick(event) {
876
+ if (disabled) {
877
+ event.preventDefault();
878
+ return;
879
+ }
880
+ externalOnClick?.(event);
881
+ },
882
+ onMouseDown(event) {
883
+ if (!disabled) {
884
+ externalOnMouseDown?.(event);
885
+ }
886
+ },
887
+ onKeyDown(event) {
888
+ if (disabled) {
889
+ return;
890
+ }
891
+ makeEventPreventable(event);
892
+ externalOnKeyDown?.(event);
893
+ if (event.baseUIHandlerPrevented) {
894
+ return;
895
+ }
896
+ const isCurrentTarget = event.target === event.currentTarget;
897
+ const currentTarget = event.currentTarget;
898
+ const isButton = isButtonElement(currentTarget);
899
+ const isLink = !isNativeButton && isValidLinkElement(currentTarget);
900
+ const shouldClick = isCurrentTarget && (isNativeButton ? isButton : !isLink);
901
+ const isEnterKey = event.key === 'Enter';
902
+ const isSpaceKey = event.key === ' ';
903
+ const role = currentTarget.getAttribute('role');
904
+ const isTextNavigationRole = role?.startsWith('menuitem') || role === 'option' || role === 'gridcell';
905
+ if (isCurrentTarget && isCompositeItem && isSpaceKey) {
906
+ if (event.defaultPrevented && isTextNavigationRole) {
907
+ return;
908
+ }
909
+ event.preventDefault();
910
+ if (isLink || isNativeButton && isButton) {
911
+ currentTarget.click();
912
+ event.preventBaseUIHandler();
913
+ } else if (shouldClick) {
914
+ externalOnClick?.(event);
915
+ event.preventBaseUIHandler();
916
+ }
917
+ return;
918
+ }
919
+
920
+ // Keyboard accessibility for native and non-native elements.
921
+ if (shouldClick) {
922
+ if (!isNativeButton && (isSpaceKey || isEnterKey)) {
923
+ event.preventDefault();
924
+ }
925
+ if (!isNativeButton && isEnterKey) {
926
+ externalOnClick?.(event);
927
+ }
928
+ }
929
+ },
930
+ onKeyUp(event) {
931
+ if (disabled) {
932
+ return;
933
+ }
934
+
935
+ // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
936
+ // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
937
+ makeEventPreventable(event);
938
+ externalOnKeyUp?.(event);
939
+ if (event.target === event.currentTarget && isNativeButton && isCompositeItem && isButtonElement(event.currentTarget) && event.key === ' ') {
940
+ event.preventDefault();
941
+ return;
942
+ }
943
+ if (event.baseUIHandlerPrevented) {
944
+ return;
945
+ }
946
+
947
+ // Keyboard accessibility for non interactive elements
948
+ if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && event.key === ' ') {
949
+ externalOnClick?.(event);
950
+ }
951
+ },
952
+ onPointerDown(event) {
953
+ if (disabled) {
954
+ event.preventDefault();
955
+ return;
956
+ }
957
+ externalOnPointerDown?.(event);
958
+ }
959
+ }, !isNativeButton ? {
960
+ role: 'button'
961
+ } : undefined, focusableWhenDisabledProps, otherExternalProps);
962
+ }, [disabled, focusableWhenDisabledProps, isCompositeItem, isNativeButton]);
963
+ const buttonRef = useStableCallback(element => {
964
+ elementRef.current = element;
965
+ updateDisabled();
966
+ });
967
+ return {
968
+ getButtonProps,
969
+ buttonRef
970
+ };
971
+ }
972
+ function isButtonElement(elem) {
973
+ return isHTMLElement(elem) && elem.tagName === 'BUTTON';
974
+ }
975
+ function isValidLinkElement(elem) {
976
+ return Boolean(elem?.tagName === 'A' && elem?.href);
977
+ }
978
+
979
+ function clamp(val, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
980
+ return Math.max(min, Math.min(val, max));
981
+ }
982
+
983
+ export { isClickLikeEvent as $, stopEvent as A, keyboard as B, isMouseLikePointerType as C, useId as D, triggerFocus as E, isMac as F, isSafari as G, triggerPress as H, escapeKey as I, triggerHover as J, listNavigation as K, focusOut as L, isVirtualClick as M, isVirtualPointerEvent as N, closePress as O, itemPress as P, outsidePress as Q, useAnimationsFinished as R, siblingOpen as S, Timeout as T, imperativeAction as U, useCompositeRootContext as V, cancelOpen as W, isJSDOM as X, useAnimationFrame as Y, resolveRef as Z, isWebKit as _, useStableCallback as a, isReactEvent as a0, TransitionStatusDataAttributes as a1, AnimationFrame as a2, isAndroid as a3, closeWatcher as a4, inputPress as a5, pointer as a6, isFirefox as a7, clearPress as a8, swipe as a9, SafeReact as aa, error as ab, chipRemovePress as ac, scrub as ad, useTimeout as b, useTransitionStatus as c, useIsoLayoutEffect as d, useOpenChangeComplete as e, useOnMount as f, clamp as g, useControlled as h, useValueAsRef as i, isIOS as j, createChangeEventDetails as k, visuallyHidden as l, inputChange as m, none as n, inputClear as o, inputBlur as p, inputPaste as q, createGenericEventDetails as r, ownerDocument as s, transitionStatusMapping as t, useBaseUiId as u, visuallyHiddenInput as v, incrementPress as w, decrementPress as x, useButton as y, useValueChanged as z };