diff --git a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap index 3f2a5a14210..44d0882bc92 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_form.test.tsx.snap @@ -5,142 +5,81 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.cancelButtonProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -153,142 +92,81 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.formRowProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -301,147 +179,86 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.inputProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
- -
-
-
-
- -
-
-
-
+ Prepend Example +
-
-
- Loaded -
- - +
+ +
+
-
-
-
-
- Loaded -
- - -
-
+
@@ -454,141 +271,80 @@ exports[`EuiInlineEditForm Edit Mode editModeProps.saveButtonProps 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -601,150 +357,89 @@ exports[`EuiInlineEditForm Edit Mode isInvalid 1`] = ` class="euiInlineEdit testClass1 testClass2" >
+
- -
- -
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -757,103 +452,103 @@ exports[`EuiInlineEditForm Edit Mode isLoading 1`] = ` class="euiInlineEdit testClass1 testClass2" >
+
- -
- -
-
+ +
+
-
-
-
+ Loading
+ -
-
-
-
+ aria-label="Loading " + class="euiSkeletonRectangle emotion-euiSkeletonGradientAnimation-euiSkeletonRectangle-m" + role="progressbar" + style="inline-size: 40px; block-size: 40px;" + /> +
+
+
@@ -867,141 +562,80 @@ exports[`EuiInlineEditForm Edit Mode renders 1`] = ` class="euiInlineEdit testClass1 testClass2" >
-
- -
+
-
- +
-
-
-
-
- Loaded -
- - -
-
+
@@ -1014,6 +648,7 @@ exports[`EuiInlineEditForm Read Mode readModeProps 1`] = ` class="euiInlineEdit testClass1 testClass2" > +
`; @@ -1041,6 +682,7 @@ exports[`EuiInlineEditForm Read Mode renders 1`] = ` class="euiInlineEdit testClass1 testClass2" > +
`; @@ -1068,6 +716,7 @@ exports[`EuiInlineEditForm Read Mode sizes 1`] = ` class="euiInlineEdit testClass1 testClass2" > +
`; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap index 42d20b7f64e..c84ffa0c68a 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_text.test.tsx.snap @@ -5,6 +5,7 @@ exports[`EuiInlineEditText renders 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-m-m" > +
`; @@ -36,6 +43,7 @@ exports[`EuiInlineEditText text sizes renders m 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-m-m" > +
`; @@ -67,6 +81,7 @@ exports[`EuiInlineEditText text sizes renders s 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-s-s" > +
`; @@ -98,6 +119,7 @@ exports[`EuiInlineEditText text sizes renders xs 1`] = ` class="euiInlineEdit euiInlineEditText testClass1 testClass2 emotion-euiInlineEditText-xs-xs" > +
`; diff --git a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap index 19a670a600d..58527fff8f8 100644 --- a/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap +++ b/src/components/inline_edit/__snapshots__/inline_edit_title.test.tsx.snap @@ -5,6 +5,7 @@ exports[`EuiInlineEditTitle renders 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-m-m" > +
`; @@ -36,6 +43,7 @@ exports[`EuiInlineEditTitle title sizes renders size l 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-l-l" > +
`; @@ -67,6 +81,7 @@ exports[`EuiInlineEditTitle title sizes renders size m 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-m-m" > +
`; @@ -98,6 +119,7 @@ exports[`EuiInlineEditTitle title sizes renders size s 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-s-s" > +
`; @@ -129,6 +157,7 @@ exports[`EuiInlineEditTitle title sizes renders size xs 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-xs-xs" > +
`; @@ -160,6 +195,7 @@ exports[`EuiInlineEditTitle title sizes renders size xxs 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-xxs-xxs" > +
`; @@ -191,6 +233,7 @@ exports[`EuiInlineEditTitle title sizes renders size xxxs 1`] = ` class="euiInlineEdit euiInlineEditTitle testClass1 testClass2 emotion-euiInlineEditTitle-xxxs-xxxs" > +
`; diff --git a/src/components/inline_edit/inline_edit_form.test.tsx b/src/components/inline_edit/inline_edit_form.test.tsx index 98a46cb935a..49c9e7de684 100644 --- a/src/components/inline_edit/inline_edit_form.test.tsx +++ b/src/components/inline_edit/inline_edit_form.test.tsx @@ -76,6 +76,8 @@ describe('EuiInlineEditForm', () => { }); test('editModeProps.inputProps', () => { + const onChange = jest.fn(); + const { container, getByTestSubject } = render( { inputProps: { prepend: 'Prepend Example', 'data-test-subj': 'customInput', + onChange, }, }} /> ); - expect(container.firstChild).toMatchSnapshot(); - expect(getByTestSubject('customInput')).toBeTruthy(); + + const mockChangeEvent = { target: { value: 'changed' } }; + fireEvent.change(getByTestSubject('customInput'), mockChangeEvent); + expect(onChange).toHaveBeenCalled(); + + // Consumer `onChange` callbacks should not override EUI's + expect( + (getByTestSubject('customInput') as HTMLInputElement).value + ).toEqual('changed'); }); test('editModeProps.formRowProps', () => { @@ -181,11 +191,18 @@ describe('EuiInlineEditForm', () => { }); describe('Toggling between readMode and editMode', () => { + jest + .spyOn(window, 'requestAnimationFrame') + .mockImplementation((cb: Function) => cb()); + const onClick = jest.fn(); const onSave = jest.fn(); - beforeEach(() => jest.resetAllMocks()); + beforeEach(() => { + onClick.mockReset(); + onSave.mockReset(); + }); - it('toggles to editMode when the readModeButton is clicked', () => { + it('activates editMode when the readModeButton is clicked', () => { const { getByTestSubject, queryByTestSubject } = render( { ); fireEvent.click(getByTestSubject('euiInlineReadModeButton')); - expect(getByTestSubject('euiInlineEditModeInput')).toBeTruthy(); + expect(queryByTestSubject('euiInlineReadModeButton')).toBeFalsy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineEditModeInput') + ); + }); expect(onClick).toHaveBeenCalledTimes(1); }); @@ -217,7 +239,11 @@ describe('EuiInlineEditForm', () => { ).toEqual('New message!'); fireEvent.click(getByTestSubject('euiInlineEditModeSaveButton')); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('New message!')).toBeTruthy(); expect(onSave).toHaveBeenCalledWith('New message!'); expect(onClick).toHaveBeenCalledTimes(1); @@ -241,7 +267,11 @@ describe('EuiInlineEditForm', () => { ).toEqual('New message!'); fireEvent.click(getByTestSubject('euiInlineEditModeCancelButton')); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('Hello World!')).toBeTruthy(); expect(onSave).not.toHaveBeenCalled(); expect(onClick).toHaveBeenCalledTimes(1); @@ -350,7 +380,11 @@ describe('EuiInlineEditForm', () => { key: 'Enter', }); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('New message!')).toBeTruthy(); }); @@ -369,7 +403,11 @@ describe('EuiInlineEditForm', () => { key: 'Escape', }); - expect(getByTestSubject('euiInlineReadModeButton')).toBeTruthy(); + waitFor(() => { + expect(document.activeElement).toEqual( + getByTestSubject('euiInlineReadModeButton') + ); + }); expect(getByText('Hello World!')).toBeTruthy(); }); diff --git a/src/components/inline_edit/inline_edit_form.tsx b/src/components/inline_edit/inline_edit_form.tsx index 7ac900295c6..eb655660b9d 100644 --- a/src/components/inline_edit/inline_edit_form.tsx +++ b/src/components/inline_edit/inline_edit_form.tsx @@ -10,6 +10,7 @@ import React, { ReactNode, FunctionComponent, useState, + useRef, HTMLAttributes, MouseEvent, KeyboardEvent, @@ -21,7 +22,6 @@ import { EuiFormRow, EuiFormRowProps, EuiFieldText, - EuiForm, EuiFieldTextProps, } from '../form'; import { euiFormVariables } from '../form/form.styles'; @@ -29,8 +29,8 @@ import { EuiButtonIcon, EuiButtonEmpty } from '../button'; import { EuiButtonIconPropsForButton } from '../button/button_icon'; import { EuiButtonEmptyPropsForButton } from '../button/button_empty/button_empty'; import { EuiFlexGroup, EuiFlexItem } from '../flex'; -import { EuiSkeletonRectangle } from '../skeleton'; -import { useEuiTheme, keys } from '../../services'; +import { EuiSkeletonLoading, EuiSkeletonRectangle } from '../skeleton'; +import { useEuiTheme, useCombinedRefs, keys } from '../../services'; import { EuiI18n, useEuiI18n } from '../i18n'; import { useGeneratedHtmlId } from '../../services/accessibility'; @@ -140,17 +140,34 @@ export const EuiInlineEditForm: FunctionComponent = ({ 'Cancel edit' ); + const readModeDescribedById = useGeneratedHtmlId({ prefix: 'inlineEdit' }); const editModeDescribedById = useGeneratedHtmlId({ prefix: 'inlineEdit' }); - const [isEditing, setIsEditing] = useState(false || startWithEditOpen); - const inlineEditInputId = useGeneratedHtmlId({ prefix: '__inlineEditInput' }); + const readModeFocusRef = useRef(null); + const editModeFocusRef = useRef(null); + const setReadModeRefs = useCombinedRefs([ + readModeFocusRef, + readModeProps?.buttonRef, + ]); + const setEditModeRefs = useCombinedRefs([ + editModeFocusRef, + editModeProps?.inputProps?.inputRef, + ]); + const [isEditing, setIsEditing] = useState(false || startWithEditOpen); const [editModeValue, setEditModeValue] = useState(defaultValue); const [readModeValue, setReadModeValue] = useState(defaultValue); + const activateEditMode = () => { + setIsEditing(true); + // Waits a tick for state to settle and the focus target to render + requestAnimationFrame(() => editModeFocusRef.current?.focus()); + }; + const cancelInlineEdit = () => { setEditModeValue(readModeValue); setIsEditing(false); + requestAnimationFrame(() => readModeFocusRef.current?.focus()); }; const saveInlineEditValue = async () => { @@ -164,6 +181,7 @@ export const EuiInlineEditForm: FunctionComponent = ({ setReadModeValue(editModeValue); setIsEditing(false); + requestAnimationFrame(() => readModeFocusRef.current?.focus()); }; const editModeInputOnKeyDown = (event: KeyboardEvent) => { @@ -178,53 +196,67 @@ export const EuiInlineEditForm: FunctionComponent = ({ }; const editModeForm = ( - - - - + + + - { - setEditModeValue(e.target.value); - }} - aria-label={inputAriaLabel} - autoFocus - compressed={sizes.compressed} - isInvalid={isInvalid} - isLoading={isLoading} - data-test-subj="euiInlineEditModeInput" - {...editModeProps?.inputProps} - aria-describedby={classNames( - editModeDescribedById, - editModeProps?.inputProps?.['aria-describedby'] - )} - onKeyDown={(e: KeyboardEvent) => { - editModeInputOnKeyDown(e); - editModeProps?.inputProps?.onKeyDown?.(e); - }} - /> - - - + isLoading={isLoading} + data-test-subj="euiInlineEditModeInput" + {...editModeProps?.inputProps} + inputRef={setEditModeRefs} + onChange={(e) => { + setEditModeValue(e.target.value); + editModeProps?.inputProps?.onChange?.(e); + }} + onKeyDown={(e) => { + editModeInputOnKeyDown(e); + editModeProps?.inputProps?.onKeyDown?.(e); + }} + aria-describedby={classNames( + editModeDescribedById, + editModeProps?.inputProps?.['aria-describedby'] + )} + /> + + + - - - + + + + + + } + loadedContent={ + = ({ editModeProps?.saveButtonProps?.onClick?.(e); }} /> - - - - - - - = ({ editModeProps?.cancelButtonProps?.onClick?.(e); }} /> - - - - - + + } + /> + + ); const readModeElement = ( - { - setIsEditing(true); - readModeProps?.onClick?.(e); - }} - > - {children(readModeValue)} - + <> + ) => { + activateEditMode(); + readModeProps?.onClick?.(e); + }} + > + {children(readModeValue)} + + + ); return (