luxen-ui 0.9.1 → 0.9.3
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.
- package/cdn/custom-elements.json +27 -2
- package/cdn/elements/dropdown-item/dropdown-item.js +1 -1
- package/cdn/elements/dropdown-item/dropdown-item.js.map +1 -1
- package/cdn/elements/tree/tree.d.ts +11 -1
- package/cdn/elements/tree/tree.d.ts.map +1 -1
- package/cdn/elements/tree/tree.js +1 -3
- package/cdn/elements/tree/tree.js.map +1 -1
- package/cdn/elements/tree-item/tree-item.d.ts +17 -1
- package/cdn/elements/tree-item/tree-item.d.ts.map +1 -1
- package/cdn/elements/tree-item/tree-item.js +2 -1
- package/cdn/elements/tree-item/tree-item.js.map +1 -1
- package/cdn/standalone.js +65 -17
- package/cdn/standalone.js.map +1 -1
- package/dist/custom-elements.json +27 -2
- package/dist/elements/dropdown-item/dropdown-item.css +1 -0
- package/dist/elements/tree/tree.d.ts +11 -1
- package/dist/elements/tree/tree.d.ts.map +1 -1
- package/dist/elements/tree/tree.js +37 -11
- package/dist/elements/tree-item/tree-item.css +7 -1
- package/dist/elements/tree-item/tree-item.d.ts +17 -1
- package/dist/elements/tree-item/tree-item.d.ts.map +1 -1
- package/dist/elements/tree-item/tree-item.js +51 -10
- package/dist/metadata/index.json +22 -3
- package/dist/metadata/tree-item.json +20 -1
- package/dist/metadata/tree.json +1 -1
- package/dist/templates/elements/tree.md +18 -0
- package/package.json +4 -2
package/cdn/custom-elements.json
CHANGED
|
@@ -6040,7 +6040,7 @@
|
|
|
6040
6040
|
"declarations": [
|
|
6041
6041
|
{
|
|
6042
6042
|
"kind": "class",
|
|
6043
|
-
"description": "A hierarchical tree view composed of `<l-tree-item>` children.",
|
|
6043
|
+
"description": "A hierarchical tree view composed of `<l-tree-item>` children.\n\nThe host carries `role=\"tree\"`, so give it an accessible name with\n`aria-label` or `aria-labelledby` (e.g. `<l-tree aria-label=\"Files\">`).",
|
|
6044
6044
|
"name": "Tree",
|
|
6045
6045
|
"cssProperties": [
|
|
6046
6046
|
{
|
|
@@ -6357,6 +6357,31 @@
|
|
|
6357
6357
|
"text": "number"
|
|
6358
6358
|
}
|
|
6359
6359
|
},
|
|
6360
|
+
{
|
|
6361
|
+
"kind": "method",
|
|
6362
|
+
"name": "setPosition",
|
|
6363
|
+
"parameters": [
|
|
6364
|
+
{
|
|
6365
|
+
"name": "level",
|
|
6366
|
+
"type": {
|
|
6367
|
+
"text": "number"
|
|
6368
|
+
}
|
|
6369
|
+
},
|
|
6370
|
+
{
|
|
6371
|
+
"name": "posInSet",
|
|
6372
|
+
"type": {
|
|
6373
|
+
"text": "number"
|
|
6374
|
+
}
|
|
6375
|
+
},
|
|
6376
|
+
{
|
|
6377
|
+
"name": "setSize",
|
|
6378
|
+
"type": {
|
|
6379
|
+
"text": "number"
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6382
|
+
],
|
|
6383
|
+
"description": "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\nscreen readers announce \"level 2, 3 of 5\" even when `lazy` children keep the\nfull set out of the DOM."
|
|
6384
|
+
},
|
|
6360
6385
|
{
|
|
6361
6386
|
"kind": "field",
|
|
6362
6387
|
"name": "hasChildren",
|
|
@@ -6405,7 +6430,7 @@
|
|
|
6405
6430
|
{
|
|
6406
6431
|
"kind": "method",
|
|
6407
6432
|
"name": "toggle",
|
|
6408
|
-
"description": "Toggle expand state.
|
|
6433
|
+
"description": "Toggle expand state. Opening a `lazy` item emits `lazy-load` (via `updated`)."
|
|
6409
6434
|
}
|
|
6410
6435
|
],
|
|
6411
6436
|
"events": [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e}from"../../chunks/lit.js";import{a as t,t 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";var s=e(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{cursor:pointer;color:var(--l-color-text-primary,CanvasText);white-space:nowrap;border-radius:4px;outline:none;align-items:center;gap:8px;padding:.375rem .5rem;font-size:.875rem;line-height:1.5;display:flex}.item:focus-visible{background:var(--l-color-bg-state-hover)}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}.check{flex-shrink:0;width:16px;display:flex}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),c=class extends r{static{this.styles=[o,s]}#e=``;get value(){return this.#e}set value(e){this.#e=e}#t=!1;get disabled(){return this.#t}set disabled(e){this.#t=e}#n=`normal`;get type(){return this.#n}set type(e){this.#n=e}#r=!1;get checked(){return this.#r}set checked(e){this.#r=e}getTextLabel(){return(this.textContent??``).trim()}render(){let e=this.type===`checkbox`;return t`
|
|
1
|
+
import{i as e}from"../../chunks/lit.js";import{a as t,t 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";var s=e(`:host{display:block}:host([disabled]){pointer-events:none;opacity:.5}.item{cursor:pointer;color:var(--l-color-text-primary,CanvasText);white-space:nowrap;text-align:start;border-radius:4px;outline:none;align-items:center;gap:8px;padding:.375rem .5rem;font-size:.875rem;line-height:1.5;display:flex}.item:focus-visible{background:var(--l-color-bg-state-hover)}@media (hover:hover){.item:hover{background:var(--l-color-bg-state-hover)}}.check{flex-shrink:0;width:16px;display:flex}:host(:not([checked])) .check svg{visibility:hidden}::slotted([slot=prefix]),::slotted([slot=suffix]){flex-shrink:0;display:flex}.label{flex:1}`),c=class extends r{static{this.styles=[o,s]}#e=``;get value(){return this.#e}set value(e){this.#e=e}#t=!1;get disabled(){return this.#t}set disabled(e){this.#t=e}#n=`normal`;get type(){return this.#n}set type(e){this.#n=e}#r=!1;get checked(){return this.#r}set checked(e){this.#r=e}getTextLabel(){return(this.textContent??``).trim()}render(){let e=this.type===`checkbox`;return t`
|
|
2
2
|
<div
|
|
3
3
|
class="item"
|
|
4
4
|
role=${e?`menuitemcheckbox`:`menuitem`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dropdown-item.js","names":[],"sources":["../../../src/html/elements/dropdown-item/dropdown-item.css?inline","../../../src/html/elements/dropdown-item/dropdown-item.ts"],"sourcesContent":[":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","import { html, nothing, unsafeCSS } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property } from 'lit/decorators.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './dropdown-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A menu item for use inside `<l-dropdown>`.\n *\n * @slot - Label text.\n * @slot prefix - Leading content (e.g. icon).\n * @slot suffix - Trailing content.\n *\n * @cssproperty --color - Text color.\n *\n * @customElement l-dropdown-item\n */\nexport class DropdownItem extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n /** The value associated with this item. */\n @property()\n accessor value = '';\n\n /** Disables the item. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** The type of item: `normal` or `checkbox`. */\n @property()\n accessor type: 'normal' | 'checkbox' = 'normal';\n\n /** Whether the checkbox item is checked. */\n @property({ type: Boolean, reflect: true })\n accessor checked = false;\n\n /** Returns the text label of this item. */\n getTextLabel(): string {\n return (this.textContent ?? '').trim();\n }\n\n override render() {\n const isCheckbox = this.type === 'checkbox';\n\n return html`\n <div\n class=\"item\"\n role=${isCheckbox ? 'menuitemcheckbox' : 'menuitem'}\n aria-checked=${isCheckbox ? String(this.checked) : nothing}\n aria-disabled=${this.disabled ? 'true' : nothing}\n tabindex=\"-1\"\n >\n ${isCheckbox\n ? html`\n <span\n class=\"check\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n `\n : html` <slot name=\"prefix\"></slot> `}\n <span class=\"label\"><slot></slot></span>\n <slot name=\"suffix\"></slot>\n </div>\n `;\n }\n}\n"],"mappings":"0PCMA,IAAM,EAAS,EAAU,
|
|
1
|
+
{"version":3,"file":"dropdown-item.js","names":[],"sources":["../../../src/html/elements/dropdown-item/dropdown-item.css?inline","../../../src/html/elements/dropdown-item/dropdown-item.ts"],"sourcesContent":[":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","import { html, nothing, unsafeCSS } from 'lit';\nimport { LuxenElement } from '../../shared/luxen-element.js';\nimport { property } from 'lit/decorators.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './dropdown-item.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\n/**\n * A menu item for use inside `<l-dropdown>`.\n *\n * @slot - Label text.\n * @slot prefix - Leading content (e.g. icon).\n * @slot suffix - Trailing content.\n *\n * @cssproperty --color - Text color.\n *\n * @customElement l-dropdown-item\n */\nexport class DropdownItem extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n /** The value associated with this item. */\n @property()\n accessor value = '';\n\n /** Disables the item. */\n @property({ type: Boolean, reflect: true })\n accessor disabled = false;\n\n /** The type of item: `normal` or `checkbox`. */\n @property()\n accessor type: 'normal' | 'checkbox' = 'normal';\n\n /** Whether the checkbox item is checked. */\n @property({ type: Boolean, reflect: true })\n accessor checked = false;\n\n /** Returns the text label of this item. */\n getTextLabel(): string {\n return (this.textContent ?? '').trim();\n }\n\n override render() {\n const isCheckbox = this.type === 'checkbox';\n\n return html`\n <div\n class=\"item\"\n role=${isCheckbox ? 'menuitemcheckbox' : 'menuitem'}\n aria-checked=${isCheckbox ? String(this.checked) : nothing}\n aria-disabled=${this.disabled ? 'true' : nothing}\n tabindex=\"-1\"\n >\n ${isCheckbox\n ? html`\n <span\n class=\"check\"\n aria-hidden=\"true\"\n >\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n `\n : html` <slot name=\"prefix\"></slot> `}\n <span class=\"label\"><slot></slot></span>\n <slot name=\"suffix\"></slot>\n </div>\n `;\n }\n}\n"],"mappings":"0PCMA,IAAM,EAAS,EAAU,inBAAU,CAatB,EAAb,cAAkC,CAAa,oBACpB,CAAC,EAAY,EAAO,IAI5B,OAAR,OAAA,0CAIW,OAAX,UAAA,6CAI8B,aAA9B,MAAA,yCAIU,OAAV,SAAA,yCAGT,cAAuB,CACrB,OAAQ,KAAK,aAAe,IAAI,MAAM,CAGxC,QAAkB,CAChB,IAAM,EAAa,KAAK,OAAS,WAEjC,MAAO,EAAI;;;eAGA,EAAa,mBAAqB,WAAW;uBACrC,EAAa,OAAO,KAAK,QAAQ,CAAG,EAAQ;wBAC3C,KAAK,SAAW,OAAS,EAAQ;;;UAG/C,EACE,CAAI;;;;;;;;;;;;;;;;;;;;cAqBJ,CAAI,gCAAgC;;;;WArD7C,GAAU,CAAA,CAAA,EAAA,UAAA,QAAA,KAAA,IAIV,EAAS,CAAE,KAAM,QAAS,QAAS,GAAM,CAAC,CAAA,CAAA,EAAA,UAAA,WAAA,KAAA,IAI1C,GAAU,CAAA,CAAA,EAAA,UAAA,OAAA,KAAA,IAIV,EAAS,CAAE,KAAM,QAAS,QAAS,GAAM,CAAC,CAAA,CAAA,EAAA,UAAA,UAAA,KAAA"}
|
|
@@ -5,6 +5,9 @@ export type TreeSelection = 'single' | 'multiple' | 'leaf' | 'none';
|
|
|
5
5
|
/**
|
|
6
6
|
* A hierarchical tree view composed of `<l-tree-item>` children.
|
|
7
7
|
*
|
|
8
|
+
* The host carries `role="tree"`, so give it an accessible name with
|
|
9
|
+
* `aria-label` or `aria-labelledby` (e.g. `<l-tree aria-label="Files">`).
|
|
10
|
+
*
|
|
8
11
|
* @slot - One or more `l-tree-item` elements.
|
|
9
12
|
*
|
|
10
13
|
* @csspart base - The root tree container.
|
|
@@ -24,6 +27,7 @@ export type TreeSelection = 'single' | 'multiple' | 'leaf' | 'none';
|
|
|
24
27
|
*/
|
|
25
28
|
export declare class Tree extends LuxenElement {
|
|
26
29
|
static styles: import('lit').CSSResult[];
|
|
30
|
+
private _internals;
|
|
27
31
|
private _mutationObserver?;
|
|
28
32
|
private _lastFocusedItem;
|
|
29
33
|
/**
|
|
@@ -54,7 +58,13 @@ export declare class Tree extends LuxenElement {
|
|
|
54
58
|
/** Collapses every item. */
|
|
55
59
|
collapseAll(): void;
|
|
56
60
|
private _syncAll;
|
|
57
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Sync depth, checkbox visibility and ARIA position for a sibling group, then
|
|
63
|
+
* recurse. `aria-level`/`aria-setsize`/`aria-posinset` let screen readers
|
|
64
|
+
* announce "level N, M of K" — valuable here because `lazy` items mean the
|
|
65
|
+
* full set isn't always in the DOM (see WAI-ARIA Tree View pattern).
|
|
66
|
+
*/
|
|
67
|
+
private _syncLevel;
|
|
58
68
|
private _canShowCheckboxOn;
|
|
59
69
|
private _rootItems;
|
|
60
70
|
private _ensureTabStop;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tree/tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAM1D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpE
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../../src/html/elements/tree/tree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAE3D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAM1D,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,IAAK,SAAQ,YAAY;IACpC,OAAgB,MAAM,4BAAwB;IAE9C,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,iBAAiB,CAAC,CAAmB;IAC7C,OAAO,CAAC,gBAAgB,CAAyB;IAEjD;;;;;;OAMG;IAEH,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAY;IAE7C;;;;OAIG;IAEH,QAAQ,CAAC,WAAW,UAAS;IAEpB,iBAAiB;IAgBjB,oBAAoB;IAMpB,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC;IAe9C,yEAAyE;IACzE,WAAW,CAAC,EAAE,eAAsB,EAAE;;KAAK,GAAG,QAAQ,EAAE;IAOxD,wCAAwC;IACxC,YAAY,IAAI,QAAQ,EAAE;IAI1B,4CAA4C;IAC5C,SAAS;IAMT,4BAA4B;IAC5B,WAAW;IAQX,OAAO,CAAC,QAAQ;IAoBhB;;;;;OAKG;IACH,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,cAAc;IAUtB,2DAA2D;IAC3D,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,aAAa,CAGnB;IAEF,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,oBAAoB;IAS5B,0EAA0E;IAC1E,OAAO,CAAC,mBAAmB;IA6B3B,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,QAAQ,CAqDd;IAEF,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,UAAU,CAMhB;IAEF,OAAO,CAAC,UAAU,CAqEhB;IAEO,MAAM;CAahB"}
|
|
@@ -1,9 +1,7 @@
|
|
|
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";var s=t(`:host{--indent-size:1rem;--indent-guide-width:1px;--indent-guide-style:solid;--indent-guide-color:var(--l-color-border-interactive,var(--lightningcss-light,#d1d5db)var(--lightningcss-dark,#3a4048));--row-height:1.75rem;--row-padding-inline:.25rem;--chevron-size:1.125rem;--item-gap:.375rem;color:var(--l-color-text-primary,CanvasText);font-family:inherit;line-height:1.5;display:block}.tree{outline:none;display:block}.tree:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px;border-radius:.375rem}`),c=class extends r{constructor(...t){super(...t),this._lastFocusedItem=null,this.#e=`single`,this.#t=!1,this._onItemToggle=e=>{let{item:t,checked:n}=e.detail;this._selectItem(t,n)},this._onClick=e=>{let t=this._itemFromEvent(e);if(!t||t.disabled)return;let n=e.composedPath();if(n.some(e=>e instanceof HTMLInputElement&&e.type===`checkbox`))return;let r=new Set([`BUTTON`,`A`,`INPUT`,`SELECT`,`TEXTAREA`]),i=new Set([`button`,`link`,`menuitem`,`menuitemcheckbox`]);if(n.some(e=>{if(!(e instanceof HTMLElement)||e===t||e.getAttribute?.(`part`)===`expand-button`||e instanceof HTMLInputElement&&e.type===`checkbox`)return!1;if(r.has(e.tagName))return!0;let n=e.getAttribute?.(`role`);return n!==null&&i.has(n)}))return;let a=n.some(e=>e instanceof HTMLElement&&e.getAttribute?.(`part`)===`expand-button`);if(this._focusItem(t),a){t.toggle();return}switch(this.selection){case`single`:this._setSingleSelection(t),t.isLeaf()||t.toggle();break;case`leaf`:t.isLeaf()?this._setSingleSelection(t):t.toggle();break;case`multiple`:this._selectItem(t,!t.selected);break;case`none`:t.toggle();break}},this._onFocusIn=t=>{let n=t.target;if(n instanceof HTMLElement){let t=n.closest(e(`tree-item`));t&&(this._lastFocusedItem=t)}},this._onKeyDown=t=>{let n=this._lastFocusedItem??this._visibleItems()[0];if(!n)return;let r=this._visibleItems(),i=r.indexOf(n);switch(t.key){case`ArrowDown`:{t.preventDefault();let e=r[Math.min(i+1,r.length-1)];e&&this._focusItem(e);break}case`ArrowUp`:{t.preventDefault();let e=r[Math.max(i-1,0)];e&&this._focusItem(e);break}case`ArrowRight`:if(t.preventDefault(),!n.isLeaf()&&!n.expanded)n.expanded=!0;else if(n.expanded){let e=n.getChildrenItems()[0];e&&this._focusItem(e)}break;case`ArrowLeft`:if(t.preventDefault(),n.expanded&&!n.isLeaf())n.expanded=!1;else{let t=n.parentElement?.closest(e(`tree-item`));t&&this._focusItem(t)}break;case`Home`:t.preventDefault(),r[0]&&this._focusItem(r[0]);break;case`End`:{t.preventDefault();let e=r[r.length-1];e&&this._focusItem(e);break}case`Enter`:case` `:t.preventDefault(),this._handleRowActivate(n);break;case`*`:{t.preventDefault();let r=(n.parentElement?Array.from(n.parentElement.children):[]).filter(t=>t.tagName===e(`tree-item`).toUpperCase());for(let e of r)e.isLeaf()||(e.expanded=!0);break}}}}static{this.styles=[o,s]}#e;get selection(){return this.#e}set selection(e){this.#e=e}#t;get independent(){return this.#t}set independent(e){this.#t=e}connectedCallback(){super.connectedCallback(),this._mutationObserver=new MutationObserver(()=>this._syncAll()),this._mutationObserver.observe(this,{childList:!0,subtree:!0}),this.addEventListener(`l-tree-item-toggle`,this._onItemToggle),queueMicrotask(()=>this._syncAll())}disconnectedCallback(){super.disconnectedCallback(),this._mutationObserver?.disconnect(),this.removeEventListener(`l-tree-item-toggle`,this._onItemToggle)}updated(e){(e.has(`selection`)||e.has(`independent`))&&this._syncAll()}getAllItems({includeDisabled:t=!0}={}){let n=e(`tree-item`);return Array.from(this.querySelectorAll(n)).filter(e=>t||!e.disabled)}getSelection(){return this.getAllItems().filter(e=>e.selected)}expandAll(){for(let e of this.getAllItems())e.isLeaf()||(e.expanded=!0)}collapseAll(){for(let e of this.getAllItems())e.expanded=!1}_syncAll(){customElements.upgrade(this);let t=e(`tree-item`),n=this._rootItems();if(n.some(e=>typeof e.getChildrenItems!=`function`)){customElements.whenDefined(t).then(()=>this._syncAll());return}let r=this.selection===`multiple`;
|
|
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";var s=t(`:host{--indent-size:1rem;--indent-guide-width:1px;--indent-guide-style:solid;--indent-guide-color:var(--l-color-border-interactive,var(--lightningcss-light,#d1d5db)var(--lightningcss-dark,#3a4048));--row-height:1.75rem;--row-padding-inline:.25rem;--chevron-size:1.125rem;--item-gap:.375rem;color:var(--l-color-text-primary,CanvasText);font-family:inherit;line-height:1.5;display:block}.tree{outline:none;display:block}.tree:focus-visible{outline:2px solid var(--l-focus-ring);outline-offset:2px;border-radius:.375rem}`),c=class extends r{constructor(...t){super(...t),this._internals=this.attachInternals(),this._lastFocusedItem=null,this.#e=`single`,this.#t=!1,this._onItemToggle=e=>{let{item:t,checked:n}=e.detail;this._selectItem(t,n)},this._onClick=e=>{let t=this._itemFromEvent(e);if(!t||t.disabled)return;let n=e.composedPath();if(n.some(e=>e instanceof HTMLInputElement&&e.type===`checkbox`))return;let r=new Set([`BUTTON`,`A`,`INPUT`,`SELECT`,`TEXTAREA`]),i=new Set([`button`,`link`,`menuitem`,`menuitemcheckbox`]);if(n.some(e=>{if(!(e instanceof HTMLElement)||e===t||e.getAttribute?.(`part`)===`expand-button`||e instanceof HTMLInputElement&&e.type===`checkbox`)return!1;if(r.has(e.tagName))return!0;let n=e.getAttribute?.(`role`);return n!==null&&i.has(n)}))return;let a=n.some(e=>e instanceof HTMLElement&&e.getAttribute?.(`part`)===`expand-button`);if(this._focusItem(t),a){t.toggle();return}switch(this.selection){case`single`:this._setSingleSelection(t),t.isLeaf()||t.toggle();break;case`leaf`:t.isLeaf()?this._setSingleSelection(t):t.toggle();break;case`multiple`:this._selectItem(t,!t.selected);break;case`none`:t.toggle();break}},this._onFocusIn=t=>{let n=t.target;if(n instanceof HTMLElement){let t=n.closest(e(`tree-item`));t&&(this._lastFocusedItem=t)}},this._onKeyDown=t=>{let n=this._lastFocusedItem??this._visibleItems()[0];if(!n)return;let r=this._visibleItems(),i=r.indexOf(n);switch(t.key){case`ArrowDown`:{t.preventDefault();let e=r[Math.min(i+1,r.length-1)];e&&this._focusItem(e);break}case`ArrowUp`:{t.preventDefault();let e=r[Math.max(i-1,0)];e&&this._focusItem(e);break}case`ArrowRight`:if(t.preventDefault(),!n.isLeaf()&&!n.expanded)n.expanded=!0;else if(n.expanded){let e=n.getChildrenItems()[0];e&&this._focusItem(e)}break;case`ArrowLeft`:if(t.preventDefault(),n.expanded&&!n.isLeaf())n.expanded=!1;else{let t=n.parentElement?.closest(e(`tree-item`));t&&this._focusItem(t)}break;case`Home`:t.preventDefault(),r[0]&&this._focusItem(r[0]);break;case`End`:{t.preventDefault();let e=r[r.length-1];e&&this._focusItem(e);break}case`Enter`:case` `:t.preventDefault(),this._handleRowActivate(n);break;case`*`:{t.preventDefault();let r=(n.parentElement?Array.from(n.parentElement.children):[]).filter(t=>t.tagName===e(`tree-item`).toUpperCase());for(let e of r)e.isLeaf()||(e.expanded=!0);break}}}}static{this.styles=[o,s]}#e;get selection(){return this.#e}set selection(e){this.#e=e}#t;get independent(){return this.#t}set independent(e){this.#t=e}connectedCallback(){super.connectedCallback(),this._internals.role=`tree`,this.hasAttribute(`role`)||this.setAttribute(`role`,`tree`),this._mutationObserver=new MutationObserver(()=>this._syncAll()),this._mutationObserver.observe(this,{childList:!0,subtree:!0}),this.addEventListener(`l-tree-item-toggle`,this._onItemToggle),queueMicrotask(()=>this._syncAll())}disconnectedCallback(){super.disconnectedCallback(),this._mutationObserver?.disconnect(),this.removeEventListener(`l-tree-item-toggle`,this._onItemToggle)}updated(e){if(e.has(`selection`)){let e=this.selection===`multiple`?`true`:`false`;this._internals.ariaMultiSelectable=e,this.setAttribute(`aria-multiselectable`,e)}(e.has(`selection`)||e.has(`independent`))&&this._syncAll()}getAllItems({includeDisabled:t=!0}={}){let n=e(`tree-item`);return Array.from(this.querySelectorAll(n)).filter(e=>t||!e.disabled)}getSelection(){return this.getAllItems().filter(e=>e.selected)}expandAll(){for(let e of this.getAllItems())e.isLeaf()||(e.expanded=!0)}collapseAll(){for(let e of this.getAllItems())e.expanded=!1}_syncAll(){customElements.upgrade(this);let t=e(`tree-item`),n=this._rootItems();if(n.some(e=>typeof e.getChildrenItems!=`function`)){customElements.whenDefined(t).then(()=>this._syncAll());return}let r=this.selection===`multiple`;this._syncLevel(n,0,r),this._updateBranchStates(),this._ensureTabStop()}_syncLevel(e,t,n){let r=e.length;e.forEach((e,i)=>{e.depth=t,e.showCheckbox=n&&this._canShowCheckboxOn(e),e.setPosition(t+1,i+1,r),this._syncLevel(e.getChildrenItems(),t+1,n)})}_canShowCheckboxOn(e){return this.selection===`multiple`}_rootItems(){let t=e(`tree-item`).toUpperCase();return Array.from(this.children).filter(e=>e.tagName===t)}_ensureTabStop(){let e=this._visibleItems();if(e.length!==0&&!e.some(e=>e.tabIndex===0)){for(let t of e)t.tabIndex=-1;e[0].tabIndex=0}}_visibleItems(){let e=[],t=n=>{for(let r of n)e.push(r),r.expanded&&t(r.getChildrenItems())};return t(this._rootItems()),e}_handleRowActivate(e){if(!e.disabled)switch(this.selection){case`single`:this._setSingleSelection(e),e.isLeaf()||e.toggle();break;case`leaf`:e.isLeaf()?this._setSingleSelection(e):e.toggle();break;case`multiple`:this._selectItem(e,!e.selected);break;case`none`:e.toggle();break}}_setSingleSelection(e){for(let t of this.getAllItems())t!==e&&t.selected&&(t.selected=!1);e.selected=!0,this._emitSelectionChange()}_selectItem(e,t){e.disabled||(e.selected=t,this.selection===`multiple`&&!this.independent&&this._setSubtreeSelection(e,t),e.indeterminate=!1,this._updateBranchStates(),this._emitSelectionChange())}_setSubtreeSelection(e,t){for(let n of e.getChildrenItems())n.disabled||(n.selected=t,n.indeterminate=!1,this._setSubtreeSelection(n,t))}_updateBranchStates(){if(this.selection!==`multiple`||this.independent){for(let e of this.getAllItems())e.indeterminate=!1;return}let e=t=>{let n=t.getChildrenItems({includeDisabled:!1});if(n.length===0)return{all:t.selected,any:t.selected};let r=!0,i=!1;for(let t of n){let n=e(t);n.all||(r=!1),n.any&&(i=!0)}return t.selected=r,t.indeterminate=!r&&i,{all:r&&(t.getChildrenItems().length>0?r:t.selected),any:i}};for(let t of this._rootItems())e(t)}_emitSelectionChange(){this.emit(`selection-change`,{detail:{selection:this.getSelection()}})}_itemFromEvent(t){let n=e(`tree-item`),r=t.composedPath();for(let e of r)if(e instanceof HTMLElement&&e.matches?.(n))return e;return null}_focusItem(e){let t=this._visibleItems();for(let e of t)e.tabIndex=-1;e.tabIndex=0,e.focus(),this._lastFocusedItem=e}render(){return n`
|
|
2
2
|
<div
|
|
3
3
|
class="tree"
|
|
4
4
|
part="base"
|
|
5
|
-
role="tree"
|
|
6
|
-
aria-multiselectable=${this.selection===`multiple`?`true`:`false`}
|
|
7
5
|
@click=${this._onClick}
|
|
8
6
|
@keydown=${this._onKeyDown}
|
|
9
7
|
@focusin=${this._onFocusIn}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.js","names":[],"sources":["../../../src/html/elements/tree/tree.css?inline","../../../src/html/elements/tree/tree.ts"],"sourcesContent":[":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px;\n --indent-guide-style: solid;\n --indent-guide-color: var(--l-color-border-interactive, light-dark(#d1d5db, #3a4048));\n --row-height: 1.75rem;\n --row-padding-inline: 0.25rem;\n --chevron-size: 1.125rem;\n --item-gap: 0.375rem;\n\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-family: inherit;\n line-height: 1.5;\n}\n\n.tree {\n display: block;\n outline: none;\n}\n\n.tree:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n border-radius: 0.375rem;\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 type { TreeItem } from '../tree-item/tree-item.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './tree.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type TreeSelection = 'single' | 'multiple' | 'leaf' | 'none';\n\n/**\n * A hierarchical tree view composed of `<l-tree-item>` children.\n *\n * @slot - One or more `l-tree-item` elements.\n *\n * @csspart base - The root tree container.\n *\n * @cssproperty --indent-size - Horizontal indent per depth level. Default `1rem`.\n * @cssproperty --indent-guide-width - Thickness of the vertical guide line between a parent and its children. Default `1px`. Set to `0` to hide guides.\n * @cssproperty --indent-guide-style - Line style of the guide (`solid`, `dashed`, `dotted`, `double`…). Default `solid`.\n * @cssproperty --indent-guide-color - Color of the guide line.\n * @cssproperty --row-height - Minimum row height. Default `1.75rem`.\n * @cssproperty --row-padding-inline - Inner inline padding of the row; also drives the content slot left indent and the indent guide column. Default `0.25rem`.\n * @cssproperty --chevron-size - Size of the expand/collapse chevron box. Default `1.125rem`.\n * @cssproperty --item-gap - Horizontal gap between chevron, prefix, label and suffix on the row; also drives the content slot left indent. Default `0.375rem`.\n *\n * @event selection-change - Fired when the selected items change. Detail: `{ selection: TreeItem[] }`.\n *\n * @customElement l-tree\n */\nexport class Tree extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _mutationObserver?: MutationObserver;\n private _lastFocusedItem: TreeItem | null = null;\n\n /**\n * Selection behaviour:\n * - `single` (default): at most one item selected via `aria-selected`.\n * - `multiple`: any number of items selected. Checkboxes are rendered.\n * - `leaf`: only leaf items can be selected (single). Branches just toggle.\n * - `none`: purely navigable, no selection state.\n */\n @property({ reflect: true })\n accessor selection: TreeSelection = 'single';\n\n /**\n * When set with `selection=\"multiple\"`, parent and children selection are decoupled:\n * toggling a parent does NOT toggle its descendants and vice versa.\n * Without it, selection cascades both ways and branches may become indeterminate.\n */\n @property({ type: Boolean, reflect: true })\n accessor independent = false;\n\n override connectedCallback() {\n super.connectedCallback();\n this._mutationObserver = new MutationObserver(() => this._syncAll());\n this._mutationObserver.observe(this, { childList: true, subtree: true });\n this.addEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n\n // Defer sync to let light DOM upgrade.\n queueMicrotask(() => this._syncAll());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._mutationObserver?.disconnect();\n this.removeEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('selection') || changed.has('independent')) {\n this._syncAll();\n }\n }\n\n // --- Public API ---\n\n /** Returns all items in document (flat) order, including nested ones. */\n getAllItems({ includeDisabled = true } = {}): TreeItem[] {\n const tag = tagName('tree-item');\n return Array.from(this.querySelectorAll<TreeItem>(tag)).filter(\n (item) => includeDisabled || !item.disabled,\n );\n }\n\n /** Returns currently selected items. */\n getSelection(): TreeItem[] {\n return this.getAllItems().filter((i) => i.selected);\n }\n\n /** Expands every item that has children. */\n expandAll() {\n for (const item of this.getAllItems()) {\n if (!item.isLeaf()) item.expanded = true;\n }\n }\n\n /** Collapses every item. */\n collapseAll() {\n for (const item of this.getAllItems()) {\n item.expanded = false;\n }\n }\n\n // --- Sync / ARIA / depth / checkbox visibility ---\n\n private _syncAll() {\n // `_syncAll()` may run from `updated()` before `<l-tree-item>` is registered\n // (e.g. when the tree module is imported before tree-item, or in async chunks).\n // Force-upgrade any pending custom elements in our subtree, then bail and retry\n // once the registration completes if any item is still un-upgraded.\n customElements.upgrade(this);\n const itemTag = tagName('tree-item');\n const roots = this._rootItems();\n if (roots.some((r) => typeof r.getChildrenItems !== 'function')) {\n void customElements.whenDefined(itemTag).then(() => this._syncAll());\n return;\n }\n\n const showCheckbox = this.selection === 'multiple';\n for (const root of roots) {\n this._syncSubtree(root, 0, showCheckbox);\n }\n this._updateBranchStates();\n // Ensure at least one item is tabbable.\n this._ensureTabStop();\n }\n\n private _syncSubtree(item: TreeItem, depth: number, showCheckbox: boolean) {\n item.depth = depth;\n item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);\n for (const child of item.getChildrenItems()) {\n this._syncSubtree(child, depth + 1, showCheckbox);\n }\n }\n\n private _canShowCheckboxOn(_item: TreeItem): boolean {\n if (this.selection !== 'multiple') return false;\n // In cascade mode, branches get a checkbox too so you can bulk-toggle children.\n // In leaf-only selection, hidden here because selection !== 'multiple'.\n return true;\n }\n\n private _rootItems(): TreeItem[] {\n const tag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as TreeItem[]).filter((el) => el.tagName === tag);\n }\n\n private _ensureTabStop() {\n const items = this._visibleItems();\n if (items.length === 0) return;\n const hasTabStop = items.some((i) => i.tabIndex === 0);\n if (!hasTabStop) {\n for (const i of items) i.tabIndex = -1;\n items[0].tabIndex = 0;\n }\n }\n\n /** Items currently visible (parent chain all expanded). */\n private _visibleItems(): TreeItem[] {\n const out: TreeItem[] = [];\n const walk = (items: TreeItem[]) => {\n for (const i of items) {\n out.push(i);\n if (i.expanded) walk(i.getChildrenItems());\n }\n };\n walk(this._rootItems());\n return out;\n }\n\n // --- Selection handling ---\n\n private _onItemToggle = (event: CustomEvent<{ item: TreeItem; checked: boolean }>) => {\n const { item, checked } = event.detail;\n this._selectItem(item, checked);\n };\n\n private _handleRowActivate(item: TreeItem) {\n if (item.disabled) return;\n\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n }\n\n private _setSingleSelection(item: TreeItem) {\n for (const i of this.getAllItems()) {\n if (i !== item && i.selected) i.selected = false;\n }\n item.selected = true;\n this._emitSelectionChange();\n }\n\n private _selectItem(item: TreeItem, value: boolean) {\n if (item.disabled) return;\n item.selected = value;\n\n if (this.selection === 'multiple' && !this.independent) {\n // Cascade DOWN: toggling a branch toggles all descendants.\n this._setSubtreeSelection(item, value);\n }\n\n item.indeterminate = false;\n this._updateBranchStates();\n this._emitSelectionChange();\n }\n\n private _setSubtreeSelection(item: TreeItem, value: boolean) {\n for (const child of item.getChildrenItems()) {\n if (child.disabled) continue;\n child.selected = value;\n child.indeterminate = false;\n this._setSubtreeSelection(child, value);\n }\n }\n\n /** Propagate child state UP to parents (indeterminate / auto-checked). */\n private _updateBranchStates() {\n if (this.selection !== 'multiple' || this.independent) {\n // In independent or non-multiple modes, clear any indeterminate flags.\n for (const i of this.getAllItems()) i.indeterminate = false;\n return;\n }\n\n const recompute = (item: TreeItem): { all: boolean; any: boolean } => {\n const children = item.getChildrenItems({ includeDisabled: false });\n if (children.length === 0) {\n return { all: item.selected, any: item.selected };\n }\n\n let all = true;\n let any = false;\n for (const child of children) {\n const state = recompute(child);\n if (!state.all) all = false;\n if (state.any) any = true;\n }\n\n item.selected = all;\n item.indeterminate = !all && any;\n return { all: all && (item.getChildrenItems().length > 0 ? all : item.selected), any };\n };\n\n for (const root of this._rootItems()) recompute(root);\n }\n\n private _emitSelectionChange() {\n this.emit('selection-change', { detail: { selection: this.getSelection() } });\n }\n\n // --- Keyboard / focus ---\n\n private _onClick = (event: MouseEvent) => {\n const item = this._itemFromEvent(event);\n if (!item || item.disabled) return;\n\n const path = event.composedPath();\n const onCheckbox = path.some((n) => n instanceof HTMLInputElement && n.type === 'checkbox');\n if (onCheckbox) return; // handled via change event\n\n // Clicks on consumer-provided interactive elements (buttons, links, form\n // controls, menu items…) must not toggle the row — the consumer owns that\n // interaction. Works regardless of which slot the element was placed in.\n const INTERACTIVE_TAGS = new Set(['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA']);\n const INTERACTIVE_ROLES = new Set(['button', 'link', 'menuitem', 'menuitemcheckbox']);\n const onInteractive = path.some((n) => {\n if (!(n instanceof HTMLElement) || n === item) return false;\n if (n.getAttribute?.('part') === 'expand-button') return false;\n if (n instanceof HTMLInputElement && n.type === 'checkbox') return false;\n if (INTERACTIVE_TAGS.has(n.tagName)) return true;\n const role = n.getAttribute?.('role');\n return role !== null && INTERACTIVE_ROLES.has(role);\n });\n if (onInteractive) return;\n\n const onExpand = path.some(\n (n) => n instanceof HTMLElement && n.getAttribute?.('part') === 'expand-button',\n );\n\n this._focusItem(item);\n\n if (onExpand) {\n item.toggle();\n return;\n }\n\n // Row click (label area): mode-dependent behaviour.\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n if (!item.isLeaf()) item.toggle();\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n // The whole row acts like a <label> for the checkbox: clicking anywhere\n // on it toggles selection. Use the chevron to expand/collapse branches.\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n };\n\n private _itemFromEvent(event: Event): TreeItem | null {\n const tag = tagName('tree-item');\n const path = event.composedPath();\n for (const node of path) {\n if (node instanceof HTMLElement && node.matches?.(tag)) {\n return node as TreeItem;\n }\n }\n return null;\n }\n\n private _focusItem(item: TreeItem) {\n const visible = this._visibleItems();\n for (const i of visible) i.tabIndex = -1;\n item.tabIndex = 0;\n item.focus();\n this._lastFocusedItem = item;\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (target instanceof HTMLElement) {\n const item = target.closest<TreeItem>(tagName('tree-item'));\n if (item) this._lastFocusedItem = item;\n }\n };\n\n private _onKeyDown = (event: KeyboardEvent) => {\n const current = this._lastFocusedItem ?? this._visibleItems()[0];\n if (!current) return;\n\n const visible = this._visibleItems();\n const index = visible.indexOf(current);\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n const next = visible[Math.min(index + 1, visible.length - 1)];\n if (next) this._focusItem(next);\n break;\n }\n case 'ArrowUp': {\n event.preventDefault();\n const prev = visible[Math.max(index - 1, 0)];\n if (prev) this._focusItem(prev);\n break;\n }\n case 'ArrowRight': {\n event.preventDefault();\n if (!current.isLeaf() && !current.expanded) {\n current.expanded = true;\n } else if (current.expanded) {\n const first = current.getChildrenItems()[0];\n if (first) this._focusItem(first);\n }\n break;\n }\n case 'ArrowLeft': {\n event.preventDefault();\n if (current.expanded && !current.isLeaf()) {\n current.expanded = false;\n } else {\n const parent = current.parentElement?.closest(tagName('tree-item')) as TreeItem | null;\n if (parent) this._focusItem(parent);\n }\n break;\n }\n case 'Home': {\n event.preventDefault();\n if (visible[0]) this._focusItem(visible[0]);\n break;\n }\n case 'End': {\n event.preventDefault();\n const last = visible[visible.length - 1];\n if (last) this._focusItem(last);\n break;\n }\n case 'Enter':\n case ' ': {\n event.preventDefault();\n this._handleRowActivate(current);\n break;\n }\n case '*': {\n event.preventDefault();\n // Expand all siblings of the current item.\n const siblings = (\n current.parentElement ? (Array.from(current.parentElement.children) as TreeItem[]) : []\n ).filter((el) => el.tagName === tagName('tree-item').toUpperCase());\n for (const sib of siblings) {\n if (!sib.isLeaf()) sib.expanded = true;\n }\n break;\n }\n }\n };\n\n override render() {\n return html`\n <div\n class=\"tree\"\n part=\"base\"\n role=\"tree\"\n aria-multiselectable=${this.selection === 'multiple' ? 'true' : 'false'}\n @click=${this._onClick}\n @keydown=${this._onKeyDown}\n @focusin=${this._onFocusIn}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tree.js","names":[],"sources":["../../../src/html/elements/tree/tree.css?inline","../../../src/html/elements/tree/tree.ts"],"sourcesContent":[":host {\n --indent-size: 1rem;\n --indent-guide-width: 1px;\n --indent-guide-style: solid;\n --indent-guide-color: var(--l-color-border-interactive, light-dark(#d1d5db, #3a4048));\n --row-height: 1.75rem;\n --row-padding-inline: 0.25rem;\n --chevron-size: 1.125rem;\n --item-gap: 0.375rem;\n\n display: block;\n color: var(--l-color-text-primary, CanvasText);\n font-family: inherit;\n line-height: 1.5;\n}\n\n.tree {\n display: block;\n outline: none;\n}\n\n.tree:focus-visible {\n outline: 2px solid var(--l-focus-ring);\n outline-offset: 2px;\n border-radius: 0.375rem;\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 type { TreeItem } from '../tree-item/tree-item.js';\nimport hostStyles from '../../shared/styles/host.styles.js';\nimport rawStyles from './tree.css?inline';\n\nconst styles = unsafeCSS(rawStyles);\n\nexport type TreeSelection = 'single' | 'multiple' | 'leaf' | 'none';\n\n/**\n * A hierarchical tree view composed of `<l-tree-item>` children.\n *\n * The host carries `role=\"tree\"`, so give it an accessible name with\n * `aria-label` or `aria-labelledby` (e.g. `<l-tree aria-label=\"Files\">`).\n *\n * @slot - One or more `l-tree-item` elements.\n *\n * @csspart base - The root tree container.\n *\n * @cssproperty --indent-size - Horizontal indent per depth level. Default `1rem`.\n * @cssproperty --indent-guide-width - Thickness of the vertical guide line between a parent and its children. Default `1px`. Set to `0` to hide guides.\n * @cssproperty --indent-guide-style - Line style of the guide (`solid`, `dashed`, `dotted`, `double`…). Default `solid`.\n * @cssproperty --indent-guide-color - Color of the guide line.\n * @cssproperty --row-height - Minimum row height. Default `1.75rem`.\n * @cssproperty --row-padding-inline - Inner inline padding of the row; also drives the content slot left indent and the indent guide column. Default `0.25rem`.\n * @cssproperty --chevron-size - Size of the expand/collapse chevron box. Default `1.125rem`.\n * @cssproperty --item-gap - Horizontal gap between chevron, prefix, label and suffix on the row; also drives the content slot left indent. Default `0.375rem`.\n *\n * @event selection-change - Fired when the selected items change. Detail: `{ selection: TreeItem[] }`.\n *\n * @customElement l-tree\n */\nexport class Tree extends LuxenElement {\n static override styles = [hostStyles, styles];\n\n private _internals = this.attachInternals();\n private _mutationObserver?: MutationObserver;\n private _lastFocusedItem: TreeItem | null = null;\n\n /**\n * Selection behaviour:\n * - `single` (default): at most one item selected via `aria-selected`.\n * - `multiple`: any number of items selected. Checkboxes are rendered.\n * - `leaf`: only leaf items can be selected (single). Branches just toggle.\n * - `none`: purely navigable, no selection state.\n */\n @property({ reflect: true })\n accessor selection: TreeSelection = 'single';\n\n /**\n * When set with `selection=\"multiple\"`, parent and children selection are decoupled:\n * toggling a parent does NOT toggle its descendants and vice versa.\n * Without it, selection cascades both ways and branches may become indeterminate.\n */\n @property({ type: Boolean, reflect: true })\n accessor independent = false;\n\n override connectedCallback() {\n super.connectedCallback();\n this._internals.role = 'tree';\n // Mirror the role to a DOM attribute too. The ElementInternals role alone is\n // not `[role]`-selectable (CSS, querySelector, Cypress/Playwright CSS), which\n // silently breaks consumers migrating from libraries that expose an attribute\n // role. Respect an author-provided role if one is already set.\n if (!this.hasAttribute('role')) this.setAttribute('role', 'tree');\n this._mutationObserver = new MutationObserver(() => this._syncAll());\n this._mutationObserver.observe(this, { childList: true, subtree: true });\n this.addEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n\n // Defer sync to let light DOM upgrade.\n queueMicrotask(() => this._syncAll());\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._mutationObserver?.disconnect();\n this.removeEventListener('l-tree-item-toggle', this._onItemToggle as EventListener);\n }\n\n override updated(changed: PropertyValues<this>) {\n if (changed.has('selection')) {\n // Mirror to ElementInternals (a11y tree) and a content attribute, so\n // `[aria-multiselectable]` selectors keep matching — see tree-item `_aria`.\n const multiselectable = this.selection === 'multiple' ? 'true' : 'false';\n this._internals.ariaMultiSelectable = multiselectable;\n this.setAttribute('aria-multiselectable', multiselectable);\n }\n if (changed.has('selection') || changed.has('independent')) {\n this._syncAll();\n }\n }\n\n // --- Public API ---\n\n /** Returns all items in document (flat) order, including nested ones. */\n getAllItems({ includeDisabled = true } = {}): TreeItem[] {\n const tag = tagName('tree-item');\n return Array.from(this.querySelectorAll<TreeItem>(tag)).filter(\n (item) => includeDisabled || !item.disabled,\n );\n }\n\n /** Returns currently selected items. */\n getSelection(): TreeItem[] {\n return this.getAllItems().filter((i) => i.selected);\n }\n\n /** Expands every item that has children. */\n expandAll() {\n for (const item of this.getAllItems()) {\n if (!item.isLeaf()) item.expanded = true;\n }\n }\n\n /** Collapses every item. */\n collapseAll() {\n for (const item of this.getAllItems()) {\n item.expanded = false;\n }\n }\n\n // --- Sync / ARIA / depth / checkbox visibility ---\n\n private _syncAll() {\n // `_syncAll()` may run from `updated()` before `<l-tree-item>` is registered\n // (e.g. when the tree module is imported before tree-item, or in async chunks).\n // Force-upgrade any pending custom elements in our subtree, then bail and retry\n // once the registration completes if any item is still un-upgraded.\n customElements.upgrade(this);\n const itemTag = tagName('tree-item');\n const roots = this._rootItems();\n if (roots.some((r) => typeof r.getChildrenItems !== 'function')) {\n void customElements.whenDefined(itemTag).then(() => this._syncAll());\n return;\n }\n\n const showCheckbox = this.selection === 'multiple';\n this._syncLevel(roots, 0, showCheckbox);\n this._updateBranchStates();\n // Ensure at least one item is tabbable.\n this._ensureTabStop();\n }\n\n /**\n * Sync depth, checkbox visibility and ARIA position for a sibling group, then\n * recurse. `aria-level`/`aria-setsize`/`aria-posinset` let screen readers\n * announce \"level N, M of K\" — valuable here because `lazy` items mean the\n * full set isn't always in the DOM (see WAI-ARIA Tree View pattern).\n */\n private _syncLevel(items: TreeItem[], depth: number, showCheckbox: boolean) {\n const setSize = items.length;\n items.forEach((item, index) => {\n item.depth = depth;\n item.showCheckbox = showCheckbox && this._canShowCheckboxOn(item);\n item.setPosition(depth + 1, index + 1, setSize);\n this._syncLevel(item.getChildrenItems(), depth + 1, showCheckbox);\n });\n }\n\n private _canShowCheckboxOn(_item: TreeItem): boolean {\n if (this.selection !== 'multiple') return false;\n // In cascade mode, branches get a checkbox too so you can bulk-toggle children.\n // In leaf-only selection, hidden here because selection !== 'multiple'.\n return true;\n }\n\n private _rootItems(): TreeItem[] {\n const tag = tagName('tree-item').toUpperCase();\n return (Array.from(this.children) as TreeItem[]).filter((el) => el.tagName === tag);\n }\n\n private _ensureTabStop() {\n const items = this._visibleItems();\n if (items.length === 0) return;\n const hasTabStop = items.some((i) => i.tabIndex === 0);\n if (!hasTabStop) {\n for (const i of items) i.tabIndex = -1;\n items[0].tabIndex = 0;\n }\n }\n\n /** Items currently visible (parent chain all expanded). */\n private _visibleItems(): TreeItem[] {\n const out: TreeItem[] = [];\n const walk = (items: TreeItem[]) => {\n for (const i of items) {\n out.push(i);\n if (i.expanded) walk(i.getChildrenItems());\n }\n };\n walk(this._rootItems());\n return out;\n }\n\n // --- Selection handling ---\n\n private _onItemToggle = (event: CustomEvent<{ item: TreeItem; checked: boolean }>) => {\n const { item, checked } = event.detail;\n this._selectItem(item, checked);\n };\n\n private _handleRowActivate(item: TreeItem) {\n if (item.disabled) return;\n\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n // Mirror the row-click behaviour: activating a branch also toggles it,\n // so keyboard users expand lazy branches (and trigger their fetch) too.\n if (!item.isLeaf()) item.toggle();\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n }\n\n private _setSingleSelection(item: TreeItem) {\n for (const i of this.getAllItems()) {\n if (i !== item && i.selected) i.selected = false;\n }\n item.selected = true;\n this._emitSelectionChange();\n }\n\n private _selectItem(item: TreeItem, value: boolean) {\n if (item.disabled) return;\n item.selected = value;\n\n if (this.selection === 'multiple' && !this.independent) {\n // Cascade DOWN: toggling a branch toggles all descendants.\n this._setSubtreeSelection(item, value);\n }\n\n item.indeterminate = false;\n this._updateBranchStates();\n this._emitSelectionChange();\n }\n\n private _setSubtreeSelection(item: TreeItem, value: boolean) {\n for (const child of item.getChildrenItems()) {\n if (child.disabled) continue;\n child.selected = value;\n child.indeterminate = false;\n this._setSubtreeSelection(child, value);\n }\n }\n\n /** Propagate child state UP to parents (indeterminate / auto-checked). */\n private _updateBranchStates() {\n if (this.selection !== 'multiple' || this.independent) {\n // In independent or non-multiple modes, clear any indeterminate flags.\n for (const i of this.getAllItems()) i.indeterminate = false;\n return;\n }\n\n const recompute = (item: TreeItem): { all: boolean; any: boolean } => {\n const children = item.getChildrenItems({ includeDisabled: false });\n if (children.length === 0) {\n return { all: item.selected, any: item.selected };\n }\n\n let all = true;\n let any = false;\n for (const child of children) {\n const state = recompute(child);\n if (!state.all) all = false;\n if (state.any) any = true;\n }\n\n item.selected = all;\n item.indeterminate = !all && any;\n return { all: all && (item.getChildrenItems().length > 0 ? all : item.selected), any };\n };\n\n for (const root of this._rootItems()) recompute(root);\n }\n\n private _emitSelectionChange() {\n this.emit('selection-change', { detail: { selection: this.getSelection() } });\n }\n\n // --- Keyboard / focus ---\n\n private _onClick = (event: MouseEvent) => {\n const item = this._itemFromEvent(event);\n if (!item || item.disabled) return;\n\n const path = event.composedPath();\n const onCheckbox = path.some((n) => n instanceof HTMLInputElement && n.type === 'checkbox');\n if (onCheckbox) return; // handled via change event\n\n // Clicks on consumer-provided interactive elements (buttons, links, form\n // controls, menu items…) must not toggle the row — the consumer owns that\n // interaction. Works regardless of which slot the element was placed in.\n const INTERACTIVE_TAGS = new Set(['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA']);\n const INTERACTIVE_ROLES = new Set(['button', 'link', 'menuitem', 'menuitemcheckbox']);\n const onInteractive = path.some((n) => {\n if (!(n instanceof HTMLElement) || n === item) return false;\n if (n.getAttribute?.('part') === 'expand-button') return false;\n if (n instanceof HTMLInputElement && n.type === 'checkbox') return false;\n if (INTERACTIVE_TAGS.has(n.tagName)) return true;\n const role = n.getAttribute?.('role');\n return role !== null && INTERACTIVE_ROLES.has(role);\n });\n if (onInteractive) return;\n\n const onExpand = path.some(\n (n) => n instanceof HTMLElement && n.getAttribute?.('part') === 'expand-button',\n );\n\n this._focusItem(item);\n\n if (onExpand) {\n item.toggle();\n return;\n }\n\n // Row click (label area): mode-dependent behaviour.\n switch (this.selection) {\n case 'single':\n this._setSingleSelection(item);\n if (!item.isLeaf()) item.toggle();\n break;\n case 'leaf':\n if (item.isLeaf()) this._setSingleSelection(item);\n else item.toggle();\n break;\n case 'multiple':\n // The whole row acts like a <label> for the checkbox: clicking anywhere\n // on it toggles selection. Use the chevron to expand/collapse branches.\n this._selectItem(item, !item.selected);\n break;\n case 'none':\n item.toggle();\n break;\n }\n };\n\n private _itemFromEvent(event: Event): TreeItem | null {\n const tag = tagName('tree-item');\n const path = event.composedPath();\n for (const node of path) {\n if (node instanceof HTMLElement && node.matches?.(tag)) {\n return node as TreeItem;\n }\n }\n return null;\n }\n\n private _focusItem(item: TreeItem) {\n const visible = this._visibleItems();\n for (const i of visible) i.tabIndex = -1;\n item.tabIndex = 0;\n item.focus();\n this._lastFocusedItem = item;\n }\n\n private _onFocusIn = (event: FocusEvent) => {\n const target = event.target;\n if (target instanceof HTMLElement) {\n const item = target.closest<TreeItem>(tagName('tree-item'));\n if (item) this._lastFocusedItem = item;\n }\n };\n\n private _onKeyDown = (event: KeyboardEvent) => {\n const current = this._lastFocusedItem ?? this._visibleItems()[0];\n if (!current) return;\n\n const visible = this._visibleItems();\n const index = visible.indexOf(current);\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault();\n const next = visible[Math.min(index + 1, visible.length - 1)];\n if (next) this._focusItem(next);\n break;\n }\n case 'ArrowUp': {\n event.preventDefault();\n const prev = visible[Math.max(index - 1, 0)];\n if (prev) this._focusItem(prev);\n break;\n }\n case 'ArrowRight': {\n event.preventDefault();\n if (!current.isLeaf() && !current.expanded) {\n current.expanded = true;\n } else if (current.expanded) {\n const first = current.getChildrenItems()[0];\n if (first) this._focusItem(first);\n }\n break;\n }\n case 'ArrowLeft': {\n event.preventDefault();\n if (current.expanded && !current.isLeaf()) {\n current.expanded = false;\n } else {\n const parent = current.parentElement?.closest(tagName('tree-item')) as TreeItem | null;\n if (parent) this._focusItem(parent);\n }\n break;\n }\n case 'Home': {\n event.preventDefault();\n if (visible[0]) this._focusItem(visible[0]);\n break;\n }\n case 'End': {\n event.preventDefault();\n const last = visible[visible.length - 1];\n if (last) this._focusItem(last);\n break;\n }\n case 'Enter':\n case ' ': {\n event.preventDefault();\n this._handleRowActivate(current);\n break;\n }\n case '*': {\n event.preventDefault();\n // Expand all siblings of the current item.\n const siblings = (\n current.parentElement ? (Array.from(current.parentElement.children) as TreeItem[]) : []\n ).filter((el) => el.tagName === tagName('tree-item').toUpperCase());\n for (const sib of siblings) {\n if (!sib.isLeaf()) sib.expanded = true;\n }\n break;\n }\n }\n };\n\n override render() {\n return html`\n <div\n class=\"tree\"\n part=\"base\"\n @click=${this._onClick}\n @keydown=${this._onKeyDown}\n @focusin=${this._onFocusIn}\n >\n <slot></slot>\n </div>\n `;\n }\n}\n"],"mappings":"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"}
|
|
@@ -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.
|
|
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;
|
|
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:
|
|
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{overflow-clip-margin:.375rem;text-overflow:ellipsis;white-space:nowrap;flex:1;align-items:center;gap:.375rem;min-inline-size:0;display:flex;overflow:clip}.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 /* `clip` (not `hidden`) so the label still truncates long text to an ellipsis\n while `overflow-clip-margin` lets a slotted interactive control's hover/focus\n decoration (a row-action button, an `<l-dropdown>` trigger) bleed past the\n row-height label box instead of being cut on its top/bottom/left edges. The\n margin is kept small so the bleed stays imperceptible for text. */\n overflow: clip;\n overflow-clip-margin: 0.375rem;\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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"}
|