{
return (
-
+
{renderButton('decrement')}
{renderInputElement()}
{renderButton('increment')}
{helper &&
}
- {inputUpdated && (
-
- {inputUpdated}
-
- )}
+
+ {announcement}
+
);
};
diff --git a/src/tedi/providers/label-provider/labels-map.ts b/src/tedi/providers/label-provider/labels-map.ts
index 09b4cf9d2..850b7971e 100644
--- a/src/tedi/providers/label-provider/labels-map.ts
+++ b/src/tedi/providers/label-provider/labels-map.ts
@@ -1259,25 +1259,29 @@ export const labelsMap = validateDefaultLabels({
ru: 'Нижний колонтитул',
},
'numberField.decrement': {
- description: 'Label for screen-reader for number field decrease button',
+ description: 'Label for screen-reader for number field decrease button. Second argument is the field label.',
components: ['NumberField'],
- et: (count: string | number) => `Vähenda ${count} võrra`,
- en: (count: string | number) => `Decrease by ${count}`,
- ru: (count: string | number) => `Уменьшить на ${count}`,
+ et: (count: string | number, field?: string) =>
+ field ? `Vähenda välja "${field}" ${count} võrra` : `Vähenda ${count} võrra`,
+ en: (count: string | number, field?: string) => (field ? `Decrease ${field} by ${count}` : `Decrease by ${count}`),
+ ru: (count: string | number, field?: string) =>
+ field ? `Уменьшить «${field}» на ${count}` : `Уменьшить на ${count}`,
},
'numberField.increment': {
- description: 'Label for screen-reader for number field increase button',
+ description: 'Label for screen-reader for number field increase button. Second argument is the field label.',
components: ['NumberField'],
- et: (count: string | number) => `Suurenda ${count} võrra`,
- en: (count: string | number) => `Increase by ${count}`,
- ru: (count: string | number) => `Увеличить на ${count}`,
+ et: (count: string | number, field?: string) =>
+ field ? `Suurenda välja "${field}" ${count} võrra` : `Suurenda ${count} võrra`,
+ en: (count: string | number, field?: string) => (field ? `Increase ${field} by ${count}` : `Increase by ${count}`),
+ ru: (count: string | number, field?: string) =>
+ field ? `Увеличить «${field}» на ${count}` : `Увеличить на ${count}`,
},
'numberField.quantityUpdated': {
description: 'Label for screen-reader when quantity get updated by button click',
components: ['NumberField'],
et: (count: string | number) => `Uuendatud. Uus väärtus ${count}`,
en: (count: string | number) => `Updated. New value ${count}`,
- ru: (count: string | number) => `Ууэндатуд. Уус вяэртус ${count}`,
+ ru: (count: string | number) => `Обновлено. Новое значение ${count}`,
},
'sidenav.backToMainMenu': {
description: 'Side navigation label',
From 1e5d0465aee9853b5595c40bf666f4139d684d2a Mon Sep 17 00:00:00 2001
From: Airike Jaska <95303654+airikej@users.noreply.github.com>
Date: Wed, 29 Jul 2026 15:22:37 +0300
Subject: [PATCH 2/2] fix(number-field): cr fixes #59
---
.../form/number-field/number-field.spec.tsx | 22 ++++++++++++++++---
.../form/number-field/number-field.tsx | 9 ++++----
2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/src/tedi/components/form/number-field/number-field.spec.tsx b/src/tedi/components/form/number-field/number-field.spec.tsx
index c6d761e32..73ef6198a 100644
--- a/src/tedi/components/form/number-field/number-field.spec.tsx
+++ b/src/tedi/components/form/number-field/number-field.spec.tsx
@@ -194,6 +194,13 @@ describe('NumberField component', () => {
expect(input).toHaveAttribute('inputmode', 'decimal');
});
+ it('treats a supplied value as controlled even without onChange (display + aria stay in sync)', () => {
+ render(
);
+ const input = screen.getByRole('spinbutton');
+ expect(input).toHaveValue('7');
+ expect(input).toHaveAttribute('aria-valuenow', '7');
+ });
+
describe('accessibility', () => {
it('associates the label with the spinbutton even without an explicit id', () => {
render(
);
@@ -228,16 +235,25 @@ describe('NumberField component', () => {
it('announces value changes driven by an external/controlled update', () => {
jest.useFakeTimers();
try {
- const { rerender } = render(
);
+ const { rerender } = render(
+
+
+
+ );
const status = screen.getByRole('status');
expect(status).toHaveTextContent('');
- rerender(
);
+ rerender(
+
+
+
+ );
act(() => {
jest.advanceTimersByTime(150);
});
- expect(status).toHaveTextContent('numberField.quantityUpdated');
+ // Assert the user-facing localized announcement, not the fallback key.
+ expect(status).toHaveTextContent('Updated. New value 2');
} finally {
jest.useRealTimers();
}
diff --git a/src/tedi/components/form/number-field/number-field.tsx b/src/tedi/components/form/number-field/number-field.tsx
index 11fa2904f..c5e4cdfb0 100644
--- a/src/tedi/components/form/number-field/number-field.tsx
+++ b/src/tedi/components/form/number-field/number-field.tsx
@@ -138,7 +138,8 @@ export const NumberField = (props: NumberFieldProps) => {
const [inputInnerValue, setInputInnerValue] = useState
(defaultValue);
const [displayValue, setDisplayValue] = useState(() => formatNumber(value ?? defaultValue));
- const currentValue: number | undefined = onChange && typeof value !== 'undefined' ? value : inputInnerValue;
+ const isControlled = value !== undefined;
+ const currentValue: number | undefined = isControlled ? value : inputInnerValue;
const announceShowTimer = useRef>();
const announceHideTimer = useRef>();
@@ -219,7 +220,7 @@ export const NumberField = (props: NumberFieldProps) => {
returnValue = roundValue(returnValue);
onChange?.(returnValue);
- setInputInnerValue(returnValue);
+ if (!isControlled) setInputInnerValue(returnValue);
setDisplayValue(formatNumber(returnValue));
};
@@ -229,7 +230,7 @@ export const NumberField = (props: NumberFieldProps) => {
if (rawValue === '') {
if (currentValue !== undefined) {
onChange?.(undefined);
- setInputInnerValue(undefined);
+ if (!isControlled) setInputInnerValue(undefined);
}
return;
}
@@ -248,7 +249,7 @@ export const NumberField = (props: NumberFieldProps) => {
if (rounded !== currentValue) {
onChange?.(rounded);
- setInputInnerValue(rounded);
+ if (!isControlled) setInputInnerValue(rounded);
}
};