UNPKG

19.3 kBJavaScriptView Raw
1import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
2import _extends from "@babel/runtime/helpers/esm/extends";
3import { formatMuiErrorMessage as _formatMuiErrorMessage } from "@material-ui/utils";
4
5/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
6import * as React from 'react';
7import PropTypes from 'prop-types';
8import clsx from 'clsx';
9import { refType } from '@material-ui/utils';
10import formControlState from '../FormControl/formControlState';
11import FormControlContext, { useFormControl } from '../FormControl/FormControlContext';
12import withStyles from '../styles/withStyles';
13import capitalize from '../utils/capitalize';
14import useForkRef from '../utils/useForkRef';
15import TextareaAutosize from '../TextareaAutosize';
16import { isFilled } from './utils';
17export const styles = theme => {
18 const light = theme.palette.type === 'light';
19 const placeholder = {
20 color: 'currentColor',
21 opacity: light ? 0.42 : 0.5,
22 transition: theme.transitions.create('opacity', {
23 duration: theme.transitions.duration.shorter
24 })
25 };
26 const placeholderHidden = {
27 opacity: '0 !important'
28 };
29 const placeholderVisible = {
30 opacity: light ? 0.42 : 0.5
31 };
32 return {
33 '@global': {
34 '@keyframes mui-auto-fill': {},
35 '@keyframes mui-auto-fill-cancel': {}
36 },
37
38 /* Styles applied to the root element. */
39 root: _extends({}, theme.typography.body1, {
40 color: theme.palette.text.primary,
41 lineHeight: '1.1876em',
42 // Reset (19px), match the native input line-height
43 boxSizing: 'border-box',
44 // Prevent padding issue with fullWidth.
45 position: 'relative',
46 cursor: 'text',
47 display: 'inline-flex',
48 alignItems: 'center',
49 '&$disabled': {
50 color: theme.palette.text.disabled,
51 cursor: 'default'
52 }
53 }),
54
55 /* Styles applied to the root element if the component is a descendant of `FormControl`. */
56 formControl: {},
57
58 /* Styles applied to the root element if the component is focused. */
59 focused: {},
60
61 /* Styles applied to the root element if `disabled={true}`. */
62 disabled: {},
63
64 /* Styles applied to the root element if `startAdornment` is provided. */
65 adornedStart: {},
66
67 /* Styles applied to the root element if `endAdornment` is provided. */
68 adornedEnd: {},
69
70 /* Pseudo-class applied to the root element if `error={true}`. */
71 error: {},
72
73 /* Styles applied to the `input` element if `margin="dense"`. */
74 marginDense: {},
75
76 /* Styles applied to the root element if `multiline={true}`. */
77 multiline: {
78 padding: `${8 - 2}px 0 ${8 - 1}px`,
79 '&$marginDense': {
80 paddingTop: 4 - 1
81 }
82 },
83
84 /* Styles applied to the root element if the color is secondary. */
85 colorSecondary: {},
86
87 /* Styles applied to the root element if `fullWidth={true}`. */
88 fullWidth: {
89 width: '100%'
90 },
91
92 /* Styles applied to the `input` element. */
93 input: {
94 font: 'inherit',
95 letterSpacing: 'inherit',
96 color: 'currentColor',
97 padding: `${8 - 2}px 0 ${8 - 1}px`,
98 border: 0,
99 boxSizing: 'content-box',
100 background: 'none',
101 height: '1.1876em',
102 // Reset (19px), match the native input line-height
103 margin: 0,
104 // Reset for Safari
105 WebkitTapHighlightColor: 'transparent',
106 display: 'block',
107 // Make the flex item shrink with Firefox
108 minWidth: 0,
109 width: '100%',
110 // Fix IE 11 width issue
111 animationName: 'mui-auto-fill-cancel',
112 animationDuration: '10ms',
113 '&::-webkit-input-placeholder': placeholder,
114 '&::-moz-placeholder': placeholder,
115 // Firefox 19+
116 '&:-ms-input-placeholder': placeholder,
117 // IE 11
118 '&::-ms-input-placeholder': placeholder,
119 // Edge
120 '&:focus': {
121 outline: 0
122 },
123 // Reset Firefox invalid required input style
124 '&:invalid': {
125 boxShadow: 'none'
126 },
127 '&::-webkit-search-decoration': {
128 // Remove the padding when type=search.
129 '-webkit-appearance': 'none'
130 },
131 // Show and hide the placeholder logic
132 'label[data-shrink=false] + $formControl &': {
133 '&::-webkit-input-placeholder': placeholderHidden,
134 '&::-moz-placeholder': placeholderHidden,
135 // Firefox 19+
136 '&:-ms-input-placeholder': placeholderHidden,
137 // IE 11
138 '&::-ms-input-placeholder': placeholderHidden,
139 // Edge
140 '&:focus::-webkit-input-placeholder': placeholderVisible,
141 '&:focus::-moz-placeholder': placeholderVisible,
142 // Firefox 19+
143 '&:focus:-ms-input-placeholder': placeholderVisible,
144 // IE 11
145 '&:focus::-ms-input-placeholder': placeholderVisible // Edge
146
147 },
148 '&$disabled': {
149 opacity: 1 // Reset iOS opacity
150
151 },
152 '&:-webkit-autofill': {
153 animationDuration: '5000s',
154 animationName: 'mui-auto-fill'
155 }
156 },
157
158 /* Styles applied to the `input` element if `margin="dense"`. */
159 inputMarginDense: {
160 paddingTop: 4 - 1
161 },
162
163 /* Styles applied to the `input` element if `multiline={true}`. */
164 inputMultiline: {
165 height: 'auto',
166 resize: 'none',
167 padding: 0
168 },
169
170 /* Styles applied to the `input` element if `type="search"`. */
171 inputTypeSearch: {
172 // Improve type search style.
173 '-moz-appearance': 'textfield',
174 '-webkit-appearance': 'textfield'
175 },
176
177 /* Styles applied to the `input` element if `startAdornment` is provided. */
178 inputAdornedStart: {},
179
180 /* Styles applied to the `input` element if `endAdornment` is provided. */
181 inputAdornedEnd: {},
182
183 /* Styles applied to the `input` element if `hiddenLabel={true}`. */
184 inputHiddenLabel: {}
185 };
186};
187const useEnhancedEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
188/**
189 * `InputBase` contains as few styles as possible.
190 * It aims to be a simple building block for creating an input.
191 * It contains a load of style reset and some state logic.
192 */
193
194const InputBase = /*#__PURE__*/React.forwardRef(function InputBase(props, ref) {
195 const {
196 'aria-describedby': ariaDescribedby,
197 autoComplete,
198 autoFocus,
199 classes,
200 className,
201 defaultValue,
202 disabled,
203 endAdornment,
204 fullWidth = false,
205 id,
206 inputComponent = 'input',
207 inputProps: inputPropsProp = {},
208 inputRef: inputRefProp,
209 multiline = false,
210 name,
211 onBlur,
212 onChange,
213 onClick,
214 onFocus,
215 onKeyDown,
216 onKeyUp,
217 placeholder,
218 readOnly,
219 renderSuffix,
220 rows,
221 rowsMax,
222 rowsMin,
223 maxRows,
224 minRows,
225 startAdornment,
226 type = 'text',
227 value: valueProp
228 } = props,
229 other = _objectWithoutPropertiesLoose(props, ["aria-describedby", "autoComplete", "autoFocus", "classes", "className", "color", "defaultValue", "disabled", "endAdornment", "error", "fullWidth", "id", "inputComponent", "inputProps", "inputRef", "margin", "multiline", "name", "onBlur", "onChange", "onClick", "onFocus", "onKeyDown", "onKeyUp", "placeholder", "readOnly", "renderSuffix", "rows", "rowsMax", "rowsMin", "maxRows", "minRows", "startAdornment", "type", "value"]);
230
231 const value = inputPropsProp.value != null ? inputPropsProp.value : valueProp;
232 const {
233 current: isControlled
234 } = React.useRef(value != null);
235 const inputRef = React.useRef();
236 const handleInputRefWarning = React.useCallback(instance => {
237 if (process.env.NODE_ENV !== 'production') {
238 if (instance && instance.nodeName !== 'INPUT' && !instance.focus) {
239 console.error(['Material-UI: You have provided a `inputComponent` to the input component', 'that does not correctly handle the `inputRef` prop.', 'Make sure the `inputRef` prop is called with a HTMLInputElement.'].join('\n'));
240 }
241 }
242 }, []);
243 const handleInputPropsRefProp = useForkRef(inputPropsProp.ref, handleInputRefWarning);
244 const handleInputRefProp = useForkRef(inputRefProp, handleInputPropsRefProp);
245 const handleInputRef = useForkRef(inputRef, handleInputRefProp);
246 const [focused, setFocused] = React.useState(false);
247 const muiFormControl = useFormControl();
248
249 if (process.env.NODE_ENV !== 'production') {
250 // eslint-disable-next-line react-hooks/rules-of-hooks
251 React.useEffect(() => {
252 if (muiFormControl) {
253 return muiFormControl.registerEffect();
254 }
255
256 return undefined;
257 }, [muiFormControl]);
258 }
259
260 const fcs = formControlState({
261 props,
262 muiFormControl,
263 states: ['color', 'disabled', 'error', 'hiddenLabel', 'margin', 'required', 'filled']
264 });
265 fcs.focused = muiFormControl ? muiFormControl.focused : focused; // The blur won't fire when the disabled state is set on a focused input.
266 // We need to book keep the focused state manually.
267
268 React.useEffect(() => {
269 if (!muiFormControl && disabled && focused) {
270 setFocused(false);
271
272 if (onBlur) {
273 onBlur();
274 }
275 }
276 }, [muiFormControl, disabled, focused, onBlur]);
277 const onFilled = muiFormControl && muiFormControl.onFilled;
278 const onEmpty = muiFormControl && muiFormControl.onEmpty;
279 const checkDirty = React.useCallback(obj => {
280 if (isFilled(obj)) {
281 if (onFilled) {
282 onFilled();
283 }
284 } else if (onEmpty) {
285 onEmpty();
286 }
287 }, [onFilled, onEmpty]);
288 useEnhancedEffect(() => {
289 if (isControlled) {
290 checkDirty({
291 value
292 });
293 }
294 }, [value, checkDirty, isControlled]);
295
296 const handleFocus = event => {
297 // Fix a bug with IE 11 where the focus/blur events are triggered
298 // while the input is disabled.
299 if (fcs.disabled) {
300 event.stopPropagation();
301 return;
302 }
303
304 if (onFocus) {
305 onFocus(event);
306 }
307
308 if (inputPropsProp.onFocus) {
309 inputPropsProp.onFocus(event);
310 }
311
312 if (muiFormControl && muiFormControl.onFocus) {
313 muiFormControl.onFocus(event);
314 } else {
315 setFocused(true);
316 }
317 };
318
319 const handleBlur = event => {
320 if (onBlur) {
321 onBlur(event);
322 }
323
324 if (inputPropsProp.onBlur) {
325 inputPropsProp.onBlur(event);
326 }
327
328 if (muiFormControl && muiFormControl.onBlur) {
329 muiFormControl.onBlur(event);
330 } else {
331 setFocused(false);
332 }
333 };
334
335 const handleChange = (event, ...args) => {
336 if (!isControlled) {
337 const element = event.target || inputRef.current;
338
339 if (element == null) {
340 throw new Error(process.env.NODE_ENV !== "production" ? `Material-UI: Expected valid input target. Did you use a custom \`inputComponent\` and forget to forward refs? See https://material-ui.com/r/input-component-ref-interface for more info.` : _formatMuiErrorMessage(1));
341 }
342
343 checkDirty({
344 value: element.value
345 });
346 }
347
348 if (inputPropsProp.onChange) {
349 inputPropsProp.onChange(event, ...args);
350 } // Perform in the willUpdate
351
352
353 if (onChange) {
354 onChange(event, ...args);
355 }
356 }; // Check the input state on mount, in case it was filled by the user
357 // or auto filled by the browser before the hydration (for SSR).
358
359
360 React.useEffect(() => {
361 checkDirty(inputRef.current);
362 }, []); // eslint-disable-line react-hooks/exhaustive-deps
363
364 const handleClick = event => {
365 if (inputRef.current && event.currentTarget === event.target) {
366 inputRef.current.focus();
367 }
368
369 if (onClick) {
370 onClick(event);
371 }
372 };
373
374 let InputComponent = inputComponent;
375
376 let inputProps = _extends({}, inputPropsProp, {
377 ref: handleInputRef
378 });
379
380 if (typeof InputComponent !== 'string') {
381 inputProps = _extends({
382 // Rename ref to inputRef as we don't know the
383 // provided `inputComponent` structure.
384 inputRef: handleInputRef,
385 type
386 }, inputProps, {
387 ref: null
388 });
389 } else if (multiline) {
390 if (rows && !maxRows && !minRows && !rowsMax && !rowsMin) {
391 InputComponent = 'textarea';
392 } else {
393 inputProps = _extends({
394 minRows: rows || minRows,
395 rowsMax,
396 maxRows
397 }, inputProps);
398 InputComponent = TextareaAutosize;
399 }
400 } else {
401 inputProps = _extends({
402 type
403 }, inputProps);
404 }
405
406 const handleAutoFill = event => {
407 // Provide a fake value as Chrome might not let you access it for security reasons.
408 checkDirty(event.animationName === 'mui-auto-fill-cancel' ? inputRef.current : {
409 value: 'x'
410 });
411 };
412
413 React.useEffect(() => {
414 if (muiFormControl) {
415 muiFormControl.setAdornedStart(Boolean(startAdornment));
416 }
417 }, [muiFormControl, startAdornment]);
418 return /*#__PURE__*/React.createElement("div", _extends({
419 className: clsx(classes.root, classes[`color${capitalize(fcs.color || 'primary')}`], className, fcs.disabled && classes.disabled, fcs.error && classes.error, fullWidth && classes.fullWidth, fcs.focused && classes.focused, muiFormControl && classes.formControl, multiline && classes.multiline, startAdornment && classes.adornedStart, endAdornment && classes.adornedEnd, fcs.margin === 'dense' && classes.marginDense),
420 onClick: handleClick,
421 ref: ref
422 }, other), startAdornment, /*#__PURE__*/React.createElement(FormControlContext.Provider, {
423 value: null
424 }, /*#__PURE__*/React.createElement(InputComponent, _extends({
425 "aria-invalid": fcs.error,
426 "aria-describedby": ariaDescribedby,
427 autoComplete: autoComplete,
428 autoFocus: autoFocus,
429 defaultValue: defaultValue,
430 disabled: fcs.disabled,
431 id: id,
432 onAnimationStart: handleAutoFill,
433 name: name,
434 placeholder: placeholder,
435 readOnly: readOnly,
436 required: fcs.required,
437 rows: rows,
438 value: value,
439 onKeyDown: onKeyDown,
440 onKeyUp: onKeyUp
441 }, inputProps, {
442 className: clsx(classes.input, inputPropsProp.className, fcs.disabled && classes.disabled, multiline && classes.inputMultiline, fcs.hiddenLabel && classes.inputHiddenLabel, startAdornment && classes.inputAdornedStart, endAdornment && classes.inputAdornedEnd, type === 'search' && classes.inputTypeSearch, fcs.margin === 'dense' && classes.inputMarginDense),
443 onBlur: handleBlur,
444 onChange: handleChange,
445 onFocus: handleFocus
446 }))), endAdornment, renderSuffix ? renderSuffix(_extends({}, fcs, {
447 startAdornment
448 })) : null);
449});
450process.env.NODE_ENV !== "production" ? InputBase.propTypes = {
451 // ----------------------------- Warning --------------------------------
452 // | These PropTypes are generated from the TypeScript type definitions |
453 // | To update them edit the d.ts file and run "yarn proptypes" |
454 // ----------------------------------------------------------------------
455
456 /**
457 * @ignore
458 */
459 'aria-describedby': PropTypes.string,
460
461 /**
462 * This prop helps users to fill forms faster, especially on mobile devices.
463 * The name can be confusing, as it's more like an autofill.
464 * You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
465 */
466 autoComplete: PropTypes.string,
467
468 /**
469 * If `true`, the `input` element will be focused during the first mount.
470 */
471 autoFocus: PropTypes.bool,
472
473 /**
474 * Override or extend the styles applied to the component.
475 * See [CSS API](#css) below for more details.
476 */
477 classes: PropTypes.object,
478
479 /**
480 * @ignore
481 */
482 className: PropTypes.string,
483
484 /**
485 * The color of the component. It supports those theme colors that make sense for this component.
486 */
487 color: PropTypes.oneOf(['primary', 'secondary']),
488
489 /**
490 * The default `input` element value. Use when the component is not controlled.
491 */
492 defaultValue: PropTypes.any,
493
494 /**
495 * If `true`, the `input` element will be disabled.
496 */
497 disabled: PropTypes.bool,
498
499 /**
500 * End `InputAdornment` for this component.
501 */
502 endAdornment: PropTypes.node,
503
504 /**
505 * If `true`, the input will indicate an error. This is normally obtained via context from
506 * FormControl.
507 */
508 error: PropTypes.bool,
509
510 /**
511 * If `true`, the input will take up the full width of its container.
512 */
513 fullWidth: PropTypes.bool,
514
515 /**
516 * The id of the `input` element.
517 */
518 id: PropTypes.string,
519
520 /**
521 * The component used for the `input` element.
522 * Either a string to use a HTML element or a component.
523 */
524 inputComponent: PropTypes.elementType,
525
526 /**
527 * [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes) applied to the `input` element.
528 */
529 inputProps: PropTypes.object,
530
531 /**
532 * Pass a ref to the `input` element.
533 */
534 inputRef: refType,
535
536 /**
537 * If `dense`, will adjust vertical spacing. This is normally obtained via context from
538 * FormControl.
539 */
540 margin: PropTypes.oneOf(['dense', 'none']),
541
542 /**
543 * Maximum number of rows to display when multiline option is set to true.
544 */
545 maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
546
547 /**
548 * Minimum number of rows to display when multiline option is set to true.
549 */
550 minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
551
552 /**
553 * If `true`, a textarea element will be rendered.
554 */
555 multiline: PropTypes.bool,
556
557 /**
558 * Name attribute of the `input` element.
559 */
560 name: PropTypes.string,
561
562 /**
563 * Callback fired when the input is blurred.
564 *
565 * Notice that the first argument (event) might be undefined.
566 */
567 onBlur: PropTypes.func,
568
569 /**
570 * Callback fired when the value is changed.
571 *
572 * @param {object} event The event source of the callback.
573 * You can pull out the new value by accessing `event.target.value` (string).
574 */
575 onChange: PropTypes.func,
576
577 /**
578 * @ignore
579 */
580 onClick: PropTypes.func,
581
582 /**
583 * @ignore
584 */
585 onFocus: PropTypes.func,
586
587 /**
588 * @ignore
589 */
590 onKeyDown: PropTypes.func,
591
592 /**
593 * @ignore
594 */
595 onKeyUp: PropTypes.func,
596
597 /**
598 * The short hint displayed in the input before the user enters a value.
599 */
600 placeholder: PropTypes.string,
601
602 /**
603 * It prevents the user from changing the value of the field
604 * (not from interacting with the field).
605 */
606 readOnly: PropTypes.bool,
607
608 /**
609 * @ignore
610 */
611 renderSuffix: PropTypes.func,
612
613 /**
614 * If `true`, the `input` element will be required.
615 */
616 required: PropTypes.bool,
617
618 /**
619 * Number of rows to display when multiline option is set to true.
620 */
621 rows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
622
623 /**
624 * Maximum number of rows to display.
625 * @deprecated Use `maxRows` instead.
626 */
627 rowsMax: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
628
629 /**
630 * Minimum number of rows to display.
631 * @deprecated Use `minRows` instead.
632 */
633 rowsMin: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
634
635 /**
636 * Start `InputAdornment` for this component.
637 */
638 startAdornment: PropTypes.node,
639
640 /**
641 * Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types).
642 */
643 type: PropTypes.string,
644
645 /**
646 * The value of the `input` element, required for a controlled component.
647 */
648 value: PropTypes.any
649} : void 0;
650export default withStyles(styles, {
651 name: 'MuiInputBase'
652})(InputBase);
\No newline at end of file