@jobber/components 9.5.3 → 9.6.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.
- package/Combobox.d.ts +1 -0
- package/Combobox.js +17 -0
- package/dist/Autocomplete/index.cjs +0 -2
- package/dist/Autocomplete/index.mjs +0 -2
- package/dist/Autocomplete-cjs.js +4 -5
- package/dist/Autocomplete-es.js +1 -2
- package/dist/Card/index.cjs +2 -2
- package/dist/Card/index.mjs +2 -2
- package/dist/Chip/index.cjs +0 -2
- package/dist/Chip/index.mjs +0 -2
- package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
- package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
- package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
- package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
- package/dist/Chips/index.cjs +0 -2
- package/dist/Chips/index.mjs +0 -2
- package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
- package/dist/Combobox/Combobox.context.d.ts +34 -0
- package/dist/Combobox/Combobox.d.ts +69 -0
- package/dist/Combobox/Combobox.footer.d.ts +7 -0
- package/dist/Combobox/Combobox.items.d.ts +25 -0
- package/dist/Combobox/Combobox.responsive.d.ts +54 -0
- package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
- package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
- package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
- package/dist/Combobox/Combobox.types.d.ts +166 -0
- package/dist/Combobox/index.cjs +35 -0
- package/dist/Combobox/index.d.ts +2 -0
- package/dist/Combobox/index.mjs +29 -0
- package/dist/Combobox-cjs.js +675 -0
- package/dist/Combobox-es.js +673 -0
- package/dist/ComboboxPrimitive-cjs.js +88 -47
- package/dist/ComboboxPrimitive-es.js +39 -23
- package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
- package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
- package/dist/ConfirmationModal/index.cjs +0 -2
- package/dist/ConfirmationModal/index.mjs +0 -2
- package/dist/ContentBlock/index.cjs +1 -0
- package/dist/ContentBlock/index.mjs +1 -0
- package/dist/ContentBlock-cjs.js +2 -18
- package/dist/ContentBlock-es.js +1 -17
- package/dist/DataDump/index.cjs +2 -2
- package/dist/DataDump/index.mjs +2 -2
- package/dist/DataList/components/DataListActions/index.cjs +0 -2
- package/dist/DataList/components/DataListActions/index.mjs +0 -2
- package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
- package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
- package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
- package/dist/DataList/components/DataListFilters/index.cjs +0 -2
- package/dist/DataList/components/DataListFilters/index.mjs +0 -2
- package/dist/DataList/components/DataListHeader/index.cjs +0 -2
- package/dist/DataList/components/DataListHeader/index.mjs +0 -2
- package/dist/DataList/components/DataListItem/index.cjs +0 -2
- package/dist/DataList/components/DataListItem/index.mjs +0 -2
- package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
- package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
- package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
- package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
- package/dist/DataList/components/DataListItems/index.cjs +0 -2
- package/dist/DataList/components/DataListItems/index.mjs +0 -2
- package/dist/DataList/components/DataListLayout/index.cjs +0 -2
- package/dist/DataList/components/DataListLayout/index.mjs +0 -2
- package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
- package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
- package/dist/DataList/index.cjs +0 -2
- package/dist/DataList/index.mjs +0 -2
- package/dist/DatePicker/index.cjs +0 -2
- package/dist/DatePicker/index.mjs +0 -2
- package/dist/DatePicker-cjs.js +4 -5
- package/dist/DatePicker-es.js +1 -2
- package/dist/Dialog/index.cjs +2 -2
- package/dist/Dialog/index.mjs +2 -2
- package/dist/DrawerDescription-cjs.js +268 -99
- package/dist/DrawerDescription-es.js +270 -103
- package/dist/DrawerTitle-cjs.js +2 -6
- package/dist/DrawerTitle-es.js +3 -7
- package/dist/FieldDescription-cjs.js +184 -181
- package/dist/FieldDescription-es.js +187 -184
- package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
- package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
- package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
- package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
- package/dist/FilterPicker/index.cjs +0 -2
- package/dist/FilterPicker/index.mjs +0 -2
- package/dist/FilterPickerContent-cjs.js +4 -5
- package/dist/FilterPickerContent-es.js +1 -2
- package/dist/FormatFile/index.cjs +0 -2
- package/dist/FormatFile/index.mjs +0 -2
- package/dist/Gallery/index.cjs +0 -2
- package/dist/Gallery/index.mjs +0 -2
- package/dist/InputDate/index.cjs +0 -2
- package/dist/InputDate/index.mjs +0 -2
- package/dist/InputNumber/index.cjs +2 -2
- package/dist/InputNumber/index.mjs +2 -2
- package/dist/InputNumber/types.d.ts +5 -0
- package/dist/InputNumber-cjs.js +2 -2
- package/dist/InputNumber-es.js +2 -2
- package/dist/LightBox/index.cjs +0 -2
- package/dist/LightBox/index.mjs +0 -2
- package/dist/Menu/index.cjs +2 -2
- package/dist/Menu/index.mjs +2 -2
- package/dist/Menu-cjs.js +4 -5
- package/dist/Menu-es.js +1 -2
- package/dist/MenuSubmenuTrigger-cjs.js +206 -164
- package/dist/MenuSubmenuTrigger-es.js +194 -152
- package/dist/Modal/index.cjs +0 -2
- package/dist/Modal/index.mjs +1 -3
- package/dist/NumberFieldInput-cjs.js +187 -308
- package/dist/NumberFieldInput-es.js +187 -308
- package/dist/Page/index.cjs +2 -2
- package/dist/Page/index.mjs +2 -2
- package/dist/Popover/index.cjs +0 -2
- package/dist/Popover/index.mjs +0 -2
- package/dist/Popover-cjs.js +7 -8
- package/dist/Popover-es.js +1 -2
- package/dist/ScrollAreaViewport-cjs.js +747 -1093
- package/dist/ScrollAreaViewport-es.js +734 -1081
- package/dist/Select/index.cjs +2 -2
- package/dist/Select/index.mjs +2 -2
- package/dist/SelectGroupLabel-cjs.js +207 -306
- package/dist/SelectGroupLabel-es.js +203 -302
- package/dist/SelectPrimitive-cjs.js +11 -17
- package/dist/SelectPrimitive-es.js +1 -7
- package/dist/Tooltip/index.cjs +0 -2
- package/dist/Tooltip/index.mjs +0 -2
- package/dist/Tooltip-cjs.js +6 -7
- package/dist/Tooltip-es.js +1 -2
- package/dist/buttonRenderAdapter-cjs.js +2 -1
- package/dist/buttonRenderAdapter-es.js +2 -1
- package/dist/dialogReturnFocus-cjs.js +1 -1
- package/dist/dialogReturnFocus-es.js +2 -2
- package/dist/docs/InputNumber/InputNumber.md +2 -2
- package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
- package/dist/floating-ui.react-cjs.js +2557 -185
- package/dist/floating-ui.react-es.js +2512 -149
- package/dist/getMappedBreakpointWidth-cjs.js +21 -0
- package/dist/getMappedBreakpointWidth-es.js +18 -0
- package/dist/index.cjs +15 -10
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +14 -10
- package/dist/mergeClassName-cjs.js +12 -0
- package/dist/mergeClassName-es.js +10 -0
- package/dist/primitives/BottomSheet/index.cjs +1 -1
- package/dist/primitives/BottomSheet/index.mjs +1 -1
- package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
- package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
- package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
- package/dist/primitives/SelectPrimitive/index.cjs +1 -2
- package/dist/primitives/SelectPrimitive/index.mjs +1 -2
- package/dist/primitives/index.cjs +3 -3
- package/dist/primitives/index.mjs +3 -3
- package/dist/scrollEdges-cjs.js +1 -11
- package/dist/scrollEdges-es.js +1 -11
- package/dist/scrollable-cjs.js +58 -0
- package/dist/scrollable-es.js +54 -0
- package/dist/styles.css +759 -42
- package/dist/unstyledPrimitives/index.cjs +720 -478
- package/dist/unstyledPrimitives/index.mjs +663 -421
- package/dist/useCompositeListItem-cjs.js +2108 -361
- package/dist/useCompositeListItem-es.js +2076 -331
- package/dist/useLabel-cjs.js +2 -3
- package/dist/useLabel-es.js +2 -3
- package/dist/useRenderElement-cjs.js +9 -7
- package/dist/useRenderElement-es.js +9 -7
- package/dist/useScrollLock-cjs.js +819 -344
- package/dist/useScrollLock-es.js +732 -296
- package/dist/useScrollToActive-cjs.js +4 -5
- package/dist/useScrollToActive-es.js +1 -2
- package/dist/utils/meta/meta.json +43 -1
- package/package.json +8 -3
- package/dist/floating-ui.react-dom-cjs.js +0 -2103
- package/dist/floating-ui.react-dom-es.js +0 -2072
- package/dist/floating-ui.utils-cjs.js +0 -342
- package/dist/floating-ui.utils-es.js +0 -300
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { v as useControlled, w as useValueAsRef, c as useIsoLayoutEffect, d as useStableCallback, x as ios, y as addEventListener, h as createChangeEventDetails, z as none, B as visuallyHiddenInput, C as visuallyHidden, D as activeElement, o as ownerDocument, E as wheel, G as createGenericEventDetails, H as useOnMount, T as Timeout, q as useTimeout, I as getWindow, f as useButton, J as incrementPress, K as decrementPress, L as useValueChanged, M as inputClear, N as inputBlur, O as inputChange, P as keyboard, S as SafeReact, Q as inputPaste } from './useScrollLock-es.js';
|
|
3
3
|
import { f as formatErrorMessage, b as useMergedRefs, u as useRenderElement, a as useRefWithInit, N as NOOP, w as warn } from './useRenderElement-es.js';
|
|
4
4
|
import { f as fieldValidityMapping, b as useFieldRootContext, a as useFormContext, c as useLabelableId, u as useLabelableContext, d as useRegisterFieldControl } from './useCompositeListItem-es.js';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
-
import { K as getWindow } from './floating-ui.utils-es.js';
|
|
7
6
|
import { s as stringifyLocale } from './stringifyLocale-es.js';
|
|
8
7
|
import { c as clamp } from './scrollEdges-es.js';
|
|
9
8
|
|
|
@@ -54,121 +53,112 @@ const stateAttributesMapping = {
|
|
|
54
53
|
...fieldValidityMapping
|
|
55
54
|
};
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
五: '5',
|
|
67
|
-
六: '6',
|
|
68
|
-
七: '7',
|
|
69
|
-
八: '8',
|
|
70
|
-
九: '9'
|
|
71
|
-
};
|
|
72
|
-
const ARABIC_NUMERALS = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
|
|
73
|
-
const PERSIAN_NUMERALS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
|
74
|
-
const FULLWIDTH_NUMERALS = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
|
56
|
+
// Han numerals in digit order, with both zero forms first ('零' at 0, '〇' at 1), so a
|
|
57
|
+
// character's digit value is `max(indexOf - 1, 0)`.
|
|
58
|
+
const HAN_NUMERALS = '零〇一二三四五六七八九';
|
|
59
|
+
|
|
60
|
+
// Arabic-Indic (U+0660–0669), Persian (U+06F0–06F9), and fullwidth (U+FF10–FF19) digits are
|
|
61
|
+
// contiguous ranges whose bases are divisible by 16, so `charCode % 16` is the digit value for
|
|
62
|
+
// all three systems.
|
|
63
|
+
const NON_ASCII_DIGIT_RE = /[٠-٩۰-۹0-9]/g;
|
|
64
|
+
const HAN_RE = /[零〇一二三四五六七八九]/g;
|
|
75
65
|
const PERCENTAGES = ['%', '٪', '%', '﹪'];
|
|
76
66
|
const PERMILLE = ['‰', '؉'];
|
|
77
|
-
const UNICODE_MINUS_SIGNS = ['−', '-', '‒', '–', '—', '﹣'];
|
|
78
|
-
const UNICODE_PLUS_SIGNS = ['+', '﹢'];
|
|
79
67
|
|
|
80
68
|
// Fullwidth punctuation common in CJK inputs
|
|
81
69
|
const FULLWIDTH_DECIMAL = '.'; // U+FF0E
|
|
82
70
|
const FULLWIDTH_GROUP = ','; // U+FF0C
|
|
83
71
|
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
// so they can't drift out of sync.
|
|
95
|
-
const ARABIC_DETECT_RE = new RegExp(`[${ARABIC_NUMERALS.join('')}]`);
|
|
96
|
-
const PERSIAN_DETECT_RE = new RegExp(`[${PERSIAN_NUMERALS.join('')}]`);
|
|
97
|
-
const HAN_DETECT_RE = new RegExp(`[${HAN_NUMERALS.join('')}]`);
|
|
98
|
-
const FULLWIDTH_DETECT_RE = new RegExp(`[${FULLWIDTH_NUMERALS.join('')}]`);
|
|
72
|
+
const PERCENT_RE = /[%٪%﹪]/;
|
|
73
|
+
const PERMILLE_RE = /[‰؉]/;
|
|
74
|
+
const PERCENT_GLOBAL_RE = /[%٪%﹪]/g;
|
|
75
|
+
const PERMILLE_GLOBAL_RE = /[‰؉]/g;
|
|
76
|
+
|
|
77
|
+
// Detection regexes (non-global to avoid lastIndex side effects). Arabic-Indic and Persian
|
|
78
|
+
// digits share one regex because both resolve to the `ar` locale heuristic.
|
|
79
|
+
const ARABIC_PERSIAN_DETECT_RE = /[٠-٩۰-۹]/;
|
|
80
|
+
const HAN_DETECT_RE = /[零〇一二三四五六七八九]/;
|
|
81
|
+
const ANY_NUMERAL_DETECT_RE = /[0-9٠-٩۰-۹0-9零〇一二三四五六七八九]/;
|
|
99
82
|
|
|
100
83
|
// Whether the character is a digit in any numeral system the field accepts.
|
|
101
84
|
function isNumeralChar(char) {
|
|
102
|
-
return
|
|
85
|
+
return ANY_NUMERAL_DETECT_RE.test(char);
|
|
103
86
|
}
|
|
104
87
|
const BASE_NON_NUMERIC_SYMBOLS = ['.', ',', FULLWIDTH_DECIMAL, FULLWIDTH_GROUP, '٫', '٬'];
|
|
105
88
|
const SPACE_SEPARATOR_RE = /\p{Zs}/u;
|
|
106
|
-
|
|
107
|
-
|
|
89
|
+
// Format/bidi control characters (e.g. the LRM/ALM marks RTL locales insert around exponent and
|
|
90
|
+
// currency signs). `parseNumber` strips these, so input validation must treat them as ignorable
|
|
91
|
+
// rather than rejecting the typed string. Non-global so it's safe for repeated `.test(char)`.
|
|
92
|
+
const FORMAT_CONTROL_DETECT_RE = /\p{Cf}/u;
|
|
93
|
+
const FORMAT_CONTROL_GLOBAL_RE = /\p{Cf}/gu;
|
|
94
|
+
const PLUS_SIGNS_WITH_ASCII = ['+', '+', '﹢'];
|
|
95
|
+
const MINUS_SIGNS_WITH_ASCII = ['-', '−', '-', '‒', '–', '—', '﹣'];
|
|
108
96
|
const escapeRegExp = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
109
|
-
const escapeClassChar = s => s.replace(/[-\\\]^]/g, m => `\\${m}`); // escape for use inside [...]
|
|
110
|
-
|
|
111
97
|
function shiftDecimal(value, exponentDelta) {
|
|
112
98
|
const [coefficient, exponent = '0'] = String(value).split('e');
|
|
113
99
|
return Number(`${coefficient}e${Number(exponent) + exponentDelta}`);
|
|
114
100
|
}
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
const ANY_MINUS_RE = /[-−-‒–—﹣]/gu;
|
|
102
|
+
const ANY_PLUS_RE = /[++﹢]/gu;
|
|
103
|
+
const ANY_MINUS_DETECT_RE = /[-−-‒–—﹣]/;
|
|
104
|
+
const ANY_PLUS_DETECT_RE = /[++﹢]/;
|
|
105
|
+
|
|
106
|
+
// A representative value with a grouping separator and a fractional part, so that the formatter
|
|
107
|
+
// emits every locale-specific part (group, decimal, currency, unit, literal, exponent, …). Shared
|
|
108
|
+
// so the locale-detail and allowed-character derivations enumerate the same parts.
|
|
109
|
+
const SAMPLE_FORMAT_NUMBER = 11111.1;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns the `Intl.NumberFormat` parts of a representative number, which surface every
|
|
113
|
+
* non-numeric symbol a given locale/format renders.
|
|
114
|
+
*/
|
|
115
|
+
function getFormatParts(locale, options) {
|
|
116
|
+
return getFormatter(locale, options).formatToParts(SAMPLE_FORMAT_NUMBER);
|
|
117
|
+
}
|
|
122
118
|
function getNumberLocaleDetails(locale, options) {
|
|
123
|
-
const parts =
|
|
119
|
+
const parts = getFormatParts(locale, options);
|
|
124
120
|
const result = {};
|
|
125
121
|
parts.forEach(part => {
|
|
126
122
|
result[part.type] = part.value;
|
|
127
123
|
});
|
|
128
124
|
|
|
129
125
|
// The formatting options may omit the decimal separator (e.g. integer formats), so resolve it
|
|
130
|
-
// from the plain locale formatter
|
|
131
|
-
//
|
|
126
|
+
// from the plain locale formatter, which renders one for every locale and numbering system. This
|
|
127
|
+
// overrides any options-derived decimal too, which is safe because the separator is
|
|
128
|
+
// locale-determined and identical across format styles. The ASCII seed is a formality to keep
|
|
129
|
+
// the separator non-optional for callers; it is always replaced below.
|
|
130
|
+
let decimal = '.';
|
|
132
131
|
getFormatter(locale).formatToParts(0.1).forEach(part => {
|
|
133
132
|
if (part.type === 'decimal') {
|
|
134
|
-
|
|
133
|
+
decimal = part.value;
|
|
135
134
|
}
|
|
136
135
|
});
|
|
137
|
-
return
|
|
136
|
+
return {
|
|
137
|
+
...result,
|
|
138
|
+
decimal
|
|
139
|
+
};
|
|
138
140
|
}
|
|
139
141
|
function parseNumber(formattedNumber, locale, options) {
|
|
140
|
-
if (formattedNumber == null) {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
142
|
// Normalize control characters and whitespace; remove bidi/format controls
|
|
145
|
-
let input =
|
|
143
|
+
let input = formattedNumber.replace(FORMAT_CONTROL_GLOBAL_RE, '').trim();
|
|
146
144
|
|
|
147
145
|
// Normalize unicode minus/plus to ASCII, handle leading/trailing signs
|
|
148
146
|
input = input.replace(ANY_MINUS_RE, '-').replace(ANY_PLUS_RE, '+');
|
|
149
147
|
let isNegative = false;
|
|
150
148
|
|
|
151
|
-
//
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
if (trailing[1] === '-') {
|
|
149
|
+
// Strips a matched sign (leading "-12" / trailing "1234-") while recording negativity.
|
|
150
|
+
const takeSign = (match, sign) => {
|
|
151
|
+
if (sign === '-') {
|
|
155
152
|
isNegative = true;
|
|
156
153
|
}
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const leading = input.match(/^\s*([+-])/);
|
|
161
|
-
if (leading) {
|
|
162
|
-
if (leading[1] === '-') {
|
|
163
|
-
isNegative = true;
|
|
164
|
-
}
|
|
165
|
-
input = input.replace(/^\s*[+-]/, '');
|
|
166
|
-
}
|
|
154
|
+
return '';
|
|
155
|
+
};
|
|
156
|
+
input = input.replace(/([+-])\s*$/, takeSign).replace(/^\s*([+-])/, takeSign);
|
|
167
157
|
|
|
168
158
|
// Heuristic locale detection
|
|
169
159
|
let computedLocale = locale;
|
|
170
160
|
if (computedLocale === undefined) {
|
|
171
|
-
if (
|
|
161
|
+
if (ARABIC_PERSIAN_DETECT_RE.test(input)) {
|
|
172
162
|
computedLocale = 'ar';
|
|
173
163
|
} else if (HAN_DETECT_RE.test(input)) {
|
|
174
164
|
computedLocale = 'zh';
|
|
@@ -200,70 +190,17 @@ function parseNumber(formattedNumber, locale, options) {
|
|
|
200
190
|
groupRegex = new RegExp(escapeRegExp(group), 'g');
|
|
201
191
|
}
|
|
202
192
|
}
|
|
203
|
-
const replacements = [
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
},
|
|
210
|
-
// Fullwidth punctuation
|
|
211
|
-
{
|
|
212
|
-
regex: /./g,
|
|
213
|
-
replacement: '.'
|
|
214
|
-
},
|
|
215
|
-
// FULLWIDTH_DECIMAL
|
|
216
|
-
{
|
|
217
|
-
regex: /,/g,
|
|
218
|
-
replacement: ''
|
|
219
|
-
},
|
|
220
|
-
// FULLWIDTH_GROUP
|
|
221
|
-
// Arabic punctuation
|
|
222
|
-
{
|
|
223
|
-
regex: /٫/g,
|
|
224
|
-
replacement: '.'
|
|
225
|
-
},
|
|
226
|
-
// ARABIC DECIMAL SEPARATOR (U+066B)
|
|
227
|
-
{
|
|
228
|
-
regex: /٬/g,
|
|
229
|
-
replacement: ''
|
|
230
|
-
},
|
|
231
|
-
// ARABIC THOUSANDS SEPARATOR (U+066C)
|
|
193
|
+
const replacements = [[groupRegex, ''], [new RegExp(escapeRegExp(decimal), 'g'), '.'],
|
|
194
|
+
// Fullwidth/Arabic punctuation
|
|
195
|
+
[/[.٫]/g, '.'],
|
|
196
|
+
// FULLWIDTH_DECIMAL, ARABIC DECIMAL SEPARATOR (U+066B)
|
|
197
|
+
[/[,٬]/g, ''],
|
|
198
|
+
// FULLWIDTH_GROUP, ARABIC THOUSANDS SEPARATOR (U+066C)
|
|
232
199
|
// Currency & unit labels
|
|
233
|
-
|
|
234
|
-
regex: currency ? new RegExp(escapeRegExp(currency), 'g') : null,
|
|
235
|
-
replacement: ''
|
|
236
|
-
}, {
|
|
237
|
-
regex: unitRegex,
|
|
238
|
-
replacement: ''
|
|
239
|
-
}, {
|
|
240
|
-
regex: PERCENT_GLOBAL_RE,
|
|
241
|
-
replacement: ''
|
|
242
|
-
}, {
|
|
243
|
-
regex: PERMILLE_GLOBAL_RE,
|
|
244
|
-
replacement: ''
|
|
245
|
-
}, {
|
|
246
|
-
regex: exponentSeparator ? new RegExp(escapeRegExp(exponentSeparator), 'g') : null,
|
|
247
|
-
replacement: 'e'
|
|
248
|
-
},
|
|
200
|
+
[currency ? new RegExp(escapeRegExp(currency), 'g') : null, ''], [unitRegex, ''], [PERCENT_GLOBAL_RE, ''], [PERMILLE_GLOBAL_RE, ''], [exponentSeparator ? new RegExp(escapeRegExp(exponentSeparator), 'g') : null, 'e'],
|
|
249
201
|
// Numeral systems to ASCII digits
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
replacement: ch => String(ARABIC_NUMERALS.indexOf(ch))
|
|
253
|
-
}, {
|
|
254
|
-
regex: PERSIAN_RE,
|
|
255
|
-
replacement: ch => String(PERSIAN_NUMERALS.indexOf(ch))
|
|
256
|
-
}, {
|
|
257
|
-
regex: FULLWIDTH_RE,
|
|
258
|
-
replacement: ch => String(FULLWIDTH_NUMERALS.indexOf(ch))
|
|
259
|
-
}, {
|
|
260
|
-
regex: HAN_RE,
|
|
261
|
-
replacement: ch => HAN_NUMERAL_TO_DIGIT[ch]
|
|
262
|
-
}];
|
|
263
|
-
let unformatted = replacements.reduce((acc, {
|
|
264
|
-
regex,
|
|
265
|
-
replacement
|
|
266
|
-
}) => {
|
|
202
|
+
[NON_ASCII_DIGIT_RE, ch => String(ch.charCodeAt(0) % 16)], [HAN_RE, ch => String(Math.max(HAN_NUMERALS.indexOf(ch) - 1, 0))]];
|
|
203
|
+
let unformatted = replacements.reduce((acc, [regex, replacement]) => {
|
|
267
204
|
return regex ? acc.replace(regex, replacement) : acc;
|
|
268
205
|
}, input);
|
|
269
206
|
|
|
@@ -341,27 +278,18 @@ function removeFloatingPointErrors(value, format) {
|
|
|
341
278
|
}
|
|
342
279
|
return formatter.format(roundedValue) === roundedText ? roundedValue : value;
|
|
343
280
|
}
|
|
344
|
-
function snapToStep(value, base, step,
|
|
281
|
+
function snapToStep(value, base, step, nearest) {
|
|
345
282
|
const stepSize = Math.abs(step);
|
|
346
283
|
const direction = Math.sign(step);
|
|
347
284
|
const tolerance = stepSize * STEP_EPSILON_FACTOR * direction;
|
|
348
285
|
const rawSteps = value - base + tolerance;
|
|
349
|
-
if (
|
|
286
|
+
if (nearest) {
|
|
350
287
|
return base + Math.round(rawSteps / step) * step;
|
|
351
288
|
}
|
|
352
289
|
const snappedSteps = direction > 0 ? Math.floor(rawSteps / stepSize) : Math.ceil(rawSteps / stepSize);
|
|
353
290
|
return base + snappedSteps * stepSize;
|
|
354
291
|
}
|
|
355
|
-
function toValidatedNumber(value, {
|
|
356
|
-
step,
|
|
357
|
-
minWithDefault,
|
|
358
|
-
maxWithDefault,
|
|
359
|
-
minWithZeroDefault,
|
|
360
|
-
format,
|
|
361
|
-
snapOnStep,
|
|
362
|
-
small,
|
|
363
|
-
clamp: shouldClamp
|
|
364
|
-
}) {
|
|
292
|
+
function toValidatedNumber(value, step, minWithDefault, maxWithDefault, minWithZeroDefault, format, snapOnStep, small, shouldClamp) {
|
|
365
293
|
if (value === null) {
|
|
366
294
|
return value;
|
|
367
295
|
}
|
|
@@ -369,8 +297,9 @@ function toValidatedNumber(value, {
|
|
|
369
297
|
if (step != null && snapOnStep && step !== 0) {
|
|
370
298
|
const base = small || minWithDefault === Number.MIN_SAFE_INTEGER ? minWithZeroDefault : minWithDefault;
|
|
371
299
|
|
|
372
|
-
// Snap before clamping so non-step-aligned boundaries stay reachable.
|
|
373
|
-
|
|
300
|
+
// Snap before clamping so non-step-aligned boundaries stay reachable. Small (alt-key) steps
|
|
301
|
+
// snap to the nearest multiple; regular steps snap directionally.
|
|
302
|
+
nextValue = snapToStep(nextValue, base, step, small);
|
|
374
303
|
}
|
|
375
304
|
|
|
376
305
|
// Clamp before rounding so a value just outside a fractional boundary (e.g. `0.4` with
|
|
@@ -425,7 +354,6 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
425
354
|
validityData,
|
|
426
355
|
disabled: fieldDisabled,
|
|
427
356
|
setFilled,
|
|
428
|
-
invalid,
|
|
429
357
|
name: fieldName,
|
|
430
358
|
state: fieldState,
|
|
431
359
|
validation
|
|
@@ -474,48 +402,46 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
474
402
|
const [inputValue, setInputValue] = React.useState(() => formatNumber(value, locale, format));
|
|
475
403
|
const [inputMode, setInputMode] = React.useState('numeric');
|
|
476
404
|
const getAllowedNonNumericKeys = useStableCallback(() => {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
405
|
+
const parts = getFormatParts(locale, format);
|
|
406
|
+
const keys = new Set(BASE_NON_NUMERIC_SYMBOLS);
|
|
407
|
+
const addAll = chars => chars.forEach(char => keys.add(char));
|
|
408
|
+
|
|
409
|
+
// Integer formats omit the decimal from `parts`, so fall back to the locale's separator in that
|
|
410
|
+
// case; it must stay typeable regardless of whether the format renders a fraction.
|
|
411
|
+
const decimal = parts.find(part => part.type === 'decimal')?.value ?? getNumberLocaleDetails(locale, format).decimal;
|
|
412
|
+
keys.add(decimal);
|
|
413
|
+
|
|
414
|
+
// Allow every non-digit character the formatter renders — separators, currency symbols, units
|
|
415
|
+
// (e.g. `km/h`, `°C`), exponent separators, and locale literals — decomposed per character
|
|
416
|
+
// because the input validates the typed string one character at a time. Deriving these from
|
|
417
|
+
// the formatter covers multi-character and locale-specific symbols of every part type
|
|
418
|
+
// uniformly. `compact` suffixes (e.g. `K`/`M`) are excluded because `parseNumber` can't reverse
|
|
419
|
+
// them, so allowing them would yield a silently incorrect value.
|
|
420
|
+
parts.forEach(part => {
|
|
421
|
+
if (part.type === 'integer' || part.type === 'fraction' || part.type === 'exponentInteger' || part.type === 'compact') {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
addAll(Array.from(part.value));
|
|
425
|
+
if (SPACE_SEPARATOR_RE.test(part.value)) {
|
|
491
426
|
keys.add(' ');
|
|
492
427
|
}
|
|
493
|
-
}
|
|
428
|
+
});
|
|
494
429
|
const allowPercentSymbols = formatStyle === 'percent' || formatStyle === 'unit' && format?.unit === 'percent';
|
|
495
430
|
const allowPermilleSymbols = formatStyle === 'percent' || formatStyle === 'unit' && format?.unit === 'permille';
|
|
431
|
+
|
|
432
|
+
// Tolerate percent/permille variants the formatter doesn't emit but users may type or paste.
|
|
496
433
|
if (allowPercentSymbols) {
|
|
497
|
-
PERCENTAGES
|
|
434
|
+
addAll(PERCENTAGES);
|
|
498
435
|
}
|
|
499
436
|
if (allowPermilleSymbols) {
|
|
500
|
-
PERMILLE
|
|
501
|
-
}
|
|
502
|
-
if (formatStyle === 'currency' && currency) {
|
|
503
|
-
keys.add(currency);
|
|
504
|
-
}
|
|
505
|
-
if (literal) {
|
|
506
|
-
// Some locales (e.g. de-DE) insert a literal space character between the number
|
|
507
|
-
// and the symbol, so allow those characters to be typed/removed.
|
|
508
|
-
Array.from(literal).forEach(char => keys.add(char));
|
|
509
|
-
if (SPACE_SEPARATOR_RE.test(literal)) {
|
|
510
|
-
keys.add(' ');
|
|
511
|
-
}
|
|
437
|
+
addAll(PERMILLE);
|
|
512
438
|
}
|
|
513
439
|
|
|
514
440
|
// Allow plus sign in all cases; minus sign when negatives are valid, or when out-of-range
|
|
515
441
|
// entry is allowed so native underflow validation can be triggered from the keyboard.
|
|
516
|
-
PLUS_SIGNS_WITH_ASCII
|
|
442
|
+
addAll(PLUS_SIGNS_WITH_ASCII);
|
|
517
443
|
if (minWithDefault < 0 || allowOutOfRange) {
|
|
518
|
-
MINUS_SIGNS_WITH_ASCII
|
|
444
|
+
addAll(MINUS_SIGNS_WITH_ASCII);
|
|
519
445
|
}
|
|
520
446
|
return keys;
|
|
521
447
|
});
|
|
@@ -533,21 +459,13 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
533
459
|
const dir = details.direction;
|
|
534
460
|
|
|
535
461
|
// Direct text entry (typing, pasting, clearing, autofill) behaves natively; step-based
|
|
536
|
-
// interactions (keyboard arrows, buttons, wheel, scrub) do not.
|
|
537
|
-
|
|
462
|
+
// interactions (keyboard arrows, buttons, wheel, scrub) do not. All direct-entry reasons
|
|
463
|
+
// (`input-change`, `input-clear`, `input-blur`, `input-paste`) share the `input-` prefix.
|
|
464
|
+
const isInputReason = details.reason.startsWith('input-') || details.reason === none;
|
|
538
465
|
|
|
539
466
|
// Only allow out-of-range values for direct text entry. Step-based interactions still clamp.
|
|
540
467
|
const shouldClampValue = !allowOutOfRange || !isInputReason;
|
|
541
|
-
const validatedValue = toValidatedNumber(unvalidatedValue,
|
|
542
|
-
step: dir ? getStepAmount(eventWithOptionalKeyState) * dir : undefined,
|
|
543
|
-
format: formatOptionsRef.current,
|
|
544
|
-
minWithDefault,
|
|
545
|
-
maxWithDefault,
|
|
546
|
-
minWithZeroDefault,
|
|
547
|
-
snapOnStep,
|
|
548
|
-
small: eventWithOptionalKeyState?.altKey ?? false,
|
|
549
|
-
clamp: shouldClampValue
|
|
550
|
-
});
|
|
468
|
+
const validatedValue = toValidatedNumber(unvalidatedValue, dir ? getStepAmount(eventWithOptionalKeyState) * dir : undefined, minWithDefault, maxWithDefault, minWithZeroDefault, formatOptionsRef.current, snapOnStep, eventWithOptionalKeyState?.altKey ?? false, shouldClampValue);
|
|
551
469
|
|
|
552
470
|
// Notify about a change even when the numeric value is unchanged for input reasons: the
|
|
553
471
|
// typed text may clamp/snap to the current value, or differ while validation normalizes
|
|
@@ -657,10 +575,10 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
657
575
|
const changed = incrementValue(amount, {
|
|
658
576
|
direction: event.deltaY > 0 ? -1 : 1,
|
|
659
577
|
event,
|
|
660
|
-
reason:
|
|
578
|
+
reason: wheel
|
|
661
579
|
});
|
|
662
580
|
if (changed) {
|
|
663
|
-
onValueCommitted(lastChangedValueRef.current
|
|
581
|
+
onValueCommitted(lastChangedValueRef.current, createGenericEventDetails(wheel, event));
|
|
664
582
|
}
|
|
665
583
|
}
|
|
666
584
|
return addEventListener(element, 'wheel', handleWheel);
|
|
@@ -676,12 +594,8 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
676
594
|
}), [fieldState, disabled, readOnly, required, value, inputValue, isScrubbing]);
|
|
677
595
|
const contextValue = React.useMemo(() => ({
|
|
678
596
|
inputRef,
|
|
679
|
-
inputValue,
|
|
680
|
-
value,
|
|
681
597
|
minWithDefault,
|
|
682
598
|
maxWithDefault,
|
|
683
|
-
disabled,
|
|
684
|
-
readOnly,
|
|
685
599
|
id,
|
|
686
600
|
setValue,
|
|
687
601
|
incrementValue,
|
|
@@ -693,19 +607,16 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
693
607
|
hasPendingCommitRef,
|
|
694
608
|
name,
|
|
695
609
|
nameProp,
|
|
696
|
-
required,
|
|
697
|
-
invalid,
|
|
698
610
|
inputMode,
|
|
699
611
|
getAllowedNonNumericKeys,
|
|
700
612
|
min,
|
|
701
613
|
max,
|
|
702
614
|
setInputValue,
|
|
703
615
|
locale,
|
|
704
|
-
isScrubbing,
|
|
705
616
|
setIsScrubbing,
|
|
706
617
|
state,
|
|
707
618
|
onValueCommitted
|
|
708
|
-
}), [inputRef,
|
|
619
|
+
}), [inputRef, minWithDefault, maxWithDefault, id, setValue, incrementValue, getStepAmount, formatOptionsRef, valueRef, name, nameProp, inputMode, getAllowedNonNumericKeys, min, max, setInputValue, locale, state, onValueCommitted]);
|
|
709
620
|
const element = useRenderElement('div', componentProps, {
|
|
710
621
|
ref: forwardedRef,
|
|
711
622
|
state,
|
|
@@ -720,7 +631,7 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
720
631
|
inputRef.current?.focus();
|
|
721
632
|
},
|
|
722
633
|
onChange(event) {
|
|
723
|
-
// Workaround for https://github.com/
|
|
634
|
+
// Workaround for https://github.com/react/react/issues/9023
|
|
724
635
|
if (event.nativeEvent.defaultPrevented || disabled || readOnly) {
|
|
725
636
|
return;
|
|
726
637
|
}
|
|
@@ -745,7 +656,7 @@ const NumberFieldRoot = /*#__PURE__*/React.forwardRef(function NumberFieldRoot(c
|
|
|
745
656
|
min: min,
|
|
746
657
|
max: max
|
|
747
658
|
// stepMismatch validation is broken unless an explicit `min` is added.
|
|
748
|
-
// See https://github.com/
|
|
659
|
+
// See https://github.com/react/react/issues/12334.
|
|
749
660
|
,
|
|
750
661
|
step: stepProp,
|
|
751
662
|
disabled: disabled,
|
|
@@ -842,7 +753,6 @@ function isTouchLikePointerType(pointerType) {
|
|
|
842
753
|
function usePressAndHold(params) {
|
|
843
754
|
const {
|
|
844
755
|
disabled,
|
|
845
|
-
readOnly = false,
|
|
846
756
|
tick,
|
|
847
757
|
onStop,
|
|
848
758
|
tickDelay = DEFAULT_TICK_DELAY,
|
|
@@ -922,8 +832,7 @@ function usePressAndHold(params) {
|
|
|
922
832
|
isTouchingButtonRef.current = false;
|
|
923
833
|
},
|
|
924
834
|
onPointerDown(event) {
|
|
925
|
-
|
|
926
|
-
if (event.defaultPrevented || !isMainButton || disabled || readOnly) {
|
|
835
|
+
if (event.defaultPrevented || event.button || disabled) {
|
|
927
836
|
return;
|
|
928
837
|
}
|
|
929
838
|
pointerTypeRef.current = event.pointerType;
|
|
@@ -966,12 +875,10 @@ function usePressAndHold(params) {
|
|
|
966
875
|
}
|
|
967
876
|
},
|
|
968
877
|
onPointerMove(event) {
|
|
969
|
-
if (disabled ||
|
|
878
|
+
if (disabled || !isTouchLikePointerType(event.pointerType) || !isPressedRef.current) {
|
|
970
879
|
return;
|
|
971
880
|
}
|
|
972
|
-
|
|
973
|
-
movesAfterTouchRef.current += 1;
|
|
974
|
-
}
|
|
881
|
+
movesAfterTouchRef.current += 1;
|
|
975
882
|
const {
|
|
976
883
|
x,
|
|
977
884
|
y
|
|
@@ -983,7 +890,7 @@ function usePressAndHold(params) {
|
|
|
983
890
|
}
|
|
984
891
|
},
|
|
985
892
|
onMouseEnter(event) {
|
|
986
|
-
if (event.defaultPrevented || disabled ||
|
|
893
|
+
if (event.defaultPrevented || disabled || !isPressedRef.current || isTouchingButtonRef.current || isTouchLikePointerType(pointerTypeRef.current)) {
|
|
987
894
|
return;
|
|
988
895
|
}
|
|
989
896
|
startAutoChange(event.nativeEvent);
|
|
@@ -1016,32 +923,47 @@ function usePressAndHold(params) {
|
|
|
1016
923
|
};
|
|
1017
924
|
}
|
|
1018
925
|
|
|
1019
|
-
const CHANGE_VALUE_TICK_DELAY = 60;
|
|
1020
|
-
const START_AUTO_CHANGE_DELAY = 400;
|
|
1021
|
-
const SCROLLING_POINTER_MOVE_DISTANCE = 8;
|
|
1022
|
-
|
|
1023
926
|
const SELECT_NONE_STYLE = {
|
|
1024
927
|
WebkitUserSelect: 'none',
|
|
1025
928
|
userSelect: 'none'
|
|
1026
929
|
};
|
|
1027
|
-
|
|
930
|
+
/**
|
|
931
|
+
* Shared implementation for the increment and decrement stepper buttons. They differ only in the
|
|
932
|
+
* direction they step and the boundary (`max` vs `min`) at which they become disabled.
|
|
933
|
+
*/
|
|
934
|
+
function useNumberFieldStepperButton(componentProps, forwardedRef, isIncrement) {
|
|
935
|
+
const {
|
|
936
|
+
render,
|
|
937
|
+
className,
|
|
938
|
+
disabled: disabledProp = false,
|
|
939
|
+
nativeButton = true,
|
|
940
|
+
style,
|
|
941
|
+
...elementProps
|
|
942
|
+
} = componentProps;
|
|
1028
943
|
const {
|
|
1029
944
|
allowInputSyncRef,
|
|
1030
|
-
disabled,
|
|
1031
945
|
formatOptionsRef,
|
|
1032
946
|
getStepAmount,
|
|
1033
947
|
id,
|
|
1034
948
|
incrementValue,
|
|
1035
949
|
inputRef,
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
locale,
|
|
1039
|
-
readOnly,
|
|
950
|
+
maxWithDefault,
|
|
951
|
+
minWithDefault,
|
|
1040
952
|
setValue,
|
|
953
|
+
state,
|
|
1041
954
|
valueRef,
|
|
955
|
+
locale,
|
|
1042
956
|
lastChangedValueRef,
|
|
1043
957
|
onValueCommitted
|
|
1044
|
-
} =
|
|
958
|
+
} = useNumberFieldRootContext();
|
|
959
|
+
const {
|
|
960
|
+
disabled: contextDisabled,
|
|
961
|
+
readOnly,
|
|
962
|
+
value,
|
|
963
|
+
inputValue
|
|
964
|
+
} = state;
|
|
965
|
+
const isAtBoundary = value != null && (isIncrement ? value >= maxWithDefault : value <= minWithDefault);
|
|
966
|
+
const disabled = disabledProp || contextDisabled || isAtBoundary;
|
|
1045
967
|
const pressReason = isIncrement ? incrementPress : decrementPress;
|
|
1046
968
|
function commitValue(nativeEvent) {
|
|
1047
969
|
const shouldCommitInputValue = !allowInputSyncRef.current;
|
|
@@ -1077,9 +999,6 @@ function useNumberFieldButton(params) {
|
|
|
1077
999
|
} = usePressAndHold({
|
|
1078
1000
|
disabled: disabled || readOnly,
|
|
1079
1001
|
elementRef: inputRef,
|
|
1080
|
-
tickDelay: CHANGE_VALUE_TICK_DELAY,
|
|
1081
|
-
startDelay: START_AUTO_CHANGE_DELAY,
|
|
1082
|
-
scrollDistance: SCROLLING_POINTER_MOVE_DISTANCE,
|
|
1083
1002
|
tick(triggerEvent) {
|
|
1084
1003
|
const amount = getStepAmount(triggerEvent);
|
|
1085
1004
|
return incrementValue(amount, {
|
|
@@ -1124,8 +1043,7 @@ function useNumberFieldButton(params) {
|
|
|
1124
1043
|
}
|
|
1125
1044
|
},
|
|
1126
1045
|
onPointerDown(event) {
|
|
1127
|
-
|
|
1128
|
-
if (event.defaultPrevented || readOnly || !isMainButton || disabled) {
|
|
1046
|
+
if (event.defaultPrevented || readOnly || event.button || disabled) {
|
|
1129
1047
|
return;
|
|
1130
1048
|
}
|
|
1131
1049
|
|
|
@@ -1141,61 +1059,6 @@ function useNumberFieldButton(params) {
|
|
|
1141
1059
|
pointerHandlers.onPointerDown(event);
|
|
1142
1060
|
}
|
|
1143
1061
|
};
|
|
1144
|
-
return props;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* Shared implementation for the increment and decrement stepper buttons. They differ only in the
|
|
1149
|
-
* direction they step and the boundary (`max` vs `min`) at which they become disabled.
|
|
1150
|
-
*/
|
|
1151
|
-
function useNumberFieldStepperButton(componentProps, forwardedRef, isIncrement) {
|
|
1152
|
-
const {
|
|
1153
|
-
render,
|
|
1154
|
-
className,
|
|
1155
|
-
disabled: disabledProp = false,
|
|
1156
|
-
nativeButton = true,
|
|
1157
|
-
style,
|
|
1158
|
-
...elementProps
|
|
1159
|
-
} = componentProps;
|
|
1160
|
-
const {
|
|
1161
|
-
allowInputSyncRef,
|
|
1162
|
-
disabled: contextDisabled,
|
|
1163
|
-
formatOptionsRef,
|
|
1164
|
-
getStepAmount,
|
|
1165
|
-
id,
|
|
1166
|
-
incrementValue,
|
|
1167
|
-
inputRef,
|
|
1168
|
-
inputValue,
|
|
1169
|
-
maxWithDefault,
|
|
1170
|
-
minWithDefault,
|
|
1171
|
-
readOnly,
|
|
1172
|
-
setValue,
|
|
1173
|
-
state,
|
|
1174
|
-
value,
|
|
1175
|
-
valueRef,
|
|
1176
|
-
locale,
|
|
1177
|
-
lastChangedValueRef,
|
|
1178
|
-
onValueCommitted
|
|
1179
|
-
} = useNumberFieldRootContext();
|
|
1180
|
-
const isAtBoundary = value != null && (isIncrement ? value >= maxWithDefault : value <= minWithDefault);
|
|
1181
|
-
const disabled = disabledProp || contextDisabled || isAtBoundary;
|
|
1182
|
-
const props = useNumberFieldButton({
|
|
1183
|
-
isIncrement,
|
|
1184
|
-
inputRef,
|
|
1185
|
-
inputValue,
|
|
1186
|
-
disabled,
|
|
1187
|
-
readOnly,
|
|
1188
|
-
id,
|
|
1189
|
-
setValue,
|
|
1190
|
-
getStepAmount,
|
|
1191
|
-
incrementValue,
|
|
1192
|
-
allowInputSyncRef,
|
|
1193
|
-
formatOptionsRef,
|
|
1194
|
-
valueRef,
|
|
1195
|
-
locale,
|
|
1196
|
-
lastChangedValueRef,
|
|
1197
|
-
onValueCommitted
|
|
1198
|
-
});
|
|
1199
1062
|
const {
|
|
1200
1063
|
getButtonProps,
|
|
1201
1064
|
buttonRef
|
|
@@ -1258,31 +1121,33 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1258
1121
|
} = componentProps;
|
|
1259
1122
|
const {
|
|
1260
1123
|
allowInputSyncRef,
|
|
1261
|
-
disabled,
|
|
1262
1124
|
formatOptionsRef,
|
|
1263
1125
|
getAllowedNonNumericKeys,
|
|
1264
1126
|
getStepAmount,
|
|
1265
1127
|
id,
|
|
1266
1128
|
incrementValue,
|
|
1267
1129
|
inputMode,
|
|
1268
|
-
inputValue,
|
|
1269
1130
|
max,
|
|
1270
1131
|
min,
|
|
1271
1132
|
name,
|
|
1272
1133
|
nameProp,
|
|
1273
|
-
readOnly,
|
|
1274
|
-
required,
|
|
1275
1134
|
setValue,
|
|
1276
1135
|
state,
|
|
1277
1136
|
setInputValue,
|
|
1278
1137
|
locale,
|
|
1279
1138
|
inputRef,
|
|
1280
|
-
value,
|
|
1281
1139
|
onValueCommitted,
|
|
1282
1140
|
lastChangedValueRef,
|
|
1283
1141
|
hasPendingCommitRef,
|
|
1284
1142
|
valueRef
|
|
1285
1143
|
} = useNumberFieldRootContext();
|
|
1144
|
+
const {
|
|
1145
|
+
disabled,
|
|
1146
|
+
readOnly,
|
|
1147
|
+
required,
|
|
1148
|
+
value,
|
|
1149
|
+
inputValue
|
|
1150
|
+
} = state;
|
|
1286
1151
|
const {
|
|
1287
1152
|
clearErrors
|
|
1288
1153
|
} = useFormContext();
|
|
@@ -1417,7 +1282,7 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1417
1282
|
blockRevalidationRef.current = false;
|
|
1418
1283
|
return;
|
|
1419
1284
|
}
|
|
1420
|
-
committedValue = lastChangedValueRef.current
|
|
1285
|
+
committedValue = lastChangedValueRef.current;
|
|
1421
1286
|
// If validation normalized back to the current value, `useValueChanged` won't fire to
|
|
1422
1287
|
// reset the flag, so reset it here or the next external change won't revalidate.
|
|
1423
1288
|
if (committedValue === value) {
|
|
@@ -1438,7 +1303,7 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1438
1303
|
}
|
|
1439
1304
|
},
|
|
1440
1305
|
onChange(event) {
|
|
1441
|
-
// Workaround for https://github.com/
|
|
1306
|
+
// Workaround for https://github.com/react/react/issues/9023
|
|
1442
1307
|
if (event.nativeEvent.defaultPrevented) {
|
|
1443
1308
|
return;
|
|
1444
1309
|
}
|
|
@@ -1454,7 +1319,10 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1454
1319
|
// currently parseable into a number. This preserves good UX for IME
|
|
1455
1320
|
// composition/partial input while still providing live numeric updates when possible.
|
|
1456
1321
|
const allowedNonNumericKeys = getAllowedNonNumericKeys();
|
|
1457
|
-
const isValidCharacterString = Array.from(targetValue).every(ch => isNumeralChar(ch) || ANY_MINUS_DETECT_RE.test(ch) || allowedNonNumericKeys.has(ch)
|
|
1322
|
+
const isValidCharacterString = Array.from(targetValue).every(ch => isNumeralChar(ch) || ANY_MINUS_DETECT_RE.test(ch) || allowedNonNumericKeys.has(ch) ||
|
|
1323
|
+
// Bidi/format controls are stripped by `parseNumber`; don't let them reject the string
|
|
1324
|
+
// (RTL locales insert them around exponent/currency signs, e.g. scientific notation).
|
|
1325
|
+
FORMAT_CONTROL_DETECT_RE.test(ch));
|
|
1458
1326
|
if (!isValidCharacterString) {
|
|
1459
1327
|
return;
|
|
1460
1328
|
}
|
|
@@ -1504,7 +1372,7 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1504
1372
|
if (event.key === symbol) {
|
|
1505
1373
|
const symbolIndex = inputValue.indexOf(symbol);
|
|
1506
1374
|
const isSymbolHighlighted = selectionContainsIndex(symbolIndex);
|
|
1507
|
-
isAllowedNonNumericKey =
|
|
1375
|
+
isAllowedNonNumericKey = symbolIndex === -1 || isAllSelected || isSymbolHighlighted;
|
|
1508
1376
|
}
|
|
1509
1377
|
});
|
|
1510
1378
|
const isNavigateKey = NAVIGATE_KEYS.has(event.key);
|
|
@@ -1517,12 +1385,18 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1517
1385
|
event.which === 229 || event.altKey && !isStepKey || event.ctrlKey || event.metaKey || isAllowedNonNumericKey || isNumeralChar(event.key) || isNavigateKey) {
|
|
1518
1386
|
return;
|
|
1519
1387
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1388
|
+
|
|
1389
|
+
// Home/End jump to the corresponding bound, but only when that bound is defined.
|
|
1390
|
+
let boundaryValue = null;
|
|
1391
|
+
if (event.key === 'Home' && min != null) {
|
|
1392
|
+
boundaryValue = min;
|
|
1393
|
+
} else if (event.key === 'End' && max != null) {
|
|
1394
|
+
boundaryValue = max;
|
|
1395
|
+
}
|
|
1522
1396
|
|
|
1523
1397
|
// Let the browser handle multi-character keys we don't act on (PageUp, Insert, F-keys,
|
|
1524
1398
|
// Home/End without min/max); invalid single characters are still blocked below.
|
|
1525
|
-
if (event.key.length > 1 && !isStepKey &&
|
|
1399
|
+
if (event.key.length > 1 && !isStepKey && boundaryValue === null) {
|
|
1526
1400
|
return;
|
|
1527
1401
|
}
|
|
1528
1402
|
|
|
@@ -1534,12 +1408,14 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1534
1408
|
const amount = getStepAmount(event);
|
|
1535
1409
|
|
|
1536
1410
|
// Prevent insertion of text or caret from moving.
|
|
1537
|
-
|
|
1411
|
+
event.preventDefault();
|
|
1412
|
+
event.stopPropagation();
|
|
1538
1413
|
const commitDetails = createGenericEventDetails(keyboard, nativeEvent);
|
|
1539
1414
|
let changed = false;
|
|
1540
|
-
if (
|
|
1415
|
+
if (isStepKey || boundaryValue !== null) {
|
|
1541
1416
|
allowInputSyncRef.current = true;
|
|
1542
|
-
|
|
1417
|
+
}
|
|
1418
|
+
if (isStepKey) {
|
|
1543
1419
|
// When stepping from the synced numeric state, refresh the commit ref to the current
|
|
1544
1420
|
// value so a canceled step can't commit a stale `lastChangedValueRef` left over from an
|
|
1545
1421
|
// earlier change (mirrors the button path).
|
|
@@ -1552,15 +1428,14 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1552
1428
|
event: nativeEvent,
|
|
1553
1429
|
reason: keyboard
|
|
1554
1430
|
});
|
|
1555
|
-
} else if (
|
|
1556
|
-
|
|
1557
|
-
changed = setValue(min, createChangeEventDetails(keyboard, nativeEvent));
|
|
1558
|
-
} else if (willSetEnd) {
|
|
1559
|
-
allowInputSyncRef.current = true;
|
|
1560
|
-
changed = setValue(max, createChangeEventDetails(keyboard, nativeEvent));
|
|
1431
|
+
} else if (boundaryValue !== null) {
|
|
1432
|
+
changed = setValue(boundaryValue, createChangeEventDetails(keyboard, nativeEvent));
|
|
1561
1433
|
}
|
|
1434
|
+
|
|
1435
|
+
// `changed` is only true when `setValue` applied the change, which records the stored
|
|
1436
|
+
// (clamped/snapped) value, so commit that rather than the pre-validation input.
|
|
1562
1437
|
if (changed) {
|
|
1563
|
-
onValueCommitted(lastChangedValueRef.current
|
|
1438
|
+
onValueCommitted(lastChangedValueRef.current, commitDetails);
|
|
1564
1439
|
}
|
|
1565
1440
|
},
|
|
1566
1441
|
onPaste(event) {
|
|
@@ -1571,6 +1446,7 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1571
1446
|
try {
|
|
1572
1447
|
pastedData = event.clipboardData?.getData('text/plain') ?? '';
|
|
1573
1448
|
} catch {
|
|
1449
|
+
/* istanbul ignore else -- `process.env.NODE_ENV` is a build-time constant under test */
|
|
1574
1450
|
if (process.env.NODE_ENV !== 'production') {
|
|
1575
1451
|
const ownerStackMessage = SafeReact.captureOwnerStack?.() || '';
|
|
1576
1452
|
warn('<NumberField.Input> could not read clipboard text during paste handling.', ownerStackMessage);
|
|
@@ -1583,9 +1459,12 @@ const NumberFieldInput = /*#__PURE__*/React.forwardRef(function NumberFieldInput
|
|
|
1583
1459
|
|
|
1584
1460
|
// Insert the pasted text at the caret/selection instead of replacing the entire value,
|
|
1585
1461
|
// matching native input behavior (e.g. pasting "5" into "123|" yields "1235").
|
|
1462
|
+
// The component renders `type="text"`, which always reports a selection range. Overriding
|
|
1463
|
+
// `type` with a selection-less one (`email`, `number`) is unsupported either way: the caret
|
|
1464
|
+
// restore above throws on those, so there is no working behavior to preserve here.
|
|
1586
1465
|
const input = event.currentTarget;
|
|
1587
|
-
const selectionStart = input.selectionStart
|
|
1588
|
-
const selectionEnd = input.selectionEnd
|
|
1466
|
+
const selectionStart = input.selectionStart;
|
|
1467
|
+
const selectionEnd = input.selectionEnd;
|
|
1589
1468
|
const nextText = inputValue.slice(0, selectionStart) + pastedData + inputValue.slice(selectionEnd);
|
|
1590
1469
|
const parsedValue = parseNumber(nextText, locale, formatOptionsRef.current);
|
|
1591
1470
|
if (parsedValue !== null) {
|