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"`