Skip to content

Commit 33fd48f

Browse files
author
Constance
authored
[EuiSelectableTemplateSitewide] Pass keyboard/mouse event to onChange handler (#5926)
* Enhance EuiSelectable to send back click/keyboard events - which will allow consumers to, e.g. react to shift clicks * [REVERT ME] Example of how Kibana would update their `onChange` event to react to shift key * changelog * Revert "[REVERT ME] Example of how Kibana would update their `onChange` event to react to shift key" This reverts commit 1d893f4.
1 parent 3f19485 commit 33fd48f

4 files changed

Lines changed: 70 additions & 27 deletions

File tree

src/components/selectable/selectable.test.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,8 @@ describe('EuiSelectable', () => {
226226
},
227227
];
228228

229-
const onChange = (options: OptionalOption[]) => {
230-
jest.fn(() => options);
231-
};
232-
233229
const component = mount(
234-
<EuiSelectable<OptionalOption> options={options} onChange={onChange}>
230+
<EuiSelectable<OptionalOption> options={options}>
235231
{(list) => list}
236232
</EuiSelectable>
237233
);
@@ -260,12 +256,8 @@ describe('EuiSelectable', () => {
260256
},
261257
];
262258

263-
const onChange = (options: ExtendedOption[]) => {
264-
jest.fn(() => options);
265-
};
266-
267259
const component = mount(
268-
<EuiSelectable<ExtendedOption> options={options} onChange={onChange}>
260+
<EuiSelectable<ExtendedOption> options={options}>
269261
{(list) => list}
270262
</EuiSelectable>
271263
);
@@ -321,6 +313,27 @@ describe('EuiSelectable', () => {
321313
});
322314
});
323315

316+
describe('onChange', () => {
317+
it('calls onChange with selected options array and click/keyboard event', () => {
318+
const onChange = jest.fn();
319+
const component = mount(
320+
<EuiSelectable options={options} onChange={onChange}>
321+
{(list) => list}
322+
</EuiSelectable>
323+
);
324+
325+
component.find('[role="option"]').first().simulate('click');
326+
expect(onChange).toHaveBeenCalledTimes(1);
327+
expect(onChange.mock.calls[0][0][0].checked).toEqual('on');
328+
expect(onChange.mock.calls[0][1].type).toEqual('click');
329+
330+
component.simulate('keydown', { key: 'Enter', shiftKey: true });
331+
expect(onChange).toHaveBeenCalledTimes(2);
332+
expect(onChange.mock.calls[1][0][0].checked).toEqual('on');
333+
expect(onChange.mock.calls[1][1].type).toEqual('keydown');
334+
});
335+
});
336+
324337
describe('errorMessage prop', () => {
325338
it('does not render the message when not defined', () => {
326339
const component = render(

src/components/selectable/selectable.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import React, {
1313
createRef,
1414
ReactElement,
1515
KeyboardEvent,
16+
MouseEvent,
1617
} from 'react';
1718
import classNames from 'classnames';
1819
import { CommonProps, ExclusiveUnion } from '../common';
@@ -33,6 +34,8 @@ import { EuiSelectableOptionsListProps } from './selectable_list/selectable_list
3334
import { EuiSelectableSearchProps } from './selectable_search/selectable_search';
3435
import { Align } from 'react-window';
3536

37+
export type EuiSelectableOnChangeEvent = KeyboardEvent | MouseEvent;
38+
3639
type RequiredEuiSelectableOptionsListProps = Omit<
3740
EuiSelectableOptionsListProps,
3841
keyof typeof EuiSelectableList['defaultProps']
@@ -92,9 +95,13 @@ export type EuiSelectableProps<T = {}> = CommonProps &
9295
*/
9396
options: Array<EuiSelectableOption<T>>;
9497
/**
95-
* Passes back the altered `options` array with selected options as
98+
* Passes back the altered `options` array with selected options having `checked: 'on'`.
99+
* Also passes back the React click/keyboard event as a second argument.
96100
*/
97-
onChange?: (options: Array<EuiSelectableOption<T>>) => void;
101+
onChange?: (
102+
options: Array<EuiSelectableOption<T>>,
103+
event: EuiSelectableOnChangeEvent
104+
) => void;
98105
/**
99106
* Sets the single selection policy of
100107
* `false`: allows multiple selection
@@ -334,7 +341,8 @@ export class EuiSelectable<T = {}> extends Component<
334341
event.stopPropagation();
335342
if (this.state.activeOptionIndex != null && optionsList) {
336343
optionsList.onAddOrRemoveOption(
337-
this.state.visibleOptions[this.state.activeOptionIndex]
344+
this.state.visibleOptions[this.state.activeOptionIndex],
345+
event
338346
);
339347
}
340348
break;
@@ -428,7 +436,10 @@ export class EuiSelectable<T = {}> extends Component<
428436
});
429437
};
430438

431-
onOptionClick = (options: Array<EuiSelectableOption<T>>) => {
439+
onOptionClick = (
440+
options: Array<EuiSelectableOption<T>>,
441+
event: EuiSelectableOnChangeEvent
442+
) => {
432443
const { isPreFiltered, onChange } = this.props;
433444
const { searchValue } = this.state;
434445
const visibleOptions = getMatchingOptions(
@@ -440,7 +451,7 @@ export class EuiSelectable<T = {}> extends Component<
440451
this.setState({ visibleOptions });
441452

442453
if (onChange) {
443-
onChange(options);
454+
onChange(options, event);
444455
}
445456
};
446457

src/components/selectable/selectable_list/selectable_list.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { CommonProps, ExclusiveUnion } from '../../common';
2424
import { EuiAutoSizer } from '../../auto_sizer';
2525
import { EuiHighlight } from '../../highlight';
2626
import { EuiSelectableOption } from '../selectable_option';
27+
import { EuiSelectableOnChangeEvent } from '../selectable';
2728
import {
2829
EuiSelectableListItem,
2930
EuiSelectableListItemProps,
@@ -109,7 +110,10 @@ export type EuiSelectableListProps<T> = EuiSelectableOptionsListProps & {
109110
/**
110111
* Returns the array of options with altered checked state
111112
*/
112-
onOptionClick: (options: Array<EuiSelectableOption<T>>) => void;
113+
onOptionClick: (
114+
options: Array<EuiSelectableOption<T>>,
115+
event: EuiSelectableOnChangeEvent
116+
) => void;
113117
/**
114118
* Custom render for the label portion of the option;
115119
* Takes (option, searchValue), returns ReactNode
@@ -273,7 +277,9 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
273277
onMouseDown={() => {
274278
setActiveOptionIndex(index);
275279
}}
276-
onClick={() => this.onAddOrRemoveOption(option)}
280+
onClick={(event) => {
281+
this.onAddOrRemoveOption(option, event);
282+
}}
277283
ref={ref ? ref.bind(null, index) : undefined}
278284
isFocused={activeOptionIndex === index}
279285
title={searchableLabel || label}
@@ -414,7 +420,10 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
414420
);
415421
}
416422

417-
onAddOrRemoveOption = (option: EuiSelectableOption<T>) => {
423+
onAddOrRemoveOption = (
424+
option: EuiSelectableOption<T>,
425+
event: EuiSelectableOnChangeEvent
426+
) => {
418427
if (option.disabled) {
419428
return;
420429
}
@@ -425,17 +434,20 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
425434
visibleOptions.findIndex(({ label }) => label === option.label),
426435
() => {
427436
if (option.checked === 'on' && allowExclusions) {
428-
this.onExcludeOption(option);
437+
this.onExcludeOption(option, event);
429438
} else if (option.checked === 'on' || option.checked === 'off') {
430-
this.onRemoveOption(option);
439+
this.onRemoveOption(option, event);
431440
} else {
432-
this.onAddOption(option);
441+
this.onAddOption(option, event);
433442
}
434443
}
435444
);
436445
};
437446

438-
private onAddOption = (addedOption: EuiSelectableOption<T>) => {
447+
private onAddOption = (
448+
addedOption: EuiSelectableOption<T>,
449+
event: EuiSelectableOnChangeEvent
450+
) => {
439451
const { onOptionClick, options, singleSelection } = this.props;
440452

441453
const updatedOptions = options.map((option) => {
@@ -453,10 +465,13 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
453465
return updatedOption;
454466
});
455467

456-
onOptionClick(updatedOptions);
468+
onOptionClick(updatedOptions, event);
457469
};
458470

459-
private onRemoveOption = (removedOption: EuiSelectableOption<T>) => {
471+
private onRemoveOption = (
472+
removedOption: EuiSelectableOption<T>,
473+
event: EuiSelectableOnChangeEvent
474+
) => {
460475
const { onOptionClick, singleSelection, options } = this.props;
461476

462477
const updatedOptions = options.map((option) => {
@@ -469,10 +484,13 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
469484
return updatedOption;
470485
});
471486

472-
onOptionClick(updatedOptions);
487+
onOptionClick(updatedOptions, event);
473488
};
474489

475-
private onExcludeOption = (excludedOption: EuiSelectableOption<T>) => {
490+
private onExcludeOption = (
491+
excludedOption: EuiSelectableOption<T>,
492+
event: EuiSelectableOnChangeEvent
493+
) => {
476494
const { onOptionClick, options } = this.props;
477495
excludedOption.checked = 'off';
478496

@@ -486,6 +504,6 @@ export class EuiSelectableList<T> extends Component<EuiSelectableListProps<T>> {
486504
return updatedOption;
487505
});
488506

489-
onOptionClick(updatedOptions);
507+
onOptionClick(updatedOptions, event);
490508
};
491509
}

upcoming_changelogs/5926.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Added the click/keydown event as an argument to `EuiSelectable`/`EuiSelectableTemplateSitewide`'s `onChange` prop

0 commit comments

Comments
 (0)