Skip to content

feat(Slider): Recognize localized number input - #12599

Open
rebeccaalpert wants to merge 2 commits into
patternfly:mainfrom
rebeccaalpert:slider
Open

feat(Slider): Recognize localized number input#12599
rebeccaalpert wants to merge 2 commits into
patternfly:mainfrom
rebeccaalpert:slider

Conversation

@rebeccaalpert

@rebeccaalpert rebeccaalpert commented Jul 28, 2026

Copy link
Copy Markdown
Member

I went with a locale prop since it seemed like it would provide a better, more consistent solution. There are a lot of ways people could be setting language. Examples: https://github.com/i18next/i18next-browser-languageDetector#introduction.

OpenShift (the internationalized system I'm most familiar with) doesn't update the lang attribute when it changes languages, so I didn't want to rely on that.

What: Closes #12052

Additional issues:

Summary by CodeRabbit

  • New Features
    • Added locale-aware decimal formatting and parsing for slider value inputs (including comma decimals).
    • Added an optional locale setting to control displayed decimal separators.
  • Bug Fixes
    • Improved slider ↔ text input syncing to preserve in-progress edits and reliably apply normalized values on blur/enter.
  • Tests
    • Added coverage for localized decimal formatting/parsing, including separator validation and slider input behavior.
  • Documentation
    • Updated TypeScript internationalization typings to include es2018.intl.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Slider value inputs now support locale-aware decimal display and parsing through Intl.NumberFormat. The Slider tracks formatted text during editing, normalizes values on commit, synchronizes changes from input and slider interactions, and exposes an optional locale prop.

Changes

Localized Slider Input

Layer / File(s) Summary
Locale-aware decimal helpers
packages/react-core/src/helpers/util.ts, packages/react-core/src/helpers/__tests__/util.test.ts, packages/tsconfig.base.json
Adds strict locale-aware decimal parsing and formatting, corresponding separator and digit-handling tests, and es2018.intl typings.
Slider localized input synchronization
packages/react-core/src/components/Slider/Slider.tsx
Adds the locale prop, localized text input formatting/parsing, focus-aware display state, and synchronization across typing, blur, dragging, rail clicks, and keyboard changes.
Localized Slider behavior validation
packages/react-core/src/components/Slider/__tests__/Slider.test.tsx
Tests localized decimal display and comma-based input normalization for de-DE.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SliderInput
  participant Slider
  participant parseLocalizedDecimal
  participant formatLocalizedDecimal
  participant ParentOnChange
  SliderInput->>Slider: Enter localized decimal
  Slider->>parseLocalizedDecimal: Parse value with Intl.NumberFormat
  parseLocalizedDecimal-->>Slider: Return numeric value
  Slider->>ParentOnChange: Report numeric value and display updater
  Slider->>formatLocalizedDecimal: Format synchronized value
  formatLocalizedDecimal-->>SliderInput: Return localized display value
Loading

Possibly related PRs

Suggested reviewers: kmcfaul, thatblindgeye

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the localized number input feature.
Linked Issues check ✅ Passed The changes address #12052 by accepting localized decimals, keeping slider/input in sync, and normalizing user input with locale support.
Out of Scope Changes check ✅ Passed The extra helper tests and tsconfig update directly support the localized-decimal slider work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rebeccaalpert
rebeccaalpert force-pushed the slider branch 7 times, most recently from 111a2a9 to f5ccd76 Compare July 28, 2026 14:58
@rebeccaalpert
rebeccaalpert marked this pull request as ready for review July 28, 2026 15:13

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-core/src/components/Slider/Slider.tsx`:
- Around line 131-140: Initialize inputDisplayValue in the Slider component
using the locale prop instead of the hardcoded 'en' locale, so the first render
is formatted consistently with the requested locale and avoids an incorrect
initial display.
- Around line 170-183: The functional updater inside
setLocalInputValueWithDisplay must remain pure. Resolve and apply the numeric
state through setLocalInputValue, then move updateInputDisplay outside that
updater into a post-commit effect or separate update while preserving the
isInputFocusedRef behavior.
- Around line 152-190: Update the updateInputDisplay useCallback dependency
array to include locale, ensuring formatting reflects runtime locale changes
while preserving all existing callers and behavior.

In `@packages/react-core/src/helpers/util.ts`:
- Around line 602-611: Update parseLocalizedDecimal to wrap
Intl.NumberFormat(locale) construction and subsequent locale-based parsing setup
in a try/catch. On invalid locale input, fall back to Intl.NumberFormat() and
continue using its group and decimal symbols so Slider render/input callbacks do
not throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ea4875c7-d8e4-4842-8ece-4db9acfaa07f

📥 Commits

Reviewing files that changed from the base of the PR and between c4086a0 and f5ccd76.

⛔ Files ignored due to path filters (1)
  • packages/react-core/src/components/Slider/__tests__/__snapshots__/Slider.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • packages/react-core/src/components/Slider/Slider.tsx
  • packages/react-core/src/components/Slider/__tests__/Slider.test.tsx
  • packages/react-core/src/helpers/__tests__/util.test.ts
  • packages/react-core/src/helpers/util.ts
  • packages/tsconfig.base.json

Comment thread packages/react-core/src/components/Slider/Slider.tsx Outdated
Comment thread packages/react-core/src/components/Slider/Slider.tsx
Comment thread packages/react-core/src/components/Slider/Slider.tsx
Comment thread packages/react-core/src/helpers/util.ts Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-core/src/helpers/util.ts`:
- Around line 627-632: Update the numeric parsing logic around the cleaned value
and parseFloat so it validates the entire normalized input before conversion,
returning NaN for malformed tokens such as embedded letters, misplaced
separators, or multiple signs. Preserve valid numeric formats and ensure Slider
blur handling only commits fully valid values.
- Around line 607-630: Update the number normalization flow around groupSymbol,
decimalSymbol, and cleaned so grouping removal is only applied when
formatter.formatToParts reports an actual group separator, never by falling back
to the decimal separator. Before replacing the locale decimal symbol with '.',
reject inputs containing alternate grouping characters that would otherwise be
silently merged, preserving valid locale-formatted values while making cases
such as comma-decimal en-US and mixed-separator de-DE inputs invalid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 86eb8ae5-f520-44c6-9103-d0319c3c8b15

📥 Commits

Reviewing files that changed from the base of the PR and between f5ccd76 and 24b0a32.

⛔ Files ignored due to path filters (1)
  • packages/react-core/src/components/Slider/__tests__/__snapshots__/Slider.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • packages/react-core/src/components/Slider/Slider.tsx
  • packages/react-core/src/components/Slider/__tests__/Slider.test.tsx
  • packages/react-core/src/helpers/__tests__/util.test.ts
  • packages/react-core/src/helpers/util.ts
  • packages/tsconfig.base.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/tsconfig.base.json
  • packages/react-core/src/helpers/tests/util.test.ts
  • packages/react-core/src/components/Slider/tests/Slider.test.tsx
  • packages/react-core/src/components/Slider/Slider.tsx

Comment thread packages/react-core/src/helpers/util.ts Outdated
Comment thread packages/react-core/src/helpers/util.ts Outdated

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
packages/react-core/src/helpers/util.ts (1)

653-661: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Hardcoded 3-digit grouping breaks Indian-numbering locales (en-IN, hi-IN, etc.).

The grouping-structure check assumes every group after the first is exactly 3 digits (/^\d{3}$/). This holds for Western grouping but not for the Indian numbering system, which Intl.NumberFormat('en-IN') (and similar locales) uses: e.g. 1234567.89 formats as "12,34,567.89" — groups of 2 digits after the initial 3.

Tracing the current code with an en-IN formatter on '12,34,567.89': intPart.split(',')['12','34','567']; groups.slice(1) = ['34','567'], and '34' fails /^\d{3}$/, so the function returns NaN for a value that the very same formatter would itself produce via .format(). This breaks slider/input synchronization for any consumer using an Indian-numbering locale with values ≥ 1 lakh (100,000) — directly undermining this PR's goal of general non-English locale support.

A more robust check would validate grouping structure against the formatter's own output (e.g., compare the input's grouping to what formatter.format() produces for the parsed integer) rather than hardcoding a single Western pattern. Happy to help draft this if useful.

new Intl.NumberFormat("en-IN").format(1234567.89); // "12,34,567.89" — not X,XXX,XXX
🌐 Web verification

Confirmed via MDN/W3C: const enINFormatter = new Intl.NumberFormat('en-IN'); console.log(Indian English: ${enINFormatter.format(number)}); // Output: Indian English: 12,34,567.89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-core/src/helpers/util.ts` around lines 653 - 661, Update the
grouping validation in the integer-parsing logic around intPart and groupSymbol
to validate against the configured Intl.NumberFormat output for the parsed
integer, rather than requiring every trailing group to contain three digits.
Preserve rejection of malformed or silently mergeable grouping, while accepting
valid Indian-style formats such as en-IN’s “12,34,567”.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/react-core/src/helpers/util.ts`:
- Around line 653-661: Update the grouping validation in the integer-parsing
logic around intPart and groupSymbol to validate against the configured
Intl.NumberFormat output for the parsed integer, rather than requiring every
trailing group to contain three digits. Preserve rejection of malformed or
silently mergeable grouping, while accepting valid Indian-style formats such as
en-IN’s “12,34,567”.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2abcc166-fb42-471e-a5cc-cca98a928e53

📥 Commits

Reviewing files that changed from the base of the PR and between 24b0a32 and 82dd5e0.

📒 Files selected for processing (2)
  • packages/react-core/src/helpers/__tests__/util.test.ts
  • packages/react-core/src/helpers/util.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/helpers/tests/util.test.ts

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/react-core/src/helpers/util.ts`:
- Around line 612-650: Update parseLocalizedDecimal to normalize locale-specific
numeral glyphs to ASCII before validation, using a digit map derived from
formatter.formatToParts. Apply this normalization to the trimmed input and to
the grouped-format comparison, while preserving the existing separator,
grouping, sign, and fractional validation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5881202d-1816-4d60-9b2b-f37297d4d571

📥 Commits

Reviewing files that changed from the base of the PR and between 82dd5e0 and bd10366.

📒 Files selected for processing (2)
  • packages/react-core/src/helpers/__tests__/util.test.ts
  • packages/react-core/src/helpers/util.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/helpers/tests/util.test.ts

Comment on lines +612 to +650
const normalizedString = value.trim();

// Reject '.' / ',' when they are neither this locale's decimal nor its reported group separator
// (e.g. mixed-separator de-DE input like "1,234.56").
for (const sep of ['.', ','] as const) {
if (sep !== decimalSymbol && sep !== groupSymbol && normalizedString.includes(sep)) {
return NaN;
}
}

const decimalIndex = normalizedString.indexOf(decimalSymbol);
if (decimalIndex !== -1 && normalizedString.indexOf(decimalSymbol, decimalIndex + decimalSymbol.length) !== -1) {
return NaN;
}

let intPart = decimalIndex === -1 ? normalizedString : normalizedString.slice(0, decimalIndex);
const fracPart = decimalIndex === -1 ? undefined : normalizedString.slice(decimalIndex + decimalSymbol.length);

let sign = '';
if (intPart.startsWith('-') || intPart.startsWith('+')) {
sign = intPart[0] === '-' ? '-' : '';
intPart = intPart.slice(1);
}

if (fracPart !== undefined && groupSymbol && fracPart.includes(groupSymbol)) {
return NaN;
}

// If grouping appears, require locale-valid groups so values like en-US "50,2" are invalid
// instead of silently merging into 502, while accepting locales such as en-IN "12,34,567".
if (groupSymbol && intPart.includes(groupSymbol)) {
const digitsOnly = intPart.split(groupSymbol).join('');
if (!/^\d+$/.test(digitsOnly) || formatter.format(Number(digitsOnly)) !== intPart) {
return NaN;
}
intPart = digitsOnly;
}

if (!/^\d*$/.test(intPart) || (fracPart !== undefined && !/^\d*$/.test(fracPart))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node <<'JS'
const formatter = new Intl.NumberFormat('ar-EG');
console.log(formatter.format(50.2));
console.log(formatter.formatToParts(1234567890));
JS

Repository: patternfly/patternfly-react

Length of output: 418


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the helper implementation and relevant usages without running repository code.
fd -a 'util\.ts$' packages/react-core/src/helpers 2>/dev/null | sed 's#^\./##'
printf '\n--- util.ts outline around parseLocalizedDecimal/formatLocalizedDecimal ---\n'
ast-grep outline packages/react-core/src/helpers/util.ts --match parseLocalizedDecimal --view expanded || true
printf '\n--- relevant source lines ---\n'
sed -n '560,680p' packages/react-core/src/helpers/util.ts

printf '\n--- relevant tests/usages ---\n'
rg -n "parseLocalizedDecimal|formatLocalizedDecimal|ar-EG|٥٠٫٢|formattedDigits|localized" packages -g '!**/node_modules/**' || true

Repository: patternfly/patternfly-react

Length of output: 10749


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node <<'JS'
const locales = ['ar-EG', 'ar-SA', 'fa-IR', 'hi-IN', 'bn-IN', 'de-DE', 'en-IN'];
for (const locale of locales) {
  const f = new Intl.NumberFormat(locale);
  console.log(locale, 'format(500.2):', f.format(500.2));
  const parts = f.formatToParts(1234567890);
  const locals = parts.filter(p => p.type === 'integer').map(p => p.value).join('');
  console.log('  integer glyphs for 1234567890:', locals);
}
JS

printf '\n--- Slider formatter construction ---\n'
sed -n '1,190p' packages/react-core/src/components/Slider/Slider.tsx

Repository: patternfly/patternfly-react

Length of output: 7846


Normalize locale-specific numeral glyphs before validating.

formatLocalizedDecimal can return values like ar-EG ٥٠٫٢, but parseLocalizedDecimal only accepts ASCII digits, so the displayed value roundtrips to NaN. Build an ASCII-normalization map from formatToParts, normalize both the input and the grouped formatting comparison, then validate.

Proposed fix
-  const normalizedString = value.trim();
+  const localDigits = formatter
+    .formatToParts(1234567890)
+    .filter((part) => part.type === 'integer')
+    .map((part) => part.value)
+    .join('');
+  const digitMap = new Map([...localDigits].map((digit, index) => [digit, '1234567890'[index]]));
+  const normalizeDigits = (input: string) => [...input].map((char) => digitMap.get(char) || char).join('');
+  const normalizedString = normalizeDigits(value.trim());
...
-    if (!/^\d+$/.test(digitsOnly) || formatter.format(Number(digitsOnly)) !== intPart) {
+    if (!/^\d+$/.test(digitsOnly) || normalizeDigits(formatter.format(Number(digitsOnly))) !== intPart) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const normalizedString = value.trim();
// Reject '.' / ',' when they are neither this locale's decimal nor its reported group separator
// (e.g. mixed-separator de-DE input like "1,234.56").
for (const sep of ['.', ','] as const) {
if (sep !== decimalSymbol && sep !== groupSymbol && normalizedString.includes(sep)) {
return NaN;
}
}
const decimalIndex = normalizedString.indexOf(decimalSymbol);
if (decimalIndex !== -1 && normalizedString.indexOf(decimalSymbol, decimalIndex + decimalSymbol.length) !== -1) {
return NaN;
}
let intPart = decimalIndex === -1 ? normalizedString : normalizedString.slice(0, decimalIndex);
const fracPart = decimalIndex === -1 ? undefined : normalizedString.slice(decimalIndex + decimalSymbol.length);
let sign = '';
if (intPart.startsWith('-') || intPart.startsWith('+')) {
sign = intPart[0] === '-' ? '-' : '';
intPart = intPart.slice(1);
}
if (fracPart !== undefined && groupSymbol && fracPart.includes(groupSymbol)) {
return NaN;
}
// If grouping appears, require locale-valid groups so values like en-US "50,2" are invalid
// instead of silently merging into 502, while accepting locales such as en-IN "12,34,567".
if (groupSymbol && intPart.includes(groupSymbol)) {
const digitsOnly = intPart.split(groupSymbol).join('');
if (!/^\d+$/.test(digitsOnly) || formatter.format(Number(digitsOnly)) !== intPart) {
return NaN;
}
intPart = digitsOnly;
}
if (!/^\d*$/.test(intPart) || (fracPart !== undefined && !/^\d*$/.test(fracPart))) {
const localDigits = formatter
.formatToParts(1234567890)
.filter((part) => part.type === 'integer')
.map((part) => part.value)
.join('');
const digitMap = new Map([...localDigits].map((digit, index) => [digit, '1234567890'[index]]));
const normalizeDigits = (input: string) => [...input].map((char) => digitMap.get(char) || char).join('');
const normalizedString = normalizeDigits(value.trim());
// Reject '.' / ',' when they are neither this locale's decimal nor its reported group separator
// (e.g. mixed-separator de-DE input like "1,234.56").
for (const sep of ['.', ','] as const) {
if (sep !== decimalSymbol && sep !== groupSymbol && normalizedString.includes(sep)) {
return NaN;
}
}
const decimalIndex = normalizedString.indexOf(decimalSymbol);
if (decimalIndex !== -1 && normalizedString.indexOf(decimalSymbol, decimalIndex + decimalSymbol.length) !== -1) {
return NaN;
}
let intPart = decimalIndex === -1 ? normalizedString : normalizedString.slice(0, decimalIndex);
const fracPart = decimalIndex === -1 ? undefined : normalizedString.slice(decimalIndex + decimalSymbol.length);
let sign = '';
if (intPart.startsWith('-') || intPart.startsWith('+')) {
sign = intPart[0] === '-' ? '-' : '';
intPart = intPart.slice(1);
}
if (fracPart !== undefined && groupSymbol && fracPart.includes(groupSymbol)) {
return NaN;
}
// If grouping appears, require locale-valid groups so values like en-US "50,2" are invalid
// instead of silently merging into 502, while accepting locales such as en-IN "12,34,567".
if (groupSymbol && intPart.includes(groupSymbol)) {
const digitsOnly = intPart.split(groupSymbol).join('');
if (!/^\d+$/.test(digitsOnly) || normalizeDigits(formatter.format(Number(digitsOnly))) !== intPart) {
return NaN;
}
intPart = digitsOnly;
}
if (!/^\d*$/.test(intPart) || (fracPart !== undefined && !/^\d*$/.test(fracPart))) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-core/src/helpers/util.ts` around lines 612 - 650, Update
parseLocalizedDecimal to normalize locale-specific numeral glyphs to ASCII
before validation, using a digit map derived from formatter.formatToParts. Apply
this normalization to the trimmed input and to the grouped-format comparison,
while preserving the existing separator, grouping, sign, and fractional
validation behavior.

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.

Bug - [Slider with Input] - [should accept , as comma for non en US locales]

1 participant