@jobber/components 8.15.0 → 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.
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  var React = require('react');
4
- var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
5
4
  var useRenderElement = require('./useRenderElement-cjs.js');
6
5
  var ReactDOM = require('react-dom');
6
+ var floatingUi_utils_dom = require('./floating-ui.utils.dom-cjs.js');
7
7
 
8
8
  function _interopNamespaceDefault(e) {
9
9
  var n = Object.create(null);
@@ -25,35 +25,6 @@ function _interopNamespaceDefault(e) {
25
25
  var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
26
26
  var ReactDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(ReactDOM);
27
27
 
28
- let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
29
- /**
30
- * Present when the component is animating in.
31
- */
32
- TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
33
- /**
34
- * Present when the component is animating out.
35
- */
36
- TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
37
- return TransitionStatusDataAttributes;
38
- }({});
39
- const STARTING_HOOK = {
40
- [TransitionStatusDataAttributes.startingStyle]: ''
41
- };
42
- const ENDING_HOOK = {
43
- [TransitionStatusDataAttributes.endingStyle]: ''
44
- };
45
- const transitionStatusMapping = {
46
- transitionStatus(value) {
47
- if (value === 'starting') {
48
- return STARTING_HOOK;
49
- }
50
- if (value === 'ending') {
51
- return ENDING_HOOK;
52
- }
53
- return null;
54
- }
55
- };
56
-
57
28
  // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
58
29
  const useInsertionEffect = React__namespace[`useInsertionEffect${Math.random().toFixed(1)}`.slice(0, -3)];
59
30
  const useSafeInsertionEffect =
@@ -94,392 +65,187 @@ function assertNotCalled() {
94
65
  }
95
66
  }
96
67
 
97
- let set;
98
- if (process.env.NODE_ENV !== 'production') {
99
- set = new Set();
100
- }
101
- function error(...messages) {
68
+ function useControlled({
69
+ controlled,
70
+ default: defaultProp,
71
+ name,
72
+ state = 'value'
73
+ }) {
74
+ // isControlled is ignored in the hook dependency lists as it should never change.
75
+ const {
76
+ current: isControlled
77
+ } = React__namespace.useRef(controlled !== undefined);
78
+ const [valueState, setValue] = React__namespace.useState(defaultProp);
79
+ const value = isControlled ? controlled : valueState;
102
80
  if (process.env.NODE_ENV !== 'production') {
103
- const messageKey = messages.join(' ');
104
- if (!set.has(messageKey)) {
105
- set.add(messageKey);
106
- console.error(`Base UI: ${messageKey}`);
107
- }
81
+ React__namespace.useEffect(() => {
82
+ if (isControlled !== (controlled !== undefined)) {
83
+ 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'));
84
+ }
85
+ }, [state, name, controlled]);
86
+ const {
87
+ current: defaultValue
88
+ } = React__namespace.useRef(defaultProp);
89
+ React__namespace.useEffect(() => {
90
+ // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
91
+ if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
92
+ 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'));
93
+ }
94
+ }, [JSON.stringify(defaultProp)]);
108
95
  }
96
+ const setValueIfUncontrolled = React__namespace.useCallback(newValue => {
97
+ if (!isControlled) {
98
+ setValue(newValue);
99
+ }
100
+ }, []);
101
+ return [value, setValueIfUncontrolled];
109
102
  }
110
103
 
111
- // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
112
- const SafeReact = {
113
- ...React__namespace
114
- };
115
-
116
104
  const noop = () => {};
117
105
  const useIsoLayoutEffect = typeof document !== 'undefined' ? React__namespace.useLayoutEffect : noop;
118
106
 
119
- const CompositeRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
120
- if (process.env.NODE_ENV !== "production") CompositeRootContext.displayName = "CompositeRootContext";
121
- function useCompositeRootContext(optional = false) {
122
- const context = React__namespace.useContext(CompositeRootContext);
123
- if (context === undefined && !optional) {
124
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.' : useRenderElement.formatErrorMessage(16));
125
- }
126
- return context;
107
+ const visuallyHiddenBase = {
108
+ clipPath: 'inset(50%)',
109
+ overflow: 'hidden',
110
+ whiteSpace: 'nowrap',
111
+ border: 0,
112
+ padding: 0,
113
+ width: 1,
114
+ height: 1,
115
+ margin: -1
116
+ };
117
+ const visuallyHidden = {
118
+ ...visuallyHiddenBase,
119
+ position: 'fixed',
120
+ top: 0,
121
+ left: 0
122
+ };
123
+ const visuallyHiddenInput = {
124
+ ...visuallyHiddenBase,
125
+ position: 'absolute'
126
+ };
127
+
128
+ const EMPTY$2 = [];
129
+
130
+ /**
131
+ * A React.useEffect equivalent that runs once, when the component is mounted.
132
+ */
133
+ function useOnMount(fn) {
134
+ // 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
135
+ /* eslint-disable react-hooks/exhaustive-deps */
136
+ React__namespace.useEffect(fn, EMPTY$2);
137
+ /* eslint-enable react-hooks/exhaustive-deps */
127
138
  }
128
139
 
129
- function useFocusableWhenDisabled(parameters) {
130
- const {
131
- focusableWhenDisabled,
132
- disabled,
133
- composite = false,
134
- tabIndex: tabIndexProp = 0,
135
- isNativeButton
136
- } = parameters;
137
- const isFocusableComposite = composite && focusableWhenDisabled !== false;
138
- const isNonFocusableComposite = composite && focusableWhenDisabled === false;
140
+ /** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
141
+ * a monomorphic `uint` type with `0` meaning empty.
142
+ * See warning note at:
143
+ * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
144
+ const EMPTY$1 = null;
145
+ let LAST_RAF = globalThis.requestAnimationFrame;
146
+ class Scheduler {
147
+ /* This implementation uses an array as a backing data-structure for frame callbacks.
148
+ * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
149
+ * never calls the native `cancelAnimationFrame` if there are no frames left. This can
150
+ * be much more efficient if there is a call pattern that alterns as
151
+ * "request-cancel-request-cancel-…".
152
+ * But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
153
+ * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
139
154
 
140
- // we can't explicitly assign `undefined` to any of these props because it
141
- // would otherwise prevent subsequently merged props from setting them
142
- const props = React__namespace.useMemo(() => {
143
- const additionalProps = {
144
- // allow Tabbing away from focusableWhenDisabled elements
145
- onKeyDown(event) {
146
- if (disabled && focusableWhenDisabled && event.key !== 'Tab') {
147
- event.preventDefault();
148
- }
149
- }
150
- };
151
- if (!composite) {
152
- additionalProps.tabIndex = tabIndexProp;
153
- if (!isNativeButton && disabled) {
154
- additionalProps.tabIndex = focusableWhenDisabled ? tabIndexProp : -1;
155
+ callbacks = [];
156
+ callbacksCount = 0;
157
+ nextId = 1;
158
+ startId = 1;
159
+ isScheduled = false;
160
+ tick = timestamp => {
161
+ this.isScheduled = false;
162
+ const currentCallbacks = this.callbacks;
163
+ const currentCallbacksCount = this.callbacksCount;
164
+
165
+ // Update these before iterating, callbacks could call `requestAnimationFrame` again.
166
+ this.callbacks = [];
167
+ this.callbacksCount = 0;
168
+ this.startId = this.nextId;
169
+ if (currentCallbacksCount > 0) {
170
+ for (let i = 0; i < currentCallbacks.length; i += 1) {
171
+ currentCallbacks[i]?.(timestamp);
155
172
  }
156
173
  }
157
- if (isNativeButton && (focusableWhenDisabled || isFocusableComposite) || !isNativeButton && disabled) {
158
- additionalProps['aria-disabled'] = disabled;
159
- }
160
- if (isNativeButton && (!focusableWhenDisabled || isNonFocusableComposite)) {
161
- additionalProps.disabled = disabled;
162
- }
163
- return additionalProps;
164
- }, [composite, disabled, focusableWhenDisabled, isFocusableComposite, isNonFocusableComposite, isNativeButton, tabIndexProp]);
165
- return {
166
- props
167
174
  };
168
- }
175
+ request(fn) {
176
+ const id = this.nextId;
177
+ this.nextId += 1;
178
+ this.callbacks.push(fn);
179
+ this.callbacksCount += 1;
169
180
 
170
- function useButton(parameters = {}) {
171
- const {
172
- disabled = false,
173
- focusableWhenDisabled,
174
- tabIndex = 0,
175
- native: isNativeButton = true,
176
- composite: compositeProp
177
- } = parameters;
178
- const elementRef = React__namespace.useRef(null);
179
- const compositeRootContext = useCompositeRootContext(true);
180
- const isCompositeItem = compositeProp ?? compositeRootContext !== undefined;
181
- const {
182
- props: focusableWhenDisabledProps
183
- } = useFocusableWhenDisabled({
184
- focusableWhenDisabled,
185
- disabled,
186
- composite: isCompositeItem,
187
- tabIndex,
188
- isNativeButton
189
- });
190
- if (process.env.NODE_ENV !== 'production') {
191
- // eslint-disable-next-line react-hooks/rules-of-hooks
192
- React__namespace.useEffect(() => {
193
- if (!elementRef.current) {
194
- return;
195
- }
196
- const isButtonTag = isButtonElement(elementRef.current);
197
- if (isNativeButton) {
198
- if (!isButtonTag) {
199
- const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
200
- 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`.';
201
- error(`${message}${ownerStackMessage}`);
202
- }
203
- } else if (isButtonTag) {
204
- const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
205
- 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`.';
206
- error(`${message}${ownerStackMessage}`);
207
- }
208
- }, [isNativeButton]);
181
+ /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
182
+ * but there's no guarantee that the animation frame will actually run before the fake
183
+ * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
184
+ const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
185
+ if (!this.isScheduled || didRAFChange) {
186
+ requestAnimationFrame(this.tick);
187
+ this.isScheduled = true;
188
+ }
189
+ return id;
209
190
  }
210
-
211
- // handles a disabled composite button rendering another button, e.g.
212
- // <Toolbar.Button disabled render={<Menu.Trigger />} />
213
- // the `disabled` prop needs to pass through 2 `useButton`s then finally
214
- // delete the `disabled` attribute from DOM
215
- const updateDisabled = React__namespace.useCallback(() => {
216
- const element = elementRef.current;
217
- if (!isButtonElement(element)) {
191
+ cancel(id) {
192
+ const index = id - this.startId;
193
+ if (index < 0 || index >= this.callbacks.length) {
218
194
  return;
219
195
  }
220
- if (isCompositeItem && disabled && focusableWhenDisabledProps.disabled === undefined && element.disabled) {
221
- element.disabled = false;
222
- }
223
- }, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]);
224
- useIsoLayoutEffect(updateDisabled, [updateDisabled]);
225
- const getButtonProps = React__namespace.useCallback((externalProps = {}) => {
226
- const {
227
- onClick: externalOnClick,
228
- onMouseDown: externalOnMouseDown,
229
- onKeyUp: externalOnKeyUp,
230
- onKeyDown: externalOnKeyDown,
231
- onPointerDown: externalOnPointerDown,
232
- ...otherExternalProps
233
- } = externalProps;
234
- const type = isNativeButton ? 'button' : undefined;
235
- return useRenderElement.mergeProps({
236
- type,
237
- onClick(event) {
238
- if (disabled) {
239
- event.preventDefault();
240
- return;
241
- }
242
- externalOnClick?.(event);
243
- },
244
- onMouseDown(event) {
245
- if (!disabled) {
246
- externalOnMouseDown?.(event);
247
- }
248
- },
249
- onKeyDown(event) {
250
- if (disabled) {
251
- return;
252
- }
253
- useRenderElement.makeEventPreventable(event);
254
- externalOnKeyDown?.(event);
255
- if (event.baseUIHandlerPrevented) {
256
- return;
257
- }
258
- const isCurrentTarget = event.target === event.currentTarget;
259
- const currentTarget = event.currentTarget;
260
- const isButton = isButtonElement(currentTarget);
261
- const isLink = !isNativeButton && isValidLinkElement(currentTarget);
262
- const shouldClick = isCurrentTarget && (isNativeButton ? isButton : !isLink);
263
- const isEnterKey = event.key === 'Enter';
264
- const isSpaceKey = event.key === ' ';
265
- const role = currentTarget.getAttribute('role');
266
- const isTextNavigationRole = role?.startsWith('menuitem') || role === 'option' || role === 'gridcell';
267
- if (isCurrentTarget && isCompositeItem && isSpaceKey) {
268
- if (event.defaultPrevented && isTextNavigationRole) {
269
- return;
270
- }
271
- event.preventDefault();
272
- if (isLink || isNativeButton && isButton) {
273
- currentTarget.click();
274
- event.preventBaseUIHandler();
275
- } else if (shouldClick) {
276
- externalOnClick?.(event);
277
- event.preventBaseUIHandler();
278
- }
279
- return;
280
- }
281
-
282
- // Keyboard accessibility for native and non-native elements.
283
- if (shouldClick) {
284
- if (!isNativeButton && (isSpaceKey || isEnterKey)) {
285
- event.preventDefault();
286
- }
287
- if (!isNativeButton && isEnterKey) {
288
- externalOnClick?.(event);
289
- }
290
- }
291
- },
292
- onKeyUp(event) {
293
- if (disabled) {
294
- return;
295
- }
296
-
297
- // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
298
- // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
299
- useRenderElement.makeEventPreventable(event);
300
- externalOnKeyUp?.(event);
301
- if (event.target === event.currentTarget && isNativeButton && isCompositeItem && isButtonElement(event.currentTarget) && event.key === ' ') {
302
- event.preventDefault();
303
- return;
304
- }
305
- if (event.baseUIHandlerPrevented) {
306
- return;
307
- }
308
-
309
- // Keyboard accessibility for non interactive elements
310
- if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && event.key === ' ') {
311
- externalOnClick?.(event);
312
- }
313
- },
314
- onPointerDown(event) {
315
- if (disabled) {
316
- event.preventDefault();
317
- return;
318
- }
319
- externalOnPointerDown?.(event);
320
- }
321
- }, !isNativeButton ? {
322
- role: 'button'
323
- } : undefined, focusableWhenDisabledProps, otherExternalProps);
324
- }, [disabled, focusableWhenDisabledProps, isCompositeItem, isNativeButton]);
325
- const buttonRef = useStableCallback(element => {
326
- elementRef.current = element;
327
- updateDisabled();
328
- });
329
- return {
330
- getButtonProps,
331
- buttonRef
332
- };
333
- }
334
- function isButtonElement(elem) {
335
- return floatingUi_utils_dom.isHTMLElement(elem) && elem.tagName === 'BUTTON';
336
- }
337
- function isValidLinkElement(elem) {
338
- return Boolean(elem?.tagName === 'A' && elem?.href);
196
+ this.callbacks[index] = null;
197
+ this.callbacksCount -= 1;
198
+ }
339
199
  }
200
+ const scheduler = new Scheduler();
201
+ class AnimationFrame {
202
+ static create() {
203
+ return new AnimationFrame();
204
+ }
205
+ static request(fn) {
206
+ return scheduler.request(fn);
207
+ }
208
+ static cancel(id) {
209
+ return scheduler.cancel(id);
210
+ }
211
+ currentId = EMPTY$1;
340
212
 
341
- const none = 'none';
342
- const triggerPress = 'trigger-press';
343
- const triggerHover = 'trigger-hover';
344
- const triggerFocus = 'trigger-focus';
345
- const outsidePress = 'outside-press';
346
- const itemPress = 'item-press';
347
- const closePress = 'close-press';
348
- const clearPress = 'clear-press';
349
- const chipRemovePress = 'chip-remove-press';
350
- const incrementPress = 'increment-press';
351
- const decrementPress = 'decrement-press';
352
- const inputChange = 'input-change';
353
- const inputClear = 'input-clear';
354
- const inputBlur = 'input-blur';
355
- const inputPaste = 'input-paste';
356
- const inputPress = 'input-press';
357
- const focusOut = 'focus-out';
358
- const escapeKey = 'escape-key';
359
- const closeWatcher = 'close-watcher';
360
- const listNavigation = 'list-navigation';
361
- const keyboard = 'keyboard';
362
- const scrub = 'scrub';
363
- const cancelOpen = 'cancel-open';
364
- const siblingOpen = 'sibling-open';
365
- const imperativeAction = 'imperative-action';
366
- const swipe = 'swipe';
367
-
368
- /**
369
- * Maps a change `reason` string to the corresponding native event type.
370
- */
371
-
372
- /**
373
- * Details of custom change events emitted by Base UI components.
374
- */
375
-
376
- /**
377
- * Details of custom generic events emitted by Base UI components.
378
- */
379
-
380
- /**
381
- * Creates a Base UI event details object with the given reason and utilities
382
- * for preventing Base UI's internal event handling.
383
- */
384
- function createChangeEventDetails(reason, event, trigger, customProperties) {
385
- let canceled = false;
386
- let allowPropagation = false;
387
- const custom = customProperties ?? useRenderElement.EMPTY_OBJECT;
388
- const details = {
389
- reason,
390
- event: event ?? new Event('base-ui'),
391
- cancel() {
392
- canceled = true;
393
- },
394
- allowPropagation() {
395
- allowPropagation = true;
396
- },
397
- get isCanceled() {
398
- return canceled;
399
- },
400
- get isPropagationAllowed() {
401
- return allowPropagation;
402
- },
403
- trigger,
404
- ...custom
213
+ /**
214
+ * Executes `fn` after `delay`, clearing any previously scheduled call.
215
+ */
216
+ request(fn) {
217
+ this.cancel();
218
+ this.currentId = scheduler.request(() => {
219
+ this.currentId = EMPTY$1;
220
+ fn();
221
+ });
222
+ }
223
+ cancel = () => {
224
+ if (this.currentId !== EMPTY$1) {
225
+ scheduler.cancel(this.currentId);
226
+ this.currentId = EMPTY$1;
227
+ }
405
228
  };
406
- return details;
407
- }
408
- function createGenericEventDetails(reason, event, customProperties) {
409
- const custom = customProperties ?? useRenderElement.EMPTY_OBJECT;
410
- const details = {
411
- reason,
412
- event: event ?? new Event('base-ui'),
413
- ...custom
229
+ disposeEffect = () => {
230
+ return this.cancel;
414
231
  };
415
- return details;
416
- }
417
-
418
- let globalId = 0;
419
-
420
- // TODO React 17: Remove `useGlobalId` once React 17 support is removed
421
- function useGlobalId(idOverride, prefix = 'mui') {
422
- const [defaultId, setDefaultId] = React__namespace.useState(idOverride);
423
- const id = idOverride || defaultId;
424
- React__namespace.useEffect(() => {
425
- if (defaultId == null) {
426
- // Fallback to this default id when possible.
427
- // Use the incrementing value for client-side rendering only.
428
- // We can't use it server-side.
429
- // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
430
- globalId += 1;
431
- setDefaultId(`${prefix}-${globalId}`);
432
- }
433
- }, [defaultId, prefix]);
434
- return id;
435
- }
436
- const maybeReactUseId = SafeReact.useId;
437
-
438
- /**
439
- *
440
- * @example <div id={useId()} />
441
- * @param idOverride
442
- * @returns {string}
443
- */
444
- function useId(idOverride, prefix) {
445
- // React.useId() is only available from React 17.0.0.
446
- if (maybeReactUseId !== undefined) {
447
- const reactId = maybeReactUseId();
448
- return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
449
- }
450
-
451
- // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
452
- // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
453
- return useGlobalId(idOverride, prefix);
454
232
  }
455
233
 
456
234
  /**
457
- * Wraps `useId` and prefixes generated `id`s with `base-ui-`
458
- * @param {string | undefined} idOverride overrides the generated id when provided
459
- * @returns {string | undefined}
460
- */
461
- function useBaseUiId(idOverride) {
462
- return useId(idOverride, 'base-ui');
463
- }
464
-
465
- const EMPTY$2 = [];
466
-
467
- /**
468
- * A React.useEffect equivalent that runs once, when the component is mounted.
235
+ * A `requestAnimationFrame` with automatic cleanup and guard.
469
236
  */
470
- function useOnMount(fn) {
471
- // 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
472
- /* eslint-disable react-hooks/exhaustive-deps */
473
- React__namespace.useEffect(fn, EMPTY$2);
474
- /* eslint-enable react-hooks/exhaustive-deps */
237
+ function useAnimationFrame() {
238
+ const timeout = useRenderElement.useRefWithInit(AnimationFrame.create).current;
239
+ useOnMount(timeout.disposeEffect);
240
+ return timeout;
475
241
  }
476
242
 
477
- const EMPTY$1 = 0;
243
+ const EMPTY = 0;
478
244
  class Timeout {
479
245
  static create() {
480
246
  return new Timeout();
481
247
  }
482
- currentId = EMPTY$1;
248
+ currentId = EMPTY;
483
249
 
484
250
  /**
485
251
  * Executes `fn` after `delay`, clearing any previously scheduled call.
@@ -487,17 +253,17 @@ class Timeout {
487
253
  start(delay, fn) {
488
254
  this.clear();
489
255
  this.currentId = setTimeout(() => {
490
- this.currentId = EMPTY$1;
256
+ this.currentId = EMPTY;
491
257
  fn();
492
258
  }, delay); /* Node.js types are enabled in development */
493
259
  }
494
260
  isStarted() {
495
- return this.currentId !== EMPTY$1;
261
+ return this.currentId !== EMPTY;
496
262
  }
497
263
  clear = () => {
498
- if (this.currentId !== EMPTY$1) {
264
+ if (this.currentId !== EMPTY) {
499
265
  clearTimeout(this.currentId);
500
- this.currentId = EMPTY$1;
266
+ this.currentId = EMPTY;
501
267
  }
502
268
  };
503
269
  disposeEffect = () => {
@@ -514,6 +280,30 @@ function useTimeout() {
514
280
  return timeout;
515
281
  }
516
282
 
283
+ /**
284
+ * Untracks the provided value by turning it into a ref to remove its reactivity.
285
+ *
286
+ * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
287
+ */
288
+ function useValueAsRef(value) {
289
+ const latest = useRenderElement.useRefWithInit(createLatestRef, value).current;
290
+ latest.next = value;
291
+
292
+ // eslint-disable-next-line react-hooks/exhaustive-deps
293
+ useIsoLayoutEffect(latest.effect);
294
+ return latest;
295
+ }
296
+ function createLatestRef(value) {
297
+ const latest = {
298
+ current: value,
299
+ next: value,
300
+ effect: () => {
301
+ latest.current = latest.next;
302
+ }
303
+ };
304
+ return latest;
305
+ }
306
+
517
307
  const hasNavigator = typeof navigator !== 'undefined';
518
308
  const nav = getNavigatorData();
519
309
  const platform = getPlatform();
@@ -616,153 +406,126 @@ function ownerDocument(node) {
616
406
  return node?.ownerDocument || document;
617
407
  }
618
408
 
409
+ const none = 'none';
410
+ const triggerPress = 'trigger-press';
411
+ const triggerHover = 'trigger-hover';
412
+ const triggerFocus = 'trigger-focus';
413
+ const outsidePress = 'outside-press';
414
+ const itemPress = 'item-press';
415
+ const closePress = 'close-press';
416
+ const clearPress = 'clear-press';
417
+ const chipRemovePress = 'chip-remove-press';
418
+ const incrementPress = 'increment-press';
419
+ const decrementPress = 'decrement-press';
420
+ const inputChange = 'input-change';
421
+ const inputClear = 'input-clear';
422
+ const inputBlur = 'input-blur';
423
+ const inputPaste = 'input-paste';
424
+ const inputPress = 'input-press';
425
+ const focusOut = 'focus-out';
426
+ const escapeKey = 'escape-key';
427
+ const closeWatcher = 'close-watcher';
428
+ const listNavigation = 'list-navigation';
429
+ const keyboard = 'keyboard';
430
+ const pointer = 'pointer';
431
+ const scrub = 'scrub';
432
+ const cancelOpen = 'cancel-open';
433
+ const siblingOpen = 'sibling-open';
434
+ const imperativeAction = 'imperative-action';
435
+ const swipe = 'swipe';
436
+
619
437
  /**
620
- * Untracks the provided value by turning it into a ref to remove its reactivity.
621
- *
622
- * Used to access the passed value inside `React.useEffect` without causing the effect to re-run when the value changes.
438
+ * Maps a change `reason` string to the corresponding native event type.
623
439
  */
624
- function useValueAsRef(value) {
625
- const latest = useRenderElement.useRefWithInit(createLatestRef, value).current;
626
- latest.next = value;
627
440
 
628
- // eslint-disable-next-line react-hooks/exhaustive-deps
629
- useIsoLayoutEffect(latest.effect);
630
- return latest;
631
- }
632
- function createLatestRef(value) {
633
- const latest = {
634
- current: value,
635
- next: value,
636
- effect: () => {
637
- latest.current = latest.next;
638
- }
639
- };
640
- return latest;
641
- }
642
-
643
- /** Unlike `setTimeout`, rAF doesn't guarantee a positive integer return value, so we can't have
644
- * a monomorphic `uint` type with `0` meaning empty.
645
- * See warning note at:
646
- * https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame#return_value */
647
- const EMPTY = null;
648
- let LAST_RAF = globalThis.requestAnimationFrame;
649
- class Scheduler {
650
- /* This implementation uses an array as a backing data-structure for frame callbacks.
651
- * It allows `O(1)` callback cancelling by inserting a `null` in the array, though it
652
- * never calls the native `cancelAnimationFrame` if there are no frames left. This can
653
- * be much more efficient if there is a call pattern that alterns as
654
- * "request-cancel-request-cancel-…".
655
- * But in the case of "request-request-…-cancel-cancel-…", it leaves the final animation
656
- * frame to run anyway. We turn that frame into a `O(1)` no-op via `callbacksCount`. */
441
+ /**
442
+ * Details of custom change events emitted by Base UI components.
443
+ */
657
444
 
658
- callbacks = [];
659
- callbacksCount = 0;
660
- nextId = 1;
661
- startId = 1;
662
- isScheduled = false;
663
- tick = timestamp => {
664
- this.isScheduled = false;
665
- const currentCallbacks = this.callbacks;
666
- const currentCallbacksCount = this.callbacksCount;
445
+ /**
446
+ * Details of custom generic events emitted by Base UI components.
447
+ */
667
448
 
668
- // Update these before iterating, callbacks could call `requestAnimationFrame` again.
669
- this.callbacks = [];
670
- this.callbacksCount = 0;
671
- this.startId = this.nextId;
672
- if (currentCallbacksCount > 0) {
673
- for (let i = 0; i < currentCallbacks.length; i += 1) {
674
- currentCallbacks[i]?.(timestamp);
675
- }
676
- }
449
+ /**
450
+ * Creates a Base UI event details object with the given reason and utilities
451
+ * for preventing Base UI's internal event handling.
452
+ */
453
+ function createChangeEventDetails(reason, event, trigger, customProperties) {
454
+ let canceled = false;
455
+ let allowPropagation = false;
456
+ const custom = customProperties ?? useRenderElement.EMPTY_OBJECT;
457
+ const details = {
458
+ reason,
459
+ event: event ?? new Event('base-ui'),
460
+ cancel() {
461
+ canceled = true;
462
+ },
463
+ allowPropagation() {
464
+ allowPropagation = true;
465
+ },
466
+ get isCanceled() {
467
+ return canceled;
468
+ },
469
+ get isPropagationAllowed() {
470
+ return allowPropagation;
471
+ },
472
+ trigger,
473
+ ...custom
677
474
  };
678
- request(fn) {
679
- const id = this.nextId;
680
- this.nextId += 1;
681
- this.callbacks.push(fn);
682
- this.callbacksCount += 1;
683
-
684
- /* In a test environment with fake timers, a fake `requestAnimationFrame` can be called
685
- * but there's no guarantee that the animation frame will actually run before the fake
686
- * timers are teared, which leaves `isScheduled` set, but won't run our `tick()`. */
687
- const didRAFChange = process.env.NODE_ENV !== 'production' && LAST_RAF !== requestAnimationFrame && (LAST_RAF = requestAnimationFrame, true);
688
- if (!this.isScheduled || didRAFChange) {
689
- requestAnimationFrame(this.tick);
690
- this.isScheduled = true;
691
- }
692
- return id;
693
- }
694
- cancel(id) {
695
- const index = id - this.startId;
696
- if (index < 0 || index >= this.callbacks.length) {
697
- return;
698
- }
699
- this.callbacks[index] = null;
700
- this.callbacksCount -= 1;
701
- }
475
+ return details;
476
+ }
477
+ function createGenericEventDetails(reason, event, customProperties) {
478
+ const custom = customProperties ?? useRenderElement.EMPTY_OBJECT;
479
+ const details = {
480
+ reason,
481
+ event: event ?? new Event('base-ui'),
482
+ ...custom
483
+ };
484
+ return details;
702
485
  }
703
- const scheduler = new Scheduler();
704
- class AnimationFrame {
705
- static create() {
706
- return new AnimationFrame();
707
- }
708
- static request(fn) {
709
- return scheduler.request(fn);
710
- }
711
- static cancel(id) {
712
- return scheduler.cancel(id);
713
- }
714
- currentId = EMPTY;
715
486
 
716
- /**
717
- * Executes `fn` after `delay`, clearing any previously scheduled call.
718
- */
719
- request(fn) {
720
- this.cancel();
721
- this.currentId = scheduler.request(() => {
722
- this.currentId = EMPTY;
723
- fn();
724
- });
725
- }
726
- cancel = () => {
727
- if (this.currentId !== EMPTY) {
728
- scheduler.cancel(this.currentId);
729
- this.currentId = EMPTY;
487
+ // https://github.com/mui/material-ui/issues/41190#issuecomment-2040873379
488
+ const SafeReact = {
489
+ ...React__namespace
490
+ };
491
+
492
+ let globalId = 0;
493
+
494
+ // TODO React 17: Remove `useGlobalId` once React 17 support is removed
495
+ function useGlobalId(idOverride, prefix = 'mui') {
496
+ const [defaultId, setDefaultId] = React__namespace.useState(idOverride);
497
+ const id = idOverride || defaultId;
498
+ React__namespace.useEffect(() => {
499
+ if (defaultId == null) {
500
+ // Fallback to this default id when possible.
501
+ // Use the incrementing value for client-side rendering only.
502
+ // We can't use it server-side.
503
+ // If you want to use random values please consider the Birthday Problem: https://en.wikipedia.org/wiki/Birthday_problem
504
+ globalId += 1;
505
+ setDefaultId(`${prefix}-${globalId}`);
730
506
  }
731
- };
732
- disposeEffect = () => {
733
- return this.cancel;
734
- };
507
+ }, [defaultId, prefix]);
508
+ return id;
735
509
  }
510
+ const maybeReactUseId = SafeReact.useId;
736
511
 
737
512
  /**
738
- * A `requestAnimationFrame` with automatic cleanup and guard.
513
+ *
514
+ * @example <div id={useId()} />
515
+ * @param idOverride
516
+ * @returns {string}
739
517
  */
740
- function useAnimationFrame() {
741
- const timeout = useRenderElement.useRefWithInit(AnimationFrame.create).current;
742
- useOnMount(timeout.disposeEffect);
743
- return timeout;
744
- }
518
+ function useId(idOverride, prefix) {
519
+ // React.useId() is only available from React 17.0.0.
520
+ if (maybeReactUseId !== undefined) {
521
+ const reactId = maybeReactUseId();
522
+ return idOverride ?? (prefix ? `${prefix}-${reactId}` : reactId);
523
+ }
745
524
 
746
- const visuallyHiddenBase = {
747
- clipPath: 'inset(50%)',
748
- overflow: 'hidden',
749
- whiteSpace: 'nowrap',
750
- border: 0,
751
- padding: 0,
752
- width: 1,
753
- height: 1,
754
- margin: -1
755
- };
756
- const visuallyHidden = {
757
- ...visuallyHiddenBase,
758
- position: 'fixed',
759
- top: 0,
760
- left: 0
761
- };
762
- const visuallyHiddenInput = {
763
- ...visuallyHiddenBase,
764
- position: 'absolute'
765
- };
525
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
526
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- `React.useId` is invariant at runtime.
527
+ return useGlobalId(idOverride, prefix);
528
+ }
766
529
 
767
530
  /**
768
531
  * If the provided argument is a ref object, returns its `current` value.
@@ -838,6 +601,35 @@ function useTransitionStatus(open, enableIdleState = false, deferEndingState = f
838
601
  }), [mounted, transitionStatus]);
839
602
  }
840
603
 
604
+ let TransitionStatusDataAttributes = /*#__PURE__*/function (TransitionStatusDataAttributes) {
605
+ /**
606
+ * Present when the component is animating in.
607
+ */
608
+ TransitionStatusDataAttributes["startingStyle"] = "data-starting-style";
609
+ /**
610
+ * Present when the component is animating out.
611
+ */
612
+ TransitionStatusDataAttributes["endingStyle"] = "data-ending-style";
613
+ return TransitionStatusDataAttributes;
614
+ }({});
615
+ const STARTING_HOOK = {
616
+ [TransitionStatusDataAttributes.startingStyle]: ''
617
+ };
618
+ const ENDING_HOOK = {
619
+ [TransitionStatusDataAttributes.endingStyle]: ''
620
+ };
621
+ const transitionStatusMapping = {
622
+ transitionStatus(value) {
623
+ if (value === 'starting') {
624
+ return STARTING_HOOK;
625
+ }
626
+ if (value === 'ending') {
627
+ return ENDING_HOOK;
628
+ }
629
+ return null;
630
+ }
631
+ };
632
+
841
633
  /**
842
634
  * Executes a function once all animations have finished on the provided element.
843
635
  * @param elementOrRef - The element to watch for animations.
@@ -914,91 +706,300 @@ function useAnimationsFinished(elementOrRef, waitForStartingStyleRemoved = false
914
706
  }
915
707
  });
916
708
  }
917
- if (waitForStartingStyleRemoved) {
918
- execWaitForStartingStyleRemoved();
919
- return;
709
+ if (waitForStartingStyleRemoved) {
710
+ execWaitForStartingStyleRemoved();
711
+ return;
712
+ }
713
+ frame.request(exec);
714
+ }
715
+ });
716
+ }
717
+
718
+ /**
719
+ * Calls the provided function when the CSS open/close animation or transition completes.
720
+ */
721
+ function useOpenChangeComplete(parameters) {
722
+ const {
723
+ enabled = true,
724
+ open,
725
+ ref,
726
+ onComplete: onCompleteParam
727
+ } = parameters;
728
+ const onComplete = useStableCallback(onCompleteParam);
729
+ const runOnceAnimationsFinish = useAnimationsFinished(ref, open, false);
730
+ React__namespace.useEffect(() => {
731
+ if (!enabled) {
732
+ return undefined;
733
+ }
734
+ const abortController = new AbortController();
735
+ runOnceAnimationsFinish(onComplete, abortController.signal);
736
+ return () => {
737
+ abortController.abort();
738
+ };
739
+ }, [enabled, open, onComplete, runOnceAnimationsFinish]);
740
+ }
741
+
742
+ /**
743
+ * Wraps `useId` and prefixes generated `id`s with `base-ui-`
744
+ * @param {string | undefined} idOverride overrides the generated id when provided
745
+ * @returns {string | undefined}
746
+ */
747
+ function useBaseUiId(idOverride) {
748
+ return useId(idOverride, 'base-ui');
749
+ }
750
+
751
+ function useValueChanged(value, onChange) {
752
+ const valueRef = React__namespace.useRef(value);
753
+ const onChangeCallback = useStableCallback(onChange);
754
+ useIsoLayoutEffect(() => {
755
+ if (valueRef.current === value) {
756
+ return;
757
+ }
758
+ onChangeCallback(valueRef.current);
759
+ }, [value, onChangeCallback]);
760
+ useIsoLayoutEffect(() => {
761
+ valueRef.current = value;
762
+ }, [value]);
763
+ }
764
+
765
+ let set;
766
+ if (process.env.NODE_ENV !== 'production') {
767
+ set = new Set();
768
+ }
769
+ function error(...messages) {
770
+ if (process.env.NODE_ENV !== 'production') {
771
+ const messageKey = messages.join(' ');
772
+ if (!set.has(messageKey)) {
773
+ set.add(messageKey);
774
+ console.error(`Base UI: ${messageKey}`);
775
+ }
776
+ }
777
+ }
778
+
779
+ const CompositeRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
780
+ if (process.env.NODE_ENV !== "production") CompositeRootContext.displayName = "CompositeRootContext";
781
+ function useCompositeRootContext(optional = false) {
782
+ const context = React__namespace.useContext(CompositeRootContext);
783
+ if (context === undefined && !optional) {
784
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: CompositeRootContext is missing. Composite parts must be placed within <Composite.Root>.' : useRenderElement.formatErrorMessage(16));
785
+ }
786
+ return context;
787
+ }
788
+
789
+ function useFocusableWhenDisabled(parameters) {
790
+ const {
791
+ focusableWhenDisabled,
792
+ disabled,
793
+ composite = false,
794
+ tabIndex: tabIndexProp = 0,
795
+ isNativeButton
796
+ } = parameters;
797
+ const isFocusableComposite = composite && focusableWhenDisabled !== false;
798
+ const isNonFocusableComposite = composite && focusableWhenDisabled === false;
799
+
800
+ // we can't explicitly assign `undefined` to any of these props because it
801
+ // would otherwise prevent subsequently merged props from setting them
802
+ const props = React__namespace.useMemo(() => {
803
+ const additionalProps = {
804
+ // allow Tabbing away from focusableWhenDisabled elements
805
+ onKeyDown(event) {
806
+ if (disabled && focusableWhenDisabled && event.key !== 'Tab') {
807
+ event.preventDefault();
808
+ }
809
+ }
810
+ };
811
+ if (!composite) {
812
+ additionalProps.tabIndex = tabIndexProp;
813
+ if (!isNativeButton && disabled) {
814
+ additionalProps.tabIndex = focusableWhenDisabled ? tabIndexProp : -1;
920
815
  }
921
- frame.request(exec);
922
816
  }
923
- });
817
+ if (isNativeButton && (focusableWhenDisabled || isFocusableComposite) || !isNativeButton && disabled) {
818
+ additionalProps['aria-disabled'] = disabled;
819
+ }
820
+ if (isNativeButton && (!focusableWhenDisabled || isNonFocusableComposite)) {
821
+ additionalProps.disabled = disabled;
822
+ }
823
+ return additionalProps;
824
+ }, [composite, disabled, focusableWhenDisabled, isFocusableComposite, isNonFocusableComposite, isNativeButton, tabIndexProp]);
825
+ return {
826
+ props
827
+ };
924
828
  }
925
829
 
926
- /**
927
- * Calls the provided function when the CSS open/close animation or transition completes.
928
- */
929
- function useOpenChangeComplete(parameters) {
830
+ function useButton(parameters = {}) {
930
831
  const {
931
- enabled = true,
932
- open,
933
- ref,
934
- onComplete: onCompleteParam
832
+ disabled = false,
833
+ focusableWhenDisabled,
834
+ tabIndex = 0,
835
+ native: isNativeButton = true,
836
+ composite: compositeProp
935
837
  } = parameters;
936
- const onComplete = useStableCallback(onCompleteParam);
937
- const runOnceAnimationsFinish = useAnimationsFinished(ref, open, false);
938
- React__namespace.useEffect(() => {
939
- if (!enabled) {
940
- return undefined;
941
- }
942
- const abortController = new AbortController();
943
- runOnceAnimationsFinish(onComplete, abortController.signal);
944
- return () => {
945
- abortController.abort();
946
- };
947
- }, [enabled, open, onComplete, runOnceAnimationsFinish]);
948
- }
949
-
950
- function clamp(val, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
951
- return Math.max(min, Math.min(val, max));
952
- }
953
-
954
- function useControlled({
955
- controlled,
956
- default: defaultProp,
957
- name,
958
- state = 'value'
959
- }) {
960
- // isControlled is ignored in the hook dependency lists as it should never change.
838
+ const elementRef = React__namespace.useRef(null);
839
+ const compositeRootContext = useCompositeRootContext(true);
840
+ const isCompositeItem = compositeProp ?? compositeRootContext !== undefined;
961
841
  const {
962
- current: isControlled
963
- } = React__namespace.useRef(controlled !== undefined);
964
- const [valueState, setValue] = React__namespace.useState(defaultProp);
965
- const value = isControlled ? controlled : valueState;
842
+ props: focusableWhenDisabledProps
843
+ } = useFocusableWhenDisabled({
844
+ focusableWhenDisabled,
845
+ disabled,
846
+ composite: isCompositeItem,
847
+ tabIndex,
848
+ isNativeButton
849
+ });
966
850
  if (process.env.NODE_ENV !== 'production') {
851
+ // eslint-disable-next-line react-hooks/rules-of-hooks
967
852
  React__namespace.useEffect(() => {
968
- if (isControlled !== (controlled !== undefined)) {
969
- 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'));
853
+ if (!elementRef.current) {
854
+ return;
970
855
  }
971
- }, [state, name, controlled]);
972
- const {
973
- current: defaultValue
974
- } = React__namespace.useRef(defaultProp);
975
- React__namespace.useEffect(() => {
976
- // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is for more details.
977
- if (!isControlled && JSON.stringify(defaultValue) !== JSON.stringify(defaultProp)) {
978
- 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'));
856
+ const isButtonTag = isButtonElement(elementRef.current);
857
+ if (isNativeButton) {
858
+ if (!isButtonTag) {
859
+ const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
860
+ 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`.';
861
+ error(`${message}${ownerStackMessage}`);
862
+ }
863
+ } else if (isButtonTag) {
864
+ const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
865
+ 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`.';
866
+ error(`${message}${ownerStackMessage}`);
979
867
  }
980
- }, [JSON.stringify(defaultProp)]);
868
+ }, [isNativeButton]);
981
869
  }
982
- const setValueIfUncontrolled = React__namespace.useCallback(newValue => {
983
- if (!isControlled) {
984
- setValue(newValue);
985
- }
986
- }, []);
987
- return [value, setValueIfUncontrolled];
988
- }
989
870
 
990
- function useValueChanged(value, onChange) {
991
- const valueRef = React__namespace.useRef(value);
992
- const onChangeCallback = useStableCallback(onChange);
993
- useIsoLayoutEffect(() => {
994
- if (valueRef.current === value) {
871
+ // handles a disabled composite button rendering another button, e.g.
872
+ // <Toolbar.Button disabled render={<Menu.Trigger />} />
873
+ // the `disabled` prop needs to pass through 2 `useButton`s then finally
874
+ // delete the `disabled` attribute from DOM
875
+ const updateDisabled = React__namespace.useCallback(() => {
876
+ const element = elementRef.current;
877
+ if (!isButtonElement(element)) {
995
878
  return;
996
879
  }
997
- onChangeCallback(valueRef.current);
998
- }, [value, onChangeCallback]);
999
- useIsoLayoutEffect(() => {
1000
- valueRef.current = value;
1001
- }, [value]);
880
+ if (isCompositeItem && disabled && focusableWhenDisabledProps.disabled === undefined && element.disabled) {
881
+ element.disabled = false;
882
+ }
883
+ }, [disabled, focusableWhenDisabledProps.disabled, isCompositeItem]);
884
+ useIsoLayoutEffect(updateDisabled, [updateDisabled]);
885
+ const getButtonProps = React__namespace.useCallback((externalProps = {}) => {
886
+ const {
887
+ onClick: externalOnClick,
888
+ onMouseDown: externalOnMouseDown,
889
+ onKeyUp: externalOnKeyUp,
890
+ onKeyDown: externalOnKeyDown,
891
+ onPointerDown: externalOnPointerDown,
892
+ ...otherExternalProps
893
+ } = externalProps;
894
+ const type = isNativeButton ? 'button' : undefined;
895
+ return useRenderElement.mergeProps({
896
+ type,
897
+ onClick(event) {
898
+ if (disabled) {
899
+ event.preventDefault();
900
+ return;
901
+ }
902
+ externalOnClick?.(event);
903
+ },
904
+ onMouseDown(event) {
905
+ if (!disabled) {
906
+ externalOnMouseDown?.(event);
907
+ }
908
+ },
909
+ onKeyDown(event) {
910
+ if (disabled) {
911
+ return;
912
+ }
913
+ useRenderElement.makeEventPreventable(event);
914
+ externalOnKeyDown?.(event);
915
+ if (event.baseUIHandlerPrevented) {
916
+ return;
917
+ }
918
+ const isCurrentTarget = event.target === event.currentTarget;
919
+ const currentTarget = event.currentTarget;
920
+ const isButton = isButtonElement(currentTarget);
921
+ const isLink = !isNativeButton && isValidLinkElement(currentTarget);
922
+ const shouldClick = isCurrentTarget && (isNativeButton ? isButton : !isLink);
923
+ const isEnterKey = event.key === 'Enter';
924
+ const isSpaceKey = event.key === ' ';
925
+ const role = currentTarget.getAttribute('role');
926
+ const isTextNavigationRole = role?.startsWith('menuitem') || role === 'option' || role === 'gridcell';
927
+ if (isCurrentTarget && isCompositeItem && isSpaceKey) {
928
+ if (event.defaultPrevented && isTextNavigationRole) {
929
+ return;
930
+ }
931
+ event.preventDefault();
932
+ if (isLink || isNativeButton && isButton) {
933
+ currentTarget.click();
934
+ event.preventBaseUIHandler();
935
+ } else if (shouldClick) {
936
+ externalOnClick?.(event);
937
+ event.preventBaseUIHandler();
938
+ }
939
+ return;
940
+ }
941
+
942
+ // Keyboard accessibility for native and non-native elements.
943
+ if (shouldClick) {
944
+ if (!isNativeButton && (isSpaceKey || isEnterKey)) {
945
+ event.preventDefault();
946
+ }
947
+ if (!isNativeButton && isEnterKey) {
948
+ externalOnClick?.(event);
949
+ }
950
+ }
951
+ },
952
+ onKeyUp(event) {
953
+ if (disabled) {
954
+ return;
955
+ }
956
+
957
+ // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
958
+ // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
959
+ useRenderElement.makeEventPreventable(event);
960
+ externalOnKeyUp?.(event);
961
+ if (event.target === event.currentTarget && isNativeButton && isCompositeItem && isButtonElement(event.currentTarget) && event.key === ' ') {
962
+ event.preventDefault();
963
+ return;
964
+ }
965
+ if (event.baseUIHandlerPrevented) {
966
+ return;
967
+ }
968
+
969
+ // Keyboard accessibility for non interactive elements
970
+ if (event.target === event.currentTarget && !isNativeButton && !isCompositeItem && event.key === ' ') {
971
+ externalOnClick?.(event);
972
+ }
973
+ },
974
+ onPointerDown(event) {
975
+ if (disabled) {
976
+ event.preventDefault();
977
+ return;
978
+ }
979
+ externalOnPointerDown?.(event);
980
+ }
981
+ }, !isNativeButton ? {
982
+ role: 'button'
983
+ } : undefined, focusableWhenDisabledProps, otherExternalProps);
984
+ }, [disabled, focusableWhenDisabledProps, isCompositeItem, isNativeButton]);
985
+ const buttonRef = useStableCallback(element => {
986
+ elementRef.current = element;
987
+ updateDisabled();
988
+ });
989
+ return {
990
+ getButtonProps,
991
+ buttonRef
992
+ };
993
+ }
994
+ function isButtonElement(elem) {
995
+ return floatingUi_utils_dom.isHTMLElement(elem) && elem.tagName === 'BUTTON';
996
+ }
997
+ function isValidLinkElement(elem) {
998
+ return Boolean(elem?.tagName === 'A' && elem?.href);
999
+ }
1000
+
1001
+ function clamp(val, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
1002
+ return Math.max(min, Math.min(val, max));
1002
1003
  }
1003
1004
 
1004
1005
  exports.AnimationFrame = AnimationFrame;
@@ -1042,6 +1043,7 @@ exports.listNavigation = listNavigation;
1042
1043
  exports.none = none;
1043
1044
  exports.outsidePress = outsidePress;
1044
1045
  exports.ownerDocument = ownerDocument;
1046
+ exports.pointer = pointer;
1045
1047
  exports.resolveRef = resolveRef;
1046
1048
  exports.scrub = scrub;
1047
1049
  exports.siblingOpen = siblingOpen;