luxen-ui 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,13 @@ export declare class TreeItem extends LuxenElement {
52
52
  set depth(value: number);
53
53
  get depth(): number;
54
54
  private _depth;
55
+ /**
56
+ * Set by `<l-tree>`: ARIA position within the tree. `level` is 1-based depth,
57
+ * `posInSet`/`setSize` describe the item's rank among its siblings. These let
58
+ * screen readers announce "level 2, 3 of 5" even when `lazy` children keep the
59
+ * full set out of the DOM.
60
+ */
61
+ setPosition(level: number, posInSet: number, setSize: number): void;
55
62
  /** Whether this item has nested tree-item children. */
56
63
  get hasChildren(): boolean;
57
64
  private _hasChildren;
@@ -66,9 +73,18 @@ export declare class TreeItem extends LuxenElement {
66
73
  connectedCallback(): void;
67
74
  disconnectedCallback(): void;
68
75
  updated(changed: PropertyValues<this>): void;
76
+ /** Leaf items omit `aria-expanded` entirely; branches reflect their state. */
77
+ private _reflectExpanded;
78
+ /**
79
+ * Write an ARIA state to BOTH ElementInternals (the semantic source, in the
80
+ * accessibility tree) and a content attribute (so `[aria-*]` CSS / query / test
81
+ * selectors keep matching — same belt-and-suspenders as the mirrored `role`).
82
+ * A `null` value clears both.
83
+ */
84
+ private _aria;
69
85
  private _setState;
70
86
  private _syncChildren;
71
- /** Toggle expand state. Emits `lazy-load` the first time a lazy item opens. */
87
+ /** Toggle expand state. Opening a `lazy` item emits `lazy-load` (via `updated`). */
72
88
  toggle(): void;
73
89
  private _onCheckboxChange;
74
90
  render(): import('lit').TemplateResult<1>;
@@ -1 +1 @@
1
- {"version":3,"file":"tree-item.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tree-item/tree-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAQ7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,QAAS,SAAQ,YAAY;IACxC,OAAgB,MAAM,4BAA4C;IAElE,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAAC,CAAmB;IAE1C,oCAAoC;IAEpC,QAAQ,CAAC,QAAQ,UAAS;IAE1B,oCAAoC;IAEpC,QAAQ,CAAC,QAAQ,UAAS;IAE1B,yEAAyE;IAEzE,QAAQ,CAAC,aAAa,UAAS;IAE/B,oCAAoC;IAEpC,QAAQ,CAAC,QAAQ,UAAS;IAE1B,8EAA8E;IAE9E,QAAQ,CAAC,IAAI,UAAS;IAEtB,+DAA+D;IAE/D,QAAQ,CAAC,OAAO,UAAS;IAEzB,sDAAsD;IACtD,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAG9B;IACD,IAAI,YAAY,IAAI,OAAO,CAE1B;IACD,OAAO,CAAC,aAAa,CAAS;IAE9B,mEAAmE;IACnE,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IACD,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,OAAO,CAAC,MAAM,CAAK;IAEnB,uDAAuD;IACvD,IAAI,WAAW,IAAI,OAAO,CAEzB;IACD,OAAO,CAAC,YAAY,CAAS;IAE7B,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,eAAsB,EAAE;;KAAK,GAAG,QAAQ,EAAE;IAO7D,4DAA4D;IAC5D,MAAM,IAAI,OAAO;IAIjB,2CAA2C;IAC3C,YAAY,IAAI,MAAM;IAUb,iBAAiB;IAQjB,oBAAoB;IAKpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAe9C,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAmBrB,+EAA+E;IAC/E,MAAM;IASN,OAAO,CAAC,iBAAiB,CAUvB;IAEO,MAAM;CAwFhB"}
1
+ {"version":3,"file":"tree-item.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tree-item/tree-item.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAQ7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,QAAS,SAAQ,YAAY;IACxC,OAAgB,MAAM,4BAA4C;IAElE,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAAC,CAAmB;IAE1C,oCAAoC;IAEpC,QAAQ,CAAC,QAAQ,UAAS;IAE1B,oCAAoC;IAEpC,QAAQ,CAAC,QAAQ,UAAS;IAE1B,yEAAyE;IAEzE,QAAQ,CAAC,aAAa,UAAS;IAE/B,oCAAoC;IAEpC,QAAQ,CAAC,QAAQ,UAAS;IAE1B,8EAA8E;IAE9E,QAAQ,CAAC,IAAI,UAAS;IAEtB,+DAA+D;IAE/D,QAAQ,CAAC,OAAO,UAAS;IAEzB,sDAAsD;IACtD,IAAI,YAAY,CAAC,KAAK,EAAE,OAAO,EAG9B;IACD,IAAI,YAAY,IAAI,OAAO,CAE1B;IACD,OAAO,CAAC,aAAa,CAAS;IAE9B,mEAAmE;IACnE,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAGtB;IACD,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,OAAO,CAAC,MAAM,CAAK;IAEnB;;;;;OAKG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IAM5D,uDAAuD;IACvD,IAAI,WAAW,IAAI,OAAO,CAEzB;IACD,OAAO,CAAC,YAAY,CAAS;IAE7B,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,eAAsB,EAAE;;KAAK,GAAG,QAAQ,EAAE;IAO7D,4DAA4D;IAC5D,MAAM,IAAI,OAAO;IAIjB,2CAA2C;IAC3C,YAAY,IAAI,MAAM;IAUb,iBAAiB;IAajB,oBAAoB;IAKpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAwB9C,8EAA8E;IAC9E,OAAO,CAAC,gBAAgB;IAIxB;;;;;OAKG;IACH,OAAO,CAAC,KAAK;IAMb,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAmBrB,oFAAoF;IACpF,MAAM;IAMN,OAAO,CAAC,iBAAiB,CAUvB;IAEO,MAAM;CAyFhB"}
@@ -1,4 +1,4 @@
1
- import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{a as n}from"../../chunks/lit-html.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";import s from"../../shared/styles/checkbox-appearance.styles.js";var c=t(`:host{color:var(--l-color-text-primary,CanvasText);outline:none;font-size:.875rem;line-height:1.5;display:block}:host([disabled]){opacity:.4}:host([disabled]) .item{cursor:not-allowed}.item{align-items:center;gap:var(--item-gap);min-block-size:var(--row-height);padding-inline:var(--row-padding-inline);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.375rem;padding-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline));transition:background-color .12s,color .12s;display:flex;position:relative}.item:focus-visible,:host(:focus-visible) .item{outline:2px solid var(--l-focus-ring);outline-offset:1px}@media (hover:hover){:host(:not([disabled])) .item:hover{background-color:var(--l-color-bg-state-hover)}}:host([selected]:not([disabled])) .item{background-color:var(--l-color-bg-state-selected)}.expand{inline-size:var(--chevron-size);block-size:var(--chevron-size);color:var(--l-color-text-secondary,CanvasText);cursor:pointer;border-radius:3px;flex:none;place-items:center;display:grid}:host(:not(:state(has-children)):not([lazy])) .expand>slot>svg{display:none}.expand svg{width:100%;height:100%;display:block}:host(:not(:state(checkbox))) .checkbox{display:none}.label{text-overflow:ellipsis;white-space:nowrap;flex:1;align-items:center;gap:.375rem;min-inline-size:0;display:flex;overflow:hidden}.label ::slotted(*){min-inline-size:0}.branch{position:relative}.content{padding-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline) + var(--chevron-size) + var(--item-gap));padding-inline-end:var(--row-padding-inline);display:block}:host(:state(has-children):not([expanded])) .content,.children{display:none}:host([expanded]) .children{display:block}.branch:before{content:"";border-inline-start:var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);pointer-events:none;inline-size:0;position:absolute;inset-block:0;inset-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline) + (var(--chevron-size) / 2) - (var(--indent-guide-width) / 2))}:host(:not([expanded])) .branch:before,:host(:not(:state(has-children))) .branch:before{display:none}.spinner{border:2px solid var(--l-color-border,currentColor);border-block-start-color:#0000;border-radius:50%;block-size:.875rem;inline-size:.875rem;animation:.7s linear infinite spin}@media (prefers-reduced-motion:reduce){.spinner{animation:none}}@keyframes spin{to{transform:rotate(360deg)}}`),l=class extends r{constructor(...e){super(...e),this._internals=this.attachInternals(),this.#e=!1,this.#t=!1,this.#n=!1,this.#r=!1,this.#i=!1,this.#a=!1,this._showCheckbox=!1,this._depth=0,this._hasChildren=!1,this._onCheckboxChange=e=>{e.stopPropagation();let t=e.target;this.dispatchEvent(new CustomEvent(`l-tree-item-toggle`,{bubbles:!0,composed:!0,detail:{item:this,checked:t.checked}}))}}static{this.styles=[o,s,c]}#e;get expanded(){return this.#e}set expanded(e){this.#e=e}#t;get selected(){return this.#t}set selected(e){this.#t=e}#n;get indeterminate(){return this.#n}set indeterminate(e){this.#n=e}#r;get disabled(){return this.#r}set disabled(e){this.#r=e}#i;get lazy(){return this.#i}set lazy(e){this.#i=e}#a;get loading(){return this.#a}set loading(e){this.#a=e}set showCheckbox(e){this._showCheckbox=e,this._setState(`checkbox`,e)}get showCheckbox(){return this._showCheckbox}set depth(e){this._depth=e,this.style.setProperty(`--_depth`,String(e))}get depth(){return this._depth}get hasChildren(){return this._hasChildren}getChildrenItems({includeDisabled:t=!0}={}){let n=e(`tree-item`).toUpperCase();return Array.from(this.children).filter(e=>e.tagName===n&&(t||!e.disabled))}isLeaf(){return!this.lazy&&this.getChildrenItems().length===0}getTextLabel(){let e=this.shadowRoot?.querySelector(`slot:not([name])`);return e?e.assignedNodes({flatten:!0}).map(e=>e.textContent??``).join(``).trim():(this.textContent??``).trim()}connectedCallback(){super.connectedCallback(),this._internals.role=`treeitem`,this._childObserver=new MutationObserver(()=>this._syncChildren()),this._childObserver.observe(this,{childList:!0}),this._syncChildren()}disconnectedCallback(){super.disconnectedCallback(),this._childObserver?.disconnect()}updated(e){e.has(`expanded`)&&(this._internals.ariaExpanded=this.isLeaf()?null:String(this.expanded),this.emit(this.expanded?`expand`:`collapse`)),e.has(`selected`)&&(this._internals.ariaSelected=String(this.selected)),e.has(`disabled`)&&(this._internals.ariaDisabled=this.disabled?`true`:null)}_setState(e,t){this._internals.states&&(t?this._internals.states.add(e):this._internals.states.delete(e))}_syncChildren(){let t=e(`tree-item`).toUpperCase(),n=0;for(let e of Array.from(this.children))e.tagName===t&&(n++,e.slot!==`children`&&(e.slot=`children`));this._hasChildren=n>0,this._setState(`has-children`,this._hasChildren),!this._hasChildren&&!this.lazy&&this.expanded&&(this.expanded=!1),this._internals.ariaExpanded=this.isLeaf()?null:String(this.expanded)}toggle(){if(this.isLeaf()&&!this.lazy)return;let e=!this.expanded;e&&this.lazy&&this.emit(`lazy-load`),this.expanded=e}render(){return n`
1
+ import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.js";import{a as n}from"../../chunks/lit-html.js";import{LuxenElement as r}from"../../shared/luxen-element.js";import{i,t as a}from"../../chunks/decorate.js";import o from"../../shared/styles/host.styles.js";import s from"../../shared/styles/checkbox-appearance.styles.js";var c=t(`:host{color:var(--l-color-text-primary,CanvasText);outline:none;font-size:.875rem;line-height:1.5;display:block}:host([disabled]){opacity:.4}:host([disabled]) .item{cursor:not-allowed}.item{align-items:center;gap:var(--item-gap);min-block-size:var(--row-height);padding-inline:var(--row-padding-inline);cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.375rem;padding-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline));transition:background-color .12s,color .12s;display:flex;position:relative}.item:focus-visible,:host(:focus-visible) .item{outline:2px solid var(--l-focus-ring);outline-offset:1px}@media (hover:hover){:host(:not([disabled])) .item:hover{background-color:var(--l-color-bg-state-hover)}}:host([selected]:not([disabled])) .item{background-color:var(--l-color-bg-state-selected)}.expand{inline-size:var(--chevron-size);block-size:var(--chevron-size);color:var(--l-color-text-secondary,CanvasText);cursor:pointer;border-radius:3px;flex:none;place-items:center;display:grid}:host(:not(:state(has-children)):not([lazy])) .expand>slot>svg{display:none}.expand svg{width:100%;height:100%;display:block}:host(:not(:state(checkbox))) .checkbox{display:none}.label{text-overflow:ellipsis;white-space:nowrap;flex:1;align-items:center;gap:.375rem;min-inline-size:0;display:flex;overflow:hidden}.label ::slotted(*){min-inline-size:0}.branch{position:relative}.content{padding-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline) + var(--chevron-size) + var(--item-gap));padding-inline-end:var(--row-padding-inline);display:block}:host(:state(has-children):not([expanded])) .content,.children{display:none}:host([expanded]) .children{display:block}.branch:before{content:"";border-inline-start:var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);pointer-events:none;inline-size:0;position:absolute;inset-block:0;inset-inline-start:calc(var(--indent-size) * var(--_depth,0) + var(--row-padding-inline) + (var(--chevron-size) / 2) - (var(--indent-guide-width) / 2))}:host(:not([expanded])) .branch:before,:host(:not(:state(has-children))) .branch:before{display:none}.spinner{border:2px solid var(--l-color-border,currentColor);border-block-start-color:#0000;border-radius:50%;block-size:.875rem;inline-size:.875rem;animation:.7s linear infinite spin}@media (prefers-reduced-motion:reduce){.spinner{animation:none}}@keyframes spin{to{transform:rotate(360deg)}}`),l=class extends r{constructor(...e){super(...e),this._internals=this.attachInternals(),this.#e=!1,this.#t=!1,this.#n=!1,this.#r=!1,this.#i=!1,this.#a=!1,this._showCheckbox=!1,this._depth=0,this._hasChildren=!1,this._onCheckboxChange=e=>{e.stopPropagation();let t=e.target;this.dispatchEvent(new CustomEvent(`l-tree-item-toggle`,{bubbles:!0,composed:!0,detail:{item:this,checked:t.checked}}))}}static{this.styles=[o,s,c]}#e;get expanded(){return this.#e}set expanded(e){this.#e=e}#t;get selected(){return this.#t}set selected(e){this.#t=e}#n;get indeterminate(){return this.#n}set indeterminate(e){this.#n=e}#r;get disabled(){return this.#r}set disabled(e){this.#r=e}#i;get lazy(){return this.#i}set lazy(e){this.#i=e}#a;get loading(){return this.#a}set loading(e){this.#a=e}set showCheckbox(e){this._showCheckbox=e,this._setState(`checkbox`,e)}get showCheckbox(){return this._showCheckbox}set depth(e){this._depth=e,this.style.setProperty(`--_depth`,String(e))}get depth(){return this._depth}setPosition(e,t,n){this._aria(`ariaLevel`,`aria-level`,String(e)),this._aria(`ariaPosInSet`,`aria-posinset`,String(t)),this._aria(`ariaSetSize`,`aria-setsize`,String(n))}get hasChildren(){return this._hasChildren}getChildrenItems({includeDisabled:t=!0}={}){let n=e(`tree-item`).toUpperCase();return Array.from(this.children).filter(e=>e.tagName===n&&(t||!e.disabled))}isLeaf(){return!this.lazy&&this.getChildrenItems().length===0}getTextLabel(){let e=this.shadowRoot?.querySelector(`slot:not([name])`);return e?e.assignedNodes({flatten:!0}).map(e=>e.textContent??``).join(``).trim():(this.textContent??``).trim()}connectedCallback(){super.connectedCallback(),this._internals.role=`treeitem`,this.hasAttribute(`role`)||this.setAttribute(`role`,`treeitem`),this._childObserver=new MutationObserver(()=>this._syncChildren()),this._childObserver.observe(this,{childList:!0}),this._syncChildren()}disconnectedCallback(){super.disconnectedCallback(),this._childObserver?.disconnect()}updated(e){e.has(`expanded`)&&(this._reflectExpanded(),this.expanded&&this.lazy&&this.emit(`lazy-load`),this.emit(this.expanded?`expand`:`collapse`)),e.has(`selected`)&&this._aria(`ariaSelected`,`aria-selected`,String(this.selected)),e.has(`disabled`)&&this._aria(`ariaDisabled`,`aria-disabled`,this.disabled?`true`:null),e.has(`loading`)&&this._aria(`ariaBusy`,`aria-busy`,this.loading?`true`:null)}_reflectExpanded(){this._aria(`ariaExpanded`,`aria-expanded`,this.isLeaf()?null:String(this.expanded))}_aria(e,t,n){this._internals[e]=n,n===null?this.removeAttribute(t):this.setAttribute(t,n)}_setState(e,t){this._internals.states&&(t?this._internals.states.add(e):this._internals.states.delete(e))}_syncChildren(){let t=e(`tree-item`).toUpperCase(),n=0;for(let e of Array.from(this.children))e.tagName===t&&(n++,e.slot!==`children`&&(e.slot=`children`));this._hasChildren=n>0,this._setState(`has-children`,this._hasChildren),!this._hasChildren&&!this.lazy&&this.expanded&&(this.expanded=!1),this._reflectExpanded()}toggle(){this.isLeaf()&&!this.lazy||(this.expanded=!this.expanded)}render(){return n`
2
2
  <div
3
3
  class="item"
4
4
  part="base"
@@ -45,6 +45,7 @@ import{tagName as e}from"../../registry.js";import{i as t}from"../../chunks/lit.
45
45
  part="checkbox"
46
46
  type="checkbox"
47
47
  tabindex="-1"
48
+ aria-hidden="true"
48
49
  .checked=${this.selected}
49
50
  .indeterminate=${this.indeterminate}
50
51
  ?disabled=${this.disabled}
@@ -1 +1 @@
1
- {"version":3,"file":"tree-item.js","names":[],"sources":["../../../src/html/elements/tree-item/tree-item.css?inline","../../../src/html/elements/tree-item/tree-item.ts"],"sourcesContent":[":host {\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-size: 0.875rem;\n line-height: 1.5;\n /* The host is the roving-tabindex focus target, but the visible ring is drawn\n on the inner `.item` row. Suppress the host's UA outline so it doesn't paint\n a second ring around the whole subtree (row + children). */\n outline: none;\n}\n\n:host([disabled]) {\n opacity: 0.4;\n}\n\n:host([disabled]) .item {\n cursor: not-allowed;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--item-gap);\n min-block-size: var(--row-height);\n padding-inline: var(--row-padding-inline);\n padding-inline-start: calc(var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline));\n border-radius: 0.375rem;\n cursor: pointer;\n user-select: none;\n transition:\n background-color 120ms ease,\n color 120ms ease;\n position: relative;\n}\n\n.item:focus-visible,\n:host(:focus-visible) .item {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 1px;\n}\n\n@media (hover: hover) {\n :host(:not([disabled])) .item:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n:host([selected]:not([disabled])) .item {\n background-color: var(--l-color-bg-state-selected);\n}\n\n.expand {\n inline-size: var(--chevron-size);\n block-size: var(--chevron-size);\n display: grid;\n place-items: center;\n flex: none;\n color: var(--l-color-text-secondary, CanvasText);\n border-radius: 3px;\n cursor: pointer;\n}\n\n/* Hide the DEFAULT fallback chevron SVG on a leaf — slotted content\n (user-provided icon, avatar, etc.) remains visible because it lives outside\n the slot in the DOM tree. The `.expand` span keeps its fixed --chevron-size\n so leaf rows stay aligned with branches. */\n:host(:not(:state(has-children)):not([lazy])) .expand > slot > svg {\n display: none;\n}\n\n.expand svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* The checkbox appearance comes from the shared `.l-checkbox` skin imported\n above; this rule only governs visibility. */\n:host(:not(:state(checkbox))) .checkbox {\n display: none;\n}\n\n.label {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.label ::slotted(*) {\n min-inline-size: 0;\n}\n\n/* Wrapper around the content slot + children — anchor for the indent guide. */\n.branch {\n position: relative;\n}\n\n/* Content slot — block area between the row and the children.\n Aligned under the label text: same left offset as the row's label\n (depth indent + row padding + chevron + gap). Visible for leaves (no\n children) and for expanded branches; hidden when a branch is collapsed\n (mirrors the children visibility). */\n.content {\n display: block;\n padding-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + var(--chevron-size) +\n var(--item-gap)\n );\n padding-inline-end: var(--row-padding-inline);\n}\n\n:host(:state(has-children):not([expanded])) .content {\n display: none;\n}\n\n.children {\n display: none;\n}\n\n:host([expanded]) .children {\n display: block;\n}\n\n/* Vertical indent guide — spans the content + children block, starting\n right below the row so it never overlaps the chevron/avatar.\n The guide's visual centre sits exactly on the parent chevron's centre. */\n.branch::before {\n content: '';\n position: absolute;\n inset-block: 0;\n inset-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + (var(--chevron-size) / 2) -\n (var(--indent-guide-width) / 2)\n );\n inline-size: 0;\n border-inline-start: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);\n pointer-events: none;\n}\n\n/* Only render the guide for open branches that have children. */\n:host(:not([expanded])) .branch::before,\n:host(:not(:state(has-children))) .branch::before {\n display: none;\n}\n\n.spinner {\n inline-size: 0.875rem;\n block-size: 0.875rem;\n border-radius: 50%;\n border: 2px solid var(--l-color-border, currentColor);\n border-block-start-color: transparent;\n animation: spin 700ms linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { tagName } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport checkboxAppearance from '../../shared/styles/checkbox-appearance.styles.js';\nimport rawStyles from './tree-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A node inside `<l-tree>`. Nested `<l-tree-item>` children become sub-nodes.\n *\n * @slot - Label content (kept to a single row).\n * @slot prefix - Leading content before the label (e.g. icon).\n * @slot suffix - Trailing content.\n * @slot expand-icon - Icon shown when the item is collapsed.\n * @slot collapse-icon - Icon shown when the item is expanded.\n * @slot content - Block content that belongs to the item but not to its header row (e.g. comment body, action bar). Hidden when a branch is collapsed.\n *\n * @csspart base - The item row.\n * @csspart expand-button - The chevron toggle area.\n * @csspart checkbox - The native checkbox input.\n * @csspart label - The label container.\n * @csspart branch - Wrapper around the content and children slots; carries the indent guide.\n * @csspart content - The content slot wrapper (block area between the row and the children).\n * @csspart children - The nested items container.\n *\n * @cssproperty [--_depth] - Internal depth index driving indentation. Set by `<l-tree>`.\n *\n * Layout tokens (`--chevron-size`, `--row-height`, `--row-padding-inline`, `--item-gap`) live on `<l-tree>` and cascade down — see its CSS custom properties.\n *\n * @event expand - Fired when the item is expanded.\n * @event collapse - Fired when the item is collapsed.\n * @event lazy-load - Fired when a lazy item is expanded for the first time. Consumers should append children and set `lazy=false`.\n *\n * @customElement l-tree-item\n */\nexport class TreeItem extends LuxenElement {\n static override styles = [hostStyles, checkboxAppearance, styles];\n\n private _internals = this.attachInternals();\n private _childObserver?: MutationObserver;\n\n /** Whether the item is expanded. */\n @property({ type: Boolean, reflect: true })\n accessor expanded = false;\n\n /** Whether the item is selected. */\n @property({ type: Boolean, reflect: true })\n accessor selected = false;\n\n /** Whether the checkbox is indeterminate (some descendants selected). */\n @property({ type: Boolean, reflect: true })\n accessor indeterminate = false;\n\n /** Whether the item is disabled. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** Marks this item as having children that will be loaded on first expand. */\n @property({ type: Boolean, reflect: true })\n accessor lazy = false;\n\n /** Whether the item is currently loading (shows a spinner). */\n @property({ type: Boolean, reflect: true })\n accessor loading = false;\n\n /** Set by `<l-tree>`: whether a checkbox is shown. */\n set showCheckbox(value: boolean) {\n this._showCheckbox = value;\n this._setState('checkbox', value);\n }\n get showCheckbox(): boolean {\n return this._showCheckbox;\n }\n private _showCheckbox = false;\n\n /** Set by `<l-tree>`: depth of the item in the tree (0 = root). */\n set depth(value: number) {\n this._depth = value;\n this.style.setProperty('--_depth', String(value));\n }\n get depth(): number {\n return this._depth;\n }\n private _depth = 0;\n\n /** Whether this item has nested tree-item children. */\n get hasChildren(): boolean {\n return this._hasChildren;\n }\n private _hasChildren = false;\n\n /** Returns the child `<l-tree-item>` elements directly under this one. */\n getChildrenItems({ includeDisabled = true } = {}): TreeItem[] {\n const childTag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as TreeItem[]).filter(\n (el) => el.tagName === childTag && (includeDisabled || !el.disabled),\n );\n }\n\n /** Returns true if this item has no expandable children. */\n isLeaf(): boolean {\n return !this.lazy && this.getChildrenItems().length === 0;\n }\n\n /** Returns the text label of this item. */\n getTextLabel(): string {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!slot) return (this.textContent ?? '').trim();\n return slot\n .assignedNodes({ flatten: true })\n .map((n) => n.textContent ?? '')\n .join('')\n .trim();\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._internals.role = 'treeitem';\n this._childObserver = new MutationObserver(() => this._syncChildren());\n this._childObserver.observe(this, { childList: true });\n this._syncChildren();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._childObserver?.disconnect();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('expanded')) {\n this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);\n this.emit(this.expanded ? 'expand' : 'collapse');\n }\n\n if (changed.has('selected')) {\n this._internals.ariaSelected = String(this.selected);\n }\n\n if (changed.has('disabled')) {\n this._internals.ariaDisabled = this.disabled ? 'true' : null;\n }\n }\n\n private _setState(name: string, on: boolean) {\n if (!this._internals.states) return;\n if (on) this._internals.states.add(name);\n else this._internals.states.delete(name);\n }\n\n private _syncChildren() {\n // Auto-slot nested tree-items into the `children` slot so they render in the group container,\n // while label text/elements remain in the default slot.\n const childTag = tagName('tree-item').toUpperCase();\n let count = 0;\n for (const child of Array.from(this.children) as HTMLElement[]) {\n if (child.tagName === childTag) {\n count++;\n if (child.slot !== 'children') child.slot = 'children';\n }\n }\n this._hasChildren = count > 0;\n this._setState('has-children', this._hasChildren);\n if (!this._hasChildren && !this.lazy && this.expanded) {\n this.expanded = false;\n }\n this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);\n }\n\n /** Toggle expand state. Emits `lazy-load` the first time a lazy item opens. */\n toggle() {\n if (this.isLeaf() && !this.lazy) return;\n const next = !this.expanded;\n if (next && this.lazy) {\n this.emit('lazy-load');\n }\n this.expanded = next;\n }\n\n private _onCheckboxChange = (event: Event) => {\n event.stopPropagation();\n const input = event.target as HTMLInputElement;\n this.dispatchEvent(\n new CustomEvent('l-tree-item-toggle', {\n bubbles: true,\n composed: true,\n detail: { item: this, checked: input.checked },\n }),\n );\n };\n\n override render() {\n return html`\n <div\n class=\"item\"\n part=\"base\"\n >\n <span\n class=\"expand\"\n part=\"expand-button\"\n aria-hidden=\"true\"\n >\n ${this.loading\n ? html`<span\n class=\"spinner\"\n role=\"status\"\n ></span>`\n : this.expanded || this.isLeaf()\n ? html`<slot name=\"collapse-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 6l4.5 5 4.5-5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`\n : html`<slot name=\"expand-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M6 3.5l5 4.5-5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`}\n </span>\n\n <input\n class=\"checkbox l-checkbox\"\n part=\"checkbox\"\n type=\"checkbox\"\n tabindex=\"-1\"\n .checked=${this.selected}\n .indeterminate=${this.indeterminate}\n ?disabled=${this.disabled}\n @click=${(e: Event) => e.stopPropagation()}\n @change=${this._onCheckboxChange}\n />\n\n <slot name=\"prefix\"></slot>\n <span\n class=\"label\"\n part=\"label\"\n ><slot></slot\n ></span>\n <slot name=\"suffix\"></slot>\n </div>\n\n <div\n class=\"branch\"\n part=\"branch\"\n >\n <div\n class=\"content\"\n part=\"content\"\n >\n <slot name=\"content\"></slot>\n </div>\n <div\n class=\"children\"\n part=\"children\"\n role=\"group\"\n >\n <slot name=\"children\"></slot>\n </div>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tree-item.js","names":[],"sources":["../../../src/html/elements/tree-item/tree-item.css?inline","../../../src/html/elements/tree-item/tree-item.ts"],"sourcesContent":[":host {\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-size: 0.875rem;\n line-height: 1.5;\n /* The host is the roving-tabindex focus target, but the visible ring is drawn\n on the inner `.item` row. Suppress the host's UA outline so it doesn't paint\n a second ring around the whole subtree (row + children). */\n outline: none;\n}\n\n:host([disabled]) {\n opacity: 0.4;\n}\n\n:host([disabled]) .item {\n cursor: not-allowed;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: var(--item-gap);\n min-block-size: var(--row-height);\n padding-inline: var(--row-padding-inline);\n padding-inline-start: calc(var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline));\n border-radius: 0.375rem;\n cursor: pointer;\n user-select: none;\n transition:\n background-color 120ms ease,\n color 120ms ease;\n position: relative;\n}\n\n.item:focus-visible,\n:host(:focus-visible) .item {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 1px;\n}\n\n@media (hover: hover) {\n :host(:not([disabled])) .item:hover {\n background-color: var(--l-color-bg-state-hover);\n }\n}\n\n:host([selected]:not([disabled])) .item {\n background-color: var(--l-color-bg-state-selected);\n}\n\n.expand {\n inline-size: var(--chevron-size);\n block-size: var(--chevron-size);\n display: grid;\n place-items: center;\n flex: none;\n color: var(--l-color-text-secondary, CanvasText);\n border-radius: 3px;\n cursor: pointer;\n}\n\n/* Hide the DEFAULT fallback chevron SVG on a leaf — slotted content\n (user-provided icon, avatar, etc.) remains visible because it lives outside\n the slot in the DOM tree. The `.expand` span keeps its fixed --chevron-size\n so leaf rows stay aligned with branches. */\n:host(:not(:state(has-children)):not([lazy])) .expand > slot > svg {\n display: none;\n}\n\n.expand svg {\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* The checkbox appearance comes from the shared `.l-checkbox` skin imported\n above; this rule only governs visibility. */\n:host(:not(:state(checkbox))) .checkbox {\n display: none;\n}\n\n.label {\n flex: 1;\n min-inline-size: 0;\n display: flex;\n align-items: center;\n gap: 0.375rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.label ::slotted(*) {\n min-inline-size: 0;\n}\n\n/* Wrapper around the content slot + children — anchor for the indent guide. */\n.branch {\n position: relative;\n}\n\n/* Content slot — block area between the row and the children.\n Aligned under the label text: same left offset as the row's label\n (depth indent + row padding + chevron + gap). Visible for leaves (no\n children) and for expanded branches; hidden when a branch is collapsed\n (mirrors the children visibility). */\n.content {\n display: block;\n padding-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + var(--chevron-size) +\n var(--item-gap)\n );\n padding-inline-end: var(--row-padding-inline);\n}\n\n:host(:state(has-children):not([expanded])) .content {\n display: none;\n}\n\n.children {\n display: none;\n}\n\n:host([expanded]) .children {\n display: block;\n}\n\n/* Vertical indent guide — spans the content + children block, starting\n right below the row so it never overlaps the chevron/avatar.\n The guide's visual centre sits exactly on the parent chevron's centre. */\n.branch::before {\n content: '';\n position: absolute;\n inset-block: 0;\n inset-inline-start: calc(\n var(--indent-size) * var(--_depth, 0) + var(--row-padding-inline) + (var(--chevron-size) / 2) -\n (var(--indent-guide-width) / 2)\n );\n inline-size: 0;\n border-inline-start: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color);\n pointer-events: none;\n}\n\n/* Only render the guide for open branches that have children. */\n:host(:not([expanded])) .branch::before,\n:host(:not(:state(has-children))) .branch::before {\n display: none;\n}\n\n.spinner {\n inline-size: 0.875rem;\n block-size: 0.875rem;\n border-radius: 50%;\n border: 2px solid var(--l-color-border, currentColor);\n border-block-start-color: transparent;\n animation: spin 700ms linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation: none;\n }\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n","import { html, unsafeCSS, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { tagName } from '../../registry.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport checkboxAppearance from '../../shared/styles/checkbox-appearance.styles.js';\nimport rawStyles from './tree-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A node inside `<l-tree>`. Nested `<l-tree-item>` children become sub-nodes.\n *\n * @slot - Label content (kept to a single row).\n * @slot prefix - Leading content before the label (e.g. icon).\n * @slot suffix - Trailing content.\n * @slot expand-icon - Icon shown when the item is collapsed.\n * @slot collapse-icon - Icon shown when the item is expanded.\n * @slot content - Block content that belongs to the item but not to its header row (e.g. comment body, action bar). Hidden when a branch is collapsed.\n *\n * @csspart base - The item row.\n * @csspart expand-button - The chevron toggle area.\n * @csspart checkbox - The native checkbox input.\n * @csspart label - The label container.\n * @csspart branch - Wrapper around the content and children slots; carries the indent guide.\n * @csspart content - The content slot wrapper (block area between the row and the children).\n * @csspart children - The nested items container.\n *\n * @cssproperty [--_depth] - Internal depth index driving indentation. Set by `<l-tree>`.\n *\n * Layout tokens (`--chevron-size`, `--row-height`, `--row-padding-inline`, `--item-gap`) live on `<l-tree>` and cascade down — see its CSS custom properties.\n *\n * @event expand - Fired when the item is expanded.\n * @event collapse - Fired when the item is collapsed.\n * @event lazy-load - Fired when a lazy item is expanded for the first time. Consumers should append children and set `lazy=false`.\n *\n * @customElement l-tree-item\n */\nexport class TreeItem extends LuxenElement {\n static override styles = [hostStyles, checkboxAppearance, styles];\n\n private _internals = this.attachInternals();\n private _childObserver?: MutationObserver;\n\n /** Whether the item is expanded. */\n @property({ type: Boolean, reflect: true })\n accessor expanded = false;\n\n /** Whether the item is selected. */\n @property({ type: Boolean, reflect: true })\n accessor selected = false;\n\n /** Whether the checkbox is indeterminate (some descendants selected). */\n @property({ type: Boolean, reflect: true })\n accessor indeterminate = false;\n\n /** Whether the item is disabled. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** Marks this item as having children that will be loaded on first expand. */\n @property({ type: Boolean, reflect: true })\n accessor lazy = false;\n\n /** Whether the item is currently loading (shows a spinner). */\n @property({ type: Boolean, reflect: true })\n accessor loading = false;\n\n /** Set by `<l-tree>`: whether a checkbox is shown. */\n set showCheckbox(value: boolean) {\n this._showCheckbox = value;\n this._setState('checkbox', value);\n }\n get showCheckbox(): boolean {\n return this._showCheckbox;\n }\n private _showCheckbox = false;\n\n /** Set by `<l-tree>`: depth of the item in the tree (0 = root). */\n set depth(value: number) {\n this._depth = value;\n this.style.setProperty('--_depth', String(value));\n }\n get depth(): number {\n return this._depth;\n }\n private _depth = 0;\n\n /**\n * Set by `<l-tree>`: ARIA position within the tree. `level` is 1-based depth,\n * `posInSet`/`setSize` describe the item's rank among its siblings. These let\n * screen readers announce \"level 2, 3 of 5\" even when `lazy` children keep the\n * full set out of the DOM.\n */\n setPosition(level: number, posInSet: number, setSize: number) {\n this._aria('ariaLevel', 'aria-level', String(level));\n this._aria('ariaPosInSet', 'aria-posinset', String(posInSet));\n this._aria('ariaSetSize', 'aria-setsize', String(setSize));\n }\n\n /** Whether this item has nested tree-item children. */\n get hasChildren(): boolean {\n return this._hasChildren;\n }\n private _hasChildren = false;\n\n /** Returns the child `<l-tree-item>` elements directly under this one. */\n getChildrenItems({ includeDisabled = true } = {}): TreeItem[] {\n const childTag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as TreeItem[]).filter(\n (el) => el.tagName === childTag && (includeDisabled || !el.disabled),\n );\n }\n\n /** Returns true if this item has no expandable children. */\n isLeaf(): boolean {\n return !this.lazy && this.getChildrenItems().length === 0;\n }\n\n /** Returns the text label of this item. */\n getTextLabel(): string {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>('slot:not([name])');\n if (!slot) return (this.textContent ?? '').trim();\n return slot\n .assignedNodes({ flatten: true })\n .map((n) => n.textContent ?? '')\n .join('')\n .trim();\n }\n\n override connectedCallback() {\n super.connectedCallback();\n this._internals.role = 'treeitem';\n // Mirror the role to a DOM attribute too, so `[role=\"treeitem\"]` selectors\n // (CSS, querySelector, Cypress/Playwright CSS) keep matching — the\n // ElementInternals role alone is not attribute-selectable. ARIA states are\n // mirrored the same way in `_aria()` (aria-expanded/selected/disabled/…).\n if (!this.hasAttribute('role')) this.setAttribute('role', 'treeitem');\n this._childObserver = new MutationObserver(() => this._syncChildren());\n this._childObserver.observe(this, { childList: true });\n this._syncChildren();\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._childObserver?.disconnect();\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('expanded')) {\n this._reflectExpanded();\n // Emit lazy-load for ANY transition to expanded (keyboard, chevron,\n // `expandAll()`…), not just `toggle()` — otherwise keyboard users expand a\n // lazy branch with no children and no fetch. Fires while still `lazy`; the\n // consumer appends children and clears `lazy`, so it won't re-fire.\n if (this.expanded && this.lazy) this.emit('lazy-load');\n this.emit(this.expanded ? 'expand' : 'collapse');\n }\n\n if (changed.has('selected')) {\n this._aria('ariaSelected', 'aria-selected', String(this.selected));\n }\n\n if (changed.has('disabled')) {\n this._aria('ariaDisabled', 'aria-disabled', this.disabled ? 'true' : null);\n }\n\n if (changed.has('loading')) {\n this._aria('ariaBusy', 'aria-busy', this.loading ? 'true' : null);\n }\n }\n\n /** Leaf items omit `aria-expanded` entirely; branches reflect their state. */\n private _reflectExpanded() {\n this._aria('ariaExpanded', 'aria-expanded', this.isLeaf() ? null : String(this.expanded));\n }\n\n /**\n * Write an ARIA state to BOTH ElementInternals (the semantic source, in the\n * accessibility tree) and a content attribute (so `[aria-*]` CSS / query / test\n * selectors keep matching — same belt-and-suspenders as the mirrored `role`).\n * A `null` value clears both.\n */\n private _aria(key: keyof ElementInternals, attr: string, value: string | null) {\n (this._internals as unknown as Record<string, string | null>)[key] = value;\n if (value === null) this.removeAttribute(attr);\n else this.setAttribute(attr, value);\n }\n\n private _setState(name: string, on: boolean) {\n if (!this._internals.states) return;\n if (on) this._internals.states.add(name);\n else this._internals.states.delete(name);\n }\n\n private _syncChildren() {\n // Auto-slot nested tree-items into the `children` slot so they render in the group container,\n // while label text/elements remain in the default slot.\n const childTag = tagName('tree-item').toUpperCase();\n let count = 0;\n for (const child of Array.from(this.children) as HTMLElement[]) {\n if (child.tagName === childTag) {\n count++;\n if (child.slot !== 'children') child.slot = 'children';\n }\n }\n this._hasChildren = count > 0;\n this._setState('has-children', this._hasChildren);\n if (!this._hasChildren && !this.lazy && this.expanded) {\n this.expanded = false;\n }\n this._reflectExpanded();\n }\n\n /** Toggle expand state. Opening a `lazy` item emits `lazy-load` (via `updated`). */\n toggle() {\n if (this.isLeaf() && !this.lazy) return;\n // lazy-load is emitted centrally from `updated()` on the expand transition.\n this.expanded = !this.expanded;\n }\n\n private _onCheckboxChange = (event: Event) => {\n event.stopPropagation();\n const input = event.target as HTMLInputElement;\n this.dispatchEvent(\n new CustomEvent('l-tree-item-toggle', {\n bubbles: true,\n composed: true,\n detail: { item: this, checked: input.checked },\n }),\n );\n };\n\n override render() {\n return html`\n <div\n class=\"item\"\n part=\"base\"\n >\n <span\n class=\"expand\"\n part=\"expand-button\"\n aria-hidden=\"true\"\n >\n ${this.loading\n ? html`<span\n class=\"spinner\"\n role=\"status\"\n ></span>`\n : this.expanded || this.isLeaf()\n ? html`<slot name=\"collapse-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 6l4.5 5 4.5-5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`\n : html`<slot name=\"expand-icon\">\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M6 3.5l5 4.5-5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </slot>`}\n </span>\n\n <input\n class=\"checkbox l-checkbox\"\n part=\"checkbox\"\n type=\"checkbox\"\n tabindex=\"-1\"\n aria-hidden=\"true\"\n .checked=${this.selected}\n .indeterminate=${this.indeterminate}\n ?disabled=${this.disabled}\n @click=${(e: Event) => e.stopPropagation()}\n @change=${this._onCheckboxChange}\n />\n\n <slot name=\"prefix\"></slot>\n <span\n class=\"label\"\n part=\"label\"\n ><slot></slot\n ></span>\n <slot name=\"suffix\"></slot>\n </div>\n\n <div\n class=\"branch\"\n part=\"branch\"\n >\n <div\n class=\"content\"\n part=\"content\"\n >\n <slot name=\"content\"></slot>\n </div>\n <div\n class=\"children\"\n part=\"children\"\n role=\"group\"\n >\n <slot name=\"children\"></slot>\n </div>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
package/cdn/standalone.js CHANGED
@@ -5081,7 +5081,7 @@ __decorate([n$1({ attribute: "min-width" })], Dropdown.prototype, "minWidth", nu
5081
5081
  define("dropdown", Dropdown);
5082
5082
  //#endregion
5083
5083
  //#region src/html/elements/dropdown-item/dropdown-item.ts
5084
- var styles$10 = r$6(":host {\n display: block;\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0.375rem 0.5rem;\n cursor: pointer;\n outline: none;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--l-color-text-primary, CanvasText);\n white-space: nowrap;\n}\n\n.item:focus-visible {\n background: var(--l-color-bg-state-hover);\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\n}\n\n:host(:not([checked])) .check svg {\n visibility: hidden;\n}\n\n::slotted([slot='prefix']),\n::slotted([slot='suffix']) {\n display: flex;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1;\n}\n");
5084
+ var styles$10 = r$6(":host {\n display: block;\n}\n\n:host([disabled]) {\n pointer-events: none;\n opacity: 0.5;\n}\n\n.item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0.375rem 0.5rem;\n cursor: pointer;\n outline: none;\n border-radius: 4px;\n font-size: 0.875rem;\n line-height: 1.5;\n color: var(--l-color-text-primary, CanvasText);\n white-space: nowrap;\n text-align: start;\n}\n\n.item:focus-visible {\n background: var(--l-color-bg-state-hover);\n}\n\n@media (hover: hover) {\n .item:hover {\n background: var(--l-color-bg-state-hover);\n }\n}\n\n.check {\n display: flex;\n width: 16px;\n flex-shrink: 0;\n}\n\n:host(:not([checked])) .check svg {\n visibility: hidden;\n}\n\n::slotted([slot='prefix']),\n::slotted([slot='suffix']) {\n display: flex;\n flex-shrink: 0;\n}\n\n.label {\n flex: 1;\n}\n");
5085
5085
  /**
5086
5086
  * A menu item for use inside `<l-dropdown>`.
5087
5087
  *
@@ -34538,6 +34538,9 @@ var styles$1 = r$6(":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px
34538
34538
  /**
34539
34539
  * A hierarchical tree view composed of `<l-tree-item>` children.
34540
34540
  *
34541
+ * The host carries `role="tree"`, so give it an accessible name with
34542
+ * `aria-label` or `aria-labelledby` (e.g. `<l-tree aria-label="Files">`).
34543
+ *
34541
34544
  * @slot - One or more `l-tree-item` elements.
34542
34545
  *
34543
34546
  * @csspart base - The root tree container.
@@ -34558,6 +34561,7 @@ var styles$1 = r$6(":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px
34558
34561
  var Tree = class extends LuxenElement {
34559
34562
  constructor(..._args) {
34560
34563
  super(..._args);
34564
+ this._internals = this.attachInternals();
34561
34565
  this._lastFocusedItem = null;
34562
34566
  this.#_selection_accessor_storage = "single";
34563
34567
  this.#_independent_accessor_storage = false;
@@ -34698,6 +34702,8 @@ var Tree = class extends LuxenElement {
34698
34702
  }
34699
34703
  connectedCallback() {
34700
34704
  super.connectedCallback();
34705
+ this._internals.role = "tree";
34706
+ if (!this.hasAttribute("role")) this.setAttribute("role", "tree");
34701
34707
  this._mutationObserver = new MutationObserver(() => this._syncAll());
34702
34708
  this._mutationObserver.observe(this, {
34703
34709
  childList: true,
@@ -34712,6 +34718,11 @@ var Tree = class extends LuxenElement {
34712
34718
  this.removeEventListener("l-tree-item-toggle", this._onItemToggle);
34713
34719
  }
34714
34720
  updated(changed) {
34721
+ if (changed.has("selection")) {
34722
+ const multiselectable = this.selection === "multiple" ? "true" : "false";
34723
+ this._internals.ariaMultiSelectable = multiselectable;
34724
+ this.setAttribute("aria-multiselectable", multiselectable);
34725
+ }
34715
34726
  if (changed.has("selection") || changed.has("independent")) this._syncAll();
34716
34727
  }
34717
34728
  /** Returns all items in document (flat) order, including nested ones. */
@@ -34740,14 +34751,24 @@ var Tree = class extends LuxenElement {
34740
34751
  return;
34741
34752
  }
34742
34753
  const showCheckbox = this.selection === "multiple";
34743
- for (const root of roots) this._syncSubtree(root, 0, showCheckbox);
34754
+ this._syncLevel(roots, 0, showCheckbox);
34744
34755
  this._updateBranchStates();
34745
34756
  this._ensureTabStop();
34746
34757
  }
34747
- _syncSubtree(item, depth, showCheckbox) {
34748
- item.depth = depth;
34749
- item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);
34750
- for (const child of item.getChildrenItems()) this._syncSubtree(child, depth + 1, showCheckbox);
34758
+ /**
34759
+ * Sync depth, checkbox visibility and ARIA position for a sibling group, then
34760
+ * recurse. `aria-level`/`aria-setsize`/`aria-posinset` let screen readers
34761
+ * announce "level N, M of K" valuable here because `lazy` items mean the
34762
+ * full set isn't always in the DOM (see WAI-ARIA Tree View pattern).
34763
+ */
34764
+ _syncLevel(items, depth, showCheckbox) {
34765
+ const setSize = items.length;
34766
+ items.forEach((item, index) => {
34767
+ item.depth = depth;
34768
+ item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);
34769
+ item.setPosition(depth + 1, index + 1, setSize);
34770
+ this._syncLevel(item.getChildrenItems(), depth + 1, showCheckbox);
34771
+ });
34751
34772
  }
34752
34773
  _canShowCheckboxOn(_item) {
34753
34774
  if (this.selection !== "multiple") return false;
@@ -34782,6 +34803,7 @@ var Tree = class extends LuxenElement {
34782
34803
  switch (this.selection) {
34783
34804
  case "single":
34784
34805
  this._setSingleSelection(item);
34806
+ if (!item.isLeaf()) item.toggle();
34785
34807
  break;
34786
34808
  case "leaf":
34787
34809
  if (item.isLeaf()) this._setSingleSelection(item);
@@ -34865,8 +34887,6 @@ var Tree = class extends LuxenElement {
34865
34887
  <div
34866
34888
  class="tree"
34867
34889
  part="base"
34868
- role="tree"
34869
- aria-multiselectable=${this.selection === "multiple" ? "true" : "false"}
34870
34890
  @click=${this._onClick}
34871
34891
  @keydown=${this._onKeyDown}
34872
34892
  @focusin=${this._onFocusIn}
@@ -35015,6 +35035,17 @@ var TreeItem = class extends LuxenElement {
35015
35035
  get depth() {
35016
35036
  return this._depth;
35017
35037
  }
35038
+ /**
35039
+ * Set by `<l-tree>`: ARIA position within the tree. `level` is 1-based depth,
35040
+ * `posInSet`/`setSize` describe the item's rank among its siblings. These let
35041
+ * screen readers announce "level 2, 3 of 5" even when `lazy` children keep the
35042
+ * full set out of the DOM.
35043
+ */
35044
+ setPosition(level, posInSet, setSize) {
35045
+ this._aria("ariaLevel", "aria-level", String(level));
35046
+ this._aria("ariaPosInSet", "aria-posinset", String(posInSet));
35047
+ this._aria("ariaSetSize", "aria-setsize", String(setSize));
35048
+ }
35018
35049
  /** Whether this item has nested tree-item children. */
35019
35050
  get hasChildren() {
35020
35051
  return this._hasChildren;
@@ -35037,6 +35068,7 @@ var TreeItem = class extends LuxenElement {
35037
35068
  connectedCallback() {
35038
35069
  super.connectedCallback();
35039
35070
  this._internals.role = "treeitem";
35071
+ if (!this.hasAttribute("role")) this.setAttribute("role", "treeitem");
35040
35072
  this._childObserver = new MutationObserver(() => this._syncChildren());
35041
35073
  this._childObserver.observe(this, { childList: true });
35042
35074
  this._syncChildren();
@@ -35047,11 +35079,28 @@ var TreeItem = class extends LuxenElement {
35047
35079
  }
35048
35080
  updated(changed) {
35049
35081
  if (changed.has("expanded")) {
35050
- this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);
35082
+ this._reflectExpanded();
35083
+ if (this.expanded && this.lazy) this.emit("lazy-load");
35051
35084
  this.emit(this.expanded ? "expand" : "collapse");
35052
35085
  }
35053
- if (changed.has("selected")) this._internals.ariaSelected = String(this.selected);
35054
- if (changed.has("disabled")) this._internals.ariaDisabled = this.disabled ? "true" : null;
35086
+ if (changed.has("selected")) this._aria("ariaSelected", "aria-selected", String(this.selected));
35087
+ if (changed.has("disabled")) this._aria("ariaDisabled", "aria-disabled", this.disabled ? "true" : null);
35088
+ if (changed.has("loading")) this._aria("ariaBusy", "aria-busy", this.loading ? "true" : null);
35089
+ }
35090
+ /** Leaf items omit `aria-expanded` entirely; branches reflect their state. */
35091
+ _reflectExpanded() {
35092
+ this._aria("ariaExpanded", "aria-expanded", this.isLeaf() ? null : String(this.expanded));
35093
+ }
35094
+ /**
35095
+ * Write an ARIA state to BOTH ElementInternals (the semantic source, in the
35096
+ * accessibility tree) and a content attribute (so `[aria-*]` CSS / query / test
35097
+ * selectors keep matching — same belt-and-suspenders as the mirrored `role`).
35098
+ * A `null` value clears both.
35099
+ */
35100
+ _aria(key, attr, value) {
35101
+ this._internals[key] = value;
35102
+ if (value === null) this.removeAttribute(attr);
35103
+ else this.setAttribute(attr, value);
35055
35104
  }
35056
35105
  _setState(name, on) {
35057
35106
  if (!this._internals.states) return;
@@ -35068,14 +35117,12 @@ var TreeItem = class extends LuxenElement {
35068
35117
  this._hasChildren = count > 0;
35069
35118
  this._setState("has-children", this._hasChildren);
35070
35119
  if (!this._hasChildren && !this.lazy && this.expanded) this.expanded = false;
35071
- this._internals.ariaExpanded = this.isLeaf() ? null : String(this.expanded);
35120
+ this._reflectExpanded();
35072
35121
  }
35073
- /** Toggle expand state. Emits `lazy-load` the first time a lazy item opens. */
35122
+ /** Toggle expand state. Opening a `lazy` item emits `lazy-load` (via `updated`). */
35074
35123
  toggle() {
35075
35124
  if (this.isLeaf() && !this.lazy) return;
35076
- const next = !this.expanded;
35077
- if (next && this.lazy) this.emit("lazy-load");
35078
- this.expanded = next;
35125
+ this.expanded = !this.expanded;
35079
35126
  }
35080
35127
  render() {
35081
35128
  return b`
@@ -35125,6 +35172,7 @@ var TreeItem = class extends LuxenElement {
35125
35172
  part="checkbox"
35126
35173
  type="checkbox"
35127
35174
  tabindex="-1"
35175
+ aria-hidden="true"
35128
35176
  .checked=${this.selected}
35129
35177
  .indeterminate=${this.indeterminate}
35130
35178
  ?disabled=${this.disabled}