UNPKG

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