1 | 'use client';
|
2 |
|
3 | import _formatMuiErrorMessage from "@mui/utils/formatMuiErrorMessage";
|
4 | var _span;
|
5 | import * as React from 'react';
|
6 | import { isFragment } from 'react-is';
|
7 | import PropTypes from 'prop-types';
|
8 | import clsx from 'clsx';
|
9 | import composeClasses from '@mui/utils/composeClasses';
|
10 | import useId from '@mui/utils/useId';
|
11 | import refType from '@mui/utils/refType';
|
12 | import ownerDocument from "../utils/ownerDocument.js";
|
13 | import capitalize from "../utils/capitalize.js";
|
14 | import Menu from "../Menu/Menu.js";
|
15 | import { StyledSelectSelect, StyledSelectIcon } from "../NativeSelect/NativeSelectInput.js";
|
16 | import { isFilled } from "../InputBase/utils.js";
|
17 | import { styled } from "../zero-styled/index.js";
|
18 | import slotShouldForwardProp from "../styles/slotShouldForwardProp.js";
|
19 | import useForkRef from "../utils/useForkRef.js";
|
20 | import useControlled from "../utils/useControlled.js";
|
21 | import selectClasses, { getSelectUtilityClasses } from "./selectClasses.js";
|
22 | import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
23 | const SelectSelect = styled(StyledSelectSelect, {
|
24 | name: 'MuiSelect',
|
25 | slot: 'Select',
|
26 | overridesResolver: (props, styles) => {
|
27 | const {
|
28 | ownerState
|
29 | } = props;
|
30 | return [
|
31 |
|
32 | {
|
33 | [`&.${selectClasses.select}`]: styles.select
|
34 | }, {
|
35 | [`&.${selectClasses.select}`]: styles[ownerState.variant]
|
36 | }, {
|
37 | [`&.${selectClasses.error}`]: styles.error
|
38 | }, {
|
39 | [`&.${selectClasses.multiple}`]: styles.multiple
|
40 | }];
|
41 | }
|
42 | })({
|
43 |
|
44 | [`&.${selectClasses.select}`]: {
|
45 | height: 'auto',
|
46 |
|
47 | minHeight: '1.4375em',
|
48 |
|
49 | textOverflow: 'ellipsis',
|
50 | whiteSpace: 'nowrap',
|
51 | overflow: 'hidden'
|
52 | }
|
53 | });
|
54 | const SelectIcon = styled(StyledSelectIcon, {
|
55 | name: 'MuiSelect',
|
56 | slot: 'Icon',
|
57 | overridesResolver: (props, styles) => {
|
58 | const {
|
59 | ownerState
|
60 | } = props;
|
61 | return [styles.icon, ownerState.variant && styles[`icon${capitalize(ownerState.variant)}`], ownerState.open && styles.iconOpen];
|
62 | }
|
63 | })({});
|
64 | const SelectNativeInput = styled('input', {
|
65 | shouldForwardProp: prop => slotShouldForwardProp(prop) && prop !== 'classes',
|
66 | name: 'MuiSelect',
|
67 | slot: 'NativeInput',
|
68 | overridesResolver: (props, styles) => styles.nativeInput
|
69 | })({
|
70 | bottom: 0,
|
71 | left: 0,
|
72 | position: 'absolute',
|
73 | opacity: 0,
|
74 | pointerEvents: 'none',
|
75 | width: '100%',
|
76 | boxSizing: 'border-box'
|
77 | });
|
78 | function areEqualValues(a, b) {
|
79 | if (typeof b === 'object' && b !== null) {
|
80 | return a === b;
|
81 | }
|
82 |
|
83 |
|
84 | return String(a) === String(b);
|
85 | }
|
86 | function isEmpty(display) {
|
87 | return display == null || typeof display === 'string' && !display.trim();
|
88 | }
|
89 | const useUtilityClasses = ownerState => {
|
90 | const {
|
91 | classes,
|
92 | variant,
|
93 | disabled,
|
94 | multiple,
|
95 | open,
|
96 | error
|
97 | } = ownerState;
|
98 | const slots = {
|
99 | select: ['select', variant, disabled && 'disabled', multiple && 'multiple', error && 'error'],
|
100 | icon: ['icon', `icon${capitalize(variant)}`, open && 'iconOpen', disabled && 'disabled'],
|
101 | nativeInput: ['nativeInput']
|
102 | };
|
103 | return composeClasses(slots, getSelectUtilityClasses, classes);
|
104 | };
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | const SelectInput = React.forwardRef(function SelectInput(props, ref) {
|
110 | const {
|
111 | 'aria-describedby': ariaDescribedby,
|
112 | 'aria-label': ariaLabel,
|
113 | autoFocus,
|
114 | autoWidth,
|
115 | children,
|
116 | className,
|
117 | defaultOpen,
|
118 | defaultValue,
|
119 | disabled,
|
120 | displayEmpty,
|
121 | error = false,
|
122 | IconComponent,
|
123 | inputRef: inputRefProp,
|
124 | labelId,
|
125 | MenuProps = {},
|
126 | multiple,
|
127 | name,
|
128 | onBlur,
|
129 | onChange,
|
130 | onClose,
|
131 | onFocus,
|
132 | onOpen,
|
133 | open: openProp,
|
134 | readOnly,
|
135 | renderValue,
|
136 | SelectDisplayProps = {},
|
137 | tabIndex: tabIndexProp,
|
138 |
|
139 | type,
|
140 | value: valueProp,
|
141 | variant = 'standard',
|
142 | ...other
|
143 | } = props;
|
144 | const [value, setValueState] = useControlled({
|
145 | controlled: valueProp,
|
146 | default: defaultValue,
|
147 | name: 'Select'
|
148 | });
|
149 | const [openState, setOpenState] = useControlled({
|
150 | controlled: openProp,
|
151 | default: defaultOpen,
|
152 | name: 'Select'
|
153 | });
|
154 | const inputRef = React.useRef(null);
|
155 | const displayRef = React.useRef(null);
|
156 | const [displayNode, setDisplayNode] = React.useState(null);
|
157 | const {
|
158 | current: isOpenControlled
|
159 | } = React.useRef(openProp != null);
|
160 | const [menuMinWidthState, setMenuMinWidthState] = React.useState();
|
161 | const handleRef = useForkRef(ref, inputRefProp);
|
162 | const handleDisplayRef = React.useCallback(node => {
|
163 | displayRef.current = node;
|
164 | if (node) {
|
165 | setDisplayNode(node);
|
166 | }
|
167 | }, []);
|
168 | const anchorElement = displayNode?.parentNode;
|
169 | React.useImperativeHandle(handleRef, () => ({
|
170 | focus: () => {
|
171 | displayRef.current.focus();
|
172 | },
|
173 | node: inputRef.current,
|
174 | value
|
175 | }), [value]);
|
176 |
|
177 |
|
178 | React.useEffect(() => {
|
179 | if (defaultOpen && openState && displayNode && !isOpenControlled) {
|
180 | setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
|
181 | displayRef.current.focus();
|
182 | }
|
183 |
|
184 |
|
185 | }, [displayNode, autoWidth]);
|
186 |
|
187 |
|
188 | React.useEffect(() => {
|
189 | if (autoFocus) {
|
190 | displayRef.current.focus();
|
191 | }
|
192 | }, [autoFocus]);
|
193 | React.useEffect(() => {
|
194 | if (!labelId) {
|
195 | return undefined;
|
196 | }
|
197 | const label = ownerDocument(displayRef.current).getElementById(labelId);
|
198 | if (label) {
|
199 | const handler = () => {
|
200 | if (getSelection().isCollapsed) {
|
201 | displayRef.current.focus();
|
202 | }
|
203 | };
|
204 | label.addEventListener('click', handler);
|
205 | return () => {
|
206 | label.removeEventListener('click', handler);
|
207 | };
|
208 | }
|
209 | return undefined;
|
210 | }, [labelId]);
|
211 | const update = (open, event) => {
|
212 | if (open) {
|
213 | if (onOpen) {
|
214 | onOpen(event);
|
215 | }
|
216 | } else if (onClose) {
|
217 | onClose(event);
|
218 | }
|
219 | if (!isOpenControlled) {
|
220 | setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
|
221 | setOpenState(open);
|
222 | }
|
223 | };
|
224 | const handleMouseDown = event => {
|
225 |
|
226 | if (event.button !== 0) {
|
227 | return;
|
228 | }
|
229 |
|
230 | event.preventDefault();
|
231 | displayRef.current.focus();
|
232 | update(true, event);
|
233 | };
|
234 | const handleClose = event => {
|
235 | update(false, event);
|
236 | };
|
237 | const childrenArray = React.Children.toArray(children);
|
238 |
|
239 |
|
240 | const handleChange = event => {
|
241 | const child = childrenArray.find(childItem => childItem.props.value === event.target.value);
|
242 | if (child === undefined) {
|
243 | return;
|
244 | }
|
245 | setValueState(child.props.value);
|
246 | if (onChange) {
|
247 | onChange(event, child);
|
248 | }
|
249 | };
|
250 | const handleItemClick = child => event => {
|
251 | let newValue;
|
252 |
|
253 |
|
254 | if (!event.currentTarget.hasAttribute('tabindex')) {
|
255 | return;
|
256 | }
|
257 | if (multiple) {
|
258 | newValue = Array.isArray(value) ? value.slice() : [];
|
259 | const itemIndex = value.indexOf(child.props.value);
|
260 | if (itemIndex === -1) {
|
261 | newValue.push(child.props.value);
|
262 | } else {
|
263 | newValue.splice(itemIndex, 1);
|
264 | }
|
265 | } else {
|
266 | newValue = child.props.value;
|
267 | }
|
268 | if (child.props.onClick) {
|
269 | child.props.onClick(event);
|
270 | }
|
271 | if (value !== newValue) {
|
272 | setValueState(newValue);
|
273 | if (onChange) {
|
274 |
|
275 |
|
276 |
|
277 |
|
278 | const nativeEvent = event.nativeEvent || event;
|
279 | const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
|
280 | Object.defineProperty(clonedEvent, 'target', {
|
281 | writable: true,
|
282 | value: {
|
283 | value: newValue,
|
284 | name
|
285 | }
|
286 | });
|
287 | onChange(clonedEvent, child);
|
288 | }
|
289 | }
|
290 | if (!multiple) {
|
291 | update(false, event);
|
292 | }
|
293 | };
|
294 | const handleKeyDown = event => {
|
295 | if (!readOnly) {
|
296 | const validKeys = [' ', 'ArrowUp', 'ArrowDown',
|
297 |
|
298 |
|
299 | 'Enter'];
|
300 | if (validKeys.includes(event.key)) {
|
301 | event.preventDefault();
|
302 | update(true, event);
|
303 | }
|
304 | }
|
305 | };
|
306 | const open = displayNode !== null && openState;
|
307 | const handleBlur = event => {
|
308 |
|
309 | if (!open && onBlur) {
|
310 |
|
311 | Object.defineProperty(event, 'target', {
|
312 | writable: true,
|
313 | value: {
|
314 | value,
|
315 | name
|
316 | }
|
317 | });
|
318 | onBlur(event);
|
319 | }
|
320 | };
|
321 | delete other['aria-invalid'];
|
322 | let display;
|
323 | let displaySingle;
|
324 | const displayMultiple = [];
|
325 | let computeDisplay = false;
|
326 | let foundMatch = false;
|
327 |
|
328 |
|
329 | if (isFilled({
|
330 | value
|
331 | }) || displayEmpty) {
|
332 | if (renderValue) {
|
333 | display = renderValue(value);
|
334 | } else {
|
335 | computeDisplay = true;
|
336 | }
|
337 | }
|
338 | const items = childrenArray.map(child => {
|
339 | if (! React.isValidElement(child)) {
|
340 | return null;
|
341 | }
|
342 | if (process.env.NODE_ENV !== 'production') {
|
343 | if (isFragment(child)) {
|
344 | console.error(["MUI: The Select component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
|
345 | }
|
346 | }
|
347 | let selected;
|
348 | if (multiple) {
|
349 | if (!Array.isArray(value)) {
|
350 | throw new Error(process.env.NODE_ENV !== "production" ? 'MUI: The `value` prop must be an array ' + 'when using the `Select` component with `multiple`.' : _formatMuiErrorMessage(2));
|
351 | }
|
352 | selected = value.some(v => areEqualValues(v, child.props.value));
|
353 | if (selected && computeDisplay) {
|
354 | displayMultiple.push(child.props.children);
|
355 | }
|
356 | } else {
|
357 | selected = areEqualValues(value, child.props.value);
|
358 | if (selected && computeDisplay) {
|
359 | displaySingle = child.props.children;
|
360 | }
|
361 | }
|
362 | if (selected) {
|
363 | foundMatch = true;
|
364 | }
|
365 | return React.cloneElement(child, {
|
366 | 'aria-selected': selected ? 'true' : 'false',
|
367 | onClick: handleItemClick(child),
|
368 | onKeyUp: event => {
|
369 | if (event.key === ' ') {
|
370 |
|
371 |
|
372 |
|
373 | event.preventDefault();
|
374 | }
|
375 | if (child.props.onKeyUp) {
|
376 | child.props.onKeyUp(event);
|
377 | }
|
378 | },
|
379 | role: 'option',
|
380 | selected,
|
381 | value: undefined,
|
382 |
|
383 | 'data-value': child.props.value
|
384 | });
|
385 | });
|
386 | if (process.env.NODE_ENV !== 'production') {
|
387 |
|
388 |
|
389 | React.useEffect(() => {
|
390 | if (!foundMatch && !multiple && value !== '') {
|
391 | const values = childrenArray.map(child => child.props.value);
|
392 | console.warn([`MUI: You have provided an out-of-range value \`${value}\` for the select ${name ? `(name="${name}") ` : ''}component.`, "Consider providing a value that matches one of the available options or ''.", `The available values are ${values.filter(x => x != null).map(x => `\`${x}\``).join(', ') || '""'}.`].join('\n'));
|
393 | }
|
394 | }, [foundMatch, childrenArray, multiple, name, value]);
|
395 | }
|
396 | if (computeDisplay) {
|
397 | if (multiple) {
|
398 | if (displayMultiple.length === 0) {
|
399 | display = null;
|
400 | } else {
|
401 | display = displayMultiple.reduce((output, child, index) => {
|
402 | output.push(child);
|
403 | if (index < displayMultiple.length - 1) {
|
404 | output.push(', ');
|
405 | }
|
406 | return output;
|
407 | }, []);
|
408 | }
|
409 | } else {
|
410 | display = displaySingle;
|
411 | }
|
412 | }
|
413 |
|
414 |
|
415 | let menuMinWidth = menuMinWidthState;
|
416 | if (!autoWidth && isOpenControlled && displayNode) {
|
417 | menuMinWidth = anchorElement.clientWidth;
|
418 | }
|
419 | let tabIndex;
|
420 | if (typeof tabIndexProp !== 'undefined') {
|
421 | tabIndex = tabIndexProp;
|
422 | } else {
|
423 | tabIndex = disabled ? null : 0;
|
424 | }
|
425 | const buttonId = SelectDisplayProps.id || (name ? `mui-component-select-${name}` : undefined);
|
426 | const ownerState = {
|
427 | ...props,
|
428 | variant,
|
429 | value,
|
430 | open,
|
431 | error
|
432 | };
|
433 | const classes = useUtilityClasses(ownerState);
|
434 | const paperProps = {
|
435 | ...MenuProps.PaperProps,
|
436 | ...MenuProps.slotProps?.paper
|
437 | };
|
438 | const listboxId = useId();
|
439 | return _jsxs(React.Fragment, {
|
440 | children: [_jsx(SelectSelect, {
|
441 | as: "div",
|
442 | ref: handleDisplayRef,
|
443 | tabIndex: tabIndex,
|
444 | role: "combobox",
|
445 | "aria-controls": listboxId,
|
446 | "aria-disabled": disabled ? 'true' : undefined,
|
447 | "aria-expanded": open ? 'true' : 'false',
|
448 | "aria-haspopup": "listbox",
|
449 | "aria-label": ariaLabel,
|
450 | "aria-labelledby": [labelId, buttonId].filter(Boolean).join(' ') || undefined,
|
451 | "aria-describedby": ariaDescribedby,
|
452 | onKeyDown: handleKeyDown,
|
453 | onMouseDown: disabled || readOnly ? null : handleMouseDown,
|
454 | onBlur: handleBlur,
|
455 | onFocus: onFocus,
|
456 | ...SelectDisplayProps,
|
457 | ownerState: ownerState,
|
458 | className: clsx(SelectDisplayProps.className, classes.select, className)
|
459 |
|
460 | ,
|
461 | id: buttonId,
|
462 | children: isEmpty(display) ?
|
463 | _span || (_span = _jsx("span", {
|
464 | className: "notranslate",
|
465 | children: "\u200B"
|
466 | })) : display
|
467 | }), _jsx(SelectNativeInput, {
|
468 | "aria-invalid": error,
|
469 | value: Array.isArray(value) ? value.join(',') : value,
|
470 | name: name,
|
471 | ref: inputRef,
|
472 | "aria-hidden": true,
|
473 | onChange: handleChange,
|
474 | tabIndex: -1,
|
475 | disabled: disabled,
|
476 | className: classes.nativeInput,
|
477 | autoFocus: autoFocus,
|
478 | ...other,
|
479 | ownerState: ownerState
|
480 | }), _jsx(SelectIcon, {
|
481 | as: IconComponent,
|
482 | className: classes.icon,
|
483 | ownerState: ownerState
|
484 | }), _jsx(Menu, {
|
485 | id: `menu-${name || ''}`,
|
486 | anchorEl: anchorElement,
|
487 | open: open,
|
488 | onClose: handleClose,
|
489 | anchorOrigin: {
|
490 | vertical: 'bottom',
|
491 | horizontal: 'center'
|
492 | },
|
493 | transformOrigin: {
|
494 | vertical: 'top',
|
495 | horizontal: 'center'
|
496 | },
|
497 | ...MenuProps,
|
498 | MenuListProps: {
|
499 | 'aria-labelledby': labelId,
|
500 | role: 'listbox',
|
501 | 'aria-multiselectable': multiple ? 'true' : undefined,
|
502 | disableListWrap: true,
|
503 | id: listboxId,
|
504 | ...MenuProps.MenuListProps
|
505 | },
|
506 | slotProps: {
|
507 | ...MenuProps.slotProps,
|
508 | paper: {
|
509 | ...paperProps,
|
510 | style: {
|
511 | minWidth: menuMinWidth,
|
512 | ...(paperProps != null ? paperProps.style : null)
|
513 | }
|
514 | }
|
515 | },
|
516 | children: items
|
517 | })]
|
518 | });
|
519 | });
|
520 | process.env.NODE_ENV !== "production" ? SelectInput.propTypes = {
|
521 | |
522 |
|
523 |
|
524 | 'aria-describedby': PropTypes.string,
|
525 | |
526 |
|
527 |
|
528 | 'aria-label': PropTypes.string,
|
529 | |
530 |
|
531 |
|
532 | autoFocus: PropTypes.bool,
|
533 | |
534 |
|
535 |
|
536 |
|
537 | autoWidth: PropTypes.bool,
|
538 | |
539 |
|
540 |
|
541 |
|
542 | children: PropTypes.node,
|
543 | |
544 |
|
545 |
|
546 | classes: PropTypes.object,
|
547 | |
548 |
|
549 |
|
550 | className: PropTypes.string,
|
551 | |
552 |
|
553 |
|
554 |
|
555 | defaultOpen: PropTypes.bool,
|
556 | |
557 |
|
558 |
|
559 | defaultValue: PropTypes.any,
|
560 | |
561 |
|
562 |
|
563 | disabled: PropTypes.bool,
|
564 | |
565 |
|
566 |
|
567 | displayEmpty: PropTypes.bool,
|
568 | |
569 |
|
570 |
|
571 | error: PropTypes.bool,
|
572 | |
573 |
|
574 |
|
575 | IconComponent: PropTypes.elementType.isRequired,
|
576 | |
577 |
|
578 |
|
579 |
|
580 | inputRef: refType,
|
581 | |
582 |
|
583 |
|
584 |
|
585 | labelId: PropTypes.string,
|
586 | |
587 |
|
588 |
|
589 | MenuProps: PropTypes.object,
|
590 | |
591 |
|
592 |
|
593 | multiple: PropTypes.bool,
|
594 | |
595 |
|
596 |
|
597 | name: PropTypes.string,
|
598 | |
599 |
|
600 |
|
601 | onBlur: PropTypes.func,
|
602 | |
603 |
|
604 |
|
605 |
|
606 |
|
607 |
|
608 |
|
609 | onChange: PropTypes.func,
|
610 | |
611 |
|
612 |
|
613 |
|
614 |
|
615 |
|
616 | onClose: PropTypes.func,
|
617 | |
618 |
|
619 |
|
620 | onFocus: PropTypes.func,
|
621 | |
622 |
|
623 |
|
624 |
|
625 |
|
626 |
|
627 | onOpen: PropTypes.func,
|
628 | |
629 |
|
630 |
|
631 | open: PropTypes.bool,
|
632 | |
633 |
|
634 |
|
635 | readOnly: PropTypes.bool,
|
636 | |
637 |
|
638 |
|
639 |
|
640 |
|
641 |
|
642 | renderValue: PropTypes.func,
|
643 | |
644 |
|
645 |
|
646 | SelectDisplayProps: PropTypes.object,
|
647 | |
648 |
|
649 |
|
650 | tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
651 | |
652 |
|
653 |
|
654 | type: PropTypes.any,
|
655 | |
656 |
|
657 |
|
658 | value: PropTypes.any,
|
659 | |
660 |
|
661 |
|
662 | variant: PropTypes.oneOf(['standard', 'outlined', 'filled'])
|
663 | } : void 0;
|
664 | export default SelectInput; |
\ | No newline at end of file |