@jobber/components 7.4.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.
|
@@ -624,6 +624,16 @@ function useClick(context, props = {}) {
|
|
|
624
624
|
} : useRenderElement.EMPTY_OBJECT, [enabled, reference]);
|
|
625
625
|
}
|
|
626
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Returns a function that forces a rerender.
|
|
629
|
+
*/
|
|
630
|
+
function useForcedRerendering() {
|
|
631
|
+
const [, setState] = React__namespace.useState({});
|
|
632
|
+
return React__namespace.useCallback(() => {
|
|
633
|
+
setState({});
|
|
634
|
+
}, []);
|
|
635
|
+
}
|
|
636
|
+
|
|
627
637
|
function useFloatingRootContext(options) {
|
|
628
638
|
const {
|
|
629
639
|
open = false,
|
|
@@ -1715,7 +1725,7 @@ let DialogViewportDataAttributes = function (DialogViewportDataAttributes) {
|
|
|
1715
1725
|
return DialogViewportDataAttributes;
|
|
1716
1726
|
}({});
|
|
1717
1727
|
|
|
1718
|
-
const stateAttributesMapping$
|
|
1728
|
+
const stateAttributesMapping$8 = {
|
|
1719
1729
|
...DrawerRoot.popupStateMapping,
|
|
1720
1730
|
...DrawerRoot.transitionStatusMapping,
|
|
1721
1731
|
nested(value) {
|
|
@@ -1764,7 +1774,7 @@ const DialogViewport = /*#__PURE__*/React__namespace.forwardRef(function DialogV
|
|
|
1764
1774
|
enabled: shouldRender,
|
|
1765
1775
|
state,
|
|
1766
1776
|
ref: [forwardedRef, store.useStateSetter('viewportElement')],
|
|
1767
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
1777
|
+
stateAttributesMapping: stateAttributesMapping$8,
|
|
1768
1778
|
props: [{
|
|
1769
1779
|
role: 'presentation',
|
|
1770
1780
|
hidden: !mounted,
|
|
@@ -4258,7 +4268,7 @@ const ComboboxIcon = /*#__PURE__*/React__namespace.forwardRef(function ComboboxI
|
|
|
4258
4268
|
});
|
|
4259
4269
|
if (process.env.NODE_ENV !== "production") ComboboxIcon.displayName = "ComboboxIcon";
|
|
4260
4270
|
|
|
4261
|
-
const stateAttributesMapping$
|
|
4271
|
+
const stateAttributesMapping$7 = {
|
|
4262
4272
|
...DrawerRoot.transitionStatusMapping,
|
|
4263
4273
|
...DrawerRoot.triggerOpenStateMapping
|
|
4264
4274
|
};
|
|
@@ -4354,7 +4364,7 @@ const ComboboxClear = /*#__PURE__*/React__namespace.forwardRef(function Combobox
|
|
|
4354
4364
|
store.state.inputRef.current?.focus();
|
|
4355
4365
|
}
|
|
4356
4366
|
}, elementProps, getButtonProps],
|
|
4357
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
4367
|
+
stateAttributesMapping: stateAttributesMapping$7
|
|
4358
4368
|
});
|
|
4359
4369
|
const shouldRender = keepMounted || mounted;
|
|
4360
4370
|
if (!shouldRender) {
|
|
@@ -4550,7 +4560,7 @@ const ComboboxPortal = /*#__PURE__*/React__namespace.forwardRef(function Combobo
|
|
|
4550
4560
|
});
|
|
4551
4561
|
if (process.env.NODE_ENV !== "production") ComboboxPortal.displayName = "ComboboxPortal";
|
|
4552
4562
|
|
|
4553
|
-
const stateAttributesMapping$
|
|
4563
|
+
const stateAttributesMapping$6 = {
|
|
4554
4564
|
...DrawerRoot.popupStateMapping,
|
|
4555
4565
|
...DrawerRoot.transitionStatusMapping
|
|
4556
4566
|
};
|
|
@@ -4576,7 +4586,7 @@ const ComboboxBackdrop = /*#__PURE__*/React__namespace.forwardRef(function Combo
|
|
|
4576
4586
|
return useRenderElement.useRenderElement('div', componentProps, {
|
|
4577
4587
|
state,
|
|
4578
4588
|
ref: forwardedRef,
|
|
4579
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
4589
|
+
stateAttributesMapping: stateAttributesMapping$6,
|
|
4580
4590
|
props: [{
|
|
4581
4591
|
role: 'presentation',
|
|
4582
4592
|
hidden: !mounted,
|
|
@@ -5201,7 +5211,7 @@ const ComboboxPositioner = /*#__PURE__*/React__namespace.forwardRef(function Com
|
|
|
5201
5211
|
});
|
|
5202
5212
|
if (process.env.NODE_ENV !== "production") ComboboxPositioner.displayName = "ComboboxPositioner";
|
|
5203
5213
|
|
|
5204
|
-
const stateAttributesMapping$
|
|
5214
|
+
const stateAttributesMapping$5 = {
|
|
5205
5215
|
...DrawerRoot.popupStateMapping,
|
|
5206
5216
|
...DrawerRoot.transitionStatusMapping
|
|
5207
5217
|
};
|
|
@@ -5262,7 +5272,7 @@ const ComboboxPopup = /*#__PURE__*/React__namespace.forwardRef(function Combobox
|
|
|
5262
5272
|
}
|
|
5263
5273
|
}
|
|
5264
5274
|
}, getDisabledMountTransitionStyles(transitionStatus), elementProps],
|
|
5265
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
5275
|
+
stateAttributesMapping: stateAttributesMapping$5
|
|
5266
5276
|
});
|
|
5267
5277
|
|
|
5268
5278
|
// Default initial focus logic:
|
|
@@ -6110,7 +6120,7 @@ const ComboboxChipRemove = /*#__PURE__*/React__namespace.forwardRef(function Com
|
|
|
6110
6120
|
});
|
|
6111
6121
|
if (process.env.NODE_ENV !== "production") ComboboxChipRemove.displayName = "ComboboxChipRemove";
|
|
6112
6122
|
|
|
6113
|
-
var index_parts$
|
|
6123
|
+
var index_parts$2 = /*#__PURE__*/Object.freeze({
|
|
6114
6124
|
__proto__: null,
|
|
6115
6125
|
Arrow: ComboboxArrow,
|
|
6116
6126
|
Backdrop: ComboboxBackdrop,
|
|
@@ -6158,7 +6168,7 @@ const DrawerClose = DialogClose;
|
|
|
6158
6168
|
*/
|
|
6159
6169
|
const DrawerDescription = DialogDescription;
|
|
6160
6170
|
|
|
6161
|
-
const stateAttributesMapping$
|
|
6171
|
+
const stateAttributesMapping$4 = {
|
|
6162
6172
|
active(value) {
|
|
6163
6173
|
if (value) {
|
|
6164
6174
|
return {
|
|
@@ -6228,12 +6238,12 @@ const DrawerIndent = /*#__PURE__*/React__namespace.forwardRef(function DrawerInd
|
|
|
6228
6238
|
[DrawerRoot.DrawerBackdropCssVars.swipeProgress]: '0'
|
|
6229
6239
|
}
|
|
6230
6240
|
}, elementProps],
|
|
6231
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
6241
|
+
stateAttributesMapping: stateAttributesMapping$4
|
|
6232
6242
|
});
|
|
6233
6243
|
});
|
|
6234
6244
|
if (process.env.NODE_ENV !== "production") DrawerIndent.displayName = "DrawerIndent";
|
|
6235
6245
|
|
|
6236
|
-
const stateAttributesMapping$
|
|
6246
|
+
const stateAttributesMapping$3 = {
|
|
6237
6247
|
active(value) {
|
|
6238
6248
|
if (value) {
|
|
6239
6249
|
return {
|
|
@@ -6267,7 +6277,7 @@ const DrawerIndentBackground = /*#__PURE__*/React__namespace.forwardRef(function
|
|
|
6267
6277
|
ref: forwardedRef,
|
|
6268
6278
|
state,
|
|
6269
6279
|
props: elementProps,
|
|
6270
|
-
stateAttributesMapping: stateAttributesMapping$
|
|
6280
|
+
stateAttributesMapping: stateAttributesMapping$3
|
|
6271
6281
|
});
|
|
6272
6282
|
});
|
|
6273
6283
|
if (process.env.NODE_ENV !== "production") DrawerIndentBackground.displayName = "DrawerIndentBackground";
|
|
@@ -7367,7 +7377,7 @@ const SWIPE_AREA_SWIPING_HOOK = {
|
|
|
7367
7377
|
const SWIPE_AREA_DISABLED_HOOK = {
|
|
7368
7378
|
[DrawerSwipeAreaDataAttributes.disabled]: ''
|
|
7369
7379
|
};
|
|
7370
|
-
const stateAttributesMapping = {
|
|
7380
|
+
const stateAttributesMapping$2 = {
|
|
7371
7381
|
open(value) {
|
|
7372
7382
|
return value ? SWIPE_AREA_OPEN_HOOK : SWIPE_AREA_CLOSED_HOOK;
|
|
7373
7383
|
},
|
|
@@ -7672,7 +7682,7 @@ const DrawerSwipeArea = /*#__PURE__*/React__namespace.forwardRef(function Drawer
|
|
|
7672
7682
|
return useRenderElement.useRenderElement('div', componentProps, {
|
|
7673
7683
|
state,
|
|
7674
7684
|
ref: [forwardedRef, swipeAreaRef, registerTrigger],
|
|
7675
|
-
stateAttributesMapping,
|
|
7685
|
+
stateAttributesMapping: stateAttributesMapping$2,
|
|
7676
7686
|
props: [{
|
|
7677
7687
|
role: 'presentation',
|
|
7678
7688
|
'aria-hidden': true,
|
|
@@ -8692,7 +8702,7 @@ function shouldDismissFromStartEdge(direction, axis) {
|
|
|
8692
8702
|
return null;
|
|
8693
8703
|
}
|
|
8694
8704
|
|
|
8695
|
-
var index_parts = /*#__PURE__*/Object.freeze({
|
|
8705
|
+
var index_parts$1 = /*#__PURE__*/Object.freeze({
|
|
8696
8706
|
__proto__: null,
|
|
8697
8707
|
Backdrop: DrawerRoot.DrawerBackdrop,
|
|
8698
8708
|
Close: DrawerClose,
|
|
@@ -8712,6 +8722,1849 @@ var index_parts = /*#__PURE__*/Object.freeze({
|
|
|
8712
8722
|
createHandle: createDialogHandle
|
|
8713
8723
|
});
|
|
8714
8724
|
|
|
8725
|
+
const cache = new Map();
|
|
8726
|
+
function getFormatter(locale, options) {
|
|
8727
|
+
const optionsString = JSON.stringify({
|
|
8728
|
+
locale,
|
|
8729
|
+
options
|
|
8730
|
+
});
|
|
8731
|
+
const cachedFormatter = cache.get(optionsString);
|
|
8732
|
+
if (cachedFormatter) {
|
|
8733
|
+
return cachedFormatter;
|
|
8734
|
+
}
|
|
8735
|
+
const formatter = new Intl.NumberFormat(locale, options);
|
|
8736
|
+
cache.set(optionsString, formatter);
|
|
8737
|
+
return formatter;
|
|
8738
|
+
}
|
|
8739
|
+
function formatNumber(value, locale, options) {
|
|
8740
|
+
if (value == null) {
|
|
8741
|
+
return '';
|
|
8742
|
+
}
|
|
8743
|
+
return getFormatter(locale, options).format(value);
|
|
8744
|
+
}
|
|
8745
|
+
function formatNumberMaxPrecision(value, locale, options) {
|
|
8746
|
+
return formatNumber(value, locale, {
|
|
8747
|
+
...options,
|
|
8748
|
+
maximumFractionDigits: 20
|
|
8749
|
+
});
|
|
8750
|
+
}
|
|
8751
|
+
|
|
8752
|
+
const EMPTY = 0;
|
|
8753
|
+
class Interval extends DrawerRoot.Timeout {
|
|
8754
|
+
static create() {
|
|
8755
|
+
return new Interval();
|
|
8756
|
+
}
|
|
8757
|
+
|
|
8758
|
+
/**
|
|
8759
|
+
* Executes `fn` at `delay` interval, clearing any previously scheduled call.
|
|
8760
|
+
*/
|
|
8761
|
+
start(delay, fn) {
|
|
8762
|
+
this.clear();
|
|
8763
|
+
this.currentId = setInterval(() => {
|
|
8764
|
+
fn();
|
|
8765
|
+
}, delay);
|
|
8766
|
+
}
|
|
8767
|
+
clear = () => {
|
|
8768
|
+
if (this.currentId !== EMPTY) {
|
|
8769
|
+
clearInterval(this.currentId);
|
|
8770
|
+
this.currentId = EMPTY;
|
|
8771
|
+
}
|
|
8772
|
+
};
|
|
8773
|
+
}
|
|
8774
|
+
|
|
8775
|
+
/**
|
|
8776
|
+
* A `setInterval` with automatic cleanup and guard.
|
|
8777
|
+
*/
|
|
8778
|
+
function useInterval() {
|
|
8779
|
+
const timeout = useRenderElement.useRefWithInit(Interval.create).current;
|
|
8780
|
+
DrawerRoot.useOnMount(timeout.disposeEffect);
|
|
8781
|
+
return timeout;
|
|
8782
|
+
}
|
|
8783
|
+
|
|
8784
|
+
const NumberFieldRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
8785
|
+
if (process.env.NODE_ENV !== "production") NumberFieldRootContext.displayName = "NumberFieldRootContext";
|
|
8786
|
+
function useNumberFieldRootContext() {
|
|
8787
|
+
const context = React__namespace.useContext(NumberFieldRootContext);
|
|
8788
|
+
if (context === undefined) {
|
|
8789
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: NumberFieldRootContext is missing. NumberField parts must be placed within <NumberField.Root>.' : useRenderElement.formatErrorMessage(43));
|
|
8790
|
+
}
|
|
8791
|
+
return context;
|
|
8792
|
+
}
|
|
8793
|
+
|
|
8794
|
+
const stateAttributesMapping$1 = {
|
|
8795
|
+
inputValue: () => null,
|
|
8796
|
+
value: () => null,
|
|
8797
|
+
...fieldValidityMapping
|
|
8798
|
+
};
|
|
8799
|
+
|
|
8800
|
+
const HAN_NUMERALS = ['零', '〇', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
|
|
8801
|
+
// Map Han numeral characters to ASCII digits. Includes both forms of zero.
|
|
8802
|
+
const HAN_NUMERAL_TO_DIGIT = {
|
|
8803
|
+
零: '0',
|
|
8804
|
+
〇: '0',
|
|
8805
|
+
一: '1',
|
|
8806
|
+
二: '2',
|
|
8807
|
+
三: '3',
|
|
8808
|
+
四: '4',
|
|
8809
|
+
五: '5',
|
|
8810
|
+
六: '6',
|
|
8811
|
+
七: '7',
|
|
8812
|
+
八: '8',
|
|
8813
|
+
九: '9'
|
|
8814
|
+
};
|
|
8815
|
+
const ARABIC_NUMERALS = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
|
|
8816
|
+
const PERSIAN_NUMERALS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
|
8817
|
+
const FULLWIDTH_NUMERALS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
8818
|
+
const PERCENTAGES = ['%', '٪', '%', '﹪'];
|
|
8819
|
+
const PERMILLE = ['‰', '؉'];
|
|
8820
|
+
const UNICODE_MINUS_SIGNS = ['−', '-', '‒', '–', '—', '﹣'];
|
|
8821
|
+
const UNICODE_PLUS_SIGNS = ['+', '﹢'];
|
|
8822
|
+
|
|
8823
|
+
// Fullwidth punctuation common in CJK inputs
|
|
8824
|
+
const FULLWIDTH_DECIMAL = '.'; // U+FF0E
|
|
8825
|
+
const FULLWIDTH_GROUP = ','; // U+FF0C
|
|
8826
|
+
|
|
8827
|
+
const ARABIC_RE = new RegExp(`[${ARABIC_NUMERALS.join('')}]`, 'g');
|
|
8828
|
+
const PERSIAN_RE = new RegExp(`[${PERSIAN_NUMERALS.join('')}]`, 'g');
|
|
8829
|
+
const FULLWIDTH_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`, 'g');
|
|
8830
|
+
const HAN_RE = new RegExp(`[${HAN_NUMERALS.join('')}]`, 'g');
|
|
8831
|
+
const PERCENT_RE = new RegExp(`[${PERCENTAGES.join('')}]`);
|
|
8832
|
+
const PERMILLE_RE = new RegExp(`[${PERMILLE.join('')}]`);
|
|
8833
|
+
|
|
8834
|
+
// Detection regexes (non-global to avoid lastIndex side effects)
|
|
8835
|
+
const ARABIC_DETECT_RE = /[٠١٢٣٤٥٦٧٨٩]/;
|
|
8836
|
+
const PERSIAN_DETECT_RE = /[۰۱۲۳۴۵۶۷۸۹]/;
|
|
8837
|
+
const HAN_DETECT_RE = /[零〇一二三四五六七八九]/;
|
|
8838
|
+
const FULLWIDTH_DETECT_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`);
|
|
8839
|
+
const BASE_NON_NUMERIC_SYMBOLS = ['.', ',', FULLWIDTH_DECIMAL, FULLWIDTH_GROUP, '٫', '٬'];
|
|
8840
|
+
const SPACE_SEPARATOR_RE = /\p{Zs}/u;
|
|
8841
|
+
const PLUS_SIGNS_WITH_ASCII = ['+', ...UNICODE_PLUS_SIGNS];
|
|
8842
|
+
const MINUS_SIGNS_WITH_ASCII = ['-', ...UNICODE_MINUS_SIGNS];
|
|
8843
|
+
const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8844
|
+
const escapeClassChar = s => s.replace(/[-\\\]^]/g, m => `\\${m}`); // escape for use inside [...]
|
|
8845
|
+
|
|
8846
|
+
const charClassFrom = chars => `[${chars.map(escapeClassChar).join('')}]`;
|
|
8847
|
+
const ANY_MINUS_CLASS = charClassFrom(['-'].concat(UNICODE_MINUS_SIGNS));
|
|
8848
|
+
const ANY_PLUS_CLASS = charClassFrom(['+'].concat(UNICODE_PLUS_SIGNS));
|
|
8849
|
+
const ANY_MINUS_RE = new RegExp(ANY_MINUS_CLASS, 'gu');
|
|
8850
|
+
const ANY_PLUS_RE = new RegExp(ANY_PLUS_CLASS, 'gu');
|
|
8851
|
+
const ANY_MINUS_DETECT_RE = new RegExp(ANY_MINUS_CLASS);
|
|
8852
|
+
const ANY_PLUS_DETECT_RE = new RegExp(ANY_PLUS_CLASS);
|
|
8853
|
+
function getNumberLocaleDetails(locale, options) {
|
|
8854
|
+
const parts = getFormatter(locale, options).formatToParts(11111.1);
|
|
8855
|
+
const result = {};
|
|
8856
|
+
parts.forEach(part => {
|
|
8857
|
+
result[part.type] = part.value;
|
|
8858
|
+
});
|
|
8859
|
+
|
|
8860
|
+
// The formatting options may result in not returning a decimal.
|
|
8861
|
+
getFormatter(locale).formatToParts(0.1).forEach(part => {
|
|
8862
|
+
if (part.type === 'decimal') {
|
|
8863
|
+
result[part.type] = part.value;
|
|
8864
|
+
}
|
|
8865
|
+
});
|
|
8866
|
+
return result;
|
|
8867
|
+
}
|
|
8868
|
+
function parseNumber(formattedNumber, locale, options) {
|
|
8869
|
+
if (formattedNumber == null) {
|
|
8870
|
+
return null;
|
|
8871
|
+
}
|
|
8872
|
+
|
|
8873
|
+
// Normalize control characters and whitespace; remove bidi/format controls
|
|
8874
|
+
let input = String(formattedNumber).replace(/\p{Cf}/gu, '').trim();
|
|
8875
|
+
|
|
8876
|
+
// Normalize unicode minus/plus to ASCII, handle leading/trailing signs
|
|
8877
|
+
input = input.replace(ANY_MINUS_RE, '-').replace(ANY_PLUS_RE, '+');
|
|
8878
|
+
let isNegative = false;
|
|
8879
|
+
|
|
8880
|
+
// Trailing sign, e.g. "1234-" / "1234+"
|
|
8881
|
+
const trailing = input.match(/([+-])\s*$/);
|
|
8882
|
+
if (trailing) {
|
|
8883
|
+
if (trailing[1] === '-') {
|
|
8884
|
+
isNegative = true;
|
|
8885
|
+
}
|
|
8886
|
+
input = input.replace(/([+-])\s*$/, '');
|
|
8887
|
+
}
|
|
8888
|
+
// Leading sign
|
|
8889
|
+
const leading = input.match(/^\s*([+-])/);
|
|
8890
|
+
if (leading) {
|
|
8891
|
+
if (leading[1] === '-') {
|
|
8892
|
+
isNegative = true;
|
|
8893
|
+
}
|
|
8894
|
+
input = input.replace(/^\s*[+-]/, '');
|
|
8895
|
+
}
|
|
8896
|
+
|
|
8897
|
+
// Heuristic locale detection
|
|
8898
|
+
let computedLocale = locale;
|
|
8899
|
+
if (computedLocale === undefined) {
|
|
8900
|
+
if (ARABIC_DETECT_RE.test(input) || PERSIAN_DETECT_RE.test(input)) {
|
|
8901
|
+
computedLocale = 'ar';
|
|
8902
|
+
} else if (HAN_DETECT_RE.test(input)) {
|
|
8903
|
+
computedLocale = 'zh';
|
|
8904
|
+
}
|
|
8905
|
+
}
|
|
8906
|
+
const {
|
|
8907
|
+
group,
|
|
8908
|
+
decimal,
|
|
8909
|
+
currency
|
|
8910
|
+
} = getNumberLocaleDetails(computedLocale, options);
|
|
8911
|
+
|
|
8912
|
+
// Build robust unit regex from all unit parts (such as "km/h")
|
|
8913
|
+
const unitParts = getFormatter(computedLocale, options).formatToParts(1).filter(p => p.type === 'unit').map(p => escapeRegExp(p.value));
|
|
8914
|
+
const unitRegex = unitParts.length ? new RegExp(unitParts.join('|'), 'g') : null;
|
|
8915
|
+
let groupRegex = null;
|
|
8916
|
+
if (group) {
|
|
8917
|
+
const isSpaceGroup = /\p{Zs}/u.test(group);
|
|
8918
|
+
const isApostropheGroup = group === "'" || group === '’';
|
|
8919
|
+
|
|
8920
|
+
// Check if the group separator is a space-like character.
|
|
8921
|
+
// If so, we'll replace all such characters with an empty string.
|
|
8922
|
+
if (isSpaceGroup) {
|
|
8923
|
+
groupRegex = /\p{Zs}/gu;
|
|
8924
|
+
} else if (isApostropheGroup) {
|
|
8925
|
+
// Some environments format numbers with ASCII apostrophe and others with a curly apostrophe.
|
|
8926
|
+
groupRegex = /['’]/g;
|
|
8927
|
+
} else {
|
|
8928
|
+
groupRegex = new RegExp(escapeRegExp(group), 'g');
|
|
8929
|
+
}
|
|
8930
|
+
}
|
|
8931
|
+
const replacements = [{
|
|
8932
|
+
regex: group ? groupRegex : null,
|
|
8933
|
+
replacement: ''
|
|
8934
|
+
}, {
|
|
8935
|
+
regex: decimal ? new RegExp(escapeRegExp(decimal), 'g') : null,
|
|
8936
|
+
replacement: '.'
|
|
8937
|
+
},
|
|
8938
|
+
// Fullwidth punctuation
|
|
8939
|
+
{
|
|
8940
|
+
regex: /./g,
|
|
8941
|
+
replacement: '.'
|
|
8942
|
+
},
|
|
8943
|
+
// FULLWIDTH_DECIMAL
|
|
8944
|
+
{
|
|
8945
|
+
regex: /,/g,
|
|
8946
|
+
replacement: ''
|
|
8947
|
+
},
|
|
8948
|
+
// FULLWIDTH_GROUP
|
|
8949
|
+
// Arabic punctuation
|
|
8950
|
+
{
|
|
8951
|
+
regex: /٫/g,
|
|
8952
|
+
replacement: '.'
|
|
8953
|
+
},
|
|
8954
|
+
// ARABIC DECIMAL SEPARATOR (U+066B)
|
|
8955
|
+
{
|
|
8956
|
+
regex: /٬/g,
|
|
8957
|
+
replacement: ''
|
|
8958
|
+
},
|
|
8959
|
+
// ARABIC THOUSANDS SEPARATOR (U+066C)
|
|
8960
|
+
// Currency & unit labels
|
|
8961
|
+
{
|
|
8962
|
+
regex: currency ? new RegExp(escapeRegExp(currency), 'g') : null,
|
|
8963
|
+
replacement: ''
|
|
8964
|
+
}, {
|
|
8965
|
+
regex: unitRegex,
|
|
8966
|
+
replacement: ''
|
|
8967
|
+
},
|
|
8968
|
+
// Numeral systems to ASCII digits
|
|
8969
|
+
{
|
|
8970
|
+
regex: ARABIC_RE,
|
|
8971
|
+
replacement: ch => String(ARABIC_NUMERALS.indexOf(ch))
|
|
8972
|
+
}, {
|
|
8973
|
+
regex: PERSIAN_RE,
|
|
8974
|
+
replacement: ch => String(PERSIAN_NUMERALS.indexOf(ch))
|
|
8975
|
+
}, {
|
|
8976
|
+
regex: FULLWIDTH_RE,
|
|
8977
|
+
replacement: ch => String(FULLWIDTH_NUMERALS.indexOf(ch))
|
|
8978
|
+
}, {
|
|
8979
|
+
regex: HAN_RE,
|
|
8980
|
+
replacement: ch => HAN_NUMERAL_TO_DIGIT[ch]
|
|
8981
|
+
}];
|
|
8982
|
+
let unformatted = replacements.reduce((acc, {
|
|
8983
|
+
regex,
|
|
8984
|
+
replacement
|
|
8985
|
+
}) => {
|
|
8986
|
+
return regex ? acc.replace(regex, replacement) : acc;
|
|
8987
|
+
}, input);
|
|
8988
|
+
|
|
8989
|
+
// Mixed-locale safety: keep only the last '.' as decimal
|
|
8990
|
+
const lastDot = unformatted.lastIndexOf('.');
|
|
8991
|
+
if (lastDot !== -1) {
|
|
8992
|
+
unformatted = `${unformatted.slice(0, lastDot).replace(/\./g, '')}.${unformatted.slice(lastDot + 1).replace(/\./g, '')}`;
|
|
8993
|
+
}
|
|
8994
|
+
|
|
8995
|
+
// Guard against Infinity inputs (ASCII and symbol)
|
|
8996
|
+
if (/^[-+]?Infinity$/i.test(input) || /[∞]/.test(input)) {
|
|
8997
|
+
return null;
|
|
8998
|
+
}
|
|
8999
|
+
const parseTarget = (isNegative ? '-' : '') + unformatted;
|
|
9000
|
+
let num = parseFloat(parseTarget);
|
|
9001
|
+
const style = options?.style;
|
|
9002
|
+
const isUnitPercent = style === 'unit' && options?.unit === 'percent';
|
|
9003
|
+
const hasPercentSymbol = PERCENT_RE.test(formattedNumber) || style === 'percent';
|
|
9004
|
+
const hasPermilleSymbol = PERMILLE_RE.test(formattedNumber);
|
|
9005
|
+
if (hasPermilleSymbol) {
|
|
9006
|
+
num /= 1000;
|
|
9007
|
+
} else if (!isUnitPercent && hasPercentSymbol) {
|
|
9008
|
+
num /= 100;
|
|
9009
|
+
}
|
|
9010
|
+
if (Number.isNaN(num)) {
|
|
9011
|
+
return null;
|
|
9012
|
+
}
|
|
9013
|
+
return num;
|
|
9014
|
+
}
|
|
9015
|
+
|
|
9016
|
+
const CHANGE_VALUE_TICK_DELAY = 60;
|
|
9017
|
+
const START_AUTO_CHANGE_DELAY = 400;
|
|
9018
|
+
const TOUCH_TIMEOUT = 50;
|
|
9019
|
+
const MAX_POINTER_MOVES_AFTER_TOUCH = 3;
|
|
9020
|
+
const SCROLLING_POINTER_MOVE_DISTANCE = 8;
|
|
9021
|
+
const DEFAULT_STEP = 1;
|
|
9022
|
+
|
|
9023
|
+
const STEP_EPSILON_FACTOR = 1e-10;
|
|
9024
|
+
function getFractionDigits(format) {
|
|
9025
|
+
const defaultOptions = getFormatter('en-US').resolvedOptions();
|
|
9026
|
+
const minimumFractionDigits = format?.minimumFractionDigits ?? defaultOptions.minimumFractionDigits ?? 0;
|
|
9027
|
+
const maximumFractionDigits = Math.max(format?.maximumFractionDigits ?? defaultOptions.maximumFractionDigits ?? 20, minimumFractionDigits);
|
|
9028
|
+
return {
|
|
9029
|
+
maximumFractionDigits,
|
|
9030
|
+
minimumFractionDigits
|
|
9031
|
+
};
|
|
9032
|
+
}
|
|
9033
|
+
function roundToFractionDigits(value, maximumFractionDigits) {
|
|
9034
|
+
if (!Number.isFinite(value)) {
|
|
9035
|
+
return value;
|
|
9036
|
+
}
|
|
9037
|
+
const digits = Math.min(Math.max(maximumFractionDigits, 0), 20);
|
|
9038
|
+
return Number(value.toFixed(digits));
|
|
9039
|
+
}
|
|
9040
|
+
function removeFloatingPointErrors(value, format) {
|
|
9041
|
+
const {
|
|
9042
|
+
maximumFractionDigits
|
|
9043
|
+
} = getFractionDigits(format);
|
|
9044
|
+
return roundToFractionDigits(value, maximumFractionDigits);
|
|
9045
|
+
}
|
|
9046
|
+
function snapToStep(clampedValue, base, step, mode = 'directional') {
|
|
9047
|
+
if (step === 0) {
|
|
9048
|
+
return clampedValue;
|
|
9049
|
+
}
|
|
9050
|
+
const stepSize = Math.abs(step);
|
|
9051
|
+
const direction = Math.sign(step);
|
|
9052
|
+
const tolerance = stepSize * STEP_EPSILON_FACTOR * direction;
|
|
9053
|
+
const divisor = mode === 'nearest' ? step : stepSize;
|
|
9054
|
+
const rawSteps = (clampedValue - base + tolerance) / divisor;
|
|
9055
|
+
let snappedSteps;
|
|
9056
|
+
if (mode === 'nearest') {
|
|
9057
|
+
snappedSteps = Math.round(rawSteps);
|
|
9058
|
+
} else if (direction > 0) {
|
|
9059
|
+
snappedSteps = Math.floor(rawSteps);
|
|
9060
|
+
} else {
|
|
9061
|
+
snappedSteps = Math.ceil(rawSteps);
|
|
9062
|
+
}
|
|
9063
|
+
const stepForResult = mode === 'nearest' ? step : stepSize;
|
|
9064
|
+
return base + snappedSteps * stepForResult;
|
|
9065
|
+
}
|
|
9066
|
+
function toValidatedNumber(value, {
|
|
9067
|
+
step,
|
|
9068
|
+
minWithDefault,
|
|
9069
|
+
maxWithDefault,
|
|
9070
|
+
minWithZeroDefault,
|
|
9071
|
+
format,
|
|
9072
|
+
snapOnStep,
|
|
9073
|
+
small,
|
|
9074
|
+
clamp: shouldClamp
|
|
9075
|
+
}) {
|
|
9076
|
+
if (value === null) {
|
|
9077
|
+
return value;
|
|
9078
|
+
}
|
|
9079
|
+
const clampedValue = shouldClamp ? DrawerRoot.clamp(value, minWithDefault, maxWithDefault) : value;
|
|
9080
|
+
if (step != null && snapOnStep) {
|
|
9081
|
+
if (step === 0) {
|
|
9082
|
+
return removeFloatingPointErrors(clampedValue, format);
|
|
9083
|
+
}
|
|
9084
|
+
|
|
9085
|
+
// If a real minimum is provided, use it
|
|
9086
|
+
let base = minWithZeroDefault;
|
|
9087
|
+
if (!small && minWithDefault !== Number.MIN_SAFE_INTEGER) {
|
|
9088
|
+
base = minWithDefault;
|
|
9089
|
+
}
|
|
9090
|
+
const snappedValue = snapToStep(clampedValue, base, step, small ? 'nearest' : 'directional');
|
|
9091
|
+
return removeFloatingPointErrors(snappedValue, format);
|
|
9092
|
+
}
|
|
9093
|
+
return removeFloatingPointErrors(clampedValue, format);
|
|
9094
|
+
}
|
|
9095
|
+
|
|
9096
|
+
const NumberFieldRoot = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldRoot(componentProps, forwardedRef) {
|
|
9097
|
+
const {
|
|
9098
|
+
id: idProp,
|
|
9099
|
+
min,
|
|
9100
|
+
max,
|
|
9101
|
+
smallStep = 0.1,
|
|
9102
|
+
step: stepProp = 1,
|
|
9103
|
+
largeStep = 10,
|
|
9104
|
+
required = false,
|
|
9105
|
+
disabled: disabledProp = false,
|
|
9106
|
+
readOnly = false,
|
|
9107
|
+
name: nameProp,
|
|
9108
|
+
defaultValue,
|
|
9109
|
+
value: valueProp,
|
|
9110
|
+
onValueChange: onValueChangeProp,
|
|
9111
|
+
onValueCommitted: onValueCommittedProp,
|
|
9112
|
+
allowWheelScrub = false,
|
|
9113
|
+
snapOnStep = false,
|
|
9114
|
+
allowOutOfRange = false,
|
|
9115
|
+
format,
|
|
9116
|
+
locale,
|
|
9117
|
+
render,
|
|
9118
|
+
className,
|
|
9119
|
+
inputRef: inputRefProp,
|
|
9120
|
+
...elementProps
|
|
9121
|
+
} = componentProps;
|
|
9122
|
+
const {
|
|
9123
|
+
setDirty,
|
|
9124
|
+
validityData,
|
|
9125
|
+
disabled: fieldDisabled,
|
|
9126
|
+
setFilled,
|
|
9127
|
+
invalid,
|
|
9128
|
+
name: fieldName,
|
|
9129
|
+
state: fieldState,
|
|
9130
|
+
validation,
|
|
9131
|
+
shouldValidateOnChange
|
|
9132
|
+
} = useFieldRootContext();
|
|
9133
|
+
const disabled = fieldDisabled || disabledProp;
|
|
9134
|
+
const name = fieldName ?? nameProp;
|
|
9135
|
+
const step = stepProp === 'any' ? 1 : stepProp;
|
|
9136
|
+
const [isScrubbing, setIsScrubbing] = React__namespace.useState(false);
|
|
9137
|
+
const minWithDefault = min ?? Number.MIN_SAFE_INTEGER;
|
|
9138
|
+
const maxWithDefault = max ?? Number.MAX_SAFE_INTEGER;
|
|
9139
|
+
const minWithZeroDefault = min ?? 0;
|
|
9140
|
+
const formatStyle = format?.style;
|
|
9141
|
+
const inputRef = React__namespace.useRef(null);
|
|
9142
|
+
const hiddenInputRef = useRenderElement.useMergedRefs(inputRefProp, validation.inputRef);
|
|
9143
|
+
const id = useLabelableId({
|
|
9144
|
+
id: idProp
|
|
9145
|
+
});
|
|
9146
|
+
const [valueUnwrapped, setValueUnwrapped] = DrawerRoot.useControlled({
|
|
9147
|
+
controlled: valueProp,
|
|
9148
|
+
default: defaultValue,
|
|
9149
|
+
name: 'NumberField',
|
|
9150
|
+
state: 'value'
|
|
9151
|
+
});
|
|
9152
|
+
const value = valueUnwrapped ?? null;
|
|
9153
|
+
const valueRef = DrawerRoot.useValueAsRef(value);
|
|
9154
|
+
DrawerRoot.useIsoLayoutEffect(() => {
|
|
9155
|
+
setFilled(value !== null);
|
|
9156
|
+
}, [setFilled, value]);
|
|
9157
|
+
const forceRender = useForcedRerendering();
|
|
9158
|
+
const formatOptionsRef = DrawerRoot.useValueAsRef(format);
|
|
9159
|
+
const hasPendingCommitRef = React__namespace.useRef(false);
|
|
9160
|
+
const onValueCommitted = DrawerRoot.useStableCallback((nextValue, eventDetails) => {
|
|
9161
|
+
hasPendingCommitRef.current = false;
|
|
9162
|
+
onValueCommittedProp?.(nextValue, eventDetails);
|
|
9163
|
+
});
|
|
9164
|
+
const startTickTimeout = DrawerRoot.useTimeout();
|
|
9165
|
+
const tickInterval = useInterval();
|
|
9166
|
+
const intentionalTouchCheckTimeout = DrawerRoot.useTimeout();
|
|
9167
|
+
const isPressedRef = React__namespace.useRef(false);
|
|
9168
|
+
const movesAfterTouchRef = React__namespace.useRef(0);
|
|
9169
|
+
const allowInputSyncRef = React__namespace.useRef(true);
|
|
9170
|
+
const lastChangedValueRef = React__namespace.useRef(null);
|
|
9171
|
+
const unsubscribeFromGlobalContextMenuRef = React__namespace.useRef(() => {});
|
|
9172
|
+
|
|
9173
|
+
// During SSR, the value is formatted on the server, whose locale may differ from the client's
|
|
9174
|
+
// locale. This causes a hydration mismatch, which we manually suppress. This is preferable to
|
|
9175
|
+
// rendering an empty input field and then updating it with the formatted value, as the user
|
|
9176
|
+
// can still see the value prior to hydration, even if it's not formatted correctly.
|
|
9177
|
+
const [inputValue, setInputValue] = React__namespace.useState(() => {
|
|
9178
|
+
if (valueProp !== undefined) {
|
|
9179
|
+
return getControlledInputValue(value, locale, format);
|
|
9180
|
+
}
|
|
9181
|
+
return formatNumber(value, locale, format);
|
|
9182
|
+
});
|
|
9183
|
+
const [inputMode, setInputMode] = React__namespace.useState('numeric');
|
|
9184
|
+
const getAllowedNonNumericKeys = DrawerRoot.useStableCallback(() => {
|
|
9185
|
+
const {
|
|
9186
|
+
decimal,
|
|
9187
|
+
group,
|
|
9188
|
+
currency,
|
|
9189
|
+
literal
|
|
9190
|
+
} = getNumberLocaleDetails(locale, format);
|
|
9191
|
+
const keys = new Set();
|
|
9192
|
+
BASE_NON_NUMERIC_SYMBOLS.forEach(symbol => keys.add(symbol));
|
|
9193
|
+
if (decimal) {
|
|
9194
|
+
keys.add(decimal);
|
|
9195
|
+
}
|
|
9196
|
+
if (group) {
|
|
9197
|
+
keys.add(group);
|
|
9198
|
+
if (SPACE_SEPARATOR_RE.test(group)) {
|
|
9199
|
+
keys.add(' ');
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
const allowPercentSymbols = formatStyle === 'percent' || formatStyle === 'unit' && format?.unit === 'percent';
|
|
9203
|
+
const allowPermilleSymbols = formatStyle === 'percent' || formatStyle === 'unit' && format?.unit === 'permille';
|
|
9204
|
+
if (allowPercentSymbols) {
|
|
9205
|
+
PERCENTAGES.forEach(key => keys.add(key));
|
|
9206
|
+
}
|
|
9207
|
+
if (allowPermilleSymbols) {
|
|
9208
|
+
PERMILLE.forEach(key => keys.add(key));
|
|
9209
|
+
}
|
|
9210
|
+
if (formatStyle === 'currency' && currency) {
|
|
9211
|
+
keys.add(currency);
|
|
9212
|
+
}
|
|
9213
|
+
if (literal) {
|
|
9214
|
+
// Some locales (e.g. de-DE) insert a literal space character between the number
|
|
9215
|
+
// and the symbol, so allow those characters to be typed/removed.
|
|
9216
|
+
Array.from(literal).forEach(char => keys.add(char));
|
|
9217
|
+
if (SPACE_SEPARATOR_RE.test(literal)) {
|
|
9218
|
+
keys.add(' ');
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
|
|
9222
|
+
// Allow plus sign in all cases; minus sign only when negatives are valid
|
|
9223
|
+
PLUS_SIGNS_WITH_ASCII.forEach(key => keys.add(key));
|
|
9224
|
+
if (minWithDefault < 0) {
|
|
9225
|
+
MINUS_SIGNS_WITH_ASCII.forEach(key => keys.add(key));
|
|
9226
|
+
}
|
|
9227
|
+
return keys;
|
|
9228
|
+
});
|
|
9229
|
+
const getStepAmount = DrawerRoot.useStableCallback(event => {
|
|
9230
|
+
if (event?.altKey) {
|
|
9231
|
+
return smallStep;
|
|
9232
|
+
}
|
|
9233
|
+
if (event?.shiftKey) {
|
|
9234
|
+
return largeStep;
|
|
9235
|
+
}
|
|
9236
|
+
return step;
|
|
9237
|
+
});
|
|
9238
|
+
const setValue = DrawerRoot.useStableCallback((unvalidatedValue, details) => {
|
|
9239
|
+
const eventWithOptionalKeyState = details.event;
|
|
9240
|
+
const dir = details.direction;
|
|
9241
|
+
const reason = details.reason;
|
|
9242
|
+
// Only allow out-of-range values for direct text entry (native-like behavior).
|
|
9243
|
+
// Step-based interactions (keyboard arrows, buttons, wheel, scrub) still clamp to min/max.
|
|
9244
|
+
const shouldClampValue = !allowOutOfRange || !(reason === DrawerRoot.inputChange || reason === DrawerRoot.inputBlur || reason === DrawerRoot.inputPaste || reason === DrawerRoot.inputClear || reason === DrawerRoot.none);
|
|
9245
|
+
const validatedValue = toValidatedNumber(unvalidatedValue, {
|
|
9246
|
+
step: dir ? getStepAmount(eventWithOptionalKeyState) * dir : undefined,
|
|
9247
|
+
format: formatOptionsRef.current,
|
|
9248
|
+
minWithDefault,
|
|
9249
|
+
maxWithDefault,
|
|
9250
|
+
minWithZeroDefault,
|
|
9251
|
+
snapOnStep,
|
|
9252
|
+
small: eventWithOptionalKeyState?.altKey ?? false,
|
|
9253
|
+
clamp: shouldClampValue
|
|
9254
|
+
});
|
|
9255
|
+
|
|
9256
|
+
// Determine whether we should notify about a change even if the numeric value is unchanged.
|
|
9257
|
+
// This is needed when the user input is clamped/snapped to the same current value, or when
|
|
9258
|
+
// the source value differs but validation normalizes to the existing value.
|
|
9259
|
+
const isInputReason = details.reason === DrawerRoot.inputChange || details.reason === DrawerRoot.inputClear || details.reason === DrawerRoot.inputBlur || details.reason === DrawerRoot.inputPaste || details.reason === DrawerRoot.none;
|
|
9260
|
+
const shouldFireChange = validatedValue !== value || isInputReason && (unvalidatedValue !== value || allowInputSyncRef.current === false);
|
|
9261
|
+
if (shouldFireChange) {
|
|
9262
|
+
lastChangedValueRef.current = validatedValue;
|
|
9263
|
+
onValueChangeProp?.(validatedValue, details);
|
|
9264
|
+
if (details.isCanceled) {
|
|
9265
|
+
return shouldFireChange;
|
|
9266
|
+
}
|
|
9267
|
+
setValueUnwrapped(validatedValue);
|
|
9268
|
+
setDirty(validatedValue !== validityData.initialValue);
|
|
9269
|
+
hasPendingCommitRef.current = true;
|
|
9270
|
+
}
|
|
9271
|
+
|
|
9272
|
+
// Keep the visible input in sync immediately when programmatic changes occur
|
|
9273
|
+
// (increment/decrement, wheel, etc). During direct typing we don't want
|
|
9274
|
+
// to overwrite the user-provided text until blur, so we gate on
|
|
9275
|
+
// `allowInputSyncRef`.
|
|
9276
|
+
if (allowInputSyncRef.current) {
|
|
9277
|
+
setInputValue(formatNumber(validatedValue, locale, format));
|
|
9278
|
+
}
|
|
9279
|
+
|
|
9280
|
+
// Formatting can change even if the numeric value hasn't, so ensure a re-render when needed.
|
|
9281
|
+
forceRender();
|
|
9282
|
+
return shouldFireChange;
|
|
9283
|
+
});
|
|
9284
|
+
const incrementValue = DrawerRoot.useStableCallback((amount, {
|
|
9285
|
+
direction,
|
|
9286
|
+
currentValue,
|
|
9287
|
+
event,
|
|
9288
|
+
reason
|
|
9289
|
+
}) => {
|
|
9290
|
+
const prevValue = currentValue == null ? valueRef.current : currentValue;
|
|
9291
|
+
const nextValue = typeof prevValue === 'number' ? prevValue + amount * direction : Math.max(0, min ?? 0);
|
|
9292
|
+
const nativeEvent = event;
|
|
9293
|
+
return setValue(nextValue, DrawerRoot.createChangeEventDetails(reason, nativeEvent, undefined, {
|
|
9294
|
+
direction
|
|
9295
|
+
}));
|
|
9296
|
+
});
|
|
9297
|
+
const stopAutoChange = DrawerRoot.useStableCallback(() => {
|
|
9298
|
+
intentionalTouchCheckTimeout.clear();
|
|
9299
|
+
startTickTimeout.clear();
|
|
9300
|
+
tickInterval.clear();
|
|
9301
|
+
unsubscribeFromGlobalContextMenuRef.current();
|
|
9302
|
+
movesAfterTouchRef.current = 0;
|
|
9303
|
+
});
|
|
9304
|
+
const startAutoChange = DrawerRoot.useStableCallback((isIncrement, triggerEvent) => {
|
|
9305
|
+
stopAutoChange();
|
|
9306
|
+
if (!inputRef.current) {
|
|
9307
|
+
return;
|
|
9308
|
+
}
|
|
9309
|
+
const win = index_esm.getWindow(inputRef.current);
|
|
9310
|
+
function handleContextMenu(event) {
|
|
9311
|
+
event.preventDefault();
|
|
9312
|
+
}
|
|
9313
|
+
|
|
9314
|
+
// A global context menu is necessary to prevent the context menu from appearing when the touch
|
|
9315
|
+
// is slightly outside of the element's hit area.
|
|
9316
|
+
win.addEventListener('contextmenu', handleContextMenu);
|
|
9317
|
+
unsubscribeFromGlobalContextMenuRef.current = () => {
|
|
9318
|
+
win.removeEventListener('contextmenu', handleContextMenu);
|
|
9319
|
+
};
|
|
9320
|
+
win.addEventListener('pointerup', event => {
|
|
9321
|
+
isPressedRef.current = false;
|
|
9322
|
+
stopAutoChange();
|
|
9323
|
+
const committed = lastChangedValueRef.current ?? valueRef.current;
|
|
9324
|
+
const commitReason = isIncrement ? DrawerRoot.incrementPress : DrawerRoot.decrementPress;
|
|
9325
|
+
onValueCommitted(committed, DrawerRoot.createGenericEventDetails(commitReason, event));
|
|
9326
|
+
}, {
|
|
9327
|
+
once: true
|
|
9328
|
+
});
|
|
9329
|
+
function tick() {
|
|
9330
|
+
const amount = getStepAmount(triggerEvent) ?? DEFAULT_STEP;
|
|
9331
|
+
return incrementValue(amount, {
|
|
9332
|
+
direction: isIncrement ? 1 : -1,
|
|
9333
|
+
event: triggerEvent,
|
|
9334
|
+
reason: isIncrement ? 'increment-press' : 'decrement-press'
|
|
9335
|
+
});
|
|
9336
|
+
}
|
|
9337
|
+
if (!tick()) {
|
|
9338
|
+
stopAutoChange();
|
|
9339
|
+
return;
|
|
9340
|
+
}
|
|
9341
|
+
startTickTimeout.start(START_AUTO_CHANGE_DELAY, () => {
|
|
9342
|
+
tickInterval.start(CHANGE_VALUE_TICK_DELAY, () => {
|
|
9343
|
+
if (!tick()) {
|
|
9344
|
+
stopAutoChange();
|
|
9345
|
+
}
|
|
9346
|
+
});
|
|
9347
|
+
});
|
|
9348
|
+
});
|
|
9349
|
+
|
|
9350
|
+
// We need to update the input value when the external `value` prop changes. This ends up acting
|
|
9351
|
+
// as a single source of truth to update the input value, bypassing the need to manually set it in
|
|
9352
|
+
// each event handler internally in this hook.
|
|
9353
|
+
// This is done inside a layout effect as an alternative to the technique to set state during
|
|
9354
|
+
// render as we're accessing a ref, which must be inside an effect.
|
|
9355
|
+
// https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
|
|
9356
|
+
//
|
|
9357
|
+
// ESLint is disabled because it needs to run even if the parsed value hasn't changed, since the
|
|
9358
|
+
// value still can be formatted differently.
|
|
9359
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
9360
|
+
DrawerRoot.useIsoLayoutEffect(function syncFormattedInputValueOnValueChange() {
|
|
9361
|
+
// This ensures the value is only updated on blur rather than every keystroke, but still
|
|
9362
|
+
// allows the input value to be updated when the value is changed externally.
|
|
9363
|
+
if (!allowInputSyncRef.current) {
|
|
9364
|
+
return;
|
|
9365
|
+
}
|
|
9366
|
+
const nextInputValue = valueProp !== undefined ? getControlledInputValue(value, locale, format) : formatNumber(value, locale, format);
|
|
9367
|
+
if (nextInputValue !== inputValue) {
|
|
9368
|
+
setInputValue(nextInputValue);
|
|
9369
|
+
}
|
|
9370
|
+
});
|
|
9371
|
+
DrawerRoot.useIsoLayoutEffect(function setDynamicInputModeForIOS() {
|
|
9372
|
+
if (!DrawerRoot.isIOS) {
|
|
9373
|
+
return;
|
|
9374
|
+
}
|
|
9375
|
+
|
|
9376
|
+
// iOS numeric software keyboard doesn't have a minus key, so we need to use the default
|
|
9377
|
+
// keyboard to let the user input a negative number.
|
|
9378
|
+
let computedInputMode = 'text';
|
|
9379
|
+
if (minWithDefault >= 0) {
|
|
9380
|
+
// iOS numeric software keyboard doesn't have a decimal key for "numeric" input mode, but
|
|
9381
|
+
// this is better than the "text" input if possible to use.
|
|
9382
|
+
computedInputMode = 'decimal';
|
|
9383
|
+
}
|
|
9384
|
+
setInputMode(computedInputMode);
|
|
9385
|
+
}, [minWithDefault, formatStyle]);
|
|
9386
|
+
React__namespace.useEffect(() => {
|
|
9387
|
+
return () => stopAutoChange();
|
|
9388
|
+
}, [stopAutoChange]);
|
|
9389
|
+
|
|
9390
|
+
// The `onWheel` prop can't be prevented, so we need to use a global event listener.
|
|
9391
|
+
React__namespace.useEffect(function registerElementWheelListener() {
|
|
9392
|
+
const element = inputRef.current;
|
|
9393
|
+
if (disabled || readOnly || !allowWheelScrub || !element) {
|
|
9394
|
+
return undefined;
|
|
9395
|
+
}
|
|
9396
|
+
function handleWheel(event) {
|
|
9397
|
+
if (
|
|
9398
|
+
// Allow pinch-zooming.
|
|
9399
|
+
event.ctrlKey || DrawerRoot.ownerDocument(inputRef.current).activeElement !== inputRef.current) {
|
|
9400
|
+
return;
|
|
9401
|
+
}
|
|
9402
|
+
|
|
9403
|
+
// Prevent the default behavior to avoid scrolling the page.
|
|
9404
|
+
event.preventDefault();
|
|
9405
|
+
const amount = getStepAmount(event) ?? DEFAULT_STEP;
|
|
9406
|
+
incrementValue(amount, {
|
|
9407
|
+
direction: event.deltaY > 0 ? -1 : 1,
|
|
9408
|
+
event,
|
|
9409
|
+
reason: 'wheel'
|
|
9410
|
+
});
|
|
9411
|
+
}
|
|
9412
|
+
element.addEventListener('wheel', handleWheel);
|
|
9413
|
+
return () => {
|
|
9414
|
+
element.removeEventListener('wheel', handleWheel);
|
|
9415
|
+
};
|
|
9416
|
+
}, [allowWheelScrub, incrementValue, disabled, readOnly, largeStep, step, getStepAmount]);
|
|
9417
|
+
const state = React__namespace.useMemo(() => ({
|
|
9418
|
+
...fieldState,
|
|
9419
|
+
disabled,
|
|
9420
|
+
readOnly,
|
|
9421
|
+
required,
|
|
9422
|
+
value,
|
|
9423
|
+
inputValue,
|
|
9424
|
+
scrubbing: isScrubbing
|
|
9425
|
+
}), [fieldState, disabled, readOnly, required, value, inputValue, isScrubbing]);
|
|
9426
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
9427
|
+
inputRef,
|
|
9428
|
+
inputValue,
|
|
9429
|
+
value,
|
|
9430
|
+
startAutoChange,
|
|
9431
|
+
stopAutoChange,
|
|
9432
|
+
minWithDefault,
|
|
9433
|
+
maxWithDefault,
|
|
9434
|
+
disabled,
|
|
9435
|
+
readOnly,
|
|
9436
|
+
id,
|
|
9437
|
+
setValue,
|
|
9438
|
+
incrementValue,
|
|
9439
|
+
getStepAmount,
|
|
9440
|
+
allowInputSyncRef,
|
|
9441
|
+
formatOptionsRef,
|
|
9442
|
+
valueRef,
|
|
9443
|
+
lastChangedValueRef,
|
|
9444
|
+
hasPendingCommitRef,
|
|
9445
|
+
isPressedRef,
|
|
9446
|
+
intentionalTouchCheckTimeout,
|
|
9447
|
+
movesAfterTouchRef,
|
|
9448
|
+
name,
|
|
9449
|
+
required,
|
|
9450
|
+
invalid,
|
|
9451
|
+
inputMode,
|
|
9452
|
+
getAllowedNonNumericKeys,
|
|
9453
|
+
min,
|
|
9454
|
+
max,
|
|
9455
|
+
setInputValue,
|
|
9456
|
+
locale,
|
|
9457
|
+
isScrubbing,
|
|
9458
|
+
setIsScrubbing,
|
|
9459
|
+
state,
|
|
9460
|
+
onValueCommitted
|
|
9461
|
+
}), [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]);
|
|
9462
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
9463
|
+
ref: forwardedRef,
|
|
9464
|
+
state,
|
|
9465
|
+
props: elementProps,
|
|
9466
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
9467
|
+
});
|
|
9468
|
+
return /*#__PURE__*/jsxRuntime.jsxs(NumberFieldRootContext.Provider, {
|
|
9469
|
+
value: contextValue,
|
|
9470
|
+
children: [element, /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
9471
|
+
...validation.getInputValidationProps({
|
|
9472
|
+
onFocus() {
|
|
9473
|
+
inputRef.current?.focus();
|
|
9474
|
+
},
|
|
9475
|
+
onChange(event) {
|
|
9476
|
+
// Workaround for https://github.com/facebook/react/issues/9023
|
|
9477
|
+
if (event.nativeEvent.defaultPrevented) {
|
|
9478
|
+
return;
|
|
9479
|
+
}
|
|
9480
|
+
|
|
9481
|
+
// Handle browser autofill.
|
|
9482
|
+
const nextValue = event.currentTarget.valueAsNumber;
|
|
9483
|
+
const parsedValue = Number.isNaN(nextValue) ? null : nextValue;
|
|
9484
|
+
const details = DrawerRoot.createChangeEventDetails(DrawerRoot.none, event.nativeEvent);
|
|
9485
|
+
setDirty(parsedValue !== validityData.initialValue);
|
|
9486
|
+
setValue(parsedValue, details);
|
|
9487
|
+
if (shouldValidateOnChange()) {
|
|
9488
|
+
validation.commit(parsedValue);
|
|
9489
|
+
}
|
|
9490
|
+
}
|
|
9491
|
+
}),
|
|
9492
|
+
ref: hiddenInputRef,
|
|
9493
|
+
type: "number",
|
|
9494
|
+
name: name,
|
|
9495
|
+
value: value ?? '',
|
|
9496
|
+
min: min,
|
|
9497
|
+
max: max
|
|
9498
|
+
// stepMismatch validation is broken unless an explicit `min` is added.
|
|
9499
|
+
// See https://github.com/facebook/react/issues/12334.
|
|
9500
|
+
,
|
|
9501
|
+
step: stepProp,
|
|
9502
|
+
disabled: disabled,
|
|
9503
|
+
required: required,
|
|
9504
|
+
"aria-hidden": true,
|
|
9505
|
+
tabIndex: -1,
|
|
9506
|
+
style: name ? DrawerRoot.visuallyHiddenInput : DrawerRoot.visuallyHidden
|
|
9507
|
+
})]
|
|
9508
|
+
});
|
|
9509
|
+
});
|
|
9510
|
+
if (process.env.NODE_ENV !== "production") NumberFieldRoot.displayName = "NumberFieldRoot";
|
|
9511
|
+
function getControlledInputValue(value, locale, format) {
|
|
9512
|
+
const explicitPrecision = format?.maximumFractionDigits != null || format?.minimumFractionDigits != null;
|
|
9513
|
+
return explicitPrecision ? formatNumber(value, locale, format) : formatNumberMaxPrecision(value, locale, format);
|
|
9514
|
+
}
|
|
9515
|
+
|
|
9516
|
+
/**
|
|
9517
|
+
* Groups the input with the increment and decrement buttons.
|
|
9518
|
+
* Renders a `<div>` element.
|
|
9519
|
+
*
|
|
9520
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
9521
|
+
*/
|
|
9522
|
+
const NumberFieldGroup = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldGroup(componentProps, forwardedRef) {
|
|
9523
|
+
const {
|
|
9524
|
+
render,
|
|
9525
|
+
className,
|
|
9526
|
+
...elementProps
|
|
9527
|
+
} = componentProps;
|
|
9528
|
+
const {
|
|
9529
|
+
state
|
|
9530
|
+
} = useNumberFieldRootContext();
|
|
9531
|
+
const element = useRenderElement.useRenderElement('div', componentProps, {
|
|
9532
|
+
ref: forwardedRef,
|
|
9533
|
+
state,
|
|
9534
|
+
props: [{
|
|
9535
|
+
role: 'group'
|
|
9536
|
+
}, elementProps],
|
|
9537
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
9538
|
+
});
|
|
9539
|
+
return element;
|
|
9540
|
+
});
|
|
9541
|
+
if (process.env.NODE_ENV !== "production") NumberFieldGroup.displayName = "NumberFieldGroup";
|
|
9542
|
+
|
|
9543
|
+
// Treat pen as touch-like to avoid forcing the software keyboard on stylus taps.
|
|
9544
|
+
// Linux Chrome may emit "pen" historically for mouse usage due to a bug, but the touch path
|
|
9545
|
+
// still works with minor behavioral differences.
|
|
9546
|
+
function isTouchLikePointerType(pointerType) {
|
|
9547
|
+
return pointerType === 'touch' || pointerType === 'pen';
|
|
9548
|
+
}
|
|
9549
|
+
function useNumberFieldButton(params) {
|
|
9550
|
+
const {
|
|
9551
|
+
allowInputSyncRef,
|
|
9552
|
+
disabled,
|
|
9553
|
+
formatOptionsRef,
|
|
9554
|
+
getStepAmount,
|
|
9555
|
+
id,
|
|
9556
|
+
incrementValue,
|
|
9557
|
+
inputRef,
|
|
9558
|
+
inputValue,
|
|
9559
|
+
intentionalTouchCheckTimeout,
|
|
9560
|
+
isIncrement,
|
|
9561
|
+
isPressedRef,
|
|
9562
|
+
locale,
|
|
9563
|
+
movesAfterTouchRef,
|
|
9564
|
+
readOnly,
|
|
9565
|
+
setValue,
|
|
9566
|
+
startAutoChange,
|
|
9567
|
+
stopAutoChange,
|
|
9568
|
+
valueRef,
|
|
9569
|
+
lastChangedValueRef,
|
|
9570
|
+
onValueCommitted
|
|
9571
|
+
} = params;
|
|
9572
|
+
const incrementDownCoordsRef = React__namespace.useRef({
|
|
9573
|
+
x: 0,
|
|
9574
|
+
y: 0
|
|
9575
|
+
});
|
|
9576
|
+
const isTouchingButtonRef = React__namespace.useRef(false);
|
|
9577
|
+
const ignoreClickRef = React__namespace.useRef(false);
|
|
9578
|
+
const pointerTypeRef = React__namespace.useRef('');
|
|
9579
|
+
const pressReason = isIncrement ? 'increment-press' : 'decrement-press';
|
|
9580
|
+
function commitValue(nativeEvent) {
|
|
9581
|
+
allowInputSyncRef.current = true;
|
|
9582
|
+
|
|
9583
|
+
// The input may be dirty but not yet blurred, so the value won't have been committed.
|
|
9584
|
+
const parsedValue = parseNumber(inputValue, locale, formatOptionsRef.current);
|
|
9585
|
+
if (parsedValue !== null) {
|
|
9586
|
+
// The increment value function needs to know the current input value to increment it
|
|
9587
|
+
// correctly.
|
|
9588
|
+
valueRef.current = parsedValue;
|
|
9589
|
+
setValue(parsedValue, DrawerRoot.createChangeEventDetails(pressReason, nativeEvent, undefined, {
|
|
9590
|
+
direction: isIncrement ? 1 : -1
|
|
9591
|
+
}));
|
|
9592
|
+
}
|
|
9593
|
+
}
|
|
9594
|
+
const props = {
|
|
9595
|
+
disabled,
|
|
9596
|
+
'aria-readonly': readOnly || undefined,
|
|
9597
|
+
'aria-label': isIncrement ? 'Increase' : 'Decrease',
|
|
9598
|
+
'aria-controls': id,
|
|
9599
|
+
// Keyboard users shouldn't have access to the buttons, since they can use the input element
|
|
9600
|
+
// to change the value. On the other hand, `aria-hidden` is not applied because touch screen
|
|
9601
|
+
// readers should be able to use the buttons.
|
|
9602
|
+
tabIndex: -1,
|
|
9603
|
+
style: {
|
|
9604
|
+
WebkitUserSelect: 'none',
|
|
9605
|
+
userSelect: 'none'
|
|
9606
|
+
},
|
|
9607
|
+
onTouchStart() {
|
|
9608
|
+
isTouchingButtonRef.current = true;
|
|
9609
|
+
},
|
|
9610
|
+
onTouchEnd() {
|
|
9611
|
+
isTouchingButtonRef.current = false;
|
|
9612
|
+
},
|
|
9613
|
+
onClick(event) {
|
|
9614
|
+
const isDisabled = disabled || readOnly;
|
|
9615
|
+
if (event.defaultPrevented || isDisabled || (
|
|
9616
|
+
// If it's not a keyboard/virtual click, ignore.
|
|
9617
|
+
isTouchLikePointerType(pointerTypeRef.current) ? ignoreClickRef.current : event.detail !== 0)) {
|
|
9618
|
+
return;
|
|
9619
|
+
}
|
|
9620
|
+
commitValue(event.nativeEvent);
|
|
9621
|
+
const amount = getStepAmount(event) ?? DEFAULT_STEP;
|
|
9622
|
+
const prev = valueRef.current;
|
|
9623
|
+
incrementValue(amount, {
|
|
9624
|
+
direction: isIncrement ? 1 : -1,
|
|
9625
|
+
event: event.nativeEvent,
|
|
9626
|
+
reason: pressReason
|
|
9627
|
+
});
|
|
9628
|
+
const committed = lastChangedValueRef.current ?? valueRef.current;
|
|
9629
|
+
if (committed !== prev) {
|
|
9630
|
+
onValueCommitted(committed, DrawerRoot.createGenericEventDetails(pressReason, event.nativeEvent));
|
|
9631
|
+
}
|
|
9632
|
+
},
|
|
9633
|
+
onPointerDown(event) {
|
|
9634
|
+
const isMainButton = !event.button || event.button === 0;
|
|
9635
|
+
if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
|
|
9636
|
+
return;
|
|
9637
|
+
}
|
|
9638
|
+
pointerTypeRef.current = event.pointerType;
|
|
9639
|
+
ignoreClickRef.current = false;
|
|
9640
|
+
isPressedRef.current = true;
|
|
9641
|
+
incrementDownCoordsRef.current = {
|
|
9642
|
+
x: event.clientX,
|
|
9643
|
+
y: event.clientY
|
|
9644
|
+
};
|
|
9645
|
+
commitValue(event.nativeEvent);
|
|
9646
|
+
const isTouchPointer = isTouchLikePointerType(event.pointerType);
|
|
9647
|
+
if (!isTouchPointer) {
|
|
9648
|
+
event.preventDefault();
|
|
9649
|
+
inputRef.current?.focus();
|
|
9650
|
+
startAutoChange(isIncrement, event);
|
|
9651
|
+
} else {
|
|
9652
|
+
// We need to check if the pointerdown was intentional, and not the result of a scroll
|
|
9653
|
+
// or pinch-zoom. In that case, we don't want to change the value.
|
|
9654
|
+
intentionalTouchCheckTimeout.start(TOUCH_TIMEOUT, () => {
|
|
9655
|
+
const moves = movesAfterTouchRef.current;
|
|
9656
|
+
movesAfterTouchRef.current = 0;
|
|
9657
|
+
// Only start auto-change if the touch is still pressed (prevents races
|
|
9658
|
+
// with pointerup occurring before the timeout fires on quick taps).
|
|
9659
|
+
const stillPressed = isPressedRef.current;
|
|
9660
|
+
if (stillPressed && moves != null && moves < MAX_POINTER_MOVES_AFTER_TOUCH) {
|
|
9661
|
+
startAutoChange(isIncrement, event);
|
|
9662
|
+
ignoreClickRef.current = true; // synthesized click should be ignored
|
|
9663
|
+
} else {
|
|
9664
|
+
// No auto-change (simple tap or scroll gesture), allow the click handler
|
|
9665
|
+
// to perform a single increment and commit.
|
|
9666
|
+
ignoreClickRef.current = false;
|
|
9667
|
+
stopAutoChange();
|
|
9668
|
+
}
|
|
9669
|
+
});
|
|
9670
|
+
}
|
|
9671
|
+
},
|
|
9672
|
+
onPointerUp(event) {
|
|
9673
|
+
// Ensure we mark the press as released for touch flows even if auto-change never started,
|
|
9674
|
+
// so the delayed auto-change check won’t start after a quick tap.
|
|
9675
|
+
if (isTouchLikePointerType(event.pointerType)) {
|
|
9676
|
+
isPressedRef.current = false;
|
|
9677
|
+
}
|
|
9678
|
+
},
|
|
9679
|
+
onPointerMove(event) {
|
|
9680
|
+
const isDisabled = disabled || readOnly;
|
|
9681
|
+
if (isDisabled || !isTouchLikePointerType(event.pointerType) || !isPressedRef.current) {
|
|
9682
|
+
return;
|
|
9683
|
+
}
|
|
9684
|
+
if (movesAfterTouchRef.current != null) {
|
|
9685
|
+
movesAfterTouchRef.current += 1;
|
|
9686
|
+
}
|
|
9687
|
+
const {
|
|
9688
|
+
x,
|
|
9689
|
+
y
|
|
9690
|
+
} = incrementDownCoordsRef.current;
|
|
9691
|
+
const dx = x - event.clientX;
|
|
9692
|
+
const dy = y - event.clientY;
|
|
9693
|
+
|
|
9694
|
+
// An alternative to this technique is to detect when the NumberField's parent container
|
|
9695
|
+
// has been scrolled
|
|
9696
|
+
if (dx ** 2 + dy ** 2 > SCROLLING_POINTER_MOVE_DISTANCE ** 2) {
|
|
9697
|
+
stopAutoChange();
|
|
9698
|
+
}
|
|
9699
|
+
},
|
|
9700
|
+
onMouseEnter(event) {
|
|
9701
|
+
const isDisabled = disabled || readOnly;
|
|
9702
|
+
if (event.defaultPrevented || isDisabled || !isPressedRef.current || isTouchingButtonRef.current || isTouchLikePointerType(pointerTypeRef.current)) {
|
|
9703
|
+
return;
|
|
9704
|
+
}
|
|
9705
|
+
startAutoChange(isIncrement, event);
|
|
9706
|
+
},
|
|
9707
|
+
onMouseLeave() {
|
|
9708
|
+
if (isTouchingButtonRef.current) {
|
|
9709
|
+
return;
|
|
9710
|
+
}
|
|
9711
|
+
stopAutoChange();
|
|
9712
|
+
},
|
|
9713
|
+
onMouseUp() {
|
|
9714
|
+
if (isTouchingButtonRef.current) {
|
|
9715
|
+
return;
|
|
9716
|
+
}
|
|
9717
|
+
stopAutoChange();
|
|
9718
|
+
}
|
|
9719
|
+
};
|
|
9720
|
+
return props;
|
|
9721
|
+
}
|
|
9722
|
+
|
|
9723
|
+
/**
|
|
9724
|
+
* A stepper button that increases the field value when clicked.
|
|
9725
|
+
* Renders an `<button>` element.
|
|
9726
|
+
*
|
|
9727
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
9728
|
+
*/
|
|
9729
|
+
const NumberFieldIncrement = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldIncrement(componentProps, forwardedRef) {
|
|
9730
|
+
const {
|
|
9731
|
+
render,
|
|
9732
|
+
className,
|
|
9733
|
+
disabled: disabledProp = false,
|
|
9734
|
+
nativeButton = true,
|
|
9735
|
+
...elementProps
|
|
9736
|
+
} = componentProps;
|
|
9737
|
+
const {
|
|
9738
|
+
allowInputSyncRef,
|
|
9739
|
+
disabled: contextDisabled,
|
|
9740
|
+
formatOptionsRef,
|
|
9741
|
+
getStepAmount,
|
|
9742
|
+
id,
|
|
9743
|
+
incrementValue,
|
|
9744
|
+
inputRef,
|
|
9745
|
+
inputValue,
|
|
9746
|
+
intentionalTouchCheckTimeout,
|
|
9747
|
+
isPressedRef,
|
|
9748
|
+
locale,
|
|
9749
|
+
maxWithDefault,
|
|
9750
|
+
movesAfterTouchRef,
|
|
9751
|
+
readOnly,
|
|
9752
|
+
setValue,
|
|
9753
|
+
startAutoChange,
|
|
9754
|
+
state,
|
|
9755
|
+
stopAutoChange,
|
|
9756
|
+
value,
|
|
9757
|
+
valueRef,
|
|
9758
|
+
lastChangedValueRef,
|
|
9759
|
+
onValueCommitted
|
|
9760
|
+
} = useNumberFieldRootContext();
|
|
9761
|
+
const isMax = value != null && value >= maxWithDefault;
|
|
9762
|
+
const disabled = disabledProp || contextDisabled || isMax;
|
|
9763
|
+
const props = useNumberFieldButton({
|
|
9764
|
+
isIncrement: true,
|
|
9765
|
+
inputRef,
|
|
9766
|
+
startAutoChange,
|
|
9767
|
+
stopAutoChange,
|
|
9768
|
+
inputValue,
|
|
9769
|
+
disabled,
|
|
9770
|
+
readOnly,
|
|
9771
|
+
id,
|
|
9772
|
+
setValue,
|
|
9773
|
+
getStepAmount,
|
|
9774
|
+
incrementValue,
|
|
9775
|
+
allowInputSyncRef,
|
|
9776
|
+
formatOptionsRef,
|
|
9777
|
+
valueRef,
|
|
9778
|
+
isPressedRef,
|
|
9779
|
+
intentionalTouchCheckTimeout,
|
|
9780
|
+
movesAfterTouchRef,
|
|
9781
|
+
locale,
|
|
9782
|
+
lastChangedValueRef,
|
|
9783
|
+
onValueCommitted
|
|
9784
|
+
});
|
|
9785
|
+
const {
|
|
9786
|
+
getButtonProps,
|
|
9787
|
+
buttonRef
|
|
9788
|
+
} = useButton({
|
|
9789
|
+
disabled,
|
|
9790
|
+
native: nativeButton,
|
|
9791
|
+
focusableWhenDisabled: true
|
|
9792
|
+
});
|
|
9793
|
+
const buttonState = React__namespace.useMemo(() => ({
|
|
9794
|
+
...state,
|
|
9795
|
+
disabled
|
|
9796
|
+
}), [state, disabled]);
|
|
9797
|
+
const element = useRenderElement.useRenderElement('button', componentProps, {
|
|
9798
|
+
ref: [forwardedRef, buttonRef],
|
|
9799
|
+
state: buttonState,
|
|
9800
|
+
props: [props, elementProps, getButtonProps],
|
|
9801
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
9802
|
+
});
|
|
9803
|
+
return element;
|
|
9804
|
+
});
|
|
9805
|
+
if (process.env.NODE_ENV !== "production") NumberFieldIncrement.displayName = "NumberFieldIncrement";
|
|
9806
|
+
|
|
9807
|
+
/**
|
|
9808
|
+
* A stepper button that decreases the field value when clicked.
|
|
9809
|
+
* Renders an `<button>` element.
|
|
9810
|
+
*
|
|
9811
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
9812
|
+
*/
|
|
9813
|
+
const NumberFieldDecrement = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldDecrement(componentProps, forwardedRef) {
|
|
9814
|
+
const {
|
|
9815
|
+
render,
|
|
9816
|
+
className,
|
|
9817
|
+
disabled: disabledProp = false,
|
|
9818
|
+
nativeButton = true,
|
|
9819
|
+
...elementProps
|
|
9820
|
+
} = componentProps;
|
|
9821
|
+
const {
|
|
9822
|
+
allowInputSyncRef,
|
|
9823
|
+
disabled: contextDisabled,
|
|
9824
|
+
formatOptionsRef,
|
|
9825
|
+
getStepAmount,
|
|
9826
|
+
id,
|
|
9827
|
+
incrementValue,
|
|
9828
|
+
inputRef,
|
|
9829
|
+
inputValue,
|
|
9830
|
+
intentionalTouchCheckTimeout,
|
|
9831
|
+
isPressedRef,
|
|
9832
|
+
minWithDefault,
|
|
9833
|
+
movesAfterTouchRef,
|
|
9834
|
+
readOnly,
|
|
9835
|
+
setValue,
|
|
9836
|
+
startAutoChange,
|
|
9837
|
+
state,
|
|
9838
|
+
stopAutoChange,
|
|
9839
|
+
value,
|
|
9840
|
+
valueRef,
|
|
9841
|
+
locale,
|
|
9842
|
+
lastChangedValueRef,
|
|
9843
|
+
onValueCommitted
|
|
9844
|
+
} = useNumberFieldRootContext();
|
|
9845
|
+
const isMin = value != null && value <= minWithDefault;
|
|
9846
|
+
const disabled = disabledProp || contextDisabled || isMin;
|
|
9847
|
+
const props = useNumberFieldButton({
|
|
9848
|
+
isIncrement: false,
|
|
9849
|
+
inputRef,
|
|
9850
|
+
startAutoChange,
|
|
9851
|
+
stopAutoChange,
|
|
9852
|
+
inputValue,
|
|
9853
|
+
disabled,
|
|
9854
|
+
readOnly,
|
|
9855
|
+
id,
|
|
9856
|
+
setValue,
|
|
9857
|
+
getStepAmount,
|
|
9858
|
+
incrementValue,
|
|
9859
|
+
allowInputSyncRef,
|
|
9860
|
+
formatOptionsRef,
|
|
9861
|
+
valueRef,
|
|
9862
|
+
isPressedRef,
|
|
9863
|
+
intentionalTouchCheckTimeout,
|
|
9864
|
+
movesAfterTouchRef,
|
|
9865
|
+
locale,
|
|
9866
|
+
lastChangedValueRef,
|
|
9867
|
+
onValueCommitted
|
|
9868
|
+
});
|
|
9869
|
+
const {
|
|
9870
|
+
getButtonProps,
|
|
9871
|
+
buttonRef
|
|
9872
|
+
} = useButton({
|
|
9873
|
+
disabled,
|
|
9874
|
+
native: nativeButton,
|
|
9875
|
+
focusableWhenDisabled: true
|
|
9876
|
+
});
|
|
9877
|
+
const buttonState = React__namespace.useMemo(() => ({
|
|
9878
|
+
...state,
|
|
9879
|
+
disabled
|
|
9880
|
+
}), [state, disabled]);
|
|
9881
|
+
const element = useRenderElement.useRenderElement('button', componentProps, {
|
|
9882
|
+
ref: [forwardedRef, buttonRef],
|
|
9883
|
+
state: buttonState,
|
|
9884
|
+
props: [props, elementProps, getButtonProps],
|
|
9885
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
9886
|
+
});
|
|
9887
|
+
return element;
|
|
9888
|
+
});
|
|
9889
|
+
if (process.env.NODE_ENV !== "production") NumberFieldDecrement.displayName = "NumberFieldDecrement";
|
|
9890
|
+
|
|
9891
|
+
const stateAttributesMapping = {
|
|
9892
|
+
...fieldValidityMapping,
|
|
9893
|
+
...stateAttributesMapping$1
|
|
9894
|
+
};
|
|
9895
|
+
const NAVIGATE_KEYS = new Set(['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter', 'Escape']);
|
|
9896
|
+
|
|
9897
|
+
/**
|
|
9898
|
+
* The native input control in the number field.
|
|
9899
|
+
* Renders an `<input>` element.
|
|
9900
|
+
*
|
|
9901
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
9902
|
+
*/
|
|
9903
|
+
const NumberFieldInput = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldInput(componentProps, forwardedRef) {
|
|
9904
|
+
const {
|
|
9905
|
+
render,
|
|
9906
|
+
className,
|
|
9907
|
+
...elementProps
|
|
9908
|
+
} = componentProps;
|
|
9909
|
+
const {
|
|
9910
|
+
allowInputSyncRef,
|
|
9911
|
+
disabled,
|
|
9912
|
+
formatOptionsRef,
|
|
9913
|
+
getAllowedNonNumericKeys,
|
|
9914
|
+
getStepAmount,
|
|
9915
|
+
id,
|
|
9916
|
+
incrementValue,
|
|
9917
|
+
inputMode,
|
|
9918
|
+
inputValue,
|
|
9919
|
+
max,
|
|
9920
|
+
min,
|
|
9921
|
+
name,
|
|
9922
|
+
readOnly,
|
|
9923
|
+
required,
|
|
9924
|
+
setValue,
|
|
9925
|
+
state,
|
|
9926
|
+
setInputValue,
|
|
9927
|
+
locale,
|
|
9928
|
+
inputRef,
|
|
9929
|
+
value,
|
|
9930
|
+
onValueCommitted,
|
|
9931
|
+
lastChangedValueRef,
|
|
9932
|
+
hasPendingCommitRef,
|
|
9933
|
+
valueRef
|
|
9934
|
+
} = useNumberFieldRootContext();
|
|
9935
|
+
const {
|
|
9936
|
+
clearErrors
|
|
9937
|
+
} = useFormContext();
|
|
9938
|
+
const {
|
|
9939
|
+
validationMode,
|
|
9940
|
+
setTouched,
|
|
9941
|
+
setFocused,
|
|
9942
|
+
invalid,
|
|
9943
|
+
shouldValidateOnChange,
|
|
9944
|
+
validation
|
|
9945
|
+
} = useFieldRootContext();
|
|
9946
|
+
const {
|
|
9947
|
+
labelId
|
|
9948
|
+
} = useLabelableContext();
|
|
9949
|
+
const hasTouchedInputRef = React__namespace.useRef(false);
|
|
9950
|
+
const blockRevalidationRef = React__namespace.useRef(false);
|
|
9951
|
+
useField({
|
|
9952
|
+
id,
|
|
9953
|
+
commit: validation.commit,
|
|
9954
|
+
value,
|
|
9955
|
+
controlRef: inputRef,
|
|
9956
|
+
name,
|
|
9957
|
+
getValue: () => value ?? null
|
|
9958
|
+
});
|
|
9959
|
+
DrawerRoot.useValueChanged(value, previousValue => {
|
|
9960
|
+
const validateOnChange = shouldValidateOnChange();
|
|
9961
|
+
clearErrors(name);
|
|
9962
|
+
if (validateOnChange) {
|
|
9963
|
+
validation.commit(value);
|
|
9964
|
+
}
|
|
9965
|
+
if (previousValue === value || validateOnChange) {
|
|
9966
|
+
return;
|
|
9967
|
+
}
|
|
9968
|
+
if (blockRevalidationRef.current) {
|
|
9969
|
+
blockRevalidationRef.current = false;
|
|
9970
|
+
return;
|
|
9971
|
+
}
|
|
9972
|
+
validation.commit(value, true);
|
|
9973
|
+
});
|
|
9974
|
+
const inputProps = {
|
|
9975
|
+
id,
|
|
9976
|
+
required,
|
|
9977
|
+
disabled,
|
|
9978
|
+
readOnly,
|
|
9979
|
+
inputMode,
|
|
9980
|
+
value: inputValue,
|
|
9981
|
+
type: 'text',
|
|
9982
|
+
autoComplete: 'off',
|
|
9983
|
+
autoCorrect: 'off',
|
|
9984
|
+
spellCheck: 'false',
|
|
9985
|
+
'aria-roledescription': 'Number field',
|
|
9986
|
+
'aria-invalid': invalid || undefined,
|
|
9987
|
+
'aria-labelledby': labelId,
|
|
9988
|
+
// If the server's locale does not match the client's locale, the formatting may not match,
|
|
9989
|
+
// causing a hydration mismatch.
|
|
9990
|
+
suppressHydrationWarning: true,
|
|
9991
|
+
onFocus(event) {
|
|
9992
|
+
if (event.defaultPrevented || readOnly || disabled) {
|
|
9993
|
+
return;
|
|
9994
|
+
}
|
|
9995
|
+
setFocused(true);
|
|
9996
|
+
if (hasTouchedInputRef.current) {
|
|
9997
|
+
return;
|
|
9998
|
+
}
|
|
9999
|
+
hasTouchedInputRef.current = true;
|
|
10000
|
+
|
|
10001
|
+
// Browsers set selection at the start of the input field by default. We want to set it at
|
|
10002
|
+
// the end for the first focus.
|
|
10003
|
+
const target = event.currentTarget;
|
|
10004
|
+
const length = target.value.length;
|
|
10005
|
+
target.setSelectionRange(length, length);
|
|
10006
|
+
},
|
|
10007
|
+
onBlur(event) {
|
|
10008
|
+
if (event.defaultPrevented || readOnly || disabled) {
|
|
10009
|
+
return;
|
|
10010
|
+
}
|
|
10011
|
+
setTouched(true);
|
|
10012
|
+
setFocused(false);
|
|
10013
|
+
const hadManualInput = !allowInputSyncRef.current;
|
|
10014
|
+
const hadPendingProgrammaticChange = hasPendingCommitRef.current;
|
|
10015
|
+
allowInputSyncRef.current = true;
|
|
10016
|
+
if (inputValue.trim() === '') {
|
|
10017
|
+
setValue(null, DrawerRoot.createChangeEventDetails(DrawerRoot.inputClear, event.nativeEvent));
|
|
10018
|
+
if (validationMode === 'onBlur') {
|
|
10019
|
+
validation.commit(null);
|
|
10020
|
+
}
|
|
10021
|
+
onValueCommitted(null, DrawerRoot.createGenericEventDetails(DrawerRoot.inputClear, event.nativeEvent));
|
|
10022
|
+
return;
|
|
10023
|
+
}
|
|
10024
|
+
const formatOptions = formatOptionsRef.current;
|
|
10025
|
+
const parsedValue = parseNumber(inputValue, locale, formatOptions);
|
|
10026
|
+
if (parsedValue === null) {
|
|
10027
|
+
return;
|
|
10028
|
+
}
|
|
10029
|
+
|
|
10030
|
+
// If an explicit precision is requested, round the committed numeric value.
|
|
10031
|
+
const hasExplicitPrecision = formatOptions?.maximumFractionDigits != null || formatOptions?.minimumFractionDigits != null;
|
|
10032
|
+
const maxFrac = formatOptions?.maximumFractionDigits;
|
|
10033
|
+
const committed = hasExplicitPrecision && typeof maxFrac === 'number' ? Number(parsedValue.toFixed(maxFrac)) : parsedValue;
|
|
10034
|
+
const nextEventDetails = DrawerRoot.createGenericEventDetails(DrawerRoot.inputBlur, event.nativeEvent);
|
|
10035
|
+
const shouldUpdateValue = value !== committed;
|
|
10036
|
+
const shouldCommit = hadManualInput || shouldUpdateValue || hadPendingProgrammaticChange;
|
|
10037
|
+
if (validationMode === 'onBlur') {
|
|
10038
|
+
validation.commit(committed);
|
|
10039
|
+
}
|
|
10040
|
+
if (shouldUpdateValue) {
|
|
10041
|
+
blockRevalidationRef.current = true;
|
|
10042
|
+
setValue(committed, DrawerRoot.createChangeEventDetails(DrawerRoot.inputBlur, event.nativeEvent));
|
|
10043
|
+
}
|
|
10044
|
+
if (shouldCommit) {
|
|
10045
|
+
onValueCommitted(committed, nextEventDetails);
|
|
10046
|
+
}
|
|
10047
|
+
|
|
10048
|
+
// Normalize only the displayed text
|
|
10049
|
+
const canonicalText = formatNumber(committed, locale, formatOptions);
|
|
10050
|
+
const maxPrecisionText = formatNumberMaxPrecision(parsedValue, locale, formatOptions);
|
|
10051
|
+
const shouldPreserveFullPrecision = !hasExplicitPrecision && parsedValue === value && inputValue === maxPrecisionText;
|
|
10052
|
+
if (!shouldPreserveFullPrecision && inputValue !== canonicalText) {
|
|
10053
|
+
setInputValue(canonicalText);
|
|
10054
|
+
}
|
|
10055
|
+
},
|
|
10056
|
+
onChange(event) {
|
|
10057
|
+
// Workaround for https://github.com/facebook/react/issues/9023
|
|
10058
|
+
if (event.nativeEvent.defaultPrevented) {
|
|
10059
|
+
return;
|
|
10060
|
+
}
|
|
10061
|
+
allowInputSyncRef.current = false;
|
|
10062
|
+
const targetValue = event.target.value;
|
|
10063
|
+
if (targetValue.trim() === '') {
|
|
10064
|
+
setInputValue(targetValue);
|
|
10065
|
+
setValue(null, DrawerRoot.createChangeEventDetails(DrawerRoot.inputClear, event.nativeEvent));
|
|
10066
|
+
return;
|
|
10067
|
+
}
|
|
10068
|
+
|
|
10069
|
+
// Update the input text immediately and only fire onValueChange if the typed value is
|
|
10070
|
+
// currently parseable into a number. This preserves good UX for IME
|
|
10071
|
+
// composition/partial input while still providing live numeric updates when possible.
|
|
10072
|
+
const allowedNonNumericKeys = getAllowedNonNumericKeys();
|
|
10073
|
+
const isValidCharacterString = Array.from(targetValue).every(ch => {
|
|
10074
|
+
const isAsciiDigit = ch >= '0' && ch <= '9';
|
|
10075
|
+
const isArabicNumeral = ARABIC_DETECT_RE.test(ch);
|
|
10076
|
+
const isHanNumeral = HAN_DETECT_RE.test(ch);
|
|
10077
|
+
const isPersianNumeral = PERSIAN_DETECT_RE.test(ch);
|
|
10078
|
+
const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(ch);
|
|
10079
|
+
const isMinus = ANY_MINUS_DETECT_RE.test(ch);
|
|
10080
|
+
return isAsciiDigit || isArabicNumeral || isHanNumeral || isPersianNumeral || isFullwidthNumeral || isMinus || allowedNonNumericKeys.has(ch);
|
|
10081
|
+
});
|
|
10082
|
+
if (!isValidCharacterString) {
|
|
10083
|
+
return;
|
|
10084
|
+
}
|
|
10085
|
+
const parsedValue = parseNumber(targetValue, locale, formatOptionsRef.current);
|
|
10086
|
+
setInputValue(targetValue);
|
|
10087
|
+
if (parsedValue !== null) {
|
|
10088
|
+
setValue(parsedValue, DrawerRoot.createChangeEventDetails(DrawerRoot.inputChange, event.nativeEvent));
|
|
10089
|
+
}
|
|
10090
|
+
},
|
|
10091
|
+
onKeyDown(event) {
|
|
10092
|
+
if (event.defaultPrevented || readOnly || disabled) {
|
|
10093
|
+
return;
|
|
10094
|
+
}
|
|
10095
|
+
const nativeEvent = event.nativeEvent;
|
|
10096
|
+
allowInputSyncRef.current = true;
|
|
10097
|
+
const allowedNonNumericKeys = getAllowedNonNumericKeys();
|
|
10098
|
+
let isAllowedNonNumericKey = allowedNonNumericKeys.has(event.key);
|
|
10099
|
+
const {
|
|
10100
|
+
decimal,
|
|
10101
|
+
currency,
|
|
10102
|
+
percentSign
|
|
10103
|
+
} = getNumberLocaleDetails(locale, formatOptionsRef.current);
|
|
10104
|
+
const selectionStart = event.currentTarget.selectionStart;
|
|
10105
|
+
const selectionEnd = event.currentTarget.selectionEnd;
|
|
10106
|
+
const isAllSelected = selectionStart === 0 && selectionEnd === inputValue.length;
|
|
10107
|
+
|
|
10108
|
+
// Normalize handling of plus/minus signs via precomputed regexes
|
|
10109
|
+
const selectionContainsIndex = index => selectionStart != null && selectionEnd != null && index >= selectionStart && index < selectionEnd;
|
|
10110
|
+
if (ANY_MINUS_DETECT_RE.test(event.key) && Array.from(allowedNonNumericKeys).some(k => ANY_MINUS_DETECT_RE.test(k || ''))) {
|
|
10111
|
+
// Only allow one sign unless replacing the existing one or all text is selected
|
|
10112
|
+
const existingIndex = inputValue.search(ANY_MINUS_RE);
|
|
10113
|
+
const isReplacingExisting = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);
|
|
10114
|
+
isAllowedNonNumericKey = !(ANY_MINUS_DETECT_RE.test(inputValue) || ANY_PLUS_DETECT_RE.test(inputValue)) || isAllSelected || isReplacingExisting;
|
|
10115
|
+
}
|
|
10116
|
+
if (ANY_PLUS_DETECT_RE.test(event.key) && Array.from(allowedNonNumericKeys).some(k => ANY_PLUS_DETECT_RE.test(k || ''))) {
|
|
10117
|
+
const existingIndex = inputValue.search(ANY_PLUS_RE);
|
|
10118
|
+
const isReplacingExisting = existingIndex != null && existingIndex !== -1 && selectionContainsIndex(existingIndex);
|
|
10119
|
+
isAllowedNonNumericKey = !(ANY_MINUS_DETECT_RE.test(inputValue) || ANY_PLUS_DETECT_RE.test(inputValue)) || isAllSelected || isReplacingExisting;
|
|
10120
|
+
}
|
|
10121
|
+
|
|
10122
|
+
// Only allow one of each symbol.
|
|
10123
|
+
[decimal, currency, percentSign].forEach(symbol => {
|
|
10124
|
+
if (event.key === symbol) {
|
|
10125
|
+
const symbolIndex = inputValue.indexOf(symbol);
|
|
10126
|
+
const isSymbolHighlighted = selectionContainsIndex(symbolIndex);
|
|
10127
|
+
isAllowedNonNumericKey = !inputValue.includes(symbol) || isAllSelected || isSymbolHighlighted;
|
|
10128
|
+
}
|
|
10129
|
+
});
|
|
10130
|
+
const isAsciiDigit = event.key >= '0' && event.key <= '9';
|
|
10131
|
+
const isArabicNumeral = ARABIC_DETECT_RE.test(event.key);
|
|
10132
|
+
const isHanNumeral = HAN_DETECT_RE.test(event.key);
|
|
10133
|
+
const isFullwidthNumeral = FULLWIDTH_DETECT_RE.test(event.key);
|
|
10134
|
+
const isNavigateKey = NAVIGATE_KEYS.has(event.key);
|
|
10135
|
+
if (
|
|
10136
|
+
// Allow composition events (e.g., pinyin)
|
|
10137
|
+
// event.nativeEvent.isComposing does not work in Safari:
|
|
10138
|
+
// https://bugs.webkit.org/show_bug.cgi?id=165004
|
|
10139
|
+
event.which === 229 || event.altKey || event.ctrlKey || event.metaKey || isAllowedNonNumericKey || isAsciiDigit || isArabicNumeral || isFullwidthNumeral || isHanNumeral || isNavigateKey) {
|
|
10140
|
+
return;
|
|
10141
|
+
}
|
|
10142
|
+
|
|
10143
|
+
// We need to commit the number at this point if the input hasn't been blurred.
|
|
10144
|
+
const parsedValue = parseNumber(inputValue, locale, formatOptionsRef.current);
|
|
10145
|
+
const amount = getStepAmount(event) ?? DEFAULT_STEP;
|
|
10146
|
+
|
|
10147
|
+
// Prevent insertion of text or caret from moving.
|
|
10148
|
+
DrawerRoot.stopEvent(event);
|
|
10149
|
+
const commitDetails = DrawerRoot.createGenericEventDetails(DrawerRoot.keyboard, nativeEvent);
|
|
10150
|
+
if (event.key === 'ArrowUp') {
|
|
10151
|
+
incrementValue(amount, {
|
|
10152
|
+
direction: 1,
|
|
10153
|
+
currentValue: parsedValue,
|
|
10154
|
+
event: nativeEvent,
|
|
10155
|
+
reason: DrawerRoot.keyboard
|
|
10156
|
+
});
|
|
10157
|
+
onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
|
|
10158
|
+
} else if (event.key === 'ArrowDown') {
|
|
10159
|
+
incrementValue(amount, {
|
|
10160
|
+
direction: -1,
|
|
10161
|
+
currentValue: parsedValue,
|
|
10162
|
+
event: nativeEvent,
|
|
10163
|
+
reason: DrawerRoot.keyboard
|
|
10164
|
+
});
|
|
10165
|
+
onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
|
|
10166
|
+
} else if (event.key === 'Home' && min != null) {
|
|
10167
|
+
setValue(min, DrawerRoot.createChangeEventDetails(DrawerRoot.keyboard, nativeEvent));
|
|
10168
|
+
onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
|
|
10169
|
+
} else if (event.key === 'End' && max != null) {
|
|
10170
|
+
setValue(max, DrawerRoot.createChangeEventDetails(DrawerRoot.keyboard, nativeEvent));
|
|
10171
|
+
onValueCommitted(lastChangedValueRef.current ?? valueRef.current, commitDetails);
|
|
10172
|
+
}
|
|
10173
|
+
},
|
|
10174
|
+
onPaste(event) {
|
|
10175
|
+
if (event.defaultPrevented || readOnly || disabled) {
|
|
10176
|
+
return;
|
|
10177
|
+
}
|
|
10178
|
+
|
|
10179
|
+
// Prevent `onChange` from being called.
|
|
10180
|
+
event.preventDefault();
|
|
10181
|
+
const clipboardData = event.clipboardData || window.Clipboard;
|
|
10182
|
+
const pastedData = clipboardData.getData('text/plain');
|
|
10183
|
+
const parsedValue = parseNumber(pastedData, locale, formatOptionsRef.current);
|
|
10184
|
+
if (parsedValue !== null) {
|
|
10185
|
+
allowInputSyncRef.current = false;
|
|
10186
|
+
setValue(parsedValue, DrawerRoot.createChangeEventDetails(DrawerRoot.inputPaste, event.nativeEvent));
|
|
10187
|
+
setInputValue(pastedData);
|
|
10188
|
+
}
|
|
10189
|
+
}
|
|
10190
|
+
};
|
|
10191
|
+
const element = useRenderElement.useRenderElement('input', componentProps, {
|
|
10192
|
+
ref: [forwardedRef, inputRef],
|
|
10193
|
+
state,
|
|
10194
|
+
props: [inputProps, validation.getValidationProps(), elementProps],
|
|
10195
|
+
stateAttributesMapping
|
|
10196
|
+
});
|
|
10197
|
+
return element;
|
|
10198
|
+
});
|
|
10199
|
+
if (process.env.NODE_ENV !== "production") NumberFieldInput.displayName = "NumberFieldInput";
|
|
10200
|
+
|
|
10201
|
+
const NumberFieldScrubAreaContext = /*#__PURE__*/React__namespace.createContext(undefined);
|
|
10202
|
+
if (process.env.NODE_ENV !== "production") NumberFieldScrubAreaContext.displayName = "NumberFieldScrubAreaContext";
|
|
10203
|
+
function useNumberFieldScrubAreaContext() {
|
|
10204
|
+
const context = React__namespace.useContext(NumberFieldScrubAreaContext);
|
|
10205
|
+
if (context === undefined) {
|
|
10206
|
+
throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: NumberFieldScrubAreaContext is missing. NumberFieldScrubArea parts must be placed within <NumberField.ScrubArea>.' : useRenderElement.formatErrorMessage(44));
|
|
10207
|
+
}
|
|
10208
|
+
return context;
|
|
10209
|
+
}
|
|
10210
|
+
|
|
10211
|
+
// Calculates the viewport rect for the virtual cursor.
|
|
10212
|
+
function getViewportRect(teleportDistance, scrubAreaEl) {
|
|
10213
|
+
const win = index_esm.getWindow(scrubAreaEl);
|
|
10214
|
+
const rect = scrubAreaEl.getBoundingClientRect();
|
|
10215
|
+
if (rect && teleportDistance != null) {
|
|
10216
|
+
return {
|
|
10217
|
+
x: rect.left - teleportDistance / 2,
|
|
10218
|
+
y: rect.top - teleportDistance / 2,
|
|
10219
|
+
width: rect.right + teleportDistance / 2,
|
|
10220
|
+
height: rect.bottom + teleportDistance / 2
|
|
10221
|
+
};
|
|
10222
|
+
}
|
|
10223
|
+
const vV = win.visualViewport;
|
|
10224
|
+
if (vV) {
|
|
10225
|
+
return {
|
|
10226
|
+
x: vV.offsetLeft,
|
|
10227
|
+
y: vV.offsetTop,
|
|
10228
|
+
width: vV.offsetLeft + vV.width,
|
|
10229
|
+
height: vV.offsetTop + vV.height
|
|
10230
|
+
};
|
|
10231
|
+
}
|
|
10232
|
+
return {
|
|
10233
|
+
x: 0,
|
|
10234
|
+
y: 0,
|
|
10235
|
+
width: win.document.documentElement.clientWidth,
|
|
10236
|
+
height: win.document.documentElement.clientHeight
|
|
10237
|
+
};
|
|
10238
|
+
}
|
|
10239
|
+
|
|
10240
|
+
// This lets us invert the scale of the cursor to match the OS scale, in which the cursor doesn't
|
|
10241
|
+
// scale with the content on pinch-zoom.
|
|
10242
|
+
function subscribeToVisualViewportResize(element, visualScaleRef) {
|
|
10243
|
+
const vV = index_esm.getWindow(element).visualViewport;
|
|
10244
|
+
if (!vV) {
|
|
10245
|
+
return () => {};
|
|
10246
|
+
}
|
|
10247
|
+
function handleVisualResize() {
|
|
10248
|
+
if (vV) {
|
|
10249
|
+
visualScaleRef.current = vV.scale;
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
handleVisualResize();
|
|
10253
|
+
vV.addEventListener('resize', handleVisualResize);
|
|
10254
|
+
return () => {
|
|
10255
|
+
vV.removeEventListener('resize', handleVisualResize);
|
|
10256
|
+
};
|
|
10257
|
+
}
|
|
10258
|
+
|
|
10259
|
+
const NumberFieldScrubArea = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldScrubArea(componentProps, forwardedRef) {
|
|
10260
|
+
const {
|
|
10261
|
+
render,
|
|
10262
|
+
className,
|
|
10263
|
+
direction = 'horizontal',
|
|
10264
|
+
pixelSensitivity = 2,
|
|
10265
|
+
teleportDistance,
|
|
10266
|
+
...elementProps
|
|
10267
|
+
} = componentProps;
|
|
10268
|
+
const {
|
|
10269
|
+
state,
|
|
10270
|
+
setIsScrubbing: setRootScrubbing,
|
|
10271
|
+
disabled,
|
|
10272
|
+
readOnly,
|
|
10273
|
+
inputRef,
|
|
10274
|
+
incrementValue,
|
|
10275
|
+
getStepAmount,
|
|
10276
|
+
onValueCommitted,
|
|
10277
|
+
lastChangedValueRef,
|
|
10278
|
+
valueRef
|
|
10279
|
+
} = useNumberFieldRootContext();
|
|
10280
|
+
const scrubAreaRef = React__namespace.useRef(null);
|
|
10281
|
+
const isScrubbingRef = React__namespace.useRef(false);
|
|
10282
|
+
const didMoveRef = React__namespace.useRef(false);
|
|
10283
|
+
const pointerDownTargetRef = React__namespace.useRef(null);
|
|
10284
|
+
const scrubAreaCursorRef = React__namespace.useRef(null);
|
|
10285
|
+
const virtualCursorCoords = React__namespace.useRef({
|
|
10286
|
+
x: 0,
|
|
10287
|
+
y: 0
|
|
10288
|
+
});
|
|
10289
|
+
const visualScaleRef = React__namespace.useRef(1);
|
|
10290
|
+
const exitPointerLockTimeout = DrawerRoot.useTimeout();
|
|
10291
|
+
const [isTouchInput, setIsTouchInput] = React__namespace.useState(false);
|
|
10292
|
+
const [isPointerLockDenied, setIsPointerLockDenied] = React__namespace.useState(false);
|
|
10293
|
+
const [isScrubbing, setIsScrubbing] = React__namespace.useState(false);
|
|
10294
|
+
React__namespace.useEffect(() => {
|
|
10295
|
+
if (!isScrubbing || !scrubAreaCursorRef.current) {
|
|
10296
|
+
return undefined;
|
|
10297
|
+
}
|
|
10298
|
+
return subscribeToVisualViewportResize(scrubAreaCursorRef.current, visualScaleRef);
|
|
10299
|
+
}, [isScrubbing]);
|
|
10300
|
+
function updateCursorTransform(x, y) {
|
|
10301
|
+
if (scrubAreaCursorRef.current) {
|
|
10302
|
+
scrubAreaCursorRef.current.style.transform = `translate3d(${x}px,${y}px,0) scale(${1 / visualScaleRef.current})`;
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
const onScrub = DrawerRoot.useStableCallback(({
|
|
10306
|
+
movementX,
|
|
10307
|
+
movementY
|
|
10308
|
+
}) => {
|
|
10309
|
+
const virtualCursor = scrubAreaCursorRef.current;
|
|
10310
|
+
const scrubAreaEl = scrubAreaRef.current;
|
|
10311
|
+
if (!virtualCursor || !scrubAreaEl) {
|
|
10312
|
+
return;
|
|
10313
|
+
}
|
|
10314
|
+
const rect = getViewportRect(teleportDistance, scrubAreaEl);
|
|
10315
|
+
const coords = virtualCursorCoords.current;
|
|
10316
|
+
const newCoords = {
|
|
10317
|
+
x: Math.round(coords.x + movementX),
|
|
10318
|
+
y: Math.round(coords.y + movementY)
|
|
10319
|
+
};
|
|
10320
|
+
const cursorWidth = virtualCursor.offsetWidth;
|
|
10321
|
+
const cursorHeight = virtualCursor.offsetHeight;
|
|
10322
|
+
if (newCoords.x + cursorWidth / 2 < rect.x) {
|
|
10323
|
+
newCoords.x = rect.width - cursorWidth / 2;
|
|
10324
|
+
} else if (newCoords.x + cursorWidth / 2 > rect.width) {
|
|
10325
|
+
newCoords.x = rect.x - cursorWidth / 2;
|
|
10326
|
+
}
|
|
10327
|
+
if (newCoords.y + cursorHeight / 2 < rect.y) {
|
|
10328
|
+
newCoords.y = rect.height - cursorHeight / 2;
|
|
10329
|
+
} else if (newCoords.y + cursorHeight / 2 > rect.height) {
|
|
10330
|
+
newCoords.y = rect.y - cursorHeight / 2;
|
|
10331
|
+
}
|
|
10332
|
+
virtualCursorCoords.current = newCoords;
|
|
10333
|
+
updateCursorTransform(newCoords.x, newCoords.y);
|
|
10334
|
+
});
|
|
10335
|
+
const onScrubbingChange = DrawerRoot.useStableCallback((scrubbingValue, {
|
|
10336
|
+
clientX,
|
|
10337
|
+
clientY
|
|
10338
|
+
}) => {
|
|
10339
|
+
ReactDOM__namespace.flushSync(() => {
|
|
10340
|
+
setIsScrubbing(scrubbingValue);
|
|
10341
|
+
setRootScrubbing(scrubbingValue);
|
|
10342
|
+
});
|
|
10343
|
+
const virtualCursor = scrubAreaCursorRef.current;
|
|
10344
|
+
if (!virtualCursor || !scrubbingValue) {
|
|
10345
|
+
return;
|
|
10346
|
+
}
|
|
10347
|
+
const initialCoords = {
|
|
10348
|
+
x: clientX - virtualCursor.offsetWidth / 2,
|
|
10349
|
+
y: clientY - virtualCursor.offsetHeight / 2
|
|
10350
|
+
};
|
|
10351
|
+
virtualCursorCoords.current = initialCoords;
|
|
10352
|
+
updateCursorTransform(initialCoords.x, initialCoords.y);
|
|
10353
|
+
});
|
|
10354
|
+
React__namespace.useEffect(function registerGlobalScrubbingEventListeners() {
|
|
10355
|
+
// Only listen while actively scrubbing; avoids unrelated pointerup events committing.
|
|
10356
|
+
if (!inputRef.current || disabled || readOnly || !isScrubbing) {
|
|
10357
|
+
return undefined;
|
|
10358
|
+
}
|
|
10359
|
+
let cumulativeDelta = 0;
|
|
10360
|
+
function handleScrubPointerUp(event) {
|
|
10361
|
+
function handler() {
|
|
10362
|
+
try {
|
|
10363
|
+
DrawerRoot.ownerDocument(scrubAreaRef.current).exitPointerLock();
|
|
10364
|
+
} catch {
|
|
10365
|
+
// Ignore errors.
|
|
10366
|
+
} finally {
|
|
10367
|
+
isScrubbingRef.current = false;
|
|
10368
|
+
onScrubbingChange(false, event);
|
|
10369
|
+
onValueCommitted(lastChangedValueRef.current ?? valueRef.current, DrawerRoot.createGenericEventDetails(DrawerRoot.scrub, event));
|
|
10370
|
+
|
|
10371
|
+
// Manually dispatch a click event if no movement happened, since
|
|
10372
|
+
// preventDefault on pointerdown prevents the browser click event.
|
|
10373
|
+
if (!didMoveRef.current && pointerDownTargetRef.current != null) {
|
|
10374
|
+
pointerDownTargetRef.current.dispatchEvent(new MouseEvent('click', {
|
|
10375
|
+
bubbles: true,
|
|
10376
|
+
cancelable: true
|
|
10377
|
+
}));
|
|
10378
|
+
}
|
|
10379
|
+
didMoveRef.current = false;
|
|
10380
|
+
pointerDownTargetRef.current = null;
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
if (DrawerRoot.isFirefox) {
|
|
10384
|
+
// Firefox needs a small delay here when soft-clicking as the pointer
|
|
10385
|
+
// lock will not release otherwise.
|
|
10386
|
+
exitPointerLockTimeout.start(20, handler);
|
|
10387
|
+
} else {
|
|
10388
|
+
handler();
|
|
10389
|
+
}
|
|
10390
|
+
}
|
|
10391
|
+
function handleScrubPointerMove(event) {
|
|
10392
|
+
if (!isScrubbingRef.current) {
|
|
10393
|
+
return;
|
|
10394
|
+
}
|
|
10395
|
+
|
|
10396
|
+
// Prevent text selection.
|
|
10397
|
+
event.preventDefault();
|
|
10398
|
+
onScrub(event);
|
|
10399
|
+
const {
|
|
10400
|
+
movementX,
|
|
10401
|
+
movementY
|
|
10402
|
+
} = event;
|
|
10403
|
+
cumulativeDelta += direction === 'vertical' ? movementY : movementX;
|
|
10404
|
+
if (Math.abs(cumulativeDelta) >= pixelSensitivity) {
|
|
10405
|
+
cumulativeDelta = 0;
|
|
10406
|
+
didMoveRef.current = true;
|
|
10407
|
+
const dValue = direction === 'vertical' ? -movementY : movementX;
|
|
10408
|
+
const stepAmount = getStepAmount(event) ?? DEFAULT_STEP;
|
|
10409
|
+
const rawAmount = dValue * stepAmount;
|
|
10410
|
+
if (rawAmount !== 0) {
|
|
10411
|
+
incrementValue(Math.abs(rawAmount), {
|
|
10412
|
+
direction: rawAmount >= 0 ? 1 : -1,
|
|
10413
|
+
event,
|
|
10414
|
+
reason: DrawerRoot.scrub
|
|
10415
|
+
});
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
}
|
|
10419
|
+
const win = index_esm.getWindow(inputRef.current);
|
|
10420
|
+
win.addEventListener('pointerup', handleScrubPointerUp, true);
|
|
10421
|
+
win.addEventListener('pointermove', handleScrubPointerMove, true);
|
|
10422
|
+
return () => {
|
|
10423
|
+
exitPointerLockTimeout.clear();
|
|
10424
|
+
win.removeEventListener('pointerup', handleScrubPointerUp, true);
|
|
10425
|
+
win.removeEventListener('pointermove', handleScrubPointerMove, true);
|
|
10426
|
+
};
|
|
10427
|
+
}, [disabled, readOnly, incrementValue, isScrubbing, getStepAmount, inputRef, onScrubbingChange, onScrub, direction, pixelSensitivity, lastChangedValueRef, onValueCommitted, valueRef, exitPointerLockTimeout]);
|
|
10428
|
+
|
|
10429
|
+
// Prevent scrolling using touch input when scrubbing.
|
|
10430
|
+
React__namespace.useEffect(function registerScrubberTouchPreventListener() {
|
|
10431
|
+
const element = scrubAreaRef.current;
|
|
10432
|
+
if (!element || disabled || readOnly) {
|
|
10433
|
+
return undefined;
|
|
10434
|
+
}
|
|
10435
|
+
function handleTouchStart(event) {
|
|
10436
|
+
if (event.touches.length === 1) {
|
|
10437
|
+
event.preventDefault();
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
element.addEventListener('touchstart', handleTouchStart);
|
|
10441
|
+
return () => {
|
|
10442
|
+
element.removeEventListener('touchstart', handleTouchStart);
|
|
10443
|
+
};
|
|
10444
|
+
}, [disabled, readOnly]);
|
|
10445
|
+
const defaultProps = {
|
|
10446
|
+
role: 'presentation',
|
|
10447
|
+
style: {
|
|
10448
|
+
touchAction: 'none',
|
|
10449
|
+
WebkitUserSelect: 'none',
|
|
10450
|
+
userSelect: 'none'
|
|
10451
|
+
},
|
|
10452
|
+
async onPointerDown(event) {
|
|
10453
|
+
const isMainButton = !event.button || event.button === 0;
|
|
10454
|
+
if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
|
|
10455
|
+
return;
|
|
10456
|
+
}
|
|
10457
|
+
const isTouch = event.pointerType === 'touch';
|
|
10458
|
+
setIsTouchInput(isTouch);
|
|
10459
|
+
if (event.pointerType === 'mouse') {
|
|
10460
|
+
event.preventDefault();
|
|
10461
|
+
inputRef.current?.focus();
|
|
10462
|
+
}
|
|
10463
|
+
isScrubbingRef.current = true;
|
|
10464
|
+
didMoveRef.current = false;
|
|
10465
|
+
pointerDownTargetRef.current = event.target;
|
|
10466
|
+
onScrubbingChange(true, event.nativeEvent);
|
|
10467
|
+
|
|
10468
|
+
// WebKit causes significant layout shift with the native message, so we can't use it.
|
|
10469
|
+
if (!isTouch && !DrawerRoot.isWebKit) {
|
|
10470
|
+
try {
|
|
10471
|
+
// Avoid non-deterministic errors in testing environments. This error sometimes
|
|
10472
|
+
// appears:
|
|
10473
|
+
// "The root document of this element is not valid for pointer lock."
|
|
10474
|
+
await DrawerRoot.ownerDocument(scrubAreaRef.current).body.requestPointerLock();
|
|
10475
|
+
setIsPointerLockDenied(false);
|
|
10476
|
+
} catch (error) {
|
|
10477
|
+
setIsPointerLockDenied(true);
|
|
10478
|
+
} finally {
|
|
10479
|
+
if (isScrubbingRef.current) {
|
|
10480
|
+
ReactDOM__namespace.flushSync(() => {
|
|
10481
|
+
onScrubbingChange(true, event.nativeEvent);
|
|
10482
|
+
});
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10485
|
+
}
|
|
10486
|
+
}
|
|
10487
|
+
};
|
|
10488
|
+
const element = useRenderElement.useRenderElement('span', componentProps, {
|
|
10489
|
+
ref: [forwardedRef, scrubAreaRef],
|
|
10490
|
+
state,
|
|
10491
|
+
props: [defaultProps, elementProps],
|
|
10492
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
10493
|
+
});
|
|
10494
|
+
const contextValue = React__namespace.useMemo(() => ({
|
|
10495
|
+
isScrubbing,
|
|
10496
|
+
isTouchInput,
|
|
10497
|
+
isPointerLockDenied,
|
|
10498
|
+
scrubAreaCursorRef,
|
|
10499
|
+
scrubAreaRef,
|
|
10500
|
+
direction,
|
|
10501
|
+
pixelSensitivity,
|
|
10502
|
+
teleportDistance
|
|
10503
|
+
}), [isScrubbing, isTouchInput, isPointerLockDenied, direction, pixelSensitivity, teleportDistance]);
|
|
10504
|
+
return /*#__PURE__*/jsxRuntime.jsx(NumberFieldScrubAreaContext.Provider, {
|
|
10505
|
+
value: contextValue,
|
|
10506
|
+
children: element
|
|
10507
|
+
});
|
|
10508
|
+
});
|
|
10509
|
+
if (process.env.NODE_ENV !== "production") NumberFieldScrubArea.displayName = "NumberFieldScrubArea";
|
|
10510
|
+
|
|
10511
|
+
/**
|
|
10512
|
+
* A custom element to display instead of the native cursor while using the scrub area.
|
|
10513
|
+
* Renders a `<span>` element.
|
|
10514
|
+
*
|
|
10515
|
+
* 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
|
|
10516
|
+
* in Safari to avoid a layout shift that this notification causes there.
|
|
10517
|
+
*
|
|
10518
|
+
* Documentation: [Base UI Number Field](https://base-ui.com/react/components/number-field)
|
|
10519
|
+
*/
|
|
10520
|
+
const NumberFieldScrubAreaCursor = /*#__PURE__*/React__namespace.forwardRef(function NumberFieldScrubAreaCursor(componentProps, forwardedRef) {
|
|
10521
|
+
const {
|
|
10522
|
+
render,
|
|
10523
|
+
className,
|
|
10524
|
+
...elementProps
|
|
10525
|
+
} = componentProps;
|
|
10526
|
+
const {
|
|
10527
|
+
state
|
|
10528
|
+
} = useNumberFieldRootContext();
|
|
10529
|
+
const {
|
|
10530
|
+
isScrubbing,
|
|
10531
|
+
isTouchInput,
|
|
10532
|
+
isPointerLockDenied,
|
|
10533
|
+
scrubAreaCursorRef
|
|
10534
|
+
} = useNumberFieldScrubAreaContext();
|
|
10535
|
+
const [domElement, setDomElement] = React__namespace.useState(null);
|
|
10536
|
+
const shouldRender = isScrubbing && !DrawerRoot.isWebKit && !isTouchInput && !isPointerLockDenied;
|
|
10537
|
+
const element = useRenderElement.useRenderElement('span', componentProps, {
|
|
10538
|
+
enabled: shouldRender,
|
|
10539
|
+
ref: [forwardedRef, scrubAreaCursorRef, setDomElement],
|
|
10540
|
+
state,
|
|
10541
|
+
props: [{
|
|
10542
|
+
role: 'presentation',
|
|
10543
|
+
style: {
|
|
10544
|
+
position: 'fixed',
|
|
10545
|
+
top: 0,
|
|
10546
|
+
left: 0,
|
|
10547
|
+
pointerEvents: 'none'
|
|
10548
|
+
}
|
|
10549
|
+
}, elementProps],
|
|
10550
|
+
stateAttributesMapping: stateAttributesMapping$1
|
|
10551
|
+
});
|
|
10552
|
+
return element && /*#__PURE__*/ReactDOM__namespace.createPortal(element, DrawerRoot.ownerDocument(domElement).body);
|
|
10553
|
+
});
|
|
10554
|
+
if (process.env.NODE_ENV !== "production") NumberFieldScrubAreaCursor.displayName = "NumberFieldScrubAreaCursor";
|
|
10555
|
+
|
|
10556
|
+
var index_parts = /*#__PURE__*/Object.freeze({
|
|
10557
|
+
__proto__: null,
|
|
10558
|
+
Decrement: NumberFieldDecrement,
|
|
10559
|
+
Group: NumberFieldGroup,
|
|
10560
|
+
Increment: NumberFieldIncrement,
|
|
10561
|
+
Input: NumberFieldInput,
|
|
10562
|
+
Root: NumberFieldRoot,
|
|
10563
|
+
ScrubArea: NumberFieldScrubArea,
|
|
10564
|
+
ScrubAreaCursor: NumberFieldScrubAreaCursor
|
|
10565
|
+
});
|
|
10566
|
+
|
|
8715
10567
|
exports.Separator = Separator.Separator;
|
|
8716
|
-
exports.Combobox = index_parts$
|
|
8717
|
-
exports.Drawer = index_parts;
|
|
10568
|
+
exports.Combobox = index_parts$2;
|
|
10569
|
+
exports.Drawer = index_parts$1;
|
|
10570
|
+
exports.NumberField = index_parts;
|