@e280/quay
Version:
File-browser and outliner UI for the web
69 lines • 2.22 kB
JavaScript
import { MapG } from "@e280/stz";
/**
* Tree of nestable things.
* - Things can have multiple children.
* - Things can only have one parent.
* - Things can be orphans (bring your own concept of a "root").
*/
export class Hierarchy {
#children = new MapG();
#parents = new MapG();
has(id) {
return this.#children.has(id);
}
getChildren(id) {
return this.#children.require(id);
}
getParent(id) {
return this.#parents.get(id);
}
/** establish an id as a root with no parents, but ready to accept children */
establishRoot(root) {
this.#children.set(root, new Set());
}
/** give a parent some children */
attach(parent, ...children) {
const siblings = this.getChildren(parent);
for (const childId of children) {
if (this.getParent(childId))
throw new Error(`child already has parent`);
siblings.add(childId);
this.#parents.set(childId, parent);
this.#children.set(childId, new Set());
}
}
/** detach this id from its parent in the hierarchy */
detach(id) {
if (!this.has(id))
return undefined;
const parent = this.getParent(id);
if (parent) {
const siblings = this.getChildren(parent);
siblings.delete(id);
this.#parents.delete(id);
}
}
/** destroy all relations associated with this id, and all its descendants */
destroy(id) {
const tree = [...this.crawl(id)];
for (const [id] of tree) {
this.#children.delete(id);
this.#parents.delete(id);
}
}
/** iterate over this id and all its descendants */
*crawl(id, predicate = () => true) {
const todo = [[id, []]];
const seen = new Set();
while (todo.length) {
const [id, path] = todo.shift();
if (seen.has(id) || !predicate(id, path))
continue;
seen.add(id);
yield [id, path];
for (const childId of this.getChildren(id))
todo.push([childId, [...path, id]]);
}
}
}
//# sourceMappingURL=hierarchy.js.map