import type { VNode } from 'vue'; import { defineComponent, inject } from 'vue'; import omit from 'omit.js'; import debounce from 'lodash-es/debounce'; import FolderOpenOutlined from '@ant-design/icons-vue/FolderOpenOutlined'; import FolderOutlined from '@ant-design/icons-vue/FolderOutlined'; import FileOutlined from '@ant-design/icons-vue/FileOutlined'; import PropTypes from '../_util/vue-types'; import classNames from '../_util/classNames'; import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util'; import type { CheckEvent, ExpendEvent, SelectEvent } from './Tree'; import Tree, { TreeProps } from './Tree'; import { calcRangeKeys, getFullKeyList, convertDirectoryKeysToNodes, getFullKeyListByTreeData, } from './util'; import BaseMixin from '../_util/BaseMixin'; import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; import initDefaultProps from '../_util/props-util/initDefaultProps'; import { defaultConfigProvider } from '../config-provider'; // export type ExpandAction = false | 'click' | 'dblclick'; export interface // DirectoryTreeProps extends TreeProps { expandAction?: ExpandAction; } // export interface DirectoryTreeState { expandedKeys?: string[]; // selectedKeys?: string[]; } export interface DirectoryTreeState { _expandedKeys?: (string | number)[]; _selectedKeys?: (string | number)[]; } function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) { const { isLeaf, expanded } = props; if (isLeaf) { return ; } return expanded ? : ; } export default defineComponent({ name: 'ADirectoryTree', mixins: [BaseMixin], inheritAttrs: false, props: initDefaultProps( { ...TreeProps(), expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']), }, { showIcon: true, expandAction: 'click', }, ), setup() { return { children: null, onDebounceExpand: null, tree: null, lastSelectedKey: '', cachedSelectedKeys: [], configProvider: inject('configProvider', defaultConfigProvider), }; }, data() { const props = getOptionProps(this); const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props; const children = getSlot(this); const { keyEntities } = convertTreeToEntities(children); const state: DirectoryTreeState = {}; // Selected keys state._selectedKeys = props.selectedKeys || props.defaultSelectedKeys || []; // Expanded keys if (defaultExpandAll) { if (props.treeData) { state._expandedKeys = getFullKeyListByTreeData(props.treeData, props.replaceFields); } else { state._expandedKeys = getFullKeyList(children); } } else if (defaultExpandParent) { state._expandedKeys = conductExpandParent(expandedKeys || defaultExpandedKeys, keyEntities); } else { state._expandedKeys = expandedKeys || defaultExpandedKeys; } return { _selectedKeys: [], _expandedKeys: [], ...state, }; }, watch: { expandedKeys(val) { this.setState({ _expandedKeys: val }); }, selectedKeys(val) { this.setState({ _selectedKeys: val }); }, }, created() { this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true }); }, methods: { handleExpand(expandedKeys: (string | number)[], info: ExpendEvent) { this.setUncontrolledState({ _expandedKeys: expandedKeys }); this.$emit('update:expandedKeys', expandedKeys); this.$emit('expand', expandedKeys, info); return undefined; }, handleClick(event: MouseEvent, node: VNode) { const { expandAction } = this.$props; // Expand the tree if (expandAction === 'click') { this.onDebounceExpand(event, node); } this.$emit('click', event, node); }, handleDoubleClick(event: MouseEvent, node: VNode) { const { expandAction } = this.$props; // Expand the tree if (expandAction === 'dblclick' || expandAction === 'doubleclick') { this.onDebounceExpand(event, node); } this.$emit('doubleclick', event, node); this.$emit('dblclick', event, node); }, hanldeSelect(keys: (string | number)[], event: SelectEvent) { const { multiple } = this.$props; const children = this.children || []; const { _expandedKeys: expandedKeys = [] } = this.$data; const { node, nativeEvent } = event; const { eventKey = '' } = node; const newState: DirectoryTreeState = {}; // We need wrap this event since some value is not same const newEvent = { ...event, selected: true, // Directory selected always true }; // Windows / Mac single pick const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey; const shiftPick = nativeEvent.shiftKey; // Generate new selected keys let newSelectedKeys: (string | number)[]; if (multiple && ctrlPick) { // Control click newSelectedKeys = keys; this.lastSelectedKey = eventKey; this.cachedSelectedKeys = newSelectedKeys; newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys); } else if (multiple && shiftPick) { // Shift click newSelectedKeys = Array.from( new Set([ ...(this.cachedSelectedKeys || []), ...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey), ]), ); newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys); } else { // Single click newSelectedKeys = [eventKey]; this.lastSelectedKey = eventKey; this.cachedSelectedKeys = newSelectedKeys; newEvent.selectedNodes = [event.node]; } newState._selectedKeys = newSelectedKeys; this.$emit('update:selectedKeys', newSelectedKeys); this.$emit('select', newSelectedKeys, newEvent); this.setUncontrolledState(newState); }, setTreeRef(node: VNode) { this.tree = node; }, expandFolderNode(event: MouseEvent, node: { isLeaf: boolean } & VNode) { const { isLeaf } = node; if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) { return; } if (this.tree.tree) { // Get internal vc-tree const internalTree = this.tree.tree; // Call internal rc-tree expand function // https://github.com/ant-design/ant-design/issues/12567 internalTree.onNodeExpand(event, node); } }, setUncontrolledState(state: unknown) { const newState = omit( state, Object.keys(getOptionProps(this)).map(p => `_${p}`), ); if (Object.keys(newState).length) { this.setState(newState); } }, handleCheck(checkedObj: (string | number)[], eventObj: CheckEvent) { this.$emit('update:checkedKeys', checkedObj); this.$emit('check', checkedObj, eventObj); }, }, render() { this.children = getSlot(this); const { prefixCls: customizePrefixCls, ...props } = getOptionProps(this); const getPrefixCls = this.configProvider.getPrefixCls; const prefixCls = getPrefixCls('tree', customizePrefixCls); const { _expandedKeys: expandedKeys, _selectedKeys: selectedKeys } = this.$data; const { class: className, ...restAttrs } = this.$attrs; const connectClassName = classNames(`${prefixCls}-directory`, className); const treeProps = { icon: getIcon, ...restAttrs, ...omit(props, ['onUpdate:selectedKeys', 'onUpdate:checkedKeys', 'onUpdate:expandedKeys']), prefixCls, expandedKeys, selectedKeys, switcherIcon: getComponent(this, 'switcherIcon'), ref: this.setTreeRef, class: connectClassName, onSelect: this.hanldeSelect, onClick: this.handleClick, onDblclick: this.handleDoubleClick, onExpand: this.handleExpand, onCheck: this.handleCheck, }; return ( {this.children} ); }, });