Restructure code

This commit is contained in:
2025-12-28 23:06:37 +01:00
parent 26e15356b5
commit e7b858a511
9 changed files with 928 additions and 951 deletions

123
mixins/tree-reveal.js Normal file
View File

@@ -0,0 +1,123 @@
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 };
}
};