diff --git a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap
index 5ab6de9a433..de67a54675d 100644
--- a/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap
+++ b/src/components/date_picker/super_date_picker/quick_select_popover/__snapshots__/quick_select_popover.test.tsx.snap
@@ -136,6 +136,7 @@ exports[`EuiQuickSelectPanels customQuickSelectPanels should render custom panel
aria-describedby="generated-id generated-id"
aria-label="Time value"
class="euiFieldNumber euiFieldNumber--compressed"
+ step="any"
type="number"
value="15"
/>
@@ -345,11 +346,11 @@ exports[`EuiQuickSelectPanels customQuickSelectPanels should render custom panel
>
diff --git a/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap b/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap
index 01d54d9fccc..cd938b81958 100644
--- a/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap
+++ b/src/components/form/field_number/__snapshots__/field_number.test.tsx.snap
@@ -29,6 +29,7 @@ exports[`EuiFieldNumber props controlOnly is rendered 1`] = `
@@ -44,6 +45,7 @@ exports[`EuiFieldNumber props fullWidth is rendered 1`] = `
@@ -64,6 +66,7 @@ exports[`EuiFieldNumber props isInvalid is rendered from a prop 1`] = `
@@ -80,6 +83,7 @@ exports[`EuiFieldNumber props isLoading is rendered 1`] = `
@@ -98,6 +102,7 @@ exports[`EuiFieldNumber props readOnly is rendered 1`] = `
@@ -114,6 +119,7 @@ exports[`EuiFieldNumber props value no initial value 1`] = `
@@ -130,6 +136,7 @@ exports[`EuiFieldNumber props value value is number 1`] = `
diff --git a/src/components/form/field_number/field_number.spec.tsx b/src/components/form/field_number/field_number.spec.tsx
index a2621d95e5a..66dad13f41e 100644
--- a/src/components/form/field_number/field_number.spec.tsx
+++ b/src/components/form/field_number/field_number.spec.tsx
@@ -51,5 +51,20 @@ describe('EuiFieldNumber', () => {
cy.get('input').click().type('2');
checkIsInvalid();
});
+
+ it('shows invalid state on blur', () => {
+ cy.mount();
+ checkIsValid();
+ cy.get('input').click();
+ cy.get('body').click('bottomRight');
+ checkIsInvalid();
+ });
+
+ it('does not show invalid state on decimal values by default', () => {
+ cy.mount();
+ checkIsValid();
+ cy.get('input').click().type('1.5');
+ checkIsValid();
+ });
});
});
diff --git a/src/components/form/field_number/field_number.tsx b/src/components/form/field_number/field_number.tsx
index 255a5d5c648..b4e6341eba1 100644
--- a/src/components/form/field_number/field_number.tsx
+++ b/src/components/form/field_number/field_number.tsx
@@ -48,8 +48,9 @@ export type EuiFieldNumberProps = Omit<
max?: number;
/**
* Specifies the granularity that the value must adhere to.
- * Accepts a `number` or the string `'any'` for no stepping to allow for any value.
- * Defaults to `1`
+ * Accepts a `number`, e.g. `1` for integers, or `0.5` for decimal steps.
+ * Defaults to `"any"` for no stepping, which allows any decimal value(s).
+ * @default "any"
*/
step?: number | 'any';
inputRef?: Ref;
@@ -91,6 +92,7 @@ export const EuiFieldNumber: FunctionComponent = (
name,
min,
max,
+ step = 'any',
value,
isInvalid,
fullWidth = defaultFullWidth,
@@ -101,7 +103,8 @@ export const EuiFieldNumber: FunctionComponent = (
inputRef,
readOnly,
controlOnly,
- onKeyUp: _onKeyUp,
+ onKeyUp,
+ onBlur,
...rest
} = props;
@@ -113,19 +116,11 @@ export const EuiFieldNumber: FunctionComponent = (
true | undefined
>();
- // Note that we can't use hook into `onChange` because browsers don't emit change events
- // for invalid values - see https://github.com/facebook/react/issues/16554
- const onKeyUp = useCallback(
- (e: React.KeyboardEvent) => {
- _onKeyUp?.(e);
-
- const { validity } = e.target as HTMLInputElement;
- // Prefer `undefined` over `false` so that the `aria-invalid` prop unsets completely
- const isInvalid = !validity.valid || undefined;
- setIsNativelyInvalid(isInvalid);
- },
- [_onKeyUp]
- );
+ const checkNativeValidity = useCallback((inputEl: HTMLInputElement) => {
+ // Prefer `undefined` over `false` so that the `aria-invalid` prop unsets completely
+ const isInvalid = !inputEl.validity.valid || undefined;
+ setIsNativelyInvalid(isInvalid);
+ }, []);
const numIconsClass = controlOnly
? false
@@ -147,16 +142,27 @@ export const EuiFieldNumber: FunctionComponent = (
{
+ // Note that we can't use `onChange` because browsers don't emit change events
+ // for invalid text - see https://github.com/facebook/react/issues/16554
+ onKeyUp?.(e);
+ checkNativeValidity(e.currentTarget);
+ }}
+ onBlur={(e) => {
+ // Browsers can also set/determine validity (e.g. when `step` is undefined) on focus blur
+ onBlur?.(e);
+ checkNativeValidity(e.currentTarget);
+ }}
{...rest}
/>
diff --git a/src/components/form/range/__snapshots__/range_input.test.tsx.snap b/src/components/form/range/__snapshots__/range_input.test.tsx.snap
index bd95190f1c2..3ed468af4b0 100644
--- a/src/components/form/range/__snapshots__/range_input.test.tsx.snap
+++ b/src/components/form/range/__snapshots__/range_input.test.tsx.snap
@@ -11,6 +11,7 @@ exports[`EuiRangeInput renders 1`] = `
class="euiFieldNumber euiRangeInput euiRangeInput--max emotion-euiRangeInput"
max="100"
min="0"
+ step="any"
style="inline-size: calc(12px + 1ch + 2em + 0px);"
type="number"
value="0"
diff --git a/upcoming_changelogs/6760.md b/upcoming_changelogs/6760.md
new file mode 100644
index 00000000000..c34e1c78eca
--- /dev/null
+++ b/upcoming_changelogs/6760.md
@@ -0,0 +1,3 @@
+**Breaking changes**
+
+- `EuiFieldNumber` now defaults the `step` prop to `"any"`