@jobber/components 7.3.0 → 7.5.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 (74) hide show
  1. package/dist/Button/Button.d.ts +8 -2
  2. package/dist/Button/index.cjs +1 -1
  3. package/dist/Button/index.mjs +1 -1
  4. package/dist/Button-cjs.js +35 -10
  5. package/dist/Button-es.js +35 -10
  6. package/dist/ButtonDismiss/index.cjs +1 -1
  7. package/dist/ButtonDismiss/index.mjs +1 -1
  8. package/dist/Card/index.cjs +1 -1
  9. package/dist/Card/index.mjs +1 -1
  10. package/dist/Chips/InternalChipDismissible/index.cjs +1 -1
  11. package/dist/Chips/InternalChipDismissible/index.mjs +1 -1
  12. package/dist/Chips/index.cjs +1 -1
  13. package/dist/Chips/index.mjs +1 -1
  14. package/dist/Combobox/components/ComboboxActivator/index.cjs +1 -1
  15. package/dist/Combobox/components/ComboboxActivator/index.mjs +1 -1
  16. package/dist/Combobox/components/ComboboxContent/ComboboxContentHeader/index.cjs +1 -1
  17. package/dist/Combobox/components/ComboboxContent/ComboboxContentHeader/index.mjs +1 -1
  18. package/dist/ConfirmationModal/index.cjs +1 -1
  19. package/dist/ConfirmationModal/index.mjs +1 -1
  20. package/dist/DataDump/index.cjs +1 -1
  21. package/dist/DataDump/index.mjs +1 -1
  22. package/dist/DataList/components/DataListActions/index.cjs +1 -1
  23. package/dist/DataList/components/DataListActions/index.mjs +1 -1
  24. package/dist/DataList/components/DataListBulkActions/index.cjs +1 -1
  25. package/dist/DataList/components/DataListBulkActions/index.mjs +1 -1
  26. package/dist/DataList/components/DataListHeader/index.cjs +1 -1
  27. package/dist/DataList/components/DataListHeader/index.mjs +1 -1
  28. package/dist/DataList/components/DataListItem/index.cjs +1 -1
  29. package/dist/DataList/components/DataListItem/index.mjs +1 -1
  30. package/dist/DataList/components/DataListItemActions/index.cjs +1 -1
  31. package/dist/DataList/components/DataListItemActions/index.mjs +1 -1
  32. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +1 -1
  33. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +1 -1
  34. package/dist/DataList/components/DataListItems/index.cjs +1 -1
  35. package/dist/DataList/components/DataListItems/index.mjs +1 -1
  36. package/dist/DataList/components/DataListLayout/index.cjs +1 -1
  37. package/dist/DataList/components/DataListLayout/index.mjs +1 -1
  38. package/dist/DataList/components/DataListLayoutActions/index.cjs +1 -1
  39. package/dist/DataList/components/DataListLayoutActions/index.mjs +1 -1
  40. package/dist/DataList/components/DataListLoadMore/index.cjs +1 -1
  41. package/dist/DataList/components/DataListLoadMore/index.mjs +1 -1
  42. package/dist/DataList/components/DataListSearch/index.cjs +1 -1
  43. package/dist/DataList/components/DataListSearch/index.mjs +1 -1
  44. package/dist/DatePicker/index.cjs +1 -1
  45. package/dist/DatePicker/index.mjs +1 -1
  46. package/dist/Drawer/index.cjs +1 -1
  47. package/dist/Drawer/index.mjs +1 -1
  48. package/dist/DrawerRoot-cjs.js +17 -1
  49. package/dist/DrawerRoot-es.js +8 -2
  50. package/dist/FeatureSwitch/index.cjs +1 -1
  51. package/dist/FeatureSwitch/index.mjs +1 -1
  52. package/dist/FormField/index.cjs +1 -1
  53. package/dist/FormField/index.mjs +1 -1
  54. package/dist/InputPassword/index.cjs +1 -1
  55. package/dist/InputPassword/index.mjs +1 -1
  56. package/dist/LightBox/index.cjs +1 -1
  57. package/dist/LightBox/index.mjs +1 -1
  58. package/dist/Menu/index.cjs +1 -1
  59. package/dist/Menu/index.mjs +1 -1
  60. package/dist/Modal/index.cjs +1 -1
  61. package/dist/Modal/index.mjs +1 -1
  62. package/dist/RecurringSelect/index.cjs +1 -1
  63. package/dist/RecurringSelect/index.mjs +1 -1
  64. package/dist/SideDrawer/index.cjs +1 -1
  65. package/dist/SideDrawer/index.mjs +1 -1
  66. package/dist/Toast/index.cjs +1 -1
  67. package/dist/Toast/index.mjs +1 -1
  68. package/dist/docs/Button/Button.md +4 -4
  69. package/dist/docs/Icon/Icon.md +1 -0
  70. package/dist/docs/usage-guidelines/usage-guidelines.md +0 -1
  71. package/dist/unstyledPrimitives/index.cjs +1871 -18
  72. package/dist/unstyledPrimitives/index.d.ts +1 -0
  73. package/dist/unstyledPrimitives/index.mjs +1870 -18
  74. package/package.json +3 -3
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { u as useStableCallback, e as useIsoLayoutEffect, f as useId, S as SafeReact, g as useAnimationFrame, h as useTimeout, i as isMouseLikePointerType, j as isTypeableElement, k as createChangeEventDetails, l as isClickLikeEvent, t as triggerPress, m as useFloatingParentNodeId, F as FloatingRootStore, P as PopupTriggerMap, n as useFloatingTree, o as getFloatingFocusElement, p as useValueAsRef, q as isTypeableCombobox, r as isIndexOutOfListBounds, s as getMinListIndex, v as getMaxListIndex, w as activeElement, x as ownerDocument, y as contains, z as stopEvent, A as listNavigation, B as createGridCellMap, C as isListIndexDisabled, E as getGridNavigatedIndex, G as getGridCellIndices, H as getGridCellIndexOfCorner, I as ARROW_DOWN, J as ARROW_LEFT, K as ARROW_RIGHT, L as findNonDisabledListIndex, M as getTarget, N as focusOut, O as enqueueFocus, Q as isVirtualClick, R as isVirtualPointerEvent, T as ARROW_UP, U as isElementVisible, V as useDialogRootContext, W as closePress, X as useTriggerDataForwarding, Y as useInteractions, Z as CLICK_TRIGGER_IDENTIFIER, _ as triggerOpenStateMapping, $ as CommonPopupDataAttributes, a0 as popupStateMapping, a1 as transitionStatusMapping, a2 as useDialogPortalContext, a3 as DialogStore, a4 as imperativeAction, a5 as createSelector, a6 as useControlled, a7 as useStore, a8 as useTransitionStatus, a9 as useOpenInteractionType, aa as createGenericEventDetails, ab as inputClear, ac as inputChange, ad as outsidePress, ae as itemPress, af as useOpenChangeComplete, ag as useValueChanged, ah as inputPress, ai as useDismiss, aj as useOnFirstRender, ak as none, al as visuallyHiddenInput, am as visuallyHidden, an as Store, ao as pressableTriggerOpenStateMapping, ap as isAndroid, aq as isFirefox, ar as escapeKey, as as clearPress, at as FloatingPortal, au as DISABLED_TRANSITIONS_STYLE, av as useScrollLock, aw as InternalBackdrop, ax as inertValue, ay as DROPDOWN_COLLISION_AVOIDANCE, az as FloatingFocusManager, aA as chipRemovePress, aB as useDrawerProviderContext, aC as DrawerBackdropCssVars, aD as DrawerPopupCssVars, aE as DrawerProviderContext, aF as clamp$1, aG as useDrawerRootContext, aH as useTriggerRegistration, aI as DrawerPopupDataAttributes, aJ as swipe, aK as useDrawerSnapPoints, aL as DrawerViewportContext, aM as BASE_UI_SWIPE_IGNORE_SELECTOR, aN as TransitionStatusDataAttributes, aO as DRAWER_CONTENT_ATTRIBUTE, a as DrawerBackdrop, c as DrawerContent, b as DrawerPopup, D as DrawerPortal, d as DrawerRoot } from '../DrawerRoot-es.js';
2
+ import { u as useStableCallback, e as useIsoLayoutEffect, f as useId, S as SafeReact, g as useAnimationFrame, h as useTimeout, i as isMouseLikePointerType, j as isTypeableElement, k as createChangeEventDetails, l as isClickLikeEvent, t as triggerPress, m as useFloatingParentNodeId, F as FloatingRootStore, P as PopupTriggerMap, n as useFloatingTree, o as getFloatingFocusElement, p as useValueAsRef, q as isTypeableCombobox, r as isIndexOutOfListBounds, s as getMinListIndex, v as getMaxListIndex, w as activeElement, x as ownerDocument, y as contains, z as stopEvent, A as listNavigation, B as createGridCellMap, C as isListIndexDisabled, E as getGridNavigatedIndex, G as getGridCellIndices, H as getGridCellIndexOfCorner, I as ARROW_DOWN, J as ARROW_LEFT, K as ARROW_RIGHT, L as findNonDisabledListIndex, M as getTarget, N as focusOut, O as enqueueFocus, Q as isVirtualClick, R as isVirtualPointerEvent, T as ARROW_UP, U as isElementVisible, V as useDialogRootContext, W as closePress, X as useTriggerDataForwarding, Y as useInteractions, Z as CLICK_TRIGGER_IDENTIFIER, _ as triggerOpenStateMapping, $ as CommonPopupDataAttributes, a0 as popupStateMapping, a1 as transitionStatusMapping, a2 as useDialogPortalContext, a3 as DialogStore, a4 as imperativeAction, a5 as createSelector, a6 as useControlled, a7 as useStore, a8 as useTransitionStatus, a9 as useOpenInteractionType, aa as createGenericEventDetails, ab as inputClear, ac as inputChange, ad as outsidePress, ae as itemPress, af as useOpenChangeComplete, ag as useValueChanged, ah as none, ai as inputPress, aj as useDismiss, ak as useOnFirstRender, al as visuallyHiddenInput, am as visuallyHidden, an as Store, ao as pressableTriggerOpenStateMapping, ap as isAndroid, aq as isFirefox, ar as escapeKey, as as clearPress, at as FloatingPortal, au as DISABLED_TRANSITIONS_STYLE, av as useScrollLock, aw as InternalBackdrop, ax as inertValue, ay as DROPDOWN_COLLISION_AVOIDANCE, az as FloatingFocusManager, aA as chipRemovePress, aB as useDrawerProviderContext, aC as DrawerBackdropCssVars, aD as DrawerPopupCssVars, aE as DrawerProviderContext, aF as clamp$1, aG as useDrawerRootContext, aH as useTriggerRegistration, aI as DrawerPopupDataAttributes, aJ as swipe, aK as useDrawerSnapPoints, aL as DrawerViewportContext, aM as BASE_UI_SWIPE_IGNORE_SELECTOR, aN as TransitionStatusDataAttributes, aO as DRAWER_CONTENT_ATTRIBUTE, a as DrawerBackdrop, c as DrawerContent, b as DrawerPopup, D as DrawerPortal, d as DrawerRoot, aP as useOnMount, aQ as Timeout, aR as isIOS, aS as inputBlur, aT as inputPaste, aU as incrementPress, aV as decrementPress, aW as keyboard, aX as scrub, aY as isWebKit } from '../DrawerRoot-es.js';
3
3
  import { a as useRefWithInit, f as formatErrorMessage, m as mergeProps, c as makeEventPreventable, E as EMPTY_OBJECT, u as useRenderElement, N as NOOP, d as EMPTY_ARRAY, b as useMergedRefs } from '../useRenderElement-es.js';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
  import * as ReactDOM from 'react-dom';
@@ -602,6 +602,16 @@ function useClick(context, props = {}) {
602
602
  } : EMPTY_OBJECT, [enabled, reference]);
603
603
  }
604
604
 
605
+ /**
606
+ * Returns a function that forces a rerender.
607
+ */
608
+ function useForcedRerendering() {
609
+ const [, setState] = React.useState({});
610
+ return React.useCallback(() => {
611
+ setState({});
612
+ }, []);
613
+ }
614
+
605
615
  function useFloatingRootContext(options) {
606
616
  const {
607
617
  open = false,
@@ -1693,7 +1703,7 @@ let DialogViewportDataAttributes = function (DialogViewportDataAttributes) {
1693
1703
  return DialogViewportDataAttributes;
1694
1704
  }({});
1695
1705
 
1696
- const stateAttributesMapping$6 = {
1706
+ const stateAttributesMapping$8 = {
1697
1707
  ...popupStateMapping,
1698
1708
  ...transitionStatusMapping,
1699
1709
  nested(value) {
@@ -1742,7 +1752,7 @@ const DialogViewport = /*#__PURE__*/React.forwardRef(function DialogViewport(com
1742
1752
  enabled: shouldRender,
1743
1753
  state,
1744
1754
  ref: [forwardedRef, store.useStateSetter('viewportElement')],
1745
- stateAttributesMapping: stateAttributesMapping$6,
1755
+ stateAttributesMapping: stateAttributesMapping$8,
1746
1756
  props: [{
1747
1757
  role: 'presentation',
1748
1758
  hidden: !mounted,
@@ -4236,7 +4246,7 @@ const ComboboxIcon = /*#__PURE__*/React.forwardRef(function ComboboxIcon(compone
4236
4246
  });
4237
4247
  if (process.env.NODE_ENV !== "production") ComboboxIcon.displayName = "ComboboxIcon";
4238
4248
 
4239
- const stateAttributesMapping$5 = {
4249
+ const stateAttributesMapping$7 = {
4240
4250
  ...transitionStatusMapping,
4241
4251
  ...triggerOpenStateMapping
4242
4252
  };
@@ -4332,7 +4342,7 @@ const ComboboxClear = /*#__PURE__*/React.forwardRef(function ComboboxClear(compo
4332
4342
  store.state.inputRef.current?.focus();
4333
4343
  }
4334
4344
  }, elementProps, getButtonProps],
4335
- stateAttributesMapping: stateAttributesMapping$5
4345
+ stateAttributesMapping: stateAttributesMapping$7
4336
4346
  });
4337
4347
  const shouldRender = keepMounted || mounted;
4338
4348
  if (!shouldRender) {
@@ -4528,7 +4538,7 @@ const ComboboxPortal = /*#__PURE__*/React.forwardRef(function ComboboxPortal(pro
4528
4538
  });
4529
4539
  if (process.env.NODE_ENV !== "production") ComboboxPortal.displayName = "ComboboxPortal";
4530
4540
 
4531
- const stateAttributesMapping$4 = {
4541
+ const stateAttributesMapping$6 = {
4532
4542
  ...popupStateMapping,
4533
4543
  ...transitionStatusMapping
4534
4544
  };
@@ -4554,7 +4564,7 @@ const ComboboxBackdrop = /*#__PURE__*/React.forwardRef(function ComboboxBackdrop
4554
4564
  return useRenderElement('div', componentProps, {
4555
4565
  state,
4556
4566
  ref: forwardedRef,
4557
- stateAttributesMapping: stateAttributesMapping$4,
4567
+ stateAttributesMapping: stateAttributesMapping$6,
4558
4568
  props: [{
4559
4569
  role: 'presentation',
4560
4570
  hidden: !mounted,
@@ -5179,7 +5189,7 @@ const ComboboxPositioner = /*#__PURE__*/React.forwardRef(function ComboboxPositi
5179
5189
  });
5180
5190
  if (process.env.NODE_ENV !== "production") ComboboxPositioner.displayName = "ComboboxPositioner";
5181
5191
 
5182
- const stateAttributesMapping$3 = {
5192
+ const stateAttributesMapping$5 = {
5183
5193
  ...popupStateMapping,
5184
5194
  ...transitionStatusMapping
5185
5195
  };
@@ -5240,7 +5250,7 @@ const ComboboxPopup = /*#__PURE__*/React.forwardRef(function ComboboxPopup(compo
5240
5250
  }
5241
5251
  }
5242
5252
  }, getDisabledMountTransitionStyles(transitionStatus), elementProps],
5243
- stateAttributesMapping: stateAttributesMapping$3
5253
+ stateAttributesMapping: stateAttributesMapping$5
5244
5254
  });
5245
5255
 
5246
5256
  // Default initial focus logic:
@@ -6088,7 +6098,7 @@ const ComboboxChipRemove = /*#__PURE__*/React.forwardRef(function ComboboxChipRe
6088
6098
  });
6089
6099
  if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";
6090
6100
 
6091
- var index_parts$1 = /*#__PURE__*/Object.freeze({
6101
+ var index_parts$2 = /*#__PURE__*/Object.freeze({
6092
6102
  __proto__: null,
6093
6103
  Arrow: ComboboxArrow,
6094
6104
  Backdrop: ComboboxBackdrop,
@@ -6136,7 +6146,7 @@ const DrawerClose = DialogClose;
6136
6146
  */
6137
6147
  const DrawerDescription = DialogDescription;
6138
6148
 
6139
- const stateAttributesMapping$2 = {
6149
+ const stateAttributesMapping$4 = {
6140
6150
  active(value) {
6141
6151
  if (value) {
6142
6152
  return {
@@ -6206,12 +6216,12 @@ const DrawerIndent = /*#__PURE__*/React.forwardRef(function DrawerIndent(compone
6206
6216
  [DrawerBackdropCssVars.swipeProgress]: '0'
6207
6217
  }
6208
6218
  }, elementProps],
6209
- stateAttributesMapping: stateAttributesMapping$2
6219
+ stateAttributesMapping: stateAttributesMapping$4
6210
6220
  });
6211
6221
  });
6212
6222
  if (process.env.NODE_ENV !== "production") DrawerIndent.displayName = "DrawerIndent";
6213
6223
 
6214
- const stateAttributesMapping$1 = {
6224
+ const stateAttributesMapping$3 = {
6215
6225
  active(value) {
6216
6226
  if (value) {
6217
6227
  return {
@@ -6245,7 +6255,7 @@ const DrawerIndentBackground = /*#__PURE__*/React.forwardRef(function DrawerInde
6245
6255
  ref: forwardedRef,
6246
6256
  state,
6247
6257
  props: elementProps,
6248
- stateAttributesMapping: stateAttributesMapping$1
6258
+ stateAttributesMapping: stateAttributesMapping$3
6249
6259
  });
6250
6260
  });
6251
6261
  if (process.env.NODE_ENV !== "production") DrawerIndentBackground.displayName = "DrawerIndentBackground";
@@ -7345,7 +7355,7 @@ const SWIPE_AREA_SWIPING_HOOK = {
7345
7355
  const SWIPE_AREA_DISABLED_HOOK = {
7346
7356
  [DrawerSwipeAreaDataAttributes.disabled]: ''
7347
7357
  };
7348
- const stateAttributesMapping = {
7358
+ const stateAttributesMapping$2 = {
7349
7359
  open(value) {
7350
7360
  return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;
7351
7361
  },
@@ -7650,7 +7660,7 @@ const DrawerSwipeArea = /*#__PURE__*/React.forwardRef(function DrawerSwipeArea(c
7650
7660
  return useRenderElement('div', componentProps, {
7651
7661
  state,
7652
7662
  ref: [forwardedRef, swipeAreaRef, registerTrigger],
7653
- stateAttributesMapping,
7663
+ stateAttributesMapping: stateAttributesMapping$2,
7654
7664
  props: [{
7655
7665
  role: 'presentation',
7656
7666
  'aria-hidden': true,
@@ -8670,7 +8680,7 @@ function shouldDismissFromStartEdge(direction, axis) {
8670
8680
  return null;
8671
8681
  }
8672
8682
 
8673
- var index_parts = /*#__PURE__*/Object.freeze({
8683
+ var index_parts$1 = /*#__PURE__*/Object.freeze({
8674
8684
  __proto__: null,
8675
8685
  Backdrop: DrawerBackdrop,
8676
8686
  Close: DrawerClose,
@@ -8690,4 +8700,1846 @@ var index_parts = /*#__PURE__*/Object.freeze({
8690
8700
  createHandle: createDialogHandle
8691
8701
  });
8692
8702
 
8693
- export { index_parts$1 as Combobox, index_parts as Drawer, Separator };
8703
+ const cache = new Map();
8704
+ function getFormatter(locale, options) {
8705
+ const optionsString = JSON.stringify({
8706
+ locale,
8707
+ options
8708
+ });
8709
+ const cachedFormatter = cache.get(optionsString);
8710
+ if (cachedFormatter) {
8711
+ return cachedFormatter;
8712
+ }
8713
+ const formatter = new Intl.NumberFormat(locale, options);
8714
+ cache.set(optionsString, formatter);
8715
+ return formatter;
8716
+ }
8717
+ function formatNumber(value, locale, options) {
8718
+ if (value == null) {
8719
+ return '';
8720
+ }
8721
+ return getFormatter(locale, options).format(value);
8722
+ }
8723
+ function formatNumberMaxPrecision(value, locale, options) {
8724
+ return formatNumber(value, locale, {
8725
+ ...options,
8726
+ maximumFractionDigits: 20
8727
+ });
8728
+ }
8729
+
8730
+ const EMPTY = 0;
8731
+ class Interval extends Timeout {
8732
+ static create() {
8733
+ return new Interval();
8734
+ }
8735
+
8736
+ /**
8737
+ * Executes `fn` at `delay` interval, clearing any previously scheduled call.
8738
+ */
8739
+ start(delay, fn) {
8740
+ this.clear();
8741
+ this.currentId = setInterval(() => {
8742
+ fn();
8743
+ }, delay);
8744
+ }
8745
+ clear = () => {
8746
+ if (this.currentId !== EMPTY) {
8747
+ clearInterval(this.currentId);
8748
+ this.currentId = EMPTY;
8749
+ }
8750
+ };
8751
+ }
8752
+
8753
+ /**
8754
+ * A `setInterval` with automatic cleanup and guard.
8755
+ */
8756
+ function useInterval() {
8757
+ const timeout = useRefWithInit(Interval.create).current;
8758
+ useOnMount(timeout.disposeEffect);
8759
+ return timeout;
8760
+ }
8761
+
8762
+ const NumberFieldRootContext = /*#__PURE__*/React.createContext(undefined);
8763
+ if (process.env.NODE_ENV !== "production") NumberFieldRootContext.displayName = "NumberFieldRootContext";
8764
+ function useNumberFieldRootContext() {
8765
+ const context = React.useContext(NumberFieldRootContext);
8766
+ if (context === undefined) {
8767
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: NumberFieldRootContext is missing. NumberField parts must be placed within <NumberField.Root>.' : formatErrorMessage(43));
8768
+ }
8769
+ return context;
8770
+ }
8771
+
8772
+ const stateAttributesMapping$1 = {
8773
+ inputValue: () => null,
8774
+ value: () => null,
8775
+ ...fieldValidityMapping
8776
+ };
8777
+
8778
+ const HAN_NUMERALS = ['零', '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
8779
+ // Map Han numeral characters to ASCII digits. Includes both forms of zero.
8780
+ const HAN_NUMERAL_TO_DIGIT = {
8781
+ 零: '0',
8782
+ 〇: '0',
8783
+ 一: '1',
8784
+ 二: '2',
8785
+ 三: '3',
8786
+ 四: '4',
8787
+ 五: '5',
8788
+ 六: '6',
8789
+ 七: '7',
8790
+ 八: '8',
8791
+ 九: '9'
8792
+ };
8793
+ const ARABIC_NUMERALS = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
8794
+ const PERSIAN_NUMERALS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
8795
+ const FULLWIDTH_NUMERALS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
8796
+ const PERCENTAGES = ['%', '٪', '%', '﹪'];
8797
+ const PERMILLE = ['‰', '؉'];
8798
+ const UNICODE_MINUS_SIGNS = ['−', '-', '‒', '–', '—', '﹣'];
8799
+ const UNICODE_PLUS_SIGNS = ['+', '﹢'];
8800
+
8801
+ // Fullwidth punctuation common in CJK inputs
8802
+ const FULLWIDTH_DECIMAL = '.'; // U+FF0E
8803
+ const FULLWIDTH_GROUP = ','; // U+FF0C
8804
+
8805
+ const ARABIC_RE = new RegExp(`[${ARABIC_NUMERALS.join('')}]`, 'g');
8806
+ const PERSIAN_RE = new RegExp(`[${PERSIAN_NUMERALS.join('')}]`, 'g');
8807
+ const FULLWIDTH_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`, 'g');
8808
+ const HAN_RE = new RegExp(`[${HAN_NUMERALS.join('')}]`, 'g');
8809
+ const PERCENT_RE = new RegExp(`[${PERCENTAGES.join('')}]`);
8810
+ const PERMILLE_RE = new RegExp(`[${PERMILLE.join('')}]`);
8811
+
8812
+ // Detection regexes (non-global to avoid lastIndex side effects)
8813
+ const ARABIC_DETECT_RE = /[٠١٢٣٤٥٦٧٨٩]/;
8814
+ const PERSIAN_DETECT_RE = /[۰۱۲۳۴۵۶۷۸۹]/;
8815
+ const HAN_DETECT_RE = /[零〇一二三四五六七八九]/;
8816
+ const FULLWIDTH_DETECT_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`);
8817
+ const BASE_NON_NUMERIC_SYMBOLS = ['.', ',', FULLWIDTH_DECIMAL, FULLWIDTH_GROUP, '٫', '٬'];
8818
+ const SPACE_SEPARATOR_RE = /\p{Zs}/u;
8819
+ const PLUS_SIGNS_WITH_ASCII = ['+', ...UNICODE_PLUS_SIGNS];
8820
+ const MINUS_SIGNS_WITH_ASCII = ['-', ...UNICODE_MINUS_SIGNS];
8821
+ const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
8822
+ const escapeClassChar = s => s.replace(/[-\\\]^]/g, m => `\\${m}`); // escape for use inside [...]
8823
+
8824
+ const charClassFrom = chars => `[${chars.map(escapeClassChar).join('')}]`;
8825
+ const ANY_MINUS_CLASS = charClassFrom(['-'].concat(UNICODE_MINUS_SIGNS));
8826
+ const ANY_PLUS_CLASS = charClassFrom(['+'].concat(UNICODE_PLUS_SIGNS));
8827
+ const ANY_MINUS_RE = new RegExp(ANY_MINUS_CLASS, 'gu');
8828
+ const ANY_PLUS_RE = new RegExp(ANY_PLUS_CLASS, 'gu');
8829
+ const ANY_MINUS_DETECT_RE = new RegExp(ANY_MINUS_CLASS);
8830
+ const ANY_PLUS_DETECT_RE = new RegExp(ANY_PLUS_CLASS);
8831
+ function getNumberLocaleDetails(locale, options) {
8832
+ const parts = getFormatter(locale, options).formatToParts(11111.1);
8833
+ const result = {};
8834
+ parts.forEach(part => {
8835
+ result[part.type] = part.value;
8836
+ });
8837
+
8838
+ // The formatting options may result in not returning a decimal.
8839
+ getFormatter(locale).formatToParts(0.1).forEach(part => {
8840
+ if (part.type === 'decimal') {
8841
+ result[part.type] = part.value;
8842
+ }
8843
+ });
8844
+ return result;
8845
+ }
8846
+ function parseNumber(formattedNumber, locale, options) {
8847
+ if (formattedNumber == null) {
8848
+ return null;
8849
+ }
8850
+
8851
+ // Normalize control characters and whitespace; remove bidi/format controls
8852
+ let input = String(formattedNumber).replace(/\p{Cf}/gu, '').trim();
8853
+
8854
+ // Normalize unicode minus/plus to ASCII, handle leading/trailing signs
8855
+ input = input.replace(ANY_MINUS_RE, '-').replace(ANY_PLUS_RE, '+');
8856
+ let isNegative = false;
8857
+
8858
+ // Trailing sign, e.g. "1234-" / "1234+"
8859
+ const trailing = input.match(/([+-])\s*$/);
8860
+ if (trailing) {
8861
+ if (trailing[1] === '-') {
8862
+ isNegative = true;
8863
+ }
8864
+ input = input.replace(/([+-])\s*$/, '');
8865
+ }
8866
+ // Leading sign
8867
+ const leading = input.match(/^\s*([+-])/);
8868
+ if (leading) {
8869
+ if (leading[1] === '-') {
8870
+ isNegative = true;
8871
+ }
8872
+ input = input.replace(/^\s*[+-]/, '');
8873
+ }
8874
+
8875
+ // Heuristic locale detection
8876
+ let computedLocale = locale;
8877
+ if (computedLocale === undefined) {
8878
+ if (ARABIC_DETECT_RE.test(input) || PERSIAN_DETECT_RE.test(input)) {
8879
+ computedLocale = 'ar';
8880
+ } else if (HAN_DETECT_RE.test(input)) {
8881
+ computedLocale = 'zh';
8882
+ }
8883
+ }
8884
+ const {
8885
+ group,
8886
+ decimal,
8887
+ currency
8888
+ } = getNumberLocaleDetails(computedLocale, options);
8889
+
8890
+ // Build robust unit regex from all unit parts (such as "km/h")
8891
+ const unitParts = getFormatter(computedLocale, options).formatToParts(1).filter(p => p.type === 'unit').map(p => escapeRegExp(p.value));
8892
+ const unitRegex = unitParts.length ? new RegExp(unitParts.join('|'), 'g') : null;
8893
+ let groupRegex = null;
8894
+ if (group) {
8895
+ const isSpaceGroup = /\p{Zs}/u.test(group);
8896
+ const isApostropheGroup = group === "'" || group === '’';
8897
+
8898
+ // Check if the group separator is a space-like character.
8899
+ // If so, we'll replace all such characters with an empty string.
8900
+ if (isSpaceGroup) {
8901
+ groupRegex = /\p{Zs}/gu;
8902
+ } else if (isApostropheGroup) {
8903
+ // Some environments format numbers with ASCII apostrophe and others with a curly apostrophe.
8904
+ groupRegex = /['’]/g;
8905
+ } else {
8906
+ groupRegex = new RegExp(escapeRegExp(group), 'g');
8907
+ }
8908
+ }
8909
+ const replacements = [{
8910
+ regex: group ? groupRegex : null,
8911
+ replacement: ''
8912
+ }, {
8913
+ regex: decimal ? new RegExp(escapeRegExp(decimal), 'g') : null,
8914
+ replacement: '.'
8915
+ },
8916
+ // Fullwidth punctuation
8917
+ {
8918
+ regex: /./g,
8919
+ replacement: '.'
8920
+ },
8921
+ // FULLWIDTH_DECIMAL
8922
+ {
8923
+ regex: /,/g,
8924
+ replacement: ''
8925
+ },
8926
+ // FULLWIDTH_GROUP
8927
+ // Arabic punctuation
8928
+ {
8929
+ regex: /٫/g,
8930
+ replacement: '.'
8931
+ },
8932
+ // ARABIC DECIMAL SEPARATOR (U+066B)
8933
+ {
8934
+ regex: /٬/g,
8935
+ replacement: ''
8936
+ },
8937
+ // ARABIC THOUSANDS SEPARATOR (U+066C)
8938
+ // Currency & unit labels
8939
+ {
8940
+ regex: currency ? new RegExp(escapeRegExp(currency), 'g') : null,
8941
+ replacement: ''
8942
+ }, {
8943
+ regex: unitRegex,
8944
+ replacement: ''
8945
+ },
8946
+ // Numeral systems to ASCII digits
8947
+ {
8948
+ regex: ARABIC_RE,
8949
+ replacement: ch => String(ARABIC_NUMERALS.indexOf(ch))
8950
+ }, {
8951
+ regex: PERSIAN_RE,
8952
+ replacement: ch => String(PERSIAN_NUMERALS.indexOf(ch))
8953
+ }, {
8954
+ regex: FULLWIDTH_RE,
8955
+ replacement: ch => String(FULLWIDTH_NUMERALS.indexOf(ch))
8956
+ }, {
8957
+ regex: HAN_RE,
8958
+ replacement: ch => HAN_NUMERAL_TO_DIGIT[ch]
8959
+ }];
8960
+ let unformatted = replacements.reduce((acc, {
8961
+ regex,
8962
+ replacement
8963
+ }) => {
8964
+ return regex ? acc.replace(regex, replacement) : acc;
8965
+ }, input);
8966
+
8967
+ // Mixed-locale safety: keep only the last '.' as decimal
8968
+ const lastDot = unformatted.lastIndexOf('.');
8969
+ if (lastDot !== -1) {
8970
+ unformatted = `${unformatted.slice(0, lastDot).replace(/\./g, '')}.${unformatted.slice(lastDot + 1).replace(/\./g, '')}`;
8971
+ }
8972
+
8973
+ // Guard against Infinity inputs (ASCII and symbol)
8974
+ if (/^[-+]?Infinity$/i.test(input) || /[∞]/.test(input)) {
8975
+ return null;
8976
+ }
8977
+ const parseTarget = (isNegative ? '-' : '') + unformatted;
8978
+ let num = parseFloat(parseTarget);
8979
+ const style = options?.style;
8980
+ const isUnitPercent = style === 'unit' && options?.unit === 'percent';
8981
+ const hasPercentSymbol = PERCENT_RE.test(formattedNumber) || style === 'percent';
8982
+ const hasPermilleSymbol = PERMILLE_RE.test(formattedNumber);
8983
+ if (hasPermilleSymbol) {
8984
+ num /= 1000;
8985
+ } else if (!isUnitPercent && hasPercentSymbol) {
8986
+ num /= 100;
8987
+ }
8988
+ if (Number.isNaN(num)) {
8989
+ return null;
8990
+ }
8991
+ return num;
8992
+ }
8993
+
8994
+ const CHANGE_VALUE_TICK_DELAY = 60;
8995
+ const START_AUTO_CHANGE_DELAY = 400;
8996
+ const TOUCH_TIMEOUT = 50;
8997
+ const MAX_POINTER_MOVES_AFTER_TOUCH = 3;
8998
+ const SCROLLING_POINTER_MOVE_DISTANCE = 8;
8999
+ const DEFAULT_STEP = 1;
9000
+
9001
+ const STEP_EPSILON_FACTOR = 1e-10;
9002
+ function getFractionDigits(format) {
9003
+ const defaultOptions = getFormatter('en-US').resolvedOptions();
9004
+ const minimumFractionDigits = format?.minimumFractionDigits ?? defaultOptions.minimumFractionDigits ?? 0;
9005
+ const maximumFractionDigits = Math.max(format?.maximumFractionDigits ?? defaultOptions.maximumFractionDigits ?? 20, minimumFractionDigits);
9006
+ return {
9007
+ maximumFractionDigits,
9008
+ minimumFractionDigits
9009
+ };
9010
+ }
9011
+ function roundToFractionDigits(value, maximumFractionDigits) {
9012
+ if (!Number.isFinite(value)) {
9013
+ return value;
9014
+ }
9015
+ const digits = Math.min(Math.max(maximumFractionDigits, 0), 20);
9016
+ return Number(value.toFixed(digits));
9017
+ }
9018
+ function removeFloatingPointErrors(value, format) {
9019
+ const {
9020
+ maximumFractionDigits
9021
+ } = getFractionDigits(format);
9022
+ return roundToFractionDigits(value, maximumFractionDigits);
9023
+ }
9024
+ function snapToStep(clampedValue, base, step, mode = 'directional') {
9025
+ if (step === 0) {
9026
+ return clampedValue;
9027
+ }
9028
+ const stepSize = Math.abs(step);
9029
+ const direction = Math.sign(step);
9030
+ const tolerance = stepSize * STEP_EPSILON_FACTOR * direction;
9031
+ const divisor = mode === 'nearest' ? step : stepSize;
9032
+ const rawSteps = (clampedValue - base + tolerance) / divisor;
9033
+ let snappedSteps;
9034
+ if (mode === 'nearest') {
9035
+ snappedSteps = Math.round(rawSteps);
9036
+ } else if (direction > 0) {
9037
+ snappedSteps = Math.floor(rawSteps);
9038
+ } else {
9039
+ snappedSteps = Math.ceil(rawSteps);
9040
+ }
9041
+ const stepForResult = mode === 'nearest' ? step : stepSize;
9042
+ return base + snappedSteps * stepForResult;
9043
+ }
9044
+ function toValidatedNumber(value, {
9045
+ step,
9046
+ minWithDefault,
9047
+ maxWithDefault,
9048
+ minWithZeroDefault,
9049
+ format,
9050
+ snapOnStep,
9051
+ small,
9052
+ clamp: shouldClamp
9053
+ }) {
9054
+ if (value === null) {
9055
+ return value;
9056
+ }
9057
+ const clampedValue = shouldClamp ? clamp$1(value, minWithDefault, maxWithDefault) : value;
9058
+ if (step != null && snapOnStep) {
9059
+ if (step === 0) {
9060
+ return removeFloatingPointErrors(clampedValue, format);
9061
+ }
9062
+
9063
+ // If a real minimum is provided, use it
9064
+ let base = minWithZeroDefault;
9065
+ if (!small && minWithDefault !== Number.MIN_SAFE_INTEGER) {
9066
+ base = minWithDefault;
9067
+ }
9068
+ const snappedValue = snapToStep(clampedValue, base, step, small ? 'nearest' : 'directional');
9069
+ return removeFloatingPointErrors(snappedValue, format);
9070
+ }
9071
+ return removeFloatingPointErrors(clampedValue, format);
9072
+ }
9073
+
9074
+ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(componentProps, forwardedRef) {
9075
+ const {
9076
+ id: idProp,
9077
+ min,
9078
+ max,
9079
+ smallStep = 0.1,
9080
+ step: stepProp = 1,
9081
+ largeStep = 10,
9082
+ required = false,
9083
+ disabled: disabledProp = false,
9084
+ readOnly = false,
9085
+ name: nameProp,
9086
+ defaultValue,
9087
+ value: valueProp,
9088
+ onValueChange: onValueChangeProp,
9089
+ onValueCommitted: onValueCommittedProp,
9090
+ allowWheelScrub = false,
9091
+ snapOnStep = false,
9092
+ allowOutOfRange = false,
9093
+ format,
9094
+ locale,
9095
+ render,
9096
+ className,
9097
+ inputRef: inputRefProp,
9098
+ ...elementProps
9099
+ } = componentProps;
9100
+ const {
9101
+ setDirty,
9102
+ validityData,
9103
+ disabled: fieldDisabled,
9104
+ setFilled,
9105
+ invalid,
9106
+ name: fieldName,
9107
+ state: fieldState,
9108
+ validation,
9109
+ shouldValidateOnChange
9110
+ } = useFieldRootContext();
9111
+ const disabled = fieldDisabled || disabledProp;
9112
+ const name = fieldName ?? nameProp;
9113
+ const step = stepProp === 'any' ? 1 : stepProp;
9114
+ const [isScrubbing, setIsScrubbing] = React.useState(false);
9115
+ const minWithDefault = min ?? Number.MIN_SAFE_INTEGER;
9116
+ const maxWithDefault = max ?? Number.MAX_SAFE_INTEGER;
9117
+ const minWithZeroDefault = min ?? 0;
9118
+ const formatStyle = format?.style;
9119
+ const inputRef = React.useRef(null);
9120
+ const hiddenInputRef = useMergedRefs(inputRefProp, validation.inputRef);
9121
+ const id = useLabelableId({
9122
+ id: idProp
9123
+ });
9124
+ const [valueUnwrapped, setValueUnwrapped] = useControlled({
9125
+ controlled: valueProp,
9126
+ default: defaultValue,
9127
+ name: 'NumberField',
9128
+ state: 'value'
9129
+ });
9130
+ const value = valueUnwrapped ?? null;
9131
+ const valueRef = useValueAsRef(value);
9132
+ useIsoLayoutEffect(() => {
9133
+ setFilled(value !== null);
9134
+ }, [setFilled, value]);
9135
+ const forceRender = useForcedRerendering();
9136
+ const formatOptionsRef = useValueAsRef(format);
9137
+ const hasPendingCommitRef = React.useRef(false);
9138
+ const onValueCommitted = useStableCallback((nextValue, eventDetails) => {
9139
+ hasPendingCommitRef.current = false;
9140
+ onValueCommittedProp?.(nextValue, eventDetails);
9141
+ });
9142
+ const startTickTimeout = useTimeout();
9143
+ const tickInterval = useInterval();
9144
+ const intentionalTouchCheckTimeout = useTimeout();
9145
+ const isPressedRef = React.useRef(false);
9146
+ const movesAfterTouchRef = React.useRef(0);
9147
+ const allowInputSyncRef = React.useRef(true);
9148
+ const lastChangedValueRef = React.useRef(null);
9149
+ const unsubscribeFromGlobalContextMenuRef = React.useRef(() => {});
9150
+
9151
+ // During SSR, the value is formatted on the server, whose locale may differ from the client's
9152
+ // locale. This causes a hydration mismatch, which we manually suppress. This is preferable to
9153
+ // rendering an empty input field and then updating it with the formatted value, as the user
9154
+ // can still see the value prior to hydration, even if it's not formatted correctly.
9155
+ const [inputValue, setInputValue] = React.useState(() => {
9156
+ if (valueProp !== undefined) {
9157
+ return getControlledInputValue(value, locale, format);
9158
+ }
9159
+ return formatNumber(value, locale, format);
9160
+ });
9161
+ const [inputMode, setInputMode] = React.useState('numeric');
9162
+ const getAllowedNonNumericKeys = useStableCallback(() => {
9163
+ const {
9164
+ decimal,
9165
+ group,
9166
+ currency,
9167
+ literal
9168
+ } = getNumberLocaleDetails(locale, format);
9169
+ const keys = new Set();
9170
+ BASE_NON_NUMERIC_SYMBOLS.forEach(symbol => keys.add(symbol));
9171
+ if (decimal) {
9172
+ keys.add(decimal);
9173
+ }
9174
+ if (group) {
9175
+ keys.add(group);
9176
+ if (SPACE_SEPARATOR_RE.test(group)) {
9177
+ keys.add(' ');
9178
+ }
9179
+ }
9180
+ const allowPercentSymbols = formatStyle === 'percent' || formatStyle === 'unit' && format?.unit === 'percent';
9181
+ const allowPermilleSymbols = formatStyle === 'percent' || formatStyle === 'unit' && format?.unit === 'permille';
9182
+ if (allowPercentSymbols) {
9183
+ PERCENTAGES.forEach(key => keys.add(key));
9184
+ }
9185
+ if (allowPermilleSymbols) {
9186
+ PERMILLE.forEach(key => keys.add(key));
9187
+ }
9188
+ if (formatStyle === 'currency' && currency) {
9189
+ keys.add(currency);
9190
+ }
9191
+ if (literal) {
9192
+ // Some locales (e.g. de-DE) insert a literal space character between the number
9193
+ // and the symbol, so allow those characters to be typed/removed.
9194
+ Array.from(literal).forEach(char => keys.add(char));
9195
+ if (SPACE_SEPARATOR_RE.test(literal)) {
9196
+ keys.add(' ');
9197
+ }
9198
+ }
9199
+
9200
+ // Allow plus sign in all cases; minus sign only when negatives are valid
9201
+ PLUS_SIGNS_WITH_ASCII.forEach(key => keys.add(key));
9202
+ if (minWithDefault < 0) {
9203
+ MINUS_SIGNS_WITH_ASCII.forEach(key => keys.add(key));
9204
+ }
9205
+ return keys;
9206
+ });
9207
+ const getStepAmount = useStableCallback(event => {
9208
+ if (event?.altKey) {
9209
+ return smallStep;
9210
+ }
9211
+ if (event?.shiftKey) {
9212
+ return largeStep;
9213
+ }
9214
+ return step;
9215
+ });
9216
+ const setValue = useStableCallback((unvalidatedValue, details) => {
9217
+ const eventWithOptionalKeyState = details.event;
9218
+ const dir = details.direction;
9219
+ const reason = details.reason;
9220
+ // Only allow out-of-range values for direct text entry (native-like behavior).
9221
+ // Step-based interactions (keyboard arrows, buttons, wheel, scrub) still clamp to min/max.
9222
+ const shouldClampValue = !allowOutOfRange || !(reason === inputChange || reason === inputBlur || reason === inputPaste || reason === inputClear || reason === none);
9223
+ const validatedValue = toValidatedNumber(unvalidatedValue, {
9224
+ step: dir ? getStepAmount(eventWithOptionalKeyState) * dir : undefined,
9225
+ format: formatOptionsRef.current,
9226
+ minWithDefault,
9227
+ maxWithDefault,
9228
+ minWithZeroDefault,
9229
+ snapOnStep,
9230
+ small: eventWithOptionalKeyState?.altKey ?? false,
9231
+ clamp: shouldClampValue
9232
+ });
9233
+
9234
+ // Determine whether we should notify about a change even if the numeric value is unchanged.
9235
+ // This is needed when the user input is clamped/snapped to the same current value, or when
9236
+ // the source value differs but validation normalizes to the existing value.
9237
+ const isInputReason = details.reason === inputChange || details.reason === inputClear || details.reason === inputBlur || details.reason === inputPaste || details.reason === none;
9238
+ const shouldFireChange = validatedValue !== value || isInputReason && (unvalidatedValue !== value || allowInputSyncRef.current === false);
9239
+ if (shouldFireChange) {
9240
+ lastChangedValueRef.current = validatedValue;
9241
+ onValueChangeProp?.(validatedValue, details);
9242
+ if (details.isCanceled) {
9243
+ return shouldFireChange;
9244
+ }
9245
+ setValueUnwrapped(validatedValue);
9246
+ setDirty(validatedValue !== validityData.initialValue);
9247
+ hasPendingCommitRef.current = true;
9248
+ }
9249
+
9250
+ // Keep the visible input in sync immediately when programmatic changes occur
9251
+ // (increment/decrement, wheel, etc). During direct typing we don't want
9252
+ // to overwrite the user-provided text until blur, so we gate on
9253
+ // `allowInputSyncRef`.
9254
+ if (allowInputSyncRef.current) {
9255
+ setInputValue(formatNumber(validatedValue, locale, format));
9256
+ }
9257
+
9258
+ // Formatting can change even if the numeric value hasn't, so ensure a re-render when needed.
9259
+ forceRender();
9260
+ return shouldFireChange;
9261
+ });
9262
+ const incrementValue = useStableCallback((amount, {
9263
+ direction,
9264
+ currentValue,
9265
+ event,
9266
+ reason
9267
+ }) => {
9268
+ const prevValue = currentValue == null ? valueRef.current : currentValue;
9269
+ const nextValue = typeof prevValue === 'number' ? prevValue + amount * direction : Math.max(0, min ?? 0);
9270
+ const nativeEvent = event;
9271
+ return setValue(nextValue, createChangeEventDetails(reason, nativeEvent, undefined, {
9272
+ direction
9273
+ }));
9274
+ });
9275
+ const stopAutoChange = useStableCallback(() => {
9276
+ intentionalTouchCheckTimeout.clear();
9277
+ startTickTimeout.clear();
9278
+ tickInterval.clear();
9279
+ unsubscribeFromGlobalContextMenuRef.current();
9280
+ movesAfterTouchRef.current = 0;
9281
+ });
9282
+ const startAutoChange = useStableCallback((isIncrement, triggerEvent) => {
9283
+ stopAutoChange();
9284
+ if (!inputRef.current) {
9285
+ return;
9286
+ }
9287
+ const win = getWindow(inputRef.current);
9288
+ function handleContextMenu(event) {
9289
+ event.preventDefault();
9290
+ }
9291
+
9292
+ // A global context menu is necessary to prevent the context menu from appearing when the touch
9293
+ // is slightly outside of the element's hit area.
9294
+ win.addEventListener('contextmenu', handleContextMenu);
9295
+ unsubscribeFromGlobalContextMenuRef.current = () => {
9296
+ win.removeEventListener('contextmenu', handleContextMenu);
9297
+ };
9298
+ win.addEventListener('pointerup', event => {
9299
+ isPressedRef.current = false;
9300
+ stopAutoChange();
9301
+ const committed = lastChangedValueRef.current ?? valueRef.current;
9302
+ const commitReason = isIncrement ? incrementPress : decrementPress;
9303
+ onValueCommitted(committed, createGenericEventDetails(commitReason, event));
9304
+ }, {
9305
+ once: true
9306
+ });
9307
+ function tick() {
9308
+ const amount = getStepAmount(triggerEvent) ?? DEFAULT_STEP;
9309
+ return incrementValue(amount, {
9310
+ direction: isIncrement ? 1 : -1,
9311
+ event: triggerEvent,
9312
+ reason: isIncrement ? 'increment-press' : 'decrement-press'
9313
+ });
9314
+ }
9315
+ if (!tick()) {
9316
+ stopAutoChange();
9317
+ return;
9318
+ }
9319
+ startTickTimeout.start(START_AUTO_CHANGE_DELAY, () => {
9320
+ tickInterval.start(CHANGE_VALUE_TICK_DELAY, () => {
9321
+ if (!tick()) {
9322
+ stopAutoChange();
9323
+ }
9324
+ });
9325
+ });
9326
+ });
9327
+
9328
+ // We need to update the input value when the external `value` prop changes. This ends up acting
9329
+ // as a single source of truth to update the input value, bypassing the need to manually set it in
9330
+ // each event handler internally in this hook.
9331
+ // This is done inside a layout effect as an alternative to the technique to set state during
9332
+ // render as we're accessing a ref, which must be inside an effect.
9333
+ // https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
9334
+ //
9335
+ // ESLint is disabled because it needs to run even if the parsed value hasn't changed, since the
9336
+ // value still can be formatted differently.
9337
+ // eslint-disable-next-line react-hooks/exhaustive-deps
9338
+ useIsoLayoutEffect(function syncFormattedInputValueOnValueChange() {
9339
+ // This ensures the value is only updated on blur rather than every keystroke, but still
9340
+ // allows the input value to be updated when the value is changed externally.
9341
+ if (!allowInputSyncRef.current) {
9342
+ return;
9343
+ }
9344
+ const nextInputValue = valueProp !== undefined ? getControlledInputValue(value, locale, format) : formatNumber(value, locale, format);
9345
+ if (nextInputValue !== inputValue) {
9346
+ setInputValue(nextInputValue);
9347
+ }
9348
+ });
9349
+ useIsoLayoutEffect(function setDynamicInputModeForIOS() {
9350
+ if (!isIOS) {
9351
+ return;
9352
+ }
9353
+
9354
+ // iOS numeric software keyboard doesn't have a minus key, so we need to use the default
9355
+ // keyboard to let the user input a negative number.
9356
+ let computedInputMode = 'text';
9357
+ if (minWithDefault >= 0) {
9358
+ // iOS numeric software keyboard doesn't have a decimal key for "numeric" input mode, but
9359
+ // this is better than the "text" input if possible to use.
9360
+ computedInputMode = 'decimal';
9361
+ }
9362
+ setInputMode(computedInputMode);
9363
+ }, [minWithDefault, formatStyle]);
9364
+ React.useEffect(() => {
9365
+ return () => stopAutoChange();
9366
+ }, [stopAutoChange]);
9367
+
9368
+ // The `onWheel` prop can't be prevented, so we need to use a global event listener.
9369
+ React.useEffect(function registerElementWheelListener() {
9370
+ const element = inputRef.current;
9371
+ if (disabled || readOnly || !allowWheelScrub || !element) {
9372
+ return undefined;
9373
+ }
9374
+ function handleWheel(event) {
9375
+ if (
9376
+ // Allow pinch-zooming.
9377
+ event.ctrlKey || ownerDocument(inputRef.current).activeElement !== inputRef.current) {
9378
+ return;
9379
+ }
9380
+
9381
+ // Prevent the default behavior to avoid scrolling the page.
9382
+ event.preventDefault();
9383
+ const amount = getStepAmount(event) ?? DEFAULT_STEP;
9384
+ incrementValue(amount, {
9385
+ direction: event.deltaY > 0 ? -1 : 1,
9386
+ event,
9387
+ reason: 'wheel'
9388
+ });
9389
+ }
9390
+ element.addEventListener('wheel', handleWheel);
9391
+ return () => {
9392
+ element.removeEventListener('wheel', handleWheel);
9393
+ };
9394
+ }, [allowWheelScrub, incrementValue, disabled, readOnly, largeStep, step, getStepAmount]);
9395
+ const state = React.useMemo(() => ({
9396
+ ...fieldState,
9397
+ disabled,
9398
+ readOnly,
9399
+ required,
9400
+ value,
9401
+ inputValue,
9402
+ scrubbing: isScrubbing
9403
+ }), [fieldState, disabled, readOnly, required, value, inputValue, isScrubbing]);
9404
+ const contextValue = React.useMemo(() => ({
9405
+ inputRef,
9406
+ inputValue,
9407
+ value,
9408
+ startAutoChange,
9409
+ stopAutoChange,
9410
+ minWithDefault,
9411
+ maxWithDefault,
9412
+ disabled,
9413
+ readOnly,
9414
+ id,
9415
+ setValue,
9416
+ incrementValue,
9417
+ getStepAmount,
9418
+ allowInputSyncRef,
9419
+ formatOptionsRef,
9420
+ valueRef,
9421
+ lastChangedValueRef,
9422
+ hasPendingCommitRef,
9423
+ isPressedRef,
9424
+ intentionalTouchCheckTimeout,
9425
+ movesAfterTouchRef,
9426
+ name,
9427
+ required,
9428
+ invalid,
9429
+ inputMode,
9430
+ getAllowedNonNumericKeys,
9431
+ min,
9432
+ max,
9433
+ setInputValue,
9434
+ locale,
9435
+ isScrubbing,
9436
+ setIsScrubbing,
9437
+ state,
9438
+ onValueCommitted
9439
+ }), [inputRef, inputValue, value, startAutoChange, stopAutoChange, minWithDefault, maxWithDefault, disabled, readOnly, id, setValue, incrementValue, getStepAmount, formatOptionsRef, valueRef, intentionalTouchCheckTimeout, name, required, invalid, inputMode, getAllowedNonNumericKeys, min, max, setInputValue, locale, isScrubbing, state, onValueCommitted]);
9440
+ const element = useRenderElement('div', componentProps, {
9441
+ ref: forwardedRef,
9442
+ state,
9443
+ props: elementProps,
9444
+ stateAttributesMapping: stateAttributesMapping$1
9445
+ });
9446
+ return /*#__PURE__*/jsxs(NumberFieldRootContext.Provider, {
9447
+ value: contextValue,
9448
+ children: [element, /*#__PURE__*/jsx("input", {
9449
+ ...validation.getInputValidationProps({
9450
+ onFocus() {
9451
+ inputRef.current?.focus();
9452
+ },
9453
+ onChange(event) {
9454
+ // Workaround for https://github.com/facebook/react/issues/9023
9455
+ if (event.nativeEvent.defaultPrevented) {
9456
+ return;
9457
+ }
9458
+
9459
+ // Handle browser autofill.
9460
+ const nextValue = event.currentTarget.valueAsNumber;
9461
+ const parsedValue = Number.isNaN(nextValue) ? null : nextValue;
9462
+ const details = createChangeEventDetails(none, event.nativeEvent);
9463
+ setDirty(parsedValue !== validityData.initialValue);
9464
+ setValue(parsedValue, details);
9465
+ if (shouldValidateOnChange()) {
9466
+ validation.commit(parsedValue);
9467
+ }
9468
+ }
9469
+ }),
9470
+ ref: hiddenInputRef,
9471
+ type: "number",
9472
+ name: name,
9473
+ value: value ?? '',
9474
+ min: min,
9475
+ max: max
9476
+ // stepMismatch validation is broken unless an explicit `min` is added.
9477
+ // See https://github.com/facebook/react/issues/12334.
9478
+ ,
9479
+ step: stepProp,
9480
+ disabled: disabled,
9481
+ required: required,
9482
+ "aria-hidden": true,
9483
+ tabIndex: -1,
9484
+ style: name ? visuallyHiddenInput : visuallyHidden
9485
+ })]
9486
+ });
9487
+ });
9488
+ if (process.env.NODE_ENV !== "production") NumberFieldRoot.displayName = "NumberFieldRoot";
9489
+ function getControlledInputValue(value, locale, format) {
9490
+ const explicitPrecision = format?.maximumFractionDigits != null || format?.minimumFractionDigits != null;
9491
+ return explicitPrecision ? formatNumber(value, locale, format) : formatNumberMaxPrecision(value, locale, format);
9492
+ }
9493
+
9494
+ /**
9495
+ * Groups the input with the increment and decrement buttons.
9496
+ * Renders a `<div>` element.
9497
+ *
9498
+ * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
9499
+ */
9500
+ const NumberFieldGroup = /*#__PURE__*/React.forwardRef(function NumberFieldGroup(componentProps, forwardedRef) {
9501
+ const {
9502
+ render,
9503
+ className,
9504
+ ...elementProps
9505
+ } = componentProps;
9506
+ const {
9507
+ state
9508
+ } = useNumberFieldRootContext();
9509
+ const element = useRenderElement('div', componentProps, {
9510
+ ref: forwardedRef,
9511
+ state,
9512
+ props: [{
9513
+ role: 'group'
9514
+ }, elementProps],
9515
+ stateAttributesMapping: stateAttributesMapping$1
9516
+ });
9517
+ return element;
9518
+ });
9519
+ if (process.env.NODE_ENV !== "production") NumberFieldGroup.displayName = "NumberFieldGroup";
9520
+
9521
+ // Treat pen as touch-like to avoid forcing the software keyboard on stylus taps.
9522
+ // Linux Chrome may emit "pen" historically for mouse usage due to a bug, but the touch path
9523
+ // still works with minor behavioral differences.
9524
+ function isTouchLikePointerType(pointerType) {
9525
+ return pointerType === 'touch' || pointerType === 'pen';
9526
+ }
9527
+ function useNumberFieldButton(params) {
9528
+ const {
9529
+ allowInputSyncRef,
9530
+ disabled,
9531
+ formatOptionsRef,
9532
+ getStepAmount,
9533
+ id,
9534
+ incrementValue,
9535
+ inputRef,
9536
+ inputValue,
9537
+ intentionalTouchCheckTimeout,
9538
+ isIncrement,
9539
+ isPressedRef,
9540
+ locale,
9541
+ movesAfterTouchRef,
9542
+ readOnly,
9543
+ setValue,
9544
+ startAutoChange,
9545
+ stopAutoChange,
9546
+ valueRef,
9547
+ lastChangedValueRef,
9548
+ onValueCommitted
9549
+ } = params;
9550
+ const incrementDownCoordsRef = React.useRef({
9551
+ x: 0,
9552
+ y: 0
9553
+ });
9554
+ const isTouchingButtonRef = React.useRef(false);
9555
+ const ignoreClickRef = React.useRef(false);
9556
+ const pointerTypeRef = React.useRef('');
9557
+ const pressReason = isIncrement ? 'increment-press' : 'decrement-press';
9558
+ function commitValue(nativeEvent) {
9559
+ allowInputSyncRef.current = true;
9560
+
9561
+ // The input may be dirty but not yet blurred, so the value won't have been committed.
9562
+ const parsedValue = parseNumber(inputValue, locale, formatOptionsRef.current);
9563
+ if (parsedValue !== null) {
9564
+ // The increment value function needs to know the current input value to increment it
9565
+ // correctly.
9566
+ valueRef.current = parsedValue;
9567
+ setValue(parsedValue, createChangeEventDetails(pressReason, nativeEvent, undefined, {
9568
+ direction: isIncrement ? 1 : -1
9569
+ }));
9570
+ }
9571
+ }
9572
+ const props = {
9573
+ disabled,
9574
+ 'aria-readonly': readOnly || undefined,
9575
+ 'aria-label': isIncrement ? 'Increase' : 'Decrease',
9576
+ 'aria-controls': id,
9577
+ // Keyboard users shouldn't have access to the buttons, since they can use the input element
9578
+ // to change the value. On the other hand, `aria-hidden` is not applied because touch screen
9579
+ // readers should be able to use the buttons.
9580
+ tabIndex: -1,
9581
+ style: {
9582
+ WebkitUserSelect: 'none',
9583
+ userSelect: 'none'
9584
+ },
9585
+ onTouchStart() {
9586
+ isTouchingButtonRef.current = true;
9587
+ },
9588
+ onTouchEnd() {
9589
+ isTouchingButtonRef.current = false;
9590
+ },
9591
+ onClick(event) {
9592
+ const isDisabled = disabled || readOnly;
9593
+ if (event.defaultPrevented || isDisabled || (
9594
+ // If it's not a keyboard/virtual click, ignore.
9595
+ isTouchLikePointerType(pointerTypeRef.current) ? ignoreClickRef.current : event.detail !== 0)) {
9596
+ return;
9597
+ }
9598
+ commitValue(event.nativeEvent);
9599
+ const amount = getStepAmount(event) ?? DEFAULT_STEP;
9600
+ const prev = valueRef.current;
9601
+ incrementValue(amount, {
9602
+ direction: isIncrement ? 1 : -1,
9603
+ event: event.nativeEvent,
9604
+ reason: pressReason
9605
+ });
9606
+ const committed = lastChangedValueRef.current ?? valueRef.current;
9607
+ if (committed !== prev) {
9608
+ onValueCommitted(committed, createGenericEventDetails(pressReason, event.nativeEvent));
9609
+ }
9610
+ },
9611
+ onPointerDown(event) {
9612
+ const isMainButton = !event.button || event.button === 0;
9613
+ if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
9614
+ return;
9615
+ }
9616
+ pointerTypeRef.current = event.pointerType;
9617
+ ignoreClickRef.current = false;
9618
+ isPressedRef.current = true;
9619
+ incrementDownCoordsRef.current = {
9620
+ x: event.clientX,
9621
+ y: event.clientY
9622
+ };
9623
+ commitValue(event.nativeEvent);
9624
+ const isTouchPointer = isTouchLikePointerType(event.pointerType);
9625
+ if (!isTouchPointer) {
9626
+ event.preventDefault();
9627
+ inputRef.current?.focus();
9628
+ startAutoChange(isIncrement, event);
9629
+ } else {
9630
+ // We need to check if the pointerdown was intentional, and not the result of a scroll
9631
+ // or pinch-zoom. In that case, we don't want to change the value.
9632
+ intentionalTouchCheckTimeout.start(TOUCH_TIMEOUT, () => {
9633
+ const moves = movesAfterTouchRef.current;
9634
+ movesAfterTouchRef.current = 0;
9635
+ // Only start auto-change if the touch is still pressed (prevents races
9636
+ // with pointerup occurring before the timeout fires on quick taps).
9637
+ const stillPressed = isPressedRef.current;
9638
+ if (stillPressed && moves != null && moves < MAX_POINTER_MOVES_AFTER_TOUCH) {
9639
+ startAutoChange(isIncrement, event);
9640
+ ignoreClickRef.current = true; // synthesized click should be ignored
9641
+ } else {
9642
+ // No auto-change (simple tap or scroll gesture), allow the click handler
9643
+ // to perform a single increment and commit.
9644
+ ignoreClickRef.current = false;
9645
+ stopAutoChange();
9646
+ }
9647
+ });
9648
+ }
9649
+ },
9650
+ onPointerUp(event) {
9651
+ // Ensure we mark the press as released for touch flows even if auto-change never started,
9652
+ // so the delayed auto-change check won’t start after a quick tap.
9653
+ if (isTouchLikePointerType(event.pointerType)) {
9654
+ isPressedRef.current = false;
9655
+ }
9656
+ },
9657
+ onPointerMove(event) {
9658
+ const isDisabled = disabled || readOnly;
9659
+ if (isDisabled || !isTouchLikePointerType(event.pointerType) || !isPressedRef.current) {
9660
+ return;
9661
+ }
9662
+ if (movesAfterTouchRef.current != null) {
9663
+ movesAfterTouchRef.current += 1;
9664
+ }
9665
+ const {
9666
+ x,
9667
+ y
9668
+ } = incrementDownCoordsRef.current;
9669
+ const dx = x - event.clientX;
9670
+ const dy = y - event.clientY;
9671
+
9672
+ // An alternative to this technique is to detect when the NumberField's parent container
9673
+ // has been scrolled
9674
+ if (dx ** 2 + dy ** 2 > SCROLLING_POINTER_MOVE_DISTANCE ** 2) {
9675
+ stopAutoChange();
9676
+ }
9677
+ },
9678
+ onMouseEnter(event) {
9679
+ const isDisabled = disabled || readOnly;
9680
+ if (event.defaultPrevented || isDisabled || !isPressedRef.current || isTouchingButtonRef.current || isTouchLikePointerType(pointerTypeRef.current)) {
9681
+ return;
9682
+ }
9683
+ startAutoChange(isIncrement, event);
9684
+ },
9685
+ onMouseLeave() {
9686
+ if (isTouchingButtonRef.current) {
9687
+ return;
9688
+ }
9689
+ stopAutoChange();
9690
+ },
9691
+ onMouseUp() {
9692
+ if (isTouchingButtonRef.current) {
9693
+ return;
9694
+ }
9695
+ stopAutoChange();
9696
+ }
9697
+ };
9698
+ return props;
9699
+ }
9700
+
9701
+ /**
9702
+ * A stepper button that increases the field value when clicked.
9703
+ * Renders an `<button>` element.
9704
+ *
9705
+ * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
9706
+ */
9707
+ const NumberFieldIncrement = /*#__PURE__*/React.forwardRef(function NumberFieldIncrement(componentProps, forwardedRef) {
9708
+ const {
9709
+ render,
9710
+ className,
9711
+ disabled: disabledProp = false,
9712
+ nativeButton = true,
9713
+ ...elementProps
9714
+ } = componentProps;
9715
+ const {
9716
+ allowInputSyncRef,
9717
+ disabled: contextDisabled,
9718
+ formatOptionsRef,
9719
+ getStepAmount,
9720
+ id,
9721
+ incrementValue,
9722
+ inputRef,
9723
+ inputValue,
9724
+ intentionalTouchCheckTimeout,
9725
+ isPressedRef,
9726
+ locale,
9727
+ maxWithDefault,
9728
+ movesAfterTouchRef,
9729
+ readOnly,
9730
+ setValue,
9731
+ startAutoChange,
9732
+ state,
9733
+ stopAutoChange,
9734
+ value,
9735
+ valueRef,
9736
+ lastChangedValueRef,
9737
+ onValueCommitted
9738
+ } = useNumberFieldRootContext();
9739
+ const isMax = value != null && value >= maxWithDefault;
9740
+ const disabled = disabledProp || contextDisabled || isMax;
9741
+ const props = useNumberFieldButton({
9742
+ isIncrement: true,
9743
+ inputRef,
9744
+ startAutoChange,
9745
+ stopAutoChange,
9746
+ inputValue,
9747
+ disabled,
9748
+ readOnly,
9749
+ id,
9750
+ setValue,
9751
+ getStepAmount,
9752
+ incrementValue,
9753
+ allowInputSyncRef,
9754
+ formatOptionsRef,
9755
+ valueRef,
9756
+ isPressedRef,
9757
+ intentionalTouchCheckTimeout,
9758
+ movesAfterTouchRef,
9759
+ locale,
9760
+ lastChangedValueRef,
9761
+ onValueCommitted
9762
+ });
9763
+ const {
9764
+ getButtonProps,
9765
+ buttonRef
9766
+ } = useButton({
9767
+ disabled,
9768
+ native: nativeButton,
9769
+ focusableWhenDisabled: true
9770
+ });
9771
+ const buttonState = React.useMemo(() => ({
9772
+ ...state,
9773
+ disabled
9774
+ }), [state, disabled]);
9775
+ const element = useRenderElement('button', componentProps, {
9776
+ ref: [forwardedRef, buttonRef],
9777
+ state: buttonState,
9778
+ props: [props, elementProps, getButtonProps],
9779
+ stateAttributesMapping: stateAttributesMapping$1
9780
+ });
9781
+ return element;
9782
+ });
9783
+ if (process.env.NODE_ENV !== "production") NumberFieldIncrement.displayName = "NumberFieldIncrement";
9784
+
9785
+ /**
9786
+ * A stepper button that decreases the field value when clicked.
9787
+ * Renders an `<button>` element.
9788
+ *
9789
+ * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
9790
+ */
9791
+ const NumberFieldDecrement = /*#__PURE__*/React.forwardRef(function NumberFieldDecrement(componentProps, forwardedRef) {
9792
+ const {
9793
+ render,
9794
+ className,
9795
+ disabled: disabledProp = false,
9796
+ nativeButton = true,
9797
+ ...elementProps
9798
+ } = componentProps;
9799
+ const {
9800
+ allowInputSyncRef,
9801
+ disabled: contextDisabled,
9802
+ formatOptionsRef,
9803
+ getStepAmount,
9804
+ id,
9805
+ incrementValue,
9806
+ inputRef,
9807
+ inputValue,
9808
+ intentionalTouchCheckTimeout,
9809
+ isPressedRef,
9810
+ minWithDefault,
9811
+ movesAfterTouchRef,
9812
+ readOnly,
9813
+ setValue,
9814
+ startAutoChange,
9815
+ state,
9816
+ stopAutoChange,
9817
+ value,
9818
+ valueRef,
9819
+ locale,
9820
+ lastChangedValueRef,
9821
+ onValueCommitted
9822
+ } = useNumberFieldRootContext();
9823
+ const isMin = value != null && value <= minWithDefault;
9824
+ const disabled = disabledProp || contextDisabled || isMin;
9825
+ const props = useNumberFieldButton({
9826
+ isIncrement: false,
9827
+ inputRef,
9828
+ startAutoChange,
9829
+ stopAutoChange,
9830
+ inputValue,
9831
+ disabled,
9832
+ readOnly,
9833
+ id,
9834
+ setValue,
9835
+ getStepAmount,
9836
+ incrementValue,
9837
+ allowInputSyncRef,
9838
+ formatOptionsRef,
9839
+ valueRef,
9840
+ isPressedRef,
9841
+ intentionalTouchCheckTimeout,
9842
+ movesAfterTouchRef,
9843
+ locale,
9844
+ lastChangedValueRef,
9845
+ onValueCommitted
9846
+ });
9847
+ const {
9848
+ getButtonProps,
9849
+ buttonRef
9850
+ } = useButton({
9851
+ disabled,
9852
+ native: nativeButton,
9853
+ focusableWhenDisabled: true
9854
+ });
9855
+ const buttonState = React.useMemo(() => ({
9856
+ ...state,
9857
+ disabled
9858
+ }), [state, disabled]);
9859
+ const element = useRenderElement('button', componentProps, {
9860
+ ref: [forwardedRef, buttonRef],
9861
+ state: buttonState,
9862
+ props: [props, elementProps, getButtonProps],
9863
+ stateAttributesMapping: stateAttributesMapping$1
9864
+ });
9865
+ return element;
9866
+ });
9867
+ if (process.env.NODE_ENV !== "production") NumberFieldDecrement.displayName = "NumberFieldDecrement";
9868
+
9869
+ const stateAttributesMapping = {
9870
+ ...fieldValidityMapping,
9871
+ ...stateAttributesMapping$1
9872
+ };
9873
+ const NAVIGATE_KEYS = new Set(['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter', 'Escape']);
9874
+
9875
+ /**
9876
+ * The native input control in the number field.
9877
+ * Renders an `<input>` element.
9878
+ *
9879
+ * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
9880
+ */
9881
+ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput(componentProps, forwardedRef) {
9882
+ const {
9883
+ render,
9884
+ className,
9885
+ ...elementProps
9886
+ } = componentProps;
9887
+ const {
9888
+ allowInputSyncRef,
9889
+ disabled,
9890
+ formatOptionsRef,
9891
+ getAllowedNonNumericKeys,
9892
+ getStepAmount,
9893
+ id,
9894
+ incrementValue,
9895
+ inputMode,
9896
+ inputValue,
9897
+ max,
9898
+ min,
9899
+ name,
9900
+ readOnly,
9901
+ required,
9902
+ setValue,
9903
+ state,
9904
+ setInputValue,
9905
+ locale,
9906
+ inputRef,
9907
+ value,
9908
+ onValueCommitted,
9909
+ lastChangedValueRef,
9910
+ hasPendingCommitRef,
9911
+ valueRef
9912
+ } = useNumberFieldRootContext();
9913
+ const {
9914
+ clearErrors
9915
+ } = useFormContext();
9916
+ const {
9917
+ validationMode,
9918
+ setTouched,
9919
+ setFocused,
9920
+ invalid,
9921
+ shouldValidateOnChange,
9922
+ validation
9923
+ } = useFieldRootContext();
9924
+ const {
9925
+ labelId
9926
+ } = useLabelableContext();
9927
+ const hasTouchedInputRef = React.useRef(false);
9928
+ const blockRevalidationRef = React.useRef(false);
9929
+ useField({
9930
+ id,
9931
+ commit: validation.commit,
9932
+ value,
9933
+ controlRef: inputRef,
9934
+ name,
9935
+ getValue: () => value ?? null
9936
+ });
9937
+ useValueChanged(value, previousValue => {
9938
+ const validateOnChange = shouldValidateOnChange();
9939
+ clearErrors(name);
9940
+ if (validateOnChange) {
9941
+ validation.commit(value);
9942
+ }
9943
+ if (previousValue === value || validateOnChange) {
9944
+ return;
9945
+ }
9946
+ if (blockRevalidationRef.current) {
9947
+ blockRevalidationRef.current = false;
9948
+ return;
9949
+ }
9950
+ validation.commit(value, true);
9951
+ });
9952
+ const inputProps = {
9953
+ id,
9954
+ required,
9955
+ disabled,
9956
+ readOnly,
9957
+ inputMode,
9958
+ value: inputValue,
9959
+ type: 'text',
9960
+ autoComplete: 'off',
9961
+ autoCorrect: 'off',
9962
+ spellCheck: 'false',
9963
+ 'aria-roledescription': 'Number field',
9964
+ 'aria-invalid': invalid || undefined,
9965
+ 'aria-labelledby': labelId,
9966
+ // If the server's locale does not match the client's locale, the formatting may not match,
9967
+ // causing a hydration mismatch.
9968
+ suppressHydrationWarning: true,
9969
+ onFocus(event) {
9970
+ if (event.defaultPrevented || readOnly || disabled) {
9971
+ return;
9972
+ }
9973
+ setFocused(true);
9974
+ if (hasTouchedInputRef.current) {
9975
+ return;
9976
+ }
9977
+ hasTouchedInputRef.current = true;
9978
+
9979
+ // Browsers set selection at the start of the input field by default. We want to set it at
9980
+ // the end for the first focus.
9981
+ const target = event.currentTarget;
9982
+ const length = target.value.length;
9983
+ target.setSelectionRange(length, length);
9984
+ },
9985
+ onBlur(event) {
9986
+ if (event.defaultPrevented || readOnly || disabled) {
9987
+ return;
9988
+ }
9989
+ setTouched(true);
9990
+ setFocused(false);
9991
+ const hadManualInput = !allowInputSyncRef.current;
9992
+ const hadPendingProgrammaticChange = hasPendingCommitRef.current;
9993
+ allowInputSyncRef.current = true;
9994
+ if (inputValue.trim() === '') {
9995
+ setValue(null, createChangeEventDetails(inputClear, event.nativeEvent));
9996
+ if (validationMode === 'onBlur') {
9997
+ validation.commit(null);
9998
+ }
9999
+ onValueCommitted(null, createGenericEventDetails(inputClear, event.nativeEvent));
10000
+ return;
10001
+ }
10002
+ const formatOptions = formatOptionsRef.current;
10003
+ const parsedValue = parseNumber(inputValue, locale, formatOptions);
10004
+ if (parsedValue === null) {
10005
+ return;
10006
+ }
10007
+
10008
+ // If an explicit precision is requested, round the committed numeric value.
10009
+ const hasExplicitPrecision = formatOptions?.maximumFractionDigits != null || formatOptions?.minimumFractionDigits != null;
10010
+ const maxFrac = formatOptions?.maximumFractionDigits;
10011
+ const committed = hasExplicitPrecision && typeof maxFrac === 'number' ? Number(parsedValue.toFixed(maxFrac)) : parsedValue;
10012
+ const nextEventDetails = createGenericEventDetails(inputBlur, event.nativeEvent);
10013
+ const shouldUpdateValue = value !== committed;
10014
+ const shouldCommit = hadManualInput || shouldUpdateValue || hadPendingProgrammaticChange;
10015
+ if (validationMode === 'onBlur') {
10016
+ validation.commit(committed);
10017
+ }
10018
+ if (shouldUpdateValue) {
10019
+ blockRevalidationRef.current = true;
10020
+ setValue(committed, createChangeEventDetails(inputBlur, event.nativeEvent));
10021
+ }
10022
+ if (shouldCommit) {
10023
+ onValueCommitted(committed, nextEventDetails);
10024
+ }
10025
+
10026
+ // Normalize only the displayed text
10027
+ const canonicalText = formatNumber(committed, locale, formatOptions);
10028
+ const maxPrecisionText = formatNumberMaxPrecision(parsedValue, locale, formatOptions);
10029
+ const shouldPreserveFullPrecision = !hasExplicitPrecision && parsedValue === value && inputValue === maxPrecisionText;
10030
+ if (!shouldPreserveFullPrecision && inputValue !== canonicalText) {
10031
+ setInputValue(canonicalText);
10032
+ }
10033
+ },
10034
+ onChange(event) {
10035
+ // Workaround for https://github.com/facebook/react/issues/9023
10036
+ if (event.nativeEvent.defaultPrevented) {
10037
+ return;
10038
+ }
10039
+ allowInputSyncRef.current = false;
10040
+ const targetValue = event.target.value;
10041
+ if (targetValue.trim() === '') {
10042
+ setInputValue(targetValue);
10043
+ setValue(null, createChangeEventDetails(inputClear, event.nativeEvent));
10044
+ return;
10045
+ }
10046
+
10047
+ // Update the input text immediately and only fire onValueChange if the typed value is
10048
+ // currently parseable into a number. This preserves good UX for IME
10049
+ // composition/partial input while still providing live numeric updates when possible.
10050
+ const allowedNonNumericKeys = getAllowedNonNumericKeys();
10051
+ const isValidCharacterString = Array.from(targetValue).every(ch => {
10052
+ const isAsciiDigit = ch >= '0' && ch <= '9';
10053
+ const isArabicNumeral = ARABIC_DETECT_RE.test(ch);
10054
+ const isHanNumeral = HAN_DETECT_RE.test(ch);
10055
+ const isPersianNumeral = PERSIAN_DETECT_RE.test(ch);
10056
+ const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(ch);
10057
+ const isMinus = ANY_MINUS_DETECT_RE.test(ch);
10058
+ return isAsciiDigit || isArabicNumeral || isHanNumeral || isPersianNumeral || isFullwidthNumeral || isMinus || allowedNonNumericKeys.has(ch);
10059
+ });
10060
+ if (!isValidCharacterString) {
10061
+ return;
10062
+ }
10063
+ const parsedValue = parseNumber(targetValue, locale, formatOptionsRef.current);
10064
+ setInputValue(targetValue);
10065
+ if (parsedValue !== null) {
10066
+ setValue(parsedValue, createChangeEventDetails(inputChange, event.nativeEvent));
10067
+ }
10068
+ },
10069
+ onKeyDown(event) {
10070
+ if (event.defaultPrevented || readOnly || disabled) {
10071
+ return;
10072
+ }
10073
+ const nativeEvent = event.nativeEvent;
10074
+ allowInputSyncRef.current = true;
10075
+ const allowedNonNumericKeys = getAllowedNonNumericKeys();
10076
+ let isAllowedNonNumericKey = allowedNonNumericKeys.has(event.key);
10077
+ const {
10078
+ decimal,
10079
+ currency,
10080
+ percentSign
10081
+ } = getNumberLocaleDetails(locale, formatOptionsRef.current);
10082
+ const selectionStart = event.currentTarget.selectionStart;
10083
+ const selectionEnd = event.currentTarget.selectionEnd;
10084
+ const isAllSelected = selectionStart === 0 && selectionEnd === inputValue.length;
10085
+
10086
+ // Normalize handling of plus/minus signs via precomputed regexes
10087
+ const selectionContainsIndex = index => selectionStart != null && selectionEnd != null && index >= selectionStart && index < selectionEnd;
10088
+ if (ANY_MINUS_DETECT_RE.test(event.key) && Array.from(allowedNonNumericKeys).some(k => ANY_MINUS_DETECT_RE.test(k || ''))) {
10089
+ // Only allow one sign unless replacing the existing one or all text is selected
10090
+ const existingIndex = inputValue.search(ANY_MINUS_RE);
10091
+ const isReplacingExisting = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);
10092
+ isAllowedNonNumericKey = !(ANY_MINUS_DETECT_RE.test(inputValue) || ANY_PLUS_DETECT_RE.test(inputValue)) || isAllSelected || isReplacingExisting;
10093
+ }
10094
+ if (ANY_PLUS_DETECT_RE.test(event.key) && Array.from(allowedNonNumericKeys).some(k => ANY_PLUS_DETECT_RE.test(k || ''))) {
10095
+ const existingIndex = inputValue.search(ANY_PLUS_RE);
10096
+ const isReplacingExisting = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);
10097
+ isAllowedNonNumericKey = !(ANY_MINUS_DETECT_RE.test(inputValue) || ANY_PLUS_DETECT_RE.test(inputValue)) || isAllSelected || isReplacingExisting;
10098
+ }
10099
+
10100
+ // Only allow one of each symbol.
10101
+ [decimal, currency, percentSign].forEach(symbol => {
10102
+ if (event.key === symbol) {
10103
+ const symbolIndex = inputValue.indexOf(symbol);
10104
+ const isSymbolHighlighted = selectionContainsIndex(symbolIndex);
10105
+ isAllowedNonNumericKey = !inputValue.includes(symbol) || isAllSelected || isSymbolHighlighted;
10106
+ }
10107
+ });
10108
+ const isAsciiDigit = event.key >= '0' && event.key <= '9';
10109
+ const isArabicNumeral = ARABIC_DETECT_RE.test(event.key);
10110
+ const isHanNumeral = HAN_DETECT_RE.test(event.key);
10111
+ const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(event.key);
10112
+ const isNavigateKey = NAVIGATE_KEYS.has(event.key);
10113
+ if (
10114
+ // Allow composition events (e.g., pinyin)
10115
+ // event.nativeEvent.isComposing does not work in Safari:
10116
+ // https://bugs.webkit.org/show_bug.cgi?id=165004
10117
+ event.which === 229 || event.altKey || event.ctrlKey || event.metaKey || isAllowedNonNumericKey || isAsciiDigit || isArabicNumeral || isFullwidthNumeral || isHanNumeral || isNavigateKey) {
10118
+ return;
10119
+ }
10120
+
10121
+ // We need to commit the number at this point if the input hasn't been blurred.
10122
+ const parsedValue = parseNumber(inputValue, locale, formatOptionsRef.current);
10123
+ const amount = getStepAmount(event) ?? DEFAULT_STEP;
10124
+
10125
+ // Prevent insertion of text or caret from moving.
10126
+ stopEvent(event);
10127
+ const commitDetails = createGenericEventDetails(keyboard, nativeEvent);
10128
+ if (event.key === 'ArrowUp') {
10129
+ incrementValue(amount, {
10130
+ direction: 1,
10131
+ currentValue: parsedValue,
10132
+ event: nativeEvent,
10133
+ reason: keyboard
10134
+ });
10135
+ onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
10136
+ } else if (event.key === 'ArrowDown') {
10137
+ incrementValue(amount, {
10138
+ direction: -1,
10139
+ currentValue: parsedValue,
10140
+ event: nativeEvent,
10141
+ reason: keyboard
10142
+ });
10143
+ onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
10144
+ } else if (event.key === 'Home' && min != null) {
10145
+ setValue(min, createChangeEventDetails(keyboard, nativeEvent));
10146
+ onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
10147
+ } else if (event.key === 'End' && max != null) {
10148
+ setValue(max, createChangeEventDetails(keyboard, nativeEvent));
10149
+ onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
10150
+ }
10151
+ },
10152
+ onPaste(event) {
10153
+ if (event.defaultPrevented || readOnly || disabled) {
10154
+ return;
10155
+ }
10156
+
10157
+ // Prevent `onChange` from being called.
10158
+ event.preventDefault();
10159
+ const clipboardData = event.clipboardData || window.Clipboard;
10160
+ const pastedData = clipboardData.getData('text/plain');
10161
+ const parsedValue = parseNumber(pastedData, locale, formatOptionsRef.current);
10162
+ if (parsedValue !== null) {
10163
+ allowInputSyncRef.current = false;
10164
+ setValue(parsedValue, createChangeEventDetails(inputPaste, event.nativeEvent));
10165
+ setInputValue(pastedData);
10166
+ }
10167
+ }
10168
+ };
10169
+ const element = useRenderElement('input', componentProps, {
10170
+ ref: [forwardedRef, inputRef],
10171
+ state,
10172
+ props: [inputProps, validation.getValidationProps(), elementProps],
10173
+ stateAttributesMapping
10174
+ });
10175
+ return element;
10176
+ });
10177
+ if (process.env.NODE_ENV !== "production") NumberFieldInput.displayName = "NumberFieldInput";
10178
+
10179
+ const NumberFieldScrubAreaContext = /*#__PURE__*/React.createContext(undefined);
10180
+ if (process.env.NODE_ENV !== "production") NumberFieldScrubAreaContext.displayName = "NumberFieldScrubAreaContext";
10181
+ function useNumberFieldScrubAreaContext() {
10182
+ const context = React.useContext(NumberFieldScrubAreaContext);
10183
+ if (context === undefined) {
10184
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: NumberFieldScrubAreaContext is missing. NumberFieldScrubArea parts must be placed within <NumberField.ScrubArea>.' : formatErrorMessage(44));
10185
+ }
10186
+ return context;
10187
+ }
10188
+
10189
+ // Calculates the viewport rect for the virtual cursor.
10190
+ function getViewportRect(teleportDistance, scrubAreaEl) {
10191
+ const win = getWindow(scrubAreaEl);
10192
+ const rect = scrubAreaEl.getBoundingClientRect();
10193
+ if (rect && teleportDistance != null) {
10194
+ return {
10195
+ x: rect.left - teleportDistance / 2,
10196
+ y: rect.top - teleportDistance / 2,
10197
+ width: rect.right + teleportDistance / 2,
10198
+ height: rect.bottom + teleportDistance / 2
10199
+ };
10200
+ }
10201
+ const vV = win.visualViewport;
10202
+ if (vV) {
10203
+ return {
10204
+ x: vV.offsetLeft,
10205
+ y: vV.offsetTop,
10206
+ width: vV.offsetLeft + vV.width,
10207
+ height: vV.offsetTop + vV.height
10208
+ };
10209
+ }
10210
+ return {
10211
+ x: 0,
10212
+ y: 0,
10213
+ width: win.document.documentElement.clientWidth,
10214
+ height: win.document.documentElement.clientHeight
10215
+ };
10216
+ }
10217
+
10218
+ // This lets us invert the scale of the cursor to match the OS scale, in which the cursor doesn't
10219
+ // scale with the content on pinch-zoom.
10220
+ function subscribeToVisualViewportResize(element, visualScaleRef) {
10221
+ const vV = getWindow(element).visualViewport;
10222
+ if (!vV) {
10223
+ return () => {};
10224
+ }
10225
+ function handleVisualResize() {
10226
+ if (vV) {
10227
+ visualScaleRef.current = vV.scale;
10228
+ }
10229
+ }
10230
+ handleVisualResize();
10231
+ vV.addEventListener('resize', handleVisualResize);
10232
+ return () => {
10233
+ vV.removeEventListener('resize', handleVisualResize);
10234
+ };
10235
+ }
10236
+
10237
+ const NumberFieldScrubArea = /*#__PURE__*/React.forwardRef(function NumberFieldScrubArea(componentProps, forwardedRef) {
10238
+ const {
10239
+ render,
10240
+ className,
10241
+ direction = 'horizontal',
10242
+ pixelSensitivity = 2,
10243
+ teleportDistance,
10244
+ ...elementProps
10245
+ } = componentProps;
10246
+ const {
10247
+ state,
10248
+ setIsScrubbing: setRootScrubbing,
10249
+ disabled,
10250
+ readOnly,
10251
+ inputRef,
10252
+ incrementValue,
10253
+ getStepAmount,
10254
+ onValueCommitted,
10255
+ lastChangedValueRef,
10256
+ valueRef
10257
+ } = useNumberFieldRootContext();
10258
+ const scrubAreaRef = React.useRef(null);
10259
+ const isScrubbingRef = React.useRef(false);
10260
+ const didMoveRef = React.useRef(false);
10261
+ const pointerDownTargetRef = React.useRef(null);
10262
+ const scrubAreaCursorRef = React.useRef(null);
10263
+ const virtualCursorCoords = React.useRef({
10264
+ x: 0,
10265
+ y: 0
10266
+ });
10267
+ const visualScaleRef = React.useRef(1);
10268
+ const exitPointerLockTimeout = useTimeout();
10269
+ const [isTouchInput, setIsTouchInput] = React.useState(false);
10270
+ const [isPointerLockDenied, setIsPointerLockDenied] = React.useState(false);
10271
+ const [isScrubbing, setIsScrubbing] = React.useState(false);
10272
+ React.useEffect(() => {
10273
+ if (!isScrubbing || !scrubAreaCursorRef.current) {
10274
+ return undefined;
10275
+ }
10276
+ return subscribeToVisualViewportResize(scrubAreaCursorRef.current, visualScaleRef);
10277
+ }, [isScrubbing]);
10278
+ function updateCursorTransform(x, y) {
10279
+ if (scrubAreaCursorRef.current) {
10280
+ scrubAreaCursorRef.current.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / visualScaleRef.current})`;
10281
+ }
10282
+ }
10283
+ const onScrub = useStableCallback(({
10284
+ movementX,
10285
+ movementY
10286
+ }) => {
10287
+ const virtualCursor = scrubAreaCursorRef.current;
10288
+ const scrubAreaEl = scrubAreaRef.current;
10289
+ if (!virtualCursor || !scrubAreaEl) {
10290
+ return;
10291
+ }
10292
+ const rect = getViewportRect(teleportDistance, scrubAreaEl);
10293
+ const coords = virtualCursorCoords.current;
10294
+ const newCoords = {
10295
+ x: Math.round(coords.x + movementX),
10296
+ y: Math.round(coords.y + movementY)
10297
+ };
10298
+ const cursorWidth = virtualCursor.offsetWidth;
10299
+ const cursorHeight = virtualCursor.offsetHeight;
10300
+ if (newCoords.x + cursorWidth / 2 < rect.x) {
10301
+ newCoords.x = rect.width - cursorWidth / 2;
10302
+ } else if (newCoords.x + cursorWidth / 2 > rect.width) {
10303
+ newCoords.x = rect.x - cursorWidth / 2;
10304
+ }
10305
+ if (newCoords.y + cursorHeight / 2 < rect.y) {
10306
+ newCoords.y = rect.height - cursorHeight / 2;
10307
+ } else if (newCoords.y + cursorHeight / 2 > rect.height) {
10308
+ newCoords.y = rect.y - cursorHeight / 2;
10309
+ }
10310
+ virtualCursorCoords.current = newCoords;
10311
+ updateCursorTransform(newCoords.x, newCoords.y);
10312
+ });
10313
+ const onScrubbingChange = useStableCallback((scrubbingValue, {
10314
+ clientX,
10315
+ clientY
10316
+ }) => {
10317
+ ReactDOM.flushSync(() => {
10318
+ setIsScrubbing(scrubbingValue);
10319
+ setRootScrubbing(scrubbingValue);
10320
+ });
10321
+ const virtualCursor = scrubAreaCursorRef.current;
10322
+ if (!virtualCursor || !scrubbingValue) {
10323
+ return;
10324
+ }
10325
+ const initialCoords = {
10326
+ x: clientX - virtualCursor.offsetWidth / 2,
10327
+ y: clientY - virtualCursor.offsetHeight / 2
10328
+ };
10329
+ virtualCursorCoords.current = initialCoords;
10330
+ updateCursorTransform(initialCoords.x, initialCoords.y);
10331
+ });
10332
+ React.useEffect(function registerGlobalScrubbingEventListeners() {
10333
+ // Only listen while actively scrubbing; avoids unrelated pointerup events committing.
10334
+ if (!inputRef.current || disabled || readOnly || !isScrubbing) {
10335
+ return undefined;
10336
+ }
10337
+ let cumulativeDelta = 0;
10338
+ function handleScrubPointerUp(event) {
10339
+ function handler() {
10340
+ try {
10341
+ ownerDocument(scrubAreaRef.current).exitPointerLock();
10342
+ } catch {
10343
+ // Ignore errors.
10344
+ } finally {
10345
+ isScrubbingRef.current = false;
10346
+ onScrubbingChange(false, event);
10347
+ onValueCommitted(lastChangedValueRef.current ?? valueRef.current, createGenericEventDetails(scrub, event));
10348
+
10349
+ // Manually dispatch a click event if no movement happened, since
10350
+ // preventDefault on pointerdown prevents the browser click event.
10351
+ if (!didMoveRef.current && pointerDownTargetRef.current != null) {
10352
+ pointerDownTargetRef.current.dispatchEvent(new MouseEvent('click', {
10353
+ bubbles: true,
10354
+ cancelable: true
10355
+ }));
10356
+ }
10357
+ didMoveRef.current = false;
10358
+ pointerDownTargetRef.current = null;
10359
+ }
10360
+ }
10361
+ if (isFirefox) {
10362
+ // Firefox needs a small delay here when soft-clicking as the pointer
10363
+ // lock will not release otherwise.
10364
+ exitPointerLockTimeout.start(20, handler);
10365
+ } else {
10366
+ handler();
10367
+ }
10368
+ }
10369
+ function handleScrubPointerMove(event) {
10370
+ if (!isScrubbingRef.current) {
10371
+ return;
10372
+ }
10373
+
10374
+ // Prevent text selection.
10375
+ event.preventDefault();
10376
+ onScrub(event);
10377
+ const {
10378
+ movementX,
10379
+ movementY
10380
+ } = event;
10381
+ cumulativeDelta += direction === 'vertical' ? movementY : movementX;
10382
+ if (Math.abs(cumulativeDelta) >= pixelSensitivity) {
10383
+ cumulativeDelta = 0;
10384
+ didMoveRef.current = true;
10385
+ const dValue = direction === 'vertical' ? -movementY : movementX;
10386
+ const stepAmount = getStepAmount(event) ?? DEFAULT_STEP;
10387
+ const rawAmount = dValue * stepAmount;
10388
+ if (rawAmount !== 0) {
10389
+ incrementValue(Math.abs(rawAmount), {
10390
+ direction: rawAmount >= 0 ? 1 : -1,
10391
+ event,
10392
+ reason: scrub
10393
+ });
10394
+ }
10395
+ }
10396
+ }
10397
+ const win = getWindow(inputRef.current);
10398
+ win.addEventListener('pointerup', handleScrubPointerUp, true);
10399
+ win.addEventListener('pointermove', handleScrubPointerMove, true);
10400
+ return () => {
10401
+ exitPointerLockTimeout.clear();
10402
+ win.removeEventListener('pointerup', handleScrubPointerUp, true);
10403
+ win.removeEventListener('pointermove', handleScrubPointerMove, true);
10404
+ };
10405
+ }, [disabled, readOnly, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
10406
+
10407
+ // Prevent scrolling using touch input when scrubbing.
10408
+ React.useEffect(function registerScrubberTouchPreventListener() {
10409
+ const element = scrubAreaRef.current;
10410
+ if (!element || disabled || readOnly) {
10411
+ return undefined;
10412
+ }
10413
+ function handleTouchStart(event) {
10414
+ if (event.touches.length === 1) {
10415
+ event.preventDefault();
10416
+ }
10417
+ }
10418
+ element.addEventListener('touchstart', handleTouchStart);
10419
+ return () => {
10420
+ element.removeEventListener('touchstart', handleTouchStart);
10421
+ };
10422
+ }, [disabled, readOnly]);
10423
+ const defaultProps = {
10424
+ role: 'presentation',
10425
+ style: {
10426
+ touchAction: 'none',
10427
+ WebkitUserSelect: 'none',
10428
+ userSelect: 'none'
10429
+ },
10430
+ async onPointerDown(event) {
10431
+ const isMainButton = !event.button || event.button === 0;
10432
+ if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
10433
+ return;
10434
+ }
10435
+ const isTouch = event.pointerType === 'touch';
10436
+ setIsTouchInput(isTouch);
10437
+ if (event.pointerType === 'mouse') {
10438
+ event.preventDefault();
10439
+ inputRef.current?.focus();
10440
+ }
10441
+ isScrubbingRef.current = true;
10442
+ didMoveRef.current = false;
10443
+ pointerDownTargetRef.current = event.target;
10444
+ onScrubbingChange(true, event.nativeEvent);
10445
+
10446
+ // WebKit causes significant layout shift with the native message, so we can't use it.
10447
+ if (!isTouch && !isWebKit) {
10448
+ try {
10449
+ // Avoid non-deterministic errors in testing environments. This error sometimes
10450
+ // appears:
10451
+ // "The root document of this element is not valid for pointer lock."
10452
+ await ownerDocument(scrubAreaRef.current).body.requestPointerLock();
10453
+ setIsPointerLockDenied(false);
10454
+ } catch (error) {
10455
+ setIsPointerLockDenied(true);
10456
+ } finally {
10457
+ if (isScrubbingRef.current) {
10458
+ ReactDOM.flushSync(() => {
10459
+ onScrubbingChange(true, event.nativeEvent);
10460
+ });
10461
+ }
10462
+ }
10463
+ }
10464
+ }
10465
+ };
10466
+ const element = useRenderElement('span', componentProps, {
10467
+ ref: [forwardedRef, scrubAreaRef],
10468
+ state,
10469
+ props: [defaultProps, elementProps],
10470
+ stateAttributesMapping: stateAttributesMapping$1
10471
+ });
10472
+ const contextValue = React.useMemo(() => ({
10473
+ isScrubbing,
10474
+ isTouchInput,
10475
+ isPointerLockDenied,
10476
+ scrubAreaCursorRef,
10477
+ scrubAreaRef,
10478
+ direction,
10479
+ pixelSensitivity,
10480
+ teleportDistance
10481
+ }), [isScrubbing, isTouchInput, isPointerLockDenied, direction, pixelSensitivity, teleportDistance]);
10482
+ return /*#__PURE__*/jsx(NumberFieldScrubAreaContext.Provider, {
10483
+ value: contextValue,
10484
+ children: element
10485
+ });
10486
+ });
10487
+ if (process.env.NODE_ENV !== "production") NumberFieldScrubArea.displayName = "NumberFieldScrubArea";
10488
+
10489
+ /**
10490
+ * A custom element to display instead of the native cursor while using the scrub area.
10491
+ * Renders a `<span>` element.
10492
+ *
10493
+ * This component uses the [Pointer Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API), which may prompt the browser to display a related notification. It is disabled
10494
+ * in Safari to avoid a layout shift that this notification causes there.
10495
+ *
10496
+ * Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
10497
+ */
10498
+ const NumberFieldScrubAreaCursor = /*#__PURE__*/React.forwardRef(function NumberFieldScrubAreaCursor(componentProps, forwardedRef) {
10499
+ const {
10500
+ render,
10501
+ className,
10502
+ ...elementProps
10503
+ } = componentProps;
10504
+ const {
10505
+ state
10506
+ } = useNumberFieldRootContext();
10507
+ const {
10508
+ isScrubbing,
10509
+ isTouchInput,
10510
+ isPointerLockDenied,
10511
+ scrubAreaCursorRef
10512
+ } = useNumberFieldScrubAreaContext();
10513
+ const [domElement, setDomElement] = React.useState(null);
10514
+ const shouldRender = isScrubbing && !isWebKit && !isTouchInput && !isPointerLockDenied;
10515
+ const element = useRenderElement('span', componentProps, {
10516
+ enabled: shouldRender,
10517
+ ref: [forwardedRef, scrubAreaCursorRef, setDomElement],
10518
+ state,
10519
+ props: [{
10520
+ role: 'presentation',
10521
+ style: {
10522
+ position: 'fixed',
10523
+ top: 0,
10524
+ left: 0,
10525
+ pointerEvents: 'none'
10526
+ }
10527
+ }, elementProps],
10528
+ stateAttributesMapping: stateAttributesMapping$1
10529
+ });
10530
+ return element && /*#__PURE__*/ReactDOM.createPortal(element, ownerDocument(domElement).body);
10531
+ });
10532
+ if (process.env.NODE_ENV !== "production") NumberFieldScrubAreaCursor.displayName = "NumberFieldScrubAreaCursor";
10533
+
10534
+ var index_parts = /*#__PURE__*/Object.freeze({
10535
+ __proto__: null,
10536
+ Decrement: NumberFieldDecrement,
10537
+ Group: NumberFieldGroup,
10538
+ Increment: NumberFieldIncrement,
10539
+ Input: NumberFieldInput,
10540
+ Root: NumberFieldRoot,
10541
+ ScrubArea: NumberFieldScrubArea,
10542
+ ScrubAreaCursor: NumberFieldScrubAreaCursor
10543
+ });
10544
+
10545
+ export { index_parts$2 as Combobox, index_parts$1 as Drawer, index_parts as NumberField, Separator };