Skip to content

Commit fcc6e0e

Browse files
jloleysenselasticmachinekibanamachine
authored
[Ingest Pipelines] Processors editor a11y focus states (#79122) (#79481)
* Fix showing of accessibility border - fix use of flex items (removed unnecessary use thereof) - also fixed overflow when tabbing through drop zones (compressed) * refactor isLast to compressed * optimize keyboard focus states in move mode * fix jest test Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 863988a commit fcc6e0e

6 files changed

Lines changed: 117 additions & 104 deletions

File tree

x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/inline_text_input.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@ export interface Props {
1111
placeholder: string;
1212
ariaLabel: string;
1313
onChange: (value: string) => void;
14-
disabled: boolean;
14+
/**
15+
* Whether the containing element of the text input can be focused.
16+
*
17+
* If it cannot be focused, this component cannot switch to showing
18+
* the text input field.
19+
*
20+
* Defaults to false.
21+
*/
22+
disabled?: boolean;
1523
text?: string;
1624
}
1725

1826
export const InlineTextInput: FunctionComponent<Props> = ({
19-
disabled,
2027
placeholder,
2128
text,
2229
ariaLabel,
30+
disabled = false,
2331
onChange,
2432
}) => {
2533
const [isShowingTextInput, setIsShowingTextInput] = useState<boolean>(false);
@@ -71,7 +79,11 @@ export const InlineTextInput: FunctionComponent<Props> = ({
7179
/>
7280
</div>
7381
) : (
74-
<div className={containerClasses} tabIndex={0} onFocus={() => setIsShowingTextInput(true)}>
82+
<div
83+
className={containerClasses}
84+
tabIndex={disabled ? -1 : 0}
85+
onFocus={() => setIsShowingTextInput(true)}
86+
>
7587
<EuiText size="s" color="subdued">
7688
<div className="pipelineProcessorsEditor__item__description">
7789
{text || <em>{placeholder}</em>}

x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent<Props> = memo(
159159
color={isDimmed ? 'subdued' : undefined}
160160
>
161161
<EuiLink
162+
tabIndex={isEditorNotInIdleMode ? -1 : 0}
162163
disabled={isEditorNotInIdleMode}
163164
onClick={() => {
164165
editor.setMode({

x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/drop_zone_button.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
import { i18n } from '@kbn/i18n';
88
import React, { FunctionComponent } from 'react';
99
import classNames from 'classnames';
10-
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
10+
import { EuiButtonIcon } from '@elastic/eui';
1111

1212
export interface Props {
1313
isVisible: boolean;
1414
isDisabled: boolean;
15+
/**
16+
* Useful for buttons at the very top or bottom of lists to avoid any overflow.
17+
*/
18+
compressed?: boolean;
1519
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
1620
'data-test-subj'?: string;
1721
}
@@ -29,7 +33,7 @@ const cannotMoveHereLabel = i18n.translate(
2933
);
3034

3135
export const DropZoneButton: FunctionComponent<Props> = (props) => {
32-
const { onClick, isDisabled, isVisible } = props;
36+
const { onClick, isDisabled, isVisible, compressed } = props;
3337
const isUnavailable = isVisible && isDisabled;
3438
const containerClasses = classNames({
3539
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -40,30 +44,21 @@ export const DropZoneButton: FunctionComponent<Props> = (props) => {
4044
const buttonClasses = classNames({
4145
// eslint-disable-next-line @typescript-eslint/naming-convention
4246
'pipelineProcessorsEditor__tree__dropZoneButton--visible': isVisible,
47+
// eslint-disable-next-line @typescript-eslint/naming-convention
48+
'pipelineProcessorsEditor__tree__dropZoneButton--compressed': compressed,
4349
});
4450

45-
const content = (
51+
return (
4652
<div className={`pipelineProcessorsEditor__tree__dropZoneContainer ${containerClasses}`}>
4753
<EuiButtonIcon
4854
data-test-subj={props['data-test-subj']}
4955
className={`pipelineProcessorsEditor__tree__dropZoneButton ${buttonClasses}`}
50-
aria-label={moveHereLabel}
56+
aria-label={isUnavailable ? cannotMoveHereLabel : moveHereLabel}
5157
// We artificially disable the button so that hover and pointer events are
5258
// still enabled
5359
onClick={isDisabled ? () => {} : onClick}
5460
iconType="empty"
5561
/>
5662
</div>
5763
);
58-
59-
return isUnavailable ? (
60-
<EuiToolTip
61-
className="pipelineProcessorsEditor__tree__dropZoneContainer__toolTip"
62-
content={cannotMoveHereLabel}
63-
>
64-
{content}
65-
</EuiToolTip>
66-
) : (
67-
content
68-
);
6964
};

x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/components/private_tree.tsx

Lines changed: 72 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import React, { FunctionComponent, MutableRefObject, useEffect } from 'react';
8-
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
8+
import { EuiFlexGroup } from '@elastic/eui';
99
import { AutoSizer, List, WindowScroller } from 'react-virtualized';
1010

1111
import { DropSpecialLocations } from '../../../constants';
@@ -78,50 +78,45 @@ export const PrivateTree: FunctionComponent<PrivateProps> = ({
7878
return (
7979
<>
8080
{idx === 0 ? (
81-
<EuiFlexItem>
82-
<DropZoneButton
83-
data-test-subj={`dropButtonAbove-${stringifiedSelector}`}
84-
onClick={(event) => {
85-
event.preventDefault();
86-
onAction({
87-
type: 'move',
88-
payload: {
89-
destination: selector.concat(DropSpecialLocations.top),
90-
source: movingProcessor!.selector,
91-
},
92-
});
93-
}}
94-
isVisible={Boolean(movingProcessor)}
95-
isDisabled={!movingProcessor || isDropZoneAboveDisabled(info, movingProcessor)}
96-
/>
97-
</EuiFlexItem>
98-
) : undefined}
99-
<EuiFlexItem>
100-
<TreeNode
101-
level={level}
102-
processor={processor}
103-
processorInfo={info}
104-
onAction={onAction}
105-
movingProcessor={movingProcessor}
106-
/>
107-
</EuiFlexItem>
108-
<EuiFlexItem>
10981
<DropZoneButton
110-
data-test-subj={`dropButtonBelow-${stringifiedSelector}`}
111-
isVisible={Boolean(movingProcessor)}
112-
isDisabled={!movingProcessor || isDropZoneBelowDisabled(info, movingProcessor)}
82+
data-test-subj={`dropButtonAbove-${stringifiedSelector}`}
11383
onClick={(event) => {
11484
event.preventDefault();
11585
onAction({
11686
type: 'move',
11787
payload: {
118-
destination: selector.concat(String(idx + 1)),
88+
destination: selector.concat(DropSpecialLocations.top),
11989
source: movingProcessor!.selector,
12090
},
12191
});
12292
}}
93+
isVisible={Boolean(movingProcessor)}
94+
isDisabled={!movingProcessor || isDropZoneAboveDisabled(info, movingProcessor)}
12395
/>
124-
</EuiFlexItem>
96+
) : undefined}
97+
<TreeNode
98+
level={level}
99+
processor={processor}
100+
processorInfo={info}
101+
onAction={onAction}
102+
movingProcessor={movingProcessor}
103+
/>
104+
<DropZoneButton
105+
compressed={level === 1 && idx + 1 === processors.length}
106+
data-test-subj={`dropButtonBelow-${stringifiedSelector}`}
107+
isVisible={Boolean(movingProcessor)}
108+
isDisabled={!movingProcessor || isDropZoneBelowDisabled(info, movingProcessor)}
109+
onClick={(event) => {
110+
event.preventDefault();
111+
onAction({
112+
type: 'move',
113+
payload: {
114+
destination: selector.concat(String(idx + 1)),
115+
source: movingProcessor!.selector,
116+
},
117+
});
118+
}}
119+
/>
125120
</>
126121
);
127122
};
@@ -141,52 +136,50 @@ export const PrivateTree: FunctionComponent<PrivateProps> = ({
141136
<WindowScroller ref={windowScrollerRef} scrollElement={window}>
142137
{({ height, registerChild, isScrolling, onChildScroll, scrollTop }: any) => {
143138
return (
144-
<EuiFlexGroup direction="column" responsive={false} gutterSize="none">
145-
<AutoSizer disableHeight>
146-
{({ width }) => {
147-
return (
148-
<div ref={registerChild}>
149-
<List
150-
ref={listRef}
151-
autoHeight
152-
height={height}
153-
width={width}
154-
overScanRowCount={5}
155-
isScrolling={isScrolling}
156-
onChildScroll={onChildScroll}
157-
scrollTop={scrollTop}
158-
rowCount={processors.length}
159-
rowHeight={({ index }) => {
160-
const processor = processors[index];
161-
return calculateItemHeight({
162-
processor,
163-
isFirstInArray: index === 0,
164-
});
165-
}}
166-
rowRenderer={({ index: idx, style }) => {
167-
const processor = processors[idx];
168-
const above = processors[idx - 1];
169-
const below = processors[idx + 1];
170-
const info: ProcessorInfo = {
171-
id: processor.id,
172-
selector: selector.concat(String(idx)),
173-
aboveId: above?.id,
174-
belowId: below?.id,
175-
};
139+
<AutoSizer disableHeight>
140+
{({ width }) => {
141+
return (
142+
<div style={{ width: '100%' }} ref={registerChild}>
143+
<List
144+
ref={listRef}
145+
autoHeight
146+
height={height}
147+
width={width}
148+
overScanRowCount={5}
149+
isScrolling={isScrolling}
150+
onChildScroll={onChildScroll}
151+
scrollTop={scrollTop}
152+
rowCount={processors.length}
153+
rowHeight={({ index }) => {
154+
const processor = processors[index];
155+
return calculateItemHeight({
156+
processor,
157+
isFirstInArray: index === 0,
158+
});
159+
}}
160+
rowRenderer={({ index: idx, style }) => {
161+
const processor = processors[idx];
162+
const above = processors[idx - 1];
163+
const below = processors[idx + 1];
164+
const info: ProcessorInfo = {
165+
id: processor.id,
166+
selector: selector.concat(String(idx)),
167+
aboveId: above?.id,
168+
belowId: below?.id,
169+
};
176170

177-
return (
178-
<div style={style} key={processor.id}>
179-
{renderRow({ processor, info, idx })}
180-
</div>
181-
);
182-
}}
183-
processors={processors}
184-
/>
185-
</div>
186-
);
187-
}}
188-
</AutoSizer>
189-
</EuiFlexGroup>
171+
return (
172+
<div style={style} key={processor.id}>
173+
{renderRow({ processor, info, idx })}
174+
</div>
175+
);
176+
}}
177+
processors={processors}
178+
/>
179+
</div>
180+
);
181+
}}
182+
</AutoSizer>
190183
);
191184
}}
192185
</WindowScroller>

x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.scss

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@
3131
}
3232
}
3333
$dropZoneButtonHeight: 60px;
34-
$dropZoneButtonOffsetY: $dropZoneButtonHeight * -0.5;
34+
$dropZoneButtonOffsetY: $dropZoneButtonHeight * 0.5;
3535

3636
&__dropZoneButton {
3737
position: absolute;
3838
padding: 0;
3939
height: $dropZoneButtonHeight;
40-
margin-top: $dropZoneButtonOffsetY;
40+
margin-top: -$dropZoneButtonOffsetY;
4141
width: 100%;
42-
opacity: 0;
4342
text-decoration: none !important;
4443
z-index: $dropZoneZIndex;
4544

@@ -49,6 +48,10 @@
4948
transform: none !important;
5049
}
5150
}
51+
52+
&--compressed {
53+
height: $dropZoneButtonOffsetY;
54+
}
5255
}
5356

5457
&__addProcessorButton {

x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processors_tree/processors_tree.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,16 @@ export const ProcessorsTree: FunctionComponent<Props> = memo((props) => {
9898
/>
9999
</EuiFlexItem>
100100
<EuiFlexItem grow={false}>
101-
<EuiFlexGroup responsive={false} alignItems="flexStart" gutterSize="none">
102-
<EuiFlexItem data-test-subj={selectorToDataTestSubject(baseSelector)} grow={false}>
103-
{!processors.length && (
101+
<EuiFlexGroup
102+
data-test-subj={selectorToDataTestSubject(baseSelector)}
103+
responsive={false}
104+
alignItems="flexStart"
105+
gutterSize="none"
106+
direction="column"
107+
>
108+
{!processors.length && (
109+
// We want to make this dropzone the max length of its container
110+
<EuiFlexItem style={{ width: '100%' }}>
104111
<DropZoneButton
105112
data-test-subj="dropButtonEmptyTree"
106113
isVisible={Boolean(movingProcessor)}
@@ -116,7 +123,9 @@ export const ProcessorsTree: FunctionComponent<Props> = memo((props) => {
116123
});
117124
}}
118125
/>
119-
)}
126+
</EuiFlexItem>
127+
)}
128+
<EuiFlexItem grow={false}>
120129
<AddProcessorButton
121130
onClick={() => {
122131
onAction({ type: 'addProcessor', payload: { target: baseSelector } });

0 commit comments

Comments
 (0)