init project
Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled

This commit is contained in:
how2ice
2025-12-12 14:26:25 +09:00
commit 005cf56baf
43188 changed files with 1079531 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
import * as React from 'react';
import { SxProps } from '@mui/system';
import { OverridableStringUnion } from '@mui/types';
import { Theme } from '../styles';
import { InternalStandardProps as StandardProps } from '../internal';
import { InputBaseClasses } from './inputBaseClasses';
export interface InputBasePropsSizeOverrides {}
export interface InputBasePropsColorOverrides {}
export interface InputBaseComponentsPropsOverrides {}
export interface InputBaseProps
extends StandardProps<
React.HTMLAttributes<HTMLDivElement>,
/*
* `onBlur`, `onChange`, `onFocus`, `onInvalid`, `onKeyDown`, `onKeyUp` are applied to the inner `InputComponent`,
* which by default is an input or textarea. Since these handlers differ from the
* ones inherited by `React.HTMLAttributes<HTMLDivElement>` we need to omit them.
*/
| 'children'
| 'defaultValue'
| 'onBlur'
| 'onChange'
| 'onFocus'
| 'onInvalid'
| 'onKeyDown'
| 'onKeyUp'
> {
'aria-describedby'?: 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?: string;
/**
* If `true`, the `input` element is focused during the first mount.
*/
autoFocus?: boolean;
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<InputBaseClasses>;
/**
* The color of the component.
* It supports both default and custom theme colors, which can be added as shown in the
* [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
* The prop defaults to the value (`'primary'`) inherited from the parent FormControl component.
*/
color?: OverridableStringUnion<
'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning',
InputBasePropsColorOverrides
>;
/**
* The components used for each slot inside.
*
* @deprecated use the `slots` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*
* @default {}
*/
components?: {
Root?: React.ElementType;
Input?: React.ElementType;
};
/**
* The extra props for the slot components.
* You can override the existing props or add new ones.
*
* @deprecated use the `slotProps` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*
* @default {}
*/
componentsProps?: {
root?: React.HTMLAttributes<HTMLDivElement> & InputBaseComponentsPropsOverrides;
input?: React.InputHTMLAttributes<HTMLInputElement> & InputBaseComponentsPropsOverrides;
};
/**
* The default value. Use when the component is not controlled.
*/
defaultValue?: unknown;
/**
* If `true`, the component is disabled.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
disabled?: boolean;
/**
* If `true`, GlobalStyles for the auto-fill keyframes will not be injected/removed on mount/unmount. Make sure to inject them at the top of your application.
* This option is intended to help with boosting the initial rendering performance if you are loading a big amount of Input components at once.
* @default false
*/
disableInjectingGlobalStyles?: boolean;
/**
* End `InputAdornment` for this component.
*/
endAdornment?: React.ReactNode;
/**
* If `true`, the `input` will indicate an error.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
error?: boolean;
/**
* If `true`, the `input` will take up the full width of its container.
* @default false
*/
fullWidth?: boolean;
/**
* The id of the `input` element.
*/
id?: string;
/**
* The component used for the `input` element.
* Either a string to use a HTML element or a component.
* @default 'input'
*/
inputComponent?: React.ElementType<InputBaseComponentProps>;
/**
* [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#attributes) applied to the `input` element.
* @default {}
*/
inputProps?: InputBaseComponentProps;
/**
* Pass a ref to the `input` element.
*/
inputRef?: React.Ref<any>;
/**
* If `dense`, will adjust vertical spacing. This is normally obtained via context from
* FormControl.
* The prop defaults to the value (`'none'`) inherited from the parent FormControl component.
*/
margin?: 'dense' | 'none';
/**
* If `true`, a [TextareaAutosize](https://mui.com/material-ui/react-textarea-autosize/) element is rendered.
* @default false
*/
multiline?: boolean;
/**
* Name attribute of the `input` element.
*/
name?: string;
/**
* Callback fired when the `input` is blurred.
*
* Notice that the first argument (event) might be undefined.
*/
onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
/**
* Callback fired when the value is changed.
*
* @param {React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>} event The event source of the callback.
* You can pull out the new value by accessing `event.target.value` (string).
*/
onChange?: React.ChangeEventHandler<HTMLTextAreaElement | HTMLInputElement>;
onFocus?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement | HTMLInputElement>;
onKeyUp?: React.KeyboardEventHandler<HTMLTextAreaElement | HTMLInputElement>;
/**
* Callback fired when the `input` doesn't satisfy its constraints.
*/
onInvalid?: React.FormEventHandler<HTMLInputElement | HTMLTextAreaElement>;
/**
* The short hint displayed in the `input` before the user enters a value.
*/
placeholder?: string;
/**
* It prevents the user from changing the value of the field
* (not from interacting with the field).
*/
readOnly?: boolean;
/**
* If `true`, the `input` element is required.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
required?: boolean;
renderSuffix?: (state: {
disabled?: boolean;
error?: boolean;
filled?: boolean;
focused?: boolean;
margin?: 'dense' | 'none' | 'normal';
required?: boolean;
startAdornment?: React.ReactNode;
}) => React.ReactNode;
/**
* Number of rows to display when multiline option is set to true.
*/
rows?: string | number;
/**
* Maximum number of rows to display when multiline option is set to true.
*/
maxRows?: string | number;
/**
* Minimum number of rows to display when multiline option is set to true.
*/
minRows?: string | number;
/**
* The size of the component.
*/
size?: OverridableStringUnion<'small' | 'medium', InputBasePropsSizeOverrides>;
/**
* The extra props for the slot components.
* You can override the existing props or add new ones.
*
* This prop is an alias for the `componentsProps` prop, which will be deprecated in the future.
*
* @default {}
*/
slotProps?: {
root?: React.HTMLAttributes<HTMLDivElement> &
InputBaseComponentsPropsOverrides & { sx?: SxProps<Theme> };
input?: React.InputHTMLAttributes<HTMLInputElement> &
InputBaseComponentsPropsOverrides & { sx?: SxProps<Theme> };
};
/**
* The components used for each slot inside.
*
* This prop is an alias for the `components` prop, which will be deprecated in the future.
*
* @default {}
*/
slots?: {
root?: React.ElementType;
input?: React.ElementType;
};
/**
* Start `InputAdornment` for this component.
*/
startAdornment?: React.ReactNode;
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx?: SxProps<Theme>;
/**
* Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#input_types).
* @default 'text'
*/
type?: string;
/**
* The value of the `input` element, required for a controlled component.
*/
value?: unknown;
}
export interface InputBaseComponentProps
extends React.HTMLAttributes<HTMLInputElement | HTMLTextAreaElement> {
// Accommodate arbitrary additional props coming from the `inputProps` prop
[arbitrary: string]: any;
}
/**
* `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.
*
* Demos:
*
* - [Text Field](https://mui.com/material-ui/react-text-field/)
*
* API:
*
* - [InputBase API](https://mui.com/material-ui/api/input-base/)
*/
export default function InputBase(props: InputBaseProps): React.JSX.Element;

View File

@@ -0,0 +1,847 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef';
import refType from '@mui/utils/refType';
import composeClasses from '@mui/utils/composeClasses';
import isHostComponent from '@mui/utils/isHostComponent';
import TextareaAutosize from '../TextareaAutosize';
import formControlState from '../FormControl/formControlState';
import FormControlContext from '../FormControl/FormControlContext';
import useFormControl from '../FormControl/useFormControl';
import { styled, globalCss } from '../zero-styled';
import memoTheme from '../utils/memoTheme';
import { useDefaultProps } from '../DefaultPropsProvider';
import capitalize from '../utils/capitalize';
import useForkRef from '../utils/useForkRef';
import useEnhancedEffect from '../utils/useEnhancedEffect';
import { isFilled } from './utils';
import inputBaseClasses, { getInputBaseUtilityClass } from './inputBaseClasses';
export const rootOverridesResolver = (props, styles) => {
const { ownerState } = props;
return [
styles.root,
ownerState.formControl && styles.formControl,
ownerState.startAdornment && styles.adornedStart,
ownerState.endAdornment && styles.adornedEnd,
ownerState.error && styles.error,
ownerState.size === 'small' && styles.sizeSmall,
ownerState.multiline && styles.multiline,
ownerState.color && styles[`color${capitalize(ownerState.color)}`],
ownerState.fullWidth && styles.fullWidth,
ownerState.hiddenLabel && styles.hiddenLabel,
];
};
export const inputOverridesResolver = (props, styles) => {
const { ownerState } = props;
return [
styles.input,
ownerState.size === 'small' && styles.inputSizeSmall,
ownerState.multiline && styles.inputMultiline,
ownerState.type === 'search' && styles.inputTypeSearch,
ownerState.startAdornment && styles.inputAdornedStart,
ownerState.endAdornment && styles.inputAdornedEnd,
ownerState.hiddenLabel && styles.inputHiddenLabel,
];
};
const useUtilityClasses = (ownerState) => {
const {
classes,
color,
disabled,
error,
endAdornment,
focused,
formControl,
fullWidth,
hiddenLabel,
multiline,
readOnly,
size,
startAdornment,
type,
} = ownerState;
const slots = {
root: [
'root',
`color${capitalize(color)}`,
disabled && 'disabled',
error && 'error',
fullWidth && 'fullWidth',
focused && 'focused',
formControl && 'formControl',
size && size !== 'medium' && `size${capitalize(size)}`,
multiline && 'multiline',
startAdornment && 'adornedStart',
endAdornment && 'adornedEnd',
hiddenLabel && 'hiddenLabel',
readOnly && 'readOnly',
],
input: [
'input',
disabled && 'disabled',
type === 'search' && 'inputTypeSearch',
multiline && 'inputMultiline',
size === 'small' && 'inputSizeSmall',
hiddenLabel && 'inputHiddenLabel',
startAdornment && 'inputAdornedStart',
endAdornment && 'inputAdornedEnd',
readOnly && 'readOnly',
],
};
return composeClasses(slots, getInputBaseUtilityClass, classes);
};
export const InputBaseRoot = styled('div', {
name: 'MuiInputBase',
slot: 'Root',
overridesResolver: rootOverridesResolver,
})(
memoTheme(({ theme }) => ({
...theme.typography.body1,
color: (theme.vars || theme).palette.text.primary,
lineHeight: '1.4375em', // 23px
boxSizing: 'border-box', // Prevent padding issue with fullWidth.
position: 'relative',
cursor: 'text',
display: 'inline-flex',
alignItems: 'center',
[`&.${inputBaseClasses.disabled}`]: {
color: (theme.vars || theme).palette.text.disabled,
cursor: 'default',
},
variants: [
{
props: ({ ownerState }) => ownerState.multiline,
style: {
padding: '4px 0 5px',
},
},
{
props: ({ ownerState, size }) => ownerState.multiline && size === 'small',
style: {
paddingTop: 1,
},
},
{
props: ({ ownerState }) => ownerState.fullWidth,
style: {
width: '100%',
},
},
],
})),
);
export const InputBaseInput = styled('input', {
name: 'MuiInputBase',
slot: 'Input',
overridesResolver: inputOverridesResolver,
})(
memoTheme(({ theme }) => {
const light = theme.palette.mode === 'light';
const placeholder = {
color: 'currentColor',
...(theme.vars
? {
opacity: theme.vars.opacity.inputPlaceholder,
}
: {
opacity: light ? 0.42 : 0.5,
}),
transition: theme.transitions.create('opacity', {
duration: theme.transitions.duration.shorter,
}),
};
const placeholderHidden = {
opacity: '0 !important',
};
const placeholderVisible = theme.vars
? {
opacity: theme.vars.opacity.inputPlaceholder,
}
: {
opacity: light ? 0.42 : 0.5,
};
return {
font: 'inherit',
letterSpacing: 'inherit',
color: 'currentColor',
padding: '4px 0 5px',
border: 0,
boxSizing: 'content-box',
background: 'none',
height: '1.4375em', // Reset 23pxthe native input line-height
margin: 0, // Reset for Safari
WebkitTapHighlightColor: 'transparent',
display: 'block',
// Make the flex item shrink with Firefox
minWidth: 0,
width: '100%',
'&::-webkit-input-placeholder': placeholder,
'&::-moz-placeholder': placeholder, // Firefox 19+
'&::-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.
WebkitAppearance: 'none',
},
// Show and hide the placeholder logic
[`label[data-shrink=false] + .${inputBaseClasses.formControl} &`]: {
'&::-webkit-input-placeholder': placeholderHidden,
'&::-moz-placeholder': placeholderHidden, // Firefox 19+
'&::-ms-input-placeholder': placeholderHidden, // Edge
'&:focus::-webkit-input-placeholder': placeholderVisible,
'&:focus::-moz-placeholder': placeholderVisible, // Firefox 19+
'&:focus::-ms-input-placeholder': placeholderVisible, // Edge
},
[`&.${inputBaseClasses.disabled}`]: {
opacity: 1, // Reset iOS opacity
WebkitTextFillColor: (theme.vars || theme).palette.text.disabled, // Fix opacity Safari bug
},
variants: [
{
props: ({ ownerState }) => !ownerState.disableInjectingGlobalStyles,
style: {
animationName: 'mui-auto-fill-cancel',
animationDuration: '10ms',
'&:-webkit-autofill': {
animationDuration: '5000s',
animationName: 'mui-auto-fill',
},
},
},
{
props: {
size: 'small',
},
style: {
paddingTop: 1,
},
},
{
props: ({ ownerState }) => ownerState.multiline,
style: {
height: 'auto',
resize: 'none',
padding: 0,
paddingTop: 0,
},
},
{
props: {
type: 'search',
},
style: {
MozAppearance: 'textfield', // Improve type search style.
},
},
],
};
}),
);
const InputGlobalStyles = globalCss({
'@keyframes mui-auto-fill': { from: { display: 'block' } },
'@keyframes mui-auto-fill-cancel': { from: { display: 'block' } },
});
/**
* `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.
*/
const InputBase = React.forwardRef(function InputBase(inProps, ref) {
const props = useDefaultProps({ props: inProps, name: 'MuiInputBase' });
const {
'aria-describedby': ariaDescribedby,
autoComplete,
autoFocus,
className,
color,
components = {},
componentsProps = {},
defaultValue,
disabled,
disableInjectingGlobalStyles,
endAdornment,
error,
fullWidth = false,
id,
inputComponent = 'input',
inputProps: inputPropsProp = {},
inputRef: inputRefProp,
margin,
maxRows,
minRows,
multiline = false,
name,
onBlur,
onChange,
onClick,
onFocus,
onKeyDown,
onKeyUp,
placeholder,
readOnly,
renderSuffix,
rows,
size,
slotProps = {},
slots = {},
startAdornment,
type = 'text',
value: valueProp,
...other
} = props;
const value = inputPropsProp.value != null ? inputPropsProp.value : valueProp;
const { current: isControlled } = React.useRef(value != null);
const inputRef = React.useRef();
const handleInputRefWarning = React.useCallback((instance) => {
if (process.env.NODE_ENV !== 'production') {
if (instance && instance.nodeName !== 'INPUT' && !instance.focus) {
console.error(
[
'MUI: You have provided a `inputComponent` to the input component',
'that does not correctly handle the `ref` prop.',
'Make sure the `ref` prop is called with a HTMLInputElement.',
].join('\n'),
);
}
}
}, []);
const handleInputRef = useForkRef(
inputRef,
inputRefProp,
inputPropsProp.ref,
handleInputRefWarning,
);
const [focused, setFocused] = React.useState(false);
const muiFormControl = useFormControl();
if (process.env.NODE_ENV !== 'production') {
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/rules-of-hooks
React.useEffect(() => {
if (muiFormControl) {
return muiFormControl.registerEffect();
}
return undefined;
}, [muiFormControl]);
}
const fcs = formControlState({
props,
muiFormControl,
states: ['color', 'disabled', 'error', 'hiddenLabel', 'size', '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(() => {
if (!muiFormControl && disabled && focused) {
setFocused(false);
if (onBlur) {
onBlur();
}
}
}, [muiFormControl, disabled, focused, onBlur]);
const onFilled = muiFormControl && muiFormControl.onFilled;
const onEmpty = muiFormControl && muiFormControl.onEmpty;
const checkDirty = React.useCallback(
(obj) => {
if (isFilled(obj)) {
if (onFilled) {
onFilled();
}
} else if (onEmpty) {
onEmpty();
}
},
[onFilled, onEmpty],
);
useEnhancedEffect(() => {
if (isControlled) {
checkDirty({ value });
}
}, [value, checkDirty, isControlled]);
const handleFocus = (event) => {
if (onFocus) {
onFocus(event);
}
if (inputPropsProp.onFocus) {
inputPropsProp.onFocus(event);
}
if (muiFormControl && muiFormControl.onFocus) {
muiFormControl.onFocus(event);
} else {
setFocused(true);
}
};
const handleBlur = (event) => {
if (onBlur) {
onBlur(event);
}
if (inputPropsProp.onBlur) {
inputPropsProp.onBlur(event);
}
if (muiFormControl && muiFormControl.onBlur) {
muiFormControl.onBlur(event);
} else {
setFocused(false);
}
};
const handleChange = (event, ...args) => {
if (!isControlled) {
const element = event.target || inputRef.current;
if (element == null) {
throw /* minify-error */ new Error(
'MUI: Expected valid input target. ' +
'Did you use a custom `inputComponent` and forget to forward refs? ' +
'See https://mui.com/r/input-component-ref-interface for more info.',
);
}
checkDirty({
value: element.value,
});
}
if (inputPropsProp.onChange) {
inputPropsProp.onChange(event, ...args);
}
// Perform in the willUpdate
if (onChange) {
onChange(event, ...args);
}
};
// Check the input state on mount, in case it was filled by the user
// or auto filled by the browser before the hydration (for SSR).
React.useEffect(() => {
checkDirty(inputRef.current);
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleClick = (event) => {
if (inputRef.current && event.currentTarget === event.target) {
inputRef.current.focus();
}
if (onClick) {
onClick(event);
}
};
let InputComponent = inputComponent;
let inputProps = inputPropsProp;
if (multiline && InputComponent === 'input') {
if (rows) {
if (process.env.NODE_ENV !== 'production') {
if (minRows || maxRows) {
console.warn(
'MUI: You can not use the `minRows` or `maxRows` props when the input `rows` prop is set.',
);
}
}
inputProps = {
type: undefined,
minRows: rows,
maxRows: rows,
...inputProps,
};
} else {
inputProps = {
type: undefined,
maxRows,
minRows,
...inputProps,
};
}
InputComponent = TextareaAutosize;
}
const handleAutoFill = (event) => {
// Provide a fake value as Chrome might not let you access it for security reasons.
checkDirty(event.animationName === 'mui-auto-fill-cancel' ? inputRef.current : { value: 'x' });
};
React.useEffect(() => {
if (muiFormControl) {
muiFormControl.setAdornedStart(Boolean(startAdornment));
}
}, [muiFormControl, startAdornment]);
const ownerState = {
...props,
color: fcs.color || 'primary',
disabled: fcs.disabled,
endAdornment,
error: fcs.error,
focused: fcs.focused,
formControl: muiFormControl,
fullWidth,
hiddenLabel: fcs.hiddenLabel,
multiline,
size: fcs.size,
startAdornment,
type,
};
const classes = useUtilityClasses(ownerState);
const Root = slots.root || components.Root || InputBaseRoot;
const rootProps = slotProps.root || componentsProps.root || {};
const Input = slots.input || components.Input || InputBaseInput;
inputProps = { ...inputProps, ...(slotProps.input ?? componentsProps.input) };
return (
<React.Fragment>
{!disableInjectingGlobalStyles && typeof InputGlobalStyles === 'function' && (
// For Emotion/Styled-components, InputGlobalStyles will be a function
// For Pigment CSS, this has no effect because the InputGlobalStyles will be null.
<InputGlobalStyles />
)}
<Root
{...rootProps}
ref={ref}
onClick={handleClick}
{...other}
{...(!isHostComponent(Root) && {
ownerState: { ...ownerState, ...rootProps.ownerState },
})}
className={clsx(
classes.root,
{
// TODO v6: remove this class as it duplicates with the global state class Mui-readOnly
'MuiInputBase-readOnly': readOnly,
},
rootProps.className,
className,
)}
>
{startAdornment}
<FormControlContext.Provider value={null}>
<Input
aria-invalid={fcs.error}
aria-describedby={ariaDescribedby}
autoComplete={autoComplete}
autoFocus={autoFocus}
defaultValue={defaultValue}
disabled={fcs.disabled}
id={id}
onAnimationStart={handleAutoFill}
name={name}
placeholder={placeholder}
readOnly={readOnly}
required={fcs.required}
rows={rows}
value={value}
onKeyDown={onKeyDown}
onKeyUp={onKeyUp}
type={type}
{...inputProps}
{...(!isHostComponent(Input) && {
as: InputComponent,
ownerState: { ...ownerState, ...inputProps.ownerState },
})}
ref={handleInputRef}
className={clsx(
classes.input,
{
// TODO v6: remove this class as it duplicates with the global state class Mui-readOnly
'MuiInputBase-readOnly': readOnly,
},
inputProps.className,
)}
onBlur={handleBlur}
onChange={handleChange}
onFocus={handleFocus}
/>
</FormControlContext.Provider>
{endAdornment}
{renderSuffix
? renderSuffix({
...fcs,
startAdornment,
})
: null}
</Root>
</React.Fragment>
);
});
InputBase.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the d.ts file and run `pnpm 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 is focused during the first mount.
*/
autoFocus: PropTypes.bool,
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* @ignore
*/
className: PropTypes.string,
/**
* The color of the component.
* It supports both default and custom theme colors, which can be added as shown in the
* [palette customization guide](https://mui.com/material-ui/customization/palette/#custom-colors).
* The prop defaults to the value (`'primary'`) inherited from the parent FormControl component.
*/
color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
PropTypes.oneOf(['primary', 'secondary', 'error', 'info', 'success', 'warning']),
PropTypes.string,
]),
/**
* The components used for each slot inside.
*
* @deprecated use the `slots` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*
* @default {}
*/
components: PropTypes.shape({
Input: PropTypes.elementType,
Root: PropTypes.elementType,
}),
/**
* The extra props for the slot components.
* You can override the existing props or add new ones.
*
* @deprecated use the `slotProps` prop instead. This prop will be removed in a future major release. See [Migrating from deprecated APIs](https://mui.com/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*
* @default {}
*/
componentsProps: PropTypes.shape({
input: PropTypes.object,
root: PropTypes.object,
}),
/**
* The default value. Use when the component is not controlled.
*/
defaultValue: PropTypes.any,
/**
* If `true`, the component is disabled.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
disabled: PropTypes.bool,
/**
* If `true`, GlobalStyles for the auto-fill keyframes will not be injected/removed on mount/unmount. Make sure to inject them at the top of your application.
* This option is intended to help with boosting the initial rendering performance if you are loading a big amount of Input components at once.
* @default false
*/
disableInjectingGlobalStyles: PropTypes.bool,
/**
* End `InputAdornment` for this component.
*/
endAdornment: PropTypes.node,
/**
* If `true`, the `input` will indicate an error.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
error: PropTypes.bool,
/**
* If `true`, the `input` will take up the full width of its container.
* @default false
*/
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 HTML element or a component.
* @default 'input'
*/
inputComponent: elementTypeAcceptingRef,
/**
* [Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#attributes) applied to the `input` element.
* @default {}
*/
inputProps: PropTypes.object,
/**
* Pass a ref to the `input` element.
*/
inputRef: refType,
/**
* If `dense`, will adjust vertical spacing. This is normally obtained via context from
* FormControl.
* The prop defaults to the value (`'none'`) inherited from the parent FormControl component.
*/
margin: PropTypes.oneOf(['dense', 'none']),
/**
* Maximum number of rows to display when multiline option is set to true.
*/
maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* Minimum number of rows to display when multiline option is set to true.
*/
minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* If `true`, a [TextareaAutosize](https://mui.com/material-ui/react-textarea-autosize/) element is rendered.
* @default false
*/
multiline: PropTypes.bool,
/**
* Name attribute of the `input` element.
*/
name: PropTypes.string,
/**
* Callback fired when the `input` is blurred.
*
* Notice that the first argument (event) might be undefined.
*/
onBlur: PropTypes.func,
/**
* Callback fired when the value is changed.
*
* @param {React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>} event The event source of the callback.
* You can pull out the new value by accessing `event.target.value` (string).
*/
onChange: PropTypes.func,
/**
* @ignore
*/
onClick: PropTypes.func,
/**
* @ignore
*/
onFocus: PropTypes.func,
/**
* Callback fired when the `input` doesn't satisfy its constraints.
*/
onInvalid: 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
*/
renderSuffix: PropTypes.func,
/**
* If `true`, the `input` element is required.
* The prop defaults to the value (`false`) inherited from the parent FormControl component.
*/
required: PropTypes.bool,
/**
* Number of rows to display when multiline option is set to true.
*/
rows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* The size of the component.
*/
size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
PropTypes.oneOf(['medium', 'small']),
PropTypes.string,
]),
/**
* The extra props for the slot components.
* You can override the existing props or add new ones.
*
* This prop is an alias for the `componentsProps` prop, which will be deprecated in the future.
*
* @default {}
*/
slotProps: PropTypes.shape({
input: PropTypes.object,
root: PropTypes.object,
}),
/**
* The components used for each slot inside.
*
* This prop is an alias for the `components` prop, which will be deprecated in the future.
*
* @default {}
*/
slots: PropTypes.shape({
input: PropTypes.elementType,
root: PropTypes.elementType,
}),
/**
* Start `InputAdornment` for this component.
*/
startAdornment: PropTypes.node,
/**
* The system prop that allows defining system overrides as well as additional CSS styles.
*/
sx: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
PropTypes.func,
PropTypes.object,
]),
/**
* Type of the `input` element. It should be [a valid HTML5 input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#input_types).
* @default 'text'
*/
type: PropTypes.string,
/**
* The value of the `input` element, required for a controlled component.
*/
value: PropTypes.any,
};
export default InputBase;

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import { expectType } from '@mui/types';
import InputBase from '@mui/material/InputBase';
<InputBase
onInvalid={(event) => {
expectType<React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, typeof event>(event);
}}
/>;
// Tests presence of `sx` prop on input and root slot
<InputBase
slotProps={{
input: {
sx: {
background: 'white',
},
},
root: {
sx: {
background: 'black',
},
},
}}
/>;

View File

@@ -0,0 +1,732 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import { expect } from 'chai';
import { spy } from 'sinon';
import {
act,
createRenderer,
fireEvent,
screen,
reactMajor,
isJsdom,
} from '@mui/internal-test-utils';
import { ThemeProvider } from '@emotion/react';
import FormControl, { useFormControl } from '@mui/material/FormControl';
import InputAdornment from '@mui/material/InputAdornment';
import TextField from '@mui/material/TextField';
import Select from '@mui/material/Select';
import InputBase, { inputBaseClasses as classes } from '@mui/material/InputBase';
import { createTheme } from '@mui/material/styles';
import describeConformance from '../../test/describeConformance';
describe('<InputBase />', () => {
const { render } = createRenderer();
describeConformance(<InputBase />, () => ({
classes,
inheritComponent: 'div',
render,
refInstanceof: window.HTMLDivElement,
muiName: 'MuiInputBase',
testVariantProps: { size: 'small' },
testLegacyComponentsProp: true,
slots: {
// can't test with DOM element as InputBase places an ownerState prop on it unconditionally.
root: { expectedClassName: classes.root, testWithElement: null },
input: { expectedClassName: classes.input, testWithElement: null },
},
skip: [
'componentProp',
'slotPropsCallback', // not supported yet
'slotPropsCallbackWithPropsAsOwnerState', // not supported yet
],
}));
it('should render an <input /> inside the div', () => {
const { container } = render(<InputBase />);
const input = container.querySelector('input');
expect(input).to.have.attribute('type', 'text');
expect(input).to.have.class(classes.input);
expect(input).not.to.have.attribute('required');
});
it('should add the right class when size is small', () => {
const { container } = render(<InputBase size="small" />);
expect(container.firstChild).to.have.class(classes.sizeSmall);
});
describe('multiline', () => {
it('should render a `textbox` with `aria-multiline`', () => {
render(<InputBase multiline />);
const textarea = screen.getByRole('textbox', { hidden: false });
// implicit `aria-multiline`
expect(textarea).to.have.tagName('textarea');
});
it('should render a `textbox` with `aria-multiline` if `rows` is specified', () => {
render(<InputBase multiline rows={4} />);
const textarea = screen.getByRole('textbox', { hidden: false });
// implicit `aria-multiline`
expect(textarea).to.have.tagName('textarea');
});
it('should forward the value to the textarea', () => {
render(<InputBase multiline maxRows={4} value="Hello" />);
const textarea = screen.getByRole('textbox', { hidden: false });
expect(textarea).to.have.value('Hello');
});
it('should preserve state when changing rows', () => {
const { setProps } = render(<InputBase multiline />);
const textarea = screen.getByRole('textbox', { hidden: false });
act(() => {
textarea.focus();
});
setProps({ rows: 4 });
expect(textarea).toHaveFocus();
});
});
describe('prop: disabled', () => {
it('should render a disabled <input />', () => {
const { container } = render(<InputBase disabled />);
const input = container.querySelector('input');
expect(input).to.have.class(classes.input);
expect(input).to.have.class(classes.disabled);
});
it('should reset the focused state if getting disabled', () => {
const handleBlur = spy();
const handleFocus = spy();
const { container, setProps } = render(
<InputBase onBlur={handleBlur} onFocus={handleFocus} />,
);
act(() => {
container.querySelector('input').focus();
});
expect(handleFocus.callCount).to.equal(1);
setProps({ disabled: true });
expect(handleBlur.callCount).to.equal(1);
// check if focus not initiated again
expect(handleFocus.callCount).to.equal(1);
});
it('fires the click event when the <input /> is disabled', () => {
const handleClick = spy();
render(<InputBase disabled onClick={handleClick} />);
const input = screen.getByRole('textbox');
fireEvent.click(input);
expect(handleClick.callCount).to.equal(1);
});
});
describe('prop: readonly', () => {
it('should render a readonly <input />', () => {
render(<InputBase readOnly />);
const input = screen.getByRole('textbox');
expect(input).to.have.class(classes.input);
expect(input).to.have.class(classes.readOnly);
expect(input).to.have.property('readOnly');
});
});
it('should fire event callbacks', () => {
const handleChange = spy();
const handleFocus = spy();
const handleBlur = spy();
const handleKeyUp = spy();
const handleKeyDown = spy();
render(
<InputBase
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyUp={handleKeyUp}
onKeyDown={handleKeyDown}
/>,
);
const input = screen.getByRole('textbox');
// simulating user input: gain focus, key input (keydown, (input), change, keyup), blur
act(() => {
input.focus();
});
expect(handleFocus.callCount).to.equal(1);
fireEvent.keyDown(input, { key: 'a' });
expect(handleKeyDown.callCount).to.equal(1);
fireEvent.change(input, { target: { value: 'a' } });
expect(handleChange.callCount).to.equal(1);
fireEvent.keyUp(input, { key: 'a' });
expect(handleKeyUp.callCount).to.equal(1);
act(() => {
input.blur();
});
expect(handleBlur.callCount).to.equal(1);
});
describe('controlled', () => {
it('should considered [] as controlled', () => {
render(<InputBase value={[]} />);
const input = screen.getByRole('textbox');
expect(input).to.have.property('value', '');
fireEvent.change(input, { target: { value: 'do not work' } });
expect(input).to.have.property('value', '');
});
});
describe('prop: inputComponent', () => {
it('should accept any html component', () => {
render(<InputBase inputComponent="span" inputProps={{ 'data-testid': 'input-component' }} />);
expect(screen.getByTestId('input-component')).to.have.property('nodeName', 'SPAN');
});
it('should inject onBlur and onFocus', () => {
let injectedProps;
const MyInputBase = React.forwardRef(function MyInputBase(props, ref) {
// TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler
injectedProps = props;
return <input ref={ref} {...props} />;
});
render(<InputBase inputComponent={MyInputBase} />);
expect(typeof injectedProps.onBlur).to.equal('function');
expect(typeof injectedProps.onFocus).to.equal('function');
});
describe('target mock implementations', () => {
it('can just mock the value', () => {
const MockedValue = React.forwardRef(function MockedValue(props, ref) {
const { onChange } = props;
const handleChange = (event) => {
onChange({ target: { value: event.target.value } });
};
return <input ref={ref} onChange={handleChange} />;
});
MockedValue.propTypes = { onChange: PropTypes.func.isRequired };
function FilledState(props) {
const { filled } = useFormControl();
return <span {...props}>filled: {String(filled)}</span>;
}
render(
<FormControl>
<FilledState data-testid="filled" />
<InputBase inputComponent={MockedValue} />
</FormControl>,
);
expect(screen.getByTestId('filled')).to.have.text('filled: false');
fireEvent.change(screen.getByRole('textbox'), { target: { value: 1 } });
expect(screen.getByTestId('filled')).to.have.text('filled: true');
});
it("can expose the input component's ref through the inputComponent prop", () => {
const FullTarget = React.forwardRef(function FullTarget(props, ref) {
return <input ref={ref} {...props} />;
});
function FilledState(props) {
const { filled } = useFormControl();
return <span {...props}>filled: {String(filled)}</span>;
}
render(
<FormControl>
<FilledState data-testid="filled" />
<InputBase inputComponent={FullTarget} />
</FormControl>,
);
expect(screen.getByTestId('filled')).to.have.text('filled: false');
fireEvent.change(screen.getByRole('textbox'), { target: { value: 1 } });
expect(screen.getByTestId('filled')).to.have.text('filled: true');
});
});
describe('errors', () => {
it("throws on change if the target isn't mocked", () => {
/**
* This component simulates a custom input component that hides the inner
* input value for security reasons e.g. react-stripe-element.
*
* A ref is exposed to trigger a change event instead of using fireEvent.change
*/
const BadInputComponent = React.forwardRef(function BadInputComponent(props, ref) {
const { onChange } = props;
// simulates const handleChange = () => onChange({}) and passing that
// handler to the onChange prop of `input`
React.useImperativeHandle(ref, () => () => onChange({}));
return <input />;
});
BadInputComponent.propTypes = {
onChange: PropTypes.func.isRequired,
};
const triggerChangeRef = React.createRef();
const errorMessage =
'MUI: You have provided a `inputComponent` to the input component\nthat does not correctly handle the `ref` prop.\nMake sure the `ref` prop is called with a HTMLInputElement.';
let expectedOccurrences = 1;
if (reactMajor >= 18) {
expectedOccurrences = 2;
}
expect(() => {
render(
<InputBase inputProps={{ ref: triggerChangeRef }} inputComponent={BadInputComponent} />,
);
}).toErrorDev(Array(expectedOccurrences).fill(errorMessage));
});
});
});
describe('with FormControl', () => {
it('should have the formControl class', () => {
render(
<FormControl>
<InputBase data-testid="root" />
</FormControl>,
);
expect(screen.getByTestId('root')).to.have.class(classes.formControl);
});
describe('callbacks', () => {
it('should fire the onClick prop', () => {
const handleClick = spy();
const handleFocus = spy();
render(
<FormControl>
<InputBase data-testid="root" onClick={handleClick} onFocus={handleFocus} />
</FormControl>,
);
fireEvent.click(screen.getByTestId('root'));
expect(handleClick.callCount).to.equal(1);
expect(handleFocus.callCount).to.equal(1);
});
});
describe('error', () => {
it('should be overridden by props', () => {
function InputBaseInErrorForm(props) {
return (
<FormControl error>
<InputBase data-testid="root" {...props} />
</FormControl>
);
}
const { setProps } = render(<InputBaseInErrorForm />);
expect(screen.getByTestId('root')).to.have.class(classes.error);
setProps({ error: false });
expect(screen.getByTestId('root')).not.to.have.class(classes.error);
setProps({ error: true });
expect(screen.getByTestId('root')).to.have.class(classes.error);
});
});
describe('size', () => {
it('should have the inputSizeSmall class in a dense context', () => {
const { container } = render(
<FormControl size="small">
<InputBase />
</FormControl>,
);
expect(container.querySelector('input')).to.have.class(classes.inputSizeSmall);
});
it('should be overridden by props', () => {
function InputBaseInFormWithMargin(props) {
return (
<FormControl size="medium">
<InputBase {...props} />
</FormControl>
);
}
const { container, setProps } = render(<InputBaseInFormWithMargin />);
expect(container.querySelector('input')).not.to.have.class(classes.inputSizeSmall);
setProps({ size: 'small' });
expect(container.querySelector('input')).to.have.class(classes.inputSizeSmall);
});
it('has an inputHiddenLabel class to further reduce margin', () => {
render(
<FormControl hiddenLabel margin="dense">
<InputBase />
</FormControl>,
);
expect(screen.getByRole('textbox')).to.have.class(classes.inputHiddenLabel);
});
});
describe('required', () => {
it('should have the aria-required prop with value true', () => {
const { container } = render(
<FormControl required>
<InputBase />
</FormControl>,
);
const input = container.querySelector('input');
expect(input).to.have.property('required', true);
});
});
describe('focused', () => {
it('prioritizes context focus', () => {
const FormController = React.forwardRef((props, ref) => {
const { onBlur, onFocus } = useFormControl();
React.useImperativeHandle(ref, () => ({ onBlur, onFocus }), [onBlur, onFocus]);
return null;
});
const controlRef = React.createRef();
render(
<FormControl>
<FormController ref={controlRef} />
<InputBase data-testid="root" />
</FormControl>,
);
act(() => {
screen.getByRole('textbox').focus();
});
expect(screen.getByTestId('root')).to.have.class(classes.focused);
act(() => {
controlRef.current.onBlur();
});
expect(screen.getByTestId('root')).not.to.have.class(classes.focused);
act(() => {
controlRef.current.onFocus();
});
expect(screen.getByTestId('root')).to.have.class(classes.focused);
});
it('propagates focused state', () => {
function FocusedStateLabel(props) {
const { focused } = useFormControl();
return <label {...props}>focused: {String(focused)}</label>;
}
render(
<FormControl>
<FocusedStateLabel data-testid="label" htmlFor="input" />
<InputBase id="input" />
</FormControl>,
);
expect(screen.getByTestId('label')).to.have.text('focused: false');
act(() => {
screen.getByRole('textbox').focus();
});
expect(screen.getByTestId('label')).to.have.text('focused: true');
act(() => {
screen.getByRole('textbox').blur();
});
expect(screen.getByTestId('label')).to.have.text('focused: false');
});
});
it('propagates filled state when uncontrolled', () => {
function FilledStateLabel(props) {
const { filled } = useFormControl();
return <label {...props}>filled: {String(filled)}</label>;
}
render(
<FormControl>
<FilledStateLabel data-testid="label" />
<InputBase />
</FormControl>,
);
expect(screen.getByTestId('label')).to.have.text('filled: false');
const textbox = screen.getByRole('textbox');
fireEvent.change(textbox, { target: { value: 'material' } });
expect(screen.getByTestId('label')).to.have.text('filled: true');
fireEvent.change(textbox, { target: { value: '0' } });
expect(screen.getByTestId('label')).to.have.text('filled: true');
fireEvent.change(textbox, { target: { value: '' } });
expect(screen.getByTestId('label')).to.have.text('filled: false');
});
it('propagates filled state when controlled', () => {
function FilledStateLabel(props) {
const { filled } = useFormControl();
return <label {...props}>filled: {String(filled)}</label>;
}
function ControlledInputBase(props) {
return (
<FormControl>
<FilledStateLabel data-testid="label" />
<InputBase {...props} />
</FormControl>
);
}
const { setProps } = render(<ControlledInputBase value="" />);
expect(screen.getByTestId('label')).to.have.text('filled: false');
setProps({ value: 'material' });
expect(screen.getByTestId('label')).to.have.text('filled: true');
setProps({ value: 0 });
expect(screen.getByTestId('label')).to.have.text('filled: true');
setProps({ value: '' });
expect(screen.getByTestId('label')).to.have.text('filled: false');
});
describe('registering input', () => {
it("should warn if more than one input is rendered regardless how it's nested", () => {
const errorMessage =
'MUI: There are multiple `InputBase` components inside a FormControl.\nThis creates visual inconsistencies, only use one `InputBase`.';
let expectedOccurrences = 1;
if (reactMajor >= 18) {
expectedOccurrences = 2;
}
expect(() => {
render(
<FormControl>
<InputBase />
<div>
{/* should work regardless how it's nested */}
<InputBase />
</div>
</FormControl>,
);
}).toErrorDev(Array(expectedOccurrences).fill(errorMessage));
});
it('should not warn if only one input is rendered', () => {
expect(() => {
render(
<FormControl>
<InputBase />
</FormControl>,
);
}).not.toErrorDev();
});
it('should not warn when toggling between inputs', () => {
// this will ensure that deregistering was called during unmount
function ToggleFormInputs() {
const [flag, setFlag] = React.useState(true);
return (
<FormControl>
{flag ? (
<InputBase />
) : (
<Select native>
<option value="">empty</option>
</Select>
)}
<button type="button" onClick={() => setFlag(!flag)}>
toggle
</button>
</FormControl>
);
}
render(<ToggleFormInputs />);
expect(() => {
fireEvent.click(screen.getByText('toggle'));
}).not.toErrorDev();
});
});
});
describe('prop: inputProps', () => {
it('should apply the props on the input', () => {
const { container } = render(<InputBase inputProps={{ className: 'foo', maxLength: 5 }} />);
const input = container.querySelector('input');
expect(input).to.have.class('foo');
expect(input).to.have.class(classes.input);
expect(input).to.have.property('maxLength', 5);
});
it('should be able to get a ref', () => {
const inputRef = React.createRef();
const { container } = render(<InputBase inputProps={{ ref: inputRef }} />);
expect(inputRef.current).to.equal(container.querySelector('input'));
});
it('should not repeat the same classname', () => {
const { container } = render(<InputBase inputProps={{ className: 'foo' }} />);
const input = container.querySelector('input');
const matches = input.className.match(/foo/g);
expect(input).to.have.class('foo');
expect(matches).to.have.length(1);
});
});
describe('prop: inputComponent with prop: inputProps', () => {
it('should call onChange inputProp callback with all params sent from custom inputComponent', () => {
const INPUT_VALUE = 'material';
const OUTPUT_VALUE = 'test';
const MyInputBase = React.forwardRef(function MyInputBase(props, ref) {
const { onChange, ...other } = props;
const handleChange = (event) => {
onChange(event.target.value, OUTPUT_VALUE);
};
return <input ref={ref} onChange={handleChange} {...other} />;
});
MyInputBase.propTypes = {
onChange: PropTypes.func.isRequired,
};
let outputArguments;
function parentHandleChange(...args) {
outputArguments = args;
}
render(
<InputBase inputComponent={MyInputBase} inputProps={{ onChange: parentHandleChange }} />,
);
const textbox = screen.getByRole('textbox');
fireEvent.change(textbox, { target: { value: INPUT_VALUE } });
expect(outputArguments.length).to.equal(2);
expect(outputArguments[0]).to.equal(INPUT_VALUE);
expect(outputArguments[1]).to.equal(OUTPUT_VALUE);
});
});
describe('prop: startAdornment, prop: endAdornment', () => {
it('should render adornment before input', () => {
render(
<InputBase
startAdornment={
<InputAdornment data-testid="adornment" position="start">
$
</InputAdornment>
}
/>,
);
expect(screen.getByTestId('adornment')).not.to.equal(null);
});
it('should render adornment after input', () => {
render(
<InputBase
endAdornment={
<InputAdornment data-testid="adornment" position="end">
$
</InputAdornment>
}
/>,
);
expect(screen.getByTestId('adornment')).not.to.equal(null);
});
it('should allow a Select as an adornment', () => {
render(
<TextField
value=""
name="text"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Select value="" name="suffix" />
</InputAdornment>
),
}}
variant="standard"
/>,
);
});
});
describe('prop: inputRef', () => {
it('should be able to access the native input', () => {
const inputRef = React.createRef();
const { container } = render(<InputBase inputRef={inputRef} />);
expect(inputRef.current).to.equal(container.querySelector('input'));
});
it('should be able to access the native textarea', () => {
const inputRef = React.createRef();
const { container } = render(<InputBase multiline inputRef={inputRef} />);
expect(inputRef.current).to.equal(container.querySelector('textarea'));
});
});
describe('prop: focused', () => {
it.skipIf(isJsdom())(
'should render correct border color with `ThemeProvider` imported from `@emotion/react`',
function test() {
const theme = createTheme({
palette: {
primary: {
main: 'rgb(0, 191, 165)',
},
},
});
render(
<ThemeProvider theme={theme}>
<TextField focused label="Your email" />
</ThemeProvider>,
);
const fieldset = screen.getByRole('textbox').nextSibling;
expect(fieldset).toHaveComputedStyle({
borderTopColor: 'rgb(0, 191, 165)',
borderRightColor: 'rgb(0, 191, 165)',
borderBottomColor: 'rgb(0, 191, 165)',
borderLeftColor: 'rgb(0, 191, 165)',
});
},
);
});
});

View File

@@ -0,0 +1,5 @@
export { default } from './InputBase';
export * from './InputBase';
export { default as inputBaseClasses } from './inputBaseClasses';
export * from './inputBaseClasses';

View File

@@ -0,0 +1,4 @@
export { default } from './InputBase';
export { default as inputBaseClasses } from './inputBaseClasses';
export * from './inputBaseClasses';

View File

@@ -0,0 +1,85 @@
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
export interface InputBaseClasses {
/** Styles applied to the root element. */
root: string;
/** Styles applied to the root element if the component is a descendant of `FormControl`. */
formControl: string;
/** Styles applied to the root element if the component is focused. */
focused: string;
/** Styles applied to the root element if `disabled={true}`. */
disabled: string;
/** Styles applied to the root element if `startAdornment` is provided. */
adornedStart: string;
/** Styles applied to the root element if `endAdornment` is provided. */
adornedEnd: string;
/** State class applied to the root element if `error={true}`. */
error: string;
/** Styles applied to the input element if `size="small"`. */
sizeSmall: string;
/** Styles applied to the root element if `multiline={true}`. */
multiline: string;
/** Styles applied to the root element if the color is secondary. */
colorSecondary: string;
/** Styles applied to the root element if `fullWidth={true}`. */
fullWidth: string;
/** Styles applied to the root element if `hiddenLabel={true}`. */
hiddenLabel: string;
/** State class applied to the root element if `readOnly={true}`. */
readOnly: string;
/** Styles applied to the input element. */
input: string;
/** Styles applied to the input element if `size="small"`.
* @deprecated Combine the [.MuiInputBase-input](/material-ui/api/input-base/#input-base-classes-MuiInputBase-input) and [.MuiInputBase-sizeSmall](/material-ui/api/input-base/#input-base-classes-MuiInputBase-sizeSmall) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
inputSizeSmall: string;
/** Styles applied to the input element if `multiline={true}`.
* @deprecated Combine the [.MuiInputBase-input](/material-ui/api/input-base/#input-base-classes-MuiInputBase-input) and [.MuiInputBase-multiline](/material-ui/api/input-base/#input-base-classes-MuiInputBase-multiline) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
inputMultiline: string;
inputTypeSearch: string;
/** Styles applied to the input element if `startAdornment` is provided.
* @deprecated Combine the [.MuiInputBase-input](/material-ui/api/input-base/#input-base-classes-MuiInputBase-input) and [.MuiInputBase-adornedStart](/material-ui/api/input-base/#input-base-classes-MuiInputBase-adornedStart) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
inputAdornedStart: string;
/** Styles applied to the input element if `endAdornment` is provided.
* @deprecated Combine the [.MuiInputBase-input](/material-ui/api/input-base/#input-base-classes-MuiInputBase-input) and [.MuiInputBase-adornedEnd](/material-ui/api/input-base/#input-base-classes-MuiInputBase-adornedEnd) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
inputAdornedEnd: string;
/** Styles applied to the input element if `hiddenLabel={true}`.
* @deprecated Combine the [.MuiInputBase-input](/material-ui/api/input-base/#input-base-classes-MuiInputBase-input) and [.MuiInputBase-hiddenLabel](/material-ui/api/input-base/#input-base-classes-MuiInputBase-hiddenLabel) classes instead. See [Migrating from deprecated APIs](/material-ui/migration/migrating-from-deprecated-apis/) for more details.
*/
inputHiddenLabel: string;
}
export type InputBaseClassKey = keyof InputBaseClasses;
export function getInputBaseUtilityClass(slot: string): string {
return generateUtilityClass('MuiInputBase', slot);
}
const inputBaseClasses: InputBaseClasses = generateUtilityClasses('MuiInputBase', [
'root',
'formControl',
'focused',
'disabled',
'adornedStart',
'adornedEnd',
'error',
'sizeSmall',
'multiline',
'colorSecondary',
'fullWidth',
'hiddenLabel',
'readOnly',
'input',
'inputSizeSmall',
'inputMultiline',
'inputTypeSearch',
'inputAdornedStart',
'inputAdornedEnd',
'inputHiddenLabel',
]);
export default inputBaseClasses;

View File

@@ -0,0 +1,34 @@
// Supports determination of isControlled().
// Controlled input accepts its current value as a prop.
//
// @see https://facebook.github.io/react/docs/forms.html#controlled-components
// @param value
// @returns {boolean} true if string (including '') or number (including zero)
export function hasValue(value) {
return value != null && !(Array.isArray(value) && value.length === 0);
}
// Determine if field is empty or filled.
// Response determines if label is presented above field or as placeholder.
//
// @param obj
// @param SSR
// @returns {boolean} False when not present or empty string.
// True when any number or string with length.
export function isFilled(obj, SSR = false) {
return (
obj &&
((hasValue(obj.value) && obj.value !== '') ||
(SSR && hasValue(obj.defaultValue) && obj.defaultValue !== ''))
);
}
// Determine if an Input is adorned on start.
// It's corresponding to the left with LTR.
//
// @param obj
// @returns {boolean} False when no adornments.
// True when adorned at the start.
export function isAdornedStart(obj) {
return obj.startAdornment;
}

View File

@@ -0,0 +1,39 @@
import { expect } from 'chai';
import { hasValue, isFilled } from './utils';
describe('Input/utils.js', () => {
describe('hasValue', () => {
['', 0].forEach((value) => {
it(`is true for ${value}`, () => {
expect(hasValue(value)).to.equal(true);
});
});
[null, undefined].forEach((value) => {
it(`is false for ${value}`, () => {
expect(hasValue(value)).to.equal(false);
});
});
});
describe('isFilled', () => {
[' ', 0].forEach((value) => {
it(`is true for value ${value}`, () => {
expect(isFilled({ value })).to.equal(true);
});
it(`is true for SSR defaultValue ${value}`, () => {
expect(isFilled({ defaultValue: value }, true)).to.equal(true);
});
});
[null, undefined, ''].forEach((value) => {
it(`is false for value ${value}`, () => {
expect(isFilled({ value })).to.equal(false);
});
it(`is false for SSR defaultValue ${value}`, () => {
expect(isFilled({ defaultValue: value }, true)).to.equal(false);
});
});
});
});