@material-ui/core
Version:
React components that implement Google's Material Design.
591 lines (514 loc) • 18.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
/* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */
import React from 'react';
import PropTypes from 'prop-types';
import warning from 'warning';
import clsx from 'clsx';
import formControlState from '../FormControl/formControlState';
import FormControlContext, { useFormControl } from '../FormControl/FormControlContext';
import withStyles from '../styles/withStyles';
import { useForkRef } from '../utils/reactHelpers';
import TextareaAutosize from '../TextareaAutosize';
import { isFilled } from './utils';
export var styles = function styles(theme) {
var light = theme.palette.type === 'light';
var placeholder = {
color: 'currentColor',
opacity: light ? 0.42 : 0.5,
transition: theme.transitions.create('opacity', {
duration: theme.transitions.duration.shorter
})
};
var placeholderHidden = {
opacity: '0 !important'
};
var placeholderVisible = {
opacity: light ? 0.42 : 0.5
};
return {
/* Styles applied to the root element. */
root: {
// Mimics the default input display property used by browsers for an input.
fontFamily: theme.typography.fontFamily,
color: theme.palette.text.primary,
fontSize: theme.typography.pxToRem(16),
lineHeight: '1.1875em',
// Reset (19px), match the native input line-height
boxSizing: 'border-box',
// Prevent padding issue with fullWidth.
position: 'relative',
cursor: 'text',
display: 'inline-flex',
alignItems: 'center',
'&$disabled': {
color: theme.palette.text.disabled,
cursor: 'default'
}
},
/* Styles applied to the root element if the component is a descendant of `FormControl`. */
formControl: {},
/* Styles applied to the root element if the component is focused. */
focused: {},
/* Styles applied to the root element if `disabled={true}`. */
disabled: {},
/* Styles applied to the root element if `startAdornment` is provided. */
adornedStart: {},
/* Styles applied to the root element if `endAdornment` is provided. */
adornedEnd: {},
/* Styles applied to the root element if `error={true}`. */
error: {},
/* Styles applied to the `input` element if `margin="dense"`. */
marginDense: {},
/* Styles applied to the root element if `multiline={true}`. */
multiline: {
padding: "".concat(8 - 2, "px 0 ").concat(8 - 1, "px"),
'&$marginDense': {
paddingTop: 4 - 1
}
},
/* Styles applied to the root element if `fullWidth={true}`. */
fullWidth: {
width: '100%'
},
/* Styles applied to the `input` element. */
input: {
font: 'inherit',
color: 'currentColor',
padding: "".concat(8 - 2, "px 0 ").concat(8 - 1, "px"),
border: 0,
boxSizing: 'content-box',
background: 'none',
height: '1.1875em',
// Reset (19px), match the native input line-height
margin: 0,
// Reset for Safari
// Remove grey highlight
WebkitTapHighlightColor: 'transparent',
display: 'block',
// Make the flex item shrink with Firefox
minWidth: 0,
width: '100%',
// Fix IE 11 width issue
'&::-webkit-input-placeholder': placeholder,
'&::-moz-placeholder': placeholder,
// Firefox 19+
'&:-ms-input-placeholder': placeholder,
// IE 11
'&::-ms-input-placeholder': placeholder,
// Edge
'&:focus': {
outline: 0
},
// Reset Firefox invalid required input style
'&:invalid': {
boxShadow: 'none'
},
'&::-webkit-search-decoration': {
// Remove the padding when type=search.
'-webkit-appearance': 'none'
},
// Show and hide the placeholder logic
'label[data-shrink=false] + $formControl &': {
'&::-webkit-input-placeholder': placeholderHidden,
'&::-moz-placeholder': placeholderHidden,
// Firefox 19+
'&:-ms-input-placeholder': placeholderHidden,
// IE 11
'&::-ms-input-placeholder': placeholderHidden,
// Edge
'&:focus::-webkit-input-placeholder': placeholderVisible,
'&:focus::-moz-placeholder': placeholderVisible,
// Firefox 19+
'&:focus:-ms-input-placeholder': placeholderVisible,
// IE 11
'&:focus::-ms-input-placeholder': placeholderVisible // Edge
},
'&$disabled': {
opacity: 1 // Reset iOS opacity
}
},
/* Styles applied to the `input` element if `margin="dense"`. */
inputMarginDense: {
paddingTop: 4 - 1
},
/* Styles applied to the `input` element if `select={true}`. */
inputSelect: {
paddingRight: 24
},
/* Styles applied to the `input` element if `multiline={true}`. */
inputMultiline: {
height: 'auto',
resize: 'none',
padding: 0
},
/* Styles applied to the `input` element if `type="search"`. */
inputTypeSearch: {
// Improve type search style.
'-moz-appearance': 'textfield',
'-webkit-appearance': 'textfield'
},
/* Styles applied to the `input` element if `startAdornment` is provided. */
inputAdornedStart: {},
/* Styles applied to the `input` element if `endAdornment` is provided. */
inputAdornedEnd: {},
/* Styles applied to the `input` element if `hiddenLabel={true}`. */
inputHiddenLabel: {}
};
};
var useEnhancedEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
/**
* `InputBase` contains as few styles as possible.
* It aims to be a simple building block for creating an input.
* It contains a load of style reset and some state logic.
*/
var InputBase = React.forwardRef(function InputBase(props, ref) {
var ariaDescribedby = props['aria-describedby'],
autoComplete = props.autoComplete,
autoFocus = props.autoFocus,
classes = props.classes,
classNameProp = props.className,
defaultValue = props.defaultValue,
disabled = props.disabled,
endAdornment = props.endAdornment,
error = props.error,
_props$fullWidth = props.fullWidth,
fullWidth = _props$fullWidth === void 0 ? false : _props$fullWidth,
id = props.id,
_props$inputComponent = props.inputComponent,
inputComponent = _props$inputComponent === void 0 ? 'input' : _props$inputComponent,
_props$inputProps = props.inputProps;
_props$inputProps = _props$inputProps === void 0 ? {} : _props$inputProps;
var inputPropsClassName = _props$inputProps.className,
inputPropsProp = _objectWithoutProperties(_props$inputProps, ["className"]),
inputRefProp = props.inputRef,
margin = props.margin,
_props$multiline = props.multiline,
multiline = _props$multiline === void 0 ? false : _props$multiline,
name = props.name,
onBlur = props.onBlur,
onChange = props.onChange,
onClick = props.onClick,
onFocus = props.onFocus,
onKeyDown = props.onKeyDown,
onKeyUp = props.onKeyUp,
placeholder = props.placeholder,
readOnly = props.readOnly,
renderPrefix = props.renderPrefix,
rows = props.rows,
rowsMax = props.rowsMax,
_props$select = props.select,
select = _props$select === void 0 ? false : _props$select,
startAdornment = props.startAdornment,
_props$type = props.type,
type = _props$type === void 0 ? 'text' : _props$type,
value = props.value,
other = _objectWithoutProperties(props, ["aria-describedby", "autoComplete", "autoFocus", "classes", "className", "defaultValue", "disabled", "endAdornment", "error", "fullWidth", "id", "inputComponent", "inputProps", "inputRef", "margin", "multiline", "name", "onBlur", "onChange", "onClick", "onFocus", "onKeyDown", "onKeyUp", "placeholder", "readOnly", "renderPrefix", "rows", "rowsMax", "select", "startAdornment", "type", "value"]);
var _React$useRef = React.useRef(value != null),
isControlled = _React$useRef.current;
var inputRef = React.useRef();
var handleInputRefWarning = React.useCallback(function (instance) {
process.env.NODE_ENV !== "production" ? warning(!instance || instance instanceof HTMLInputElement || instance.focus, ['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')) : void 0;
}, []);
var handleInputPropsRefProp = useForkRef(inputPropsProp.ref, handleInputRefWarning);
var handleInputRefProp = useForkRef(inputRefProp, handleInputPropsRefProp);
var handleInputRef = useForkRef(inputRef, handleInputRefProp);
var _React$useState = React.useState(false),
_React$useState2 = _slicedToArray(_React$useState, 2),
focused = _React$useState2[0],
setFocused = _React$useState2[1];
var muiFormControl = useFormControl();
var fcs = formControlState({
props: props,
muiFormControl: muiFormControl,
states: ['disabled', 'error', 'hiddenLabel', 'margin', 'required', 'filled']
});
fcs.focused = muiFormControl ? muiFormControl.focused : focused; // The blur won't fire when the disabled state is set on a focused input.
// We need to book keep the focused state manually.
React.useEffect(function () {
if (!muiFormControl && disabled && focused) {
setFocused(false);
if (onBlur) {
onBlur();
}
}
}, [muiFormControl, disabled, focused, onBlur]);
var checkDirty = React.useCallback(function (obj) {
if (isFilled(obj)) {
if (muiFormControl && muiFormControl.onFilled) {
muiFormControl.onFilled();
}
} else if (muiFormControl && muiFormControl.onEmpty) {
muiFormControl.onEmpty();
}
}, [muiFormControl]);
useEnhancedEffect(function () {
if (isControlled) {
checkDirty({
value: value
});
}
}, [value, checkDirty, isControlled]);
var handleFocus = function handleFocus(event) {
// Fix a bug with IE 11 where the focus/blur events are triggered
// while the input is disabled.
if (fcs.disabled) {
event.stopPropagation();
return;
}
if (onFocus) {
onFocus(event);
}
if (muiFormControl && muiFormControl.onFocus) {
muiFormControl.onFocus(event);
} else {
setFocused(true);
}
};
var handleBlur = function handleBlur(event) {
if (onBlur) {
onBlur(event);
}
if (muiFormControl && muiFormControl.onBlur) {
muiFormControl.onBlur(event);
} else {
setFocused(false);
}
};
var handleChange = function handleChange(event) {
if (!isControlled) {
var element = event.target || inputRef.current;
if (element == null) {
throw new TypeError('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.');
}
checkDirty({
value: element.value
});
} // Perform in the willUpdate
if (onChange) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
onChange.apply(void 0, [event].concat(args));
}
};
var handleClick = function handleClick(event) {
if (inputRef.current && event.currentTarget === event.target) {
inputRef.current.focus();
}
if (onClick) {
onClick(event);
}
};
var InputComponent = inputComponent;
var inputProps = _extends({}, inputPropsProp, {
ref: handleInputRef
});
if (typeof InputComponent !== 'string') {
inputProps = _extends({
// Rename ref to inputRef as we don't know the
// provided `inputComponent` structure.
inputRef: handleInputRef,
type: type
}, inputProps, {
ref: null
});
} else if (multiline) {
if (rows && !rowsMax) {
InputComponent = 'textarea';
} else {
inputProps = _extends({
rows: rows,
rowsMax: rowsMax
}, inputProps);
InputComponent = TextareaAutosize;
}
} else {
inputProps = _extends({
type: type
}, inputProps);
}
return React.createElement("div", _extends({
className: clsx(classes.root, classNameProp, 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, {
dense: classes.marginDense
}[fcs.margin]),
onClick: handleClick,
ref: ref
}, other), renderPrefix ? renderPrefix(_extends({}, fcs, {
startAdornment: startAdornment
})) : null, startAdornment, React.createElement(FormControlContext.Provider, {
value: null
}, React.createElement(InputComponent, _extends({
"aria-invalid": fcs.error,
"aria-describedby": ariaDescribedby,
autoComplete: autoComplete,
autoFocus: autoFocus,
className: clsx(classes.input, inputPropsClassName, fcs.disabled && classes.disabled, multiline && classes.inputMultiline, select && classes.inputSelect, fcs.hiddenLabel && classes.inputHiddenLabel, startAdornment && classes.inputAdornedStart, endAdornment && classes.inputAdornedEnd, {
search: classes.inputTypeSearch
}[type], {
dense: classes.inputMarginDense
}[fcs.margin]),
defaultValue: defaultValue,
disabled: fcs.disabled,
id: id,
name: name,
onBlur: handleBlur,
onChange: handleChange,
onFocus: handleFocus,
onKeyDown: onKeyDown,
onKeyUp: onKeyUp,
placeholder: placeholder,
readOnly: readOnly,
required: fcs.required,
rows: rows,
value: value
}, inputProps))), endAdornment);
});
process.env.NODE_ENV !== "production" ? InputBase.propTypes = {
/**
* @ignore
*/
'aria-describedby': PropTypes.string,
/**
* This prop helps users to fill forms faster, especially on mobile devices.
* The name can be confusing, as it's more like an autofill.
* You can learn more about it [following the specification](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
*/
autoComplete: PropTypes.string,
/**
* If `true`, the `input` element will be focused during the first mount.
*/
autoFocus: PropTypes.bool,
/**
* Override or extend the styles applied to the component.
* See [CSS API](#css) below for more details.
*/
classes: PropTypes.object.isRequired,
/**
* The CSS class name of the wrapper element.
*/
className: PropTypes.string,
/**
* The default `input` element value. Use when the component is not controlled.
*/
defaultValue: PropTypes.any,
/**
* If `true`, the `input` element will be disabled.
*/
disabled: PropTypes.bool,
/**
* End `InputAdornment` for this component.
*/
endAdornment: PropTypes.node,
/**
* If `true`, the input will indicate an error. This is normally obtained via context from
* FormControl.
*/
error: PropTypes.bool,
/**
* If `true`, the input will take up the full width of its container.
*/
fullWidth: PropTypes.bool,
/**
* The id of the `input` element.
*/
id: PropTypes.string,
/**
* The component used for the `input` element.
* Either a string to use a DOM element or a component.
*/
inputComponent: PropTypes.elementType,
/**
* [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#Attributes) applied to the `input` element.
*/
inputProps: PropTypes.object,
/**
* This prop can be used to pass a ref callback to the `input` element.
*/
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* If `dense`, will adjust vertical spacing. This is normally obtained via context from
* FormControl.
*/
margin: PropTypes.oneOf(['dense', 'none']),
/**
* If `true`, a textarea element will be rendered.
*/
multiline: PropTypes.bool,
/**
* Name attribute of the `input` element.
*/
name: PropTypes.string,
/**
* @ignore
*/
onBlur: PropTypes.func,
/**
* Callback fired when the value is changed.
*
* @param {object} event The event source of the callback.
* You can pull out the new value by accessing `event.target.value`.
*/
onChange: PropTypes.func,
/**
* @ignore
*/
onClick: PropTypes.func,
/**
* @ignore
*/
onFocus: PropTypes.func,
/**
* @ignore
*/
onKeyDown: PropTypes.func,
/**
* @ignore
*/
onKeyUp: PropTypes.func,
/**
* The short hint displayed in the input before the user enters a value.
*/
placeholder: PropTypes.string,
/**
* It prevents the user from changing the value of the field
* (not from interacting with the field).
*/
readOnly: PropTypes.bool,
/**
* @ignore
*/
renderPrefix: PropTypes.func,
/**
* If `true`, the `input` element will be required.
*/
required: PropTypes.bool,
/**
* Number of rows to display when multiline option is set to true.
*/
rows: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Maximum number of rows to display when multiline option is set to true.
*/
rowsMax: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Should be `true` when the component hosts a select.
*/
select: PropTypes.bool,
/**
* Start `InputAdornment` for this component.
*/
startAdornment: PropTypes.node,
/**
* 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).
*/
type: PropTypes.string,
/**
* The value of the `input` element, required for a controlled component.
*/
value: PropTypes.any
} : void 0;
export default withStyles(styles, {
name: 'MuiInputBase'
})(InputBase);