@mui/material 6.0.0-beta.0 → 6.0.0-beta.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.d.ts +6 -7
- package/Autocomplete/Autocomplete.js +1 -1
- package/Badge/Badge.d.ts +1 -1
- package/Badge/Badge.js +2 -2
- package/Badge/useBadge.d.ts +13 -0
- package/Badge/useBadge.js +41 -0
- package/Badge/useBadge.types.d.ts +40 -0
- package/Badge/useBadge.types.js +1 -0
- package/Breadcrumbs/Breadcrumbs.d.ts +1 -1
- package/Breadcrumbs/Breadcrumbs.js +1 -1
- package/CHANGELOG.md +32 -0
- package/ClickAwayListener/ClickAwayListener.d.ts +47 -0
- package/ClickAwayListener/ClickAwayListener.js +178 -0
- package/ClickAwayListener/index.d.ts +2 -2
- package/ClickAwayListener/index.js +1 -1
- package/InputBase/InputBase.js +2 -2
- package/ListItem/ListItem.js +1 -1
- package/Menu/Menu.js +1 -1
- package/Modal/Modal.d.ts +1 -1
- package/Modal/Modal.js +1 -1
- package/Modal/ModalManager.d.ts +25 -0
- package/Modal/ModalManager.js +211 -0
- package/Modal/index.d.ts +1 -1
- package/Modal/index.js +1 -1
- package/Modal/useModal.d.ts +13 -0
- package/Modal/useModal.js +195 -0
- package/Modal/useModal.types.d.ts +115 -0
- package/Modal/useModal.types.js +1 -0
- package/NoSsr/NoSsr.d.ts +25 -0
- package/NoSsr/NoSsr.js +73 -0
- package/NoSsr/NoSsr.types.d.ts +18 -0
- package/NoSsr/NoSsr.types.js +1 -0
- package/NoSsr/index.d.ts +3 -2
- package/NoSsr/index.js +1 -1
- package/Popover/Popover.js +1 -1
- package/Popper/BasePopper.d.ts +7 -0
- package/Popper/BasePopper.js +370 -0
- package/Popper/BasePopper.types.d.ts +130 -0
- package/Popper/BasePopper.types.js +1 -0
- package/Popper/Popper.d.ts +1 -1
- package/Popper/Popper.js +1 -1
- package/Popper/index.d.ts +2 -1
- package/Popper/index.js +2 -1
- package/Popper/popperClasses.d.ts +8 -0
- package/Popper/popperClasses.js +7 -0
- package/Portal/Portal.d.ts +16 -0
- package/Portal/Portal.js +92 -0
- package/Portal/Portal.types.d.ts +23 -0
- package/Portal/Portal.types.js +1 -0
- package/Portal/index.d.ts +3 -2
- package/Portal/index.js +1 -1
- package/Slider/Slider.d.ts +2 -2
- package/Slider/Slider.js +3 -2
- package/Slider/useSlider.d.ts +14 -0
- package/Slider/useSlider.js +647 -0
- package/Slider/useSlider.types.d.ts +228 -0
- package/Slider/useSlider.types.js +1 -0
- package/Snackbar/Snackbar.d.ts +1 -1
- package/Snackbar/Snackbar.js +3 -3
- package/Snackbar/useSnackbar.d.ts +14 -0
- package/Snackbar/useSnackbar.js +133 -0
- package/Snackbar/useSnackbar.types.d.ts +60 -0
- package/Snackbar/useSnackbar.types.js +1 -0
- package/SwipeableDrawer/SwipeableDrawer.js +1 -1
- package/TabScrollButton/TabScrollButton.d.ts +1 -1
- package/TabScrollButton/TabScrollButton.js +1 -1
- package/TablePagination/TablePagination.js +1 -1
- package/Tabs/Tabs.d.ts +1 -1
- package/Tabs/Tabs.js +1 -1
- package/TextareaAutosize/TextareaAutosize.d.ts +14 -0
- package/TextareaAutosize/TextareaAutosize.js +222 -0
- package/TextareaAutosize/TextareaAutosize.types.d.ts +13 -0
- package/TextareaAutosize/TextareaAutosize.types.js +1 -0
- package/TextareaAutosize/index.d.ts +3 -2
- package/TextareaAutosize/index.js +1 -1
- package/Tooltip/Tooltip.js +1 -1
- package/Unstable_TrapFocus/FocusTrap.d.ts +10 -0
- package/Unstable_TrapFocus/FocusTrap.js +330 -0
- package/Unstable_TrapFocus/FocusTrap.types.d.ts +51 -0
- package/Unstable_TrapFocus/FocusTrap.types.js +1 -0
- package/Unstable_TrapFocus/index.d.ts +2 -2
- package/Unstable_TrapFocus/index.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +2 -2
- package/modern/Autocomplete/Autocomplete.js +1 -1
- package/modern/Badge/Badge.js +2 -2
- package/modern/Badge/useBadge.js +41 -0
- package/modern/Badge/useBadge.types.js +1 -0
- package/modern/Breadcrumbs/Breadcrumbs.js +1 -1
- package/modern/ClickAwayListener/ClickAwayListener.js +178 -0
- package/modern/ClickAwayListener/index.js +1 -1
- package/modern/InputBase/InputBase.js +2 -2
- package/modern/ListItem/ListItem.js +1 -1
- package/modern/Menu/Menu.js +1 -1
- package/modern/Modal/Modal.js +1 -1
- package/modern/Modal/ModalManager.js +211 -0
- package/modern/Modal/index.js +1 -1
- package/modern/Modal/useModal.js +195 -0
- package/modern/Modal/useModal.types.js +1 -0
- package/modern/NoSsr/NoSsr.js +73 -0
- package/modern/NoSsr/NoSsr.types.js +1 -0
- package/modern/NoSsr/index.js +1 -1
- package/modern/Popover/Popover.js +1 -1
- package/modern/Popper/BasePopper.js +370 -0
- package/modern/Popper/BasePopper.types.js +1 -0
- package/modern/Popper/Popper.js +1 -1
- package/modern/Popper/index.js +2 -1
- package/modern/Popper/popperClasses.js +7 -0
- package/modern/Portal/Portal.js +92 -0
- package/modern/Portal/Portal.types.js +1 -0
- package/modern/Portal/index.js +1 -1
- package/modern/Slider/Slider.js +3 -2
- package/modern/Slider/useSlider.js +647 -0
- package/modern/Slider/useSlider.types.js +1 -0
- package/modern/Snackbar/Snackbar.js +3 -3
- package/modern/Snackbar/useSnackbar.js +133 -0
- package/modern/Snackbar/useSnackbar.types.js +1 -0
- package/modern/SwipeableDrawer/SwipeableDrawer.js +1 -1
- package/modern/TabScrollButton/TabScrollButton.js +1 -1
- package/modern/TablePagination/TablePagination.js +1 -1
- package/modern/Tabs/Tabs.js +1 -1
- package/modern/TextareaAutosize/TextareaAutosize.js +222 -0
- package/modern/TextareaAutosize/TextareaAutosize.types.js +1 -0
- package/modern/TextareaAutosize/index.js +1 -1
- package/modern/Tooltip/Tooltip.js +1 -1
- package/modern/Unstable_TrapFocus/FocusTrap.js +330 -0
- package/modern/Unstable_TrapFocus/FocusTrap.types.js +1 -0
- package/modern/Unstable_TrapFocus/index.js +1 -1
- package/modern/index.js +2 -2
- package/modern/useAutocomplete/useAutocomplete.js +976 -2
- package/modern/utils/PolymorphicComponent.js +1 -0
- package/modern/utils/areArraysEqual.js +4 -0
- package/modern/utils/index.js +1 -1
- package/modern/utils/isHostComponent.js +7 -0
- package/modern/utils/omitEventHandlers.js +18 -0
- package/modern/utils/shouldSpreadAdditionalProps.js +1 -1
- package/modern/utils/useSlot.js +3 -1
- package/node/Autocomplete/Autocomplete.js +3 -3
- package/node/Badge/Badge.js +5 -5
- package/node/Badge/useBadge.js +46 -0
- package/node/Badge/useBadge.types.js +5 -0
- package/node/Breadcrumbs/Breadcrumbs.js +2 -2
- package/node/ClickAwayListener/ClickAwayListener.js +184 -0
- package/node/ClickAwayListener/index.js +1 -1
- package/node/InputBase/InputBase.js +7 -7
- package/node/ListItem/ListItem.js +3 -3
- package/node/Menu/Menu.js +3 -3
- package/node/Modal/Modal.js +2 -2
- package/node/Modal/ModalManager.js +219 -0
- package/node/Modal/index.js +2 -2
- package/node/Modal/useModal.js +205 -0
- package/node/Modal/useModal.types.js +5 -0
- package/node/NoSsr/NoSsr.js +81 -0
- package/node/NoSsr/NoSsr.types.js +5 -0
- package/node/NoSsr/index.js +3 -2
- package/node/Popover/Popover.js +2 -2
- package/node/Popper/BasePopper.js +379 -0
- package/node/Popper/BasePopper.types.js +5 -0
- package/node/Popper/Popper.js +2 -2
- package/node/Popper/index.js +14 -1
- package/node/Popper/popperClasses.js +15 -0
- package/node/Portal/Portal.js +100 -0
- package/node/Portal/Portal.types.js +5 -0
- package/node/Portal/index.js +3 -2
- package/node/Slider/Slider.js +14 -13
- package/node/Slider/useSlider.js +670 -0
- package/node/Slider/useSlider.types.js +5 -0
- package/node/Snackbar/Snackbar.js +6 -6
- package/node/Snackbar/useSnackbar.js +141 -0
- package/node/Snackbar/useSnackbar.types.js +5 -0
- package/node/SwipeableDrawer/SwipeableDrawer.js +2 -2
- package/node/TabScrollButton/TabScrollButton.js +3 -3
- package/node/TablePagination/TablePagination.js +2 -2
- package/node/Tabs/Tabs.js +3 -3
- package/node/TextareaAutosize/TextareaAutosize.js +230 -0
- package/node/TextareaAutosize/TextareaAutosize.types.js +5 -0
- package/node/TextareaAutosize/index.js +3 -2
- package/node/Tooltip/Tooltip.js +5 -5
- package/node/Unstable_TrapFocus/FocusTrap.js +339 -0
- package/node/Unstable_TrapFocus/FocusTrap.types.js +5 -0
- package/node/Unstable_TrapFocus/index.js +3 -2
- package/node/index.js +3 -3
- package/node/useAutocomplete/useAutocomplete.js +985 -18
- package/node/utils/PolymorphicComponent.js +5 -0
- package/node/utils/areArraysEqual.js +10 -0
- package/node/utils/index.js +2 -2
- package/node/utils/isHostComponent.js +13 -0
- package/node/utils/omitEventHandlers.js +24 -0
- package/node/utils/shouldSpreadAdditionalProps.js +3 -2
- package/node/utils/useSlot.js +6 -4
- package/package.json +6 -7
- package/styles/overrides.d.ts +1 -1
- package/useAutocomplete/useAutocomplete.d.ts +468 -2
- package/useAutocomplete/useAutocomplete.js +976 -2
- package/utils/PolymorphicComponent.d.ts +17 -0
- package/utils/PolymorphicComponent.js +1 -0
- package/utils/areArraysEqual.d.ts +3 -0
- package/utils/areArraysEqual.js +4 -0
- package/utils/index.d.ts +2 -1
- package/utils/index.js +1 -1
- package/utils/isHostComponent.d.ts +6 -0
- package/utils/isHostComponent.js +7 -0
- package/utils/omitEventHandlers.d.ts +9 -0
- package/utils/omitEventHandlers.js +18 -0
- package/utils/shouldSpreadAdditionalProps.js +1 -1
- package/utils/types.d.ts +2 -1
- package/utils/useSlot.js +3 -1
|
@@ -1,25 +1,992 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
|
+
/* eslint-disable no-constant-condition */
|
|
4
5
|
Object.defineProperty(exports, "__esModule", {
|
|
5
6
|
value: true
|
|
6
7
|
});
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
exports.createFilterOptions = createFilterOptions;
|
|
9
|
+
exports.default = void 0;
|
|
10
|
+
var React = _interopRequireWildcard(require("react"));
|
|
11
|
+
var _utils = require("@mui/utils");
|
|
12
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
13
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
14
|
+
// https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
|
|
15
|
+
function stripDiacritics(string) {
|
|
16
|
+
return string.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
17
|
+
}
|
|
18
|
+
function createFilterOptions(config = {}) {
|
|
19
|
+
const {
|
|
20
|
+
ignoreAccents = true,
|
|
21
|
+
ignoreCase = true,
|
|
22
|
+
limit,
|
|
23
|
+
matchFrom = 'any',
|
|
24
|
+
stringify,
|
|
25
|
+
trim = false
|
|
26
|
+
} = config;
|
|
27
|
+
return (options, {
|
|
28
|
+
inputValue,
|
|
29
|
+
getOptionLabel
|
|
30
|
+
}) => {
|
|
31
|
+
let input = trim ? inputValue.trim() : inputValue;
|
|
32
|
+
if (ignoreCase) {
|
|
33
|
+
input = input.toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
if (ignoreAccents) {
|
|
36
|
+
input = stripDiacritics(input);
|
|
37
|
+
}
|
|
38
|
+
const filteredOptions = !input ? options : options.filter(option => {
|
|
39
|
+
let candidate = (stringify || getOptionLabel)(option);
|
|
40
|
+
if (ignoreCase) {
|
|
41
|
+
candidate = candidate.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
if (ignoreAccents) {
|
|
44
|
+
candidate = stripDiacritics(candidate);
|
|
45
|
+
}
|
|
46
|
+
return matchFrom === 'start' ? candidate.indexOf(input) === 0 : candidate.indexOf(input) > -1;
|
|
47
|
+
});
|
|
48
|
+
return typeof limit === 'number' ? filteredOptions.slice(0, limit) : filteredOptions;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const defaultFilterOptions = createFilterOptions();
|
|
52
|
+
|
|
53
|
+
// Number of options to jump in list box when `Page Up` and `Page Down` keys are used.
|
|
54
|
+
const pageSize = 5;
|
|
55
|
+
const defaultIsActiveElementInListbox = listboxRef => {
|
|
56
|
+
var _listboxRef$current$p;
|
|
57
|
+
return listboxRef.current !== null && ((_listboxRef$current$p = listboxRef.current.parentElement) == null ? void 0 : _listboxRef$current$p.contains(document.activeElement));
|
|
58
|
+
};
|
|
59
|
+
function useAutocomplete(props) {
|
|
60
|
+
const {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
62
|
+
unstable_isActiveElementInListbox = defaultIsActiveElementInListbox,
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
64
|
+
unstable_classNamePrefix = 'Mui',
|
|
65
|
+
autoComplete = false,
|
|
66
|
+
autoHighlight = false,
|
|
67
|
+
autoSelect = false,
|
|
68
|
+
blurOnSelect = false,
|
|
69
|
+
clearOnBlur = !props.freeSolo,
|
|
70
|
+
clearOnEscape = false,
|
|
71
|
+
componentName = 'useAutocomplete',
|
|
72
|
+
defaultValue = props.multiple ? [] : null,
|
|
73
|
+
disableClearable = false,
|
|
74
|
+
disableCloseOnSelect = false,
|
|
75
|
+
disabled: disabledProp,
|
|
76
|
+
disabledItemsFocusable = false,
|
|
77
|
+
disableListWrap = false,
|
|
78
|
+
filterOptions = defaultFilterOptions,
|
|
79
|
+
filterSelectedOptions = false,
|
|
80
|
+
freeSolo = false,
|
|
81
|
+
getOptionDisabled,
|
|
82
|
+
getOptionKey,
|
|
83
|
+
getOptionLabel: getOptionLabelProp = option => {
|
|
84
|
+
var _option$label;
|
|
85
|
+
return (_option$label = option.label) != null ? _option$label : option;
|
|
86
|
+
},
|
|
87
|
+
groupBy,
|
|
88
|
+
handleHomeEndKeys = !props.freeSolo,
|
|
89
|
+
id: idProp,
|
|
90
|
+
includeInputInList = false,
|
|
91
|
+
inputValue: inputValueProp,
|
|
92
|
+
isOptionEqualToValue = (option, value) => option === value,
|
|
93
|
+
multiple = false,
|
|
94
|
+
onChange,
|
|
95
|
+
onClose,
|
|
96
|
+
onHighlightChange,
|
|
97
|
+
onInputChange,
|
|
98
|
+
onOpen,
|
|
99
|
+
open: openProp,
|
|
100
|
+
openOnFocus = false,
|
|
101
|
+
options,
|
|
102
|
+
readOnly = false,
|
|
103
|
+
selectOnFocus = !props.freeSolo,
|
|
104
|
+
value: valueProp
|
|
105
|
+
} = props;
|
|
106
|
+
const id = (0, _utils.unstable_useId)(idProp);
|
|
107
|
+
let getOptionLabel = getOptionLabelProp;
|
|
108
|
+
getOptionLabel = option => {
|
|
109
|
+
const optionLabel = getOptionLabelProp(option);
|
|
110
|
+
if (typeof optionLabel !== 'string') {
|
|
111
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
112
|
+
const erroneousReturn = optionLabel === undefined ? 'undefined' : `${typeof optionLabel} (${optionLabel})`;
|
|
113
|
+
console.error(`MUI: The \`getOptionLabel\` method of ${componentName} returned ${erroneousReturn} instead of a string for ${JSON.stringify(option)}.`);
|
|
114
|
+
}
|
|
115
|
+
return String(optionLabel);
|
|
116
|
+
}
|
|
117
|
+
return optionLabel;
|
|
118
|
+
};
|
|
119
|
+
const ignoreFocus = React.useRef(false);
|
|
120
|
+
const firstFocus = React.useRef(true);
|
|
121
|
+
const inputRef = React.useRef(null);
|
|
122
|
+
const listboxRef = React.useRef(null);
|
|
123
|
+
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
124
|
+
const [focusedTag, setFocusedTag] = React.useState(-1);
|
|
125
|
+
const defaultHighlighted = autoHighlight ? 0 : -1;
|
|
126
|
+
const highlightedIndexRef = React.useRef(defaultHighlighted);
|
|
127
|
+
const [value, setValueState] = (0, _utils.unstable_useControlled)({
|
|
128
|
+
controlled: valueProp,
|
|
129
|
+
default: defaultValue,
|
|
130
|
+
name: componentName
|
|
131
|
+
});
|
|
132
|
+
const [inputValue, setInputValueState] = (0, _utils.unstable_useControlled)({
|
|
133
|
+
controlled: inputValueProp,
|
|
134
|
+
default: '',
|
|
135
|
+
name: componentName,
|
|
136
|
+
state: 'inputValue'
|
|
137
|
+
});
|
|
138
|
+
const [focused, setFocused] = React.useState(false);
|
|
139
|
+
const resetInputValue = React.useCallback((event, newValue, reason) => {
|
|
140
|
+
// retain current `inputValue` if new option isn't selected and `clearOnBlur` is false
|
|
141
|
+
// When `multiple` is enabled, `newValue` is an array of all selected items including the newly selected item
|
|
142
|
+
const isOptionSelected = multiple ? value.length < newValue.length : newValue !== null;
|
|
143
|
+
if (!isOptionSelected && !clearOnBlur) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
let newInputValue;
|
|
147
|
+
if (multiple) {
|
|
148
|
+
newInputValue = '';
|
|
149
|
+
} else if (newValue == null) {
|
|
150
|
+
newInputValue = '';
|
|
151
|
+
} else {
|
|
152
|
+
const optionLabel = getOptionLabel(newValue);
|
|
153
|
+
newInputValue = typeof optionLabel === 'string' ? optionLabel : '';
|
|
154
|
+
}
|
|
155
|
+
if (inputValue === newInputValue) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
setInputValueState(newInputValue);
|
|
159
|
+
if (onInputChange) {
|
|
160
|
+
onInputChange(event, newInputValue, reason);
|
|
161
|
+
}
|
|
162
|
+
}, [getOptionLabel, inputValue, multiple, onInputChange, setInputValueState, clearOnBlur, value]);
|
|
163
|
+
const [open, setOpenState] = (0, _utils.unstable_useControlled)({
|
|
164
|
+
controlled: openProp,
|
|
165
|
+
default: false,
|
|
166
|
+
name: componentName,
|
|
167
|
+
state: 'open'
|
|
168
|
+
});
|
|
169
|
+
const [inputPristine, setInputPristine] = React.useState(true);
|
|
170
|
+
const inputValueIsSelectedValue = !multiple && value != null && inputValue === getOptionLabel(value);
|
|
171
|
+
const popupOpen = open && !readOnly;
|
|
172
|
+
const filteredOptions = popupOpen ? filterOptions(options.filter(option => {
|
|
173
|
+
if (filterSelectedOptions && (multiple ? value : [value]).some(value2 => value2 !== null && isOptionEqualToValue(option, value2))) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return true;
|
|
177
|
+
}),
|
|
178
|
+
// we use the empty string to manipulate `filterOptions` to not filter any options
|
|
179
|
+
// i.e. the filter predicate always returns true
|
|
180
|
+
{
|
|
181
|
+
inputValue: inputValueIsSelectedValue && inputPristine ? '' : inputValue,
|
|
182
|
+
getOptionLabel
|
|
183
|
+
}) : [];
|
|
184
|
+
const previousProps = (0, _utils.usePreviousProps)({
|
|
185
|
+
filteredOptions,
|
|
186
|
+
value,
|
|
187
|
+
inputValue
|
|
188
|
+
});
|
|
189
|
+
React.useEffect(() => {
|
|
190
|
+
const valueChange = value !== previousProps.value;
|
|
191
|
+
if (focused && !valueChange) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Only reset the input's value when freeSolo if the component's value changes.
|
|
196
|
+
if (freeSolo && !valueChange) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
resetInputValue(null, value, 'reset');
|
|
200
|
+
}, [value, resetInputValue, focused, previousProps.value, freeSolo]);
|
|
201
|
+
const listboxAvailable = open && filteredOptions.length > 0 && !readOnly;
|
|
202
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
203
|
+
if (value !== null && !freeSolo && options.length > 0) {
|
|
204
|
+
const missingValue = (multiple ? value : [value]).filter(value2 => !options.some(option => isOptionEqualToValue(option, value2)));
|
|
205
|
+
if (missingValue.length > 0) {
|
|
206
|
+
console.warn([`MUI: The value provided to ${componentName} is invalid.`, `None of the options match with \`${missingValue.length > 1 ? JSON.stringify(missingValue) : JSON.stringify(missingValue[0])}\`.`, 'You can use the `isOptionEqualToValue` prop to customize the equality test.'].join('\n'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
12
209
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
210
|
+
const focusTag = (0, _utils.unstable_useEventCallback)(tagToFocus => {
|
|
211
|
+
if (tagToFocus === -1) {
|
|
212
|
+
inputRef.current.focus();
|
|
213
|
+
} else {
|
|
214
|
+
anchorEl.querySelector(`[data-tag-index="${tagToFocus}"]`).focus();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Ensure the focusedTag is never inconsistent
|
|
219
|
+
React.useEffect(() => {
|
|
220
|
+
if (multiple && focusedTag > value.length - 1) {
|
|
221
|
+
setFocusedTag(-1);
|
|
222
|
+
focusTag(-1);
|
|
223
|
+
}
|
|
224
|
+
}, [value, multiple, focusedTag, focusTag]);
|
|
225
|
+
function validOptionIndex(index, direction) {
|
|
226
|
+
if (!listboxRef.current || index < 0 || index >= filteredOptions.length) {
|
|
227
|
+
return -1;
|
|
228
|
+
}
|
|
229
|
+
let nextFocus = index;
|
|
230
|
+
while (true) {
|
|
231
|
+
const option = listboxRef.current.querySelector(`[data-option-index="${nextFocus}"]`);
|
|
232
|
+
|
|
233
|
+
// Same logic as MenuList.js
|
|
234
|
+
const nextFocusDisabled = disabledItemsFocusable ? false : !option || option.disabled || option.getAttribute('aria-disabled') === 'true';
|
|
235
|
+
if (option && option.hasAttribute('tabindex') && !nextFocusDisabled) {
|
|
236
|
+
// The next option is available
|
|
237
|
+
return nextFocus;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The next option is disabled, move to the next element.
|
|
241
|
+
// with looped index
|
|
242
|
+
if (direction === 'next') {
|
|
243
|
+
nextFocus = (nextFocus + 1) % filteredOptions.length;
|
|
244
|
+
} else {
|
|
245
|
+
nextFocus = (nextFocus - 1 + filteredOptions.length) % filteredOptions.length;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// We end up with initial index, that means we don't have available options.
|
|
249
|
+
// All of them are disabled
|
|
250
|
+
if (nextFocus === index) {
|
|
251
|
+
return -1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const setHighlightedIndex = (0, _utils.unstable_useEventCallback)(({
|
|
256
|
+
event,
|
|
257
|
+
index,
|
|
258
|
+
reason = 'auto'
|
|
259
|
+
}) => {
|
|
260
|
+
highlightedIndexRef.current = index;
|
|
261
|
+
|
|
262
|
+
// does the index exist?
|
|
263
|
+
if (index === -1) {
|
|
264
|
+
inputRef.current.removeAttribute('aria-activedescendant');
|
|
265
|
+
} else {
|
|
266
|
+
inputRef.current.setAttribute('aria-activedescendant', `${id}-option-${index}`);
|
|
267
|
+
}
|
|
268
|
+
if (onHighlightChange) {
|
|
269
|
+
onHighlightChange(event, index === -1 ? null : filteredOptions[index], reason);
|
|
270
|
+
}
|
|
271
|
+
if (!listboxRef.current) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const prev = listboxRef.current.querySelector(`[role="option"].${unstable_classNamePrefix}-focused`);
|
|
275
|
+
if (prev) {
|
|
276
|
+
prev.classList.remove(`${unstable_classNamePrefix}-focused`);
|
|
277
|
+
prev.classList.remove(`${unstable_classNamePrefix}-focusVisible`);
|
|
278
|
+
}
|
|
279
|
+
let listboxNode = listboxRef.current;
|
|
280
|
+
if (listboxRef.current.getAttribute('role') !== 'listbox') {
|
|
281
|
+
listboxNode = listboxRef.current.parentElement.querySelector('[role="listbox"]');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// "No results"
|
|
285
|
+
if (!listboxNode) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (index === -1) {
|
|
289
|
+
listboxNode.scrollTop = 0;
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const option = listboxRef.current.querySelector(`[data-option-index="${index}"]`);
|
|
293
|
+
if (!option) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
option.classList.add(`${unstable_classNamePrefix}-focused`);
|
|
297
|
+
if (reason === 'keyboard') {
|
|
298
|
+
option.classList.add(`${unstable_classNamePrefix}-focusVisible`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Scroll active descendant into view.
|
|
302
|
+
// Logic copied from https://www.w3.org/WAI/content-assets/wai-aria-practices/patterns/combobox/examples/js/select-only.js
|
|
303
|
+
// In case of mouse clicks and touch (in mobile devices) we avoid scrolling the element and keep both behaviors same.
|
|
304
|
+
// Consider this API instead once it has a better browser support:
|
|
305
|
+
// .scrollIntoView({ scrollMode: 'if-needed', block: 'nearest' });
|
|
306
|
+
if (listboxNode.scrollHeight > listboxNode.clientHeight && reason !== 'mouse' && reason !== 'touch') {
|
|
307
|
+
const element = option;
|
|
308
|
+
const scrollBottom = listboxNode.clientHeight + listboxNode.scrollTop;
|
|
309
|
+
const elementBottom = element.offsetTop + element.offsetHeight;
|
|
310
|
+
if (elementBottom > scrollBottom) {
|
|
311
|
+
listboxNode.scrollTop = elementBottom - listboxNode.clientHeight;
|
|
312
|
+
} else if (element.offsetTop - element.offsetHeight * (groupBy ? 1.3 : 0) < listboxNode.scrollTop) {
|
|
313
|
+
listboxNode.scrollTop = element.offsetTop - element.offsetHeight * (groupBy ? 1.3 : 0);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
const changeHighlightedIndex = (0, _utils.unstable_useEventCallback)(({
|
|
318
|
+
event,
|
|
319
|
+
diff,
|
|
320
|
+
direction = 'next',
|
|
321
|
+
reason = 'auto'
|
|
322
|
+
}) => {
|
|
323
|
+
if (!popupOpen) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const getNextIndex = () => {
|
|
327
|
+
const maxIndex = filteredOptions.length - 1;
|
|
328
|
+
if (diff === 'reset') {
|
|
329
|
+
return defaultHighlighted;
|
|
330
|
+
}
|
|
331
|
+
if (diff === 'start') {
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
334
|
+
if (diff === 'end') {
|
|
335
|
+
return maxIndex;
|
|
336
|
+
}
|
|
337
|
+
const newIndex = highlightedIndexRef.current + diff;
|
|
338
|
+
if (newIndex < 0) {
|
|
339
|
+
if (newIndex === -1 && includeInputInList) {
|
|
340
|
+
return -1;
|
|
341
|
+
}
|
|
342
|
+
if (disableListWrap && highlightedIndexRef.current !== -1 || Math.abs(diff) > 1) {
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
return maxIndex;
|
|
346
|
+
}
|
|
347
|
+
if (newIndex > maxIndex) {
|
|
348
|
+
if (newIndex === maxIndex + 1 && includeInputInList) {
|
|
349
|
+
return -1;
|
|
350
|
+
}
|
|
351
|
+
if (disableListWrap || Math.abs(diff) > 1) {
|
|
352
|
+
return maxIndex;
|
|
353
|
+
}
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
return newIndex;
|
|
357
|
+
};
|
|
358
|
+
const nextIndex = validOptionIndex(getNextIndex(), direction);
|
|
359
|
+
setHighlightedIndex({
|
|
360
|
+
index: nextIndex,
|
|
361
|
+
reason,
|
|
362
|
+
event
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Sync the content of the input with the highlighted option.
|
|
366
|
+
if (autoComplete && diff !== 'reset') {
|
|
367
|
+
if (nextIndex === -1) {
|
|
368
|
+
inputRef.current.value = inputValue;
|
|
369
|
+
} else {
|
|
370
|
+
const option = getOptionLabel(filteredOptions[nextIndex]);
|
|
371
|
+
inputRef.current.value = option;
|
|
372
|
+
|
|
373
|
+
// The portion of the selected suggestion that has not been typed by the user,
|
|
374
|
+
// a completion string, appears inline after the input cursor in the textbox.
|
|
375
|
+
const index = option.toLowerCase().indexOf(inputValue.toLowerCase());
|
|
376
|
+
if (index === 0 && inputValue.length > 0) {
|
|
377
|
+
inputRef.current.setSelectionRange(inputValue.length, option.length);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
const getPreviousHighlightedOptionIndex = () => {
|
|
383
|
+
const isSameValue = (value1, value2) => {
|
|
384
|
+
const label1 = value1 ? getOptionLabel(value1) : '';
|
|
385
|
+
const label2 = value2 ? getOptionLabel(value2) : '';
|
|
386
|
+
return label1 === label2;
|
|
387
|
+
};
|
|
388
|
+
if (highlightedIndexRef.current !== -1 && previousProps.filteredOptions && previousProps.filteredOptions.length !== filteredOptions.length && previousProps.inputValue === inputValue && (multiple ? value.length === previousProps.value.length && previousProps.value.every((val, i) => getOptionLabel(value[i]) === getOptionLabel(val)) : isSameValue(previousProps.value, value))) {
|
|
389
|
+
const previousHighlightedOption = previousProps.filteredOptions[highlightedIndexRef.current];
|
|
390
|
+
if (previousHighlightedOption) {
|
|
391
|
+
return filteredOptions.findIndex(option => {
|
|
392
|
+
return getOptionLabel(option) === getOptionLabel(previousHighlightedOption);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return -1;
|
|
397
|
+
};
|
|
398
|
+
const syncHighlightedIndex = React.useCallback(() => {
|
|
399
|
+
if (!popupOpen) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check if the previously highlighted option still exists in the updated filtered options list and if the value and inputValue haven't changed
|
|
404
|
+
// If it exists and the value and the inputValue haven't changed, just update its index, otherwise continue execution
|
|
405
|
+
const previousHighlightedOptionIndex = getPreviousHighlightedOptionIndex();
|
|
406
|
+
if (previousHighlightedOptionIndex !== -1) {
|
|
407
|
+
highlightedIndexRef.current = previousHighlightedOptionIndex;
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const valueItem = multiple ? value[0] : value;
|
|
411
|
+
|
|
412
|
+
// The popup is empty, reset
|
|
413
|
+
if (filteredOptions.length === 0 || valueItem == null) {
|
|
414
|
+
changeHighlightedIndex({
|
|
415
|
+
diff: 'reset'
|
|
416
|
+
});
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (!listboxRef.current) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Synchronize the value with the highlighted index
|
|
424
|
+
if (valueItem != null) {
|
|
425
|
+
const currentOption = filteredOptions[highlightedIndexRef.current];
|
|
426
|
+
|
|
427
|
+
// Keep the current highlighted index if possible
|
|
428
|
+
if (multiple && currentOption && value.findIndex(val => isOptionEqualToValue(currentOption, val)) !== -1) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const itemIndex = filteredOptions.findIndex(optionItem => isOptionEqualToValue(optionItem, valueItem));
|
|
432
|
+
if (itemIndex === -1) {
|
|
433
|
+
changeHighlightedIndex({
|
|
434
|
+
diff: 'reset'
|
|
435
|
+
});
|
|
436
|
+
} else {
|
|
437
|
+
setHighlightedIndex({
|
|
438
|
+
index: itemIndex
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Prevent the highlighted index to leak outside the boundaries.
|
|
445
|
+
if (highlightedIndexRef.current >= filteredOptions.length - 1) {
|
|
446
|
+
setHighlightedIndex({
|
|
447
|
+
index: filteredOptions.length - 1
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Restore the focus to the previous index.
|
|
453
|
+
setHighlightedIndex({
|
|
454
|
+
index: highlightedIndexRef.current
|
|
455
|
+
});
|
|
456
|
+
// Ignore filteredOptions (and options, isOptionEqualToValue, getOptionLabel) not to break the scroll position
|
|
457
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
458
|
+
}, [
|
|
459
|
+
// Only sync the highlighted index when the option switch between empty and not
|
|
460
|
+
filteredOptions.length,
|
|
461
|
+
// Don't sync the highlighted index with the value when multiple
|
|
462
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
463
|
+
multiple ? false : value, filterSelectedOptions, changeHighlightedIndex, setHighlightedIndex, popupOpen, inputValue, multiple]);
|
|
464
|
+
const handleListboxRef = (0, _utils.unstable_useEventCallback)(node => {
|
|
465
|
+
(0, _utils.unstable_setRef)(listboxRef, node);
|
|
466
|
+
if (!node) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
syncHighlightedIndex();
|
|
470
|
+
});
|
|
471
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
472
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
473
|
+
React.useEffect(() => {
|
|
474
|
+
if (!inputRef.current || inputRef.current.nodeName !== 'INPUT') {
|
|
475
|
+
if (inputRef.current && inputRef.current.nodeName === 'TEXTAREA') {
|
|
476
|
+
console.warn([`A textarea element was provided to ${componentName} where input was expected.`, `This is not a supported scenario but it may work under certain conditions.`, `A textarea keyboard navigation may conflict with Autocomplete controls (for example enter and arrow keys).`, `Make sure to test keyboard navigation and add custom event handlers if necessary.`].join('\n'));
|
|
477
|
+
} else {
|
|
478
|
+
console.error([`MUI: Unable to find the input element. It was resolved to ${inputRef.current} while an HTMLInputElement was expected.`, `Instead, ${componentName} expects an input element.`, '', componentName === 'useAutocomplete' ? 'Make sure you have bound getInputProps correctly and that the normal ref/effect resolutions order is guaranteed.' : 'Make sure you have customized the input component correctly.'].join('\n'));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}, [componentName]);
|
|
482
|
+
}
|
|
483
|
+
React.useEffect(() => {
|
|
484
|
+
syncHighlightedIndex();
|
|
485
|
+
}, [syncHighlightedIndex]);
|
|
486
|
+
const handleOpen = event => {
|
|
487
|
+
if (open) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
setOpenState(true);
|
|
491
|
+
setInputPristine(true);
|
|
492
|
+
if (onOpen) {
|
|
493
|
+
onOpen(event);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const handleClose = (event, reason) => {
|
|
497
|
+
if (!open) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
setOpenState(false);
|
|
501
|
+
if (onClose) {
|
|
502
|
+
onClose(event, reason);
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
const handleValue = (event, newValue, reason, details) => {
|
|
506
|
+
if (multiple) {
|
|
507
|
+
if (value.length === newValue.length && value.every((val, i) => val === newValue[i])) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
} else if (value === newValue) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (onChange) {
|
|
514
|
+
onChange(event, newValue, reason, details);
|
|
515
|
+
}
|
|
516
|
+
setValueState(newValue);
|
|
517
|
+
};
|
|
518
|
+
const isTouch = React.useRef(false);
|
|
519
|
+
const selectNewValue = (event, option, reasonProp = 'selectOption', origin = 'options') => {
|
|
520
|
+
let reason = reasonProp;
|
|
521
|
+
let newValue = option;
|
|
522
|
+
if (multiple) {
|
|
523
|
+
newValue = Array.isArray(value) ? value.slice() : [];
|
|
524
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
525
|
+
const matches = newValue.filter(val => isOptionEqualToValue(option, val));
|
|
526
|
+
if (matches.length > 1) {
|
|
527
|
+
console.error([`MUI: The \`isOptionEqualToValue\` method of ${componentName} does not handle the arguments correctly.`, `The component expects a single value to match a given option but found ${matches.length} matches.`].join('\n'));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const itemIndex = newValue.findIndex(valueItem => isOptionEqualToValue(option, valueItem));
|
|
531
|
+
if (itemIndex === -1) {
|
|
532
|
+
newValue.push(option);
|
|
533
|
+
} else if (origin !== 'freeSolo') {
|
|
534
|
+
newValue.splice(itemIndex, 1);
|
|
535
|
+
reason = 'removeOption';
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
resetInputValue(event, newValue, reason);
|
|
539
|
+
handleValue(event, newValue, reason, {
|
|
540
|
+
option
|
|
541
|
+
});
|
|
542
|
+
if (!disableCloseOnSelect && (!event || !event.ctrlKey && !event.metaKey)) {
|
|
543
|
+
handleClose(event, reason);
|
|
544
|
+
}
|
|
545
|
+
if (blurOnSelect === true || blurOnSelect === 'touch' && isTouch.current || blurOnSelect === 'mouse' && !isTouch.current) {
|
|
546
|
+
inputRef.current.blur();
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
function validTagIndex(index, direction) {
|
|
550
|
+
if (index === -1) {
|
|
551
|
+
return -1;
|
|
552
|
+
}
|
|
553
|
+
let nextFocus = index;
|
|
554
|
+
while (true) {
|
|
555
|
+
// Out of range
|
|
556
|
+
if (direction === 'next' && nextFocus === value.length || direction === 'previous' && nextFocus === -1) {
|
|
557
|
+
return -1;
|
|
558
|
+
}
|
|
559
|
+
const option = anchorEl.querySelector(`[data-tag-index="${nextFocus}"]`);
|
|
560
|
+
|
|
561
|
+
// Same logic as MenuList.js
|
|
562
|
+
if (!option || !option.hasAttribute('tabindex') || option.disabled || option.getAttribute('aria-disabled') === 'true') {
|
|
563
|
+
nextFocus += direction === 'next' ? 1 : -1;
|
|
564
|
+
} else {
|
|
565
|
+
return nextFocus;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const handleFocusTag = (event, direction) => {
|
|
570
|
+
if (!multiple) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (inputValue === '') {
|
|
574
|
+
handleClose(event, 'toggleInput');
|
|
575
|
+
}
|
|
576
|
+
let nextTag = focusedTag;
|
|
577
|
+
if (focusedTag === -1) {
|
|
578
|
+
if (inputValue === '' && direction === 'previous') {
|
|
579
|
+
nextTag = value.length - 1;
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
nextTag += direction === 'next' ? 1 : -1;
|
|
583
|
+
if (nextTag < 0) {
|
|
584
|
+
nextTag = 0;
|
|
585
|
+
}
|
|
586
|
+
if (nextTag === value.length) {
|
|
587
|
+
nextTag = -1;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
nextTag = validTagIndex(nextTag, direction);
|
|
591
|
+
setFocusedTag(nextTag);
|
|
592
|
+
focusTag(nextTag);
|
|
593
|
+
};
|
|
594
|
+
const handleClear = event => {
|
|
595
|
+
ignoreFocus.current = true;
|
|
596
|
+
setInputValueState('');
|
|
597
|
+
if (onInputChange) {
|
|
598
|
+
onInputChange(event, '', 'clear');
|
|
599
|
+
}
|
|
600
|
+
handleValue(event, multiple ? [] : null, 'clear');
|
|
601
|
+
};
|
|
602
|
+
const handleKeyDown = other => event => {
|
|
603
|
+
if (other.onKeyDown) {
|
|
604
|
+
other.onKeyDown(event);
|
|
605
|
+
}
|
|
606
|
+
if (event.defaultMuiPrevented) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (focusedTag !== -1 && ['ArrowLeft', 'ArrowRight'].indexOf(event.key) === -1) {
|
|
610
|
+
setFocusedTag(-1);
|
|
611
|
+
focusTag(-1);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Wait until IME is settled.
|
|
615
|
+
if (event.which !== 229) {
|
|
616
|
+
switch (event.key) {
|
|
617
|
+
case 'Home':
|
|
618
|
+
if (popupOpen && handleHomeEndKeys) {
|
|
619
|
+
// Prevent scroll of the page
|
|
620
|
+
event.preventDefault();
|
|
621
|
+
changeHighlightedIndex({
|
|
622
|
+
diff: 'start',
|
|
623
|
+
direction: 'next',
|
|
624
|
+
reason: 'keyboard',
|
|
625
|
+
event
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
break;
|
|
629
|
+
case 'End':
|
|
630
|
+
if (popupOpen && handleHomeEndKeys) {
|
|
631
|
+
// Prevent scroll of the page
|
|
632
|
+
event.preventDefault();
|
|
633
|
+
changeHighlightedIndex({
|
|
634
|
+
diff: 'end',
|
|
635
|
+
direction: 'previous',
|
|
636
|
+
reason: 'keyboard',
|
|
637
|
+
event
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
case 'PageUp':
|
|
642
|
+
// Prevent scroll of the page
|
|
643
|
+
event.preventDefault();
|
|
644
|
+
changeHighlightedIndex({
|
|
645
|
+
diff: -pageSize,
|
|
646
|
+
direction: 'previous',
|
|
647
|
+
reason: 'keyboard',
|
|
648
|
+
event
|
|
649
|
+
});
|
|
650
|
+
handleOpen(event);
|
|
651
|
+
break;
|
|
652
|
+
case 'PageDown':
|
|
653
|
+
// Prevent scroll of the page
|
|
654
|
+
event.preventDefault();
|
|
655
|
+
changeHighlightedIndex({
|
|
656
|
+
diff: pageSize,
|
|
657
|
+
direction: 'next',
|
|
658
|
+
reason: 'keyboard',
|
|
659
|
+
event
|
|
660
|
+
});
|
|
661
|
+
handleOpen(event);
|
|
662
|
+
break;
|
|
663
|
+
case 'ArrowDown':
|
|
664
|
+
// Prevent cursor move
|
|
665
|
+
event.preventDefault();
|
|
666
|
+
changeHighlightedIndex({
|
|
667
|
+
diff: 1,
|
|
668
|
+
direction: 'next',
|
|
669
|
+
reason: 'keyboard',
|
|
670
|
+
event
|
|
671
|
+
});
|
|
672
|
+
handleOpen(event);
|
|
673
|
+
break;
|
|
674
|
+
case 'ArrowUp':
|
|
675
|
+
// Prevent cursor move
|
|
676
|
+
event.preventDefault();
|
|
677
|
+
changeHighlightedIndex({
|
|
678
|
+
diff: -1,
|
|
679
|
+
direction: 'previous',
|
|
680
|
+
reason: 'keyboard',
|
|
681
|
+
event
|
|
682
|
+
});
|
|
683
|
+
handleOpen(event);
|
|
684
|
+
break;
|
|
685
|
+
case 'ArrowLeft':
|
|
686
|
+
handleFocusTag(event, 'previous');
|
|
687
|
+
break;
|
|
688
|
+
case 'ArrowRight':
|
|
689
|
+
handleFocusTag(event, 'next');
|
|
690
|
+
break;
|
|
691
|
+
case 'Enter':
|
|
692
|
+
if (highlightedIndexRef.current !== -1 && popupOpen) {
|
|
693
|
+
const option = filteredOptions[highlightedIndexRef.current];
|
|
694
|
+
const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
|
|
695
|
+
|
|
696
|
+
// Avoid early form validation, let the end-users continue filling the form.
|
|
697
|
+
event.preventDefault();
|
|
698
|
+
if (disabled) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
selectNewValue(event, option, 'selectOption');
|
|
702
|
+
|
|
703
|
+
// Move the selection to the end.
|
|
704
|
+
if (autoComplete) {
|
|
705
|
+
inputRef.current.setSelectionRange(inputRef.current.value.length, inputRef.current.value.length);
|
|
706
|
+
}
|
|
707
|
+
} else if (freeSolo && inputValue !== '' && inputValueIsSelectedValue === false) {
|
|
708
|
+
if (multiple) {
|
|
709
|
+
// Allow people to add new values before they submit the form.
|
|
710
|
+
event.preventDefault();
|
|
711
|
+
}
|
|
712
|
+
selectNewValue(event, inputValue, 'createOption', 'freeSolo');
|
|
713
|
+
}
|
|
714
|
+
break;
|
|
715
|
+
case 'Escape':
|
|
716
|
+
if (popupOpen) {
|
|
717
|
+
// Avoid Opera to exit fullscreen mode.
|
|
718
|
+
event.preventDefault();
|
|
719
|
+
// Avoid the Modal to handle the event.
|
|
720
|
+
event.stopPropagation();
|
|
721
|
+
handleClose(event, 'escape');
|
|
722
|
+
} else if (clearOnEscape && (inputValue !== '' || multiple && value.length > 0)) {
|
|
723
|
+
// Avoid Opera to exit fullscreen mode.
|
|
724
|
+
event.preventDefault();
|
|
725
|
+
// Avoid the Modal to handle the event.
|
|
726
|
+
event.stopPropagation();
|
|
727
|
+
handleClear(event);
|
|
728
|
+
}
|
|
729
|
+
break;
|
|
730
|
+
case 'Backspace':
|
|
731
|
+
// Remove the value on the left of the "cursor"
|
|
732
|
+
if (multiple && !readOnly && inputValue === '' && value.length > 0) {
|
|
733
|
+
const index = focusedTag === -1 ? value.length - 1 : focusedTag;
|
|
734
|
+
const newValue = value.slice();
|
|
735
|
+
newValue.splice(index, 1);
|
|
736
|
+
handleValue(event, newValue, 'removeOption', {
|
|
737
|
+
option: value[index]
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
break;
|
|
741
|
+
case 'Delete':
|
|
742
|
+
// Remove the value on the right of the "cursor"
|
|
743
|
+
if (multiple && !readOnly && inputValue === '' && value.length > 0 && focusedTag !== -1) {
|
|
744
|
+
const index = focusedTag;
|
|
745
|
+
const newValue = value.slice();
|
|
746
|
+
newValue.splice(index, 1);
|
|
747
|
+
handleValue(event, newValue, 'removeOption', {
|
|
748
|
+
option: value[index]
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
break;
|
|
752
|
+
default:
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const handleFocus = event => {
|
|
757
|
+
setFocused(true);
|
|
758
|
+
if (openOnFocus && !ignoreFocus.current) {
|
|
759
|
+
handleOpen(event);
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
const handleBlur = event => {
|
|
763
|
+
// Ignore the event when using the scrollbar with IE11
|
|
764
|
+
if (unstable_isActiveElementInListbox(listboxRef)) {
|
|
765
|
+
inputRef.current.focus();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
setFocused(false);
|
|
769
|
+
firstFocus.current = true;
|
|
770
|
+
ignoreFocus.current = false;
|
|
771
|
+
if (autoSelect && highlightedIndexRef.current !== -1 && popupOpen) {
|
|
772
|
+
selectNewValue(event, filteredOptions[highlightedIndexRef.current], 'blur');
|
|
773
|
+
} else if (autoSelect && freeSolo && inputValue !== '') {
|
|
774
|
+
selectNewValue(event, inputValue, 'blur', 'freeSolo');
|
|
775
|
+
} else if (clearOnBlur) {
|
|
776
|
+
resetInputValue(event, value, 'blur');
|
|
777
|
+
}
|
|
778
|
+
handleClose(event, 'blur');
|
|
779
|
+
};
|
|
780
|
+
const handleInputChange = event => {
|
|
781
|
+
const newValue = event.target.value;
|
|
782
|
+
if (inputValue !== newValue) {
|
|
783
|
+
setInputValueState(newValue);
|
|
784
|
+
setInputPristine(false);
|
|
785
|
+
if (onInputChange) {
|
|
786
|
+
onInputChange(event, newValue, 'input');
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
if (newValue === '') {
|
|
790
|
+
if (!disableClearable && !multiple) {
|
|
791
|
+
handleValue(event, null, 'clear');
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
handleOpen(event);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
const handleOptionMouseMove = event => {
|
|
798
|
+
const index = Number(event.currentTarget.getAttribute('data-option-index'));
|
|
799
|
+
if (highlightedIndexRef.current !== index) {
|
|
800
|
+
setHighlightedIndex({
|
|
801
|
+
event,
|
|
802
|
+
index,
|
|
803
|
+
reason: 'mouse'
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
const handleOptionTouchStart = event => {
|
|
808
|
+
setHighlightedIndex({
|
|
809
|
+
event,
|
|
810
|
+
index: Number(event.currentTarget.getAttribute('data-option-index')),
|
|
811
|
+
reason: 'touch'
|
|
812
|
+
});
|
|
813
|
+
isTouch.current = true;
|
|
814
|
+
};
|
|
815
|
+
const handleOptionClick = event => {
|
|
816
|
+
const index = Number(event.currentTarget.getAttribute('data-option-index'));
|
|
817
|
+
selectNewValue(event, filteredOptions[index], 'selectOption');
|
|
818
|
+
isTouch.current = false;
|
|
819
|
+
};
|
|
820
|
+
const handleTagDelete = index => event => {
|
|
821
|
+
const newValue = value.slice();
|
|
822
|
+
newValue.splice(index, 1);
|
|
823
|
+
handleValue(event, newValue, 'removeOption', {
|
|
824
|
+
option: value[index]
|
|
825
|
+
});
|
|
826
|
+
};
|
|
827
|
+
const handlePopupIndicator = event => {
|
|
828
|
+
if (open) {
|
|
829
|
+
handleClose(event, 'toggleInput');
|
|
830
|
+
} else {
|
|
831
|
+
handleOpen(event);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
// Prevent input blur when interacting with the combobox
|
|
836
|
+
const handleMouseDown = event => {
|
|
837
|
+
// Prevent focusing the input if click is anywhere outside the Autocomplete
|
|
838
|
+
if (!event.currentTarget.contains(event.target)) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (event.target.getAttribute('id') !== id) {
|
|
842
|
+
event.preventDefault();
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// Focus the input when interacting with the combobox
|
|
847
|
+
const handleClick = event => {
|
|
848
|
+
// Prevent focusing the input if click is anywhere outside the Autocomplete
|
|
849
|
+
if (!event.currentTarget.contains(event.target)) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
inputRef.current.focus();
|
|
853
|
+
if (selectOnFocus && firstFocus.current && inputRef.current.selectionEnd - inputRef.current.selectionStart === 0) {
|
|
854
|
+
inputRef.current.select();
|
|
855
|
+
}
|
|
856
|
+
firstFocus.current = false;
|
|
857
|
+
};
|
|
858
|
+
const handleInputMouseDown = event => {
|
|
859
|
+
if (!disabledProp && (inputValue === '' || !open)) {
|
|
860
|
+
handlePopupIndicator(event);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
let dirty = freeSolo && inputValue.length > 0;
|
|
864
|
+
dirty = dirty || (multiple ? value.length > 0 : value !== null);
|
|
865
|
+
let groupedOptions = filteredOptions;
|
|
866
|
+
if (groupBy) {
|
|
867
|
+
// used to keep track of key and indexes in the result array
|
|
868
|
+
const indexBy = new Map();
|
|
869
|
+
let warn = false;
|
|
870
|
+
groupedOptions = filteredOptions.reduce((acc, option, index) => {
|
|
871
|
+
const group = groupBy(option);
|
|
872
|
+
if (acc.length > 0 && acc[acc.length - 1].group === group) {
|
|
873
|
+
acc[acc.length - 1].options.push(option);
|
|
874
|
+
} else {
|
|
875
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
876
|
+
if (indexBy.get(group) && !warn) {
|
|
877
|
+
console.warn(`MUI: The options provided combined with the \`groupBy\` method of ${componentName} returns duplicated headers.`, 'You can solve the issue by sorting the options with the output of `groupBy`.');
|
|
878
|
+
warn = true;
|
|
879
|
+
}
|
|
880
|
+
indexBy.set(group, true);
|
|
881
|
+
}
|
|
882
|
+
acc.push({
|
|
883
|
+
key: index,
|
|
884
|
+
index,
|
|
885
|
+
group,
|
|
886
|
+
options: [option]
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
return acc;
|
|
890
|
+
}, []);
|
|
891
|
+
}
|
|
892
|
+
if (disabledProp && focused) {
|
|
893
|
+
handleBlur();
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
getRootProps: (other = {}) => ({
|
|
897
|
+
'aria-owns': listboxAvailable ? `${id}-listbox` : null,
|
|
898
|
+
...other,
|
|
899
|
+
onKeyDown: handleKeyDown(other),
|
|
900
|
+
onMouseDown: handleMouseDown,
|
|
901
|
+
onClick: handleClick
|
|
902
|
+
}),
|
|
903
|
+
getInputLabelProps: () => ({
|
|
904
|
+
id: `${id}-label`,
|
|
905
|
+
htmlFor: id
|
|
906
|
+
}),
|
|
907
|
+
getInputProps: () => ({
|
|
908
|
+
id,
|
|
909
|
+
value: inputValue,
|
|
910
|
+
onBlur: handleBlur,
|
|
911
|
+
onFocus: handleFocus,
|
|
912
|
+
onChange: handleInputChange,
|
|
913
|
+
onMouseDown: handleInputMouseDown,
|
|
914
|
+
// if open then this is handled imperatively so don't let react override
|
|
915
|
+
// only have an opinion about this when closed
|
|
916
|
+
'aria-activedescendant': popupOpen ? '' : null,
|
|
917
|
+
'aria-autocomplete': autoComplete ? 'both' : 'list',
|
|
918
|
+
'aria-controls': listboxAvailable ? `${id}-listbox` : undefined,
|
|
919
|
+
'aria-expanded': listboxAvailable,
|
|
920
|
+
// Disable browser's suggestion that might overlap with the popup.
|
|
921
|
+
// Handle autocomplete but not autofill.
|
|
922
|
+
autoComplete: 'off',
|
|
923
|
+
ref: inputRef,
|
|
924
|
+
autoCapitalize: 'none',
|
|
925
|
+
spellCheck: 'false',
|
|
926
|
+
role: 'combobox',
|
|
927
|
+
disabled: disabledProp
|
|
928
|
+
}),
|
|
929
|
+
getClearProps: () => ({
|
|
930
|
+
tabIndex: -1,
|
|
931
|
+
type: 'button',
|
|
932
|
+
onClick: handleClear
|
|
933
|
+
}),
|
|
934
|
+
getPopupIndicatorProps: () => ({
|
|
935
|
+
tabIndex: -1,
|
|
936
|
+
type: 'button',
|
|
937
|
+
onClick: handlePopupIndicator
|
|
938
|
+
}),
|
|
939
|
+
getTagProps: ({
|
|
940
|
+
index
|
|
941
|
+
}) => ({
|
|
942
|
+
key: index,
|
|
943
|
+
'data-tag-index': index,
|
|
944
|
+
tabIndex: -1,
|
|
945
|
+
...(!readOnly && {
|
|
946
|
+
onDelete: handleTagDelete(index)
|
|
947
|
+
})
|
|
948
|
+
}),
|
|
949
|
+
getListboxProps: () => ({
|
|
950
|
+
role: 'listbox',
|
|
951
|
+
id: `${id}-listbox`,
|
|
952
|
+
'aria-labelledby': `${id}-label`,
|
|
953
|
+
ref: handleListboxRef,
|
|
954
|
+
onMouseDown: event => {
|
|
955
|
+
// Prevent blur
|
|
956
|
+
event.preventDefault();
|
|
957
|
+
}
|
|
958
|
+
}),
|
|
959
|
+
getOptionProps: ({
|
|
960
|
+
index,
|
|
961
|
+
option
|
|
962
|
+
}) => {
|
|
963
|
+
var _getOptionKey;
|
|
964
|
+
const selected = (multiple ? value : [value]).some(value2 => value2 != null && isOptionEqualToValue(option, value2));
|
|
965
|
+
const disabled = getOptionDisabled ? getOptionDisabled(option) : false;
|
|
966
|
+
return {
|
|
967
|
+
key: (_getOptionKey = getOptionKey == null ? void 0 : getOptionKey(option)) != null ? _getOptionKey : getOptionLabel(option),
|
|
968
|
+
tabIndex: -1,
|
|
969
|
+
role: 'option',
|
|
970
|
+
id: `${id}-option-${index}`,
|
|
971
|
+
onMouseMove: handleOptionMouseMove,
|
|
972
|
+
onClick: handleOptionClick,
|
|
973
|
+
onTouchStart: handleOptionTouchStart,
|
|
974
|
+
'data-option-index': index,
|
|
975
|
+
'aria-disabled': disabled,
|
|
976
|
+
'aria-selected': selected
|
|
977
|
+
};
|
|
978
|
+
},
|
|
979
|
+
id,
|
|
980
|
+
inputValue,
|
|
981
|
+
value,
|
|
982
|
+
dirty,
|
|
983
|
+
expanded: popupOpen && anchorEl,
|
|
984
|
+
popupOpen,
|
|
985
|
+
focused: focused || focusedTag !== -1,
|
|
986
|
+
anchorEl,
|
|
987
|
+
setAnchorEl,
|
|
988
|
+
focusedTag,
|
|
989
|
+
groupedOptions
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
var _default = exports.default = useAutocomplete;
|