145 lines
4.4 KiB
JavaScript
145 lines
4.4 KiB
JavaScript
export const TreeManagedStateMixin = (superClass) => class TreeManagedStateMixin extends superClass {
|
|
_isExpandableNode(node) {
|
|
if (!node) return false;
|
|
if (Array.isArray(node.children) && node.children.length > 0) return true;
|
|
if (typeof this.loadChildren === 'function') {
|
|
if (typeof node.showAsExpandable === 'boolean') return node.showAsExpandable;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
_findNodeByKey(key) {
|
|
let found = null;
|
|
const walk = (nodes, path) => {
|
|
if (!Array.isArray(nodes)) return;
|
|
nodes.forEach((node, index) => {
|
|
if (found) return;
|
|
const slug = node?.slug ?? `${index}`;
|
|
const nextPath = [...path, slug];
|
|
const nodeKey = nextPath.join('/');
|
|
if (nodeKey === key) {
|
|
found = node;
|
|
return;
|
|
}
|
|
if (Array.isArray(node?.children) && node.children.length > 0) {
|
|
walk(node.children, nextPath);
|
|
}
|
|
});
|
|
};
|
|
walk(this.roots, []);
|
|
return found;
|
|
}
|
|
|
|
getRootKey() {
|
|
const roots = Array.isArray(this.roots) ? this.roots : [];
|
|
if (!roots.length) return '';
|
|
const first = roots[0];
|
|
const slug = first?.slug ?? '0';
|
|
return String(slug);
|
|
}
|
|
|
|
_updateNodeStatesByKey(key, updater) {
|
|
const map = (nodes, path = []) => {
|
|
if (!Array.isArray(nodes)) return nodes;
|
|
return nodes.map((node, index) => {
|
|
const slug = node?.slug ?? `${index}`;
|
|
const nextPath = [...path, slug];
|
|
const nodeKey = nextPath.join('/');
|
|
|
|
let nextNode = node;
|
|
if (nodeKey === key) {
|
|
nextNode = updater(node) ?? node;
|
|
}
|
|
|
|
const children = Array.isArray(nextNode?.children)
|
|
? map(nextNode.children, nextPath)
|
|
: nextNode?.children;
|
|
|
|
if (children !== nextNode?.children) {
|
|
return { ...nextNode, children };
|
|
}
|
|
|
|
return nextNode;
|
|
});
|
|
};
|
|
|
|
this.roots = map(this.roots);
|
|
}
|
|
|
|
_applySelectionStates() {
|
|
const selected = new Set(this._selectedPathSet);
|
|
const selectionState = this.selectionState;
|
|
const { nodes } = this._mapTree(this.roots, (node, path) => {
|
|
const key = Array.isArray(path) ? path.join('/') : '';
|
|
const states = Array.isArray(node?.states) ? node.states : [];
|
|
const without = states.filter((s) => s !== selectionState);
|
|
if (selected.has(key)) {
|
|
if (!without.includes(selectionState)) without.push(selectionState);
|
|
return { ...node, states: without };
|
|
}
|
|
if (without.length !== states.length) {
|
|
return { ...node, states: without };
|
|
}
|
|
return node;
|
|
});
|
|
this.roots = nodes;
|
|
}
|
|
|
|
_rebuildManagedTree() {
|
|
this._expandedPathSet = new Set(this.expandedPaths || this._expandedPathSet);
|
|
this._selectedPathSet = new Set(this.selectedPaths || this._selectedPathSet);
|
|
|
|
const { nodes, allPaths, expandedPaths, selectedPaths } = this.constructor.buildTree(
|
|
Array.isArray(this.items) ? this.items : [],
|
|
{
|
|
expandedPaths: this._expandedPathSet,
|
|
selectedPaths: this._selectedPathSet,
|
|
selectionState: this.selectionState,
|
|
applyStates: this.applyStates,
|
|
knownPaths: this._knownPaths,
|
|
autoExpandNew: this.autoExpandNew,
|
|
}
|
|
);
|
|
|
|
this._knownPaths = allPaths;
|
|
this._expandedPathSet = new Set([...expandedPaths].filter((p) => allPaths.has(p)));
|
|
this._selectedPathSet = new Set([...selectedPaths].filter((p) => allPaths.has(p)));
|
|
this.roots = nodes;
|
|
}
|
|
|
|
getNodesWithState(state) {
|
|
const matches = [];
|
|
this._walkNodes(this.roots, [], (node, path) => {
|
|
if (Array.isArray(node?.states) && node.states.includes(state)) {
|
|
matches.push({ node, path });
|
|
}
|
|
});
|
|
return matches;
|
|
}
|
|
|
|
clearState(state) {
|
|
const { nodes } = this._mapTree(this.roots, (node) => {
|
|
const states = Array.isArray(node?.states)
|
|
? node.states.filter((s) => s !== state)
|
|
: [];
|
|
const changed = (node.states?.length ?? 0) !== states.length;
|
|
return changed ? { ...node, states } : node;
|
|
});
|
|
return nodes;
|
|
}
|
|
|
|
applyStateIf(state, predicate) {
|
|
if (typeof predicate !== 'function') return this.roots;
|
|
const { nodes } = this._mapTree(this.roots, (node, path) => {
|
|
const states = Array.isArray(node?.states) ? [...node.states] : [];
|
|
if (predicate(node, path) && !states.includes(state)) {
|
|
states.push(state);
|
|
return { ...node, states };
|
|
}
|
|
return node;
|
|
});
|
|
return nodes;
|
|
}
|
|
};
|