Repository: mlaursen/react-md
Pull request #949 (2020-09-11 17:26:59)

Ability to trigger change event for SelectionControl on enter key

mlaursen: Looks good to me, thanks! I'll release this as `v1.18.0` under the `v1` dist tag

Patch:
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'),
   };


Test patch:
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(<SelectionControl {...props} onChange={onChange} />);
+
+  // 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(<SelectionControl {...PROPS} onChange={onChange} />);
-
-    // 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', () => {