1 | import _extends from "@babel/runtime/helpers/esm/extends";
|
2 | import * as React from 'react';
|
3 | import { unstable_useForkRef as useForkRef, unstable_useId as useId, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
|
4 | import useButton from '../useButton';
|
5 | import { SelectActionTypes } from './useSelect.types';
|
6 | import useList from '../useList';
|
7 | import defaultOptionStringifier from './defaultOptionStringifier';
|
8 | import { useCompoundParent } from '../utils/useCompound';
|
9 | import selectReducer from './selectReducer';
|
10 | import combineHooksSlotProps from '../utils/combineHooksSlotProps';
|
11 | function preventDefault(event) {
|
12 | event.preventDefault();
|
13 | }
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | function useSelect(props) {
|
26 | const {
|
27 | buttonRef: buttonRefProp,
|
28 | defaultOpen = false,
|
29 | defaultValue: defaultValueProp,
|
30 | disabled = false,
|
31 | listboxId: listboxIdProp,
|
32 | listboxRef: listboxRefProp,
|
33 | multiple = false,
|
34 | onChange,
|
35 | onHighlightChange,
|
36 | onOpenChange,
|
37 | open: openProp,
|
38 | options: optionsParam,
|
39 | getOptionAsString = defaultOptionStringifier,
|
40 | value: valueProp
|
41 | } = props;
|
42 | const buttonRef = React.useRef(null);
|
43 | const handleButtonRef = useForkRef(buttonRefProp, buttonRef);
|
44 | const listboxRef = React.useRef(null);
|
45 | const listboxId = useId(listboxIdProp);
|
46 | let defaultValue;
|
47 | if (valueProp === undefined && defaultValueProp === undefined) {
|
48 | defaultValue = [];
|
49 | } else if (defaultValueProp !== undefined) {
|
50 | if (multiple) {
|
51 | defaultValue = defaultValueProp;
|
52 | } else {
|
53 | defaultValue = defaultValueProp == null ? [] : [defaultValueProp];
|
54 | }
|
55 | }
|
56 | const value = React.useMemo(() => {
|
57 | if (valueProp !== undefined) {
|
58 | if (multiple) {
|
59 | return valueProp;
|
60 | }
|
61 | return valueProp == null ? [] : [valueProp];
|
62 | }
|
63 | return undefined;
|
64 | }, [valueProp, multiple]);
|
65 | const {
|
66 | subitems,
|
67 | contextValue: compoundComponentContextValue
|
68 | } = useCompoundParent();
|
69 | const options = React.useMemo(() => {
|
70 | if (optionsParam != null) {
|
71 | return new Map(optionsParam.map((option, index) => [option.value, {
|
72 | value: option.value,
|
73 | label: option.label,
|
74 | disabled: option.disabled,
|
75 | ref: React.createRef(),
|
76 | id: `${listboxId}_${index}`
|
77 | }]));
|
78 | }
|
79 | return subitems;
|
80 | }, [optionsParam, subitems, listboxId]);
|
81 | const handleListboxRef = useForkRef(listboxRefProp, listboxRef);
|
82 | const {
|
83 | getRootProps: getButtonRootProps,
|
84 | active: buttonActive,
|
85 | focusVisible: buttonFocusVisible,
|
86 | rootRef: mergedButtonRef
|
87 | } = useButton({
|
88 | disabled,
|
89 | rootRef: handleButtonRef
|
90 | });
|
91 | const optionValues = React.useMemo(() => Array.from(options.keys()), [options]);
|
92 | const isItemDisabled = React.useCallback(valueToCheck => {
|
93 | const option = options.get(valueToCheck);
|
94 | return option?.disabled ?? false;
|
95 | }, [options]);
|
96 | const stringifyOption = React.useCallback(valueToCheck => {
|
97 | const option = options.get(valueToCheck);
|
98 | if (!option) {
|
99 | return '';
|
100 | }
|
101 | return getOptionAsString(option);
|
102 | }, [options, getOptionAsString]);
|
103 | const controlledState = React.useMemo(() => ({
|
104 | selectedValues: value,
|
105 | open: openProp
|
106 | }), [value, openProp]);
|
107 | const getItemId = React.useCallback(itemValue => options.get(itemValue)?.id, [options]);
|
108 | const handleSelectionChange = React.useCallback((event, newValues) => {
|
109 | if (multiple) {
|
110 | onChange?.(event, newValues);
|
111 | } else {
|
112 | onChange?.(event, newValues[0] ?? null);
|
113 | }
|
114 | }, [multiple, onChange]);
|
115 | const handleHighlightChange = React.useCallback((event, newValue) => {
|
116 | onHighlightChange?.(event, newValue ?? null);
|
117 | }, [onHighlightChange]);
|
118 | const handleStateChange = React.useCallback((event, field, fieldValue) => {
|
119 | if (field === 'open') {
|
120 | onOpenChange?.(fieldValue);
|
121 | if (fieldValue === false && event?.type !== 'blur') {
|
122 | buttonRef.current?.focus();
|
123 | }
|
124 | }
|
125 | }, [onOpenChange]);
|
126 | const useListParameters = {
|
127 | getInitialState: () => ({
|
128 | highlightedValue: null,
|
129 | selectedValues: defaultValue ?? [],
|
130 | open: defaultOpen
|
131 | }),
|
132 | getItemId,
|
133 | controlledProps: controlledState,
|
134 | isItemDisabled,
|
135 | rootRef: mergedButtonRef,
|
136 | onChange: handleSelectionChange,
|
137 | onHighlightChange: handleHighlightChange,
|
138 | onStateChange: handleStateChange,
|
139 | reducerActionContext: React.useMemo(() => ({
|
140 | multiple
|
141 | }), [multiple]),
|
142 | items: optionValues,
|
143 | getItemAsString: stringifyOption,
|
144 | selectionMode: multiple ? 'multiple' : 'single',
|
145 | stateReducer: selectReducer
|
146 | };
|
147 | const {
|
148 | dispatch,
|
149 | getRootProps: getListboxRootProps,
|
150 | contextValue: listContextValue,
|
151 | state: {
|
152 | open,
|
153 | highlightedValue: highlightedOption,
|
154 | selectedValues: selectedOptions
|
155 | },
|
156 | rootRef: mergedListRootRef
|
157 | } = useList(useListParameters);
|
158 | const createHandleButtonClick = otherHandlers => event => {
|
159 | otherHandlers?.onClick?.(event);
|
160 | if (!event.defaultMuiPrevented) {
|
161 | const action = {
|
162 | type: SelectActionTypes.buttonClick,
|
163 | event
|
164 | };
|
165 | dispatch(action);
|
166 | }
|
167 | };
|
168 | useEnhancedEffect(() => {
|
169 |
|
170 | if (highlightedOption != null) {
|
171 | const optionRef = options.get(highlightedOption)?.ref;
|
172 | if (!listboxRef.current || !optionRef?.current) {
|
173 | return;
|
174 | }
|
175 | const listboxClientRect = listboxRef.current.getBoundingClientRect();
|
176 | const optionClientRect = optionRef.current.getBoundingClientRect();
|
177 | if (optionClientRect.top < listboxClientRect.top) {
|
178 | listboxRef.current.scrollTop -= listboxClientRect.top - optionClientRect.top;
|
179 | } else if (optionClientRect.bottom > listboxClientRect.bottom) {
|
180 | listboxRef.current.scrollTop += optionClientRect.bottom - listboxClientRect.bottom;
|
181 | }
|
182 | }
|
183 | }, [highlightedOption, options]);
|
184 | const getOptionMetadata = React.useCallback(optionValue => options.get(optionValue), [options]);
|
185 | const getSelectTriggerProps = (otherHandlers = {}) => {
|
186 | return _extends({}, otherHandlers, {
|
187 | onClick: createHandleButtonClick(otherHandlers),
|
188 | ref: mergedListRootRef,
|
189 | role: 'combobox',
|
190 | 'aria-expanded': open,
|
191 | 'aria-controls': listboxId
|
192 | });
|
193 | };
|
194 | const getButtonProps = (otherHandlers = {}) => {
|
195 | const listboxAndButtonProps = combineHooksSlotProps(getButtonRootProps, getListboxRootProps);
|
196 | const combinedProps = combineHooksSlotProps(listboxAndButtonProps, getSelectTriggerProps);
|
197 | return combinedProps(otherHandlers);
|
198 | };
|
199 | const getListboxProps = (otherHandlers = {}) => {
|
200 | return _extends({}, otherHandlers, {
|
201 | id: listboxId,
|
202 | role: 'listbox',
|
203 | 'aria-multiselectable': multiple ? 'true' : undefined,
|
204 | ref: handleListboxRef,
|
205 | onMouseDown: preventDefault
|
206 | });
|
207 | };
|
208 |
|
209 | React.useDebugValue({
|
210 | selectedOptions,
|
211 | highlightedOption,
|
212 | open
|
213 | });
|
214 | const contextValue = React.useMemo(() => _extends({}, listContextValue, compoundComponentContextValue), [listContextValue, compoundComponentContextValue]);
|
215 | let selectValue;
|
216 | if (props.multiple) {
|
217 | selectValue = selectedOptions;
|
218 | } else {
|
219 | selectValue = selectedOptions.length > 0 ? selectedOptions[0] : null;
|
220 | }
|
221 | return {
|
222 | buttonActive,
|
223 | buttonFocusVisible,
|
224 | buttonRef: mergedButtonRef,
|
225 | contextValue,
|
226 | disabled,
|
227 | dispatch,
|
228 | getButtonProps,
|
229 | getListboxProps,
|
230 | getOptionMetadata,
|
231 | listboxRef: mergedListRootRef,
|
232 | open,
|
233 | options: optionValues,
|
234 | value: selectValue,
|
235 | highlightedOption
|
236 | };
|
237 | }
|
238 | export default useSelect; |
\ | No newline at end of file |