import{type PropertyValues,type TemplateResult}from'lit';import{LyraElement,type LyraEventDetailSnapshot}from'../../../internal/lyra-element.js';import'./tree-item.class.js';import type{LyraTreeItem}from'./tree-item.class.js';import type{TreeBadge,LyraTreeNodeData,TreeSelection}from'./tree-types.js';export type{TreeBadge,LyraTreeNodeData,TreeSelection};export interface LyraTreeEventMap{'lr-node-toggle':CustomEvent<{nodeId:string;expanded:boolean;}>;'lr-node-select':CustomEvent<{nodeId:string;}>;'lr-reorder':CustomEvent<{nodeId:string;parentNodeId:string|null;fromIndex:number;toIndex:number;}>;'lr-selection-change':CustomEvent>;'lr-expand':CustomEvent>;'lr-after-expand':CustomEvent>;'lr-collapse':CustomEvent>;'lr-after-collapse':CustomEvent>;'lr-lazy-change':CustomEvent>;'lr-lazy-load':CustomEvent>;} /** * `` — an expand/collapse hierarchy for graph/document navigation. * * **Two child models are accepted.** Nested `` elements written as light-DOM children * mirror `wa-tree`/`sl-tree`, so that markup renames mechanically; each item carries its own * `label`/`expanded`/`disabled`/`selected` (see ``). Assigning `data` — a `LyraTreeNodeData[]` * of plain objects, which additionally supports per-row icons, descriptions and badges — is this * library's own original shape and remains fully supported. A tree containing any author-written * `` child is read purely as the declarative model and `data` is ignored, so the two * never interleave ambiguously; the empty state renders only when neither model has any items. * Data-model `LyraTreeNodeData.id` values are nonblank global identities and must be unique across * the reachable hierarchy. Malformed rows and later duplicate ids are omitted before rendering, * focus, selection, expansion, or reorder requests; the first valid depth-first occurrence wins. * * Implements the WAI-ARIA treeitem keyboard pattern: a single roving * `tabindex` (tracked here as `activeId`, pushed down to every * `` — including nested ones, recursively) and * ArrowUp/Down/Right/Left/Home/End/Enter/Space handled by one delegated * `keydown` listener. Native `KeyboardEvent`s are `composed: true` and * bubble across shadow-DOM boundaries, so a press inside a deeply-nested * ``'s own shadow root still reaches this listener. * * **`inert` excludes an item and its whole subtree from that navigation exactly as `disabled` * does** — the roving `tabindex` and `role="treeitem"` live on the `` host itself, so * an inert item refuses `focus()` outright. Marking the focused item inert therefore moves the * roving target, and real focus with it, instead of stranding focus on ``. Only `inert` * *inside* the tree counts: a tree the page behind an open modal has inerted keeps its selection, * its roving target, and its `activeId` untouched. Selection is deliberately unaffected either way * — inert means "not interactive right now", never "deselected". * * Set `reorderable` to opt into keyboard reordering: Ctrl/Cmd+ArrowUp/ArrowDown on the focused * node emits `lr-reorder` — a *request*, exactly like every other event here. `data` is * host-owned and never mutated by this component, so nothing moves until the host reassigns a * reordered `data`; focus then follows the moved node. The keybinding matches * ``'s `cells-draggable` precedent (Alt+Arrow is browser back/forward on * Windows/Linux). `` deliberately **opts out**: its `LyraTreeNodeData[]` is derived from * `nodes` on every render and keyed by filesystem path, an order it does not own. * The reorder live region announces success only after a rendered sibling-order change confirms * the host accepted the exact requested swap. Ignored, delayed, or rejected requests never claim * that a move already happened; unrelated updates keep an asynchronous request pending. * * @customElement lr-tree * @event lr-node-toggle - `detail: { nodeId, expanded }`, dispatched by a descendant `` and observed here (bubbling, composed) to keep the roving-tabindex `activeId` in sync. * @event lr-node-select - `detail: { nodeId }`, dispatched by a descendant `` and observed here (bubbling, composed) to keep the roving-tabindex `activeId` in sync. * @event lr-reorder - `detail: { nodeId, parentNodeId, fromIndex, toIndex }` — Ctrl/Cmd+ArrowUp/ArrowDown requests moving the focused node within its **own parent's** child list (`parentNodeId` is `null` for a top-level item; the indices are sibling-scoped, not flattened-visible-list positions). Only fired while `reorderable`. Never fires at a subtree boundary, so a reorder can never become a reparent. Success is announced only after the rendered sibling order confirms the request. * @event lr-selection-change - Selection changed. `detail: { selection }`, where `selection` is the current `selectedItems` array. * @event lr-expand - Bubbles from the item whose expansion began. `detail: { item }`. * @event lr-after-expand - Bubbles after an item's expansion motion completes. `detail: { item }`. * @event lr-collapse - Bubbles from the item whose collapse began. `detail: { item }`. * @event lr-after-collapse - Bubbles after an item's collapse motion completes. `detail: { item }`. * @event lr-lazy-change - Bubbles when an item's pending lazy-loading state changes. `detail: { item, loading }`. * @event lr-lazy-load - Bubbles when a lazy item requests children. `detail: { item, generation }`. * @csspart base - Compatibility name for the root wrapper; `tree` is the component-specific alias. * @csspart tree - The tree's root wrapper (`role="tree"`). It is the same node as `base`. * @csspart empty - The empty-state message shown when neither child model has any items. * @slot - Top-level `` elements, each nesting its own children — the declarative child model. Leave it empty and assign `data` instead for the object model. * @slot expand-icon - Default icon shown by expanded items; an item-level slot takes precedence. * @slot collapse-icon - Default icon shown by collapsed items; an item-level slot takes precedence. * @cssprop [--indent-size=var(--lr-space-l)] - Indentation step for nested items. * @cssprop [--indent-guide-color=var(--lr-color-border)] - Indentation guide color. * @cssprop [--indent-guide-offset=0] - Block-axis inset for indentation guides. * @cssprop [--indent-guide-style=solid] - Indentation guide border style. * @cssprop [--indent-guide-width=0] - Indentation guide width. * @status stable * @since 4.0.0 */ export declare class LyraTree extends LyraElement{protected static readonly immutableEventDetails:readonly string[];protected static readonly identityEventDetailProperties:Readonly<{'lr-selection-change':readonly string[];}>;static styles:import("lit").CSSResultGroup[]; /** Object child model. Installed values are clone-owned/frozen and bounded to 1,000 nodes over * at most 64 descendant levels. Every reachable `LyraTreeNodeData.id` must be globally unique; * later duplicate occurrences fail closed as disabled rows so one public id can never own * multiple actions. Reassign after changes. */ private _data;private declaredChildrenAtPath;private declaredRootCount;private _dataTruncated; /** Clone-owned/frozen object child model. Normalization retains at most 1,000 nodes over 64 * descendant levels and inspects at most 10,000 root/child array positions in depth-first order. * An otherwise unnamed projected row uses its stable data ID as its semantic name, without * changing its visible label or the installed data. * @default [] */ get data():readonly LyraTreeNodeData[];set data(value:readonly LyraTreeNodeData[]); /** Whether normalization omitted malformed, over-depth, or over-budget data. */ get dataTruncated():boolean; /** * Accessible-name fallback for the internal `role="tree"` element. A host `aria-label` wins by * attribute presence, including when explicitly empty; `label` is used only when that attribute * is absent. External `aria-labelledby` idrefs are not forwarded across the shadow boundary. */ label:string; /** * Opts into Ctrl/Cmd+ArrowUp/ArrowDown keyboard reordering (see the class doc). Defaults to * `false`: unset, no `lr-reorder` is ever emitted, Ctrl/Cmd+Arrow keeps behaving exactly like * a plain Arrow press, and the internal live region is not rendered at all. */ reorderable:boolean;private _selection; /** Selection behavior. Multiple modes cascade through enabled descendants and expose checkboxes. * @default 'single' */ get selection():TreeSelection;set selection(value:TreeSelection);private activeId; /** Whether the tree is being driven by author-written `` children rather than by * `data` (see the class doc's child-model note). Recomputed from the light DOM, never guessed. */ private hasAuthoredItems; /** Set by `willUpdate()` when a `data` reassignment displaces the node that currently holds real DOM focus -- either by removing it (refocus the newly-designated `activeId`) or by merely re-indexing it (refocus that same node); consumed by `getUpdateComplete()` once the target is actually focusable again. */ private pendingFocusId; /** The `` elements `syncNodes()` created from `data`. Everything else among this * element's children was written by the author, which is what puts the tree in the declarative * child model -- an identity set is the only reliable way to tell the two apart, since a * generated node and an authored one are the same tag. */ private readonly generatedNodes; /** First depth-first path owning each public data id, plus the ids seen at later paths. */ private dataIdOwnerPaths;private dataIdCollisions;private selectionSyncPending;private dataSyncPending; /** Set when the child observer reports an `inert` attribute mutation, consumed by * `resolveActiveFromDom()`'s focus repair below. */ private inertMutationPending; /** The last item this tree saw take real focus. Read only as corroboration that a focus loss the * platform caused (see `resolveActiveFromDom()`) actually happened *here*. */ private lastFocusedNodeId;private liveRegion?;private pendingReorder?; /** Top-level items only. Deliberately `:scope >`: in the declarative child model nested items are * light-DOM descendants of *this* element too, and a plain descendant query would flatten the * whole hierarchy into the top-level set (wrong `aria-setsize`/`aria-posinset`, wrong roving * order). In the data model the two queries are equivalent, since nested items are rendered into * their own parent's shadow root. */ private get nodeElements();private childrenOf; /** Whether `node` can hold the roving `tabindex` and receive arrow-key focus. `inert` counts * alongside `isDisabled` — see `isInertWithin()`. Deliberately *not* consulted by the selection * engine: an inert subtree is temporarily non-interactive, not deselected, and a modal inerting * the page must never silently wipe a tree's selection. */ private isNavigable; /** Every item in document order, including descendants of collapsed branches. */ private allNodeElements; /** The current selected item elements in document order. */ get selectedItems():readonly LyraTreeItem[];private iconSource;private applyTreeContext;private selectableInSingleMode;private normalizeSingleSelection;private setBranchSelection;private deriveMultipleSelection;private normalizeMultipleSelection;private normalizeSelection;private selectionSignature;private updateSelectionFrom; /** Recomputed from the DOM rather than tracked incrementally: children can be added by the parser, * by a framework re-render, or by `syncNodes()` itself, and only the generated-node set is a * reliable discriminator. */ private refreshAuthoredItems; /** * Every currently *visible* (ancestor-expanded) node, top-to-bottom. * * Recomputed on every call rather than memoized: `item`/`expanded` are * plain public settable properties on `` (not just * reachable through this class's own `data` setter or the bubbling * `lr-node-toggle` event), so a cache keyed off those two entry points * alone would go stale the moment a caller mutated a node directly -- * e.g. `node.item = { ...node.item, children: [...] }` to append a child * in place. This walk only runs from user-paced `keydown` handling (never * a hot render-loop path), so the cost of a `shadowRoot.querySelectorAll` * per currently-expanded node is not worth trading for that staleness risk. */ private visibleNodeElements;private findItem;private isDuplicateDataPath; /** Analyze every reachable occurrence without recursion so adversarially deep data cannot * overflow the call stack. Cycles count the repeated rendered occurrence, then stop at it. */ private rebuildDataIdentity;private treeIdentityAt;private firstEnabledId;private isEnabledReachableId; /** Resolve the exact rendered sibling list for either child model. Data descendants live in * their parent item's shadow root; declarative descendants remain light-DOM children. */ private findRenderedSiblings; /** * The `` that genuinely holds real DOM focus, or `null`. * * `document.activeElement` collapses to the outermost light-DOM node even * when the real focus target is a nested descendant several shadow roots * down, so it can't distinguish "the top-level node is focused" from "one of * its nested descendants is". Walking the `shadowRoot.activeElement` chain * resolves the actual node, which is what lets a `data` reassignment restore * focus to a *nested* node rather than yanking it up to that node's * top-level ancestor. */ private deepFocusedNode; /** * The declarative child model's answer to the `data`-driven `activeId` resolution below: there is * no `data` change to hang it off, so it is re-derived from the DOM on every update instead. If * `activeId` no longer names a currently *visible* node -- removed, disabled, inert, or hidden * inside a collapsed ancestor -- the first visible one takes over, so the tree never ends up with zero * `tabindex="0"` stops and silently drops out of the tab order. Deliberately in `willUpdate()` * rather than `updated()`: the nodes are light-DOM children, so they already exist before this * element renders, and assigning here folds into the current update instead of scheduling * another one. */ private resolveActiveFromDom; /** Remembers which item last held real focus -- see `resolveActiveFromDom()`'s focus repair. */ private onTreeFocusIn;protected willUpdate(changed:PropertyValues):void;protected updated(changed:PropertyValues):void; /** Children changed: re-derive which child model is in play, and (via the requested update) * re-resolve the roving tabindex. Covers author-written items arriving from the HTML parser or * a framework re-render *after* this element first updated -- the case `willUpdate()`'s * synchronous read cannot see -- and the active node being removed, which changes nothing * about `hasAuthoredItems` and so would otherwise schedule no update at all. */ private onChildrenChanged; /** `slotchange` sees an assignment change but not a child that never becomes assigned -- a node * moved here still carrying its old parent item's `slot="children"` is exactly that, and * `refreshAuthoredItems()` is what un-strands it. Subtree observation also catches nested * `selected`/`disabled`/`lazy` changes because those affect the tree-owned selection engine; * each item still owns its own child-slot reconciliation. */ private childObserver?;private childObserverDocument?;private childObserverGeneration; /** The observer's own entry point: it additionally records whether an `inert` toggle was among * the records, which `resolveActiveFromDom()` needs to tell a platform-caused focus loss apart * from any other reason the active item stopped being navigable. */ private onChildMutations;connectedCallback():void;private armChildObserver;disconnectedCallback():void;adoptedCallback():void;private resetChildObserver; /** By-id reconciliation of top-level items: reuses/reorders existing `` elements and removes ones no longer present in `data`. */ private syncNodes;private focusNode; /** * A mouse click always lands directly on the node it interacts with -- * `select()`/`expand()`/`collapse()` all emit their own node's id -- * independent of whatever `activeId` currently holds. Sync `activeId` to * that id here so a click always becomes the tree's new roving-tabindex * target: this keeps it aligned with real DOM focus, keeps the next * arrow-key press relative to the item the user just clicked (rather than * a stale `activeId`), and keeps `activeId` valid when a click collapses * an ancestor of the previously-active node -- the collapsed node's own * id (always still visible, since collapsing never removes a node's own * top-level or already-rendered self) replaces the now-hidden descendant's * id, so at least one node keeps a roving tabindex of 0. Keyboard-driven * toggles/selects always target the already-active node, so this is a * same-value, no-op assignment for them. */ private onNodeActivate;private onNodeSelect; /** * `updated()` only pushes the new `activeId` to *top-level* nodes; nested * nodes only receive it once their ancestor chain's own renders cascade it * down (one more pending update per depth level). Cascade `updateComplete` * to match (see `cascadeUpdateComplete`), so `focusNode()`'s `.focus()` * call never runs while a nested target is still mid-cascade -- `.focus()` * on an element with no `tabindex` attribute committed yet is a silent * no-op. * * The `pendingFocusId` refocus (set by `willUpdate()` when a `data` * reassignment removes the node that currently holds real DOM focus) is * also resolved *here*, after the cascade above, rather than from * `updated()` firing a detached `void this.updateComplete.then(...)` of * its own: `updateComplete`'s getter (see the base class) calls this * method fresh on *every* access rather than caching one promise, so a * second, independent invocation started from inside `updated()` isn't * the same promise chain a caller's own `await el.updateComplete` is * following -- both ultimately settle once the same underlying update * resolves, but as separate chains their `.then()` continuations aren't * ordered against each other, so a caller's `await` can win the race and * observe focus *not yet* restored. Doing the refocus inline, before this * method's own `await` chain resolves, makes it unconditionally part of * whatever `updateComplete` promise every caller (this class's own * `focusNode()` included) is already waiting on. */ protected getUpdateComplete():Promise; /** * Emit a sibling-scoped reorder *request* for `node`, `delta` slots later * (`+1`) or earlier (`-1`) among its own parent's children. * * Deliberately constrained to one sibling list. Ctrl+ArrowDown on the last * child of a subtree is otherwise ambiguous -- the visually next row is a * top-level uncle, so "move down" could mean either "swap with the next * sibling" (there is none) or "reparent up a level". Reparenting is a * structural edit, not a reorder, and there is no keyboard affordance that * distinguishes the two, so a request that would leave the sibling list is * simply not made: no event, no announcement, focus stays put -- exactly * like a plain ArrowDown on the last visible row. */ private requestReorder; /** Announce only after the rendered sibling order proves that the host accepted the request. * Unrelated updates retain the request; a divergent sibling change rejects and clears it. */ private confirmPendingReorder;private onTreeKeyDown; /** * Expand every node in the tree, recursively. Resolves once every * descendant has actually finished expanding (not just had `expanded` set) * -- callers that immediately read `visibleNodeElements()`-derived state * (or call `collapseAll()` right after) should `await` this instead of * firing it and moving on. * * Guarded on `n.hasChildren`, matching `expand()`'s own invariant -- a leaf * node's `expanded` must never be set to `true`, since `collapse()` (and * this method's own counterpart, `collapseAll()`) refuse to act on a node * that's `!hasChildren`, which would otherwise leave the leaf permanently * stuck with a reflected `expanded` attribute nothing can clear. * * Goes through each node's own `expand()` (rather than assigning `expanded` * directly) so a not-yet-loaded `lazy` node is routed through * `beginLazyLoad()` exactly as a click would -- `expand()` is the only code * path that emits `lr-lazy-load` and waits for children, so assigning * `expanded` directly here left a lazy node visually expanded with its * content never actually requested. It also keeps `expandAll()`'s * `lr-expand`/`lr-node-toggle` emits consistent with `collapseAll()`'s own * `collapse()` calls below. A lazy node whose children have not arrived yet * has none to recurse into, so `childrenOf(n)` naturally stops the walk * there without this method ever waiting on the external response. */ expandAll():Promise; /** * Collapse every node in the tree, recursively. Goes through each node's * own `collapse()` (rather than assigning `expanded` directly) so its * `lr-node-toggle` emit reaches `onNodeActivate` -- that keeps `activeId` * re-synced to a node that's still visible after collapsing, even when the * roving-tabindex target was a nested descendant whose ancestor's * `role="group"` is about to disappear. */ collapseAll():Promise;render():TemplateResult; /** `localize()` interpolates with a bare `String(value)`, so a number handed to it renders in * ASCII digits no matter the locale -- mixing two numbering systems inside one translated * sentence. Route every user-facing number through the effective locale instead. */ private formatCount;}declare global{interface HTMLElementTagNameMap{'lr-tree':LyraTree;}}