mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-15 09:50:58 +08:00
123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
import { warn } from 'vue'
|
||
import { fromPairs } from 'lodash-unified'
|
||
import { isObject } from '../../types'
|
||
import { hasOwn } from '../../objects'
|
||
|
||
import type { PropType } from 'vue'
|
||
import type {
|
||
EpProp,
|
||
EpPropConvert,
|
||
EpPropFinalized,
|
||
EpPropInput,
|
||
EpPropMergeType,
|
||
IfEpProp,
|
||
IfNativePropType,
|
||
NativePropType,
|
||
} from './types'
|
||
|
||
export const epPropKey = '__epPropKey'
|
||
|
||
export const definePropType = <T>(val: any): PropType<T> => val
|
||
|
||
export const isEpProp = (val: unknown): val is EpProp<any, any, any> =>
|
||
isObject(val) && !!(val as any)[epPropKey]
|
||
|
||
/**
|
||
* @description Build prop. It can better optimize prop types
|
||
* @description 生成 prop,能更好地优化类型
|
||
* @example
|
||
// limited options
|
||
// the type will be PropType<'light' | 'dark'>
|
||
buildProp({
|
||
type: String,
|
||
values: ['light', 'dark'],
|
||
} as const)
|
||
* @example
|
||
// limited options and other types
|
||
// the type will be PropType<'small' | 'large' | number>
|
||
buildProp({
|
||
type: [String, Number],
|
||
values: ['small', 'large'],
|
||
validator: (val: unknown): val is number => typeof val === 'number',
|
||
} as const)
|
||
@link see more: https://github.com/element-plus/element-plus/pull/3341
|
||
*/
|
||
export const buildProp = <
|
||
Type = never,
|
||
Value = never,
|
||
Validator = never,
|
||
Default extends EpPropMergeType<Type, Value, Validator> = never,
|
||
Required extends boolean = false
|
||
>(
|
||
prop: EpPropInput<Type, Value, Validator, Default, Required>,
|
||
key?: string
|
||
): EpPropFinalized<Type, Value, Validator, Default, Required> => {
|
||
// filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
|
||
if (!isObject(prop) || isEpProp(prop)) return prop as any
|
||
|
||
const { values, required, default: defaultValue, type, validator } = prop
|
||
|
||
const _validator =
|
||
values || validator
|
||
? (val: unknown) => {
|
||
let valid = false
|
||
let allowedValues: unknown[] = []
|
||
|
||
if (values) {
|
||
allowedValues = Array.from(values)
|
||
if (hasOwn(prop, 'default')) {
|
||
allowedValues.push(defaultValue)
|
||
}
|
||
valid ||= allowedValues.includes(val)
|
||
}
|
||
if (validator) valid ||= validator(val)
|
||
|
||
if (!valid && allowedValues.length > 0) {
|
||
const allowValuesText = [...new Set(allowedValues)]
|
||
.map((value) => JSON.stringify(value))
|
||
.join(', ')
|
||
warn(
|
||
`Invalid prop: validation failed${
|
||
key ? ` for prop "${key}"` : ''
|
||
}. Expected one of [${allowValuesText}], got value ${JSON.stringify(
|
||
val
|
||
)}.`
|
||
)
|
||
}
|
||
return valid
|
||
}
|
||
: undefined
|
||
|
||
const epProp: any = {
|
||
type,
|
||
required: !!required,
|
||
validator: _validator,
|
||
[epPropKey]: true,
|
||
}
|
||
if (hasOwn(prop, 'default')) epProp.default = defaultValue
|
||
return epProp
|
||
}
|
||
|
||
export const buildProps = <
|
||
Props extends Record<
|
||
string,
|
||
| { [epPropKey]: true }
|
||
| NativePropType
|
||
| EpPropInput<any, any, any, any, any>
|
||
>
|
||
>(
|
||
props: Props
|
||
): {
|
||
[K in keyof Props]: IfEpProp<
|
||
Props[K],
|
||
Props[K],
|
||
IfNativePropType<Props[K], Props[K], EpPropConvert<Props[K]>>
|
||
>
|
||
} =>
|
||
fromPairs(
|
||
Object.entries(props).map(([key, option]) => [
|
||
key,
|
||
buildProp(option as any, key),
|
||
])
|
||
) as any
|