21 changed files with 1139 additions and 835 deletions
File diff suppressed because it is too large
@ -1,5 +1,6 @@ |
|||||
import BasicTree from './src/Tree.vue'; |
import BasicTree from './src/Tree.vue'; |
||||
|
import './style'; |
||||
|
|
||||
export { BasicTree }; |
export { BasicTree }; |
||||
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
export type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
||||
export * from './src/typing'; |
export * from './src/tree'; |
||||
|
|||||
@ -1,108 +0,0 @@ |
|||||
import type { PropType } from 'vue'; |
|
||||
import type { |
|
||||
ReplaceFields, |
|
||||
ActionItem, |
|
||||
Keys, |
|
||||
CheckKeys, |
|
||||
ContextMenuOptions, |
|
||||
TreeItem, |
|
||||
} from './typing'; |
|
||||
import type { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
|
||||
import type { TreeDataItem } from 'ant-design-vue/es/tree'; |
|
||||
import { propTypes } from '/@/utils/propTypes'; |
|
||||
|
|
||||
export const basicProps = { |
|
||||
value: { |
|
||||
type: [Object, Array] as PropType<Keys | CheckKeys>, |
|
||||
}, |
|
||||
renderIcon: { |
|
||||
type: Function as PropType<(params: Recordable) => string>, |
|
||||
}, |
|
||||
|
|
||||
helpMessage: { |
|
||||
type: [String, Array] as PropType<string | string[]>, |
|
||||
default: '', |
|
||||
}, |
|
||||
|
|
||||
title: propTypes.string, |
|
||||
toolbar: propTypes.bool, |
|
||||
search: propTypes.bool, |
|
||||
searchValue: propTypes.string, |
|
||||
checkStrictly: propTypes.bool, |
|
||||
clickRowToExpand: propTypes.bool.def(true), |
|
||||
checkable: propTypes.bool.def(false), |
|
||||
defaultExpandLevel: { |
|
||||
type: [String, Number] as PropType<string | number>, |
|
||||
default: '', |
|
||||
}, |
|
||||
defaultExpandAll: propTypes.bool.def(false), |
|
||||
|
|
||||
replaceFields: { |
|
||||
type: Object as PropType<ReplaceFields>, |
|
||||
}, |
|
||||
|
|
||||
treeData: { |
|
||||
type: Array as PropType<TreeDataItem[]>, |
|
||||
}, |
|
||||
|
|
||||
actionList: { |
|
||||
type: Array as PropType<ActionItem[]>, |
|
||||
default: () => [], |
|
||||
}, |
|
||||
|
|
||||
expandedKeys: { |
|
||||
type: Array as PropType<Keys>, |
|
||||
default: () => [], |
|
||||
}, |
|
||||
|
|
||||
selectedKeys: { |
|
||||
type: Array as PropType<Keys>, |
|
||||
default: () => [], |
|
||||
}, |
|
||||
|
|
||||
checkedKeys: { |
|
||||
type: Array as PropType<CheckKeys>, |
|
||||
default: () => [], |
|
||||
}, |
|
||||
|
|
||||
beforeRightClick: { |
|
||||
type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>, |
|
||||
default: null, |
|
||||
}, |
|
||||
|
|
||||
rightMenuList: { |
|
||||
type: Array as PropType<ContextMenuItem[]>, |
|
||||
}, |
|
||||
// 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式)
|
|
||||
filterFn: { |
|
||||
type: Function as PropType< |
|
||||
(searchValue: any, node: TreeItem, replaceFields: ReplaceFields) => boolean |
|
||||
>, |
|
||||
default: null, |
|
||||
}, |
|
||||
// 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启
|
|
||||
highlight: { |
|
||||
type: [Boolean, String] as PropType<Boolean | String>, |
|
||||
default: false, |
|
||||
}, |
|
||||
// 搜索完成时自动展开结果
|
|
||||
expandOnSearch: propTypes.bool.def(false), |
|
||||
// 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
|
|
||||
checkOnSearch: propTypes.bool.def(false), |
|
||||
// 搜索完成自动select所有结果
|
|
||||
selectedOnSearch: propTypes.bool.def(false), |
|
||||
}; |
|
||||
|
|
||||
export const treeNodeProps = { |
|
||||
actionList: { |
|
||||
type: Array as PropType<ActionItem[]>, |
|
||||
default: () => [], |
|
||||
}, |
|
||||
replaceFields: { |
|
||||
type: Object as PropType<ReplaceFields>, |
|
||||
}, |
|
||||
treeData: { |
|
||||
type: Array as PropType<TreeDataItem[]>, |
|
||||
default: () => [], |
|
||||
}, |
|
||||
}; |
|
||||
@ -0,0 +1,184 @@ |
|||||
|
import type { ExtractPropTypes } from 'vue'; |
||||
|
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree'; |
||||
|
|
||||
|
import { buildProps } from '/@/utils/props'; |
||||
|
|
||||
|
export enum ToolbarEnum { |
||||
|
SELECT_ALL, |
||||
|
UN_SELECT_ALL, |
||||
|
EXPAND_ALL, |
||||
|
UN_EXPAND_ALL, |
||||
|
CHECK_STRICTLY, |
||||
|
CHECK_UN_STRICTLY, |
||||
|
} |
||||
|
|
||||
|
export const treeEmits = [ |
||||
|
'update:expandedKeys', |
||||
|
'update:selectedKeys', |
||||
|
'update:value', |
||||
|
'change', |
||||
|
'check', |
||||
|
'update:searchValue', |
||||
|
]; |
||||
|
|
||||
|
export interface TreeState { |
||||
|
expandedKeys: KeyType[]; |
||||
|
selectedKeys: KeyType[]; |
||||
|
checkedKeys: CheckKeys; |
||||
|
checkStrictly: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface FieldNames { |
||||
|
children?: string; |
||||
|
title?: string; |
||||
|
key?: string; |
||||
|
} |
||||
|
|
||||
|
export type KeyType = string | number; |
||||
|
|
||||
|
export type CheckKeys = |
||||
|
| KeyType[] |
||||
|
| { checked: string[] | number[]; halfChecked: string[] | number[] }; |
||||
|
|
||||
|
export const treeProps = buildProps({ |
||||
|
value: { |
||||
|
type: [Object, Array] as PropType<KeyType[] | CheckKeys>, |
||||
|
}, |
||||
|
|
||||
|
renderIcon: { |
||||
|
type: Function as PropType<(params: Recordable) => string>, |
||||
|
}, |
||||
|
|
||||
|
helpMessage: { |
||||
|
type: [String, Array] as PropType<string | string[]>, |
||||
|
default: '', |
||||
|
}, |
||||
|
|
||||
|
title: { |
||||
|
type: String, |
||||
|
default: '', |
||||
|
}, |
||||
|
toolbar: Boolean, |
||||
|
search: Boolean, |
||||
|
searchValue: { |
||||
|
type: String, |
||||
|
default: '', |
||||
|
}, |
||||
|
checkStrictly: Boolean, |
||||
|
clickRowToExpand: { |
||||
|
type: Boolean, |
||||
|
default: false, |
||||
|
}, |
||||
|
checkable: Boolean, |
||||
|
defaultExpandLevel: { |
||||
|
type: [String, Number] as PropType<string | number>, |
||||
|
default: '', |
||||
|
}, |
||||
|
defaultExpandAll: Boolean, |
||||
|
|
||||
|
fieldNames: { |
||||
|
type: Object as PropType<FieldNames>, |
||||
|
}, |
||||
|
|
||||
|
treeData: { |
||||
|
type: Array as PropType<TreeDataItem[]>, |
||||
|
}, |
||||
|
|
||||
|
actionList: { |
||||
|
type: Array as PropType<TreeActionItem[]>, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
|
||||
|
expandedKeys: { |
||||
|
type: Array as PropType<KeyType[]>, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
|
||||
|
selectedKeys: { |
||||
|
type: Array as PropType<KeyType[]>, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
|
||||
|
checkedKeys: { |
||||
|
type: Array as PropType<CheckKeys>, |
||||
|
default: () => [], |
||||
|
}, |
||||
|
|
||||
|
beforeRightClick: { |
||||
|
type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
|
||||
|
rightMenuList: { |
||||
|
type: Array as PropType<ContextMenuItem[]>, |
||||
|
}, |
||||
|
// 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式)
|
||||
|
filterFn: { |
||||
|
type: Function as PropType< |
||||
|
(searchValue: any, node: TreeItem, replaceFields: FieldNames) => boolean |
||||
|
>, |
||||
|
default: undefined, |
||||
|
}, |
||||
|
// 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启
|
||||
|
highlight: { |
||||
|
type: [Boolean, String] as PropType<Boolean | String>, |
||||
|
default: false, |
||||
|
}, |
||||
|
// 搜索完成时自动展开结果
|
||||
|
expandOnSearch: Boolean, |
||||
|
// 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
|
||||
|
checkOnSearch: Boolean, |
||||
|
// 搜索完成自动select所有结果
|
||||
|
selectedOnSearch: Boolean, |
||||
|
}); |
||||
|
|
||||
|
export type TreeProps = ExtractPropTypes<typeof treeProps>; |
||||
|
|
||||
|
export interface ContextMenuItem { |
||||
|
label: string; |
||||
|
icon?: string; |
||||
|
disabled?: boolean; |
||||
|
handler?: Fn; |
||||
|
divider?: boolean; |
||||
|
children?: ContextMenuItem[]; |
||||
|
} |
||||
|
|
||||
|
export interface ContextMenuOptions { |
||||
|
icon?: string; |
||||
|
styles?: any; |
||||
|
items?: ContextMenuItem[]; |
||||
|
} |
||||
|
|
||||
|
export interface TreeItem extends TreeDataItem { |
||||
|
icon?: any; |
||||
|
} |
||||
|
|
||||
|
export interface TreeActionItem { |
||||
|
render: (record: Recordable) => any; |
||||
|
show?: boolean | ((record: Recordable) => boolean); |
||||
|
} |
||||
|
|
||||
|
export interface InsertNodeParams { |
||||
|
parentKey: string | null; |
||||
|
node: TreeDataItem; |
||||
|
list?: TreeDataItem[]; |
||||
|
push?: 'push' | 'unshift'; |
||||
|
} |
||||
|
|
||||
|
export interface TreeActionType { |
||||
|
checkAll: (checkAll: boolean) => void; |
||||
|
expandAll: (expandAll: boolean) => void; |
||||
|
setExpandedKeys: (keys: KeyType[]) => void; |
||||
|
getExpandedKeys: () => KeyType[]; |
||||
|
setSelectedKeys: (keys: KeyType[]) => void; |
||||
|
getSelectedKeys: () => KeyType[]; |
||||
|
setCheckedKeys: (keys: CheckKeys) => void; |
||||
|
getCheckedKeys: () => CheckKeys; |
||||
|
filterByLevel: (level: number) => void; |
||||
|
insertNodeByKey: (opt: InsertNodeParams) => void; |
||||
|
insertNodesByKey: (opt: InsertNodeParams) => void; |
||||
|
deleteNodeByKey: (key: string) => void; |
||||
|
updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void; |
||||
|
setSearchValue: (value: string) => void; |
||||
|
getSearchValue: () => string; |
||||
|
} |
||||
@ -1,56 +0,0 @@ |
|||||
import type { TreeDataItem, CheckEvent as CheckEventOrigin } from 'ant-design-vue/es/tree/Tree'; |
|
||||
|
|
||||
import { ContextMenuItem } from '/@/hooks/web/useContextMenu'; |
|
||||
|
|
||||
export interface ActionItem { |
|
||||
render: (record: Recordable) => any; |
|
||||
show?: boolean | ((record: Recordable) => boolean); |
|
||||
} |
|
||||
|
|
||||
export interface TreeItem extends TreeDataItem { |
|
||||
icon?: any; |
|
||||
} |
|
||||
|
|
||||
export interface ReplaceFields { |
|
||||
children?: string; |
|
||||
title?: string; |
|
||||
key?: string; |
|
||||
} |
|
||||
|
|
||||
export type Keys = (string | number)[]; |
|
||||
export type CheckKeys = |
|
||||
| (string | number)[] |
|
||||
| { checked: (string | number)[]; halfChecked: (string | number)[] }; |
|
||||
|
|
||||
export interface TreeActionType { |
|
||||
checkAll: (checkAll: boolean) => void; |
|
||||
expandAll: (expandAll: boolean) => void; |
|
||||
setExpandedKeys: (keys: Keys) => void; |
|
||||
getExpandedKeys: () => Keys; |
|
||||
setSelectedKeys: (keys: Keys) => void; |
|
||||
getSelectedKeys: () => Keys; |
|
||||
setCheckedKeys: (keys: CheckKeys) => void; |
|
||||
getCheckedKeys: () => CheckKeys; |
|
||||
filterByLevel: (level: number) => void; |
|
||||
insertNodeByKey: (opt: InsertNodeParams) => void; |
|
||||
insertNodesByKey: (opt: InsertNodeParams) => void; |
|
||||
deleteNodeByKey: (key: string) => void; |
|
||||
updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void; |
|
||||
setSearchValue: (value: string) => void; |
|
||||
getSearchValue: () => string; |
|
||||
} |
|
||||
|
|
||||
export interface InsertNodeParams { |
|
||||
parentKey: string | null; |
|
||||
node: TreeDataItem; |
|
||||
list?: TreeDataItem[]; |
|
||||
push?: 'push' | 'unshift'; |
|
||||
} |
|
||||
|
|
||||
export interface ContextMenuOptions { |
|
||||
icon?: string; |
|
||||
styles?: any; |
|
||||
items?: ContextMenuItem[]; |
|
||||
} |
|
||||
|
|
||||
export type CheckEvent = CheckEventOrigin; |
|
||||
@ -0,0 +1,49 @@ |
|||||
|
@tree-prefix-cls: ~'@{namespace}-tree'; |
||||
|
|
||||
|
.@{tree-prefix-cls} { |
||||
|
background-color: @component-background; |
||||
|
|
||||
|
.ant-tree-node-content-wrapper { |
||||
|
position: relative; |
||||
|
|
||||
|
.ant-tree-title { |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__title { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
padding-right: 10px; |
||||
|
|
||||
|
&:hover { |
||||
|
.@{tree-prefix-cls}__action { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&__content { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
&__actions { |
||||
|
position: absolute; |
||||
|
top: 2px; |
||||
|
right: 3px; |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
&__action { |
||||
|
margin-left: 4px; |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
|
||||
|
&-header { |
||||
|
border-bottom: 1px solid @border-color-base; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
import './index.less'; |
||||
@ -0,0 +1,52 @@ |
|||||
|
import { prefixCls } from '/@/settings/designSetting'; |
||||
|
|
||||
|
type Mod = string | { [key: string]: any }; |
||||
|
type Mods = Mod | Mod[]; |
||||
|
|
||||
|
export type BEM = ReturnType<typeof createBEM>; |
||||
|
|
||||
|
function genBem(name: string, mods?: Mods): string { |
||||
|
if (!mods) { |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
if (typeof mods === 'string') { |
||||
|
return ` ${name}--${mods}`; |
||||
|
} |
||||
|
|
||||
|
if (Array.isArray(mods)) { |
||||
|
return mods.reduce<string>((ret, item) => ret + genBem(name, item), ''); |
||||
|
} |
||||
|
|
||||
|
return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? genBem(name, key) : ''), ''); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* bem helper |
||||
|
* b() // 'button'
|
||||
|
* b('text') // 'button__text'
|
||||
|
* b({ disabled }) // 'button button--disabled'
|
||||
|
* b('text', { disabled }) // 'button__text button__text--disabled'
|
||||
|
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
|
||||
|
*/ |
||||
|
export function buildBEM(name: string) { |
||||
|
return (el?: Mods, mods?: Mods): Mods => { |
||||
|
if (el && typeof el !== 'string') { |
||||
|
mods = el; |
||||
|
el = ''; |
||||
|
} |
||||
|
|
||||
|
el = el ? `${name}__${el}` : name; |
||||
|
|
||||
|
return `${el}${genBem(el, mods)}`; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function createBEM(name: string) { |
||||
|
return [buildBEM(`${prefixCls}-${name}`)]; |
||||
|
} |
||||
|
|
||||
|
export function createNamespace(name: string) { |
||||
|
const prefixedName = `${prefixCls}-${name}`; |
||||
|
return [prefixedName, buildBEM(prefixedName)] as const; |
||||
|
} |
||||
@ -0,0 +1,185 @@ |
|||||
|
// 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; |
||||
@ -0,0 +1,42 @@ |
|||||
|
// copy from element-plus
|
||||
|
|
||||
|
import type { CSSProperties, Plugin } from 'vue'; |
||||
|
|
||||
|
type OptionalKeys<T extends Record<string, unknown>> = { |
||||
|
[K in keyof T]: T extends Record<K, T[K]> ? never : K; |
||||
|
}[keyof T]; |
||||
|
|
||||
|
type RequiredKeys<T extends Record<string, unknown>> = Exclude<keyof T, OptionalKeys<T>>; |
||||
|
|
||||
|
type MonoArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg?: T[K]) => void; |
||||
|
|
||||
|
type BiArgEmitter<T, Keys extends keyof T> = <K extends Keys>(evt: K, arg: T[K]) => void; |
||||
|
|
||||
|
export type EventEmitter<T extends Record<string, unknown>> = MonoArgEmitter<T, OptionalKeys<T>> & |
||||
|
BiArgEmitter<T, RequiredKeys<T>>; |
||||
|
|
||||
|
export type AnyFunction<T> = (...args: any[]) => T; |
||||
|
|
||||
|
export type PartialReturnType<T extends (...args: unknown[]) => unknown> = Partial<ReturnType<T>>; |
||||
|
|
||||
|
export type SFCWithInstall<T> = T & Plugin; |
||||
|
|
||||
|
export type Nullable<T> = T | null; |
||||
|
|
||||
|
export type RefElement = Nullable<HTMLElement>; |
||||
|
|
||||
|
export type CustomizedHTMLElement<T> = HTMLElement & T; |
||||
|
|
||||
|
export type Indexable<T> = { |
||||
|
[key: string]: T; |
||||
|
}; |
||||
|
|
||||
|
export type Hash<T> = Indexable<T>; |
||||
|
|
||||
|
export type TimeoutHandle = ReturnType<typeof global.setTimeout>; |
||||
|
|
||||
|
export type ComponentSize = 'large' | 'medium' | 'small' | 'mini'; |
||||
|
|
||||
|
export type StyleValue = string | CSSProperties | Array<StyleValue>; |
||||
|
|
||||
|
export type Mutable<T> = { -readonly [P in keyof T]: T[P] }; |
||||
Loading…
Reference in new issue