runtime.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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 {
  7. EpProp,
  8. EpPropConvert,
  9. EpPropFinalized,
  10. EpPropInput,
  11. EpPropMergeType,
  12. IfEpProp,
  13. IfNativePropType,
  14. NativePropType,
  15. } from './types'
  16. export const epPropKey = '__epPropKey'
  17. export const definePropType = <T>(val: any): PropType<T> => val
  18. export const isEpProp = (val: unknown): val is EpProp<any, any, any> =>
  19. isObject(val) && !!(val as any)[epPropKey]
  20. /**
  21. * @description Build prop. It can better optimize prop types
  22. * @description 生成 prop,能更好地优化类型
  23. * @example
  24. // limited options
  25. // the type will be PropType<'light' | 'dark'>
  26. buildProp({
  27. type: String,
  28. values: ['light', 'dark'],
  29. } as const)
  30. * @example
  31. // limited options and other types
  32. // the type will be PropType<'small' | 'large' | number>
  33. buildProp({
  34. type: [String, Number],
  35. values: ['small', 'large'],
  36. validator: (val: unknown): val is number => typeof val === 'number',
  37. } as const)
  38. @link see more: https://github.com/element-plus/element-plus/pull/3341
  39. */
  40. export const buildProp = <
  41. Type = never,
  42. Value = never,
  43. Validator = never,
  44. Default extends EpPropMergeType<Type, Value, Validator> = never,
  45. Required extends boolean = false
  46. >(
  47. prop: EpPropInput<Type, Value, Validator, Default, Required>,
  48. key?: string
  49. ): EpPropFinalized<Type, Value, Validator, Default, Required> => {
  50. // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
  51. if (!isObject(prop) || isEpProp(prop)) return prop as any
  52. const { values, required, default: defaultValue, type, validator } = prop
  53. const _validator =
  54. values || validator
  55. ? (val: unknown) => {
  56. let valid = false
  57. let allowedValues: unknown[] = []
  58. if (values) {
  59. allowedValues = Array.from(values)
  60. if (hasOwn(prop, 'default')) {
  61. allowedValues.push(defaultValue)
  62. }
  63. valid ||= allowedValues.includes(val)
  64. }
  65. if (validator) valid ||= validator(val)
  66. if (!valid && allowedValues.length > 0) {
  67. const allowValuesText = [...new Set(allowedValues)]
  68. .map((value) => JSON.stringify(value))
  69. .join(', ')
  70. warn(
  71. `Invalid prop: validation failed${
  72. key ? ` for prop "${key}"` : ''
  73. }. Expected one of [${allowValuesText}], got value ${JSON.stringify(
  74. val
  75. )}.`
  76. )
  77. }
  78. return valid
  79. }
  80. : undefined
  81. const epProp: any = {
  82. type,
  83. required: !!required,
  84. validator: _validator,
  85. [epPropKey]: true,
  86. }
  87. if (hasOwn(prop, 'default')) epProp.default = defaultValue
  88. return epProp
  89. }
  90. export const buildProps = <
  91. Props extends Record<
  92. string,
  93. | { [epPropKey]: true }
  94. | NativePropType
  95. | EpPropInput<any, any, any, any, any>
  96. >
  97. >(
  98. props: Props
  99. ): {
  100. [K in keyof Props]: IfEpProp<
  101. Props[K],
  102. Props[K],
  103. IfNativePropType<Props[K], Props[K], EpPropConvert<Props[K]>>
  104. >
  105. } =>
  106. fromPairs(
  107. Object.entries(props).map(([key, option]) => [
  108. key,
  109. buildProp(option as any, key),
  110. ])
  111. ) as any