-
-
Notifications
You must be signed in to change notification settings - Fork 502
[number field] Reject implausibly grouped input text #5425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -104,7 +104,12 @@ export function getNumberLocaleDetails( | |
| return { ...result, decimal }; | ||
| } | ||
|
|
||
| export function parseNumber( | ||
| /** | ||
| * Runs the locale-aware normalization shared by `parseNumber` and | ||
| * `isPlausibleNumberInput`: separators, symbols, and numerals are reduced to an | ||
| * ASCII string where `.` is the only remaining separator candidate. | ||
| */ | ||
| function unformatNumber( | ||
| formattedNumber: string, | ||
| locale?: Intl.LocalesArgument, | ||
| options?: Intl.NumberFormatOptions, | ||
|
|
@@ -183,10 +188,56 @@ export function parseNumber( | |
| [HAN_RE, (ch: string) => String(Math.max(HAN_NUMERALS.indexOf(ch) - 1, 0))], | ||
| ]; | ||
|
|
||
| let unformatted = replacements.reduce((acc, [regex, replacement]) => { | ||
| const unformatted = replacements.reduce((acc, [regex, replacement]) => { | ||
| return regex ? acc.replace(regex, replacement as any) : acc; | ||
| }, input); | ||
|
|
||
| return { input, unformatted, isNegative }; | ||
| } | ||
|
|
||
| /** | ||
| * Whether raw input text keeps its meaning through `parseNumber`'s | ||
| * keep-last-dot collapse. Multiple surviving `.` separators are plausible only | ||
| * when they read as European-style grouping (`1.234.567.89`); anything else | ||
| * (`1.2.3`) would parse to a number whose decimal placement differs from the | ||
| * visible text, so live text entry should reject it rather than let the | ||
| * display and the parsed value diverge. | ||
| */ | ||
| export function isPlausibleNumberInput( | ||
| text: string, | ||
| locale?: Intl.LocalesArgument, | ||
| options?: Intl.NumberFormatOptions, | ||
| ) { | ||
| const { unformatted } = unformatNumber(text, locale, options); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For Useful? React with 👍 / 👎. |
||
|
|
||
| // Only the mantissa can contain grouping; the exponent is parsed as-is. | ||
| const [mantissa] = unformatted.split(/e/i); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
With scientific notation, a raw change such as Useful? React with 👍 / 👎. |
||
| const runs = mantissa.split('.'); | ||
|
|
||
| if (runs.length <= 2) { | ||
| return true; | ||
| } | ||
|
|
||
| const first = runs[0]; | ||
| const fraction = runs[runs.length - 1]; | ||
| const interior = runs.slice(1, -1); | ||
|
|
||
| return ( | ||
| /^\d{1,3}$/.test(first) && | ||
| interior.every((run) => /^\d{3}$/.test(run)) && | ||
| (fraction === '' || /^\d+$/.test(fraction)) | ||
| ); | ||
| } | ||
|
|
||
| export function parseNumber( | ||
| formattedNumber: string, | ||
| locale?: Intl.LocalesArgument, | ||
| options?: Intl.NumberFormatOptions, | ||
| ) { | ||
| const result = unformatNumber(formattedNumber, locale, options); | ||
| const { input, isNegative } = result; | ||
| let { unformatted } = result; | ||
|
|
||
| // Mixed-locale safety: keep only the last '.' as decimal | ||
| const lastDot = unformatted.lastIndexOf('.'); | ||
| if (lastDot !== -1) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this new guard rejects otherwise character-valid input on an untouched field,
allowInputSyncRefhas already been set tofalseand is not restored. Blurring then treats the unchanged field as manually edited and callsonValueCommittedwith its old value; a controlled value update before blur is also skipped by the root's input-sync effect, leaving stale displayed text. Restore the sync flag before returning when no input text was accepted.Useful? React with 👍 / 👎.