123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- // copy from element-plus
- import { warn } from 'vue';
- import { isObject } from '@vue/shared';
- import { fromPairs } from 'lodash-es';
- import type { ExtractPropTypes, PropType } from '@vue/runtime-core';
- 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[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>(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;
|