UNPKG

ng-devui

Version:

DevUI components based on Angular

1 lines 163 kB
{"version":3,"file":"ng-devui-tree.mjs","sources":["../../devui/tree/tree-factory.class.ts","../../devui/tree/pipe/filter-nodes.pipe.ts","../../devui/tree/tree-nodes.component.ts","../../devui/tree/tree-nodes.component.html","../../devui/tree/pipe/transfer-to-array.pipe.ts","../../devui/tree/tree.component.ts","../../devui/tree/tree.component.html","../../devui/tree/auto-focus.directive.ts","../../devui/tree/operable-tree.component.ts","../../devui/tree/operable-tree.component.html","../../devui/tree/tree.module.ts","../../devui/tree/ng-devui-tree.ts"],"sourcesContent":["import { forEach, isUndefined, omitBy, pickBy, reduce, trim, values } from 'lodash-es';\r\nimport { BehaviorSubject } from 'rxjs';\r\nexport interface Dictionary<T> {\r\n [id: number]: T;\r\n}\r\nexport interface ITreeNodeData {\r\n id?: number | string;\r\n parentId?: number | string;\r\n title?: string;\r\n isOpen?: boolean;\r\n data?: any;\r\n isParent?: boolean;\r\n loading?: boolean;\r\n isMatch?: boolean;\r\n isHide?: boolean;\r\n isActive?: boolean;\r\n isChecked?: boolean;\r\n disabled?: boolean;\r\n\r\n [prop: string]: any;\r\n\r\n children?: [];\r\n}\r\n\r\nexport interface ITreeMap {\r\n [index: number]: ITreeNodeData;\r\n}\r\n\r\nexport interface ITreeItem {\r\n title?: string;\r\n open?: boolean;\r\n loading?: boolean;\r\n isMatch?: boolean;\r\n items?: ITreeItem[];\r\n isParent?: boolean;\r\n data?: any;\r\n id?: number | string;\r\n isHide?: boolean;\r\n isActive?: boolean;\r\n isChecked?: boolean;\r\n halfChecked?: boolean;\r\n disabled?: boolean;\r\n showCheckbox?: boolean;\r\n [prop: string]: any;\r\n\r\n disableAdd?: boolean;\r\n disableEdit?: boolean;\r\n disableDelete?: boolean;\r\n disableSelect?: boolean;\r\n disableToggle?: boolean;\r\n}\r\n\r\nexport interface ITreeInput {\r\n treeItems: Array<ITreeItem>;\r\n parentId?: number | string;\r\n treeNodeChildrenKey?: string;\r\n treeNodeIdKey?: string;\r\n checkboxDisabledKey?: string;\r\n selectDisabledKey?: string;\r\n toggleDisabledKey?: string;\r\n treeNodeTitleKey?: string;\r\n isVirtualScroll?: boolean;\r\n}\r\n\r\nexport class TreeNode implements ITreeNodeData {\r\n constructor(public id, public parentId, public data) {}\r\n}\r\n\r\nexport class TreeFactory {\r\n nodes: Dictionary<TreeNode>;\r\n private idx: number;\r\n private _checked = new Set<Object>();\r\n private _treeRoot: TreeNode[] = [];\r\n searchItem: string;\r\n flattenNodes = new BehaviorSubject<TreeNode[]>([]);\r\n virtualScroll: boolean;\r\n canIdEmpty = true;\r\n static create(isVirtualScroll) {\r\n return new TreeFactory(isVirtualScroll);\r\n }\r\n\r\n // tree model with items\r\n static fromTree({\r\n treeItems,\r\n isVirtualScroll = false,\r\n treeNodeChildrenKey = 'items',\r\n treeNodeIdKey = 'id',\r\n checkboxDisabledKey = 'disabled',\r\n selectDisabledKey = 'disabled', // 默认值与checkboxDisabledKey相同,为了兼容以前tree的disable情况\r\n toggleDisabledKey = 'disabledToggle',\r\n treeNodeTitleKey = 'title',\r\n }: ITreeInput): TreeFactory {\r\n const treeFactory = TreeFactory.create(isVirtualScroll);\r\n treeFactory.mapTreeItems(\r\n {\r\n treeItems,\r\n parentId: undefined,\r\n treeNodeChildrenKey,\r\n treeNodeIdKey,\r\n checkboxDisabledKey,\r\n selectDisabledKey,\r\n toggleDisabledKey,\r\n treeNodeTitleKey,\r\n },\r\n false\r\n );\r\n return treeFactory;\r\n }\r\n\r\n constructor(public isVirtualScroll) {\r\n this.virtualScroll = isVirtualScroll;\r\n this.idx = 0;\r\n this.nodes = {};\r\n }\r\n\r\n mapTreeItems = (\r\n {\r\n treeItems,\r\n parentId,\r\n treeNodeChildrenKey = 'items',\r\n treeNodeIdKey = 'id',\r\n checkboxDisabledKey = 'disabled',\r\n selectDisabledKey = 'disableSelect',\r\n toggleDisabledKey = 'disableToggle',\r\n treeNodeTitleKey = 'title',\r\n }: ITreeInput,\r\n renderTree = true\r\n ) => {\r\n forEach(treeItems, (item: ITreeItem) => {\r\n const node = this.addNode(\r\n {\r\n id: item[treeNodeIdKey],\r\n parentId,\r\n title: item[treeNodeTitleKey],\r\n isOpen: !!item.open,\r\n data: item.data || {},\r\n originItem: item,\r\n isParent: !!item.isParent || !!(item[treeNodeChildrenKey] && item[treeNodeChildrenKey].length > 0),\r\n loading: !!item.loading,\r\n isMatch: !!item.isMatch,\r\n isHide: !!item.isHide,\r\n isChecked: !!item.isChecked,\r\n halfChecked: !!item.halfChecked,\r\n isActive: !!item.isActive,\r\n disabled: !!item[checkboxDisabledKey],\r\n disableSelect: !!item[selectDisabledKey],\r\n disableToggle: !!item[toggleDisabledKey],\r\n disableAdd: !!item.disableAdd,\r\n disableEdit: !!item.disableEdit,\r\n disableDelete: !!item.disableDelete,\r\n children: [],\r\n showCheckbox: item.showCheckbox,\r\n },\r\n undefined,\r\n renderTree\r\n );\r\n\r\n if (item.isChecked) {\r\n this._checked.add(node);\r\n }\r\n\r\n this.mapTreeItems(\r\n {\r\n treeItems: item[treeNodeChildrenKey] || [],\r\n parentId: node.id,\r\n treeNodeChildrenKey,\r\n treeNodeIdKey,\r\n checkboxDisabledKey,\r\n selectDisabledKey,\r\n toggleDisabledKey,\r\n treeNodeTitleKey,\r\n },\r\n renderTree\r\n );\r\n });\r\n return this;\r\n };\r\n\r\n addNode({ id, parentId, ...data }: ITreeNodeData, index?, renderTree = true): TreeNode {\r\n let newId = id;\r\n if (isUndefined(id)) {\r\n this.idx++;\r\n newId = this.idx;\r\n }\r\n const treeNode = new TreeNode(newId, parentId, data);\r\n if (Object.prototype.hasOwnProperty.call(this.nodes, treeNode.id)) {\r\n throw new Error(`Duplicated id: ${treeNode.id} detected, please specify unique ids in the tree.`);\r\n }\r\n this.nodes[treeNode.id] = treeNode;\r\n this.addChildNode(this.nodes[parentId], treeNode, index);\r\n // 兼容当前用户外部直接调用addNode方法创建节点\r\n if (renderTree) {\r\n this.renderFlattenTree();\r\n }\r\n return treeNode;\r\n }\r\n\r\n editNodeTitle(id: number | string) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.editable = true;\r\n }\r\n\r\n deleteNodeById(id: number | string, renderTree = true) {\r\n const node = this.nodes[id];\r\n if (!node) {\r\n return;\r\n }\r\n const parentNode = this.nodes[node.parentId];\r\n this.removeChildNode(parentNode, node);\r\n\r\n const deleteItems = (nodeId) => {\r\n this.maintainCheckedNodeList(this.nodes[nodeId], false);\r\n const children = this.getChildrenById(nodeId);\r\n this.nodes = omitBy(this.nodes, (_node) => {\r\n return _node.id === nodeId;\r\n }) as Dictionary<TreeNode>;\r\n forEach(children, (child) => {\r\n deleteItems(child.id);\r\n });\r\n };\r\n deleteItems(id);\r\n if (parentNode && (!parentNode.data.children || !parentNode.data.children.length)) {\r\n parentNode.data.isParent = false;\r\n }\r\n if (renderTree) {\r\n this.renderFlattenTree();\r\n }\r\n return this;\r\n }\r\n\r\n toggleNodeById(id: number | string) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.isOpen = !this.nodes[id].data.isOpen;\r\n this.renderFlattenTree();\r\n return this;\r\n }\r\n\r\n openNodesById(id: number | string) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.isOpen = true;\r\n if (this.nodes[id].parentId !== undefined) {\r\n this.openNodesById(this.nodes[id].parentId);\r\n }\r\n this.renderFlattenTree();\r\n return this;\r\n }\r\n\r\n closeNodesById(id: number | string, closeChildren = false) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.isOpen = false;\r\n if (closeChildren) {\r\n if (this.nodes[id] && this.nodes[id].data.children) {\r\n this.nodes[id].data.children.forEach((node) => {\r\n this.closeNodesById(node.id);\r\n });\r\n }\r\n }\r\n this.renderFlattenTree();\r\n return this;\r\n }\r\n\r\n disabledNodesById(id: number | string) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.disabled = true;\r\n\r\n const parentId = this.nodes[id].parentId;\r\n this._disabledParentNodes(parentId);\r\n\r\n const disabledNodes = (nodeId: number | string) => {\r\n const children = this.getChildrenById(nodeId);\r\n if (children.length > 0) {\r\n children.forEach((child) => {\r\n this.nodes[child.id].data.disabled = true;\r\n disabledNodes(child.id);\r\n });\r\n }\r\n };\r\n disabledNodes(id);\r\n return this;\r\n }\r\n\r\n private _disabledParentNodes(parentId: number | string | undefined) {\r\n const children = this.getChildrenById(parentId);\r\n\r\n if (children.length < 1) {\r\n return;\r\n }\r\n const result = reduce(\r\n children,\r\n (status: boolean, child) => {\r\n return status && child.data.disabled;\r\n },\r\n true\r\n );\r\n\r\n if (this.nodes[parentId]) {\r\n this.nodes[parentId].data.disabled = result;\r\n }\r\n }\r\n\r\n checkNodesById(\r\n id: number | string,\r\n checked: boolean,\r\n checkableRelation: 'upward' | 'downward' | 'both' | 'none' = 'both'\r\n ): Array<Object> {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.halfChecked = false;\r\n this.nodes[id].data.isChecked = checked;\r\n switch (checkableRelation) {\r\n case 'upward':\r\n this.checkParentNodes(this.nodes[id]);\r\n break;\r\n case 'downward':\r\n this.checkChildNodes(this.nodes[id], checked, this.nodes[id].data.isHide);\r\n break;\r\n case 'both':\r\n this.checkParentNodes(this.nodes[id]);\r\n this.checkChildNodes(this.nodes[id], checked, this.nodes[id].data.isHide);\r\n break;\r\n case 'none':\r\n break;\r\n default:\r\n }\r\n this.maintainCheckedNodeList(this.nodes[id], checked);\r\n return this.getCheckedNodes();\r\n }\r\n\r\n checkParentNodes(node: TreeNode) {\r\n const { parentId } = node;\r\n const parentNode = this.nodes[parentId];\r\n if (parentNode) {\r\n const childrenNode = this.getChildrenById(parentId);\r\n if (childrenNode.every((childNode) => childNode.data.isChecked && !childNode.data.halfChecked)) {\r\n parentNode.data.isChecked = true;\r\n parentNode.data.halfChecked = false;\r\n } else if (childrenNode.some((childNode) => childNode.data.halfChecked || childNode.data.isChecked)) {\r\n parentNode.data.isChecked = true;\r\n parentNode.data.halfChecked = true;\r\n } else {\r\n parentNode.data.isChecked = false;\r\n parentNode.data.halfChecked = false;\r\n }\r\n this.maintainCheckedNodeList(parentNode, parentNode.data.isChecked);\r\n this.checkParentNodes(parentNode);\r\n }\r\n }\r\n\r\n private checkChildNodes(node: TreeNode, checked: boolean, hasHiddenAncestor = undefined) {\r\n const { id } = node;\r\n const childrenNode = this.getChildrenById(id);\r\n if (childrenNode.length > 0) {\r\n childrenNode.forEach((childNode) => {\r\n const { id: childId } = childNode;\r\n const { data: nodeData } = this.nodes[childId];\r\n if (!nodeData.disabled) {\r\n nodeData.isChecked = checked;\r\n nodeData.halfChecked = false;\r\n nodeData.hasHiddenAncestor = hasHiddenAncestor;\r\n this.maintainCheckedNodeList(childNode, checked);\r\n }\r\n this.checkChildNodes(childNode, checked, nodeData.isHide);\r\n });\r\n const childrenFullCheckedCount = childrenNode.filter(({ data: nodeData }) => nodeData.isChecked).length;\r\n const childrenCheckedCount = childrenNode.filter(({ data: nodeData }) => nodeData.isChecked || nodeData.halfChecked).length;\r\n node.data.halfChecked = childrenCheckedCount > 0 && childrenNode.length > childrenFullCheckedCount;\r\n }\r\n }\r\n\r\n getLineage(node: TreeNode): Array<string> {\r\n const { parentId } = node;\r\n if (parentId) {\r\n const parentNode = this.nodes[parentId];\r\n return [node.id, ...this.getLineage(parentNode)];\r\n } else {\r\n return [node.id];\r\n }\r\n }\r\n\r\n getCheckedNodes(): Array<any> {\r\n return Array.from(this._checked);\r\n }\r\n\r\n getCheckedNodesWithoutHide(hideInVirtualScroll = false): Array<any> {\r\n return Array.from(this._checked).filter(\r\n (item: any) => !((hideInVirtualScroll ? item.data.hideInVirtualScroll : item.data.isHide) || item.data.hasHiddenAncestor)\r\n );\r\n }\r\n\r\n getActivatedNodes(): Array<any> {\r\n const results = pickBy(this.nodes, (node) => node.data.isActive === true);\r\n return values(results);\r\n }\r\n\r\n getDisabledNodes(): Array<any> {\r\n const results = pickBy(this.nodes, (node) => node.data.disabled === true);\r\n return values(results);\r\n }\r\n\r\n activeNodeById(id: number | string, isMultiple?: boolean) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n if (!isMultiple) {\r\n this.deactivateAllNodes();\r\n }\r\n this.nodes[id].data.isActive = !this.nodes[id].data.isActive;\r\n }\r\n\r\n getChildrenById(id: number | string): Array<TreeNode> {\r\n if (this.nodes[id]) {\r\n return this.nodes[id].data.children || [];\r\n } else if (id === undefined) {\r\n return this._treeRoot;\r\n }\r\n\r\n return [];\r\n }\r\n\r\n startLoading(id: number | string) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.loading = true;\r\n }\r\n\r\n endLoading(id: number | string) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.loading = false;\r\n }\r\n\r\n getNodeById(id: number | string): any {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n return this.nodes[id].data;\r\n }\r\n\r\n getCompleteNodeById(id: number | string): any {\r\n return this.nodes[id];\r\n }\r\n\r\n hideNodeById(id: number | string, hide: boolean) {\r\n if (!this.nodes[id]) {\r\n return;\r\n }\r\n this.nodes[id].data.isHide = hide;\r\n this.renderFlattenTree();\r\n return this;\r\n }\r\n\r\n private maintainCheckedNodeList(node: TreeNode, checked: boolean) {\r\n if (checked && !node.data.halfChecked) {\r\n this._checked.add(node);\r\n } else {\r\n this._checked.delete(node);\r\n }\r\n }\r\n\r\n private dfs(target, tree, hideUnmatched?: boolean, keyword?, pattern?) {\r\n if (!tree) {\r\n return false;\r\n }\r\n if (!target) {\r\n return false;\r\n }\r\n if (Array.isArray(tree)) {\r\n return tree.map((treeNode) => {\r\n return this.dfs(target, treeNode, hideUnmatched, keyword, pattern);\r\n });\r\n } else {\r\n const treeNode = tree;\r\n const treeChildren = this.getChildrenById(treeNode.id);\r\n const key = keyword ? treeNode.data.originItem[keyword] : treeNode.data.title;\r\n const selfMatched = pattern ? pattern.test(key) : key.toLowerCase().includes(target);\r\n if (selfMatched) {\r\n treeNode.data.isMatch = true;\r\n treeNode.data.isCustomSearch = keyword;\r\n }\r\n // Test if children matches target recursively, do not hide children if parent is matched.\r\n const childrenMatched = this.dfs(target, treeChildren, hideUnmatched && !selfMatched, keyword, pattern).some((_) => !!_);\r\n if (selfMatched || childrenMatched) {\r\n if (childrenMatched && treeChildren.length > 0) {\r\n this.openNodesById(treeNode.id);\r\n }\r\n return true;\r\n } else {\r\n treeNode.data.isHide = hideUnmatched;\r\n return false;\r\n }\r\n }\r\n }\r\n\r\n public addChildNode(parentNode: TreeNode, childNode: TreeNode, index?) {\r\n if (parentNode) {\r\n if (Array.isArray(parentNode.data.children)) {\r\n if (index !== undefined) {\r\n parentNode.data.children.splice(index, 0, childNode);\r\n } else {\r\n parentNode.data.children.push(childNode);\r\n }\r\n } else {\r\n parentNode.data.children = [childNode];\r\n }\r\n } else {\r\n index !== undefined ? this._treeRoot.splice(index, 0, childNode) : this._treeRoot.push(childNode);\r\n }\r\n this.nodes[childNode.id] = childNode;\r\n }\r\n\r\n private removeChildNode(parentNode: TreeNode, childNode: TreeNode) {\r\n if (parentNode) {\r\n parentNode.data.children = parentNode.data.children.filter((node) => node.id !== childNode.id);\r\n } else {\r\n this._treeRoot = this._treeRoot.filter((node) => node.id !== childNode.id);\r\n }\r\n }\r\n\r\n resetSearchResults() {\r\n Object.keys(this.nodes).forEach((key) => {\r\n const treeNode = this.nodes[key];\r\n treeNode.data.isMatch = false;\r\n treeNode.data.isHide = false;\r\n treeNode.data.isCustomSearch = false;\r\n });\r\n }\r\n\r\n public searchTree(target: string, hideUnmatched = false, keyword?, pattern?) {\r\n this.searchItem = target;\r\n const TrimmedTarget = trim(target);\r\n this.resetSearchResults();\r\n return this.dfs(TrimmedTarget.toLowerCase(), this._treeRoot, hideUnmatched, keyword, pattern);\r\n }\r\n\r\n get treeRoot() {\r\n return this._treeRoot;\r\n }\r\n\r\n public deactivateAllNodes() {\r\n for (const id of Object.keys(this.nodes)) {\r\n this.nodes[id].data.isActive = false;\r\n }\r\n }\r\n\r\n public checkAllNodes(checked: boolean) {\r\n for (const id of Object.keys(this.nodes)) {\r\n if (!this.nodes[id].data.disabled) {\r\n this.nodes[id].data.halfChecked = false;\r\n this.nodes[id].data.isChecked = checked;\r\n }\r\n this.maintainCheckedNodeList(this.nodes[id], this.nodes[id].data.isChecked);\r\n }\r\n }\r\n\r\n public getNodeIndex(node: TreeNode) {\r\n let parentNode;\r\n let children;\r\n if (node.parentId !== undefined) {\r\n parentNode = this.getNodeById(node.parentId);\r\n children = parentNode.children;\r\n } else {\r\n children = this.treeRoot;\r\n }\r\n for (let i = 0; i < children.length; i++) {\r\n if (children[i].id === node.id) {\r\n return i;\r\n }\r\n }\r\n return -1;\r\n }\r\n\r\n public checkIsParent(childNodeId: number | string, parentNodeId: number | string) {\r\n const realParentId = this.nodes[childNodeId].parentId;\r\n if (realParentId === parentNodeId) {\r\n return true;\r\n } else if (realParentId !== undefined) {\r\n return this.checkIsParent(realParentId, parentNodeId);\r\n } else {\r\n return false;\r\n }\r\n }\r\n\r\n public getFlattenNodes() {\r\n this.flattenNodes.next(this.flattenTree());\r\n }\r\n\r\n public flattenTree() {\r\n const flattenTree = [];\r\n const flatTree = (nodes) => {\r\n for (let i = 0; i < nodes.length; i++) {\r\n const hasParentId = this.canIdEmpty ? nodes[i].parentId : nodes[i].parentId !== undefined;\r\n nodes[i].data.depth = hasParentId ? this.nodes[nodes[i].parentId].data.depth + 1 : 0;\r\n nodes[i].data.hideInVirtualScroll =\r\n nodes[i].data.isHide ||\r\n (hasParentId ? this.nodes[nodes[i].parentId].data.hideInVirtualScroll || !this.nodes[nodes[i].parentId].data.isOpen : false);\r\n nodes[i].data.isLast = i === nodes.length - 1;\r\n flattenTree.push(nodes[i]);\r\n if (nodes[i].data.children) {\r\n flatTree(nodes[i].data.children);\r\n }\r\n }\r\n };\r\n flatTree(this.treeRoot);\r\n return flattenTree;\r\n }\r\n\r\n public mergeTreeNodes(targetNode = this.treeRoot) {\r\n const mergeToNode = (node) => {\r\n if (!node) {\r\n return;\r\n }\r\n if (node.data.children?.length === 1 && node.data.children[0]?.data?.children?.length !== 0) {\r\n node.data.title = node.data.title + ' / ' + node.data.children[0]?.data?.title;\r\n node.data.children = node.data.children[0]?.data?.children;\r\n node.data.children.forEach((child) => {\r\n child.parentId = node.id;\r\n });\r\n mergeToNode(node);\r\n }\r\n if (node.data.children?.length > 1) {\r\n node.data.children.forEach((element) => {\r\n mergeToNode(element);\r\n });\r\n }\r\n };\r\n if (targetNode === this.treeRoot) {\r\n this.treeRoot.forEach((element) => {\r\n mergeToNode(element);\r\n });\r\n } else {\r\n mergeToNode(targetNode);\r\n }\r\n }\r\n\r\n public renderFlattenTree() {\r\n if (!this.virtualScroll) {\r\n return;\r\n }\r\n this.getFlattenNodes();\r\n }\r\n\r\n public disableAllNodesChecked(disabled = true) {\r\n for (const id of Object.keys(this.nodes)) {\r\n this.nodes[id].data.disabled = disabled;\r\n }\r\n }\r\n\r\n public disableAllNodesSelected(disabled = true) {\r\n for (const id of Object.keys(this.nodes)) {\r\n this.nodes[id].data.disableSelect = disabled;\r\n }\r\n }\r\n\r\n public disableAllNodesToggled(disabled = true) {\r\n for (const id of Object.keys(this.nodes)) {\r\n this.nodes[id].data.disableToggle = disabled;\r\n }\r\n }\r\n\r\n public toggleAllNodes(toggle = true) {\r\n for (const id of Object.keys(this.nodes)) {\r\n this.nodes[id].data.isOpen = toggle;\r\n }\r\n if (this.isVirtualScroll) {\r\n this.renderFlattenTree();\r\n }\r\n }\r\n\r\n public transferToTreeNode(\r\n originNode,\r\n parentId?,\r\n treeNodeChildrenKey = 'items',\r\n treeNodeIdKey = 'id',\r\n checkboxDisabledKey = 'disabled',\r\n selectDisabledKey = 'disableSelect',\r\n toggleDisabledKey = 'disableToggle',\r\n treeNodeTitleKey = 'title'\r\n ) {\r\n const node = {\r\n id: originNode[treeNodeIdKey],\r\n parentId,\r\n title: originNode[treeNodeTitleKey],\r\n isOpen: !!originNode.open,\r\n data: originNode.data || {},\r\n originItem: originNode,\r\n isParent: !!originNode.isParent || !!(originNode[treeNodeChildrenKey] && originNode[treeNodeChildrenKey].length > 0),\r\n loading: !!originNode.loading,\r\n isMatch: !!originNode.isMatch,\r\n isHide: !!originNode.isHide,\r\n isChecked: !!originNode.isChecked,\r\n halfChecked: !!originNode.halfChecked,\r\n isActive: !!originNode.isActive,\r\n disabled: !!originNode[checkboxDisabledKey],\r\n disableSelect: !!originNode[selectDisabledKey],\r\n disableToggle: !!originNode[toggleDisabledKey],\r\n disableAdd: !!originNode.disableAdd,\r\n disableEdit: !!originNode.disableEdit,\r\n disableDelete: !!originNode.disableDelete,\r\n children: [],\r\n };\r\n return new TreeNode(node.id, node.parentId, { ...node });\r\n }\r\n}\r\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'filterNodesPipe' })\nexport class FilterNodesPipe implements PipeTransform {\n\n constructor() {\n }\n\n transform(nodes, key) {\n return nodes.filter(item => !item.data[key]);\n }\n}\n","import {\n Component,\n Input,\n TemplateRef\n} from '@angular/core';\nimport {\n TreeFactory,\n TreeNode\n} from './tree-factory.class';\n\n@Component({\n selector: 'd-tree-nodes',\n templateUrl: './tree-nodes.component.html',\n styleUrls: ['./tree-nodes.component.scss'],\n preserveWhitespaces: false,\n})\nexport class TreeNodesComponent {\n @Input() treeList: Array<TreeNode>;\n @Input() treeNodesRef: TemplateRef<any>;\n @Input() treeFactory: TreeFactory;\n @Input() virtualScroll = false;\n trackByFn(index, item) {\n return index;\n }\n}\n","<ng-container *ngIf=\"virtualScroll\">\n <ng-template\n *cdkVirtualFor=\"let treeNode of treeList | filterNodesPipe: 'hideInVirtualScroll'; trackBy: trackByFn\"\n [ngTemplateOutlet]=\"treeNodesRef\"\n [ngTemplateOutletContext]=\"{\n $implicit: this,\n treeNode: treeNode,\n treeFactory: treeFactory\n }\"\n >\n </ng-template>\n</ng-container>\n<ng-container *ngIf=\"!virtualScroll\">\n <ng-template\n *ngFor=\"let treeNode of treeList; trackBy: trackByFn\"\n [ngTemplateOutlet]=\"treeNodesRef\"\n [ngTemplateOutletContext]=\"{\n $implicit: this,\n treeNode: treeNode,\n treeFactory: treeFactory\n }\"\n >\n </ng-template>\n</ng-container>\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({ name: 'transferToArrayPipe' })\nexport class TransferToArrayPipe implements PipeTransform {\n\n constructor() {\n }\n\n transform(number) {\n return Array(number).fill(0);\n }\n}\n","import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';\nimport {\n AfterViewInit,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnChanges,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewChildren\n} from '@angular/core';\nimport { I18nInterface, I18nService } from 'ng-devui/i18n';\nimport { DevConfigService, expandCollapseForDomDestroy, WithConfig } from 'ng-devui/utils';\nimport { Subject, Subscription } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { Dictionary, ITreeItem, TreeFactory, TreeNode } from './tree-factory.class';\n@Component({\n selector: 'd-tree',\n templateUrl: './tree.component.html',\n styleUrls: ['./tree.component.scss'],\n preserveWhitespaces: false,\n animations: [expandCollapseForDomDestroy],\n})\nexport class TreeComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy {\n treeFactory: TreeFactory;\n @Input() tree: Array<ITreeItem>;\n @Input() treeNodesRef: TemplateRef<any>;\n @Input() treeNodeIdKey: string;\n @Input() treeNodeChildrenKey: string;\n @Input() iconParentOpen: string;\n @Input() iconParentClose: string;\n @Input() iconLeaf: string;\n @Input() loadingTemplateRef: TemplateRef<any>;\n @Input() treeNodeTitleKey = 'title';\n @Input() checkboxDisabledKey = 'disabled';\n @Input() selectDisabledKey = 'disabled';\n @Input() toggleDisabledKey = 'disableToggle';\n @Input() virtualScroll = false;\n @Input() virtualScrollHeight = '800px';\n @Input() @WithConfig() showAnimation = true;\n @Input() minBufferPx = 600;\n @Input() maxBufferPx = 900;\n @Input() itemSize = 30;\n @Input() indent = '16px';\n /**\n * 默认不需要判断parentId是否undefined,有业务使用了空字符串作为非根目录的id,导致必须判断来区分,当业务整改后移除该判断\n * @deprecated\n */\n @Input() canIdEmpty = true;\n @Output() nodeSelected = new EventEmitter<TreeNode>();\n @Output() nodeDblClicked = new EventEmitter<TreeNode>();\n @Output() nodeRightClicked = new EventEmitter<{ node: TreeNode; event: MouseEvent }>();\n @Output() nodeToggled = new EventEmitter<TreeNode>();\n @Output() afterTreeInit = new EventEmitter<Dictionary<TreeNode>>();\n @ViewChildren('treeNodeContent') treeNodeContent: QueryList<ElementRef>; // 获取content以取得tree宽度\n @ViewChild(CdkVirtualScrollViewport) viewPort: CdkVirtualScrollViewport;\n i18nCommonText: I18nInterface['common'];\n i18nSubscription: Subscription;\n treeNodes = [];\n destroy$ = new Subject<void>();\n afterInitAnimate = true;\n\n constructor(private i18n: I18nService, private devConfigService: DevConfigService) {}\n\n ngOnInit() {\n this.initTree();\n this.i18nCommonText = this.i18n.getI18nText().common;\n this.i18nSubscription = this.i18n.langChange().subscribe((data) => {\n this.i18nCommonText = data.common;\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes && changes.tree && !changes.tree.isFirstChange()) {\n this.initTree();\n }\n }\n\n initTree() {\n this.treeFactory = TreeFactory.fromTree({\n treeItems: this.tree,\n isVirtualScroll: this.virtualScroll,\n treeNodeChildrenKey: this.treeNodeChildrenKey,\n treeNodeIdKey: this.treeNodeIdKey,\n treeNodeTitleKey: this.treeNodeTitleKey,\n checkboxDisabledKey: this.checkboxDisabledKey,\n selectDisabledKey: this.selectDisabledKey,\n toggleDisabledKey: this.toggleDisabledKey,\n });\n this.treeFactory.canIdEmpty = this.canIdEmpty;\n if (this.virtualScroll) {\n this.treeFactory.flattenNodes.pipe(takeUntil(this.destroy$)).subscribe((data) => {\n this.treeNodes = data;\n });\n this.treeFactory.getFlattenNodes();\n }\n this.afterTreeInit.emit(this.treeFactory.nodes);\n }\n\n ngAfterViewInit() {\n setTimeout(() => {\n this.afterInitAnimate = false;\n });\n }\n\n contextmenuEvent(event, node) {\n this.nodeRightClicked.emit({ node: node, event: event });\n }\n\n selectNode(event, treeNode: TreeNode) {\n if (treeNode.data.disableSelect) {\n return;\n }\n if (!this.isSelectableRegion(event.target)) {\n return;\n }\n this.nodeSelected.emit(treeNode);\n this.treeFactory.activeNodeById(treeNode.id);\n }\n\n toggleNode(event, treeNode: TreeNode) {\n if (treeNode.data.disableToggle) {\n return;\n }\n this.treeFactory.toggleNodeById(treeNode.id);\n this.nodeToggled.emit(treeNode);\n }\n\n scrollToIndex(index: number) {\n this.viewPort.scrollToIndex(index, 'smooth');\n }\n\n public appendTreeItems(treeItems: Array<ITreeItem>, parentId) {\n if (!this.treeFactory.nodes[parentId]) {\n throw new Error('parent node does not exist.');\n }\n this.treeFactory.mapTreeItems({\n treeItems: treeItems,\n parentId: parentId,\n treeNodeChildrenKey: this.treeNodeChildrenKey,\n treeNodeIdKey: this.treeNodeIdKey,\n treeNodeTitleKey: this.treeNodeTitleKey,\n checkboxDisabledKey: this.checkboxDisabledKey,\n selectDisabledKey: this.selectDisabledKey,\n toggleDisabledKey: this.toggleDisabledKey,\n });\n }\n public nodeDblClick(event, node) {\n this.nodeDblClicked.emit(node);\n }\n\n public isSelectableRegion(ele) {\n if (ele && !ele.classList.contains('devui-tree-node__content--value-wrapper')\n && !ele.classList.contains('devui-tree-node__content')\n && !ele.classList.contains('devui-tree-node__title')\n && !ele.classList.contains('devui-tree-node-highlight')\n && ele.tagName !== 'D-HIGHLIGHT'\n && ele.parentNode?.tagName !== 'D-HIGHLIGHT') {\n return false;\n }\n return true;\n }\n\n ngOnDestroy() {\n if (this.i18nSubscription) {\n this.i18nSubscription.unsubscribe();\n }\n this.destroy$.next();\n this.destroy$.complete();\n }\n}\n","<cdk-virtual-scroll-viewport\n *ngIf=\"virtualScroll\"\n class=\"devui-scrollbar devui-scroll-overlay\"\n [itemSize]=\"itemSize\"\n [minBufferPx]=\"minBufferPx\"\n [maxBufferPx]=\"maxBufferPx\"\n [style.height]=\"virtualScrollHeight\"\n>\n <d-tree-nodes\n [virtualScroll]=\"true\"\n [treeList]=\"treeNodes\"\n [treeNodesRef]=\"treeNodesRef ? treeNodesRef : virtualScrollRef\"\n [treeFactory]=\"treeFactory\"\n >\n </d-tree-nodes>\n</cdk-virtual-scroll-viewport>\n\n<d-tree-nodes\n *ngIf=\"!virtualScroll\"\n [treeList]=\"treeFactory.treeRoot\"\n [treeNodesRef]=\"treeNodesRef ? treeNodesRef : default\"\n [treeFactory]=\"treeFactory\"\n>\n</d-tree-nodes>\n<!-- TODO: 虚拟滚动支持动效 -->\n<ng-template #virtualScrollRef let-treeNode=\"treeNode\" let-treeFactory=\"treeFactory\">\n <div\n class=\"devui-tree-node\"\n [style.paddingLeft.px]=\"treeNode.data.depth * 24\"\n [ngClass]=\"{\n 'devui-tree-node__open': treeNode.data.isOpen,\n 'devui-tree-node__customIcon': iconParentClose\n }\"\n #treeNodeContent\n >\n <div\n class=\"devui-tree-vertical-line\"\n *ngFor=\"let item of treeNode.data.depth | transferToArrayPipe; let i = index\"\n [style.marginLeft.px]=\"i === 0 ? -16 : -16 - 24 * i\"\n [ngStyle]=\"{ height: i === 0 && treeNode.data.isLast && !treeNode.data.isOpen ? '15px' : '30px' }\"\n ></div>\n <div\n *ngIf=\"treeNode.data.depth\"\n [ngStyle]=\"{ width: treeNode.data.isParent ? '8px' : '16px' }\"\n class=\"devui-tree-horizontal-line\"\n ></div>\n <div\n class=\"devui-tree-node__content\"\n [class.active]=\"treeNode.data.isActive\"\n [class.devui-tree-node--parent]=\"(treeNode.data.children || []).length > 0\"\n (click)=\"selectNode($event, treeNode)\"\n >\n <div class=\"devui-tree-node__content--value-wrapper\" [class.isMatch]=\"treeNode.data.isMatch\">\n <span\n (click)=\"toggleNode($event, treeNode)\"\n *ngIf=\"(treeNode.data.children || []).length > 0 || treeNode.data.isParent\"\n class=\"devui-tree-node__folder\"\n [class.toggle-disabled]=\"treeNode.data.disableToggle\"\n >\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"iconParentClose && !treeNode.data.isOpen\" [innerHTML]=\"iconParentClose\"></span>\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"iconParentOpen && treeNode.data.isOpen\" [innerHTML]=\"iconParentOpen\"></span>\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"!iconParentClose && !treeNode.data.isOpen\">\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n class=\"svg-icon\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"2\"></rect>\n <path\n d=\"M8.75,4 L8.75,7.25 L12,7.25 L12,8.75 L8.749,8.75 L8.75,12 L7.25,12 L7.249,8.75 L4,8.75 L4,7.25 L7.25,7.25 L7.25,4 L8.75,4 Z\"\n ></path>\n </g>\n </svg>\n </span>\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"!iconParentOpen && treeNode.data.isOpen\">\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n class=\"svg-icon svg-icon-close\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"2\"></rect>\n <rect x=\"4\" y=\"7\" width=\"8\" height=\"2\"></rect>\n </g>\n </svg>\n </span>\n </span>\n <span class=\"devui-tree-node__leaf\" *ngIf=\"(treeNode.data.children || []).length === 0 && !treeNode.data.isParent\">\n <span *ngIf=\"!iconLeaf\" class=\"devui-leaf-icon-none\" [ngStyle]=\"{ width: indent }\"></span>\n <span *ngIf=\"iconLeaf\" [innerHTML]=\"iconLeaf\"></span>\n </span>\n <span\n (dblclick)=\"nodeDblClick($event, treeNode)\"\n (contextmenu)=\"contextmenuEvent($event, treeNode)\"\n class=\"devui-tree-node__title\"\n [class.select-disabled]=\"treeNode.data.disableSelect\"\n title=\"{{ treeNode.data.title }}\"\n >{{ treeNode.data.title }}</span\n >\n <span\n dLoading\n [showLoading]=\"treeNode.data.loading\"\n [loadingTemplateRef]=\"loadingTemplateRef ? loadingTemplateRef : defaultLoadingTmpl\"\n >\n </span>\n </div>\n </div>\n </div>\n</ng-template>\n<ng-template #default let-treeNode=\"treeNode\" let-treeFactory=\"treeFactory\">\n <div\n class=\"devui-tree-node devui-tree-without-virtual-scroll\"\n [ngClass]=\"{\n 'devui-tree-node__open': treeNode.data.isOpen,\n 'devui-tree-node__customIcon': iconParentClose\n }\"\n #treeNodeContent\n >\n <div\n class=\"devui-tree-node__content\"\n [class.active]=\"treeNode.data.isActive\"\n [class.devui-tree-node--parent]=\"(treeNode.data.children || []).length > 0\"\n (click)=\"selectNode($event, treeNode)\"\n >\n <div class=\"devui-tree-node__content--value-wrapper\" [class.isMatch]=\"treeNode.data.isMatch\">\n <span\n (click)=\"toggleNode($event, treeNode)\"\n *ngIf=\"(treeNode.data.children || []).length > 0 || treeNode.data.isParent\"\n class=\"devui-tree-node__folder\"\n [class.toggle-disabled]=\"treeNode.data.disableToggle\"\n >\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"iconParentClose && !treeNode.data.isOpen\" [innerHTML]=\"iconParentClose\"></span>\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"iconParentOpen && treeNode.data.isOpen\" [innerHTML]=\"iconParentOpen\"></span>\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"!iconParentClose && !treeNode.data.isOpen\">\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n class=\"svg-icon\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"2\"></rect>\n <path\n d=\"M8.75,4 L8.75,7.25 L12,7.25 L12,8.75 L8.749,8.75 L8.75,12 L7.25,12 L7.249,8.75 L4,8.75 L4,7.25 L7.25,7.25 L7.25,4 L8.75,4 Z\"\n ></path>\n </g>\n </svg>\n </span>\n <span class=\"devui-tree-node__folder--icon\" *ngIf=\"!iconParentOpen && treeNode.data.isOpen\">\n <svg\n width=\"16px\"\n height=\"16px\"\n viewBox=\"0 0 16 16\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n class=\"svg-icon svg-icon-close\"\n >\n <g stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\">\n <rect x=\"0.5\" y=\"0.5\" width=\"15\" height=\"15\" rx=\"2\"></rect>\n <rect x=\"4\" y=\"7\" width=\"8\" height=\"2\"></rect>\n </g>\n </svg>\n </span>\n </span>\n <span class=\"devui-tree-node__leaf\" *ngIf=\"(treeNode.data.children || []).length === 0 && !treeNode.data.isParent\">\n <span *ngIf=\"!iconLeaf\" class=\"devui-leaf-icon-none\" [ngStyle]=\"{ width: indent }\"></span>\n <span *ngIf=\"iconLeaf\" [innerHTML]=\"iconLeaf\"></span>\n </span>\n <span\n (dblclick)=\"nodeDblClick($event, treeNode)\"\n (contextmenu)=\"contextmenuEvent($event, treeNode)\"\n class=\"devui-tree-node__title\"\n [class.select-disabled]=\"treeNode.data.disableSelect\"\n title=\"{{ treeNode.data.title }}\"\n >{{ treeNode.data.title }}</span\n >\n <span\n dLoading\n [showLoading]=\"treeNode.data.loading\"\n [loadingTemplateRef]=\"loadingTemplateRef ? loadingTemplateRef : defaultLoadingTmpl\"\n >\n </span>\n </div>\n </div>\n <div\n *ngIf=\"treeNode.data.isOpen\"\n class=\"devui-tree-node__children\"\n @collapseForDomDestroy\n [@.disabled]=\"afterInitAnimate || !showAnimation\"\n >\n <d-tree-nodes [treeList]=\"treeNode.data.children || []\" [treeNodesRef]=\"default\" [treeFactory]=\"treeFactory\"> </d-tree-nodes>\n </div>\n </div>\n</ng-template>\n\n<ng-template #defaultLoadingTmpl>\n <span class=\"devui-loading-children\">{{ i18nCommonText?.loading }}</span>\n</ng-template>\n","import { Directive, ElementRef, OnInit } from '@angular/core';\n\n@Directive({\n selector: '[dTreeAutoFocus]'\n})\nexport class AutofocusDirective implements OnInit {\n\n constructor(private elementRef: ElementRef) { }\n\n ngOnInit(): void {\n setTimeout(() => {\n this.elementRef.nativeElement.focus();\n this.elementRef.nativeElement.select();\n });\n }\n\n}\n","import { DOCUMENT } from '@angular/common';\r\nimport {\r\n AfterViewInit,\r\n Component,\r\n ContentChild,\r\n ElementRef,\r\n EventEmitter,\r\n HostListener,\r\n Inject,\r\n Input,\r\n OnDestroy,\r\n OnInit,\r\n Output,\r\n QueryList,\r\n TemplateRef,\r\n ViewChild,\r\n ViewChildren,\r\n} from '@angular/core';\r\nimport { I18nInterface, I18nService } from 'ng-devui/i18n';\r\nimport { DevConfigService, WithConfig, expandCollapseForDomDestroy } from 'ng-devui/utils';\r\nimport { difference } from 'lodash-es';\r\nimport { Subscription } from 'rxjs';\r\nimport { Dictionary, ITreeItem, ITreeNodeData, TreeNode } from './tree-factory.class';\r\nimport { TreeComponent } from './tree.component';\r\nimport { ICheckboxInput, IDropType } from './tree.types';\r\n\r\n@Component({\r\n selector: 'd-operable-tree',\r\n templateUrl: './operable-tree.component.html',\r\n styleUrls: ['./operable-tree.component.scss'],\r\n exportAs: 'dOperableTreeComponent',\r\n preserveWhitespaces: false,\r\n animations: [expandCollapseForDomDestroy],\r\n})\r\nexport class OperableTreeComponent implements OnInit, OnDestroy, AfterViewInit {\r\n static ID_SEED = 0;\r\n @Input() tree: Array<ITreeItem>;\r\n @Input() treeNodeIdKey: string;\r\n @Input() treeNodeChildrenKey: string;\r\n @Input() checkboxDisabledKey: string;\r\n @Input() selectDisabledKey: string;\r\n @Input() toggleDisabledKey: string;\r\n @Input() iconParentOpen: string;\r\n @Input() iconParentClose: string;\r\n @Input() iconLeaf: string;\r\n /**\r\n * @deprecated\r\n */\r\n @Input() showLoading: boolean;\r\n @Input() loadingTemplateRef: TemplateRef<any>;\r\n @Input() treeNodesRef: TemplateRef<any>;\r\n @Input() checkable = true;\r\n @Input() deletable = false;\r\n @Input() addable = false;\r\n @Input() editable = false;\r\n @Input() draggable = false;\r\n @Input() dropFromOutside = false;\r\n @Input() disableMouseEvent: boolean;\r\n @Input() checkboxInput: ICheckboxInput = {};\r\n @Input() beforeAddNode: (node: TreeNode) => Promise<any>;\r\n @Input() beforeDeleteNode: (node: TreeNode) => Promise<any>;\r\n @Input() beforeEditNode: (node: TreeNode) => Promise<any>;\r\n @Input() beforeSelectNode: (node: TreeNode) => Promise<any>;\r\n @Input() beforeNodeDrop: (dragNodeId: string, dropNodeId: string, dropType: string, dragNodeIds?: string[]) => Promise<any>;\r\n @Input() canActivateNode = true;\r\n @Input() canActivateParentNode = true;\r\n @Input() canActivateMultipleNode = false;\r\n @Input() treeNodeTitleKey = 'title';\r\n @Input() postAddNode: (node: TreeNode) => Promise<any>;\r\n @Input() iconTemplatePosition: string;\r\n @Input() virtualScroll = false;\r\n @Input() virtualScrollHeight = '800px';\r\n @Input() @WithConfig() showAnimation = true;\r\n @Input() itemSize = 30;\r\n @Input() minBufferPx = 600;\r\n @Input() maxBufferPx = 900;\r\n @Input() checkableRelation: 'upward' | 'downward' | 'both' | 'none' = 'both';\r\n @Input() indent = '16px';\r\n /**\r\n * @deprecated\r\n */\r\n @Input() canIdEmpty = true;\r\n @Input() operatorAlign: 'start' | 'end' = 'start';\r\n @Output() nodeSelected = new EventEmitter<TreeNode | TreeNode[]>();\r\n @Output() nodeDblClicked = new EventEmitter<TreeNode>();\r\n @Output() nodeRightClicked = new EventEmitter<{ node: TreeNode; event: MouseEvent }>();\r\n @Output() nodeToggled = new EventEmitter<TreeNode>();\r\n @Output() afterTreeInit = new EventEmitter<Dictionary<TreeNode>>();\r\n @Output() nodeDeleted = new EventEmitter<TreeNode>();\r\n @Output() nodeChecked = new EventEmitter<any>();\r\n @Output() currentNodeChecked = new EventEmitter<{ id: string | number; data: ITreeNodeData }>();\r\n @Output() nodeEdited = new EventEmitter<TreeNode>();\r\n @Output() editValueChange = new EventEmitter<{ value: string; callback: Function }>();\r\n @Output() nodeDragStart = new EventEmitter<{ event: DragEvent; treeNode: TreeNode; treeNodes?: TreeNode[] }>();\r\n @Output() nodeOnDrop = new EventEmitter<{ event: DragEvent; treeNode: TreeNode; dropType: IDropType; isFromOutside?: boolean }>();\r\n @ViewChild('operableTree', { static: true }) operableTree: TreeComponent;\r\n @ViewChild('operableTreeContainer', { static: true }) operableTreeEle: ElementRef;\r\n @ViewChild('treeDropIndicator') treeDropIndicator: ElementRef;\r\n @ContentChild('iconTemplate') iconTemplate;\r\n @ContentChild('nodeTemplate') nodeTemplate;\r\n @ContentChild('operatorTemplate') operatorTemplate;\r\n @ContentChild('statusTemplate') statusTemplate;\r\n @Input() dropType: IDropType = {\r\n dropPrev: false,\r\n dropNext: false,\r\n dropInner: true,\r\n };\r\n private addingNode = false;\r\n private mouseRightButton = 2;\r\n private treeNodeDragoverResponder = {\r\n node: null,\r\n timeout: null,\r\n };\r\n @ViewChildren('treeNodeContent') treeNodeContent: QueryList<ElementRef>;\r\n id: string;\r\n i18nCommonText: I18nInterface['common'];\r\n i18nSubscription: Subscription;\r\n dragState = {\r\n showIndicator: true,\r\n dropType: null,\r\n draggingNode: null,\r\n indicatorTop: 0,\r\n indicatorLeft: 0,\r\n indicatorWidth: 0,\r\n };\r\n afterInitAnimate = true;\r\n document: Document;\r\n isOpenedOonDragOver = [];\r\n\r\n constructor(@Inject(DOCUMENT) private doc: any, private i18n: I18nService, private devConfigService: DevConfigService) {\r\n this.id = `d-operable-tree-${OperableTreeComponent.ID_SEED++}`;\r\n this.document = this.doc;\r\n }\r\n\r\n ngOnInit(): void {\r\n this.i18nCommonText = this.i18n.getI18nText().common;\r\n this.i18nSubscription = this.i18n.langChange().subscribe((data) => {\r\n this.i18nCommonText = data.common;\r\n });\r\n }\r\n\r\n ngAfterViewInit() {\r\n setTimeout(() => {\r\n this.afterInitAnimate = false;\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n if (this.i18nSubscription) {\r\n this.i18nSubscription.unsubscribe();\r\n }\r\n }\r\n\r\n contextmenuEvent(event, node) {\r\n this.nodeRightClicked.emit({ node: node, event: event });\r\n }\r\n\r\n copyStyle(source, target) {\r\n ['id', 'class', 'style', 'draggable'].forEach((attr) => target.removeAttribute(attr));\r\n\r\n const computedStyle = getComputedStyle(source);\r\n for (let i = 0; i < computedStyle.length; i++) {\r\n const key = computedStyle[i];\r\n if (key.indexOf('transition') < 0) {\r\n target.style[key] = computedStyle[key];\r\n }\r\n }\r\n target.style.pointerEvents = 'none';\r\n\r\n for (let i = 0; i < source.children.length; i++) {\r\n this.copyStyle(source.children[i], target.children[i]);\r\n }\r\n }\r\n\r\n multipleDragStyle(event, nodes, target) {\r\n const num = nodes.length > 2 ? 2 : nodes.length - 1;\r\n const cloneNodes = new Array(num).fill(null);\r\n const container = this.document.createElement('div');\r\n const cloneNode = target.cloneNode(true);\r\n this.copyStyle(target, cloneNode);\r\n cloneNode.style.position = 'absolute';\r\n cloneNode.style.border = 'solid 1px var(--devui-connected-overlay-line, #526ecc)';\r\n cloneNodes.push(cloneNode);\r\n cloneNodes.forEach((node, index) => {\r\n const child = node || cloneNode.cloneNode(true);\r\n child.style.left = `${8 * (num - index)}px`;\r\n child.style.top = `${4 * index}px`;\r\n container.appendChild(child);\r\n });\r\n container.className = 'devui-tree-drag-ghost-container';\r\n // setDragImage 只能对viewport内的 dom 起作用\r\n this.document.body.appendChild(container);\r\n event.dataTransfer.setDragImage(container, -16, 0);\r\n setTimeout(() => container.remove());\r\n }\r\n\r\n onDragstart(event, treeNode) {\r\n this.isOpenedOonDragOver = [];\r\n this.dragState.draggingNode = event.target;\r\n const result = { event, treeNode };\r\n const data = {\r\n type: 'operable-tree-node',\r\n treeId: this.id,\r\n nodeId: treeNode.id,\r\n parentId: treeNode.parentId,\r\n nodeTitle: treeNode.data.title,\r\n isParent: treeNode.data.isParent,\r\n isChecked: treeNode.data.isChecked,\r\n halfChecked: treeNode.data.halfChecked,\r\n };\r\n if (this.canActivateMultipleNode) {\r\n const activatedNodes = this.treeFactory.getActivatedNodes();\r\n // 存在无激活项,直接拖拽单个节点情况\r\n const availableNodes = activatedNodes.length ? activatedNodes : [treeNode];\r\n (data as any).multipleData = availableNodes;\r\n (result as any).treeNodes = availableNodes;\r\n // 拖拽启用层叠样式,随拖拽个数变化\r\n this.multipleDragStyle(event, availableNodes, event.target);\r\n }\r\n event.dataTransfer.setData('Text', JSON.stringify(data));\r\n this.nodeDragStart.emit(result);\r\n }\r\n\r\n onDragover(event, droppable, treeNode) {\r\n if (droppable) {\r\n event.preventDefault();\r\n event.dataTransfer.dropEffect = 'move';\r\n if (\r\n this.dropType.dropInner &&\r\n (!this.treeNodeDragoverResponder.node ||\r\n (this.treeNodeDragoverResponder.node && this.treeNodeDragoverResponder.node.id !== treeNode.id))\r\n ) {\r\n clearTimeout(this.treeNodeDragoverResponder.timeout);\r\n this.treeNodeDragoverResponder.node = treeNode;\r\n this.treeNodeDragoverResponder.timeout = setTimeout(() => {\r\n if (treeNode.data.isParent && !treeNode.data.isOpen) {\r\n this.isOpenedOonDragOver.push(treeNode.id);\r\n this.treeFactory.openNodesById(tree