1
1
'use client' ;
2
- import * as React from 'react' ;
3
- import { isFragment } from 'react-is' ;
4
- import PropTypes from 'prop-types' ;
5
- import clsx from 'clsx' ;
6
2
import composeClasses from '@mui/utils/composeClasses' ;
7
- import useId from '@mui/utils/useId' ;
8
3
import refType from '@mui/utils/refType' ;
9
- import ownerDocument from '../utils/ownerDocument' ;
10
- import capitalize from '../utils/capitalize' ;
11
- import Menu from '../Menu/Menu' ;
12
- import { StyledSelectSelect , StyledSelectIcon } from '../NativeSelect/NativeSelectInput' ;
4
+ import useId from '@mui/utils/useId' ;
5
+ import clsx from 'clsx' ;
6
+ import PropTypes from 'prop-types' ;
7
+ import * as React from 'react' ;
8
+ import { isFragment } from 'react-is' ;
13
9
import { isFilled } from '../InputBase/utils' ;
14
- import { styled } from '../zero-styled' ;
10
+ import Menu from '../Menu/Menu' ;
11
+ import { StyledSelectIcon , StyledSelectSelect } from '../NativeSelect/NativeSelectInput' ;
15
12
import slotShouldForwardProp from '../styles/slotShouldForwardProp' ;
16
- import useForkRef from '../utils/useForkRef' ;
13
+ import capitalize from '../utils/capitalize' ;
14
+ import ownerDocument from '../utils/ownerDocument' ;
17
15
import useControlled from '../utils/useControlled' ;
16
+ import useForkRef from '../utils/useForkRef' ;
17
+ import { styled } from '../zero-styled' ;
18
18
import selectClasses , { getSelectUtilityClasses } from './selectClasses' ;
19
19
20
20
const SelectSelect = styled ( StyledSelectSelect , {
@@ -162,6 +162,17 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
162
162
163
163
const anchorElement = displayNode ?. parentNode ;
164
164
165
+ const [ isPointerDown , setIsPointerDown ] = React . useState ( false ) ;
166
+ const dragSelectRef = React . useRef ( {
167
+ isDragging : false ,
168
+ startedOn : null ,
169
+ } ) ;
170
+
171
+ const focusTrackingRef = React . useRef ( {
172
+ isFocused : false ,
173
+ pendingBlur : false ,
174
+ } ) ;
175
+
165
176
React . useImperativeHandle (
166
177
handleRef ,
167
178
( ) => ( {
@@ -210,20 +221,23 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
210
221
return undefined ;
211
222
} , [ labelId ] ) ;
212
223
213
- const update = ( open , event ) => {
214
- if ( open ) {
215
- if ( onOpen ) {
216
- onOpen ( event ) ;
224
+ const update = React . useCallback (
225
+ ( open , event ) => {
226
+ if ( open ) {
227
+ if ( onOpen ) {
228
+ onOpen ( event ) ;
229
+ }
230
+ } else if ( onClose ) {
231
+ onClose ( event ) ;
217
232
}
218
- } else if ( onClose ) {
219
- onClose ( event ) ;
220
- }
221
233
222
- if ( ! isOpenControlled ) {
223
- setMenuMinWidthState ( autoWidth ? null : anchorElement . clientWidth ) ;
224
- setOpenState ( open ) ;
225
- }
226
- } ;
234
+ if ( ! isOpenControlled ) {
235
+ setMenuMinWidthState ( autoWidth ? null : anchorElement . clientWidth ) ;
236
+ setOpenState ( open ) ;
237
+ }
238
+ } ,
239
+ [ autoWidth , anchorElement , isOpenControlled , onOpen , onClose , setOpenState ] ,
240
+ ) ;
227
241
228
242
const handleMouseDown = ( event ) => {
229
243
// Ignore everything but left-click
@@ -234,6 +248,11 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
234
248
event . preventDefault ( ) ;
235
249
displayRef . current . focus ( ) ;
236
250
251
+ // Mark that we've initiated a pointer interaction
252
+ setIsPointerDown ( true ) ;
253
+ dragSelectRef . current . startedOn = displayRef . current ;
254
+
255
+ // Open the menu immediately
237
256
update ( true , event ) ;
238
257
} ;
239
258
@@ -258,6 +277,59 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
258
277
}
259
278
} ;
260
279
280
+ React . useEffect ( ( ) => {
281
+ if ( isPointerDown ) {
282
+ const doc = ownerDocument ( displayRef . current ) ;
283
+
284
+ const handleGlobalMouseUp = ( event ) => {
285
+ setIsPointerDown ( false ) ;
286
+
287
+ if ( dragSelectRef . current . isDragging ) {
288
+ // If we're dragging and the mouse is released, check where it was released
289
+ dragSelectRef . current . isDragging = false ;
290
+
291
+ // Check if mouse is over a menu item
292
+ const targetElement = event . target ;
293
+ let menuItem = null ;
294
+
295
+ // Find if we released on a menu item (checking up the parent chain)
296
+ let current = targetElement ;
297
+ while ( current && ! menuItem ) {
298
+ // Check if this element has role="option"
299
+ if ( current . getAttribute && current . getAttribute ( 'role' ) === 'option' ) {
300
+ menuItem = current ;
301
+ }
302
+ current = current . parentElement ;
303
+ }
304
+
305
+ if ( menuItem ) {
306
+ // Simulate a click on the menu item if we released on one
307
+ menuItem . click ( ) ;
308
+ } else {
309
+ // If released outside menu items, close the menu
310
+ update ( false , event ) ;
311
+ }
312
+ }
313
+ } ;
314
+
315
+ const handleGlobalMouseMove = ( ) => {
316
+ if ( isPointerDown ) {
317
+ dragSelectRef . current . isDragging = true ;
318
+ }
319
+ } ;
320
+
321
+ doc . addEventListener ( 'mouseup' , handleGlobalMouseUp ) ;
322
+ doc . addEventListener ( 'mousemove' , handleGlobalMouseMove ) ;
323
+
324
+ return ( ) => {
325
+ doc . removeEventListener ( 'mouseup' , handleGlobalMouseUp ) ;
326
+ doc . removeEventListener ( 'mousemove' , handleGlobalMouseMove ) ;
327
+ } ;
328
+ }
329
+
330
+ return undefined ;
331
+ } , [ isPointerDown , update ] ) ;
332
+
261
333
const handleItemClick = ( child ) => ( event ) => {
262
334
let newValue ;
263
335
@@ -326,9 +398,23 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
326
398
327
399
const open = displayNode !== null && openState ;
328
400
401
+ const handleFocus = ( event ) => {
402
+ // Skip duplicate focus events
403
+ if ( ! focusTrackingRef . current . isFocused ) {
404
+ focusTrackingRef . current . isFocused = true ;
405
+ focusTrackingRef . current . pendingBlur = false ;
406
+
407
+ if ( onFocus ) {
408
+ onFocus ( event ) ;
409
+ }
410
+ }
411
+ } ;
412
+
329
413
const handleBlur = ( event ) => {
330
414
// if open event.stopImmediatePropagation
331
415
if ( ! open && onBlur ) {
416
+ focusTrackingRef . current . pendingBlur = false ;
417
+ focusTrackingRef . current . isFocused = false ;
332
418
// Preact support, target is read only property on a native event.
333
419
Object . defineProperty ( event , 'target' , { writable : true , value : { value, name } } ) ;
334
420
onBlur ( event ) ;
@@ -509,7 +595,7 @@ const SelectInput = React.forwardRef(function SelectInput(props, ref) {
509
595
onKeyDown = { handleKeyDown }
510
596
onMouseDown = { disabled || readOnly ? null : handleMouseDown }
511
597
onBlur = { handleBlur }
512
- onFocus = { onFocus }
598
+ onFocus = { handleFocus }
513
599
{ ...SelectDisplayProps }
514
600
ownerState = { ownerState }
515
601
className = { clsx ( SelectDisplayProps . className , classes . select , className ) }
0 commit comments