From f4b1a38bd3160fed17abbc2e656025611f8459e9 Mon Sep 17 00:00:00 2001 From: saller Date: Thu, 6 Jul 2023 15:54:27 +0800 Subject: [PATCH] feat(comp:tree): expandIcon prop supports render function now (#1586) --- packages/components/config/src/types.ts | 4 +-- .../components/tree-select/docs/Api.zh.md | 6 ++++- packages/components/tree-select/src/types.ts | 13 ++++++++-- packages/components/tree/docs/Api.zh.md | 6 ++++- packages/components/tree/index.ts | 1 + .../tree/src/composables/useExpandable.ts | 25 ++++++++++++++++--- packages/components/tree/src/node/Expand.tsx | 25 +++++-------------- packages/components/tree/src/types.ts | 12 +++++++-- packages/pro/search/docs/Api.zh.md | 6 +++-- .../__snapshots__/proTree.spec.ts.snap | 2 +- packages/pro/tree/docs/Api.zh.md | 6 ++++- packages/pro/tree/src/types.ts | 5 ++-- 12 files changed, 74 insertions(+), 37 deletions(-) diff --git a/packages/components/config/src/types.ts b/packages/components/config/src/types.ts index d00738242..f2de0a7ff 100644 --- a/packages/components/config/src/types.ts +++ b/packages/components/config/src/types.ts @@ -44,7 +44,7 @@ import type { import type { TabsSize } from '@idux/components/tabs' import type { TagShape } from '@idux/components/tag' import type { TextareaAutoRows, TextareaResize } from '@idux/components/textarea' -import type { TreeNode } from '@idux/components/tree' +import type { TreeExpandIconRenderer, TreeNode } from '@idux/components/tree' import type { UploadFilesType, UploadIconType, UploadRequestMethod, UploadRequestOption } from '@idux/components/upload' import type { OverlayContainerType } from '@idux/components/utils' import type { VNode, VNodeChild } from 'vue' @@ -549,7 +549,7 @@ export interface TreeConfig { autoHeight: boolean blocked: boolean childrenKey: string - expandIcon: string | [string, string] + expandIcon: string | TreeExpandIconRenderer | [string, string] draggableIcon: string getKey: string | ((data: TreeNode) => any) labelKey: string diff --git a/packages/components/tree-select/docs/Api.zh.md b/packages/components/tree-select/docs/Api.zh.md index e09d57b8b..70e6c2999 100644 --- a/packages/components/tree-select/docs/Api.zh.md +++ b/packages/components/tree-select/docs/Api.zh.md @@ -24,7 +24,7 @@ | `draggableIcon` | 拖拽节点图标 | `string \| #draggableIcon` | `holder` | - | - | | `droppable` | 是否允许放置节点,参见[TreeDroppable](/components/tree/zh#TreeDroppable) | `TreeDroppable` | - | - | - | | `empty` | 空数据时的内容 | `'default' \| 'simple' \| [EmptyProps](/components/empty/zh#EmptyProps)` | `'simple'` | - | - | -| `expandIcon` | 树组件中的展开图标 | `string \| [string, string] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `right` | ✅ | 当为数组时表示[`展开时图标`,`未展开时图标`] | +| `expandIcon` | 树组件中的展开图标 | `string \| TreeExpandIconRenderer \| [string, string] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `right` | ✅ | 当为数组时表示[`展开时图标`,`未展开时图标`] | | `getKey` | 获取数据的唯一标识 | `string \| (record: any) => VKey` | `key` | ✅ | - | | `labelKey` | 替代[TreeSelectNode](#TreeSelectNode)中的`label`字段 | `string` | `label` | ✅ | - | `leafLineIcon` | 叶子节点的图标,用于替换默认的连接线 | `string` | - | - | 仅在 `showLine` 时生效 | @@ -65,6 +65,10 @@ | `onScrolledChange` | 滚动的位置发生变化 | `(startIndex: number, endIndex: number, visibleNodes: TreeSelectNode[]) => void` | - | - | 仅 `virtual` 模式下可用 | | `onScrolledBottom` | 滚动到底部时触发 | `() => void` | - | - | 仅 `virtual` 模式下可用 | +```ts +type TreeExpandIconRenderer = (data: { key: VKey; expanded: boolean; node: TreeNode }) => VNodeChild | string +``` + ##### TreeSelectNode | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | diff --git a/packages/components/tree-select/src/types.ts b/packages/components/tree-select/src/types.ts index 87ad110e4..94f3c6013 100644 --- a/packages/components/tree-select/src/types.ts +++ b/packages/components/tree-select/src/types.ts @@ -13,7 +13,13 @@ import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } import type { CascaderStrategy } from '@idux/components/cascader' import type { EmptyProps } from '@idux/components/empty' import type { FormSize } from '@idux/components/form' -import type { TreeCustomAdditional, TreeDragDropOptions, TreeDroppable, TreeNode } from '@idux/components/tree' +import type { + TreeCustomAdditional, + TreeDragDropOptions, + TreeDroppable, + TreeExpandIconRenderer, + TreeNode, +} from '@idux/components/tree' import type { OverlayContainerType } from '@idux/components/utils' import type { DefineComponent, HTMLAttributes, PropType, VNode, VNodeChild } from 'vue' @@ -42,7 +48,10 @@ export const treeSelectProps = { draggableIcon: { type: String, default: undefined }, droppable: { type: Function as PropType, default: undefined }, empty: { type: [String, Object] as PropType<'default' | 'simple' | EmptyProps>, default: 'simple' }, - expandIcon: { type: [String, Array] as PropType, default: undefined }, + expandIcon: { + type: [String, Function, Array] as PropType, + default: undefined, + }, getKey: { type: [String, Function] as PropType) => any)>, default: undefined }, labelKey: { type: String, default: undefined }, leafLineIcon: { type: String, default: undefined }, diff --git a/packages/components/tree/docs/Api.zh.md b/packages/components/tree/docs/Api.zh.md index 163568435..4616f7b4f 100644 --- a/packages/components/tree/docs/Api.zh.md +++ b/packages/components/tree/docs/Api.zh.md @@ -22,7 +22,7 @@ | `draggableIcon` | 拖拽节点图标 | `string \| #draggableIcon` | `holder` | ✅ | - | | `droppable` | 是否允许放置节点,参见[TreeDroppable](#TreeDroppable) | `TreeDroppable` | - | - | - | | `empty` | 空数据时的内容 | `'default' \| 'simple' \| EmptyProps` | `'simple'` | - | - | -| `expandIcon` | 展开图标 | `string \| [string, string] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `right` | ✅ | 当为数组时表示[`展开时图标`,`未展开时图标`] | +| `expandIcon` | 展开图标 | `string \| \| TreeExpandIconRenderer \| [string, string] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `right` | ✅ | 当为数组时表示[`展开时图标`,`未展开时图标`] | | `getKey` | 获取数据的唯一标识 | `string \| (record: any) => VKey` | `key` | ✅ | - | | `height` | 设置虚拟滚动容器高度 | `number` | - | - | - | | `labelKey` | 替代[TreeNode](#TreeNode)中的`label`字段 | `string` | `label` | ✅ | - @@ -52,6 +52,10 @@ | `onScrolledChange` | 滚动的位置发生变化 | `(startIndex: number, endIndex: number, visibleNodes: TreeNode[]) => void` | - | - | 仅 `virtual` 模式下可用 | | `onScrolledBottom` | 滚动到底部时触发 | `() => void` | - | - | 仅 `virtual` 模式下可用 | +```ts +type TreeExpandIconRenderer = (data: { key: VKey; expanded: boolean; node: TreeNode }) => VNodeChild | string +``` + ##### TreeNode | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | diff --git a/packages/components/tree/index.ts b/packages/components/tree/index.ts index 641d7ef75..724920242 100644 --- a/packages/components/tree/index.ts +++ b/packages/components/tree/index.ts @@ -24,5 +24,6 @@ export type { TreeDroppableOptions, TreeDragDropOptions, TreeDropType, + TreeExpandIconRenderer, CheckStrategy as TreeCheckStrategy, } from './src/types' diff --git a/packages/components/tree/src/composables/useExpandable.ts b/packages/components/tree/src/composables/useExpandable.ts index 6bc4c2dce..5ffba8816 100644 --- a/packages/components/tree/src/composables/useExpandable.ts +++ b/packages/components/tree/src/composables/useExpandable.ts @@ -5,18 +5,21 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import { type ComputedRef, type Ref, computed, ref, watch } from 'vue' +import { type ComputedRef, type Ref, computed, h, ref, watch } from 'vue' + +import { isArray, isFunction, isString } from 'lodash-es' import { VKey, callEmit, useControlledProp } from '@idux/cdk/utils' import { type TreeConfig } from '@idux/components/config' +import { IxIcon } from '@idux/components/icon' import { type GetKeyFn } from '@idux/components/utils' import { type MergedNode, convertMergeNodes, convertMergedNodeMap } from './useDataSource' -import { type TreeNode, type TreeProps } from '../types' +import { type TreeExpandIconRenderer, type TreeNode, type TreeProps } from '../types' import { callChange, getParentKeys } from '../utils' export interface ExpandableContext { - expandIcon: ComputedRef + expandIconRenderer: TreeExpandIconRenderer expandedKeys: ComputedRef setExpandedKeys: (value: VKey[]) => void expandAll: () => void @@ -39,6 +42,20 @@ export function useExpandable( const [loadedKeys, setLoadedKeys] = useControlledProp(props, 'loadedKeys', () => []) const loadingKeys = ref([]) + const expandIconRenderer: TreeExpandIconRenderer = ({ key, expanded, node }) => { + if (isString(expandIcon.value)) { + return h(IxIcon, { name: expandIcon.value, rotate: expanded ? 90 : 0 }) + } + + if (isFunction(expandIcon.value)) { + return expandIcon.value({ key, expanded, node }) + } + + if (isArray(expandIcon.value)) { + return h(IxIcon, { name: expanded ? expandIcon.value[0] : expandIcon.value[1] }) + } + } + const setExpandWithSearch = (searchedKeys?: VKey[]) => { if (!searchedKeys || searchedKeys.length <= 0) { return @@ -132,5 +149,5 @@ export function useExpandable( setExpandWithSearch(searchedKeys.value) } - return { expandIcon, expandedKeys, setExpandedKeys, expandAll, collapseAll, handleExpand, loadingKeys } + return { expandIconRenderer, expandedKeys, setExpandedKeys, expandAll, collapseAll, handleExpand, loadingKeys } } diff --git a/packages/components/tree/src/node/Expand.tsx b/packages/components/tree/src/node/Expand.tsx index f23dfae78..c507c5f07 100644 --- a/packages/components/tree/src/node/Expand.tsx +++ b/packages/components/tree/src/node/Expand.tsx @@ -5,12 +5,10 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { VNodeTypes } from 'vue' +import type { VNodeChild } from 'vue' import { computed, defineComponent, inject, normalizeClass } from 'vue' -import { isArray } from 'lodash-es' - import { IxIcon } from '@idux/components/icon' import { treeToken } from '../token' @@ -19,7 +17,7 @@ import { treeNodeExpandProps } from '../types' export default defineComponent({ props: treeNodeExpandProps, setup(props) { - const { mergedPrefixCls, slots, expandIcon, loadingKeys, handleExpand } = inject(treeToken)! + const { mergedPrefixCls, slots, expandIconRenderer, loadingKeys, handleExpand } = inject(treeToken)! const isLoading = computed(() => loadingKeys.value.includes(props.nodeKey)) const classes = computed(() => { @@ -38,25 +36,14 @@ export default defineComponent({ return () => { const prefixCls = `${mergedPrefixCls.value}-node-expand` - let children: VNodeTypes | undefined + let children: VNodeChild | undefined if (isLoading.value) { children = } else if (!props.isLeaf) { - const { expanded } = props - if (slots.expandIcon) { - const { nodeKey: key, rawNode: node } = props - children = slots.expandIcon({ key, expanded, node }) - } else { - const expandIconValue = expandIcon.value - const iconIsArray = isArray(expandIconValue) - children = ( - - ) - } + const { expanded, nodeKey: key, rawNode: node } = props + children = (slots.expandIcon ?? expandIconRenderer)?.({ key, expanded, node }) } + return ( {props.hasTopLine &&
} diff --git a/packages/components/tree/src/types.ts b/packages/components/tree/src/types.ts index 9a94c6a81..3a57b4bdc 100644 --- a/packages/components/tree/src/types.ts +++ b/packages/components/tree/src/types.ts @@ -12,9 +12,14 @@ import type { VirtualScrollToFn } from '@idux/cdk/scroll' import type { ExtractInnerPropTypes, ExtractPublicPropTypes, MaybeArray, VKey } from '@idux/cdk/utils' import type { CascaderStrategy } from '@idux/components/cascader' import type { EmptyProps } from '@idux/components/empty' -import type { DefineComponent, HTMLAttributes, PropType } from 'vue' +import type { DefineComponent, HTMLAttributes, PropType, VNodeChild } from 'vue' export type CheckStrategy = 'all' | 'parent' | 'child' +export type TreeExpandIconRenderer = (data: { + key: VKey + expanded: boolean + node: TreeNode +}) => VNodeChild | string export const treeProps = { autoHeight: { @@ -54,7 +59,10 @@ export const treeProps = { draggableIcon: { type: String, default: undefined }, droppable: Function as PropType, empty: { type: [String, Object] as PropType<'default' | 'simple' | EmptyProps>, default: 'simple' }, - expandIcon: { type: [String, Array] as PropType, default: undefined }, + expandIcon: { + type: [String, Object, Function, Array] as PropType, + default: undefined, + }, getKey: { type: [String, Function] as PropType) => any)>, default: undefined }, height: Number, labelKey: String, diff --git a/packages/pro/search/docs/Api.zh.md b/packages/pro/search/docs/Api.zh.md index ad34525e6..484168515 100644 --- a/packages/pro/search/docs/Api.zh.md +++ b/packages/pro/search/docs/Api.zh.md @@ -137,8 +137,8 @@ TreeSelectSearchFieldConfig | `draggable` | 是否可拖拽 | `boolean` | - | - | 详情参考[Tree](/components/tree/zh) | | `draggableIcon` | 拖拽图标 | `string` | - | - | 详情参考[Tree](/components/tree/zh) | | `customDraggableIcon` | 拖拽图标自定义渲染 | `string \| () => VNodeChild` | - | - | 值为string时为对应名称的插槽 | -| `expandIcon` | 展开收起图标 | `string \| [string, string]` | - | - | 详情参考[Tree](/components/tree/zh) | -| `customExpandIcon` | 展开收起图标自定义渲染 | `string \| (options: { key: VKey, expanded: boolean, node: TreeSelectPanelData }) => VNodeChild` | - | - | 值为string时为对应名称的插槽 | +| `expandIcon` | 展开收起图标 | `string \| TreeExpandIconRenderer \| [string, string]` | - | - | 详情参考[Tree](/components/tree/zh) | +| `customExpandIcon` | 展开收起图标自定义渲染 | `string \| TreeExpandIconRenderer` | - | - | 值为string时为对应名称的插槽 | | `showLine` | 是否展示连线 | `boolean` | - | - | 详情参考[Tree](/components/tree/zh) | | `searchable` | 是否支持筛选 | `boolean` | `false` | - | 默认不支持 | | `searchFn` | 搜索函数 | `(node: TreeSelectPanelData, searchValue?: string) => boolean` | - | - | 默认模糊匹配 | @@ -157,6 +157,8 @@ TreeSelectSearchFieldConfig | `onLoaded` | 子节点加载完毕时触发 | `((loadedKeys: any[], node: TreeSelectPanelData) => void) \| ((loadedKeys: any[], node: TreeSelectPanelData) => void)[]` | - | - | 详情参考[Tree](/components/tree/zh) | ```typescript +type TreeExpandIconRenderer = (data: { key: VKey; expanded: boolean; node: TreeNode }) => VNodeChild | string + type TreeSelectPanelData = TreeSelectNode & Required> & { children?: TreeSelectPanelData[] diff --git a/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap b/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap index 53d165261..37d830b31 100644 --- a/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap +++ b/packages/pro/tree/__tests__/__snapshots__/proTree.spec.ts.snap @@ -24,7 +24,7 @@ exports[`ProTree > render work 1`] = `
- +
diff --git a/packages/pro/tree/docs/Api.zh.md b/packages/pro/tree/docs/Api.zh.md index 2eda1aa29..57a80cdae 100644 --- a/packages/pro/tree/docs/Api.zh.md +++ b/packages/pro/tree/docs/Api.zh.md @@ -25,7 +25,7 @@ | `draggableIcon` | 拖拽节点图标 | `string \| #draggableIcon` | `holder` | - | - | | `droppable` | 是否允许放置节点,参见[TreeDroppable](#TreeDroppable) | `TreeDroppable` | - | - | - | | `empty` | 空数据时的内容 | `'default' \| 'simple' \| EmptyProps` | `'simple'` | - | - | -| `expandIcon` | 树节点展开图标 | `string \| [string, string] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `['minus-square', 'plus-square']` | - | 当为数组时表示[`展开时图标`,`未展开时图标`] | +| `expandIcon` | 树节点展开图标 | `string \| TreeExpandIconRenderer \| [string, string] \| #expandIcon="{key: VKey, expanded: boolean, node: TreeNode}"` | `['minus-square', 'plus-square']` | - | 当为数组时表示[`展开时图标`,`未展开时图标`] | | `header` | 树的头部 | `string \| HeaderProps \| #header="{expanded, onClick}"` | - | - | - | | `height` | 设置虚拟滚动容器高度 | `number` | - | - | - | | `getKey` | 获取数据的唯一标识 | `string \| (record: any) => VKey` | `key` | - | - | @@ -54,6 +54,10 @@ | `onSearch` | 开启搜索功能后,输入后的回调 | `(searchValue: string) => void` | - | - | 通常用于服务端搜索 | | `onNodeContextmenu` | 节点右击事件 | `(evt: Event, node: TreeNode) => void` | - | - | - | +```ts +type TreeExpandIconRenderer = (data: { key: VKey; expanded: boolean; node: TreeNode }) => VNodeChild | string +``` + #### ProTreeSlots | 名称 | 说明 | 参数类型 | 备注 | diff --git a/packages/pro/tree/src/types.ts b/packages/pro/tree/src/types.ts index 1a22cc550..58370c5bc 100644 --- a/packages/pro/tree/src/types.ts +++ b/packages/pro/tree/src/types.ts @@ -15,6 +15,7 @@ import type { TreeCustomAdditional, TreeDragDropOptions, TreeDroppable, + TreeExpandIconRenderer, TreeNode, TreeNodeDisabled, } from '@idux/components/tree' @@ -45,8 +46,8 @@ export const proTreeProps = { draggableIcon: { type: String, default: undefined }, droppable: { type: Function as PropType, default: undefined }, expandIcon: { - type: [String, Array] as PropType, - default: () => ['minus-square', 'plus-square'], + type: [String, Function, Array] as PropType, + default: undefined, }, getKey: { type: [String, Function] as PropType) => any)>, default: undefined }, header: { type: [String, Object] as PropType, default: undefined },