runtime.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { warn } from 'vue'
  2. import { fromPairs } from 'lodash-unified'
  3. import { isObject } from '../../types'
  4. import { hasOwn } from '../../objects'
  5. import type { PropType } from 'vue'
  6. import type { EpProp, EpPropConvert, EpPropFinalized, EpPropInput, EpPropMergeType, IfEpProp, IfNativePropType, NativePropType } from './types'
  7. export const epPropKey = '__epPropKey'
  8. export const definePropType = <T>(val: any): PropType<T> => val
  9. export const isEpProp = (val: unknown): val is EpProp<any, any, any> => isObject(val) && !!(val as any)[epPropKey]
  10. /**
  11. * @description Build prop. It can better optimize prop types
  12. * @description 生成 prop,能更好地优化类型
  13. * @example
  14. // limited options
  15. // the type will be PropType<'light' | 'dark'>
  16. buildProp({
  17. type: String,
  18. values: ['light', 'dark'],
  19. } as const)
  20. * @example
  21. // limited options and other types
  22. // the type will be PropType<'small' | 'large' | number>
  23. buildProp({
  24. type: [String, Number],
  25. values: ['small', 'large'],
  26. validator: (val: unknown): val is number => typeof val === 'number',
  27. } as const)
  28. @link see more: https://github.com/element-plus/element-plus/pull/3341
  29. */
  30. export const buildProp = <Type = never, Value = never, Validator = never, Default extends EpPropMergeType<Type, Value, Validator> = never, Required extends boolean = false>(
  31. prop: EpPropInput<Type, Value, Validator, Default, Required>,
  32. key?: string
  33. ): EpPropFinalized<Type, Value, Validator, Default, Required> => {
  34. // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
  35. if (!isObject(prop) || isEpProp(prop)) return prop as any
  36. const { values, required, default: defaultValue, type, validator } = prop
  37. const _validator =
  38. values || validator
  39. ? (val: unknown) => {
  40. let valid = false
  41. let allowedValues: unknown[] = []
  42. if (values) {
  43. allowedValues = Array.from(values)
  44. if (hasOwn(prop, 'default')) {
  45. allowedValues.push(defaultValue)
  46. }
  47. valid ||= allowedValues.includes(val)
  48. }
  49. if (validator) valid ||= validator(val)
  50. if (!valid && allowedValues.length > 0) {
  51. const allowValuesText = [...new Set(allowedValues)].map(value => JSON.stringify(value)).join(', ')
  52. warn(`Invalid prop: validation failed${key ? ` for prop "${key}"` : ''}. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`)
  53. }
  54. return valid
  55. }
  56. : undefined
  57. const epProp: any = {
  58. type,
  59. required: !!required,
  60. validator: _validator,
  61. [epPropKey]: true,
  62. }
  63. if (hasOwn(prop, 'default')) epProp.default = defaultValue
  64. return epProp
  65. }
  66. export const buildProps = <Props extends Record<string, { [epPropKey]: true } | NativePropType | EpPropInput<any, any, any, any, any>>>(
  67. props: Props
  68. ): {
  69. [K in keyof Props]: IfEpProp<Props[K], Props[K], IfNativePropType<Props[K], Props[K], EpPropConvert<Props[K]>>>
  70. } => fromPairs(Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)])) as any