props.ts 5.6 KB

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