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