///
/**
* InputBase Component - Lynx 版 MUI InputBase
* 100% 一比一复刻 MUI InputBase
*
* InputBase 是所有输入组件的基础,包含最少的样式
* 用于构建 Input, OutlinedInput, FilledInput 等
*
* 对应 MUI: packages/mui-material/src/InputBase/InputBase.js
*/
import './InputBase.css'
import inputBaseClasses, { getInputBaseUtilityClass } from './inputBaseClasses'
export { inputBaseClasses, getInputBaseUtilityClass }
// =============================================
// 类型定义 - 对应 MUI InputBase.d.ts
// =============================================
export type InputBaseColor = 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning'
export type InputBaseSize = 'small' | 'medium'
export type InputBaseMargin = 'dense' | 'none'
export interface InputBaseProps {
/** aria-describedby */
'aria-describedby'?: string
/** 自动完成 */
autoComplete?: string
/** 自动聚焦 */
autoFocus?: boolean
/** 自定义类名 */
className?: string
/** 样式类覆盖 */
classes?: Partial
/** 颜色 */
color?: InputBaseColor
/** 默认值 */
defaultValue?: any
/** 是否禁用 */
disabled?: boolean
/** 结束装饰器 */
endAdornment?: any
/** 是否错误状态 */
error?: boolean
/** 是否全宽 */
fullWidth?: boolean
/** ID */
id?: string
/** 输入组件 */
inputComponent?: any
/** 输入属性 */
inputProps?: Record
/** 输入引用 */
inputRef?: any
/** 边距 */
margin?: InputBaseMargin
/** 最大行数 (多行时) */
maxRows?: number
/** 最小行数 (多行时) */
minRows?: number
/** 是否多行 */
multiline?: boolean
/** 名称 */
name?: string
/** 失焦事件 */
onBlur?: (event?: any) => void
/** 值变化事件 */
onChange?: (event?: any) => void
/** 点击事件 */
onClick?: (event?: any) => void
/** 聚焦事件 */
onFocus?: (event?: any) => void
/** 键盘按下事件 */
onKeyDown?: (event?: any) => void
/** 键盘抬起事件 */
onKeyUp?: (event?: any) => void
/** 占位符 */
placeholder?: string
/** 是否只读 */
readOnly?: boolean
/** 是否必填 */
required?: boolean
/** 行数 (多行时) */
rows?: number
/** 尺寸 */
size?: InputBaseSize
/** 开始装饰器 */
startAdornment?: any
/** 内联样式 */
style?: Record
/** sx 属性 */
sx?: Record
/** 输入类型 */
type?: string
/** 值 */
value?: any
/** 子元素 */
children?: any
}
// =============================================
// 辅助函数
// =============================================
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1)
}
function composeClasses(
slots: Record,
getUtilityClass: (slot: string) => string,
classes?: Record
): Record {
const output: Record = {}
Object.keys(slots).forEach((slot) => {
output[slot] = slots[slot]
.filter(Boolean)
.map((key) => {
if (classes && classes[key as string]) {
return `${getUtilityClass(key as string)} ${classes[key as string]}`
}
return getUtilityClass(key as string)
})
.join(' ')
})
return output
}
/**
* 检查输入是否有值
*/
export function isFilled(obj: any, SSR = false): boolean {
return (
obj &&
((hasValue(obj.value) && obj.value !== '') ||
(SSR && hasValue(obj.defaultValue) && obj.defaultValue !== ''))
)
}
function hasValue(value: any): boolean {
return value != null && !(Array.isArray(value) && value.length === 0)
}
// =============================================
// useUtilityClasses
// =============================================
interface OwnerState extends InputBaseProps {
focused?: boolean
formControl?: boolean
hiddenLabel?: boolean
}
function useUtilityClasses(ownerState: OwnerState) {
const {
classes,
color = 'primary',
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)
}
// =============================================
// InputBase 组件 - 完整实现
// =============================================
export function InputBase(props: InputBaseProps) {
const {
'aria-describedby': ariaDescribedby,
autoComplete,
autoFocus,
className,
classes: classesProp,
color = 'primary',
defaultValue,
disabled = false,
endAdornment,
error = false,
fullWidth = false,
id,
inputComponent = 'input',
inputProps = {},
inputRef,
margin,
maxRows,
minRows,
multiline = false,
name,
onBlur,
onChange,
onClick,
onFocus,
onKeyDown,
onKeyUp,
placeholder,
readOnly,
required,
rows,
size,
startAdornment,
style,
sx,
type = 'text',
value,
...other
} = props
// 状态
const focused = false // 在实际使用中通过 useState 管理
const ownerState: OwnerState = {
...props,
color,
disabled,
error,
focused,
formControl: false,
fullWidth,
hiddenLabel: false,
multiline,
size,
type,
}
const classes = useUtilityClasses(ownerState)
// 处理事件
const handleClick = (event: any) => {
if (onClick) {
onClick(event)
}
}
const handleFocus = (event: any) => {
if (onFocus) {
onFocus(event)
}
}
const handleBlur = (event: any) => {
if (onBlur) {
onBlur(event)
}
}
const handleChange = (event: any) => {
if (onChange) {
onChange(event)
}
}
// 构建根类名
const rootClasses = [
classes.root,
className,
disabled && inputBaseClasses.disabled,
error && inputBaseClasses.error,
focused && inputBaseClasses.focused,
readOnly && 'MuiInputBase-readOnly',
].filter(Boolean).join(' ')
// 构建输入类名
const inputClasses = [
classes.input,
readOnly && 'MuiInputBase-readOnly',
].filter(Boolean).join(' ')
// 输入属性 - 注意:Lynx 原生 input 不支持 value 属性(非受控组件)
// 使用 defaultValue 作为初始值,通过 bindinput 事件跟踪变化
const inputElementProps = {
'aria-invalid': error,
'aria-describedby': ariaDescribedby,
autoComplete,
autoFocus,
defaultValue: value !== undefined ? value : defaultValue, // 将 value 作为 defaultValue
disabled,
id,
name,
placeholder,
readOnly,
required,
type: multiline ? undefined : type,
// 注意:不传递 value 属性,因为 Lynx input 是非受控组件
...inputProps,
}
return (
{startAdornment}
{multiline ? (
) : (
)}
{endAdornment}
)
}
export default InputBase