From c048495e1693a8d8ffec19caf2bfaf13454f1a1b Mon Sep 17 00:00:00 2001 From: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:46:20 -0500 Subject: [PATCH] [Index management] Fix a11y focus order in index mappings page (#203361) ## Summary Fix a11y focus order in index mappings page. When new field is in pending state and after closing edit pending field Flyout. https://github.com/user-attachments/assets/dbdf59e5-0ebd-47e0-9b5e-19ab1556e771 ### Test instructions #### Adding a field 1. Add new field in index mappings page by navigating via tab 2. Notice that type fields combo box is focused 3. Add field and click to Add field button again with in pending fields form 4. Notice focus is on new create field form #### Edit field in pending state 1. Add new fields via tab key 2. click on edit field 3. Try closing, updating and cancelling in the edit field flyout form 4. Notice after edit field flyout is closed, focus is on the pending fields form --- .../document_fields/document_fields.tsx | 6 +++++- .../field_parameters/type_parameter.tsx | 5 +++++ .../fields/create_field/create_field.tsx | 16 +++++++++++++++- .../document_fields/fields/fields_list.tsx | 3 +++ .../document_fields/fields/fields_list_item.tsx | 5 ++++- .../fields/fields_list_item_container.tsx | 3 +++ .../document_fields/fields_tree_editor.tsx | 14 +++++++++++--- .../details_page_mappings_content.tsx | 7 +++++-- 8 files changed, 51 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx index d81b1e3dc9e5b..54dd434458486 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/document_fields.tsx @@ -27,6 +27,7 @@ interface Props { onCancelAddingNewFields?: () => void; isAddingFields?: boolean; semanticTextInfo?: SemanticTextInfo; + pendingFieldsRef?: React.RefObject; } export const DocumentFields = React.memo( ({ @@ -35,6 +36,7 @@ export const DocumentFields = React.memo( onCancelAddingNewFields, isAddingFields, semanticTextInfo, + pendingFieldsRef, }: Props) => { const { fields, documentFields } = useMappingsState(); const dispatch = useDispatch(); @@ -58,6 +60,7 @@ export const DocumentFields = React.memo( onCancelAddingNewFields={onCancelAddingNewFields} isAddingFields={isAddingFields} semanticTextInfo={semanticTextInfo} + pendingFieldsRef={pendingFieldsRef} /> ); @@ -81,8 +84,9 @@ export const DocumentFields = React.memo( useEffect(() => { if (!isEditing) { removeContentFromGlobalFlyout('mappingsEditField'); + if (pendingFieldsRef?.current) pendingFieldsRef.current.focus(); } - }, [isEditing, removeContentFromGlobalFlyout]); + }, [isEditing, removeContentFromGlobalFlyout, pendingFieldsRef]); useEffect(() => { return () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx index ec467f113c46d..528019416ad69 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/type_parameter.tsx @@ -24,6 +24,7 @@ interface Props { isMultiField?: boolean | null; showDocLink?: boolean; isSemanticTextEnabled?: boolean; + fieldTypeInputRef?: React.MutableRefObject; } export const TypeParameter = ({ @@ -31,6 +32,7 @@ export const TypeParameter = ({ isRootLevelField, showDocLink = false, isSemanticTextEnabled = true, + fieldTypeInputRef, }: Props) => { const fieldTypeOptions = useMemo(() => { let options = isMultiField @@ -97,6 +99,9 @@ export const TypeParameter = ({ onChange={typeField.setValue} isClearable={false} data-test-subj="fieldType" + inputRef={(input) => { + if (fieldTypeInputRef) fieldTypeInputRef.current = input; + }} /> ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index 1daa437356546..0289f07534d35 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -17,7 +17,7 @@ import { i18n } from '@kbn/i18n'; import { TrainedModelStat } from '@kbn/ml-plugin/common/types/trained_models'; import { MlPluginStart } from '@kbn/ml-plugin/public'; import classNames from 'classnames'; -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { EUI_SIZE, TYPE_DEFINITION } from '../../../../constants'; import { fieldSerializer } from '../../../../lib'; import { isSemanticTextField } from '../../../../lib/utils'; @@ -62,6 +62,7 @@ interface Props { onCancelAddingNewFields?: () => void; isAddingFields?: boolean; semanticTextInfo?: SemanticTextInfo; + createFieldFormRef?: React.RefObject; } export const CreateField = React.memo(function CreateFieldComponent({ @@ -74,9 +75,11 @@ export const CreateField = React.memo(function CreateFieldComponent({ onCancelAddingNewFields, isAddingFields, semanticTextInfo, + createFieldFormRef, }: Props) { const { isSemanticTextEnabled, ml, setErrorsInTrainedModelDeployment } = semanticTextInfo ?? {}; const dispatch = useDispatch(); + const fieldTypeInputRef = useRef(null); const { form } = useForm({ serializer: fieldSerializer, @@ -111,6 +114,10 @@ export const CreateField = React.memo(function CreateFieldComponent({ const isSemanticText = form.getFormData().type === 'semantic_text'; + useEffect(() => { + if (createFieldFormRef?.current) createFieldFormRef?.current.focus(); + }, [createFieldFormRef]); + const submitForm = async ( e?: React.FormEvent, exitAfter: boolean = false, @@ -134,6 +141,10 @@ export const CreateField = React.memo(function CreateFieldComponent({ } form.reset(); } + + if (fieldTypeInputRef.current) { + fieldTypeInputRef.current.focus(); + } }; const onClickOutside = () => { @@ -157,6 +168,7 @@ export const CreateField = React.memo(function CreateFieldComponent({ isMultiField={isMultiField} showDocLink isSemanticTextEnabled={isSemanticTextEnabled} + fieldTypeInputRef={fieldTypeInputRef} /> @@ -266,6 +278,8 @@ export const CreateField = React.memo(function CreateFieldComponent({ : paddingLeft }px`, }} + ref={createFieldFormRef} + tabIndex={0} >
{renderFormFields()} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx index 241fa132a28bf..9b2e8e0219908 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list.tsx @@ -16,6 +16,7 @@ interface Props { state: State; setPreviousState?: (state: State) => void; isAddingFields?: boolean; + pendingFieldsRef?: React.RefObject; } export const FieldsList = React.memo(function FieldsListComponent({ @@ -24,6 +25,7 @@ export const FieldsList = React.memo(function FieldsListComponent({ state, setPreviousState, isAddingFields, + pendingFieldsRef, }: Props) { if (fields === undefined) { return null; @@ -39,6 +41,7 @@ export const FieldsList = React.memo(function FieldsListComponent({ state={state} setPreviousState={setPreviousState} isAddingFields={isAddingFields} + pendingFieldsRef={pendingFieldsRef} /> ))} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index 33c51a3cb644b..505ca26ce7ca4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -64,6 +64,8 @@ interface Props { treeDepth: number; state: State; isAddingFields?: boolean; + createFieldFormRef?: React.RefObject; + pendingFieldsRef?: React.RefObject; } function FieldListItemComponent( @@ -85,6 +87,7 @@ function FieldListItemComponent( state, isAddingFields, setPreviousState, + pendingFieldsRef, }: Props, ref: React.Ref ) { @@ -141,7 +144,6 @@ function FieldListItemComponent( const { addMultiFieldButtonLabel, addPropertyButtonLabel, editButtonLabel, deleteButtonLabel } = i18nTexts; - return ( {canHaveMultiFields && ( @@ -321,6 +323,7 @@ function FieldListItemComponent( state={state} isAddingFields={isAddingFields} setPreviousState={setPreviousState} + pendingFieldsRef={pendingFieldsRef} /> )} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx index a5e85eb1aad17..bcadd859f179e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item_container.tsx @@ -18,6 +18,7 @@ interface Props { state: State; setPreviousState?: (state: State) => void; isAddingFields?: boolean; + pendingFieldsRef?: React.RefObject; } export const FieldsListItemContainer = ({ @@ -27,6 +28,7 @@ export const FieldsListItemContainer = ({ state, setPreviousState, isAddingFields, + pendingFieldsRef, }: Props) => { const dispatch = useDispatch(); const listElement = useRef(null); @@ -110,6 +112,7 @@ export const FieldsListItemContainer = ({ toggleExpand={toggleExpand} state={state} isAddingFields={isAddingFields} + pendingFieldsRef={pendingFieldsRef} /> ); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields_tree_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields_tree_editor.tsx index e7ae3c995a2f3..9274f10ab603e 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields_tree_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields_tree_editor.tsx @@ -7,7 +7,7 @@ import { EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useRef } from 'react'; import { useDispatch, useMappingsState } from '../../mappings_state_context'; import { CreateField, FieldsList, SemanticTextInfo } from './fields'; @@ -16,19 +16,21 @@ interface Props { onCancelAddingNewFields?: () => void; isAddingFields?: boolean; semanticTextInfo?: SemanticTextInfo; + pendingFieldsRef?: React.RefObject; } export const DocumentFieldsTreeEditor = ({ onCancelAddingNewFields, isAddingFields, semanticTextInfo, + pendingFieldsRef, }: Props) => { const dispatch = useDispatch(); const { fields: { byId, rootLevelFields }, documentFields: { status, fieldToAddFieldTo }, } = useMappingsState(); - + const createFieldFormRef = useRef(null); const getField = useCallback((fieldId: string) => byId[fieldId], [byId]); const fields = useMemo(() => rootLevelFields.map(getField), [rootLevelFields, getField]); @@ -52,6 +54,7 @@ export const DocumentFieldsTreeEditor = ({ onCancelAddingNewFields={onCancelAddingNewFields} isAddingFields={isAddingFields} semanticTextInfo={semanticTextInfo} + createFieldFormRef={createFieldFormRef} /> ); }; @@ -77,7 +80,12 @@ export const DocumentFieldsTreeEditor = ({ return ( <> - + {renderCreateField()} {renderAddFieldButton()} diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx index ec5deabc5f1f3..063460f448ccc 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/details_page/details_page_mappings_content.tsx @@ -27,7 +27,7 @@ import { import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import React, { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ILicense } from '@kbn/licensing-plugin/public'; import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt'; import { @@ -85,6 +85,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{ overlays, history, } = useAppContext(); + const pendingFieldsRef = useRef(null); const [isPlatinumLicense, setIsPlatinumLicense] = useState(false); useEffect(() => { @@ -559,7 +560,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{ {errorSavingMappings} {isAddingFields && ( - + ) : ( )}