import { ShallowRef } from 'vue';
import { PrivateRowDT, RowKeyGen, TreeConfig, UniqKey } from './types';
type DT = PrivateRowDT & { children?: DT[] };
type SetTreeExpandOption = {
/**
* 是否展开
* en: Whether to expand
* @default false
*/
expand?: boolean;
/**
* 是否展开所有子节点
* en: Whether to expand all child nodes
* @default false
* @version 1.0.4
*/
all?: boolean;
/**
* 展开到第几层
* en: Expand to the nth level
* @version 1.0.4
*/
level?: number;
/**
* 将传入 row 视为目标子节点,展开/收起其所有父节点;展开时若目标行自身有子节点则一并展开
* en: Treat the given row as a target child, expand/collapse all its ancestors. The target row itself is also expanded when expanding if it has children
* @version 1.1.0
*/
parents?: boolean;
};
export function useTree(props: any, dataSourceCopy: ShallowRef
, rowKeyGen: RowKeyGen, emits: any, onDataSourceChange: () => void) {
const { defaultExpandAll, defaultExpandKeys, defaultExpandLevel }: TreeConfig = props.treeConfig;
/** It used to check if it is first load. To execute defaultExpandXXX */
let isFirstLoad = true;
/** click expended icon to toggle expand row */
function toggleTreeNode(row: DT, col: any) {
const expand = row ? !row.__T_EXP__ : false;
privateSetTreeExpand(row, { expand, col, isClick: true });
}
/**
*
* @param row rowKey or row
* @param option
* @param option.expand expand or collapse
* @param option.all expand all descendants
* @param option.level expand to the nth level
* @param option.parents expand/collapse all ancestors of the given row, the target row itself is also expanded if it has children
* @param option.silent if set true, not emit `toggle-tree-expand`, default:false
*/
function privateSetTreeExpand(row: (UniqKey | DT) | (UniqKey | DT)[], option: SetTreeExpandOption & { col?: any; isClick: boolean }) {
const rowKeyOrRowArr: (UniqKey | DT)[] = Array.isArray(row) ? row : [row];
const tempData = dataSourceCopy.value.slice();
for (let i = 0; i < rowKeyOrRowArr.length; i++) {
const rowKeyOrRow = rowKeyOrRowArr[i];
let rowKey: UniqKey;
if (typeof rowKeyOrRow === 'string' || typeof rowKeyOrRow === 'number') {
rowKey = rowKeyOrRow;
} else {
rowKey = rowKeyGen(rowKeyOrRow);
}
const index = tempData.findIndex(it => rowKeyGen(it) === rowKey);
if (index === -1) {
console.warn('treeExpandRow failed.rowKey:', rowKey);
return;
}
const row = tempData[index];
const level = row.__T_LV__ || 0;
const wasExpanded = Boolean(row.__T_EXP__);
let expanded = option?.expand;
if (expanded === void 0) {
expanded = !row.__T_EXP__;
}
if (option.all || option.level !== void 0) {
const targetLevel = option.all ? Infinity : option.level || 0;
setDescendantsToLevel(row, level + 1, targetLevel, expanded);
}
if (expanded) {
if (wasExpanded) {
// already expanded, rebuild the flattened subtree so newly expanded
// descendants are inserted into the visible data source
const deleteCount = foldNode(index, tempData, level);
const children = expandNode(row, level);
tempData.splice(index + 1, deleteCount, ...children);
} else {
const children = expandNode(row, level);
tempData.splice(index + 1, 0, ...children);
}
} else {
// delete all child nodes from i
const deleteCount = foldNode(index, tempData, level);
tempData.splice(index + 1, deleteCount);
}
setNodeExpanded(row, expanded, level);
if (option.isClick) {
emits('toggle-tree-expand', { expanded: Boolean(expanded), row, col: option.col });
}
}
dataSourceCopy.value = tempData;
onDataSourceChange();
}
function setTreeExpand(row: (UniqKey | DT) | (UniqKey | DT)[], option?: SetTreeExpandOption) {
if (option?.parents) {
const rowKeyOrRow = Array.isArray(row) ? row[0] : row;
const rowKey = typeof rowKeyOrRow === 'string' || typeof rowKeyOrRow === 'number' ? rowKeyOrRow : rowKeyGen(rowKeyOrRow);
const path = findPath(props.dataSource || [], rowKey);
if (!path) {
console.warn('treeExpandRow failed.rowKey:', rowKey);
return;
}
const expanded = option?.expand !== false;
const target = path[path.length - 1];
const keys = path.slice(0, -1).map(it => rowKeyGen(it));
// 展开时若目标行自身有子节点则一并展开;收起时仅处理父节点,目标行自身状态不变
// en: when expanding, also expand the target row itself if it has children; when collapsing, only ancestors are handled
if (expanded && target.children?.length) keys.push(rowKeyGen(target));
if (!keys.length) return;
// 展开时从根到目标逐级展开;收起时逆序处理,避免先折叠根节点导致其余节点从可见数据中移除而查找失败
// en: expand from root to target; collapse in reverse order, otherwise collapsing the root first removes the rest nodes from visible data
if (!expanded) keys.reverse();
privateSetTreeExpand(keys, { expand: expanded, isClick: false });
return;
}
privateSetTreeExpand(row, { ...option, isClick: false });
}
/**
* 在原始树形数据中查找目标节点,返回从根节点到目标节点的完整路径(含目标节点自身)
* en: Find target node in raw tree data, return the full path from root to target node (target included)
* @returns full path including target, or null if target not found
*/
function findPath(data: DT[], targetKey: UniqKey): DT[] | null {
const path: DT[] = [];
function dfs(list: DT[]): boolean {
for (const item of list) {
if (rowKeyGen(item) === targetKey) {
path.push(item);
return true;
}
if (item.children) {
path.push(item);
if (dfs(item.children)) return true;
path.pop();
}
}
return false;
}
return dfs(data) ? path : null;
}
function setNodeExpanded(row: DT, expanded: boolean, level?: number, parent?: DT) {
row.__T_EXP__ = expanded;
if (level !== void 0) {
row.__T_LV__ = level;
}
// if (parent) {
// row.__T_P_K__ = rowKeyGen(parent);
// }
}
function recursionFlat(data: DT[] | undefined, level: number, parent?: DT): DT[] {
if (!data) return [];
let result: DT[] = [];
for (let i = 0; i < data.length; i++) {
const item = data[i];
result.push(item);
const isExpanded = Boolean(item.__T_EXP__);
setNodeExpanded(item, isExpanded, level, parent);
if (isFirstLoad && !isExpanded) {
// first load will expand all node if defaultExpandAll is true
if (defaultExpandAll) {
setNodeExpanded(item, true);
} else {
if (defaultExpandLevel && level < defaultExpandLevel) {
setNodeExpanded(item, true);
}
if (defaultExpandKeys?.includes(rowKeyGen(item))) {
setNodeExpanded(item, true);
}
}
}
if (item.__T_EXP__) {
const res = recursionFlat(item.children, level + 1, item);
result = result.concat(res);
}
}
return result;
}
/**
* 根据保存的展开状态,深度遍历,展平树形数据。
* en: flatten tree data by saved expand state.
* @param data
* @returns
*/
function flatTreeData(data: DT[]) {
const result = recursionFlat(data, 0);
isFirstLoad = false;
return result;
}
/**
* 递归设置目标节点后代到指定层级的展开/折叠状态
* en: Recursively set expand/collapse state for descendants up to the target level
*/
function setDescendantsToLevel(row: DT, currentLevel: number, targetLevel: number, expanded: boolean) {
if (!row.children || currentLevel > targetLevel) return;
for (const child of row.children) {
setNodeExpanded(child, expanded, currentLevel, row);
setDescendantsToLevel(child, currentLevel + 1, targetLevel, expanded);
}
}
function expandNode(row: DT, level: number) {
let result: DT[] = [];
row.children &&
row.children.forEach((child: DT) => {
result.push(child);
const childLv = level + 1;
if (child.__T_EXP__ && child.children) {
const res = expandNode(child, childLv);
result = result.concat(res);
} else {
setNodeExpanded(child, false, childLv, row);
}
});
return result;
}
function foldNode(index: number, tempData: DT[], level: number) {
let deleteCount = 0;
for (let i = index + 1; i < tempData.length; i++) {
const child = tempData[i];
if (child.__T_LV__ && child.__T_LV__ > level) {
deleteCount++;
} else {
break;
}
}
return deleteCount;
}
return [toggleTreeNode, setTreeExpand, flatTreeData] as const;
}