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