element-plus/packages/utils/vue/props/runtime.ts

123 lines
3.3 KiB
TypeScript
Raw Normal View History

2022-06-01 16:00:27 +08:00
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