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 | required,
|
137 | SelectDisplayProps = {},
|
138 | tabIndex: tabIndexProp,
|
139 |
|
140 | type,
|
141 | value: valueProp,
|
142 | variant = 'standard',
|
143 | ...other
|
144 | } = props;
|
145 | const [value, setValueState] = useControlled({
|
146 | controlled: valueProp,
|
147 | default: defaultValue,
|
148 | name: 'Select'
|
149 | });
|
150 | const [openState, setOpenState] = useControlled({
|
151 | controlled: openProp,
|
152 | default: defaultOpen,
|
153 | name: 'Select'
|
154 | });
|
155 | const inputRef = React.useRef(null);
|
156 | const displayRef = React.useRef(null);
|
157 | const [displayNode, setDisplayNode] = React.useState(null);
|
158 | const {
|
159 | current: isOpenControlled
|
160 | } = React.useRef(openProp != null);
|
161 | const [menuMinWidthState, setMenuMinWidthState] = React.useState();
|
162 | const handleRef = useForkRef(ref, inputRefProp);
|
163 | const handleDisplayRef = React.useCallback(node => {
|
164 | displayRef.current = node;
|
165 | if (node) {
|
166 | setDisplayNode(node);
|
167 | }
|
168 | }, []);
|
169 | const anchorElement = displayNode?.parentNode;
|
170 | React.useImperativeHandle(handleRef, () => ({
|
171 | focus: () => {
|
172 | displayRef.current.focus();
|
173 | },
|
174 | node: inputRef.current,
|
175 | value
|
176 | }), [value]);
|
177 |
|
178 |
|
179 | React.useEffect(() => {
|
180 | if (defaultOpen && openState && displayNode && !isOpenControlled) {
|
181 | setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
|
182 | displayRef.current.focus();
|
183 | }
|
184 |
|
185 |
|
186 | }, [displayNode, autoWidth]);
|
187 |
|
188 |
|
189 | React.useEffect(() => {
|
190 | if (autoFocus) {
|
191 | displayRef.current.focus();
|
192 | }
|
193 | }, [autoFocus]);
|
194 | React.useEffect(() => {
|
195 | if (!labelId) {
|
196 | return undefined;
|
197 | }
|
198 | const label = ownerDocument(displayRef.current).getElementById(labelId);
|
199 | if (label) {
|
200 | const handler = () => {
|
201 | if (getSelection().isCollapsed) {
|
202 | displayRef.current.focus();
|
203 | }
|
204 | };
|
205 | label.addEventListener('click', handler);
|
206 | return () => {
|
207 | label.removeEventListener('click', handler);
|
208 | };
|
209 | }
|
210 | return undefined;
|
211 | }, [labelId]);
|
212 | const update = (open, event) => {
|
213 | if (open) {
|
214 | if (onOpen) {
|
215 | onOpen(event);
|
216 | }
|
217 | } else if (onClose) {
|
218 | onClose(event);
|
219 | }
|
220 | if (!isOpenControlled) {
|
221 | setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
|
222 | setOpenState(open);
|
223 | }
|
224 | };
|
225 | const handleMouseDown = event => {
|
226 |
|
227 | if (event.button !== 0) {
|
228 | return;
|
229 | }
|
230 |
|
231 | event.preventDefault();
|
232 | displayRef.current.focus();
|
233 | update(true, event);
|
234 | };
|
235 | const handleClose = event => {
|
236 | update(false, event);
|
237 | };
|
238 | const childrenArray = React.Children.toArray(children);
|
239 |
|
240 |
|
241 | const handleChange = event => {
|
242 | const child = childrenArray.find(childItem => childItem.props.value === event.target.value);
|
243 | if (child === undefined) {
|
244 | return;
|
245 | }
|
246 | setValueState(child.props.value);
|
247 | if (onChange) {
|
248 | onChange(event, child);
|
249 | }
|
250 | };
|
251 | const handleItemClick = child => event => {
|
252 | let newValue;
|
253 |
|
254 |
|
255 | if (!event.currentTarget.hasAttribute('tabindex')) {
|
256 | return;
|
257 | }
|
258 | if (multiple) {
|
259 | newValue = Array.isArray(value) ? value.slice() : [];
|
260 | const itemIndex = value.indexOf(child.props.value);
|
261 | if (itemIndex === -1) {
|
262 | newValue.push(child.props.value);
|
263 | } else {
|
264 | newValue.splice(itemIndex, 1);
|
265 | }
|
266 | } else {
|
267 | newValue = child.props.value;
|
268 | }
|
269 | if (child.props.onClick) {
|
270 | child.props.onClick(event);
|
271 | }
|
272 | if (value !== newValue) {
|
273 | setValueState(newValue);
|
274 | if (onChange) {
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | const nativeEvent = event.nativeEvent || event;
|
280 | const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
|
281 | Object.defineProperty(clonedEvent, 'target', {
|
282 | writable: true,
|
283 | value: {
|
284 | value: newValue,
|
285 | name
|
286 | }
|
287 | });
|
288 | onChange(clonedEvent, child);
|
289 | }
|
290 | }
|
291 | if (!multiple) {
|
292 | update(false, event);
|
293 | }
|
294 | };
|
295 | const handleKeyDown = event => {
|
296 | if (!readOnly) {
|
297 | const validKeys = [' ', 'ArrowUp', 'ArrowDown',
|
298 |
|
299 |
|
300 | 'Enter'];
|
301 | if (validKeys.includes(event.key)) {
|
302 | event.preventDefault();
|
303 | update(true, event);
|
304 | }
|
305 | }
|
306 | };
|
307 | const open = displayNode !== null && openState;
|
308 | const handleBlur = event => {
|
309 |
|
310 | if (!open && onBlur) {
|
311 |
|
312 | Object.defineProperty(event, 'target', {
|
313 | writable: true,
|
314 | value: {
|
315 | value,
|
316 | name
|
317 | }
|
318 | });
|
319 | onBlur(event);
|
320 | }
|
321 | };
|
322 | delete other['aria-invalid'];
|
323 | let display;
|
324 | let displaySingle;
|
325 | const displayMultiple = [];
|
326 | let computeDisplay = false;
|
327 | let foundMatch = false;
|
328 |
|
329 |
|
330 | if (isFilled({
|
331 | value
|
332 | }) || displayEmpty) {
|
333 | if (renderValue) {
|
334 | display = renderValue(value);
|
335 | } else {
|
336 | computeDisplay = true;
|
337 | }
|
338 | }
|
339 | const items = childrenArray.map(child => {
|
340 | if (! React.isValidElement(child)) {
|
341 | return null;
|
342 | }
|
343 | if (process.env.NODE_ENV !== 'production') {
|
344 | if (isFragment(child)) {
|
345 | console.error(["MUI: The Select component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
|
346 | }
|
347 | }
|
348 | let selected;
|
349 | if (multiple) {
|
350 | if (!Array.isArray(value)) {
|
351 | 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));
|
352 | }
|
353 | selected = value.some(v => areEqualValues(v, child.props.value));
|
354 | if (selected && computeDisplay) {
|
355 | displayMultiple.push(child.props.children);
|
356 | }
|
357 | } else {
|
358 | selected = areEqualValues(value, child.props.value);
|
359 | if (selected && computeDisplay) {
|
360 | displaySingle = child.props.children;
|
361 | }
|
362 | }
|
363 | if (selected) {
|
364 | foundMatch = true;
|
365 | }
|
366 | return React.cloneElement(child, {
|
367 | 'aria-selected': selected ? 'true' : 'false',
|
368 | onClick: handleItemClick(child),
|
369 | onKeyUp: event => {
|
370 | if (event.key === ' ') {
|
371 |
|
372 |
|
373 |
|
374 | event.preventDefault();
|
375 | }
|
376 | if (child.props.onKeyUp) {
|
377 | child.props.onKeyUp(event);
|
378 | }
|
379 | },
|
380 | role: 'option',
|
381 | selected,
|
382 | value: undefined,
|
383 |
|
384 | 'data-value': child.props.value
|
385 | });
|
386 | });
|
387 | if (process.env.NODE_ENV !== 'production') {
|
388 |
|
389 |
|
390 | React.useEffect(() => {
|
391 | if (!foundMatch && !multiple && value !== '') {
|
392 | const values = childrenArray.map(child => child.props.value);
|
393 | 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'));
|
394 | }
|
395 | }, [foundMatch, childrenArray, multiple, name, value]);
|
396 | }
|
397 | if (computeDisplay) {
|
398 | if (multiple) {
|
399 | if (displayMultiple.length === 0) {
|
400 | display = null;
|
401 | } else {
|
402 | display = displayMultiple.reduce((output, child, index) => {
|
403 | output.push(child);
|
404 | if (index < displayMultiple.length - 1) {
|
405 | output.push(', ');
|
406 | }
|
407 | return output;
|
408 | }, []);
|
409 | }
|
410 | } else {
|
411 | display = displaySingle;
|
412 | }
|
413 | }
|
414 |
|
415 |
|
416 | let menuMinWidth = menuMinWidthState;
|
417 | if (!autoWidth && isOpenControlled && displayNode) {
|
418 | menuMinWidth = anchorElement.clientWidth;
|
419 | }
|
420 | let tabIndex;
|
421 | if (typeof tabIndexProp !== 'undefined') {
|
422 | tabIndex = tabIndexProp;
|
423 | } else {
|
424 | tabIndex = disabled ? null : 0;
|
425 | }
|
426 | const buttonId = SelectDisplayProps.id || (name ? `mui-component-select-${name}` : undefined);
|
427 | const ownerState = {
|
428 | ...props,
|
429 | variant,
|
430 | value,
|
431 | open,
|
432 | error
|
433 | };
|
434 | const classes = useUtilityClasses(ownerState);
|
435 | const paperProps = {
|
436 | ...MenuProps.PaperProps,
|
437 | ...MenuProps.slotProps?.paper
|
438 | };
|
439 | const listboxId = useId();
|
440 | return _jsxs(React.Fragment, {
|
441 | children: [_jsx(SelectSelect, {
|
442 | as: "div",
|
443 | ref: handleDisplayRef,
|
444 | tabIndex: tabIndex,
|
445 | role: "combobox",
|
446 | "aria-controls": open ? listboxId : undefined,
|
447 | "aria-disabled": disabled ? 'true' : undefined,
|
448 | "aria-expanded": open ? 'true' : 'false',
|
449 | "aria-haspopup": "listbox",
|
450 | "aria-label": ariaLabel,
|
451 | "aria-labelledby": [labelId, buttonId].filter(Boolean).join(' ') || undefined,
|
452 | "aria-describedby": ariaDescribedby,
|
453 | "aria-required": required ? 'true' : undefined,
|
454 | "aria-invalid": error ? 'true' : undefined,
|
455 | onKeyDown: handleKeyDown,
|
456 | onMouseDown: disabled || readOnly ? null : handleMouseDown,
|
457 | onBlur: handleBlur,
|
458 | onFocus: onFocus,
|
459 | ...SelectDisplayProps,
|
460 | ownerState: ownerState,
|
461 | className: clsx(SelectDisplayProps.className, classes.select, className)
|
462 |
|
463 | ,
|
464 | id: buttonId,
|
465 | children: isEmpty(display) ?
|
466 | _span || (_span = _jsx("span", {
|
467 | className: "notranslate",
|
468 | "aria-hidden": true,
|
469 | children: "\u200B"
|
470 | })) : display
|
471 | }), _jsx(SelectNativeInput, {
|
472 | "aria-invalid": error,
|
473 | value: Array.isArray(value) ? value.join(',') : value,
|
474 | name: name,
|
475 | ref: inputRef,
|
476 | "aria-hidden": true,
|
477 | onChange: handleChange,
|
478 | tabIndex: -1,
|
479 | disabled: disabled,
|
480 | className: classes.nativeInput,
|
481 | autoFocus: autoFocus,
|
482 | required: required,
|
483 | ...other,
|
484 | ownerState: ownerState
|
485 | }), _jsx(SelectIcon, {
|
486 | as: IconComponent,
|
487 | className: classes.icon,
|
488 | ownerState: ownerState
|
489 | }), _jsx(Menu, {
|
490 | id: `menu-${name || ''}`,
|
491 | anchorEl: anchorElement,
|
492 | open: open,
|
493 | onClose: handleClose,
|
494 | anchorOrigin: {
|
495 | vertical: 'bottom',
|
496 | horizontal: 'center'
|
497 | },
|
498 | transformOrigin: {
|
499 | vertical: 'top',
|
500 | horizontal: 'center'
|
501 | },
|
502 | ...MenuProps,
|
503 | MenuListProps: {
|
504 | 'aria-labelledby': labelId,
|
505 | role: 'listbox',
|
506 | 'aria-multiselectable': multiple ? 'true' : undefined,
|
507 | disableListWrap: true,
|
508 | id: listboxId,
|
509 | ...MenuProps.MenuListProps
|
510 | },
|
511 | slotProps: {
|
512 | ...MenuProps.slotProps,
|
513 | paper: {
|
514 | ...paperProps,
|
515 | style: {
|
516 | minWidth: menuMinWidth,
|
517 | ...(paperProps != null ? paperProps.style : null)
|
518 | }
|
519 | }
|
520 | },
|
521 | children: items
|
522 | })]
|
523 | });
|
524 | });
|
525 | process.env.NODE_ENV !== "production" ? SelectInput.propTypes = {
|
526 | |
527 |
|
528 |
|
529 | 'aria-describedby': PropTypes.string,
|
530 | |
531 |
|
532 |
|
533 | 'aria-label': PropTypes.string,
|
534 | |
535 |
|
536 |
|
537 | autoFocus: PropTypes.bool,
|
538 | |
539 |
|
540 |
|
541 |
|
542 | autoWidth: PropTypes.bool,
|
543 | |
544 |
|
545 |
|
546 |
|
547 | children: PropTypes.node,
|
548 | |
549 |
|
550 |
|
551 | classes: PropTypes.object,
|
552 | |
553 |
|
554 |
|
555 | className: PropTypes.string,
|
556 | |
557 |
|
558 |
|
559 |
|
560 | defaultOpen: PropTypes.bool,
|
561 | |
562 |
|
563 |
|
564 | defaultValue: PropTypes.any,
|
565 | |
566 |
|
567 |
|
568 | disabled: PropTypes.bool,
|
569 | |
570 |
|
571 |
|
572 | displayEmpty: PropTypes.bool,
|
573 | |
574 |
|
575 |
|
576 | error: PropTypes.bool,
|
577 | |
578 |
|
579 |
|
580 | IconComponent: PropTypes.elementType.isRequired,
|
581 | |
582 |
|
583 |
|
584 |
|
585 | inputRef: refType,
|
586 | |
587 |
|
588 |
|
589 |
|
590 | labelId: PropTypes.string,
|
591 | |
592 |
|
593 |
|
594 | MenuProps: PropTypes.object,
|
595 | |
596 |
|
597 |
|
598 | multiple: PropTypes.bool,
|
599 | |
600 |
|
601 |
|
602 | name: PropTypes.string,
|
603 | |
604 |
|
605 |
|
606 | onBlur: PropTypes.func,
|
607 | |
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 | onChange: PropTypes.func,
|
615 | |
616 |
|
617 |
|
618 |
|
619 |
|
620 |
|
621 | onClose: PropTypes.func,
|
622 | |
623 |
|
624 |
|
625 | onFocus: PropTypes.func,
|
626 | |
627 |
|
628 |
|
629 |
|
630 |
|
631 |
|
632 | onOpen: PropTypes.func,
|
633 | |
634 |
|
635 |
|
636 | open: PropTypes.bool,
|
637 | |
638 |
|
639 |
|
640 | readOnly: PropTypes.bool,
|
641 | |
642 |
|
643 |
|
644 |
|
645 |
|
646 |
|
647 | renderValue: PropTypes.func,
|
648 | |
649 |
|
650 |
|
651 | required: PropTypes.bool,
|
652 | |
653 |
|
654 |
|
655 | SelectDisplayProps: PropTypes.object,
|
656 | |
657 |
|
658 |
|
659 | tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
660 | |
661 |
|
662 |
|
663 | type: PropTypes.any,
|
664 | |
665 |
|
666 |
|
667 | value: PropTypes.any,
|
668 | |
669 |
|
670 |
|
671 | variant: PropTypes.oneOf(['standard', 'outlined', 'filled'])
|
672 | } : void 0;
|
673 | export default SelectInput; |
\ | No newline at end of file |