UNPKG

21.2 kBJavaScriptView Raw
1"use strict";
2'use client';
3
4var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
6Object.defineProperty(exports, "__esModule", {
7 value: true
8});
9exports.default = void 0;
10var _formatMuiErrorMessage2 = _interopRequireDefault(require("@mui/utils/formatMuiErrorMessage"));
11var React = _interopRequireWildcard(require("react"));
12var _reactIs = require("react-is");
13var _propTypes = _interopRequireDefault(require("prop-types"));
14var _clsx = _interopRequireDefault(require("clsx"));
15var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
16var _useId = _interopRequireDefault(require("@mui/utils/useId"));
17var _refType = _interopRequireDefault(require("@mui/utils/refType"));
18var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
19var _capitalize = _interopRequireDefault(require("../utils/capitalize"));
20var _Menu = _interopRequireDefault(require("../Menu/Menu"));
21var _NativeSelectInput = require("../NativeSelect/NativeSelectInput");
22var _utils = require("../InputBase/utils");
23var _zeroStyled = require("../zero-styled");
24var _slotShouldForwardProp = _interopRequireDefault(require("../styles/slotShouldForwardProp"));
25var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
26var _useControlled = _interopRequireDefault(require("../utils/useControlled"));
27var _selectClasses = _interopRequireWildcard(require("./selectClasses"));
28var _jsxRuntime = require("react/jsx-runtime");
29var _span;
30const SelectSelect = (0, _zeroStyled.styled)(_NativeSelectInput.StyledSelectSelect, {
31 name: 'MuiSelect',
32 slot: 'Select',
33 overridesResolver: (props, styles) => {
34 const {
35 ownerState
36 } = props;
37 return [
38 // Win specificity over the input base
39 {
40 [`&.${_selectClasses.default.select}`]: styles.select
41 }, {
42 [`&.${_selectClasses.default.select}`]: styles[ownerState.variant]
43 }, {
44 [`&.${_selectClasses.default.error}`]: styles.error
45 }, {
46 [`&.${_selectClasses.default.multiple}`]: styles.multiple
47 }];
48 }
49})({
50 // Win specificity over the input base
51 [`&.${_selectClasses.default.select}`]: {
52 height: 'auto',
53 // Resets for multiple select with chips
54 minHeight: '1.4375em',
55 // Required for select\text-field height consistency
56 textOverflow: 'ellipsis',
57 whiteSpace: 'nowrap',
58 overflow: 'hidden'
59 }
60});
61const SelectIcon = (0, _zeroStyled.styled)(_NativeSelectInput.StyledSelectIcon, {
62 name: 'MuiSelect',
63 slot: 'Icon',
64 overridesResolver: (props, styles) => {
65 const {
66 ownerState
67 } = props;
68 return [styles.icon, ownerState.variant && styles[`icon${(0, _capitalize.default)(ownerState.variant)}`], ownerState.open && styles.iconOpen];
69 }
70})({});
71const SelectNativeInput = (0, _zeroStyled.styled)('input', {
72 shouldForwardProp: prop => (0, _slotShouldForwardProp.default)(prop) && prop !== 'classes',
73 name: 'MuiSelect',
74 slot: 'NativeInput',
75 overridesResolver: (props, styles) => styles.nativeInput
76})({
77 bottom: 0,
78 left: 0,
79 position: 'absolute',
80 opacity: 0,
81 pointerEvents: 'none',
82 width: '100%',
83 boxSizing: 'border-box'
84});
85function areEqualValues(a, b) {
86 if (typeof b === 'object' && b !== null) {
87 return a === b;
88 }
89
90 // The value could be a number, the DOM will stringify it anyway.
91 return String(a) === String(b);
92}
93function isEmpty(display) {
94 return display == null || typeof display === 'string' && !display.trim();
95}
96const useUtilityClasses = ownerState => {
97 const {
98 classes,
99 variant,
100 disabled,
101 multiple,
102 open,
103 error
104 } = ownerState;
105 const slots = {
106 select: ['select', variant, disabled && 'disabled', multiple && 'multiple', error && 'error'],
107 icon: ['icon', `icon${(0, _capitalize.default)(variant)}`, open && 'iconOpen', disabled && 'disabled'],
108 nativeInput: ['nativeInput']
109 };
110 return (0, _composeClasses.default)(slots, _selectClasses.getSelectUtilityClasses, classes);
111};
112
113/**
114 * @ignore - internal component.
115 */
116const SelectInput = /*#__PURE__*/React.forwardRef(function SelectInput(props, ref) {
117 const {
118 'aria-describedby': ariaDescribedby,
119 'aria-label': ariaLabel,
120 autoFocus,
121 autoWidth,
122 children,
123 className,
124 defaultOpen,
125 defaultValue,
126 disabled,
127 displayEmpty,
128 error = false,
129 IconComponent,
130 inputRef: inputRefProp,
131 labelId,
132 MenuProps = {},
133 multiple,
134 name,
135 onBlur,
136 onChange,
137 onClose,
138 onFocus,
139 onOpen,
140 open: openProp,
141 readOnly,
142 renderValue,
143 SelectDisplayProps = {},
144 tabIndex: tabIndexProp,
145 // catching `type` from Input which makes no sense for SelectInput
146 type,
147 value: valueProp,
148 variant = 'standard',
149 ...other
150 } = props;
151 const [value, setValueState] = (0, _useControlled.default)({
152 controlled: valueProp,
153 default: defaultValue,
154 name: 'Select'
155 });
156 const [openState, setOpenState] = (0, _useControlled.default)({
157 controlled: openProp,
158 default: defaultOpen,
159 name: 'Select'
160 });
161 const inputRef = React.useRef(null);
162 const displayRef = React.useRef(null);
163 const [displayNode, setDisplayNode] = React.useState(null);
164 const {
165 current: isOpenControlled
166 } = React.useRef(openProp != null);
167 const [menuMinWidthState, setMenuMinWidthState] = React.useState();
168 const handleRef = (0, _useForkRef.default)(ref, inputRefProp);
169 const handleDisplayRef = React.useCallback(node => {
170 displayRef.current = node;
171 if (node) {
172 setDisplayNode(node);
173 }
174 }, []);
175 const anchorElement = displayNode?.parentNode;
176 React.useImperativeHandle(handleRef, () => ({
177 focus: () => {
178 displayRef.current.focus();
179 },
180 node: inputRef.current,
181 value
182 }), [value]);
183
184 // Resize menu on `defaultOpen` automatic toggle.
185 React.useEffect(() => {
186 if (defaultOpen && openState && displayNode && !isOpenControlled) {
187 setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
188 displayRef.current.focus();
189 }
190 // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
191 // eslint-disable-next-line react-hooks/exhaustive-deps
192 }, [displayNode, autoWidth]);
193 // `isOpenControlled` is ignored because the component should never switch between controlled and uncontrolled modes.
194 // `defaultOpen` and `openState` are ignored to avoid unnecessary callbacks.
195 React.useEffect(() => {
196 if (autoFocus) {
197 displayRef.current.focus();
198 }
199 }, [autoFocus]);
200 React.useEffect(() => {
201 if (!labelId) {
202 return undefined;
203 }
204 const label = (0, _ownerDocument.default)(displayRef.current).getElementById(labelId);
205 if (label) {
206 const handler = () => {
207 if (getSelection().isCollapsed) {
208 displayRef.current.focus();
209 }
210 };
211 label.addEventListener('click', handler);
212 return () => {
213 label.removeEventListener('click', handler);
214 };
215 }
216 return undefined;
217 }, [labelId]);
218 const update = (open, event) => {
219 if (open) {
220 if (onOpen) {
221 onOpen(event);
222 }
223 } else if (onClose) {
224 onClose(event);
225 }
226 if (!isOpenControlled) {
227 setMenuMinWidthState(autoWidth ? null : anchorElement.clientWidth);
228 setOpenState(open);
229 }
230 };
231 const handleMouseDown = event => {
232 // Ignore everything but left-click
233 if (event.button !== 0) {
234 return;
235 }
236 // Hijack the default focus behavior.
237 event.preventDefault();
238 displayRef.current.focus();
239 update(true, event);
240 };
241 const handleClose = event => {
242 update(false, event);
243 };
244 const childrenArray = React.Children.toArray(children);
245
246 // Support autofill.
247 const handleChange = event => {
248 const child = childrenArray.find(childItem => childItem.props.value === event.target.value);
249 if (child === undefined) {
250 return;
251 }
252 setValueState(child.props.value);
253 if (onChange) {
254 onChange(event, child);
255 }
256 };
257 const handleItemClick = child => event => {
258 let newValue;
259
260 // We use the tabindex attribute to signal the available options.
261 if (!event.currentTarget.hasAttribute('tabindex')) {
262 return;
263 }
264 if (multiple) {
265 newValue = Array.isArray(value) ? value.slice() : [];
266 const itemIndex = value.indexOf(child.props.value);
267 if (itemIndex === -1) {
268 newValue.push(child.props.value);
269 } else {
270 newValue.splice(itemIndex, 1);
271 }
272 } else {
273 newValue = child.props.value;
274 }
275 if (child.props.onClick) {
276 child.props.onClick(event);
277 }
278 if (value !== newValue) {
279 setValueState(newValue);
280 if (onChange) {
281 // Redefine target to allow name and value to be read.
282 // This allows seamless integration with the most popular form libraries.
283 // https://github.com/mui/material-ui/issues/13485#issuecomment-676048492
284 // Clone the event to not override `target` of the original event.
285 const nativeEvent = event.nativeEvent || event;
286 const clonedEvent = new nativeEvent.constructor(nativeEvent.type, nativeEvent);
287 Object.defineProperty(clonedEvent, 'target', {
288 writable: true,
289 value: {
290 value: newValue,
291 name
292 }
293 });
294 onChange(clonedEvent, child);
295 }
296 }
297 if (!multiple) {
298 update(false, event);
299 }
300 };
301 const handleKeyDown = event => {
302 if (!readOnly) {
303 const validKeys = [' ', 'ArrowUp', 'ArrowDown',
304 // The native select doesn't respond to enter on macOS, but it's recommended by
305 // https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
306 'Enter'];
307 if (validKeys.includes(event.key)) {
308 event.preventDefault();
309 update(true, event);
310 }
311 }
312 };
313 const open = displayNode !== null && openState;
314 const handleBlur = event => {
315 // if open event.stopImmediatePropagation
316 if (!open && onBlur) {
317 // Preact support, target is read only property on a native event.
318 Object.defineProperty(event, 'target', {
319 writable: true,
320 value: {
321 value,
322 name
323 }
324 });
325 onBlur(event);
326 }
327 };
328 delete other['aria-invalid'];
329 let display;
330 let displaySingle;
331 const displayMultiple = [];
332 let computeDisplay = false;
333 let foundMatch = false;
334
335 // No need to display any value if the field is empty.
336 if ((0, _utils.isFilled)({
337 value
338 }) || displayEmpty) {
339 if (renderValue) {
340 display = renderValue(value);
341 } else {
342 computeDisplay = true;
343 }
344 }
345 const items = childrenArray.map(child => {
346 if (! /*#__PURE__*/React.isValidElement(child)) {
347 return null;
348 }
349 if (process.env.NODE_ENV !== 'production') {
350 if ((0, _reactIs.isFragment)(child)) {
351 console.error(["MUI: The Select component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
352 }
353 }
354 let selected;
355 if (multiple) {
356 if (!Array.isArray(value)) {
357 throw new Error(process.env.NODE_ENV !== "production" ? 'MUI: The `value` prop must be an array ' + 'when using the `Select` component with `multiple`.' : (0, _formatMuiErrorMessage2.default)(2));
358 }
359 selected = value.some(v => areEqualValues(v, child.props.value));
360 if (selected && computeDisplay) {
361 displayMultiple.push(child.props.children);
362 }
363 } else {
364 selected = areEqualValues(value, child.props.value);
365 if (selected && computeDisplay) {
366 displaySingle = child.props.children;
367 }
368 }
369 if (selected) {
370 foundMatch = true;
371 }
372 return /*#__PURE__*/React.cloneElement(child, {
373 'aria-selected': selected ? 'true' : 'false',
374 onClick: handleItemClick(child),
375 onKeyUp: event => {
376 if (event.key === ' ') {
377 // otherwise our MenuItems dispatches a click event
378 // it's not behavior of the native <option> and causes
379 // the select to close immediately since we open on space keydown
380 event.preventDefault();
381 }
382 if (child.props.onKeyUp) {
383 child.props.onKeyUp(event);
384 }
385 },
386 role: 'option',
387 selected,
388 value: undefined,
389 // The value is most likely not a valid HTML attribute.
390 'data-value': child.props.value // Instead, we provide it as a data attribute.
391 });
392 });
393 if (process.env.NODE_ENV !== 'production') {
394 // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
395 // eslint-disable-next-line react-hooks/rules-of-hooks
396 React.useEffect(() => {
397 if (!foundMatch && !multiple && value !== '') {
398 const values = childrenArray.map(child => child.props.value);
399 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'));
400 }
401 }, [foundMatch, childrenArray, multiple, name, value]);
402 }
403 if (computeDisplay) {
404 if (multiple) {
405 if (displayMultiple.length === 0) {
406 display = null;
407 } else {
408 display = displayMultiple.reduce((output, child, index) => {
409 output.push(child);
410 if (index < displayMultiple.length - 1) {
411 output.push(', ');
412 }
413 return output;
414 }, []);
415 }
416 } else {
417 display = displaySingle;
418 }
419 }
420
421 // Avoid performing a layout computation in the render method.
422 let menuMinWidth = menuMinWidthState;
423 if (!autoWidth && isOpenControlled && displayNode) {
424 menuMinWidth = anchorElement.clientWidth;
425 }
426 let tabIndex;
427 if (typeof tabIndexProp !== 'undefined') {
428 tabIndex = tabIndexProp;
429 } else {
430 tabIndex = disabled ? null : 0;
431 }
432 const buttonId = SelectDisplayProps.id || (name ? `mui-component-select-${name}` : undefined);
433 const ownerState = {
434 ...props,
435 variant,
436 value,
437 open,
438 error
439 };
440 const classes = useUtilityClasses(ownerState);
441 const paperProps = {
442 ...MenuProps.PaperProps,
443 ...MenuProps.slotProps?.paper
444 };
445 const listboxId = (0, _useId.default)();
446 return /*#__PURE__*/(0, _jsxRuntime.jsxs)(React.Fragment, {
447 children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(SelectSelect, {
448 as: "div",
449 ref: handleDisplayRef,
450 tabIndex: tabIndex,
451 role: "combobox",
452 "aria-controls": listboxId,
453 "aria-disabled": disabled ? 'true' : undefined,
454 "aria-expanded": open ? 'true' : 'false',
455 "aria-haspopup": "listbox",
456 "aria-label": ariaLabel,
457 "aria-labelledby": [labelId, buttonId].filter(Boolean).join(' ') || undefined,
458 "aria-describedby": ariaDescribedby,
459 onKeyDown: handleKeyDown,
460 onMouseDown: disabled || readOnly ? null : handleMouseDown,
461 onBlur: handleBlur,
462 onFocus: onFocus,
463 ...SelectDisplayProps,
464 ownerState: ownerState,
465 className: (0, _clsx.default)(SelectDisplayProps.className, classes.select, className)
466 // The id is required for proper a11y
467 ,
468 id: buttonId,
469 children: isEmpty(display) ? // notranslate needed while Google Translate will not fix zero-width space issue
470 _span || (_span = /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
471 className: "notranslate",
472 children: "\u200B"
473 })) : display
474 }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SelectNativeInput, {
475 "aria-invalid": error,
476 value: Array.isArray(value) ? value.join(',') : value,
477 name: name,
478 ref: inputRef,
479 "aria-hidden": true,
480 onChange: handleChange,
481 tabIndex: -1,
482 disabled: disabled,
483 className: classes.nativeInput,
484 autoFocus: autoFocus,
485 ...other,
486 ownerState: ownerState
487 }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SelectIcon, {
488 as: IconComponent,
489 className: classes.icon,
490 ownerState: ownerState
491 }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Menu.default, {
492 id: `menu-${name || ''}`,
493 anchorEl: anchorElement,
494 open: open,
495 onClose: handleClose,
496 anchorOrigin: {
497 vertical: 'bottom',
498 horizontal: 'center'
499 },
500 transformOrigin: {
501 vertical: 'top',
502 horizontal: 'center'
503 },
504 ...MenuProps,
505 MenuListProps: {
506 'aria-labelledby': labelId,
507 role: 'listbox',
508 'aria-multiselectable': multiple ? 'true' : undefined,
509 disableListWrap: true,
510 id: listboxId,
511 ...MenuProps.MenuListProps
512 },
513 slotProps: {
514 ...MenuProps.slotProps,
515 paper: {
516 ...paperProps,
517 style: {
518 minWidth: menuMinWidth,
519 ...(paperProps != null ? paperProps.style : null)
520 }
521 }
522 },
523 children: items
524 })]
525 });
526});
527process.env.NODE_ENV !== "production" ? SelectInput.propTypes = {
528 /**
529 * @ignore
530 */
531 'aria-describedby': _propTypes.default.string,
532 /**
533 * @ignore
534 */
535 'aria-label': _propTypes.default.string,
536 /**
537 * @ignore
538 */
539 autoFocus: _propTypes.default.bool,
540 /**
541 * If `true`, the width of the popover will automatically be set according to the items inside the
542 * menu, otherwise it will be at least the width of the select input.
543 */
544 autoWidth: _propTypes.default.bool,
545 /**
546 * The option elements to populate the select with.
547 * Can be some `<MenuItem>` elements.
548 */
549 children: _propTypes.default.node,
550 /**
551 * Override or extend the styles applied to the component.
552 */
553 classes: _propTypes.default.object,
554 /**
555 * The CSS class name of the select element.
556 */
557 className: _propTypes.default.string,
558 /**
559 * If `true`, the component is toggled on mount. Use when the component open state is not controlled.
560 * You can only use it when the `native` prop is `false` (default).
561 */
562 defaultOpen: _propTypes.default.bool,
563 /**
564 * The default value. Use when the component is not controlled.
565 */
566 defaultValue: _propTypes.default.any,
567 /**
568 * If `true`, the select is disabled.
569 */
570 disabled: _propTypes.default.bool,
571 /**
572 * If `true`, the selected item is displayed even if its value is empty.
573 */
574 displayEmpty: _propTypes.default.bool,
575 /**
576 * If `true`, the `select input` will indicate an error.
577 */
578 error: _propTypes.default.bool,
579 /**
580 * The icon that displays the arrow.
581 */
582 IconComponent: _propTypes.default.elementType.isRequired,
583 /**
584 * Imperative handle implementing `{ value: T, node: HTMLElement, focus(): void }`
585 * Equivalent to `ref`
586 */
587 inputRef: _refType.default,
588 /**
589 * The ID of an element that acts as an additional label. The Select will
590 * be labelled by the additional label and the selected value.
591 */
592 labelId: _propTypes.default.string,
593 /**
594 * Props applied to the [`Menu`](/material-ui/api/menu/) element.
595 */
596 MenuProps: _propTypes.default.object,
597 /**
598 * If `true`, `value` must be an array and the menu will support multiple selections.
599 */
600 multiple: _propTypes.default.bool,
601 /**
602 * Name attribute of the `select` or hidden `input` element.
603 */
604 name: _propTypes.default.string,
605 /**
606 * @ignore
607 */
608 onBlur: _propTypes.default.func,
609 /**
610 * Callback fired when a menu item is selected.
611 *
612 * @param {object} event The event source of the callback.
613 * You can pull out the new value by accessing `event.target.value` (any).
614 * @param {object} [child] The react element that was selected.
615 */
616 onChange: _propTypes.default.func,
617 /**
618 * Callback fired when the component requests to be closed.
619 * Use in controlled mode (see open).
620 *
621 * @param {object} event The event source of the callback.
622 */
623 onClose: _propTypes.default.func,
624 /**
625 * @ignore
626 */
627 onFocus: _propTypes.default.func,
628 /**
629 * Callback fired when the component requests to be opened.
630 * Use in controlled mode (see open).
631 *
632 * @param {object} event The event source of the callback.
633 */
634 onOpen: _propTypes.default.func,
635 /**
636 * If `true`, the component is shown.
637 */
638 open: _propTypes.default.bool,
639 /**
640 * @ignore
641 */
642 readOnly: _propTypes.default.bool,
643 /**
644 * Render the selected value.
645 *
646 * @param {any} value The `value` provided to the component.
647 * @returns {ReactNode}
648 */
649 renderValue: _propTypes.default.func,
650 /**
651 * Props applied to the clickable div element.
652 */
653 SelectDisplayProps: _propTypes.default.object,
654 /**
655 * @ignore
656 */
657 tabIndex: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
658 /**
659 * @ignore
660 */
661 type: _propTypes.default.any,
662 /**
663 * The input value.
664 */
665 value: _propTypes.default.any,
666 /**
667 * The variant to use.
668 */
669 variant: _propTypes.default.oneOf(['standard', 'outlined', 'filled'])
670} : void 0;
671var _default = exports.default = SelectInput;
\No newline at end of file