export const TreeRevealMixin = (superClass) => class TreeRevealMixin extends superClass { async scrollToKey(key) { await this.updateComplete; const idx = this._findFlatIndexByKey(key); if (idx < 0) return false; const scroller = this.shadowRoot?.querySelector('.tree'); if (scroller?.scrollToIndex) { scroller.scrollToIndex(idx, { block: 'center' }); return true; } return false; } /** * Expand+load ancestors so `path` becomes visible, then optionally select/scroll it. * `path` is a key path (slug path), like `root/dir/file`. */ async revealPath(path, options = {}) { const segments = this._normalizeKeyPath(path); if (!segments.length) return { found: false, key: '', node: undefined }; const select = options.select !== false; const scroll = options.scroll !== false; const open = options.open === true; const source = options.source ?? 'revealPath'; const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 15000; if (this._revealController) { try { this._revealController.abort(); } catch (e) { } } const controller = new AbortController(); this._revealController = controller; const externalSignal = options.signal; if (externalSignal?.aborted) return { found: false, key: segments.join('/') }; const abortIfExternal = () => { try { controller.abort(); } catch (e) { } }; externalSignal?.addEventListener?.('abort', abortIfExternal, { once: true }); const start = performance.now(); const ensureNotTimedOut = () => { if (timeoutMs <= 0) return; if (performance.now() - start > timeoutMs) { const err = new Error('revealPath timeout'); err.name = 'TimeoutError'; throw err; } }; const ensureExpanded = (key) => { if (!this.manageState) return; const set = new Set(this._expandedPathSet); if (!set.has(key)) { set.add(key); this._expandedPathSet = set; this.expandedPaths = Array.from(set); this._rebuildManagedTree(); } }; const ensureSelected = (key) => { if (!this.manageState) return; const set = new Set(); set.add(key); this._selectedPathSet = set; this.selectedPaths = Array.from(set); this._rebuildManagedTree(); }; let currentKey = ''; for (let i = 0; i < segments.length; i++) { ensureNotTimedOut(); if (controller.signal.aborted) break; currentKey = currentKey ? `${currentKey}/${segments[i]}` : segments[i]; const node = this._findNodeByKey(currentKey); if (!node) { externalSignal?.removeEventListener?.('abort', abortIfExternal); return { found: false, key: currentKey, node: undefined }; } const isLeaf = i === segments.length - 1; ensureExpanded(currentKey); if (!isLeaf && typeof this.loadChildren === 'function') { await this._loadChildrenForExpanded(currentKey, source, null, node, currentKey.split('/')); if (controller.signal.aborted) break; const nextKey = `${currentKey}/${segments[i + 1]}`; if (!this._findNodeByKey(nextKey)) { externalSignal?.removeEventListener?.('abort', abortIfExternal); return { found: false, key: nextKey, node: undefined }; } } } if (controller.signal.aborted) { externalSignal?.removeEventListener?.('abort', abortIfExternal); return { found: false, key: currentKey, node: this._findNodeByKey(currentKey) }; } if (open && typeof this.loadChildren === 'function') { const leafKey = currentKey; const leafNode = this._findNodeByKey(leafKey); if (leafNode && this._isExpandableNode(leafNode)) { ensureExpanded(leafKey); await this._loadChildrenForExpanded(leafKey, source, null, leafNode, leafKey.split('/')); } } const finalNode = this._findNodeByKey(currentKey); if (finalNode && select) { ensureSelected(currentKey); } if (finalNode && scroll) { await this.scrollToKey(currentKey); } externalSignal?.removeEventListener?.('abort', abortIfExternal); return { found: Boolean(finalNode), key: currentKey, node: finalNode }; } };