@xui/tree 2.0.0-alpha.19 → 2.0.0-alpha.21
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/fesm2022/xui-tree.mjs
CHANGED
|
@@ -16,16 +16,22 @@ import { XuiIcon } from '@xui/icon';
|
|
|
16
16
|
class XuiTree {
|
|
17
17
|
host = inject(ElementRef);
|
|
18
18
|
direction = injectXDirection();
|
|
19
|
+
/** Extra classes, merged into the component's own rather than replacing them. */
|
|
19
20
|
class = input('', /* @ts-ignore */
|
|
20
21
|
...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
|
|
22
|
+
/** Accessible name for the tree — what it is a tree of. */
|
|
21
23
|
ariaLabel = input(null, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
24
|
+
/** The roots. Each node carries its own `children`, so the whole tree is one nested array. */
|
|
22
25
|
nodes = input([], /* @ts-ignore */
|
|
23
26
|
...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
|
|
24
27
|
/** The selected node id. Two-way bindable with `[(selectedId)]`. */
|
|
25
28
|
selectedId = model(null, /* @ts-ignore */
|
|
26
29
|
...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
30
|
+
/** Emits the node that was activated, by click or by Enter. */
|
|
27
31
|
nodeClick = output();
|
|
32
|
+
/** Emits the node that was just expanded. */
|
|
28
33
|
nodeExpanded = output();
|
|
34
|
+
/** Emits the node that was just collapsed. */
|
|
29
35
|
nodeCollapsed = output();
|
|
30
36
|
expanded = signal(new Set(), /* @ts-ignore */
|
|
31
37
|
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xui-tree.mjs","sources":["../../../../../../libs/ui/tree/xui/src/lib/tree.ts","../../../../../../libs/ui/tree/xui/src/index.ts","../../../../../../libs/ui/tree/xui/src/xui-tree.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n ElementRef,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n ViewEncapsulation\n} from '@angular/core';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { matChevronRightRound } from '@ng-icons/material-icons/round';\nimport { xui } from '@xui/core';\nimport { injectXDirection } from '@xui/core/a11y';\nimport {\n collectExpandedIds,\n flattenVisibleTree,\n isTreeNodeExpandable,\n toggleExpandedId,\n treeKeyAction,\n type XFlatTreeNode\n} from '@xui/core/tree';\nimport { XuiIcon } from '@xui/icon';\nimport type { ClassValue } from 'clsx';\nimport type { XuiTreeNode } from './tree.types';\n\n/**\n * A hierarchical tree of {@link XuiTreeNode}s with expand/collapse carets, single\n * selection, optional icons and secondary labels, and full keyboard support\n * (Up/Down move, Right/Left expand/collapse or step in/out, Enter/Space select).\n */\n@Component({\n selector: 'xui-tree',\n imports: [NgTemplateOutlet, NgIcon, XuiIcon],\n template: `\n <ng-template #nodeTpl let-node let-level=\"level\">\n <li\n role=\"treeitem\"\n [attr.aria-expanded]=\"isExpandable(node) ? isExpanded(node) : null\"\n [attr.aria-selected]=\"node.id === selectedId()\"\n >\n <div\n [class]=\"rowClass(node)\"\n [style.padding-inline-start.rem]=\"0.5 + level * 1.25\"\n [attr.data-node-id]=\"node.id\"\n [tabindex]=\"node.id === focusedId() ? 0 : -1\"\n (click)=\"onRowClick(node)\"\n (keydown)=\"onKeydown($event, node)\"\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center\">\n @if (isExpandable(node)) {\n <ng-icon\n xui\n name=\"matChevronRightRound\"\n size=\"sm\"\n [class]=\"caretClass(isExpanded(node))\"\n (click)=\"toggle(node); $event.stopPropagation()\"\n />\n }\n </span>\n\n @if (node.icon) {\n <ng-icon xui [name]=\"node.icon\" size=\"sm\" class=\"text-foreground-muted shrink-0\" />\n }\n\n <span class=\"flex-1 truncate\">{{ node.label }}</span>\n\n @if (node.secondaryLabel) {\n <span class=\"text-foreground-muted ms-2 shrink-0 text-xs\">{{ node.secondaryLabel }}</span>\n }\n </div>\n\n @if (isExpandable(node) && isExpanded(node)) {\n <ul role=\"group\" class=\"m-0 list-none p-0\">\n @for (child of node.children ?? []; track child.id) {\n <ng-container\n [ngTemplateOutlet]=\"nodeTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child, level: level + 1 }\"\n />\n }\n </ul>\n }\n </li>\n </ng-template>\n\n <ul role=\"tree\" class=\"m-0 list-none p-0\" [attr.aria-label]=\"ariaLabel()\">\n @for (node of nodes(); track node.id) {\n <ng-container [ngTemplateOutlet]=\"nodeTpl\" [ngTemplateOutletContext]=\"{ $implicit: node, level: 0 }\" />\n }\n </ul>\n `,\n host: {\n '[class]': 'computedClass()'\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n viewProviders: [provideIcons({ matChevronRightRound })]\n})\nexport class XuiTree {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly direction = injectXDirection();\n\n readonly class = input<ClassValue>('');\n readonly ariaLabel = input<string | null>(null, { alias: 'aria-label' });\n\n readonly nodes = input<XuiTreeNode[]>([]);\n\n /** The selected node id. Two-way bindable with `[(selectedId)]`. */\n readonly selectedId = model<string | number | null>(null);\n\n readonly nodeClick = output<XuiTreeNode>();\n readonly nodeExpanded = output<XuiTreeNode>();\n readonly nodeCollapsed = output<XuiTreeNode>();\n\n private readonly expanded = signal(new Set<string | number>());\n protected readonly focusedId = signal<string | number | null>(null);\n\n protected readonly computedClass = computed(() => xui('block text-sm select-none', this.class()));\n\n /** Every node flattened in visual (depth-first) order, respecting expansion. */\n private readonly visible = computed<XFlatTreeNode<XuiTreeNode>[]>(() =>\n flattenVisibleTree(this.nodes(), this.expanded())\n );\n\n constructor() {\n // Seed expansion from each node's `isExpanded` flag, and the initial focus.\n effect(() => {\n const nodes = this.nodes();\n untracked(() => {\n this.expanded.set(collectExpandedIds(nodes));\n if (this.focusedId() == null && nodes.length) {\n this.focusedId.set(nodes[0].id);\n }\n });\n });\n }\n\n protected isExpandable(node: XuiTreeNode): boolean {\n return isTreeNodeExpandable(node);\n }\n\n protected isExpanded(node: XuiTreeNode): boolean {\n return this.expanded().has(node.id);\n }\n\n /**\n * The expand caret. It is drawn pointing right, so a collapsed node has to be\n * flipped in RTL to keep pointing \"further in\"; expanded always points down.\n */\n protected caretClass(expanded: boolean): string {\n return xui('transition-transform', expanded ? 'rotate-90' : this.direction() === 'rtl' && 'rotate-180');\n }\n\n protected rowClass(node: XuiTreeNode): string {\n return xui(\n 'flex items-center gap-1.5 rounded py-1 pe-2 transition-colors',\n node.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:bg-surface-inset/60',\n 'focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-focus',\n node.id === this.selectedId() && 'bg-primary/10 text-primary'\n );\n }\n\n protected toggle(node: XuiTreeNode): void {\n if (node.disabled || !this.isExpandable(node)) {\n return;\n }\n\n const wasExpanded = this.isExpanded(node);\n this.expanded.update(set => toggleExpandedId(set, node.id));\n (wasExpanded ? this.nodeCollapsed : this.nodeExpanded).emit(node);\n }\n\n protected onRowClick(node: XuiTreeNode): void {\n if (node.disabled) {\n return;\n }\n\n this.focusedId.set(node.id);\n this.selectedId.set(node.id);\n this.nodeClick.emit(node);\n }\n\n protected onKeydown(event: KeyboardEvent, node: XuiTreeNode): void {\n const flat = this.visible();\n const index = flat.findIndex(f => f.node.id === node.id);\n const action = treeKeyAction(event.key, this.direction(), flat, index, this.expanded());\n\n if (!action) {\n return;\n }\n event.preventDefault();\n\n switch (action.kind) {\n case 'expand':\n case 'collapse':\n this.toggle(action.node);\n break;\n case 'focus':\n this.focusIndex(action.index);\n break;\n case 'select':\n this.onRowClick(action.node);\n break;\n }\n }\n\n private focusIndex(index: number): void {\n const target = this.visible()[index];\n if (!target) {\n return;\n }\n\n this.focusedId.set(target.node.id);\n // Roving tabindex updates on the next tick; move DOM focus to the row.\n queueMicrotask(() => {\n this.host.nativeElement.querySelector<HTMLElement>(`[data-node-id=\"${target.node.id}\"]`)?.focus();\n });\n }\n}\n","import { XuiTree } from './lib/tree';\n\nexport * from './lib/tree';\nexport * from './lib/tree.types';\n\nexport const XuiTreeImports = [XuiTree] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"xui-tree.mjs","sources":["../../../../../../libs/ui/tree/xui/src/lib/tree.ts","../../../../../../libs/ui/tree/xui/src/index.ts","../../../../../../libs/ui/tree/xui/src/xui-tree.ts"],"sourcesContent":["import { NgTemplateOutlet } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n computed,\n effect,\n ElementRef,\n inject,\n input,\n model,\n output,\n signal,\n untracked,\n ViewEncapsulation\n} from '@angular/core';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { matChevronRightRound } from '@ng-icons/material-icons/round';\nimport { xui } from '@xui/core';\nimport { injectXDirection } from '@xui/core/a11y';\nimport {\n collectExpandedIds,\n flattenVisibleTree,\n isTreeNodeExpandable,\n toggleExpandedId,\n treeKeyAction,\n type XFlatTreeNode\n} from '@xui/core/tree';\nimport { XuiIcon } from '@xui/icon';\nimport type { ClassValue } from 'clsx';\nimport type { XuiTreeNode } from './tree.types';\n\n/**\n * A hierarchical tree of {@link XuiTreeNode}s with expand/collapse carets, single\n * selection, optional icons and secondary labels, and full keyboard support\n * (Up/Down move, Right/Left expand/collapse or step in/out, Enter/Space select).\n */\n@Component({\n selector: 'xui-tree',\n imports: [NgTemplateOutlet, NgIcon, XuiIcon],\n template: `\n <ng-template #nodeTpl let-node let-level=\"level\">\n <li\n role=\"treeitem\"\n [attr.aria-expanded]=\"isExpandable(node) ? isExpanded(node) : null\"\n [attr.aria-selected]=\"node.id === selectedId()\"\n >\n <div\n [class]=\"rowClass(node)\"\n [style.padding-inline-start.rem]=\"0.5 + level * 1.25\"\n [attr.data-node-id]=\"node.id\"\n [tabindex]=\"node.id === focusedId() ? 0 : -1\"\n (click)=\"onRowClick(node)\"\n (keydown)=\"onKeydown($event, node)\"\n >\n <span class=\"flex size-5 shrink-0 items-center justify-center\">\n @if (isExpandable(node)) {\n <ng-icon\n xui\n name=\"matChevronRightRound\"\n size=\"sm\"\n [class]=\"caretClass(isExpanded(node))\"\n (click)=\"toggle(node); $event.stopPropagation()\"\n />\n }\n </span>\n\n @if (node.icon) {\n <ng-icon xui [name]=\"node.icon\" size=\"sm\" class=\"text-foreground-muted shrink-0\" />\n }\n\n <span class=\"flex-1 truncate\">{{ node.label }}</span>\n\n @if (node.secondaryLabel) {\n <span class=\"text-foreground-muted ms-2 shrink-0 text-xs\">{{ node.secondaryLabel }}</span>\n }\n </div>\n\n @if (isExpandable(node) && isExpanded(node)) {\n <ul role=\"group\" class=\"m-0 list-none p-0\">\n @for (child of node.children ?? []; track child.id) {\n <ng-container\n [ngTemplateOutlet]=\"nodeTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: child, level: level + 1 }\"\n />\n }\n </ul>\n }\n </li>\n </ng-template>\n\n <ul role=\"tree\" class=\"m-0 list-none p-0\" [attr.aria-label]=\"ariaLabel()\">\n @for (node of nodes(); track node.id) {\n <ng-container [ngTemplateOutlet]=\"nodeTpl\" [ngTemplateOutletContext]=\"{ $implicit: node, level: 0 }\" />\n }\n </ul>\n `,\n host: {\n '[class]': 'computedClass()'\n },\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n viewProviders: [provideIcons({ matChevronRightRound })]\n})\nexport class XuiTree {\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n protected readonly direction = injectXDirection();\n\n /** Extra classes, merged into the component's own rather than replacing them. */\n readonly class = input<ClassValue>('');\n /** Accessible name for the tree — what it is a tree of. */\n readonly ariaLabel = input<string | null>(null, { alias: 'aria-label' });\n\n /** The roots. Each node carries its own `children`, so the whole tree is one nested array. */\n readonly nodes = input<XuiTreeNode[]>([]);\n\n /** The selected node id. Two-way bindable with `[(selectedId)]`. */\n readonly selectedId = model<string | number | null>(null);\n\n /** Emits the node that was activated, by click or by Enter. */\n readonly nodeClick = output<XuiTreeNode>();\n /** Emits the node that was just expanded. */\n readonly nodeExpanded = output<XuiTreeNode>();\n /** Emits the node that was just collapsed. */\n readonly nodeCollapsed = output<XuiTreeNode>();\n\n private readonly expanded = signal(new Set<string | number>());\n protected readonly focusedId = signal<string | number | null>(null);\n\n protected readonly computedClass = computed(() => xui('block text-sm select-none', this.class()));\n\n /** Every node flattened in visual (depth-first) order, respecting expansion. */\n private readonly visible = computed<XFlatTreeNode<XuiTreeNode>[]>(() =>\n flattenVisibleTree(this.nodes(), this.expanded())\n );\n\n constructor() {\n // Seed expansion from each node's `isExpanded` flag, and the initial focus.\n effect(() => {\n const nodes = this.nodes();\n untracked(() => {\n this.expanded.set(collectExpandedIds(nodes));\n if (this.focusedId() == null && nodes.length) {\n this.focusedId.set(nodes[0].id);\n }\n });\n });\n }\n\n protected isExpandable(node: XuiTreeNode): boolean {\n return isTreeNodeExpandable(node);\n }\n\n protected isExpanded(node: XuiTreeNode): boolean {\n return this.expanded().has(node.id);\n }\n\n /**\n * The expand caret. It is drawn pointing right, so a collapsed node has to be\n * flipped in RTL to keep pointing \"further in\"; expanded always points down.\n */\n protected caretClass(expanded: boolean): string {\n return xui('transition-transform', expanded ? 'rotate-90' : this.direction() === 'rtl' && 'rotate-180');\n }\n\n protected rowClass(node: XuiTreeNode): string {\n return xui(\n 'flex items-center gap-1.5 rounded py-1 pe-2 transition-colors',\n node.disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:bg-surface-inset/60',\n 'focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-focus',\n node.id === this.selectedId() && 'bg-primary/10 text-primary'\n );\n }\n\n protected toggle(node: XuiTreeNode): void {\n if (node.disabled || !this.isExpandable(node)) {\n return;\n }\n\n const wasExpanded = this.isExpanded(node);\n this.expanded.update(set => toggleExpandedId(set, node.id));\n (wasExpanded ? this.nodeCollapsed : this.nodeExpanded).emit(node);\n }\n\n protected onRowClick(node: XuiTreeNode): void {\n if (node.disabled) {\n return;\n }\n\n this.focusedId.set(node.id);\n this.selectedId.set(node.id);\n this.nodeClick.emit(node);\n }\n\n protected onKeydown(event: KeyboardEvent, node: XuiTreeNode): void {\n const flat = this.visible();\n const index = flat.findIndex(f => f.node.id === node.id);\n const action = treeKeyAction(event.key, this.direction(), flat, index, this.expanded());\n\n if (!action) {\n return;\n }\n event.preventDefault();\n\n switch (action.kind) {\n case 'expand':\n case 'collapse':\n this.toggle(action.node);\n break;\n case 'focus':\n this.focusIndex(action.index);\n break;\n case 'select':\n this.onRowClick(action.node);\n break;\n }\n }\n\n private focusIndex(index: number): void {\n const target = this.visible()[index];\n if (!target) {\n return;\n }\n\n this.focusedId.set(target.node.id);\n // Roving tabindex updates on the next tick; move DOM focus to the row.\n queueMicrotask(() => {\n this.host.nativeElement.querySelector<HTMLElement>(`[data-node-id=\"${target.node.id}\"]`)?.focus();\n });\n }\n}\n","import { XuiTree } from './lib/tree';\n\nexport * from './lib/tree';\nexport * from './lib/tree.types';\n\nexport const XuiTreeImports = [XuiTree] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xui/tree",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.21",
|
|
4
4
|
"description": "Modern Angular 22 UI Library based on TailwindCSS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"@angular/core": "22",
|
|
41
41
|
"@ng-icons/core": "34",
|
|
42
42
|
"@ng-icons/material-icons": "34",
|
|
43
|
-
"@xui/core": "2.0.0-alpha.
|
|
44
|
-
"@xui/icon": "2.0.0-alpha.
|
|
43
|
+
"@xui/core": "2.0.0-alpha.21",
|
|
44
|
+
"@xui/icon": "2.0.0-alpha.21",
|
|
45
45
|
"clsx": "^2.1.1"
|
|
46
46
|
},
|
|
47
47
|
"publishConfig": {
|
package/types/xui-tree.d.ts
CHANGED
|
@@ -25,13 +25,19 @@ interface XuiTreeNode extends XTreeNode {
|
|
|
25
25
|
declare class XuiTree {
|
|
26
26
|
private readonly host;
|
|
27
27
|
protected readonly direction: _angular_core.Signal<_xui_core_a11y.XDirection>;
|
|
28
|
+
/** Extra classes, merged into the component's own rather than replacing them. */
|
|
28
29
|
readonly class: _angular_core.InputSignal<ClassValue>;
|
|
30
|
+
/** Accessible name for the tree — what it is a tree of. */
|
|
29
31
|
readonly ariaLabel: _angular_core.InputSignal<string | null>;
|
|
32
|
+
/** The roots. Each node carries its own `children`, so the whole tree is one nested array. */
|
|
30
33
|
readonly nodes: _angular_core.InputSignal<XuiTreeNode[]>;
|
|
31
34
|
/** The selected node id. Two-way bindable with `[(selectedId)]`. */
|
|
32
35
|
readonly selectedId: _angular_core.ModelSignal<string | number | null>;
|
|
36
|
+
/** Emits the node that was activated, by click or by Enter. */
|
|
33
37
|
readonly nodeClick: _angular_core.OutputEmitterRef<XuiTreeNode>;
|
|
38
|
+
/** Emits the node that was just expanded. */
|
|
34
39
|
readonly nodeExpanded: _angular_core.OutputEmitterRef<XuiTreeNode>;
|
|
40
|
+
/** Emits the node that was just collapsed. */
|
|
35
41
|
readonly nodeCollapsed: _angular_core.OutputEmitterRef<XuiTreeNode>;
|
|
36
42
|
private readonly expanded;
|
|
37
43
|
protected readonly focusedId: _angular_core.WritableSignal<string | number | null>;
|