Skip to content

[number field] Reject implausibly grouped input text - #5425

Open
kirthi-b wants to merge 1 commit into
mui:masterfrom
kirthi-b:fix/number-field-parse-plausibility
Open

[number field] Reject implausibly grouped input text#5425
kirthi-b wants to merge 1 commit into
mui:masterfrom
kirthi-b:fix/number-field-parse-plausibility

Conversation

@kirthi-b

@kirthi-b kirthi-b commented Aug 6, 2026

Copy link
Copy Markdown

Raw text entry (drop, IME composition, autofill) accepted strings like 1.2.3, which parseNumber collapses to 12.3. The input kept showing 1.2.3 while value and the hidden input held 12.3 until blur, so a form submitted in between sent a number the user never saw.

  • NumberField.Input now rejects change-event text whose separator structure is not plausible for the locale, next to the existing character check. Multi-dot strings are only accepted when they read as European-style grouping.
  • 1.234.567.89 and mixed-locale paste normalization behave as before. parseNumber itself is unchanged.
  • Intentional change: 1..5 was previously accepted from raw text entry and parsed as 1.5; it is now rejected there. On blur and paste it still normalizes to 1.5.

Fixes #5424

Raw text entry (drop, IME composition, autofill) accepted strings like
1.2.3 that parseNumber collapses to 12.3, so the input displayed one
number while the value and the hidden input submitted another until
blur. Validate the separator structure on the change path: multi-dot
strings are only accepted when they read as European-style grouping
(1.234.567.89), keeping the mixed-locale normalization intact.

Fixes mui#5424
@code-infra-dashboard

code-infra-dashboard Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bundle size

Bundle Parsed size Gzip size
@base-ui/react 🔺+373B(+0.08%) 🔺+163B(+0.11%)

Details of bundle changes

Performance

Total duration: 1,249.16 ms -179.23 ms(-12.5%) | Renders: 78 (+0) | Paint: 1,962.24 ms -275.67 ms(-12.3%)

Test Duration Renders
Tabs mount (200 instances) 182.43 ms ▼-46.75 ms(-20.4%) 3 (+0)
Select mount (200 instances) 143.07 ms ▼-42.03 ms(-22.7%) 3 (+0)
Popover mount (300 instances) 64.67 ms ▼-24.00 ms(-27.1%) 1 (+0)
Select open (500 options) 39.51 ms ▼-16.83 ms(-29.9%) 14 (+0)
Tooltip mount (300 contained roots) 44.62 ms ▼-12.80 ms(-22.3%) 1 (+0)

…and 1 more (+9 within noise) — details

Metric alarms

Test Metric Change
Combobox type — 500 items, narrows to ~11 (type "Row 25") bench:paint#combobox-open 🔺 +15.60 ms
Combobox type — 500 items, narrows to ~11 (type "Row 25") bench:paint 🔺 +15.60 ms

Check out the code infra dashboard for more information about this PR.

@netlify

netlify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Deploy Preview for base-ui ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit f01e625
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/6a74b7a22859290008a3c564
😎 Deploy Preview https://deploy-preview-5425--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f01e625077

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

locale?: Intl.LocalesArgument,
options?: Intl.NumberFormatOptions,
) {
const { unformatted } = unformatNumber(text, locale, options);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate grouping before locale normalization

For locale="de-DE"—and when an omitted locale resolves to one using . for grouping—unformatNumber removes every dot before this function inspects the runs. Consequently, isPlausibleNumberInput('1.2.3', 'de-DE') sees 123 and returns true; NumberField explicitly permits repeated group symbols, so this text remains visible while the hidden input becomes 123, bypassing the guard intended to reject implausible grouping. Validate raw locale-group runs before stripping them.

Useful? React with 👍 / 👎.

const { unformatted } = unformatNumber(text, locale, options);

// Only the mantissa can contain grouping; the exponent is parsed as-is.
const [mantissa] = unformatted.split(/e/i);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate separators after the exponent marker

With scientific notation, a raw change such as 1E2.3.4 passes because this split discards the exponent from validation. parseNumber then collapses the dots into 1e23.4, which parseFloat reads as 1e23, while the input continues displaying 1E2.3.4; autofill, drop, and IME paths can therefore retain the same visible/hidden-value divergence this guard targets. Validate the exponent structure as well, or reject separators within it.

Useful? React with 👍 / 👎.

Comment on lines +281 to +282
if (!isPlausibleNumberInput(targetValue, locale, formatOptionsRef.current)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset dirty state when rejecting the change

When this new guard rejects otherwise character-valid input on an untouched field, allowInputSyncRef has already been set to false and is not restored. Blurring then treats the unchanged field as manually edited and calls onValueCommitted with 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[number-field] Input displays one number while submitting another for implausibly formatted text

1 participant