UNPKG

17 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.default = void 0;
9
10var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
11
12var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
13
14var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
15
16var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
17
18var _react = _interopRequireDefault(require("react"));
19
20var _reactIs = require("react-is");
21
22var _propTypes = _interopRequireDefault(require("prop-types"));
23
24var _clsx = _interopRequireDefault(require("clsx"));
25
26var _capitalize = _interopRequireDefault(require("../utils/capitalize"));
27
28var _utils = require("@material-ui/utils");
29
30var _Menu = _interopRequireDefault(require("../Menu/Menu"));
31
32var _utils2 = require("../InputBase/utils");
33
34var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
35
36function areEqualValues(a, b) {
37 if ((0, _typeof2.default)(b) === 'object' && b !== null) {
38 return a === b;
39 }
40
41 return String(a) === String(b);
42}
43
44function isEmpty(display) {
45 return display == null || typeof display === 'string' && !display.trim();
46}
47/**
48 * @ignore - internal component.
49 */
50
51
52var SelectInput = _react.default.forwardRef(function SelectInput(props, ref) {
53 var autoFocus = props.autoFocus,
54 autoWidth = props.autoWidth,
55 children = props.children,
56 classes = props.classes,
57 className = props.className,
58 defaultValue = props.defaultValue,
59 disabled = props.disabled,
60 displayEmpty = props.displayEmpty,
61 IconComponent = props.IconComponent,
62 inputRefProp = props.inputRef,
63 labelId = props.labelId,
64 _props$MenuProps = props.MenuProps,
65 MenuProps = _props$MenuProps === void 0 ? {} : _props$MenuProps,
66 multiple = props.multiple,
67 name = props.name,
68 onBlur = props.onBlur,
69 onChange = props.onChange,
70 onClose = props.onClose,
71 onFocus = props.onFocus,
72 onOpen = props.onOpen,
73 openProp = props.open,
74 readOnly = props.readOnly,
75 renderValue = props.renderValue,
76 required = props.required,
77 _props$SelectDisplayP = props.SelectDisplayProps,
78 SelectDisplayProps = _props$SelectDisplayP === void 0 ? {} : _props$SelectDisplayP,
79 tabIndexProp = props.tabIndex,
80 type = props.type,
81 valueProp = props.value,
82 _props$variant = props.variant,
83 variant = _props$variant === void 0 ? 'standard' : _props$variant,
84 other = (0, _objectWithoutProperties2.default)(props, ["autoFocus", "autoWidth", "children", "classes", "className", "defaultValue", "disabled", "displayEmpty", "IconComponent", "inputRef", "labelId", "MenuProps", "multiple", "name", "onBlur", "onChange", "onClose", "onFocus", "onOpen", "open", "readOnly", "renderValue", "required", "SelectDisplayProps", "tabIndex", "type", "value", "variant"]);
85
86 var _React$useRef = _react.default.useRef(valueProp != null),
87 isControlled = _React$useRef.current;
88
89 var _React$useState = _react.default.useState(defaultValue),
90 valueState = _React$useState[0],
91 setValueState = _React$useState[1];
92
93 var value = isControlled ? valueProp : valueState;
94
95 if (process.env.NODE_ENV !== 'production') {
96 // eslint-disable-next-line react-hooks/rules-of-hooks
97 _react.default.useEffect(function () {
98 if (isControlled !== (valueProp != null)) {
99 console.error(["Material-UI: A component is changing ".concat(isControlled ? 'a ' : 'an un', "controlled Select to be ").concat(isControlled ? 'un' : '', "controlled."), 'Elements should not switch from uncontrolled to controlled (or vice versa).', 'Decide between using a controlled or uncontrolled Select ' + 'element for the lifetime of the component.', 'More info: https://fb.me/react-controlled-components'].join('\n'));
100 }
101 }, [valueProp, isControlled]);
102 }
103
104 var inputRef = _react.default.useRef(null);
105
106 var _React$useState2 = _react.default.useState(null),
107 displayNode = _React$useState2[0],
108 setDisplayNode = _React$useState2[1];
109
110 var _React$useRef2 = _react.default.useRef(openProp != null),
111 isOpenControlled = _React$useRef2.current;
112
113 var _React$useState3 = _react.default.useState(),
114 menuMinWidthState = _React$useState3[0],
115 setMenuMinWidthState = _React$useState3[1];
116
117 var _React$useState4 = _react.default.useState(false),
118 openState = _React$useState4[0],
119 setOpenState = _React$useState4[1];
120
121 var handleRef = (0, _useForkRef.default)(ref, inputRefProp);
122
123 _react.default.useImperativeHandle(handleRef, function () {
124 return {
125 focus: function focus() {
126 displayNode.focus();
127 },
128 node: inputRef.current,
129 value: value
130 };
131 }, [displayNode, value]);
132
133 _react.default.useEffect(function () {
134 if (autoFocus && displayNode) {
135 displayNode.focus();
136 }
137 }, [autoFocus, displayNode]);
138
139 var update = function update(open, event) {
140 if (open) {
141 if (onOpen) {
142 onOpen(event);
143 }
144 } else {
145 displayNode.focus();
146
147 if (onClose) {
148 onClose(event);
149 }
150 }
151
152 if (!isOpenControlled) {
153 setMenuMinWidthState(autoWidth ? null : displayNode.clientWidth);
154 setOpenState(open);
155 }
156 };
157
158 var handleMouseDown = function handleMouseDown(event) {
159 // Hijack the default focus behavior.
160 event.preventDefault();
161 displayNode.focus();
162 update(true, event);
163 };
164
165 var handleClose = function handleClose(event) {
166 update(false, event);
167 };
168
169 var handleItemClick = function handleItemClick(child) {
170 return function (event) {
171 if (!multiple) {
172 update(false, event);
173 }
174
175 var newValue;
176
177 if (multiple) {
178 newValue = Array.isArray(value) ? (0, _toConsumableArray2.default)(value) : [];
179 var itemIndex = value.indexOf(child.props.value);
180
181 if (itemIndex === -1) {
182 newValue.push(child.props.value);
183 } else {
184 newValue.splice(itemIndex, 1);
185 }
186 } else {
187 newValue = child.props.value;
188 }
189
190 if (!isControlled) {
191 setValueState(newValue);
192 }
193
194 if (onChange) {
195 event.persist(); // Preact support, target is read only property on a native event.
196
197 Object.defineProperty(event, 'target', {
198 writable: true,
199 value: {
200 value: newValue,
201 name: name
202 }
203 });
204 onChange(event, child);
205 }
206 };
207 };
208
209 var handleKeyDown = function handleKeyDown(event) {
210 if (!readOnly) {
211 var validKeys = [' ', 'ArrowUp', 'ArrowDown', // The native select doesn't respond to enter on MacOS, but it's recommended by
212 // https://www.w3.org/TR/wai-aria-practices/examples/listbox/listbox-collapsible.html
213 'Enter'];
214
215 if (validKeys.indexOf(event.key) !== -1) {
216 event.preventDefault();
217 update(true, event);
218 }
219 }
220 };
221
222 var open = displayNode !== null && (isOpenControlled ? openProp : openState);
223
224 var handleBlur = function handleBlur(event) {
225 // if open event.stopImmediatePropagation
226 if (!open && onBlur) {
227 event.persist(); // Preact support, target is read only property on a native event.
228
229 Object.defineProperty(event, 'target', {
230 writable: true,
231 value: {
232 value: value,
233 name: name
234 }
235 });
236 onBlur(event);
237 }
238 };
239
240 delete other['aria-invalid'];
241 var display;
242 var displaySingle;
243 var displayMultiple = [];
244 var computeDisplay = false;
245 var foundMatch = false; // No need to display any value if the field is empty.
246
247 if ((0, _utils2.isFilled)({
248 value: value
249 }) || displayEmpty) {
250 if (renderValue) {
251 display = renderValue(value);
252 } else {
253 computeDisplay = true;
254 }
255 }
256
257 var items = _react.default.Children.map(children, function (child) {
258 if (!_react.default.isValidElement(child)) {
259 return null;
260 }
261
262 if (process.env.NODE_ENV !== 'production') {
263 if ((0, _reactIs.isFragment)(child)) {
264 console.error(["Material-UI: the Select component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
265 }
266 }
267
268 var selected;
269
270 if (multiple) {
271 if (!Array.isArray(value)) {
272 throw new Error('Material-UI: the `value` prop must be an array ' + 'when using the `Select` component with `multiple`.');
273 }
274
275 selected = value.some(function (v) {
276 return areEqualValues(v, child.props.value);
277 });
278
279 if (selected && computeDisplay) {
280 displayMultiple.push(child.props.children);
281 }
282 } else {
283 selected = areEqualValues(value, child.props.value);
284
285 if (selected && computeDisplay) {
286 displaySingle = child.props.children;
287 }
288 }
289
290 if (selected) {
291 foundMatch = true;
292 }
293
294 return _react.default.cloneElement(child, {
295 'aria-selected': selected ? 'true' : undefined,
296 onClick: handleItemClick(child),
297 onKeyUp: function onKeyUp(event) {
298 if (event.key === ' ') {
299 // otherwise our MenuItems dispatches a click event
300 // it's not behavior of the native <option> and causes
301 // the select to close immediately since we open on space keydown
302 event.preventDefault();
303 }
304
305 var onKeyUp = child.props.onKeyUp;
306
307 if (typeof onKeyUp === 'function') {
308 onKeyUp(event);
309 }
310 },
311 role: 'option',
312 selected: selected,
313 value: undefined,
314 // The value is most likely not a valid HTML attribute.
315 'data-value': child.props.value // Instead, we provide it as a data attribute.
316
317 });
318 });
319
320 if (process.env.NODE_ENV !== 'production') {
321 // eslint-disable-next-line react-hooks/rules-of-hooks
322 _react.default.useEffect(function () {
323 if (!foundMatch && !multiple && value !== '') {
324 var values = _react.default.Children.toArray(children).map(function (child) {
325 return child.props.value;
326 });
327
328 console.warn(["Material-UI: 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) {
329 return x != null;
330 }).map(function (x) {
331 return "`".concat(x, "`");
332 }).join(', ') || '""', ".")].join('\n'));
333 }
334 }, [foundMatch, children, multiple, name, value]);
335 }
336
337 if (computeDisplay) {
338 display = multiple ? displayMultiple.join(', ') : displaySingle;
339 } // Avoid performing a layout computation in the render method.
340
341
342 var menuMinWidth = menuMinWidthState;
343
344 if (!autoWidth && isOpenControlled && displayNode) {
345 menuMinWidth = displayNode.clientWidth;
346 }
347
348 var tabIndex;
349
350 if (typeof tabIndexProp !== 'undefined') {
351 tabIndex = tabIndexProp;
352 } else {
353 tabIndex = disabled ? null : 0;
354 }
355
356 var buttonId = SelectDisplayProps.id || (name ? "mui-component-select-".concat(name) : undefined);
357 return _react.default.createElement(_react.default.Fragment, null, _react.default.createElement("div", (0, _extends2.default)({
358 className: (0, _clsx.default)(classes.root, // TODO v5: merge root and select
359 classes.select, classes.selectMenu, classes[variant], className, disabled && classes.disabled),
360 ref: setDisplayNode,
361 tabIndex: tabIndex,
362 role: "button",
363 "aria-expanded": open ? 'true' : undefined,
364 "aria-labelledby": "".concat(labelId || '', " ").concat(buttonId || ''),
365 "aria-haspopup": "listbox",
366 onKeyDown: handleKeyDown,
367 onMouseDown: disabled || readOnly ? null : handleMouseDown,
368 onBlur: handleBlur,
369 onFocus: onFocus
370 }, SelectDisplayProps, {
371 // The id is required for proper a11y
372 id: buttonId
373 }), isEmpty(display) ? // eslint-disable-next-line react/no-danger
374 _react.default.createElement("span", {
375 dangerouslySetInnerHTML: {
376 __html: '&#8203;'
377 }
378 }) : display), _react.default.createElement("input", (0, _extends2.default)({
379 value: Array.isArray(value) ? value.join(',') : value,
380 name: name,
381 ref: inputRef,
382 type: "hidden",
383 autoFocus: autoFocus
384 }, other)), _react.default.createElement(IconComponent, {
385 className: (0, _clsx.default)(classes.icon, classes["icon".concat((0, _capitalize.default)(variant))], open && classes.iconOpen)
386 }), _react.default.createElement(_Menu.default, (0, _extends2.default)({
387 id: "menu-".concat(name || ''),
388 anchorEl: displayNode,
389 open: open,
390 onClose: handleClose
391 }, MenuProps, {
392 MenuListProps: (0, _extends2.default)({
393 'aria-labelledby': labelId,
394 role: 'listbox',
395 disableListWrap: true
396 }, MenuProps.MenuListProps),
397 PaperProps: (0, _extends2.default)({}, MenuProps.PaperProps, {
398 style: (0, _extends2.default)({
399 minWidth: menuMinWidth
400 }, MenuProps.PaperProps != null ? MenuProps.PaperProps.style : null)
401 })
402 }), items));
403});
404
405process.env.NODE_ENV !== "production" ? SelectInput.propTypes = {
406 /**
407 * @ignore
408 */
409 autoFocus: _propTypes.default.bool,
410
411 /**
412 * If `true`, the width of the popover will automatically be set according to the items inside the
413 * menu, otherwise it will be at least the width of the select input.
414 */
415 autoWidth: _propTypes.default.bool,
416
417 /**
418 * The option elements to populate the select with.
419 * Can be some `<MenuItem>` elements.
420 */
421 children: _propTypes.default.node,
422
423 /**
424 * Override or extend the styles applied to the component.
425 * See [CSS API](#css) below for more details.
426 */
427 classes: _propTypes.default.object.isRequired,
428
429 /**
430 * The CSS class name of the select element.
431 */
432 className: _propTypes.default.string,
433
434 /**
435 * The default element value. Use when the component is not controlled.
436 */
437 defaultValue: _propTypes.default.any,
438
439 /**
440 * If `true`, the select will be disabled.
441 */
442 disabled: _propTypes.default.bool,
443
444 /**
445 * If `true`, the selected item is displayed even if its value is empty.
446 */
447 displayEmpty: _propTypes.default.bool,
448
449 /**
450 * The icon that displays the arrow.
451 */
452 IconComponent: _propTypes.default.elementType.isRequired,
453
454 /**
455 * Imperative handle implementing `{ value: T, node: HTMLElement, focus(): void }`
456 * Equivalent to `ref`
457 */
458 inputRef: _utils.refType,
459
460 /**
461 * The idea of an element that acts as an additional label. The Select will
462 * be labelled by the additional label and the selected value.
463 */
464 labelId: _propTypes.default.string,
465
466 /**
467 * Props applied to the [`Menu`](/api/menu/) element.
468 */
469 MenuProps: _propTypes.default.object,
470
471 /**
472 * If `true`, `value` must be an array and the menu will support multiple selections.
473 */
474 multiple: _propTypes.default.bool,
475
476 /**
477 * Name attribute of the `select` or hidden `input` element.
478 */
479 name: _propTypes.default.string,
480
481 /**
482 * @ignore
483 */
484 onBlur: _propTypes.default.func,
485
486 /**
487 * Callback function fired when a menu item is selected.
488 *
489 * @param {object} event The event source of the callback.
490 * You can pull out the new value by accessing `event.target.value` (any).
491 * @param {object} [child] The react element that was selected.
492 */
493 onChange: _propTypes.default.func,
494
495 /**
496 * Callback fired when the component requests to be closed.
497 * Use in controlled mode (see open).
498 *
499 * @param {object} event The event source of the callback.
500 */
501 onClose: _propTypes.default.func,
502
503 /**
504 * @ignore
505 */
506 onFocus: _propTypes.default.func,
507
508 /**
509 * Callback fired when the component requests to be opened.
510 * Use in controlled mode (see open).
511 *
512 * @param {object} event The event source of the callback.
513 */
514 onOpen: _propTypes.default.func,
515
516 /**
517 * Control `select` open state.
518 */
519 open: _propTypes.default.bool,
520
521 /**
522 * @ignore
523 */
524 readOnly: _propTypes.default.bool,
525
526 /**
527 * Render the selected value.
528 *
529 * @param {any} value The `value` provided to the component.
530 * @returns {ReactNode}
531 */
532 renderValue: _propTypes.default.func,
533
534 /**
535 * @ignore
536 */
537 required: _propTypes.default.bool,
538
539 /**
540 * Props applied to the clickable div element.
541 */
542 SelectDisplayProps: _propTypes.default.object,
543
544 /**
545 * @ignore
546 */
547 tabIndex: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
548
549 /**
550 * @ignore
551 */
552 type: _propTypes.default.any,
553
554 /**
555 * The input value.
556 */
557 value: _propTypes.default.any,
558
559 /**
560 * The variant to use.
561 */
562 variant: _propTypes.default.oneOf(['standard', 'outlined', 'filled'])
563} : void 0;
564var _default = SelectInput;
565exports.default = _default;
\No newline at end of file