export const TreeUtilsMixin = (superClass) => class TreeUtilsMixin extends superClass { _normalizeKeyPath(path) { if (Array.isArray(path)) return path.map((s) => String(s)).filter(Boolean); if (typeof path !== 'string') return []; return path.split('/').map((s) => s.trim()).filter(Boolean); } _resolveIcon(icon) { if (!icon) return null; if (typeof icon === 'string') { return this.constructor?.defaultIcons?.[icon] ?? null; } return icon; } _normalizeActions(actions) { if (!actions) return []; if (Array.isArray(actions)) return actions; if (typeof actions === 'object') return Object.values(actions); return []; } _mergeActions(defaults = [], nodeActions = []) { const map = new Map(); const add = (list) => { list?.forEach((a) => { if (a && a.action) { map.set(a.action, a); } }); }; add(defaults); add(nodeActions); return Array.from(map.values()); } _walkNodes(nodes, path, visitor) { if (!Array.isArray(nodes)) return; nodes.forEach((node, index) => { const slug = node?.slug ?? `${index}`; const nextPath = [...path, slug]; visitor(node, nextPath); if (Array.isArray(node?.children) && node.children.length) { this._walkNodes(node.children, nextPath, visitor); } }); } _mapTree(nodes, mapper, path = []) { if (!Array.isArray(nodes)) return { nodes: [], changed: false }; let changed = false; const mapped = nodes.map((node, index) => { const slug = node?.slug ?? `${index}`; const nextPath = [...path, slug]; const mappedNode = mapper(node, nextPath) ?? node; const { nodes: childNodes, changed: childChanged } = this._mapTree( mappedNode?.children, mapper, nextPath ); const children = childChanged ? childNodes : mappedNode?.children; const nodeChanged = mappedNode !== node || childChanged; if (nodeChanged) { changed = true; return { ...mappedNode, children }; } return mappedNode; }); return { nodes: mapped, changed }; } };