From 9e75bb6ed29e642a6d07a2750d70def1d88bd3b4 Mon Sep 17 00:00:00 2001 From: Denis Sikuler Date: Tue, 15 Sep 2020 02:50:16 +0300 Subject: [PATCH] Ability to trigger change event for SelectionControl on enter key (#949) --- src/js/SelectionControls/Checkbox.js | 5 ++ src/js/SelectionControls/Radio.js | 5 ++ .../SelectionControls/SelectionControl.d.ts | 1 + src/js/SelectionControls/SelectionControl.js | 16 +++-- src/js/SelectionControls/Switch.js | 5 ++ .../__tests__/SelectionControl.js | 60 +++++++++++-------- 6 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/js/SelectionControls/Checkbox.js b/src/js/SelectionControls/Checkbox.js index 3b6a8d0e37..2756647b19 100644 --- a/src/js/SelectionControls/Checkbox.js +++ b/src/js/SelectionControls/Checkbox.js @@ -93,6 +93,11 @@ export default class Checkbox extends PureComponent { */ inline: PropTypes.bool, + /** + * Whether the `change` event should be triggered for `Checkbox` on pressing of `Enter` key. + */ + changeOnEnter: PropTypes.bool, + /** * The icon to display when the checkbox is checked. */ diff --git a/src/js/SelectionControls/Radio.js b/src/js/SelectionControls/Radio.js index e3740f7cce..cbad7ebc78 100644 --- a/src/js/SelectionControls/Radio.js +++ b/src/js/SelectionControls/Radio.js @@ -87,6 +87,11 @@ export default class Radio extends PureComponent { */ inline: PropTypes.bool, + /** + * Whether the `change` event should be triggered for `Radio` on pressing of `Enter` key. + */ + changeOnEnter: PropTypes.bool, + /** * The icon to display when the radio is checked/selected. */ diff --git a/src/js/SelectionControls/SelectionControl.d.ts b/src/js/SelectionControls/SelectionControl.d.ts index caa0315e33..409f7839fc 100644 --- a/src/js/SelectionControls/SelectionControl.d.ts +++ b/src/js/SelectionControls/SelectionControl.d.ts @@ -18,6 +18,7 @@ export interface BaseSelectionControlProps extends ControlProps { checked?: boolean; defaultChecked?: boolean; inline?: boolean; + changeOnEnter?: boolean; 'aria-label'?: string; 'aria-labelledby'?: IdPropType; className?: ClassNameType; diff --git a/src/js/SelectionControls/SelectionControl.js b/src/js/SelectionControls/SelectionControl.js index 44e40db810..5fe3a19005 100644 --- a/src/js/SelectionControls/SelectionControl.js +++ b/src/js/SelectionControls/SelectionControl.js @@ -4,7 +4,7 @@ import cn from 'classnames'; import deprecated from 'react-prop-types/lib/deprecated'; import isRequiredForA11y from 'react-prop-types/lib/isRequiredForA11y'; -import { SPACE } from '../constants/keyCodes'; +import { ENTER, SPACE } from '../constants/keyCodes'; import getField from '../utils/getField'; import themeColors from '../utils/themeColors'; import oneRequiredForA11y from '../utils/PropTypes/oneRequiredForA11y'; @@ -153,7 +153,7 @@ export default class SelectionControl extends PureComponent { ]))), /** - * Boolean if the `Radio` is disabled. + * Boolean if the `SelectionControl` is disabled. */ disabled: PropTypes.bool, @@ -209,6 +209,11 @@ export default class SelectionControl extends PureComponent { */ inline: PropTypes.bool, + /** + * Whether the `change` event should be triggered for `SelectionControl` on pressing of `Enter` key. + */ + changeOnEnter: PropTypes.bool, + /** * The icon to use for a checked `checkbox` selection control. */ @@ -343,12 +348,13 @@ export default class SelectionControl extends PureComponent { }; _handleKeyDown = (e) => { - if (this.props.onKeyDown) { - this.props.onKeyDown(e); + const { props } = this; + if (props.onKeyDown) { + props.onKeyDown(e); } const key = e.which || e.keyCode; - if (key === SPACE) { + if (key === SPACE || (key === ENTER && props.changeOnEnter)) { this._input.click(); } } diff --git a/src/js/SelectionControls/Switch.js b/src/js/SelectionControls/Switch.js index a3f5e51929..505b41092c 100644 --- a/src/js/SelectionControls/Switch.js +++ b/src/js/SelectionControls/Switch.js @@ -82,6 +82,11 @@ export default class Switch extends PureComponent { */ checked: controlled(PropTypes.bool, 'onChange', 'defaultChecked'), + /** + * Whether the `change` event should be triggered for `Switch` on pressing of `Enter` key. + */ + changeOnEnter: PropTypes.bool, + defaultToggled: deprecated(PropTypes.bool, 'Use the `defaultChecked` prop instead'), toggled: deprecated(PropTypes.bool, 'Use the `checked` prop instead'), }; diff --git a/src/js/SelectionControls/__tests__/SelectionControl.js b/src/js/SelectionControls/__tests__/SelectionControl.js index 97dda7f464..219336bec7 100644 --- a/src/js/SelectionControls/__tests__/SelectionControl.js +++ b/src/js/SelectionControls/__tests__/SelectionControl.js @@ -8,7 +8,7 @@ import AccessibleFakeInkedButton from '../../Helpers/AccessibleFakeInkedButton'; import SelectionControl from '../SelectionControl'; import SwitchTrack from '../SwitchTrack'; import SwitchThumb from '../SwitchThumb'; -import { SPACE } from '../../constants/keyCodes'; +import { ENTER, SPACE } from '../../constants/keyCodes'; jest.mock('../../Inks/InkContainer'); // can't calc left warning @@ -19,6 +19,35 @@ const PROPS = { label: 'Label', }; +function checkKeyHandling(props, keyCode) { + const onChange = jest.fn(); + const control = mount(); + + // While testing, the input.click() doesn't do anything, so mock it in here + let input = control.find('input').instance(); + input.click = () => onChange(); + + let toggle = control.find(AccessibleFakeInkedButton); + toggle.simulate('keyDown', { which: keyCode, keyCode }); + + expect(onChange.mock.calls.length).toBe(1); + + control.setProps({ type: 'radio' }); + input = control.find('input').instance(); + input.click = () => onChange(); + toggle = control.find(AccessibleFakeInkedButton); + toggle.simulate('keyDown', { which: keyCode, keyCode }); + expect(onChange.mock.calls.length).toBe(2); + + control.setProps({ type: 'switch' }); + input = control.find('input').instance(); + input.click = () => onChange(); + + toggle = control.find(SwitchThumb); + toggle.simulate('keyDown', { which: keyCode, keyCode }); + expect(onChange.mock.calls.length).toBe(3); +} + describe('SelectionControl', () => { it('should correctly apply styles and className', () => { const props = { @@ -91,32 +120,11 @@ describe('SelectionControl', () => { }); it('should correctly trigger the change event when the spacebar key is pressed while focusing the toggle by clicking the input', () => { - const onChange = jest.fn(); - const control = mount(); - - // While testing, the input.click() doesn't do anything, so mock it in here - let input = control.find('input').instance(); - input.click = () => onChange(); - - let toggle = control.find(AccessibleFakeInkedButton); - toggle.simulate('keyDown', { which: SPACE, keyCode: SPACE }); - - expect(onChange.mock.calls.length).toBe(1); - - control.setProps({ type: 'radio' }); - input = control.find('input').instance(); - input.click = () => onChange(); - toggle = control.find(AccessibleFakeInkedButton); - toggle.simulate('keyDown', { which: SPACE, keyCode: SPACE }); - expect(onChange.mock.calls.length).toBe(2); - - control.setProps({ type: 'switch' }); - input = control.find('input').instance(); - input.click = () => onChange(); + checkKeyHandling(PROPS, SPACE); + }); - toggle = control.find(SwitchThumb); - toggle.simulate('keyDown', { which: SPACE, keyCode: SPACE }); - expect(onChange.mock.calls.length).toBe(3); + it('should correctly trigger the change event when the enter key is pressed while focusing the toggle by clicking the input', () => { + checkKeyHandling({ ...PROPS, changeOnEnter: true }, ENTER); }); it('should call the onChange prop with the correct values', () => {