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; } };