[8.16] [Index management] Fix a11y focus order in index mappings page (#203361) (#203626)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Index management] Fix a11y focus order in index mappings page
(#203361)](https://github.com/elastic/kibana/pull/203361)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Saarika
Bhasi","email":"55930906+saarikabhasi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-12-10T15:46:20Z","message":"[Index
management] Fix a11y focus order in index mappings page (#203361)\n\n##
Summary\r\n\r\nFix a11y focus order in index mappings page. When new
field is in\r\npending state and after closing edit pending field
Flyout.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/dbdf59e5-0ebd-47e0-9b5e-19ab1556e771\r\n\r\n###
Test instructions \r\n#### Adding a field\r\n1. Add new field in index
mappings page by navigating via tab \r\n2. Notice that type fields combo
box is focused\r\n3. Add field and click to Add field button again with
in pending fields\r\nform\r\n4. Notice focus is on new create field
form\r\n\r\n#### Edit field in pending state\r\n1. Add new fields via
tab key\r\n2. click on edit field \r\n3. Try closing, updating and
cancelling in the edit field flyout form\r\n4. Notice after edit field
flyout is closed, focus is on the pending\r\nfields
form","sha":"4b0c0e92693ad759e3ce45b4b259c9908ddd0d51","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Search","backport:prev-major","v8.16.0","v8.17.0"],"title":"[Index
management] Fix a11y focus order in index mappings page
","number":203361,"url":"https://github.com/elastic/kibana/pull/203361","mergeCommit":{"message":"[Index
management] Fix a11y focus order in index mappings page (#203361)\n\n##
Summary\r\n\r\nFix a11y focus order in index mappings page. When new
field is in\r\npending state and after closing edit pending field
Flyout.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/dbdf59e5-0ebd-47e0-9b5e-19ab1556e771\r\n\r\n###
Test instructions \r\n#### Adding a field\r\n1. Add new field in index
mappings page by navigating via tab \r\n2. Notice that type fields combo
box is focused\r\n3. Add field and click to Add field button again with
in pending fields\r\nform\r\n4. Notice focus is on new create field
form\r\n\r\n#### Edit field in pending state\r\n1. Add new fields via
tab key\r\n2. click on edit field \r\n3. Try closing, updating and
cancelling in the edit field flyout form\r\n4. Notice after edit field
flyout is closed, focus is on the pending\r\nfields
form","sha":"4b0c0e92693ad759e3ce45b4b259c9908ddd0d51"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.17"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/203361","number":203361,"mergeCommit":{"message":"[Index
management] Fix a11y focus order in index mappings page (#203361)\n\n##
Summary\r\n\r\nFix a11y focus order in index mappings page. When new
field is in\r\npending state and after closing edit pending field
Flyout.\r\n\r\n\r\nhttps://github.com/user-attachments/assets/dbdf59e5-0ebd-47e0-9b5e-19ab1556e771\r\n\r\n###
Test instructions \r\n#### Adding a field\r\n1. Add new field in index
mappings page by navigating via tab \r\n2. Notice that type fields combo
box is focused\r\n3. Add field and click to Add field button again with
in pending fields\r\nform\r\n4. Notice focus is on new create field
form\r\n\r\n#### Edit field in pending state\r\n1. Add new fields via
tab key\r\n2. click on edit field \r\n3. Try closing, updating and
cancelling in the edit field flyout form\r\n4. Notice after edit field
flyout is closed, focus is on the pending\r\nfields
form","sha":"4b0c0e92693ad759e3ce45b4b259c9908ddd0d51"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Saarika Bhasi <55930906+saarikabhasi@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-12-11 04:45:35 +11:00 committed by GitHub
parent 371ae4de45
commit 7bcb2627ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 51 additions and 8 deletions

View file

@ -27,6 +27,7 @@ interface Props {
onCancelAddingNewFields?: () => void;
isAddingFields?: boolean;
semanticTextInfo?: SemanticTextInfo;
pendingFieldsRef?: React.RefObject<HTMLDivElement>;
}
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 () => {

View file

@ -24,6 +24,7 @@ interface Props {
isMultiField?: boolean | null;
showDocLink?: boolean;
isSemanticTextEnabled?: boolean;
fieldTypeInputRef?: React.MutableRefObject<HTMLInputElement | null>;
}
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;
}}
/>
</EuiFormRow>
);

View file

@ -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<HTMLDivElement>;
}
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<HTMLInputElement>(null);
const { form } = useForm<Field>({
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}
/>
</EuiFlexItem>
@ -266,6 +278,8 @@ export const CreateField = React.memo(function CreateFieldComponent({
: paddingLeft
}px`,
}}
ref={createFieldFormRef}
tabIndex={0}
>
<div className="mappingsEditor__createFieldContent">
{renderFormFields()}

View file

@ -16,6 +16,7 @@ interface Props {
state: State;
setPreviousState?: (state: State) => void;
isAddingFields?: boolean;
pendingFieldsRef?: React.RefObject<HTMLDivElement>;
}
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}
/>
))}
</ul>

View file

@ -64,6 +64,8 @@ interface Props {
treeDepth: number;
state: State;
isAddingFields?: boolean;
createFieldFormRef?: React.RefObject<HTMLDivElement>;
pendingFieldsRef?: React.RefObject<HTMLDivElement>;
}
function FieldListItemComponent(
@ -85,6 +87,7 @@ function FieldListItemComponent(
state,
isAddingFields,
setPreviousState,
pendingFieldsRef,
}: Props,
ref: React.Ref<HTMLLIElement>
) {
@ -141,7 +144,6 @@ function FieldListItemComponent(
const { addMultiFieldButtonLabel, addPropertyButtonLabel, editButtonLabel, deleteButtonLabel } =
i18nTexts;
return (
<EuiFlexGroup gutterSize="s" className="mappingsEditor__fieldsListItem__actions">
{canHaveMultiFields && (
@ -321,6 +323,7 @@ function FieldListItemComponent(
state={state}
isAddingFields={isAddingFields}
setPreviousState={setPreviousState}
pendingFieldsRef={pendingFieldsRef}
/>
)}

View file

@ -18,6 +18,7 @@ interface Props {
state: State;
setPreviousState?: (state: State) => void;
isAddingFields?: boolean;
pendingFieldsRef?: React.RefObject<HTMLDivElement>;
}
export const FieldsListItemContainer = ({
@ -27,6 +28,7 @@ export const FieldsListItemContainer = ({
state,
setPreviousState,
isAddingFields,
pendingFieldsRef,
}: Props) => {
const dispatch = useDispatch();
const listElement = useRef<HTMLLIElement | null>(null);
@ -110,6 +112,7 @@ export const FieldsListItemContainer = ({
toggleExpand={toggleExpand}
state={state}
isAddingFields={isAddingFields}
pendingFieldsRef={pendingFieldsRef}
/>
);
};

View file

@ -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<HTMLDivElement>;
}
export const DocumentFieldsTreeEditor = ({
onCancelAddingNewFields,
isAddingFields,
semanticTextInfo,
pendingFieldsRef,
}: Props) => {
const dispatch = useDispatch();
const {
fields: { byId, rootLevelFields },
documentFields: { status, fieldToAddFieldTo },
} = useMappingsState();
const createFieldFormRef = useRef<HTMLDivElement>(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 (
<>
<FieldsList fields={fields} state={useMappingsState()} isAddingFields={isAddingFields} />
<FieldsList
fields={fields}
state={useMappingsState()}
isAddingFields={isAddingFields}
pendingFieldsRef={pendingFieldsRef}
/>
{renderCreateField()}
{renderAddFieldButton()}
</>

View file

@ -26,7 +26,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 {
@ -81,6 +81,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{
overlays,
history,
} = useAppContext();
const pendingFieldsRef = useRef<HTMLDivElement>(null);
const [isPlatinumLicense, setIsPlatinumLicense] = useState<boolean>(false);
useEffect(() => {
@ -540,7 +541,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{
</EuiFlexItem>
{errorSavingMappings}
{isAddingFields && (
<EuiFlexItem grow={false}>
<EuiFlexItem grow={false} ref={pendingFieldsRef} tabIndex={0}>
<EuiPanel hasBorder paddingSize="s">
<EuiAccordion
id={pendingFieldListId}
@ -578,11 +579,13 @@ export const DetailsPageMappingsContent: FunctionComponent<{
onCancelAddingNewFields={onCancelAddingNewFields}
isAddingFields={isAddingFields}
semanticTextInfo={semanticTextInfo}
pendingFieldsRef={pendingFieldsRef}
/>
) : (
<DocumentFields
isAddingFields={isAddingFields}
semanticTextInfo={semanticTextInfo}
pendingFieldsRef={pendingFieldsRef}
/>
)}
</EuiPanel>