Repository: mui/material-ui (TypeScript)
Date: 2019-10-21 13:55:10+00:00
Task type: Feature

Issue:
[Select] Feels less response than native counterpart
- [x] I have searched the [issues](https://github.com/callemall/material-ui/issues) of this repository and believe that this is not a duplicate.

## Expected Behavior
When I use a Select Field, it should "feel" responsive and fast. Currently, before the menu with all select options pops up, we wait until the mouseclick is over. Instead, at least on non-touch devices we should consider starting the opening animation as soon as the click starts (for instance the mousedown event).

## Current Behavior
As described in "Expected Behavior", currently we wait until the click event is over before the Select field opens the menu with all options. This makes it feel much less responsive and slow compared with say the native select field in Chrome, which opens instantly.

## Steps to Reproduce
Check the Select page in the current documentation to try it out: https://material-ui.com/demos/selects/

## Context
We use the select field in various places of our app, and we feel like the current behavior of the Select field makes our application feel sluggish and slow.


## Your Environment

| Tech         | Version                     |
|--------------|-----------------------------|
| Material-UI  | Beta 20                     |
| React        | 15.6.2                      |
| browser      | Google Chrome 61.0.3163.100 |



Issue discussion:
One important thing to take into account: the behavior should stay the same for touch interactions as we need to filter our scroll touch interactions.
I hope it's as simple as implemented as replacing the onClick by onMouseDown event callback.
https://github.com/callemall/material-ui/blob/7064086473f255bd48fcfc2987b0353a8c7c7b7c/src/Select/SelectInput.js#L347
I tried your solution and it turns out it works just fine by adding an equivalent onMouseDown handler. Tested on desktop (Google Chrome) and mobile (Android Phone).
@rynti Do you want to submit a pull request?
if `onMouseDown` handler doesn't work, is there a possibility where we can have both, `onClick` & on `onMouseDown` handler.

Where as you say https://github.com/mui-org/material-ui/pull/9009#issuecomment-342283702

> I'm sorry, this is definitely not a simple issue to fix. It's surfacing another issue with how the focus is handled. It's will require a rethink of how it's done. It's certainly not a one-line change.

where `onClick`. can manage how the focus is handled and `onMouseDown` event calls 

https://github.com/mui-org/material-ui/blob/7064086473f255bd48fcfc2987b0353a8c7c7b7c/src/Select/SelectInput.js#L347

@oliviertassinari 

I worked on this for a while, but then I thought I would check on the actual premise of the idea, that using onmousedown over onclick would make things any faster, and it sure doesn't seem like it based on my testing and research. I would say maybe close this so nobody else wastes times trying to implement it. @oliviertassinari 
@jmetev1 I have been benchmarking (testing) as many platforms as possible, they all close the select and menu at the mouse down event rather than the mouse up event. I think that it's a good reason on its own to try to follow this convention. 

Solution patch:
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
@@ -123,7 +123,11 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
     }
   };
 
-  const handleClick = event => {
+  const handleMouseDown = event => {
+    // Hijack the default focus behavior.
+    event.preventDefault();
+    displayNode.focus();
+
     update(true, event);
   };
 
@@ -322,7 +326,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
         aria-labelledby={`${labelId || ''} ${buttonId || ''}`}
         aria-haspopup="listbox"
         onKeyDown={handleKeyDown}
-        onClick={disabled || readOnly ? null : handleClick}
+        onMouseDown={disabled || readOnly ? null : handleMouseDown}
         onBlur={handleBlur}
         onFocus={onFocus}
         {...SelectDisplayProps}


Test patch:
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
@@ -95,13 +95,13 @@ describe('<Select />', () => {
     const { getByRole, getAllByRole, queryByRole } = render(
       <Select
         onBlur={handleBlur}
+        value=""
         onMouseDown={event => {
           // simulating certain platforms that focus on mousedown
           if (event.defaultPrevented === false) {
             event.currentTarget.focus();
           }
         }}
-        value=""
       >
         <MenuItem value="">none</MenuItem>
         <MenuItem value={10}>Ten</MenuItem>
@@ -109,11 +109,7 @@ describe('<Select />', () => {
     );
     const trigger = getByRole('button');
 
-    // simulating user click
-    act(() => {
-      fireEvent.mouseDown(trigger);
-      trigger.click();
-    });
+    fireEvent.mouseDown(trigger);
 
     expect(handleBlur.callCount).to.equal(0);
     expect(getByRole('listbox')).to.be.ok;
@@ -196,9 +192,7 @@ describe('<Select />', () => {
   it('should focus list if no selection', () => {
     const { getByRole } = render(<Select value="" autoFocus />);
 
-    act(() => {
-      getByRole('button').click();
-    });
+    fireEvent.mouseDown(getByRole('button'));
 
     // TODO not matching WAI-ARIA authoring practices. It should focus the first (or selected) item.
     expect(getByRole('listbox')).to.have.focus;
@@ -224,7 +218,7 @@ describe('<Select />', () => {
           <MenuItem value="2" />
         </Select>,
       );
-      getByRole('button').click();
+      fireEvent.mouseDown(getByRole('button'));
       getAllByRole('option')[1].click();
 
       expect(onChangeHandler.calledOnce).to.be.true;
@@ -514,7 +508,7 @@ describe('<Select />', () => {
         </Select>,
       );
 
-      fireEvent.click(getByRole('button'));
+      fireEvent.mouseDown(getByRole('button'));
       act(() => {
         clock.tick(99);
       });
@@ -612,10 +606,7 @@ describe('<Select />', () => {
       }
       const { getByRole, queryByRole } = render(<ControlledWrapper />);
 
-      act(() => {
-        getByRole('button').click();
-      });
-
+      fireEvent.mouseDown(getByRole('button'));
       expect(getByRole('listbox')).to.be.ok;
 
       act(() => {
@@ -654,10 +645,7 @@ describe('<Select />', () => {
       const button = getByRole('button');
       stub(button, 'clientWidth').get(() => 14);
 
-      act(() => {
-        button.click();
-      });
-
+      fireEvent.mouseDown(button);
       expect(getByTestId('paper').style).to.have.property('minWidth', '14px');
     });
 
@@ -670,10 +658,7 @@ describe('<Select />', () => {
       const button = getByRole('button');
       stub(button, 'clientWidth').get(() => 14);
 
-      act(() => {
-        button.click();
-      });
-
+      fireEvent.mouseDown(button);
       expect(getByTestId('paper').style).to.have.property('minWidth', '');
     });
   });
@@ -794,7 +779,7 @@ describe('<Select />', () => {
         });
         const { getByRole, getAllByRole } = render(<ControlledSelectInput onChange={onChange} />);
 
-        fireEvent.click(getByRole('button'));
+        fireEvent.mouseDown(getByRole('button'));
         const options = getAllByRole('option');
         fireEvent.click(options[2]);
 
diff --git a/packages/material-ui/test/integration/Select.test.js b/packages/material-ui/test/integration/Select.test.js
--- a/packages/material-ui/test/integration/Select.test.js
+++ b/packages/material-ui/test/integration/Select.test.js
@@ -58,8 +58,7 @@ describe('<Select> integration', () => {
       const trigger = getByRole('button');
       // Let's open the select component
       // in the browser user click also focuses
-      trigger.focus();
-      trigger.click();
+      fireEvent.mouseDown(trigger);
 
       const options = getAllByRole('option');
       expect(options[1]).to.have.focus;
@@ -81,8 +80,7 @@ describe('<Select> integration', () => {
       expect(trigger).to.have.text('Ten');
       // Let's open the select component
       // in the browser user click also focuses
-      trigger.focus();
-      trigger.click();
+      fireEvent.mouseDown(trigger);
 
       const options = getAllByRole('option');
       expect(options[1]).to.have.focus;