@mui/material 9.0.0 → 9.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Autocomplete/Autocomplete.js +65 -11
- package/Autocomplete/Autocomplete.mjs +65 -11
- package/Avatar/Avatar.js +4 -0
- package/Avatar/Avatar.mjs +4 -0
- package/Badge/Badge.js +3 -0
- package/Badge/Badge.mjs +3 -0
- package/Button/Button.js +19 -2
- package/Button/Button.mjs +19 -2
- package/ButtonBase/ButtonBase.d.mts +7 -0
- package/ButtonBase/ButtonBase.d.ts +7 -0
- package/ButtonBase/ButtonBase.js +5 -2
- package/ButtonBase/ButtonBase.mjs +5 -2
- package/CHANGELOG.md +132 -1245
- package/Checkbox/Checkbox.js +2 -1
- package/Checkbox/Checkbox.mjs +2 -1
- package/CircularProgress/CircularProgress.d.mts +12 -2
- package/CircularProgress/CircularProgress.d.ts +12 -2
- package/CircularProgress/CircularProgress.js +33 -6
- package/CircularProgress/CircularProgress.mjs +33 -6
- package/ClickAwayListener/ClickAwayListener.js +3 -6
- package/ClickAwayListener/ClickAwayListener.mjs +3 -6
- package/Dialog/Dialog.js +11 -6
- package/Dialog/Dialog.mjs +11 -6
- package/Drawer/Drawer.js +18 -4
- package/Drawer/Drawer.mjs +18 -4
- package/Fab/Fab.js +7 -1
- package/Fab/Fab.mjs +7 -1
- package/FilledInput/FilledInput.d.mts +4 -0
- package/FilledInput/FilledInput.d.ts +4 -0
- package/FilledInput/FilledInput.js +18 -20
- package/FilledInput/FilledInput.mjs +18 -20
- package/FormControl/useFormControl.d.mts +12 -2
- package/FormControl/useFormControl.d.ts +12 -2
- package/FormControl/useFormControl.js +13 -0
- package/FormControl/useFormControl.mjs +12 -0
- package/FormControlLabel/FormControlLabel.js +5 -8
- package/FormControlLabel/FormControlLabel.mjs +5 -8
- package/FormGroup/FormGroup.js +2 -5
- package/FormGroup/FormGroup.mjs +2 -5
- package/FormHelperText/FormHelperText.js +2 -5
- package/FormHelperText/FormHelperText.mjs +2 -5
- package/FormLabel/FormLabel.js +2 -5
- package/FormLabel/FormLabel.mjs +2 -5
- package/IconButton/IconButton.js +1 -8
- package/IconButton/IconButton.mjs +1 -8
- package/Input/Input.d.mts +4 -0
- package/Input/Input.d.ts +4 -0
- package/Input/Input.js +6 -0
- package/Input/Input.mjs +6 -0
- package/InputBase/InputBase.d.mts +2 -1
- package/InputBase/InputBase.d.ts +2 -1
- package/InputBase/InputBase.js +50 -15
- package/InputBase/InputBase.mjs +50 -15
- package/InputLabel/InputLabel.js +5 -8
- package/InputLabel/InputLabel.mjs +5 -8
- package/LinearProgress/LinearProgress.d.mts +12 -2
- package/LinearProgress/LinearProgress.d.ts +12 -2
- package/LinearProgress/LinearProgress.js +42 -10
- package/LinearProgress/LinearProgress.mjs +42 -10
- package/List/List.js +2 -1
- package/List/List.mjs +2 -1
- package/ListItemButton/ListItemButton.js +7 -1
- package/ListItemButton/ListItemButton.mjs +7 -1
- package/MenuItem/MenuItem.js +7 -1
- package/MenuItem/MenuItem.mjs +7 -1
- package/MenuList/MenuList.js +2 -1
- package/MenuList/MenuList.mjs +2 -1
- package/NativeSelect/NativeSelect.js +2 -5
- package/NativeSelect/NativeSelect.mjs +2 -5
- package/OutlinedInput/OutlinedInput.js +13 -23
- package/OutlinedInput/OutlinedInput.mjs +13 -23
- package/PigmentContainer/PigmentContainer.js +0 -1
- package/PigmentContainer/PigmentContainer.mjs +0 -1
- package/Popper/BasePopper.js +23 -1
- package/Popper/BasePopper.mjs +23 -1
- package/Select/Select.js +2 -5
- package/Select/Select.mjs +2 -5
- package/Select/SelectInput.js +164 -2
- package/Select/SelectInput.mjs +164 -2
- package/Slide/Slide.js +48 -26
- package/Slide/Slide.mjs +49 -27
- package/Slider/Slider.js +10 -1
- package/Slider/Slider.mjs +10 -1
- package/Slider/useSlider.js +3 -2
- package/Slider/useSlider.mjs +3 -2
- package/SwipeableDrawer/SwipeableDrawer.js +7 -3
- package/SwipeableDrawer/SwipeableDrawer.mjs +7 -3
- package/Switch/Switch.js +7 -6
- package/Switch/Switch.mjs +7 -6
- package/Tabs/ScrollbarSize.js +2 -1
- package/Tabs/ScrollbarSize.mjs +2 -1
- package/Tabs/Tabs.js +2 -1
- package/Tabs/Tabs.mjs +2 -1
- package/Tooltip/Tooltip.js +26 -108
- package/Tooltip/Tooltip.mjs +26 -108
- package/Unstable_TrapFocus/FocusTrap.js +18 -14
- package/Unstable_TrapFocus/FocusTrap.mjs +18 -14
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +49 -49
- package/styles/responsiveFontSizes.js +19 -8
- package/styles/responsiveFontSizes.mjs +19 -8
- package/styles/useThemeProps.d.mts +3 -3
- package/styles/useThemeProps.d.ts +3 -3
- package/transitions/utils.d.mts +17 -0
- package/transitions/utils.d.ts +17 -0
- package/transitions/utils.js +64 -0
- package/transitions/utils.mjs +63 -0
- package/useAutocomplete/useAutocomplete.d.mts +4 -5
- package/useAutocomplete/useAutocomplete.d.ts +4 -5
- package/useAutocomplete/useAutocomplete.js +166 -53
- package/useAutocomplete/useAutocomplete.mjs +166 -53
- package/utils/contains.d.mts +2 -0
- package/utils/contains.d.ts +2 -0
- package/utils/contains.js +9 -0
- package/utils/contains.mjs +2 -0
- package/utils/focusable.d.mts +7 -0
- package/utils/focusable.d.ts +7 -0
- package/utils/focusable.js +20 -0
- package/utils/focusable.mjs +13 -0
- package/utils/getEventTarget.d.mts +2 -0
- package/utils/getEventTarget.d.ts +2 -0
- package/utils/getEventTarget.js +9 -0
- package/utils/getEventTarget.mjs +2 -0
- package/utils/mergeSlotProps.js +2 -8
- package/utils/mergeSlotProps.mjs +1 -8
- package/version/index.js +2 -2
- package/version/index.mjs +2 -2
- package/FormControl/formControlState.js +0 -21
- package/FormControl/formControlState.mjs +0 -15
|
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
9
9
|
exports.createFilterOptions = createFilterOptions;
|
|
10
10
|
exports.default = void 0;
|
|
11
11
|
var React = _interopRequireWildcard(require("react"));
|
|
12
|
+
var _contains = _interopRequireDefault(require("@mui/utils/contains"));
|
|
12
13
|
var _setRef = _interopRequireDefault(require("@mui/utils/setRef"));
|
|
13
14
|
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
|
|
14
15
|
var _useControlled = _interopRequireDefault(require("@mui/utils/useControlled"));
|
|
@@ -63,7 +64,7 @@ const defaultFilterOptions = createFilterOptions();
|
|
|
63
64
|
|
|
64
65
|
// Number of options to jump in list box when `Page Up` and `Page Down` keys are used.
|
|
65
66
|
const pageSize = 5;
|
|
66
|
-
const defaultIsActiveElementInListbox = listboxRef => listboxRef.current !== null && listboxRef.current.parentElement
|
|
67
|
+
const defaultIsActiveElementInListbox = listboxRef => listboxRef.current !== null && (0, _contains.default)(listboxRef.current.parentElement, document.activeElement);
|
|
67
68
|
const defaultIsOptionEqualToValue = (option, value) => option === value;
|
|
68
69
|
const MULTIPLE_DEFAULT_VALUE = [];
|
|
69
70
|
function getInputValue(value, multiple, getOptionLabel, renderValue) {
|
|
@@ -141,6 +142,20 @@ function useAutocomplete(props) {
|
|
|
141
142
|
const defaultHighlighted = autoHighlight ? 0 : -1;
|
|
142
143
|
const highlightedIndexRef = React.useRef(defaultHighlighted);
|
|
143
144
|
|
|
145
|
+
// Tracks how the current highlight was set:
|
|
146
|
+
// - 'keyboard' — arrow keys, Home/End, PageUp/PageDown
|
|
147
|
+
// - 'mouse' — handleOptionMouseMove
|
|
148
|
+
// - 'touch' — handleOptionTouchStart
|
|
149
|
+
// - null — programmatic (autoHighlight, value sync)
|
|
150
|
+
//
|
|
151
|
+
// This lets handleBlur and the Enter handler distinguish intentional
|
|
152
|
+
// interactions from incidental ones — e.g. autoSelect should not commit
|
|
153
|
+
// a highlight that came from a casual mouse hover.
|
|
154
|
+
/** @type {React.RefObject<AutocompleteHighlightChangeReason | null>} */
|
|
155
|
+
const highlightReasonRef = React.useRef(null);
|
|
156
|
+
const touchScrolledRef = React.useRef(false);
|
|
157
|
+
const isTouchRef = React.useRef(false);
|
|
158
|
+
|
|
144
159
|
// Calculate the initial inputValue on mount only.
|
|
145
160
|
// useRef ensures it doesn't update dynamically with defaultValue or value props.
|
|
146
161
|
const initialInputValue = React.useRef(getInputValue(defaultValue ?? valueProp, multiple, getOptionLabel)).current;
|
|
@@ -157,10 +172,12 @@ function useAutocomplete(props) {
|
|
|
157
172
|
});
|
|
158
173
|
const [focused, setFocused] = React.useState(false);
|
|
159
174
|
const resetInputValue = React.useCallback((event, newValue, reason) => {
|
|
160
|
-
//
|
|
161
|
-
//
|
|
175
|
+
// Retain the current `inputValue` when no new option is selected and `clearOnBlur` is false.
|
|
176
|
+
// In `multiple` mode, `newValue` is the next value array, so only length growth counts as a selection.
|
|
162
177
|
const isOptionSelected = multiple ? value.length < newValue.length : newValue !== null;
|
|
163
|
-
|
|
178
|
+
// A controlled single-value `freeSolo` reset to `null` should still clear the input.
|
|
179
|
+
const shouldClearOnReset = reason === 'reset' && freeSolo && !multiple && newValue === null;
|
|
180
|
+
if (!isOptionSelected && !clearOnBlur && !shouldClearOnReset) {
|
|
164
181
|
return;
|
|
165
182
|
}
|
|
166
183
|
const newInputValue = getInputValue(newValue, multiple, getOptionLabel, renderValue);
|
|
@@ -171,7 +188,7 @@ function useAutocomplete(props) {
|
|
|
171
188
|
if (onInputChange) {
|
|
172
189
|
onInputChange(event, newInputValue, reason);
|
|
173
190
|
}
|
|
174
|
-
}, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, value, renderValue]);
|
|
191
|
+
}, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, freeSolo, value, renderValue]);
|
|
175
192
|
const [open, setOpenState] = (0, _useControlled.default)({
|
|
176
193
|
controlled: openProp,
|
|
177
194
|
default: false,
|
|
@@ -278,22 +295,17 @@ function useAutocomplete(props) {
|
|
|
278
295
|
}
|
|
279
296
|
}
|
|
280
297
|
}
|
|
281
|
-
const
|
|
282
|
-
event,
|
|
298
|
+
const syncHighlightedIndexToDOM = (0, _useEventCallback.default)(({
|
|
283
299
|
index,
|
|
284
|
-
reason
|
|
300
|
+
reason,
|
|
301
|
+
preserveScroll = false
|
|
285
302
|
}) => {
|
|
286
|
-
highlightedIndexRef.current = index;
|
|
287
|
-
|
|
288
303
|
// does the index exist?
|
|
289
304
|
if (index === -1) {
|
|
290
305
|
inputRef.current.removeAttribute('aria-activedescendant');
|
|
291
306
|
} else {
|
|
292
307
|
inputRef.current.setAttribute('aria-activedescendant', `${id}-option-${index}`);
|
|
293
308
|
}
|
|
294
|
-
if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
|
|
295
|
-
onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
|
|
296
|
-
}
|
|
297
309
|
if (!listboxRef.current) {
|
|
298
310
|
return;
|
|
299
311
|
}
|
|
@@ -312,7 +324,9 @@ function useAutocomplete(props) {
|
|
|
312
324
|
return;
|
|
313
325
|
}
|
|
314
326
|
if (index === -1) {
|
|
315
|
-
|
|
327
|
+
if (!preserveScroll) {
|
|
328
|
+
listboxNode.scrollTop = 0;
|
|
329
|
+
}
|
|
316
330
|
return;
|
|
317
331
|
}
|
|
318
332
|
const option = listboxRef.current.querySelector(`[data-option-index="${index}"]`);
|
|
@@ -340,15 +354,46 @@ function useAutocomplete(props) {
|
|
|
340
354
|
}
|
|
341
355
|
}
|
|
342
356
|
});
|
|
357
|
+
const setHighlightedIndex = (0, _useEventCallback.default)(({
|
|
358
|
+
event,
|
|
359
|
+
index,
|
|
360
|
+
reason,
|
|
361
|
+
preserveScroll = false
|
|
362
|
+
}) => {
|
|
363
|
+
highlightedIndexRef.current = index;
|
|
364
|
+
highlightReasonRef.current = reason ?? null;
|
|
365
|
+
if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
|
|
366
|
+
onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
|
|
367
|
+
}
|
|
368
|
+
syncHighlightedIndexToDOM({
|
|
369
|
+
index,
|
|
370
|
+
reason,
|
|
371
|
+
preserveScroll
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
const setHighlightedIndexFromSync = (0, _useEventCallback.default)(({
|
|
375
|
+
index
|
|
376
|
+
}) => {
|
|
377
|
+
highlightedIndexRef.current = index;
|
|
378
|
+
syncHighlightedIndexToDOM({
|
|
379
|
+
index,
|
|
380
|
+
reason: highlightReasonRef.current
|
|
381
|
+
});
|
|
382
|
+
});
|
|
343
383
|
const changeHighlightedIndex = (0, _useEventCallback.default)(({
|
|
344
384
|
event,
|
|
345
385
|
diff,
|
|
346
386
|
direction = 'next',
|
|
347
|
-
reason
|
|
387
|
+
reason,
|
|
388
|
+
preserveScroll
|
|
348
389
|
}) => {
|
|
349
390
|
if (!popupOpen) {
|
|
350
391
|
return;
|
|
351
392
|
}
|
|
393
|
+
if (reason === 'keyboard') {
|
|
394
|
+
touchScrolledRef.current = false;
|
|
395
|
+
isTouchRef.current = false;
|
|
396
|
+
}
|
|
352
397
|
const getNextIndex = () => {
|
|
353
398
|
const maxIndex = filteredOptions.length - 1;
|
|
354
399
|
if (diff === 'reset') {
|
|
@@ -385,7 +430,8 @@ function useAutocomplete(props) {
|
|
|
385
430
|
setHighlightedIndex({
|
|
386
431
|
index: nextIndex,
|
|
387
432
|
reason,
|
|
388
|
-
event
|
|
433
|
+
event,
|
|
434
|
+
preserveScroll
|
|
389
435
|
});
|
|
390
436
|
|
|
391
437
|
// Sync the content of the input with the highlighted option.
|
|
@@ -439,15 +485,24 @@ function useAutocomplete(props) {
|
|
|
439
485
|
// If it exists and the value and the inputValue haven't changed, just update its index, otherwise continue execution
|
|
440
486
|
const previousHighlightedOptionIndex = getPreviousHighlightedOptionIndex();
|
|
441
487
|
if (previousHighlightedOptionIndex !== -1) {
|
|
442
|
-
|
|
488
|
+
// Keep the original highlight reason while re-syncing the DOM state.
|
|
489
|
+
// The highlighted option still exists after the filteredOptions array changed
|
|
490
|
+
// (e.g. async fetch returns new options while the user is mid-navigation),
|
|
491
|
+
// so the original interaction reason (keyboard, mouse, etc.) still applies.
|
|
492
|
+
setHighlightedIndexFromSync({
|
|
493
|
+
index: previousHighlightedOptionIndex
|
|
494
|
+
});
|
|
443
495
|
return;
|
|
444
496
|
}
|
|
445
497
|
const valueItem = multiple ? value[0] : value;
|
|
446
498
|
|
|
447
499
|
// The popup is empty, reset
|
|
448
500
|
if (filteredOptions.length === 0 || valueItem == null) {
|
|
501
|
+
// Preserve scroll when new options are appended without changing the current filter.
|
|
502
|
+
const isAppendOnly = filteredOptionsChanged && previousProps.inputValue === inputValue && previousProps.filteredOptions?.length > 0 && filteredOptions.length > previousProps.filteredOptions.length && previousProps.filteredOptions.every((option, index) => getOptionLabel(option) === getOptionLabel(filteredOptions[index]));
|
|
449
503
|
changeHighlightedIndex({
|
|
450
|
-
diff: 'reset'
|
|
504
|
+
diff: 'reset',
|
|
505
|
+
preserveScroll: isAppendOnly
|
|
451
506
|
});
|
|
452
507
|
return;
|
|
453
508
|
}
|
|
@@ -459,8 +514,12 @@ function useAutocomplete(props) {
|
|
|
459
514
|
if (valueItem != null) {
|
|
460
515
|
const currentOption = filteredOptions[highlightedIndexRef.current];
|
|
461
516
|
|
|
462
|
-
// Keep the current
|
|
463
|
-
|
|
517
|
+
// Keep the current selected highlight while the popup stays open;
|
|
518
|
+
// on reopen, resync from the selected value.
|
|
519
|
+
if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1 && previousProps.filteredOptions?.length > 0) {
|
|
520
|
+
setHighlightedIndexFromSync({
|
|
521
|
+
index: highlightedIndexRef.current
|
|
522
|
+
});
|
|
464
523
|
return;
|
|
465
524
|
}
|
|
466
525
|
const itemIndex = filteredOptions.findIndex(optionItem => isOptionEqualToValue(optionItem, valueItem));
|
|
@@ -495,7 +554,7 @@ function useAutocomplete(props) {
|
|
|
495
554
|
filteredOptions.length,
|
|
496
555
|
// Don't sync the highlighted index with the value when multiple
|
|
497
556
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
498
|
-
multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, popupOpen, inputValue, multiple]);
|
|
557
|
+
multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, setHighlightedIndexFromSync, popupOpen, inputValue, multiple]);
|
|
499
558
|
const handleListboxRef = (0, _useEventCallback.default)(node => {
|
|
500
559
|
(0, _setRef.default)(listboxRef, node);
|
|
501
560
|
if (!node) {
|
|
@@ -541,6 +600,7 @@ function useAutocomplete(props) {
|
|
|
541
600
|
}
|
|
542
601
|
setOpenState(true);
|
|
543
602
|
setInputPristine(true);
|
|
603
|
+
isTouchRef.current = false;
|
|
544
604
|
if (onOpen) {
|
|
545
605
|
onOpen(event);
|
|
546
606
|
}
|
|
@@ -550,6 +610,8 @@ function useAutocomplete(props) {
|
|
|
550
610
|
return;
|
|
551
611
|
}
|
|
552
612
|
setOpenState(false);
|
|
613
|
+
touchScrolledRef.current = false;
|
|
614
|
+
highlightReasonRef.current = null;
|
|
553
615
|
if (onClose) {
|
|
554
616
|
onClose(event, reason);
|
|
555
617
|
}
|
|
@@ -567,7 +629,6 @@ function useAutocomplete(props) {
|
|
|
567
629
|
}
|
|
568
630
|
setValueState(newValue);
|
|
569
631
|
};
|
|
570
|
-
const isTouch = React.useRef(false);
|
|
571
632
|
const selectNewValue = (event, option, reasonProp = 'selectOption', origin = 'options') => {
|
|
572
633
|
let reason = reasonProp;
|
|
573
634
|
let newValue = option;
|
|
@@ -594,7 +655,7 @@ function useAutocomplete(props) {
|
|
|
594
655
|
if (!disableCloseOnSelect && (!event || !event.ctrlKey && !event.metaKey)) {
|
|
595
656
|
handleClose(event, reason);
|
|
596
657
|
}
|
|
597
|
-
if (blurOnSelect === true || blurOnSelect === 'touch' &&
|
|
658
|
+
if (blurOnSelect === true || blurOnSelect === 'touch' && isTouchRef.current || blurOnSelect === 'mouse' && !isTouchRef.current) {
|
|
598
659
|
inputRef.current.blur();
|
|
599
660
|
}
|
|
600
661
|
};
|
|
@@ -652,7 +713,6 @@ function useAutocomplete(props) {
|
|
|
652
713
|
focusItem(nextItem);
|
|
653
714
|
};
|
|
654
715
|
const handleClear = event => {
|
|
655
|
-
ignoreFocus.current = true;
|
|
656
716
|
setInputValueState('');
|
|
657
717
|
if (onInputChange) {
|
|
658
718
|
onInputChange(event, '', 'clear');
|
|
@@ -780,29 +840,47 @@ function useAutocomplete(props) {
|
|
|
780
840
|
}
|
|
781
841
|
break;
|
|
782
842
|
case 'Enter':
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
843
|
+
{
|
|
844
|
+
// In freeSolo, only select the highlighted option if the user hasn't
|
|
845
|
+
// typed new text (inputPristine) or explicitly interacted with an option
|
|
846
|
+
// (keyboard, mouse, or touch — any non-null reason). This lets typed
|
|
847
|
+
// text win over a programmatic highlight (reason=null, e.g. from
|
|
848
|
+
// syncHighlightedIndex matching a previous value) while still honoring
|
|
849
|
+
// deliberate user interactions like hovering a suggestion then pressing Enter.
|
|
850
|
+
const shouldSelectHighlighted = !freeSolo || inputPristine || highlightReasonRef.current !== null;
|
|
851
|
+
if (highlightedIndexRef.current !== -1 && popupOpen && shouldSelectHighlighted &&
|
|
852
|
+
// After a touch-scroll the highlight is stale (the user scrolled
|
|
853
|
+
// past it), so skip selection until the next deliberate interaction.
|
|
854
|
+
!touchScrolledRef.current) {
|
|
855
|
+
const option = filteredOptions[highlightedIndexRef.current];
|
|
856
|
+
const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
|
|
786
857
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
858
|
+
// Avoid early form validation, let the end-users continue filling the form.
|
|
859
|
+
event.preventDefault();
|
|
860
|
+
if (disabled) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
selectNewValue(event, option, 'selectOption');
|
|
793
864
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
865
|
+
// Move the selection to the end.
|
|
866
|
+
if (autoComplete) {
|
|
867
|
+
inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
|
|
868
|
+
}
|
|
869
|
+
} else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
|
|
870
|
+
if (multiple) {
|
|
871
|
+
// Allow people to add new values before they submit the form.
|
|
872
|
+
event.preventDefault();
|
|
873
|
+
}
|
|
874
|
+
selectNewValue(event, inputValue, 'createOption', 'freeSolo');
|
|
875
|
+
} else if (popupOpen && touchScrolledRef.current) {
|
|
876
|
+
// The highlight is stale from a touch-scroll - close without selecting.
|
|
801
877
|
event.preventDefault();
|
|
878
|
+
// This happens on Enter, but re-using "escape" as the closest `AutocompleteCloseReason`
|
|
879
|
+
// to avoid creating a new reason
|
|
880
|
+
handleClose(event, 'escape');
|
|
802
881
|
}
|
|
803
|
-
|
|
882
|
+
break;
|
|
804
883
|
}
|
|
805
|
-
break;
|
|
806
884
|
case 'Escape':
|
|
807
885
|
if (popupOpen) {
|
|
808
886
|
// Avoid Opera to exit fullscreen mode.
|
|
@@ -880,7 +958,7 @@ function useAutocomplete(props) {
|
|
|
880
958
|
}
|
|
881
959
|
};
|
|
882
960
|
const handleBlur = event => {
|
|
883
|
-
// Ignore the event when using the scrollbar with
|
|
961
|
+
// Ignore the event when using the scrollbar with IE 11
|
|
884
962
|
if (unstable_isActiveElementInListbox(listboxRef)) {
|
|
885
963
|
inputRef.current.focus();
|
|
886
964
|
return;
|
|
@@ -888,7 +966,12 @@ function useAutocomplete(props) {
|
|
|
888
966
|
setFocused(false);
|
|
889
967
|
firstFocus.current = true;
|
|
890
968
|
ignoreFocus.current = false;
|
|
891
|
-
|
|
969
|
+
|
|
970
|
+
// Auto-select the highlighted option on blur, but only if the highlight
|
|
971
|
+
// came from keyboard navigation or was set programmatically (autoHighlight).
|
|
972
|
+
// Mouse hover and touch should not trigger selection — the user may have
|
|
973
|
+
// moved the pointer over an option without intending to commit to it.
|
|
974
|
+
if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen && highlightReasonRef.current !== 'mouse' && highlightReasonRef.current !== 'touch') {
|
|
892
975
|
selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
|
|
893
976
|
} else if (autoSelect && freeSolo && inputValue !== '') {
|
|
894
977
|
selectNewValue(event, inputValue, 'blur', 'freeSolo');
|
|
@@ -899,9 +982,10 @@ function useAutocomplete(props) {
|
|
|
899
982
|
};
|
|
900
983
|
const handleInputChange = event => {
|
|
901
984
|
const newValue = event.target.value;
|
|
902
|
-
|
|
985
|
+
const valueChanged = inputValue !== newValue;
|
|
986
|
+
if (valueChanged) {
|
|
903
987
|
setInputValueState(newValue);
|
|
904
|
-
|
|
988
|
+
touchScrolledRef.current = false;
|
|
905
989
|
if (onInputChange) {
|
|
906
990
|
onInputChange(event, newValue, 'input');
|
|
907
991
|
}
|
|
@@ -915,6 +999,12 @@ function useAutocomplete(props) {
|
|
|
915
999
|
} else {
|
|
916
1000
|
handleOpen(event);
|
|
917
1001
|
}
|
|
1002
|
+
|
|
1003
|
+
// Called after handleOpen so it overrides handleOpen's setInputPristine(true)
|
|
1004
|
+
// when the first keystroke also opens the popup.
|
|
1005
|
+
if (valueChanged) {
|
|
1006
|
+
setInputPristine(false);
|
|
1007
|
+
}
|
|
918
1008
|
};
|
|
919
1009
|
const handleOptionMouseMove = event => {
|
|
920
1010
|
const index = Number(event.currentTarget.getAttribute('data-option-index'));
|
|
@@ -924,20 +1014,35 @@ function useAutocomplete(props) {
|
|
|
924
1014
|
index,
|
|
925
1015
|
reason: 'mouse'
|
|
926
1016
|
});
|
|
1017
|
+
} else {
|
|
1018
|
+
// The option is already highlighted (e.g. programmatically via autoHighlight),
|
|
1019
|
+
// but the user moved the mouse over it — mark as mouse-initiated so
|
|
1020
|
+
// autoSelect on blur correctly treats this as incidental hover.
|
|
1021
|
+
highlightReasonRef.current = 'mouse';
|
|
1022
|
+
}
|
|
1023
|
+
// Don't clear the touch-scroll guard while touch state is still latched.
|
|
1024
|
+
// After a touch gesture, browsers may fire compatibility mousemove
|
|
1025
|
+
// events; if those cleared the guard immediately, later compat events in
|
|
1026
|
+
// the same sequence could be misclassified as a real mouse interaction.
|
|
1027
|
+
// Touch state is cleared by the next deliberate interaction
|
|
1028
|
+
// (keyboard nav, handleOptionClick, or handleOpen).
|
|
1029
|
+
if (!isTouchRef.current) {
|
|
1030
|
+
touchScrolledRef.current = false;
|
|
927
1031
|
}
|
|
928
1032
|
};
|
|
929
1033
|
const handleOptionTouchStart = event => {
|
|
1034
|
+
touchScrolledRef.current = false;
|
|
930
1035
|
setHighlightedIndex({
|
|
931
1036
|
event,
|
|
932
1037
|
index: Number(event.currentTarget.getAttribute('data-option-index')),
|
|
933
1038
|
reason: 'touch'
|
|
934
1039
|
});
|
|
935
|
-
|
|
1040
|
+
isTouchRef.current = true;
|
|
936
1041
|
};
|
|
937
1042
|
const handleOptionClick = event => {
|
|
938
1043
|
const index = Number(event.currentTarget.getAttribute('data-option-index'));
|
|
939
1044
|
selectNewValue(event, filteredOptions[index], 'selectOption');
|
|
940
|
-
|
|
1045
|
+
isTouchRef.current = false;
|
|
941
1046
|
};
|
|
942
1047
|
const handleItemDelete = index => event => {
|
|
943
1048
|
const newValue = value.slice();
|
|
@@ -962,11 +1067,11 @@ function useAutocomplete(props) {
|
|
|
962
1067
|
// Prevent input blur when interacting with the combobox
|
|
963
1068
|
const handleMouseDown = event => {
|
|
964
1069
|
// Prevent focusing the input if click is anywhere outside the Autocomplete
|
|
965
|
-
if (!event.currentTarget
|
|
1070
|
+
if (!(0, _contains.default)(event.currentTarget, event.target)) {
|
|
966
1071
|
return;
|
|
967
1072
|
}
|
|
968
1073
|
// Don't interfere with interactions outside the input area (e.g. helper text)
|
|
969
|
-
if (anchorEl && !
|
|
1074
|
+
if (anchorEl && !(0, _contains.default)(anchorEl, event.target)) {
|
|
970
1075
|
return;
|
|
971
1076
|
}
|
|
972
1077
|
if (event.target.getAttribute('id') !== id) {
|
|
@@ -977,11 +1082,11 @@ function useAutocomplete(props) {
|
|
|
977
1082
|
// Focus the input when interacting with the combobox
|
|
978
1083
|
const handleClick = event => {
|
|
979
1084
|
// Prevent focusing the input if click is anywhere outside the Autocomplete
|
|
980
|
-
if (!event.currentTarget
|
|
1085
|
+
if (!(0, _contains.default)(event.currentTarget, event.target)) {
|
|
981
1086
|
return;
|
|
982
1087
|
}
|
|
983
1088
|
// Don't interfere with interactions outside the input area (e.g. helper text)
|
|
984
|
-
if (anchorEl && !
|
|
1089
|
+
if (anchorEl && !(0, _contains.default)(anchorEl, event.target)) {
|
|
985
1090
|
return;
|
|
986
1091
|
}
|
|
987
1092
|
inputRef.current.focus();
|
|
@@ -1065,7 +1170,10 @@ function useAutocomplete(props) {
|
|
|
1065
1170
|
getClearProps: () => ({
|
|
1066
1171
|
tabIndex: -1,
|
|
1067
1172
|
type: 'button',
|
|
1068
|
-
onClick:
|
|
1173
|
+
onClick: event => {
|
|
1174
|
+
ignoreFocus.current = true;
|
|
1175
|
+
handleClear(event);
|
|
1176
|
+
}
|
|
1069
1177
|
}),
|
|
1070
1178
|
getItemProps: ({
|
|
1071
1179
|
index = 0
|
|
@@ -1093,6 +1201,11 @@ function useAutocomplete(props) {
|
|
|
1093
1201
|
onMouseDown: event => {
|
|
1094
1202
|
// Prevent blur
|
|
1095
1203
|
event.preventDefault();
|
|
1204
|
+
},
|
|
1205
|
+
onScroll: () => {
|
|
1206
|
+
if (isTouchRef.current) {
|
|
1207
|
+
touchScrolledRef.current = true;
|
|
1208
|
+
}
|
|
1096
1209
|
}
|
|
1097
1210
|
}),
|
|
1098
1211
|
getOptionProps: ({
|