@mui/material 9.0.0 → 9.1.0
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/Accordion/Accordion.d.mts +2 -2
- package/Accordion/Accordion.d.ts +2 -2
- package/Accordion/Accordion.js +3 -2
- package/Accordion/Accordion.mjs +3 -2
- package/AccordionSummary/AccordionSummary.js +27 -29
- package/AccordionSummary/AccordionSummary.mjs +27 -29
- package/Autocomplete/Autocomplete.js +73 -17
- package/Autocomplete/Autocomplete.mjs +73 -17
- package/Avatar/Avatar.js +4 -0
- package/Avatar/Avatar.mjs +4 -0
- package/Backdrop/Backdrop.d.mts +2 -2
- package/Backdrop/Backdrop.d.ts +2 -2
- package/Badge/Badge.js +31 -24
- package/Badge/Badge.mjs +31 -24
- package/BottomNavigationAction/BottomNavigationAction.js +6 -2
- package/BottomNavigationAction/BottomNavigationAction.mjs +6 -2
- package/Button/Button.js +19 -6
- package/Button/Button.mjs +19 -6
- 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/ButtonBase/Ripple.js +21 -11
- package/ButtonBase/Ripple.mjs +21 -11
- package/ButtonBase/TouchRipple.js +252 -116
- package/ButtonBase/TouchRipple.mjs +253 -117
- package/CHANGELOG.md +216 -1245
- package/CardActionArea/CardActionArea.js +2 -1
- package/CardActionArea/CardActionArea.mjs +2 -1
- package/Checkbox/Checkbox.js +2 -1
- package/Checkbox/Checkbox.mjs +2 -1
- package/Chip/Chip.js +2 -1
- package/Chip/Chip.mjs +2 -1
- package/CircularProgress/CircularProgress.d.mts +12 -2
- package/CircularProgress/CircularProgress.d.ts +12 -2
- package/CircularProgress/CircularProgress.js +115 -58
- package/CircularProgress/CircularProgress.mjs +114 -58
- package/ClickAwayListener/ClickAwayListener.js +3 -6
- package/ClickAwayListener/ClickAwayListener.mjs +3 -6
- package/Collapse/Collapse.d.mts +15 -3
- package/Collapse/Collapse.d.ts +15 -3
- package/Collapse/Collapse.js +44 -31
- package/Collapse/Collapse.mjs +43 -30
- package/Dialog/Dialog.d.mts +2 -2
- package/Dialog/Dialog.d.ts +2 -2
- package/Dialog/Dialog.js +13 -6
- package/Dialog/Dialog.mjs +13 -6
- package/Drawer/Drawer.d.mts +2 -2
- package/Drawer/Drawer.d.ts +2 -2
- package/Drawer/Drawer.js +18 -4
- package/Drawer/Drawer.mjs +18 -4
- package/Fab/Fab.js +9 -2
- package/Fab/Fab.mjs +9 -2
- package/Fade/Fade.d.mts +15 -2
- package/Fade/Fade.d.ts +15 -2
- package/Fade/Fade.js +46 -19
- package/Fade/Fade.mjs +45 -18
- package/FilledInput/FilledInput.d.mts +4 -0
- package/FilledInput/FilledInput.d.ts +4 -0
- package/FilledInput/FilledInput.js +22 -23
- package/FilledInput/FilledInput.mjs +22 -23
- 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/Grow/Grow.d.mts +15 -2
- package/Grow/Grow.d.ts +15 -2
- package/Grow/Grow.js +45 -28
- package/Grow/Grow.mjs +44 -27
- package/IconButton/IconButton.js +3 -9
- package/IconButton/IconButton.mjs +3 -9
- package/Input/Input.d.mts +4 -0
- package/Input/Input.d.ts +4 -0
- package/Input/Input.js +9 -2
- package/Input/Input.mjs +9 -2
- package/InputBase/InputBase.d.mts +2 -1
- package/InputBase/InputBase.d.ts +2 -1
- package/InputBase/InputBase.js +52 -16
- package/InputBase/InputBase.mjs +52 -16
- package/InputLabel/InputLabel.js +7 -9
- package/InputLabel/InputLabel.mjs +7 -9
- package/LICENSE +1 -1
- package/LinearProgress/LinearProgress.d.mts +12 -2
- package/LinearProgress/LinearProgress.d.ts +12 -2
- package/LinearProgress/LinearProgress.js +225 -126
- package/LinearProgress/LinearProgress.mjs +224 -126
- package/List/List.js +2 -1
- package/List/List.mjs +2 -1
- package/ListItem/ListItem.js +2 -1
- package/ListItem/ListItem.mjs +2 -1
- package/ListItemButton/ListItemButton.js +9 -2
- package/ListItemButton/ListItemButton.mjs +9 -2
- package/Menu/Menu.d.mts +1 -1
- package/Menu/Menu.d.ts +1 -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/MobileStepper/MobileStepper.js +2 -1
- package/MobileStepper/MobileStepper.mjs +2 -1
- package/NativeSelect/NativeSelect.js +2 -5
- package/NativeSelect/NativeSelect.mjs +2 -5
- package/OutlinedInput/NotchedOutline.js +4 -3
- package/OutlinedInput/NotchedOutline.mjs +4 -3
- package/OutlinedInput/OutlinedInput.js +13 -23
- package/OutlinedInput/OutlinedInput.mjs +13 -23
- package/PaginationItem/PaginationItem.js +2 -1
- package/PaginationItem/PaginationItem.mjs +2 -1
- package/Paper/Paper.js +2 -1
- package/Paper/Paper.mjs +2 -1
- package/PigmentContainer/PigmentContainer.js +0 -1
- package/PigmentContainer/PigmentContainer.mjs +0 -1
- package/Popover/Popover.d.mts +1 -1
- package/Popover/Popover.d.ts +1 -1
- package/Popper/BasePopper.js +23 -1
- package/Popper/BasePopper.mjs +23 -1
- package/README.md +3 -2
- package/Radio/RadioButtonIcon.js +3 -2
- package/Radio/RadioButtonIcon.mjs +3 -2
- package/Rating/Rating.js +2 -1
- package/Rating/Rating.mjs +2 -1
- package/Select/Select.js +2 -5
- package/Select/Select.mjs +2 -5
- package/Select/SelectInput.js +276 -24
- package/Select/SelectInput.mjs +276 -24
- package/Select/utils/closedTypeahead.js +73 -0
- package/Select/utils/closedTypeahead.mjs +63 -0
- package/Skeleton/Skeleton.js +22 -2
- package/Skeleton/Skeleton.mjs +22 -2
- package/Slide/Slide.d.mts +15 -2
- package/Slide/Slide.d.ts +15 -2
- package/Slide/Slide.js +97 -47
- package/Slide/Slide.mjs +97 -47
- package/Slider/Slider.js +14 -4
- package/Slider/Slider.mjs +14 -4
- package/Slider/useSlider.js +4 -3
- package/Slider/useSlider.mjs +4 -3
- package/Snackbar/Snackbar.d.mts +2 -2
- package/Snackbar/Snackbar.d.ts +2 -2
- package/SpeedDial/SpeedDial.d.mts +1 -1
- package/SpeedDial/SpeedDial.d.ts +1 -1
- package/SpeedDial/SpeedDial.js +6 -2
- package/SpeedDial/SpeedDial.mjs +6 -2
- package/SpeedDialAction/SpeedDialAction.js +11 -2
- package/SpeedDialAction/SpeedDialAction.mjs +12 -3
- package/SpeedDialIcon/SpeedDialIcon.js +40 -37
- package/SpeedDialIcon/SpeedDialIcon.mjs +40 -37
- package/Step/Step.js +47 -15
- package/Step/Step.mjs +47 -15
- package/StepButton/StepButton.js +9 -3
- package/StepButton/StepButton.mjs +9 -3
- package/StepConnector/StepConnector.js +10 -0
- package/StepConnector/StepConnector.mjs +10 -0
- package/StepContent/StepContent.d.mts +2 -2
- package/StepContent/StepContent.d.ts +2 -2
- package/StepContent/StepContent.js +26 -2
- package/StepContent/StepContent.mjs +26 -2
- package/StepIcon/StepIcon.js +2 -1
- package/StepIcon/StepIcon.mjs +2 -1
- package/StepLabel/StepLabel.js +52 -7
- package/StepLabel/StepLabel.mjs +52 -7
- package/Stepper/Stepper.d.mts +2 -0
- package/Stepper/Stepper.d.ts +2 -0
- package/Stepper/Stepper.js +18 -0
- package/Stepper/Stepper.mjs +18 -0
- package/SvgIcon/SvgIcon.js +2 -1
- package/SvgIcon/SvgIcon.mjs +2 -1
- package/SwipeableDrawer/SwipeableDrawer.js +21 -6
- package/SwipeableDrawer/SwipeableDrawer.mjs +21 -6
- package/Switch/Switch.js +10 -8
- package/Switch/Switch.mjs +10 -8
- package/TableSortLabel/TableSortLabel.js +2 -1
- package/TableSortLabel/TableSortLabel.mjs +2 -1
- package/Tabs/ScrollbarSize.js +2 -1
- package/Tabs/ScrollbarSize.mjs +2 -1
- package/Tabs/Tabs.js +16 -4
- package/Tabs/Tabs.mjs +16 -4
- package/Tooltip/Tooltip.d.mts +2 -2
- package/Tooltip/Tooltip.d.ts +2 -2
- package/Tooltip/Tooltip.js +29 -108
- package/Tooltip/Tooltip.mjs +29 -108
- package/Unstable_TrapFocus/FocusTrap.js +60 -22
- package/Unstable_TrapFocus/FocusTrap.mjs +60 -22
- package/Zoom/Zoom.d.mts +15 -2
- package/Zoom/Zoom.d.ts +15 -2
- package/Zoom/Zoom.js +43 -16
- package/Zoom/Zoom.mjs +42 -15
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/internal/Transition.d.mts +34 -0
- package/internal/Transition.d.ts +34 -0
- package/internal/Transition.js +444 -0
- package/internal/Transition.mjs +436 -0
- package/internal/react-transition-group.d.mts +8 -0
- package/internal/react-transition-group.d.ts +8 -0
- package/package.json +50 -50
- package/styles/createMotion.d.mts +8 -0
- package/styles/createMotion.d.ts +8 -0
- package/styles/createMotion.js +13 -0
- package/styles/createMotion.mjs +7 -0
- package/styles/createThemeFoundation.d.mts +2 -0
- package/styles/createThemeFoundation.d.ts +2 -0
- package/styles/createThemeNoVars.d.mts +3 -0
- package/styles/createThemeNoVars.d.ts +3 -0
- package/styles/createThemeNoVars.js +5 -0
- package/styles/createThemeNoVars.mjs +5 -0
- package/styles/createTransitions.d.mts +6 -2
- package/styles/createTransitions.d.ts +6 -2
- package/styles/createTransitions.js +12 -4
- package/styles/createTransitions.mjs +12 -4
- package/styles/enhanceHighContrast.d.mts +70 -0
- package/styles/enhanceHighContrast.d.ts +70 -0
- package/styles/enhanceHighContrast.js +502 -0
- package/styles/enhanceHighContrast.mjs +495 -0
- package/styles/index.d.mts +2 -0
- package/styles/index.d.ts +2 -0
- package/styles/index.js +8 -0
- package/styles/index.mjs +1 -0
- package/styles/reducedMotion.d.mts +7 -0
- package/styles/reducedMotion.d.ts +7 -0
- package/styles/reducedMotion.js +21 -0
- package/styles/reducedMotion.mjs +14 -0
- package/styles/responsiveFontSizes.js +19 -8
- package/styles/responsiveFontSizes.mjs +19 -8
- package/styles/shouldSkipGeneratingVar.js +1 -1
- package/styles/shouldSkipGeneratingVar.mjs +1 -1
- package/styles/stringifyTheme.js +1 -0
- package/styles/stringifyTheme.mjs +1 -0
- package/styles/useThemeProps.d.mts +3 -3
- package/styles/useThemeProps.d.ts +3 -3
- package/transitions/index.d.mts +1 -1
- package/transitions/index.d.ts +1 -1
- package/transitions/index.js +0 -11
- package/transitions/index.mjs +1 -1
- package/transitions/transition.d.mts +1 -12
- package/transitions/transition.d.ts +1 -12
- package/transitions/types.d.mts +73 -0
- package/transitions/types.d.ts +73 -0
- package/transitions/useReducedMotion.d.mts +14 -0
- package/transitions/useReducedMotion.d.ts +14 -0
- package/transitions/useReducedMotion.js +117 -0
- package/transitions/useReducedMotion.mjs +110 -0
- package/transitions/utils.d.mts +51 -2
- package/transitions/utils.d.ts +51 -2
- package/transitions/utils.js +97 -4
- package/transitions/utils.mjs +94 -4
- package/useAutocomplete/useAutocomplete.d.mts +12 -6
- package/useAutocomplete/useAutocomplete.d.ts +12 -6
- package/useAutocomplete/useAutocomplete.js +230 -55
- package/useAutocomplete/useAutocomplete.mjs +230 -55
- 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
- /package/transitions/{transition.js → types.js} +0 -0
- /package/transitions/{transition.mjs → types.mjs} +0 -0
|
@@ -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) {
|
|
@@ -115,6 +116,7 @@ function useAutocomplete(props) {
|
|
|
115
116
|
options,
|
|
116
117
|
readOnly = false,
|
|
117
118
|
renderValue,
|
|
119
|
+
resetHighlightOnMouseLeave = false,
|
|
118
120
|
selectOnFocus = !props.freeSolo,
|
|
119
121
|
value: valueProp
|
|
120
122
|
} = props;
|
|
@@ -135,12 +137,29 @@ function useAutocomplete(props) {
|
|
|
135
137
|
const firstFocus = React.useRef(true);
|
|
136
138
|
const inputRef = React.useRef(null);
|
|
137
139
|
const listboxRef = React.useRef(null);
|
|
140
|
+
// VoiceOver synthesises a spurious Backspace on the input after a chip
|
|
141
|
+
// deletion moves DOM focus back to it. This flag suppresses that one event.
|
|
142
|
+
const ignoreNextBackspaceRef = React.useRef(false);
|
|
138
143
|
const windowLostFocus = React.useRef(false);
|
|
139
144
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
140
145
|
const [focusedItem, setFocusedItem] = React.useState(-1);
|
|
141
146
|
const defaultHighlighted = autoHighlight ? 0 : -1;
|
|
142
147
|
const highlightedIndexRef = React.useRef(defaultHighlighted);
|
|
143
148
|
|
|
149
|
+
// Tracks how the current highlight was set:
|
|
150
|
+
// - 'keyboard' — arrow keys, Home/End, PageUp/PageDown
|
|
151
|
+
// - 'mouse' — handleOptionMouseMove
|
|
152
|
+
// - 'touch' — handleOptionTouchStart
|
|
153
|
+
// - null — programmatic (autoHighlight, value sync)
|
|
154
|
+
//
|
|
155
|
+
// This lets handleBlur and the Enter handler distinguish intentional
|
|
156
|
+
// interactions from incidental ones — e.g. autoSelect should not commit
|
|
157
|
+
// a highlight that came from a casual mouse hover.
|
|
158
|
+
/** @type {React.RefObject<AutocompleteHighlightChangeReason | null>} */
|
|
159
|
+
const highlightReasonRef = React.useRef(null);
|
|
160
|
+
const touchScrolledRef = React.useRef(false);
|
|
161
|
+
const isTouchRef = React.useRef(false);
|
|
162
|
+
|
|
144
163
|
// Calculate the initial inputValue on mount only.
|
|
145
164
|
// useRef ensures it doesn't update dynamically with defaultValue or value props.
|
|
146
165
|
const initialInputValue = React.useRef(getInputValue(defaultValue ?? valueProp, multiple, getOptionLabel)).current;
|
|
@@ -157,10 +176,12 @@ function useAutocomplete(props) {
|
|
|
157
176
|
});
|
|
158
177
|
const [focused, setFocused] = React.useState(false);
|
|
159
178
|
const resetInputValue = React.useCallback((event, newValue, reason) => {
|
|
160
|
-
//
|
|
161
|
-
//
|
|
179
|
+
// Retain the current `inputValue` when no new option is selected and `clearOnBlur` is false.
|
|
180
|
+
// In `multiple` mode, `newValue` is the next value array, so only length growth counts as a selection.
|
|
162
181
|
const isOptionSelected = multiple ? value.length < newValue.length : newValue !== null;
|
|
163
|
-
|
|
182
|
+
// A controlled single-value `freeSolo` reset to `null` should still clear the input.
|
|
183
|
+
const shouldClearOnReset = reason === 'reset' && freeSolo && !multiple && newValue === null;
|
|
184
|
+
if (!isOptionSelected && !clearOnBlur && !shouldClearOnReset) {
|
|
164
185
|
return;
|
|
165
186
|
}
|
|
166
187
|
const newInputValue = getInputValue(newValue, multiple, getOptionLabel, renderValue);
|
|
@@ -171,7 +192,7 @@ function useAutocomplete(props) {
|
|
|
171
192
|
if (onInputChange) {
|
|
172
193
|
onInputChange(event, newInputValue, reason);
|
|
173
194
|
}
|
|
174
|
-
}, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, value, renderValue]);
|
|
195
|
+
}, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, freeSolo, value, renderValue]);
|
|
175
196
|
const [open, setOpenState] = (0, _useControlled.default)({
|
|
176
197
|
controlled: openProp,
|
|
177
198
|
default: false,
|
|
@@ -226,8 +247,10 @@ function useAutocomplete(props) {
|
|
|
226
247
|
return;
|
|
227
248
|
}
|
|
228
249
|
|
|
229
|
-
//
|
|
230
|
-
|
|
250
|
+
// In freeSolo mode, only reset the input after a real value change.
|
|
251
|
+
// Also prevent the initial default value of `null` from clearing controlled values.
|
|
252
|
+
const shouldSkipFreeSoloReset = freeSolo && (!valueChange || value == null && previousProps.value === undefined);
|
|
253
|
+
if (shouldSkipFreeSoloReset) {
|
|
231
254
|
return;
|
|
232
255
|
}
|
|
233
256
|
resetInputValue(null, value, 'reset');
|
|
@@ -278,12 +301,16 @@ function useAutocomplete(props) {
|
|
|
278
301
|
}
|
|
279
302
|
}
|
|
280
303
|
}
|
|
281
|
-
const
|
|
282
|
-
event,
|
|
304
|
+
const syncHighlightedIndexToDOM = (0, _useEventCallback.default)(({
|
|
283
305
|
index,
|
|
284
|
-
reason
|
|
306
|
+
reason,
|
|
307
|
+
preserveScroll = false
|
|
285
308
|
}) => {
|
|
286
|
-
|
|
309
|
+
// React can clear refs before pending passive effects run during unmount.
|
|
310
|
+
// If both refs are gone, there is no DOM left to sync.
|
|
311
|
+
if (inputRef.current == null && listboxRef.current == null) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
287
314
|
|
|
288
315
|
// does the index exist?
|
|
289
316
|
if (index === -1) {
|
|
@@ -291,9 +318,6 @@ function useAutocomplete(props) {
|
|
|
291
318
|
} else {
|
|
292
319
|
inputRef.current.setAttribute('aria-activedescendant', `${id}-option-${index}`);
|
|
293
320
|
}
|
|
294
|
-
if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
|
|
295
|
-
onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
|
|
296
|
-
}
|
|
297
321
|
if (!listboxRef.current) {
|
|
298
322
|
return;
|
|
299
323
|
}
|
|
@@ -312,7 +336,9 @@ function useAutocomplete(props) {
|
|
|
312
336
|
return;
|
|
313
337
|
}
|
|
314
338
|
if (index === -1) {
|
|
315
|
-
|
|
339
|
+
if (!preserveScroll) {
|
|
340
|
+
listboxNode.scrollTop = 0;
|
|
341
|
+
}
|
|
316
342
|
return;
|
|
317
343
|
}
|
|
318
344
|
const option = listboxRef.current.querySelector(`[data-option-index="${index}"]`);
|
|
@@ -340,15 +366,46 @@ function useAutocomplete(props) {
|
|
|
340
366
|
}
|
|
341
367
|
}
|
|
342
368
|
});
|
|
369
|
+
const setHighlightedIndex = (0, _useEventCallback.default)(({
|
|
370
|
+
event,
|
|
371
|
+
index,
|
|
372
|
+
reason,
|
|
373
|
+
preserveScroll = false
|
|
374
|
+
}) => {
|
|
375
|
+
highlightedIndexRef.current = index;
|
|
376
|
+
highlightReasonRef.current = reason ?? null;
|
|
377
|
+
if (onHighlightChange && ['mouse', 'keyboard', 'touch'].includes(reason)) {
|
|
378
|
+
onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
|
|
379
|
+
}
|
|
380
|
+
syncHighlightedIndexToDOM({
|
|
381
|
+
index,
|
|
382
|
+
reason,
|
|
383
|
+
preserveScroll
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
const setHighlightedIndexFromSync = (0, _useEventCallback.default)(({
|
|
387
|
+
index
|
|
388
|
+
}) => {
|
|
389
|
+
highlightedIndexRef.current = index;
|
|
390
|
+
syncHighlightedIndexToDOM({
|
|
391
|
+
index,
|
|
392
|
+
reason: highlightReasonRef.current
|
|
393
|
+
});
|
|
394
|
+
});
|
|
343
395
|
const changeHighlightedIndex = (0, _useEventCallback.default)(({
|
|
344
396
|
event,
|
|
345
397
|
diff,
|
|
346
398
|
direction = 'next',
|
|
347
|
-
reason
|
|
399
|
+
reason,
|
|
400
|
+
preserveScroll
|
|
348
401
|
}) => {
|
|
349
402
|
if (!popupOpen) {
|
|
350
403
|
return;
|
|
351
404
|
}
|
|
405
|
+
if (reason === 'keyboard') {
|
|
406
|
+
touchScrolledRef.current = false;
|
|
407
|
+
isTouchRef.current = false;
|
|
408
|
+
}
|
|
352
409
|
const getNextIndex = () => {
|
|
353
410
|
const maxIndex = filteredOptions.length - 1;
|
|
354
411
|
if (diff === 'reset') {
|
|
@@ -385,7 +442,8 @@ function useAutocomplete(props) {
|
|
|
385
442
|
setHighlightedIndex({
|
|
386
443
|
index: nextIndex,
|
|
387
444
|
reason,
|
|
388
|
-
event
|
|
445
|
+
event,
|
|
446
|
+
preserveScroll
|
|
389
447
|
});
|
|
390
448
|
|
|
391
449
|
// Sync the content of the input with the highlighted option.
|
|
@@ -439,15 +497,24 @@ function useAutocomplete(props) {
|
|
|
439
497
|
// If it exists and the value and the inputValue haven't changed, just update its index, otherwise continue execution
|
|
440
498
|
const previousHighlightedOptionIndex = getPreviousHighlightedOptionIndex();
|
|
441
499
|
if (previousHighlightedOptionIndex !== -1) {
|
|
442
|
-
|
|
500
|
+
// Keep the original highlight reason while re-syncing the DOM state.
|
|
501
|
+
// The highlighted option still exists after the filteredOptions array changed
|
|
502
|
+
// (e.g. async fetch returns new options while the user is mid-navigation),
|
|
503
|
+
// so the original interaction reason (keyboard, mouse, etc.) still applies.
|
|
504
|
+
setHighlightedIndexFromSync({
|
|
505
|
+
index: previousHighlightedOptionIndex
|
|
506
|
+
});
|
|
443
507
|
return;
|
|
444
508
|
}
|
|
445
509
|
const valueItem = multiple ? value[0] : value;
|
|
446
510
|
|
|
447
511
|
// The popup is empty, reset
|
|
448
512
|
if (filteredOptions.length === 0 || valueItem == null) {
|
|
513
|
+
// Preserve scroll when new options are appended without changing the current filter.
|
|
514
|
+
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
515
|
changeHighlightedIndex({
|
|
450
|
-
diff: 'reset'
|
|
516
|
+
diff: 'reset',
|
|
517
|
+
preserveScroll: isAppendOnly
|
|
451
518
|
});
|
|
452
519
|
return;
|
|
453
520
|
}
|
|
@@ -459,8 +526,12 @@ function useAutocomplete(props) {
|
|
|
459
526
|
if (valueItem != null) {
|
|
460
527
|
const currentOption = filteredOptions[highlightedIndexRef.current];
|
|
461
528
|
|
|
462
|
-
// Keep the current
|
|
463
|
-
|
|
529
|
+
// Keep the current selected highlight while the popup stays open;
|
|
530
|
+
// on reopen, resync from the selected value.
|
|
531
|
+
if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1 && previousProps.filteredOptions?.length > 0) {
|
|
532
|
+
setHighlightedIndexFromSync({
|
|
533
|
+
index: highlightedIndexRef.current
|
|
534
|
+
});
|
|
464
535
|
return;
|
|
465
536
|
}
|
|
466
537
|
const itemIndex = filteredOptions.findIndex(optionItem => isOptionEqualToValue(optionItem, valueItem));
|
|
@@ -495,7 +566,7 @@ function useAutocomplete(props) {
|
|
|
495
566
|
filteredOptions.length,
|
|
496
567
|
// Don't sync the highlighted index with the value when multiple
|
|
497
568
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
498
|
-
multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, popupOpen, inputValue, multiple]);
|
|
569
|
+
multiple ? false : value, changeHighlightedIndex, setHighlightedIndex, setHighlightedIndexFromSync, popupOpen, inputValue, multiple]);
|
|
499
570
|
const handleListboxRef = (0, _useEventCallback.default)(node => {
|
|
500
571
|
(0, _setRef.default)(listboxRef, node);
|
|
501
572
|
if (!node) {
|
|
@@ -541,6 +612,7 @@ function useAutocomplete(props) {
|
|
|
541
612
|
}
|
|
542
613
|
setOpenState(true);
|
|
543
614
|
setInputPristine(true);
|
|
615
|
+
isTouchRef.current = false;
|
|
544
616
|
if (onOpen) {
|
|
545
617
|
onOpen(event);
|
|
546
618
|
}
|
|
@@ -550,6 +622,8 @@ function useAutocomplete(props) {
|
|
|
550
622
|
return;
|
|
551
623
|
}
|
|
552
624
|
setOpenState(false);
|
|
625
|
+
touchScrolledRef.current = false;
|
|
626
|
+
highlightReasonRef.current = null;
|
|
553
627
|
if (onClose) {
|
|
554
628
|
onClose(event, reason);
|
|
555
629
|
}
|
|
@@ -567,7 +641,6 @@ function useAutocomplete(props) {
|
|
|
567
641
|
}
|
|
568
642
|
setValueState(newValue);
|
|
569
643
|
};
|
|
570
|
-
const isTouch = React.useRef(false);
|
|
571
644
|
const selectNewValue = (event, option, reasonProp = 'selectOption', origin = 'options') => {
|
|
572
645
|
let reason = reasonProp;
|
|
573
646
|
let newValue = option;
|
|
@@ -594,7 +667,7 @@ function useAutocomplete(props) {
|
|
|
594
667
|
if (!disableCloseOnSelect && (!event || !event.ctrlKey && !event.metaKey)) {
|
|
595
668
|
handleClose(event, reason);
|
|
596
669
|
}
|
|
597
|
-
if (blurOnSelect === true || blurOnSelect === 'touch' &&
|
|
670
|
+
if (blurOnSelect === true || blurOnSelect === 'touch' && isTouchRef.current || blurOnSelect === 'mouse' && !isTouchRef.current) {
|
|
598
671
|
inputRef.current.blur();
|
|
599
672
|
}
|
|
600
673
|
};
|
|
@@ -652,7 +725,6 @@ function useAutocomplete(props) {
|
|
|
652
725
|
focusItem(nextItem);
|
|
653
726
|
};
|
|
654
727
|
const handleClear = event => {
|
|
655
|
-
ignoreFocus.current = true;
|
|
656
728
|
setInputValueState('');
|
|
657
729
|
if (onInputChange) {
|
|
658
730
|
onInputChange(event, '', 'clear');
|
|
@@ -780,29 +852,47 @@ function useAutocomplete(props) {
|
|
|
780
852
|
}
|
|
781
853
|
break;
|
|
782
854
|
case 'Enter':
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
855
|
+
{
|
|
856
|
+
// In freeSolo, only select the highlighted option if the user hasn't
|
|
857
|
+
// typed new text (inputPristine) or explicitly interacted with an option
|
|
858
|
+
// (keyboard, mouse, or touch — any non-null reason). This lets typed
|
|
859
|
+
// text win over a programmatic highlight (reason=null, e.g. from
|
|
860
|
+
// syncHighlightedIndex matching a previous value) while still honoring
|
|
861
|
+
// deliberate user interactions like hovering a suggestion then pressing Enter.
|
|
862
|
+
const shouldSelectHighlighted = !freeSolo || inputPristine || highlightReasonRef.current !== null;
|
|
863
|
+
if (highlightedIndexRef.current !== -1 && popupOpen && shouldSelectHighlighted &&
|
|
864
|
+
// After a touch-scroll the highlight is stale (the user scrolled
|
|
865
|
+
// past it), so skip selection until the next deliberate interaction.
|
|
866
|
+
!touchScrolledRef.current) {
|
|
867
|
+
const option = filteredOptions[highlightedIndexRef.current];
|
|
868
|
+
const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
|
|
786
869
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
870
|
+
// Avoid early form validation, let the end-users continue filling the form.
|
|
871
|
+
event.preventDefault();
|
|
872
|
+
if (disabled) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
selectNewValue(event, option, 'selectOption');
|
|
793
876
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
877
|
+
// Move the selection to the end.
|
|
878
|
+
if (autoComplete) {
|
|
879
|
+
inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
|
|
880
|
+
}
|
|
881
|
+
} else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
|
|
882
|
+
if (multiple) {
|
|
883
|
+
// Allow people to add new values before they submit the form.
|
|
884
|
+
event.preventDefault();
|
|
885
|
+
}
|
|
886
|
+
selectNewValue(event, inputValue, 'createOption', 'freeSolo');
|
|
887
|
+
} else if (popupOpen && touchScrolledRef.current) {
|
|
888
|
+
// The highlight is stale from a touch-scroll - close without selecting.
|
|
801
889
|
event.preventDefault();
|
|
890
|
+
// This happens on Enter, but re-using "escape" as the closest `AutocompleteCloseReason`
|
|
891
|
+
// to avoid creating a new reason
|
|
892
|
+
handleClose(event, 'escape');
|
|
802
893
|
}
|
|
803
|
-
|
|
894
|
+
break;
|
|
804
895
|
}
|
|
805
|
-
break;
|
|
806
896
|
case 'Escape':
|
|
807
897
|
if (popupOpen) {
|
|
808
898
|
// Avoid Opera to exit fullscreen mode.
|
|
@@ -820,6 +910,10 @@ function useAutocomplete(props) {
|
|
|
820
910
|
break;
|
|
821
911
|
case 'Backspace':
|
|
822
912
|
// Remove the value on the left of the "cursor"
|
|
913
|
+
if (ignoreNextBackspaceRef.current) {
|
|
914
|
+
ignoreNextBackspaceRef.current = false;
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
823
917
|
if (multiple && !readOnly && inputValue === '' && value.length > 0) {
|
|
824
918
|
const index = focusedItem === -1 ? value.length - 1 : focusedItem;
|
|
825
919
|
const newValue = value.slice();
|
|
@@ -827,6 +921,18 @@ function useAutocomplete(props) {
|
|
|
827
921
|
handleValue(event, newValue, 'removeOption', {
|
|
828
922
|
option: value[index]
|
|
829
923
|
});
|
|
924
|
+
if (focusedItem !== -1) {
|
|
925
|
+
// Suppress the spurious Backspace VoiceOver synthesises on the
|
|
926
|
+
// input after focus returns to it. Clear it shortly after
|
|
927
|
+
// deletion so a later real Backspace is not ignored if the
|
|
928
|
+
// synthetic follow-up event never arrives.
|
|
929
|
+
ignoreNextBackspaceRef.current = true;
|
|
930
|
+
setTimeout(() => {
|
|
931
|
+
if (ignoreNextBackspaceRef.current) {
|
|
932
|
+
ignoreNextBackspaceRef.current = false;
|
|
933
|
+
}
|
|
934
|
+
}, 0);
|
|
935
|
+
}
|
|
830
936
|
}
|
|
831
937
|
if (!multiple && renderValue && !readOnly && inputValue === '') {
|
|
832
938
|
handleValue(event, null, 'removeOption', {
|
|
@@ -880,7 +986,7 @@ function useAutocomplete(props) {
|
|
|
880
986
|
}
|
|
881
987
|
};
|
|
882
988
|
const handleBlur = event => {
|
|
883
|
-
// Ignore the event when using the scrollbar with
|
|
989
|
+
// Ignore the event when using the scrollbar with IE 11
|
|
884
990
|
if (unstable_isActiveElementInListbox(listboxRef)) {
|
|
885
991
|
inputRef.current.focus();
|
|
886
992
|
return;
|
|
@@ -888,7 +994,12 @@ function useAutocomplete(props) {
|
|
|
888
994
|
setFocused(false);
|
|
889
995
|
firstFocus.current = true;
|
|
890
996
|
ignoreFocus.current = false;
|
|
891
|
-
|
|
997
|
+
|
|
998
|
+
// Auto-select the highlighted option on blur, but only if the highlight
|
|
999
|
+
// came from keyboard navigation or was set programmatically (autoHighlight).
|
|
1000
|
+
// Mouse hover and touch should not trigger selection — the user may have
|
|
1001
|
+
// moved the pointer over an option without intending to commit to it.
|
|
1002
|
+
if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen && highlightReasonRef.current !== 'mouse' && highlightReasonRef.current !== 'touch') {
|
|
892
1003
|
selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
|
|
893
1004
|
} else if (autoSelect && freeSolo && inputValue !== '') {
|
|
894
1005
|
selectNewValue(event, inputValue, 'blur', 'freeSolo');
|
|
@@ -899,9 +1010,10 @@ function useAutocomplete(props) {
|
|
|
899
1010
|
};
|
|
900
1011
|
const handleInputChange = event => {
|
|
901
1012
|
const newValue = event.target.value;
|
|
902
|
-
|
|
1013
|
+
const valueChanged = inputValue !== newValue;
|
|
1014
|
+
if (valueChanged) {
|
|
903
1015
|
setInputValueState(newValue);
|
|
904
|
-
|
|
1016
|
+
touchScrolledRef.current = false;
|
|
905
1017
|
if (onInputChange) {
|
|
906
1018
|
onInputChange(event, newValue, 'input');
|
|
907
1019
|
}
|
|
@@ -915,6 +1027,12 @@ function useAutocomplete(props) {
|
|
|
915
1027
|
} else {
|
|
916
1028
|
handleOpen(event);
|
|
917
1029
|
}
|
|
1030
|
+
|
|
1031
|
+
// Called after handleOpen so it overrides handleOpen's setInputPristine(true)
|
|
1032
|
+
// when the first keystroke also opens the popup.
|
|
1033
|
+
if (valueChanged) {
|
|
1034
|
+
setInputPristine(false);
|
|
1035
|
+
}
|
|
918
1036
|
};
|
|
919
1037
|
const handleOptionMouseMove = event => {
|
|
920
1038
|
const index = Number(event.currentTarget.getAttribute('data-option-index'));
|
|
@@ -924,20 +1042,46 @@ function useAutocomplete(props) {
|
|
|
924
1042
|
index,
|
|
925
1043
|
reason: 'mouse'
|
|
926
1044
|
});
|
|
1045
|
+
} else {
|
|
1046
|
+
// The option is already highlighted (e.g. programmatically via autoHighlight),
|
|
1047
|
+
// but the user moved the mouse over it — mark as mouse-initiated so
|
|
1048
|
+
// autoSelect on blur correctly treats this as incidental hover.
|
|
1049
|
+
highlightReasonRef.current = 'mouse';
|
|
1050
|
+
}
|
|
1051
|
+
// Don't clear the touch-scroll guard while touch state is still latched.
|
|
1052
|
+
// After a touch gesture, browsers may fire compatibility mousemove
|
|
1053
|
+
// events; if those cleared the guard immediately, later compat events in
|
|
1054
|
+
// the same sequence could be misclassified as a real mouse interaction.
|
|
1055
|
+
// Touch state is cleared by the next deliberate interaction
|
|
1056
|
+
// (keyboard nav, handleOptionClick, or handleOpen).
|
|
1057
|
+
if (!isTouchRef.current) {
|
|
1058
|
+
touchScrolledRef.current = false;
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
const handleListboxMouseLeave = event => {
|
|
1062
|
+
if (!resetHighlightOnMouseLeave || highlightedIndexRef.current === -1 || highlightReasonRef.current !== 'mouse' || isTouchRef.current) {
|
|
1063
|
+
return;
|
|
927
1064
|
}
|
|
1065
|
+
setHighlightedIndex({
|
|
1066
|
+
event,
|
|
1067
|
+
index: -1,
|
|
1068
|
+
reason: 'mouse',
|
|
1069
|
+
preserveScroll: true
|
|
1070
|
+
});
|
|
928
1071
|
};
|
|
929
1072
|
const handleOptionTouchStart = event => {
|
|
1073
|
+
touchScrolledRef.current = false;
|
|
930
1074
|
setHighlightedIndex({
|
|
931
1075
|
event,
|
|
932
1076
|
index: Number(event.currentTarget.getAttribute('data-option-index')),
|
|
933
1077
|
reason: 'touch'
|
|
934
1078
|
});
|
|
935
|
-
|
|
1079
|
+
isTouchRef.current = true;
|
|
936
1080
|
};
|
|
937
1081
|
const handleOptionClick = event => {
|
|
938
1082
|
const index = Number(event.currentTarget.getAttribute('data-option-index'));
|
|
939
1083
|
selectNewValue(event, filteredOptions[index], 'selectOption');
|
|
940
|
-
|
|
1084
|
+
isTouchRef.current = false;
|
|
941
1085
|
};
|
|
942
1086
|
const handleItemDelete = index => event => {
|
|
943
1087
|
const newValue = value.slice();
|
|
@@ -962,11 +1106,11 @@ function useAutocomplete(props) {
|
|
|
962
1106
|
// Prevent input blur when interacting with the combobox
|
|
963
1107
|
const handleMouseDown = event => {
|
|
964
1108
|
// Prevent focusing the input if click is anywhere outside the Autocomplete
|
|
965
|
-
if (!event.currentTarget
|
|
1109
|
+
if (!(0, _contains.default)(event.currentTarget, event.target)) {
|
|
966
1110
|
return;
|
|
967
1111
|
}
|
|
968
1112
|
// Don't interfere with interactions outside the input area (e.g. helper text)
|
|
969
|
-
if (anchorEl && !
|
|
1113
|
+
if (anchorEl && !(0, _contains.default)(anchorEl, event.target)) {
|
|
970
1114
|
return;
|
|
971
1115
|
}
|
|
972
1116
|
if (event.target.getAttribute('id') !== id) {
|
|
@@ -977,11 +1121,11 @@ function useAutocomplete(props) {
|
|
|
977
1121
|
// Focus the input when interacting with the combobox
|
|
978
1122
|
const handleClick = event => {
|
|
979
1123
|
// Prevent focusing the input if click is anywhere outside the Autocomplete
|
|
980
|
-
if (!event.currentTarget
|
|
1124
|
+
if (!(0, _contains.default)(event.currentTarget, event.target)) {
|
|
981
1125
|
return;
|
|
982
1126
|
}
|
|
983
1127
|
// Don't interfere with interactions outside the input area (e.g. helper text)
|
|
984
|
-
if (anchorEl && !
|
|
1128
|
+
if (anchorEl && !(0, _contains.default)(anchorEl, event.target)) {
|
|
985
1129
|
return;
|
|
986
1130
|
}
|
|
987
1131
|
inputRef.current.focus();
|
|
@@ -1065,7 +1209,10 @@ function useAutocomplete(props) {
|
|
|
1065
1209
|
getClearProps: () => ({
|
|
1066
1210
|
tabIndex: -1,
|
|
1067
1211
|
type: 'button',
|
|
1068
|
-
onClick:
|
|
1212
|
+
onClick: event => {
|
|
1213
|
+
ignoreFocus.current = true;
|
|
1214
|
+
handleClear(event);
|
|
1215
|
+
}
|
|
1069
1216
|
}),
|
|
1070
1217
|
getItemProps: ({
|
|
1071
1218
|
index = 0
|
|
@@ -1075,6 +1222,12 @@ function useAutocomplete(props) {
|
|
|
1075
1222
|
}),
|
|
1076
1223
|
'data-item-index': index,
|
|
1077
1224
|
tabIndex: -1,
|
|
1225
|
+
onFocus: () => {
|
|
1226
|
+
// If focus on the item doesn't come from keyboard events, we update the state via onFocus.
|
|
1227
|
+
if (focusedItem !== index) {
|
|
1228
|
+
setFocusedItem(index);
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1078
1231
|
...(!readOnly && {
|
|
1079
1232
|
onDelete: multiple ? handleItemDelete(index) : handleSingleItemDelete
|
|
1080
1233
|
})
|
|
@@ -1084,15 +1237,37 @@ function useAutocomplete(props) {
|
|
|
1084
1237
|
type: 'button',
|
|
1085
1238
|
onClick: handlePopupIndicator
|
|
1086
1239
|
}),
|
|
1087
|
-
getListboxProps: () => ({
|
|
1240
|
+
getListboxProps: (other = {}) => ({
|
|
1241
|
+
...other,
|
|
1088
1242
|
role: 'listbox',
|
|
1089
1243
|
id: `${id}-listbox`,
|
|
1090
1244
|
'aria-labelledby': `${id}-label`,
|
|
1091
1245
|
'aria-multiselectable': multiple || undefined,
|
|
1092
1246
|
ref: handleListboxRef,
|
|
1093
1247
|
onMouseDown: event => {
|
|
1248
|
+
other.onMouseDown?.(event);
|
|
1249
|
+
if (event.defaultMuiPrevented) {
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1094
1253
|
// Prevent blur
|
|
1095
1254
|
event.preventDefault();
|
|
1255
|
+
},
|
|
1256
|
+
onScroll: event => {
|
|
1257
|
+
other.onScroll?.(event);
|
|
1258
|
+
if (event.defaultMuiPrevented) {
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (isTouchRef.current) {
|
|
1262
|
+
touchScrolledRef.current = true;
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
onMouseLeave: event => {
|
|
1266
|
+
other.onMouseLeave?.(event);
|
|
1267
|
+
if (event.defaultMuiPrevented) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
handleListboxMouseLeave(event);
|
|
1096
1271
|
}
|
|
1097
1272
|
}),
|
|
1098
1273
|
getOptionProps: ({
|