@@ -8,7 +8,15 @@ import { useFieldRootContext } from '../../field/root/FieldRootContext';
8
8
import { useFieldControlValidation } from '../../field/control/useFieldControlValidation' ;
9
9
import { fieldValidityMapping } from '../../field/utils/constants' ;
10
10
import { DEFAULT_STEP } from '../utils/constants' ;
11
- import { ARABIC_RE , HAN_RE , getNumberLocaleDetails , parseNumber } from '../utils/parse' ;
11
+ import {
12
+ ARABIC_RE ,
13
+ HAN_RE ,
14
+ FULLWIDTH_RE ,
15
+ getNumberLocaleDetails ,
16
+ parseNumber ,
17
+ UNICODE_MINUS_SIGNS ,
18
+ UNICODE_PLUS_SIGNS ,
19
+ } from '../utils/parse' ;
12
20
import type { NumberFieldRoot } from '../root/NumberFieldRoot' ;
13
21
import { stateAttributesMapping as numberFieldStateAttributesMapping } from '../utils/stateAttributesMapping' ;
14
22
import { useField } from '../../field/useField' ;
@@ -251,20 +259,40 @@ export const NumberFieldInput = React.forwardRef(function NumberFieldInput(
251
259
let isAllowedNonNumericKey = allowedNonNumericKeys . has ( event . key ) ;
252
260
253
261
const { decimal, currency, percentSign } = getNumberLocaleDetails (
254
- [ ] ,
262
+ locale ,
255
263
formatOptionsRef . current ,
256
264
) ;
257
265
258
266
const selectionStart = event . currentTarget . selectionStart ;
259
267
const selectionEnd = event . currentTarget . selectionEnd ;
260
268
const isAllSelected = selectionStart === 0 && selectionEnd === inputValue . length ;
261
269
262
- // Allow the minus key only if there isn't already a plus or minus sign, or if all the text
263
- // is selected, or if only the minus sign is highlighted.
264
- if ( event . key === '-' && allowedNonNumericKeys . has ( '-' ) ) {
265
- const isMinusHighlighted =
266
- selectionStart === 0 && selectionEnd === 1 && inputValue [ 0 ] === '-' ;
267
- isAllowedNonNumericKey = ! inputValue . includes ( '-' ) || isAllSelected || isMinusHighlighted ;
270
+ // Normalize handling of plus/minus signs (ASCII and unicode variants)
271
+ const anyMinus = [ '-' ] . concat ( UNICODE_MINUS_SIGNS ) ;
272
+ const anyPlus = [ '+' ] . concat ( UNICODE_PLUS_SIGNS ) ;
273
+
274
+ const containsAny = ( s : string , chars : string [ ] ) => chars . some ( ( ch ) => s . includes ( ch ) ) ;
275
+ const selectionIsExactlyCharAt = ( index : number ) =>
276
+ selectionStart === index && selectionEnd === index + 1 ;
277
+
278
+ if ( anyMinus . includes ( event . key ) && anyMinus . some ( ( k ) => allowedNonNumericKeys . has ( k ) ) ) {
279
+ // Only allow one sign unless replacing the existing one or all text is selected
280
+ const existingIndex = anyMinus . map ( ( ch ) => inputValue . indexOf ( ch ) ) . find ( ( i ) => i !== - 1 ) ;
281
+ const isReplacingExisting =
282
+ existingIndex != null && existingIndex !== - 1 && selectionIsExactlyCharAt ( existingIndex ) ;
283
+ isAllowedNonNumericKey =
284
+ ! containsAny ( inputValue , anyMinus . concat ( anyPlus ) ) ||
285
+ isAllSelected ||
286
+ isReplacingExisting ;
287
+ }
288
+ if ( anyPlus . includes ( event . key ) && anyPlus . some ( ( k ) => allowedNonNumericKeys . has ( k ) ) ) {
289
+ const existingIndex = anyPlus . map ( ( ch ) => inputValue . indexOf ( ch ) ) . find ( ( i ) => i !== - 1 ) ;
290
+ const isReplacingExisting =
291
+ existingIndex != null && existingIndex !== - 1 && selectionIsExactlyCharAt ( existingIndex ) ;
292
+ isAllowedNonNumericKey =
293
+ ! containsAny ( inputValue , anyMinus . concat ( anyPlus ) ) ||
294
+ isAllSelected ||
295
+ isReplacingExisting ;
268
296
}
269
297
270
298
// Only allow one of each symbol.
@@ -281,6 +309,7 @@ export const NumberFieldInput = React.forwardRef(function NumberFieldInput(
281
309
const isLatinNumeral = / ^ [ 0 - 9 ] $ / . test ( event . key ) ;
282
310
const isArabicNumeral = ARABIC_RE . test ( event . key ) ;
283
311
const isHanNumeral = HAN_RE . test ( event . key ) ;
312
+ const isFullwidthNumeral = FULLWIDTH_RE . test ( event . key ) ;
284
313
const isNavigateKey = NAVIGATE_KEYS . has ( event . key ) ;
285
314
286
315
if (
@@ -294,6 +323,7 @@ export const NumberFieldInput = React.forwardRef(function NumberFieldInput(
294
323
isAllowedNonNumericKey ||
295
324
isLatinNumeral ||
296
325
isArabicNumeral ||
326
+ isFullwidthNumeral ||
297
327
isHanNumeral ||
298
328
isNavigateKey
299
329
) {
0 commit comments