Repository: mui/material-ui (TypeScript)
Date: 2019-12-09 11:49:45+00:00
Task type: Bug Fix

Issue:
[Select] Opening a focused Select field using the space key does not work as expected
- [x] The issue is present in the latest release.
- [x] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.

## Current Behavior 😯

When interacting with the Select component using a keyboard, hitting the space key when focused on the component opens and then immediately closes the menu, selecting the first option. Options _can_ be selected by holding space, navigating using the arrow keys, and then releasing the space key, but I doubt this is intended functionality.
 
## Expected Behavior 🤔

When focused on the Select component, hitting the space key should open the menu and allow the user to select an option in the same way as hitting the enter key - This is how the Native Select component works.

One thing I did notice is that when using a native select, the space key is able to open the options menu however it is not able to select an option (only the enter key seems to be able to do this) - unsure if we want to replicate this exact functionality in the Select component or have the space key work exactly the same as the enter key. This could be specific to Chrome also, as I haven't tested on other browsers. 

## Steps to Reproduce 🕹

This issue can be reproduced on the docs site easily: 
https://material-ui.com/components/selects/

Steps:

1. Navigate to the Select examples page on the Material UI docs site using the link above
2. Using the tab key, focus onto one of the Simple Select example inputs
3. Hit or hold the space key (menu will open and immediately close)
4. Focus onto one of the Native Select examples
5. Hit the space key (menu will open as expected)

Below is a gif showing what happens when the space key is hit:
![CRDg76RYAc](https://user-images.githubusercontent.com/6867163/70212791-abe09c00-1783-11ea-9b6c-f4d17e509add.gif)


## Context 🔦

In our application we have a form built from MUI components - using the keyboard to navigate between inputs is a feature that is commonly used. 

## Your Environment 🌎

| Tech        | Version |
| ----------- | ------- |
| Material-UI | v4.7.1 |
| React       | v16.12.0 |
| Browser     | 72.0.3626.81 (Official Build) (64-bit) (Windows 10) |
| TypeScript  | v3.7.2 |



Issue discussion:
@matt-downs The behavior changed in #18319, @eps1lon Is on it :)

Solution patch:
diff --git a/packages/material-ui/src/ButtonBase/ButtonBase.js b/packages/material-ui/src/ButtonBase/ButtonBase.js
--- a/packages/material-ui/src/ButtonBase/ButtonBase.js
+++ b/packages/material-ui/src/ButtonBase/ButtonBase.js
@@ -219,7 +219,15 @@ const ButtonBase = React.forwardRef(function ButtonBase(props, ref) {
     }
   });
   const handleKeyUp = useEventCallback(event => {
-    if (focusRipple && event.key === ' ' && rippleRef.current && focusVisible) {
+    // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
+    // https://codesandbox.io/s/button-keyup-preventdefault-dn7f0
+    if (
+      focusRipple &&
+      event.key === ' ' &&
+      rippleRef.current &&
+      focusVisible &&
+      !event.defaultPrevented
+    ) {
       keydownRef.current = false;
       event.persist();
       rippleRef.current.stop(event, () => {
@@ -231,7 +239,12 @@ const ButtonBase = React.forwardRef(function ButtonBase(props, ref) {
     }
 
     // Keyboard accessibility for non interactive elements
-    if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ') {
+    if (
+      event.target === event.currentTarget &&
+      isNonNativeButton() &&
+      event.key === ' ' &&
+      !event.defaultPrevented
+    ) {
       event.preventDefault();
       if (onClick) {
         onClick(event);
diff --git a/packages/material-ui/src/Select/SelectInput.js b/packages/material-ui/src/Select/SelectInput.js
--- a/packages/material-ui/src/Select/SelectInput.js
+++ b/packages/material-ui/src/Select/SelectInput.js
@@ -257,6 +257,18 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
     return React.cloneElement(child, {
       'aria-selected': selected ? 'true' : undefined,
       onClick: handleItemClick(child),
+      onKeyUp: event => {
+        if (event.key === ' ') {
+          // otherwise our MenuItems dispatches a click event
+          // it's not behavior of the native <option> and causes
+          // the select to close immediately since we open on space keydown
+          event.preventDefault();
+        }
+        const { onKeyUp } = child.props;
+        if (typeof onKeyUp === 'function') {
+          onKeyUp(event);
+        }
+      },
       role: 'option',
       selected,
       value: undefined, // The value is most likely not a valid HTML attribute.


Test patch:
diff --git a/packages/material-ui/src/ButtonBase/ButtonBase.test.js b/packages/material-ui/src/ButtonBase/ButtonBase.test.js
--- a/packages/material-ui/src/ButtonBase/ButtonBase.test.js
+++ b/packages/material-ui/src/ButtonBase/ButtonBase.test.js
@@ -715,6 +715,32 @@ describe('<ButtonBase />', () => {
         expect(onClickSpy.returnValues[0]).to.equal(true);
       });
 
+      it('does not call onClick when a spacebar is released and the default is prevented', () => {
+        const onClickSpy = spy(event => event.defaultPrevented);
+        const { getByRole } = render(
+          <ButtonBase
+            onClick={onClickSpy}
+            onKeyUp={
+              /**
+               * @param {React.SyntheticEvent} event
+               */
+              event => event.preventDefault()
+            }
+            component="div"
+          >
+            Hello
+          </ButtonBase>,
+        );
+        const button = getByRole('button');
+        button.focus();
+
+        fireEvent.keyUp(document.activeElement || document.body, {
+          key: ' ',
+        });
+
+        expect(onClickSpy.callCount).to.equal(0);
+      });
+
       it('calls onClick when Enter is pressed on the element', () => {
         const onClickSpy = spy(event => event.defaultPrevented);
         const { getByRole } = render(
diff --git a/packages/material-ui/src/Select/Select.test.js b/packages/material-ui/src/Select/Select.test.js
--- a/packages/material-ui/src/Select/Select.test.js
+++ b/packages/material-ui/src/Select/Select.test.js
@@ -148,8 +148,10 @@ describe('<Select />', () => {
       getByRole('button').focus();
 
       fireEvent.keyDown(document.activeElement, { key });
+      expect(getByRole('listbox', { hidden: false })).to.be.ok;
 
-      expect(getByRole('listbox')).to.be.ok;
+      fireEvent.keyUp(document.activeElement, { key });
+      expect(getByRole('listbox', { hidden: false })).to.be.ok;
     });
   });
 
@@ -485,7 +487,9 @@ describe('<Select />', () => {
       getByRole('button').focus();
 
       fireEvent.keyDown(document.activeElement, { key: 'ArrowDown' });
+      expect(queryByRole('listbox')).not.to.be.ok;
 
+      fireEvent.keyUp(document.activeElement, { key: 'ArrowDown' });
       expect(queryByRole('listbox')).not.to.be.ok;
     });
   });
@@ -871,4 +875,20 @@ describe('<Select />', () => {
       expect(getByLabelText('A select')).to.have.property('tagName', 'SELECT');
     });
   });
+
+  it('prevents the default when releasing Space on the children', () => {
+    const keyUpSpy = spy(event => event.defaultPrevented);
+    render(
+      <Select value="one" open>
+        <MenuItem onKeyUp={keyUpSpy} value="one">
+          One
+        </MenuItem>
+      </Select>,
+    );
+
+    fireEvent.keyUp(document.activeElement, { key: ' ' });
+
+    expect(keyUpSpy.callCount).to.equal(1);
+    expect(keyUpSpy.returnValues[0]).to.equal(true);
+  });
 });