import { type DynamicProvider, inject, UniqueToken } from "@storyteller/framework"; import { LitElement, type PropertyValues, html, nothing, unsafeCSS } from "lit"; import { customElement, property } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { ifDefined } from "lit/directives/if-defined.js"; import "./icon.element"; import styles from "./tree.element.scss?inline"; export const TREE_SELECTION_PROVIDER = UniqueToken.create(); export interface TreeSelectionProvider extends DynamicProvider { readonly selectedId: string | null; } export interface Tree { id?: string; name: string; icon?: string; children?: Tree[]; } @customElement("sts-tree") export class TreeElement extends LitElement { static override styles = unsafeCSS(styles); @property() name = ""; @property() icon?: string; @property({ attribute: false }) treeChildren?: Tree[]; @property({ type: Boolean }) expanded = true; get selected() { return !!this.id && this._selectionProvider?.selectedId === this.id } @inject(TREE_SELECTION_PROVIDER) _selectionProvider?: TreeSelectionProvider; override connectedCallback(): void { this._selectionProvider?.addEventListener("property-changes", this.onProviderChanges); super.connectedCallback(); } override disconnectedCallback(): void { this._selectionProvider?.removeEventListener("property-changes", this.onProviderChanges); super.disconnectedCallback(); } onProviderChanges = ({ detail: changes }: CustomEvent>) => { if (changes.has("selectedId")) this.requestUpdate(); } toggleExpanded(): void { this.expanded = !this.expanded; } selectEntity(): void { if (this.id) { this.dispatchEvent(new CustomEvent("tree-select", { detail: this.id, bubbles: true, composed: true, })); } } // TODO: A11y protected override render = () => html`
${this.treeChildren?.length ? html` ` : nothing} ${this.icon ? html` ` : nothing} ${this.name}
${this.expanded && this.treeChildren?.length ? html`
${this.treeChildren?.map(tree => html` `)}
` : nothing} `; }