UNPKG

balm-ui

Version:

A modular and customizable UI library based on Material Design and Vue 3

558 lines (463 loc) 15.3 kB
const checkLeaf = (item, isLeaf, hasChildren) => item[isLeaf] || !hasChildren; const getNode = ( { selectedValue, nodeMap, dataFormat }, originNodeData, { level, parentKey } ) => { let item = Object.assign({}, originNodeData); const { value, children, hasChildren, isLeaf, disabled } = dataFormat; const nodeKey = item[value]; const nodeChildren = Array.isArray(item[children]) ? item[children] : []; const nodeHasChildren = item[hasChildren] || nodeChildren.length; const nodeIsLeaf = checkLeaf(item, isLeaf, nodeHasChildren); item.level = level; item.isRoot = !level; item.isLeaf = nodeIsLeaf; item.expanded = false; item.selected = !Array.isArray(selectedValue) && nodeKey === selectedValue; item.checked = Array.isArray(selectedValue) && selectedValue.includes(nodeKey); item.parentKey = parentKey; item.disabled = item[disabled]; if (!nodeChildren.length) { item[children] = []; } if (!nodeIsLeaf) { item.indeterminate = false; } if (!nodeMap.has(nodeKey)) { nodeMap.set(nodeKey, item); } return item; }; let selectedNodes = []; let parentKeys = []; class MdcTree { constructor(treeData) { this.treeData = treeData; } getData(nodes, level = 0, parentKey = '') { const { dataFormat, maxLevel } = this.treeData; const list = []; const { value, children, hasChildren } = dataFormat; for (let i = 0, len = nodes.length; i < len; i++) { let item = getNode(this.treeData, nodes[i], { level, parentKey }); const nodeChildren = Array.isArray(item[children]) ? item[children] : []; const nodeHasChildren = item[hasChildren] || nodeChildren.length; if (level < maxLevel && nodeHasChildren) { item[children] = this.getData(nodeChildren, level + 1, item[value]); } list.push(item); } return list; } /** For tree node */ static addData(treeData, item, nodes) { const { dataFormat, nodeMap } = treeData; const list = []; const { value, children } = dataFormat; const level = item.level + 1; const parentKey = item[value]; for (let i = 0, len = nodes.length; i < len; i++) { let subitem = getNode(treeData, nodes[i], { level, parentKey }); if (subitem.checked) { this.setMultipleSelectedValue(treeData, subitem[value], true); } list.push(subitem); } item[children] = list; item.expanded = true; nodeMap.set(parentKey, item); } static async onExpand(treeData, item) { if (treeData.loadData) { const { dataFormat } = treeData; const hasChildren = item[dataFormat.children] && item[dataFormat.children].length; if (hasChildren) { item.expanded = !item.expanded; } else { const nodes = await treeData.loadData(item[dataFormat.value], item); if (Array.isArray(nodes)) { this.addData(treeData, item, nodes); // review parent node const allChecked = nodes.every((node) => treeData.selectedValue.includes(node[dataFormat.value]) ); const hasChecked = allChecked ? true : nodes.some((node) => treeData.selectedValue.includes(node[dataFormat.value]) ); if (hasChecked) { if (allChecked || nodes.length === 1) { const parentNodeKey = nodes[0][dataFormat.parentKey]; if (parentNodeKey) { treeData.selectedValue.push(parentNodeKey); item.checked = true; } else { console.warn('[UiTree]', 'Missing `parentKey`'); } } else { item.indeterminate = true; } } } else { console.warn('[UiTree]', 'Invalid data'); } } } else { item.expanded = !item.expanded; } } static async collapseAllNode(treeData, nodes) { const { dataFormat, nodeMap } = treeData; for await (let node of nodes) { const nodeKey = node[dataFormat.value]; const item = nodeMap.get(nodeKey); item.expanded = false; if (item.children && item.children.length) { this.collapseAllNode(treeData, item.children); } } return true; } /** For single tree **/ static setSingleSelectedValue(treeData, nodeKey, selected) { const { nodeMap } = treeData; const item = nodeMap.get(nodeKey); if (item) { item.selected = selected; treeData.selectedEvent = { selected, selectedNodes: nodeKey, node: item }; } } static onSelect(treeData, item) { const { dataFormat, selectedValue } = treeData; const nodeKey = item[dataFormat.value]; if (selectedValue) { this.setSingleSelectedValue(treeData, selectedValue, false); } treeData.selectedValue = nodeKey; this.setSingleSelectedValue(treeData, nodeKey, true); } /** For multiple tree **/ static setMultipleSelectedValue(treeData, currentNodeKey, checked) { const { dataFormat, nodeMap, filterParentNode } = treeData; const item = nodeMap.get(currentNodeKey); if (checked && !item.indeterminate) { if (!treeData.selectedValue.includes(currentNodeKey)) { if (filterParentNode) { item.isLeaf && treeData.selectedValue.push(currentNodeKey); } else { treeData.selectedValue.push(currentNodeKey); } } } else { treeData.selectedValue = treeData.selectedValue.filter( (value) => value !== currentNodeKey ); } } static setChildrenCheckedValue(treeData, nodes, checked) { const { dataFormat, nodeMap } = treeData; const { value, children } = dataFormat; for (let i = 0, len = nodes.length; i < len; i++) { let item = Object.assign({}, nodes[i]); const nodeKey = item[value]; const nodeChildren = item[children]; const subitem = nodeMap.get(nodeKey); if (subitem) { if (checked) { !subitem.checked && selectedNodes.push(nodeKey); } else { subitem.checked && selectedNodes.push(nodeKey); } subitem.indeterminate = false; subitem.checked = checked; this.setMultipleSelectedValue(treeData, nodeKey, checked); } if (!item.isLeaf && nodeChildren.length) { this.setChildrenCheckedValue(treeData, nodeChildren, checked); } } } static setParentCheckedValue(treeData, item) { const { dataFormat, nodeMap } = treeData; const { value, children } = dataFormat; if (item) { const nodeKey = item[value]; const nodeChildren = item[children]; const nodeCheckedList = nodeChildren.filter( (subitem) => subitem.checked || subitem.indeterminate ); const subitem = nodeMap.get(nodeKey); if (nodeCheckedList.length) { const checkedAllList = nodeCheckedList.filter( (subitem) => subitem.checked ).length; const checkedAll = checkedAllList === nodeChildren.length; if (checkedAll) { !subitem.checked && selectedNodes.push(nodeKey); } else { subitem.checked && selectedNodes.push(nodeKey); } subitem.checked = checkedAll; subitem.indeterminate = !checkedAll; this.setMultipleSelectedValue(treeData, nodeKey, checkedAll); } else { subitem.checked = false; subitem.indeterminate = false; this.setMultipleSelectedValue(treeData, nodeKey, subitem.checked); } if (!item.isRoot) { this.setParentCheckedValue(treeData, nodeMap.get(item.parentKey)); } } } static onCheck(treeData, item, forceChecked = null) { let checked = !item.checked; if (typeof forceChecked === 'boolean') { checked = forceChecked; } const { dataFormat, nodeMap, singleChecked } = treeData; const { value, children } = dataFormat; const nodeKey = item[value]; const nodeChildren = item[children]; if (singleChecked) { item.checked = checked; this.setMultipleSelectedValue(treeData, nodeKey, checked); treeData.selectedEvent = { checked, checkedNodes: [nodeKey], node: item }; } else { selectedNodes = [nodeKey]; if (item.isLeaf) { item.checked = checked; this.setMultipleSelectedValue(treeData, nodeKey, checked); } else { if (item.indeterminate) { item.indeterminate = false; checked = true; } item.checked = checked; this.setMultipleSelectedValue(treeData, nodeKey, checked); this.setChildrenCheckedValue(treeData, nodeChildren, checked); } if (!item.isRoot) { this.setParentCheckedValue(treeData, nodeMap.get(item.parentKey)); } treeData.selectedEvent = { checked, checkedNodes: selectedNodes, node: item }; } } /** For expanding **/ static async handleExpandKeys(treeData, nodes, defaultExpandedKeys) { const { dataFormat, nodeMap } = treeData; for await (let node of nodes) { const nodeKey = node[dataFormat.value]; const item = nodeMap.get(nodeKey); defaultExpandedKeys.includes(nodeKey) && this.onExpand(treeData, item); if (node.children && node.children.length) { this.handleExpandKeys(treeData, node.children, defaultExpandedKeys); } } } static async handleExpandAll(treeData, nodes) { const { dataFormat, nodeMap } = treeData; for await (let node of nodes) { const nodeKey = node[dataFormat.value]; const item = nodeMap.get(nodeKey); this.onExpand(treeData, item); if (item.children && item.children.length) { this.handleExpandAll(treeData, item.children); } } } static async findTreeNode(tree, key, value) { if (tree[key] === value) return tree; if (tree.children && tree.children.length) { for (let i = 0; i < tree.children.length; i++) { const node = await this.findTreeNode(tree.children[i], key, value); if (node !== null) return node; } } return null; } static toReverseArray(arr) { const newArr = []; for (let i = arr.length - 1; i >= 0; i--) { newArr.push(arr[i]); } return newArr; } static async handleAutoExpandSelected( nodeList, key, selectedValue, treeData ) { const allNodeCollapsed = await this.collapseAllNode(treeData, nodeList); if (allNodeCollapsed) { const result = await this.findTreeNode(nodeList[0], key, selectedValue); parentKeys.push(result[key]); if (result.parentKey) { this.handleAutoExpandSelected( nodeList, key, result.parentKey, treeData ); } if (!result.parentKey) { const reversedArr = this.toReverseArray(parentKeys); treeData && this.handleExpandKeys(treeData, nodeList, reversedArr); } } } /** For init tree **/ static async setExpanded( treeData, nodeList, { autoExpandParent, defaultExpandedKeys, autoExpandAll } ) { const { dataFormat, nodeMap } = treeData; if (autoExpandAll) { this.handleExpandAll(treeData, nodeList); } if (autoExpandParent) { if (defaultExpandedKeys.length) { this.handleExpandKeys(treeData, nodeList, defaultExpandedKeys); } else { for await (let node of nodeList) { const nodeKey = node[dataFormat.value]; const item = nodeMap.get(nodeKey); this.onExpand(treeData, item); } } } } static resetSelected(treeData, oldSelectedKeys) { const { nodeMap } = treeData; for (let i = 0, len = oldSelectedKeys.length; i < len; i++) { const nodeKey = oldSelectedKeys[i]; const item = nodeMap.get(nodeKey); if (item) { this.onCheck(treeData, item, false); } } } static setSelected( treeData, newSelectedKeys, { nodeList, autoExpandSelected } ) { const { dataFormat, nodeMap, multiple } = treeData; const selectedKeys = Array.isArray(newSelectedKeys) ? newSelectedKeys : [newSelectedKeys]; for (let i = 0, len = selectedKeys.length; i < len; i++) { const nodeKey = selectedKeys[i]; const item = nodeMap.get(nodeKey); if (item) { multiple ? this.onCheck(treeData, item, true) : this.onSelect(treeData, item); } } if (autoExpandSelected && !Array.isArray(newSelectedKeys)) { parentKeys = []; this.handleAutoExpandSelected( nodeList, dataFormat.value, newSelectedKeys, treeData ); } } /** For tree operation **/ static async createNode(treeData, parentKey, originNodeData) { const { dataFormat, nodeMap } = treeData; const { value, children, hasChildren } = dataFormat; const parentItem = nodeMap.get(parentKey); const nodeKey = originNodeData[value]; let item = getNode(treeData, originNodeData, { level: parentItem.level + 1, parentKey, checked: false }); if (parentItem.isLeaf) { parentItem[children].unshift(item); if (!parentItem[hasChildren]) { parentItem[hasChildren] = true; } parentItem.isLeaf = false; } else { if (parentItem[hasChildren]) { if (parentItem[children].length) { parentItem[children].unshift(item); } else { await this.onExpand(treeData, parentItem); } } else { parentItem[children].unshift(item); parentItem[hasChildren] = true; parentItem.expanded = true; } } nodeMap.set(parentKey, parentItem); nodeMap.set(nodeKey, item); } static updateNode(treeData, parentKey, originNodeData) { const { dataFormat, nodeMap } = treeData; const { value, children } = dataFormat; const nodeKey = originNodeData[value]; const item = nodeMap.get(nodeKey); Object.keys(item).forEach((key) => { if (typeof originNodeData[key] !== 'undefined') { item[key] = originNodeData[key]; } }); const parentItem = nodeMap.get(parentKey); const parentChildren = parentItem[children]; const index = parentChildren.findIndex((item) => item[value] === nodeKey); parentItem[children][index] = item; nodeMap.set(parentKey, parentItem); nodeMap.set(nodeKey, item); } static deleteNode(treeData, parentKey, originNodeData) { const { dataFormat, nodeMap } = treeData; const { value, children, hasChildren } = dataFormat; const nodeKey = originNodeData[value]; if (nodeMap.has(nodeKey)) { const parentItem = nodeMap.get(parentKey); const parentChildren = parentItem[children]; parentChildren.splice( parentChildren.findIndex((item) => item[value] === nodeKey), 1 ); parentItem[hasChildren] = parentChildren.length; if (!parentItem[hasChildren]) { parentItem.isLeaf = true; parentItem.expanded = false; } nodeMap.set(parentKey, parentItem); nodeMap.delete(nodeKey); } } } export { MdcTree };