props.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // copy from element-plus
  2. import { warn } from 'vue';
  3. import { fromPairs, isObject } from 'lodash-es';
  4. import type { ExtractPropTypes, PropType } from 'vue';
  5. import type { Mutable } from './types';
  6. const wrapperKey = Symbol();
  7. export type PropWrapper<T> = { [wrapperKey]: T };
  8. export const propKey = Symbol();
  9. type ResolveProp<T> = ExtractPropTypes<{
  10. key: { type: T; required: true };
  11. }>['key'];
  12. type ResolvePropType<T> = ResolveProp<T> extends { type: infer V } ? V : ResolveProp<T>;
  13. type ResolvePropTypeWithReadonly<T> = Readonly<T> extends Readonly<Array<infer A>>
  14. ? ResolvePropType<A[]>
  15. : ResolvePropType<T>;
  16. type IfUnknown<T, V> = [unknown] extends [T] ? V : T;
  17. export type BuildPropOption<T, D extends BuildPropType<T, V, C>, R, V, C> = {
  18. type?: T;
  19. values?: readonly V[];
  20. required?: R;
  21. default?: R extends true
  22. ? never
  23. : D extends Record<string, unknown> | Array<any>
  24. ? () => D
  25. : (() => D) | D;
  26. validator?: ((val: any) => val is C) | ((val: any) => boolean);
  27. };
  28. type _BuildPropType<T, V, C> =
  29. | (T extends PropWrapper<unknown>
  30. ? T[typeof wrapperKey]
  31. : [V] extends [never]
  32. ? ResolvePropTypeWithReadonly<T>
  33. : never)
  34. | V
  35. | C;
  36. export type BuildPropType<T, V, C> = _BuildPropType<
  37. IfUnknown<T, never>,
  38. IfUnknown<V, never>,
  39. IfUnknown<C, never>
  40. >;
  41. type _BuildPropDefault<T, D> = [T] extends [
  42. // eslint-disable-next-line @typescript-eslint/ban-types
  43. Record<string, unknown> | Array<any> | Function,
  44. ]
  45. ? D
  46. : D extends () => T
  47. ? ReturnType<D>
  48. : D;
  49. export type BuildPropDefault<T, D, R> = R extends true
  50. ? { readonly default?: undefined }
  51. : {
  52. readonly default: Exclude<D, undefined> extends never
  53. ? undefined
  54. : Exclude<_BuildPropDefault<T, D>, undefined>;
  55. };
  56. export type BuildPropReturn<T, D, R, V, C> = {
  57. readonly type: PropType<BuildPropType<T, V, C>>;
  58. readonly required: IfUnknown<R, false>;
  59. readonly validator: ((val: unknown) => boolean) | undefined;
  60. [propKey]: true;
  61. } & BuildPropDefault<BuildPropType<T, V, C>, IfUnknown<D, never>, IfUnknown<R, false>>;
  62. /**
  63. * @description Build prop. It can better optimize prop types
  64. * @description 生成 prop,能更好地优化类型
  65. * @example
  66. // limited options
  67. // the type will be PropType<'light' | 'dark'>
  68. buildProp({
  69. type: String,
  70. values: ['light', 'dark'],
  71. } as const)
  72. * @example
  73. // limited options and other types
  74. // the type will be PropType<'small' | 'medium' | number>
  75. buildProp({
  76. type: [String, Number],
  77. values: ['small', 'medium'],
  78. validator: (val: unknown): val is number => typeof val === 'number',
  79. } as const)
  80. @link see more: https://github.com/element-plus/element-plus/pull/3341
  81. */
  82. export function buildProp<
  83. T = never,
  84. D extends BuildPropType<T, V, C> = never,
  85. R extends boolean = false,
  86. V = never,
  87. C = never,
  88. >(option: BuildPropOption<T, D, R, V, C>, key?: string): BuildPropReturn<T, D, R, V, C> {
  89. // filter native prop type and nested prop, e.g `null`, `undefined` (from `buildProps`)
  90. if (!isObject(option) || !!option[propKey]) return option as any;
  91. const { values, required, default: defaultValue, type, validator } = option;
  92. const _validator =
  93. values || validator
  94. ? (val: unknown) => {
  95. let valid = false;
  96. let allowedValues: unknown[] = [];
  97. if (values) {
  98. allowedValues = [...values, defaultValue];
  99. valid ||= allowedValues.includes(val);
  100. }
  101. if (validator) valid ||= validator(val);
  102. if (!valid && allowedValues.length > 0) {
  103. const allowValuesText = [...new Set(allowedValues)]
  104. .map((value) => JSON.stringify(value))
  105. .join(', ');
  106. warn(
  107. `Invalid prop: validation failed${
  108. key ? ` for prop "${key}"` : ''
  109. }. Expected one of [${allowValuesText}], got value ${JSON.stringify(val)}.`,
  110. );
  111. }
  112. return valid;
  113. }
  114. : undefined;
  115. return {
  116. type:
  117. typeof type === 'object' && Object.getOwnPropertySymbols(type).includes(wrapperKey) && type
  118. ? type[wrapperKey]
  119. : type,
  120. required: !!required,
  121. default: defaultValue,
  122. validator: _validator,
  123. [propKey]: true,
  124. } as unknown as BuildPropReturn<T, D, R, V, C>;
  125. }
  126. type NativePropType = [((...args: any) => any) | { new (...args: any): any } | undefined | null];
  127. export const buildProps = <
  128. O extends {
  129. [K in keyof O]: O[K] extends BuildPropReturn<any, any, any, any, any>
  130. ? O[K]
  131. : [O[K]] extends NativePropType
  132. ? O[K]
  133. : O[K] extends BuildPropOption<infer T, infer D, infer R, infer V, infer C>
  134. ? D extends BuildPropType<T, V, C>
  135. ? BuildPropOption<T, D, R, V, C>
  136. : never
  137. : never;
  138. },
  139. >(
  140. props: O,
  141. ) =>
  142. fromPairs(
  143. Object.entries(props).map(([key, option]) => [key, buildProp(option as any, key)]),
  144. ) as unknown as {
  145. [K in keyof O]: O[K] extends { [propKey]: boolean }
  146. ? O[K]
  147. : [O[K]] extends NativePropType
  148. ? O[K]
  149. : O[K] extends BuildPropOption<
  150. infer T,
  151. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  152. infer _D,
  153. infer R,
  154. infer V,
  155. infer C
  156. >
  157. ? BuildPropReturn<T, O[K]['default'], R, V, C>
  158. : never;
  159. };
  160. export const definePropType = <T>(val: any) => ({ [wrapperKey]: val }) as PropWrapper<T>;
  161. export const keyOf = <T extends object>(arr: T) => Object.keys(arr) as Array<keyof T>;
  162. export const mutable = <T extends readonly any[] | Record<string, unknown>>(val: T) =>
  163. val as Mutable<typeof val>;
  164. export const componentSize = ['large', 'medium', 'small', 'mini'] as const;