| // copy from element-plus |
| |
| import { warn } from 'vue'; |
| import { isObject } from '/@/utils/is'; |
| import { fromPairs } 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 |
| * @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 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; |