@styloviz/tree-view 0.1.1
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/LICENSE +21 -0
- package/README.md +53 -0
- package/fesm2022/styloviz-tree-view.mjs +327 -0
- package/fesm2022/styloviz-tree-view.mjs.map +1 -0
- package/package.json +48 -0
- package/types/styloviz-tree-view.d.ts +83 -0
- package/types/styloviz-tree-view.d.ts.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 StyloViz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# @styloviz/tree-view
|
|
2
|
+
|
|
3
|
+
> 8 premium variants: collapsible tree, file explorer, tri-state checkboxes, org chart, tree table, searchable, drag & drop reordering and lazy load.
|
|
4
|
+
|
|
5
|
+
Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @styloviz/core @styloviz/tree-view
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { SvTreeViewComponent } from '@styloviz/tree-view';
|
|
19
|
+
|
|
20
|
+
@Component({
|
|
21
|
+
standalone: true,
|
|
22
|
+
imports: [SvTreeViewComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<sv-tree-view [nodes]="nodes" />
|
|
25
|
+
`,
|
|
26
|
+
})
|
|
27
|
+
export class DemoComponent {}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Inputs
|
|
31
|
+
|
|
32
|
+
| Input | Type | Default | Description |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| `nodes` | `TreeNode[]` | `[]` | Tree node data (nested children, optional icons). |
|
|
35
|
+
| `selectable` | `boolean` | `true` | Enable single-row selection highlight. |
|
|
36
|
+
| `showLines` | `boolean` | `true` | Show vertical connector guide lines. |
|
|
37
|
+
|
|
38
|
+
## Outputs
|
|
39
|
+
|
|
40
|
+
| Output | Type | Description |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| `nodeClick` | `TreeNodeClickEvent` | — |
|
|
43
|
+
| `nodeExpand` | `TreeNode` | — |
|
|
44
|
+
| `nodeCollapse` | `TreeNode` | — |
|
|
45
|
+
| `nodeSelect` | `TreeNode` | — |
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
Full API reference and live demos: https://styloviz.dev/docs/tree-view
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, signal, inject, ElementRef, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* SvTreeViewComponent (Free) — Collapsible single-select tree.
|
|
7
|
+
*
|
|
8
|
+
* For the full 8-variant suite (file explorer, checkbox tri-state,
|
|
9
|
+
* org chart, tree table, searchable, draggable, lazy-load) upgrade to
|
|
10
|
+
* `@styloviz-pro/tree-view`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```html
|
|
14
|
+
* <sv-tree-view [nodes]="nodes" (nodeClick)="onNodeClick($event)" />
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
class SvTreeViewComponent {
|
|
18
|
+
/** Tree node data (nested children, optional icons). */
|
|
19
|
+
nodes = input([], ...(ngDevMode ? [{ debugName: "nodes" }] : /* istanbul ignore next */ []));
|
|
20
|
+
/** Enable single-row selection highlight. @default true */
|
|
21
|
+
selectable = input(true, ...(ngDevMode ? [{ debugName: "selectable" }] : /* istanbul ignore next */ []));
|
|
22
|
+
/** Show vertical connector guide lines. @default true */
|
|
23
|
+
showLines = input(true, ...(ngDevMode ? [{ debugName: "showLines" }] : /* istanbul ignore next */ []));
|
|
24
|
+
nodeClick = output();
|
|
25
|
+
nodeExpand = output();
|
|
26
|
+
nodeCollapse = output();
|
|
27
|
+
nodeSelect = output();
|
|
28
|
+
expandedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedIds" }] : /* istanbul ignore next */ []));
|
|
29
|
+
selectedId = signal(null, ...(ngDevMode ? [{ debugName: "selectedId" }] : /* istanbul ignore next */ []));
|
|
30
|
+
/** The treeitem currently in the tab order (roving tabindex). */
|
|
31
|
+
activeId = signal(null, ...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
32
|
+
_nodes = signal([], ...(ngDevMode ? [{ debugName: "_nodes" }] : /* istanbul ignore next */ []));
|
|
33
|
+
host = inject(ElementRef);
|
|
34
|
+
visibleNodes = computed(() => this._flatten(this._nodes(), 0, this.expandedIds()), ...(ngDevMode ? [{ debugName: "visibleNodes" }] : /* istanbul ignore next */ []));
|
|
35
|
+
ngOnInit() {
|
|
36
|
+
this._nodes.set(this.nodes());
|
|
37
|
+
const ids = new Set();
|
|
38
|
+
this._collectExpanded(this.nodes(), ids);
|
|
39
|
+
this.expandedIds.set(ids);
|
|
40
|
+
// Seed the roving tabindex on the first node so the tree is tab-reachable.
|
|
41
|
+
this.activeId.set(this.visibleNodes()[0]?.node.id ?? null);
|
|
42
|
+
}
|
|
43
|
+
toggle(node) {
|
|
44
|
+
if (node.disabled)
|
|
45
|
+
return;
|
|
46
|
+
const ids = new Set(this.expandedIds());
|
|
47
|
+
if (ids.has(node.id)) {
|
|
48
|
+
ids.delete(node.id);
|
|
49
|
+
this.nodeCollapse.emit(node);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
ids.add(node.id);
|
|
53
|
+
this.nodeExpand.emit(node);
|
|
54
|
+
}
|
|
55
|
+
this.expandedIds.set(ids);
|
|
56
|
+
}
|
|
57
|
+
select(node, depth) {
|
|
58
|
+
if (node.disabled || !this.selectable())
|
|
59
|
+
return;
|
|
60
|
+
this.selectedId.set(node.id);
|
|
61
|
+
this.nodeSelect.emit(node);
|
|
62
|
+
this.nodeClick.emit({ node, depth });
|
|
63
|
+
}
|
|
64
|
+
isSelected(id) { return this.selectedId() === id; }
|
|
65
|
+
// ── Keyboard navigation (WAI-ARIA tree) ─────────────────────────────────────
|
|
66
|
+
/** Roving tabindex: only the active treeitem is in the tab order. */
|
|
67
|
+
treeTabIndex(id) {
|
|
68
|
+
const active = this.activeId() ?? this.visibleNodes()[0]?.node.id ?? null;
|
|
69
|
+
return id === active ? 0 : -1;
|
|
70
|
+
}
|
|
71
|
+
setActive(id) { this.activeId.set(id); }
|
|
72
|
+
/**
|
|
73
|
+
* Arrow / Home / End / Enter / Space handling per the WAI-ARIA tree pattern.
|
|
74
|
+
* Arrow Up/Down move between visible rows; Right expands or steps into a
|
|
75
|
+
* child; Left collapses or steps to the parent; Enter/Space selects.
|
|
76
|
+
*/
|
|
77
|
+
onKeydown(event, fn, index) {
|
|
78
|
+
const list = this.visibleNodes();
|
|
79
|
+
switch (event.key) {
|
|
80
|
+
case 'ArrowDown':
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
this._focusStep(list, index, +1);
|
|
83
|
+
break;
|
|
84
|
+
case 'ArrowUp':
|
|
85
|
+
event.preventDefault();
|
|
86
|
+
this._focusStep(list, index, -1);
|
|
87
|
+
break;
|
|
88
|
+
case 'Home':
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
this._focusFrom(list, 0, +1);
|
|
91
|
+
break;
|
|
92
|
+
case 'End':
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
this._focusFrom(list, list.length - 1, -1);
|
|
95
|
+
break;
|
|
96
|
+
case 'ArrowRight':
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
if (fn.hasChildren && !fn.isExpanded) {
|
|
99
|
+
this.toggle(fn.node);
|
|
100
|
+
}
|
|
101
|
+
else if (fn.hasChildren && fn.isExpanded) {
|
|
102
|
+
this._focusStep(list, index, +1);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
case 'ArrowLeft':
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
if (fn.hasChildren && fn.isExpanded) {
|
|
108
|
+
this.toggle(fn.node);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this._focusParent(list, index, fn.depth);
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'Enter':
|
|
115
|
+
case ' ':
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
this.select(fn.node, fn.depth);
|
|
118
|
+
this.setActive(fn.node.id);
|
|
119
|
+
break;
|
|
120
|
+
default:
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Move focus by `dir` steps from `from`, skipping disabled rows. */
|
|
125
|
+
_focusStep(list, from, dir) {
|
|
126
|
+
for (let i = from + dir; i >= 0 && i < list.length; i += dir) {
|
|
127
|
+
if (!list[i].node.disabled) {
|
|
128
|
+
this._focus(list[i].node.id);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** From `start`, find the first enabled row in direction `dir` and focus it. */
|
|
134
|
+
_focusFrom(list, start, dir) {
|
|
135
|
+
for (let i = start; i >= 0 && i < list.length; i += dir) {
|
|
136
|
+
if (!list[i].node.disabled) {
|
|
137
|
+
this._focus(list[i].node.id);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Focus the parent row (nearest previous row with a smaller depth). */
|
|
143
|
+
_focusParent(list, index, depth) {
|
|
144
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
145
|
+
if (list[i].depth < depth) {
|
|
146
|
+
this._focus(list[i].node.id);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Set the active id and move DOM focus to that row (after any re-render). */
|
|
152
|
+
_focus(id) {
|
|
153
|
+
this.setActive(id);
|
|
154
|
+
queueMicrotask(() => {
|
|
155
|
+
const rows = this.host.nativeElement.querySelectorAll('[data-tree-id]');
|
|
156
|
+
rows.forEach(el => { if (el.dataset['treeId'] === id)
|
|
157
|
+
el.focus(); });
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
_flatten(nodes, depth, expanded) {
|
|
161
|
+
const result = [];
|
|
162
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
163
|
+
const node = nodes[i];
|
|
164
|
+
const hasChildren = !!node.children?.length;
|
|
165
|
+
const isExp = expanded.has(node.id);
|
|
166
|
+
result.push({ node, depth, isLast: i === nodes.length - 1, hasChildren, isExpanded: isExp });
|
|
167
|
+
if (isExp && node.children?.length) {
|
|
168
|
+
result.push(...this._flatten(node.children, depth + 1, expanded));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
_collectExpanded(nodes, ids) {
|
|
174
|
+
for (const n of nodes) {
|
|
175
|
+
if (n.expanded)
|
|
176
|
+
ids.add(n.id);
|
|
177
|
+
if (n.children)
|
|
178
|
+
this._collectExpanded(n.children, ids);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvTreeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
182
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvTreeViewComponent, isStandalone: true, selector: "sv-tree-view", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, showLines: { classPropertyName: "showLines", publicName: "showLines", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { nodeClick: "nodeClick", nodeExpand: "nodeExpand", nodeCollapse: "nodeCollapse", nodeSelect: "nodeSelect" }, ngImport: i0, template: `
|
|
183
|
+
<div class="sv-tree-view select-none" role="tree">
|
|
184
|
+
@for (fn of visibleNodes(); track fn.node.id) {
|
|
185
|
+
<div class="group flex items-center gap-1 rounded-lg px-1 outline-none transition-colors duration-100 cursor-pointer focus-visible:ring-2 focus-visible:ring-indigo-400"
|
|
186
|
+
[class]="fn.node.disabled
|
|
187
|
+
? 'opacity-40 cursor-not-allowed py-1.5'
|
|
188
|
+
: isSelected(fn.node.id)
|
|
189
|
+
? 'py-1.5 bg-indigo-50 dark:bg-indigo-900/25'
|
|
190
|
+
: 'py-1.5 hover:bg-gray-100/70 dark:hover:bg-gray-800/50'"
|
|
191
|
+
[style.paddingLeft.px]="fn.depth * 20 + 4"
|
|
192
|
+
(click)="select(fn.node, fn.depth); setActive(fn.node.id)"
|
|
193
|
+
(keydown)="onKeydown($event, fn, $index)"
|
|
194
|
+
role="treeitem"
|
|
195
|
+
[attr.data-tree-id]="fn.node.id"
|
|
196
|
+
[attr.tabindex]="treeTabIndex(fn.node.id)"
|
|
197
|
+
[attr.aria-level]="fn.depth + 1"
|
|
198
|
+
[attr.aria-expanded]="fn.hasChildren ? fn.isExpanded : null"
|
|
199
|
+
[attr.aria-selected]="isSelected(fn.node.id)">
|
|
200
|
+
|
|
201
|
+
<!-- Chevron toggle -->
|
|
202
|
+
<button type="button"
|
|
203
|
+
class="flex h-5 w-5 shrink-0 items-center justify-center rounded
|
|
204
|
+
text-gray-400 transition-all hover:text-gray-700 dark:hover:text-gray-200
|
|
205
|
+
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-indigo-400"
|
|
206
|
+
[class.invisible]="!fn.hasChildren"
|
|
207
|
+
(click)="toggle(fn.node); $event.stopPropagation()"
|
|
208
|
+
[attr.aria-label]="fn.isExpanded ? 'Collapse' : 'Expand'">
|
|
209
|
+
<svg class="h-3.5 w-3.5 transition-transform duration-150"
|
|
210
|
+
[class.rotate-90]="fn.isExpanded"
|
|
211
|
+
fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
|
212
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/>
|
|
213
|
+
</svg>
|
|
214
|
+
</button>
|
|
215
|
+
|
|
216
|
+
<!-- Icon -->
|
|
217
|
+
@if (fn.node.icon) {
|
|
218
|
+
<span class="shrink-0 text-sm" aria-hidden="true">{{ fn.node.icon }}</span>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
<!-- Label -->
|
|
222
|
+
<span class="flex-1 truncate text-[13px] font-medium"
|
|
223
|
+
[class]="isSelected(fn.node.id)
|
|
224
|
+
? 'text-indigo-700 dark:text-indigo-300'
|
|
225
|
+
: 'text-gray-800 dark:text-gray-100'">
|
|
226
|
+
{{ fn.node.label }}
|
|
227
|
+
</span>
|
|
228
|
+
|
|
229
|
+
<!-- Badge -->
|
|
230
|
+
@if (fn.node.badge) {
|
|
231
|
+
<span class="ml-auto shrink-0 rounded-full bg-gray-100 px-1.5 py-px
|
|
232
|
+
text-[10px] font-semibold text-gray-600
|
|
233
|
+
dark:bg-gray-800 dark:text-gray-300">
|
|
234
|
+
{{ fn.node.badge }}
|
|
235
|
+
</span>
|
|
236
|
+
}
|
|
237
|
+
</div>
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@if (!visibleNodes().length) {
|
|
241
|
+
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
|
242
|
+
No items to display
|
|
243
|
+
</p>
|
|
244
|
+
}
|
|
245
|
+
</div>
|
|
246
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
247
|
+
}
|
|
248
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvTreeViewComponent, decorators: [{
|
|
249
|
+
type: Component,
|
|
250
|
+
args: [{
|
|
251
|
+
selector: 'sv-tree-view',
|
|
252
|
+
standalone: true,
|
|
253
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
254
|
+
template: `
|
|
255
|
+
<div class="sv-tree-view select-none" role="tree">
|
|
256
|
+
@for (fn of visibleNodes(); track fn.node.id) {
|
|
257
|
+
<div class="group flex items-center gap-1 rounded-lg px-1 outline-none transition-colors duration-100 cursor-pointer focus-visible:ring-2 focus-visible:ring-indigo-400"
|
|
258
|
+
[class]="fn.node.disabled
|
|
259
|
+
? 'opacity-40 cursor-not-allowed py-1.5'
|
|
260
|
+
: isSelected(fn.node.id)
|
|
261
|
+
? 'py-1.5 bg-indigo-50 dark:bg-indigo-900/25'
|
|
262
|
+
: 'py-1.5 hover:bg-gray-100/70 dark:hover:bg-gray-800/50'"
|
|
263
|
+
[style.paddingLeft.px]="fn.depth * 20 + 4"
|
|
264
|
+
(click)="select(fn.node, fn.depth); setActive(fn.node.id)"
|
|
265
|
+
(keydown)="onKeydown($event, fn, $index)"
|
|
266
|
+
role="treeitem"
|
|
267
|
+
[attr.data-tree-id]="fn.node.id"
|
|
268
|
+
[attr.tabindex]="treeTabIndex(fn.node.id)"
|
|
269
|
+
[attr.aria-level]="fn.depth + 1"
|
|
270
|
+
[attr.aria-expanded]="fn.hasChildren ? fn.isExpanded : null"
|
|
271
|
+
[attr.aria-selected]="isSelected(fn.node.id)">
|
|
272
|
+
|
|
273
|
+
<!-- Chevron toggle -->
|
|
274
|
+
<button type="button"
|
|
275
|
+
class="flex h-5 w-5 shrink-0 items-center justify-center rounded
|
|
276
|
+
text-gray-400 transition-all hover:text-gray-700 dark:hover:text-gray-200
|
|
277
|
+
focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-indigo-400"
|
|
278
|
+
[class.invisible]="!fn.hasChildren"
|
|
279
|
+
(click)="toggle(fn.node); $event.stopPropagation()"
|
|
280
|
+
[attr.aria-label]="fn.isExpanded ? 'Collapse' : 'Expand'">
|
|
281
|
+
<svg class="h-3.5 w-3.5 transition-transform duration-150"
|
|
282
|
+
[class.rotate-90]="fn.isExpanded"
|
|
283
|
+
fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
|
284
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/>
|
|
285
|
+
</svg>
|
|
286
|
+
</button>
|
|
287
|
+
|
|
288
|
+
<!-- Icon -->
|
|
289
|
+
@if (fn.node.icon) {
|
|
290
|
+
<span class="shrink-0 text-sm" aria-hidden="true">{{ fn.node.icon }}</span>
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
<!-- Label -->
|
|
294
|
+
<span class="flex-1 truncate text-[13px] font-medium"
|
|
295
|
+
[class]="isSelected(fn.node.id)
|
|
296
|
+
? 'text-indigo-700 dark:text-indigo-300'
|
|
297
|
+
: 'text-gray-800 dark:text-gray-100'">
|
|
298
|
+
{{ fn.node.label }}
|
|
299
|
+
</span>
|
|
300
|
+
|
|
301
|
+
<!-- Badge -->
|
|
302
|
+
@if (fn.node.badge) {
|
|
303
|
+
<span class="ml-auto shrink-0 rounded-full bg-gray-100 px-1.5 py-px
|
|
304
|
+
text-[10px] font-semibold text-gray-600
|
|
305
|
+
dark:bg-gray-800 dark:text-gray-300">
|
|
306
|
+
{{ fn.node.badge }}
|
|
307
|
+
</span>
|
|
308
|
+
}
|
|
309
|
+
</div>
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@if (!visibleNodes().length) {
|
|
313
|
+
<p class="py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
|
314
|
+
No items to display
|
|
315
|
+
</p>
|
|
316
|
+
}
|
|
317
|
+
</div>
|
|
318
|
+
`,
|
|
319
|
+
}]
|
|
320
|
+
}], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], showLines: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLines", required: false }] }], nodeClick: [{ type: i0.Output, args: ["nodeClick"] }], nodeExpand: [{ type: i0.Output, args: ["nodeExpand"] }], nodeCollapse: [{ type: i0.Output, args: ["nodeCollapse"] }], nodeSelect: [{ type: i0.Output, args: ["nodeSelect"] }] } });
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Generated bundle index. Do not edit.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
export { SvTreeViewComponent };
|
|
327
|
+
//# sourceMappingURL=styloviz-tree-view.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styloviz-tree-view.mjs","sources":["../../../../projects/tree-view-free/src/lib/tree-view.component.ts","../../../../projects/tree-view-free/src/styloviz-tree-view.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n ElementRef,\n inject,\n input,\n OnInit,\n output,\n signal,\n} from '@angular/core';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface TreeNode {\n readonly id: string;\n readonly label: string;\n readonly icon?: string;\n children?: TreeNode[];\n readonly expanded?: boolean;\n readonly disabled?: boolean;\n readonly badge?: string;\n}\n\nexport interface FlatNode {\n readonly node: TreeNode;\n readonly depth: number;\n readonly isLast: boolean;\n readonly hasChildren: boolean;\n readonly isExpanded: boolean;\n}\n\nexport interface TreeNodeClickEvent {\n readonly node: TreeNode;\n readonly depth: number;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * SvTreeViewComponent (Free) — Collapsible single-select tree.\n *\n * For the full 8-variant suite (file explorer, checkbox tri-state,\n * org chart, tree table, searchable, draggable, lazy-load) upgrade to\n * `@styloviz-pro/tree-view`.\n *\n * @example\n * ```html\n * <sv-tree-view [nodes]=\"nodes\" (nodeClick)=\"onNodeClick($event)\" />\n * ```\n */\n@Component({\n selector: 'sv-tree-view',\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"sv-tree-view select-none\" role=\"tree\">\n @for (fn of visibleNodes(); track fn.node.id) {\n <div class=\"group flex items-center gap-1 rounded-lg px-1 outline-none transition-colors duration-100 cursor-pointer focus-visible:ring-2 focus-visible:ring-indigo-400\"\n [class]=\"fn.node.disabled\n ? 'opacity-40 cursor-not-allowed py-1.5'\n : isSelected(fn.node.id)\n ? 'py-1.5 bg-indigo-50 dark:bg-indigo-900/25'\n : 'py-1.5 hover:bg-gray-100/70 dark:hover:bg-gray-800/50'\"\n [style.paddingLeft.px]=\"fn.depth * 20 + 4\"\n (click)=\"select(fn.node, fn.depth); setActive(fn.node.id)\"\n (keydown)=\"onKeydown($event, fn, $index)\"\n role=\"treeitem\"\n [attr.data-tree-id]=\"fn.node.id\"\n [attr.tabindex]=\"treeTabIndex(fn.node.id)\"\n [attr.aria-level]=\"fn.depth + 1\"\n [attr.aria-expanded]=\"fn.hasChildren ? fn.isExpanded : null\"\n [attr.aria-selected]=\"isSelected(fn.node.id)\">\n\n <!-- Chevron toggle -->\n <button type=\"button\"\n class=\"flex h-5 w-5 shrink-0 items-center justify-center rounded\n text-gray-400 transition-all hover:text-gray-700 dark:hover:text-gray-200\n focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-indigo-400\"\n [class.invisible]=\"!fn.hasChildren\"\n (click)=\"toggle(fn.node); $event.stopPropagation()\"\n [attr.aria-label]=\"fn.isExpanded ? 'Collapse' : 'Expand'\">\n <svg class=\"h-3.5 w-3.5 transition-transform duration-150\"\n [class.rotate-90]=\"fn.isExpanded\"\n fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"2\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m8.25 4.5 7.5 7.5-7.5 7.5\"/>\n </svg>\n </button>\n\n <!-- Icon -->\n @if (fn.node.icon) {\n <span class=\"shrink-0 text-sm\" aria-hidden=\"true\">{{ fn.node.icon }}</span>\n }\n\n <!-- Label -->\n <span class=\"flex-1 truncate text-[13px] font-medium\"\n [class]=\"isSelected(fn.node.id)\n ? 'text-indigo-700 dark:text-indigo-300'\n : 'text-gray-800 dark:text-gray-100'\">\n {{ fn.node.label }}\n </span>\n\n <!-- Badge -->\n @if (fn.node.badge) {\n <span class=\"ml-auto shrink-0 rounded-full bg-gray-100 px-1.5 py-px\n text-[10px] font-semibold text-gray-600\n dark:bg-gray-800 dark:text-gray-300\">\n {{ fn.node.badge }}\n </span>\n }\n </div>\n }\n\n @if (!visibleNodes().length) {\n <p class=\"py-8 text-center text-sm text-gray-500 dark:text-gray-400\">\n No items to display\n </p>\n }\n </div>\n `,\n})\nexport class SvTreeViewComponent implements OnInit {\n /** Tree node data (nested children, optional icons). */\n readonly nodes = input<TreeNode[]>([]);\n /** Enable single-row selection highlight. @default true */\n readonly selectable = input<boolean>(true);\n /** Show vertical connector guide lines. @default true */\n readonly showLines = input<boolean>(true);\n\n readonly nodeClick = output<TreeNodeClickEvent>();\n readonly nodeExpand = output<TreeNode>();\n readonly nodeCollapse = output<TreeNode>();\n readonly nodeSelect = output<TreeNode>();\n\n readonly expandedIds = signal<Set<string>>(new Set());\n readonly selectedId = signal<string | null>(null);\n /** The treeitem currently in the tab order (roving tabindex). */\n readonly activeId = signal<string | null>(null);\n\n private readonly _nodes = signal<TreeNode[]>([]);\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n readonly visibleNodes = computed<FlatNode[]>(() =>\n this._flatten(this._nodes(), 0, this.expandedIds())\n );\n\n ngOnInit(): void {\n this._nodes.set(this.nodes());\n const ids = new Set<string>();\n this._collectExpanded(this.nodes(), ids);\n this.expandedIds.set(ids);\n // Seed the roving tabindex on the first node so the tree is tab-reachable.\n this.activeId.set(this.visibleNodes()[0]?.node.id ?? null);\n }\n\n toggle(node: TreeNode): void {\n if (node.disabled) return;\n const ids = new Set(this.expandedIds());\n if (ids.has(node.id)) {\n ids.delete(node.id);\n this.nodeCollapse.emit(node);\n } else {\n ids.add(node.id);\n this.nodeExpand.emit(node);\n }\n this.expandedIds.set(ids);\n }\n\n select(node: TreeNode, depth: number): void {\n if (node.disabled || !this.selectable()) return;\n this.selectedId.set(node.id);\n this.nodeSelect.emit(node);\n this.nodeClick.emit({ node, depth });\n }\n\n isSelected(id: string): boolean { return this.selectedId() === id; }\n\n // ── Keyboard navigation (WAI-ARIA tree) ─────────────────────────────────────\n\n /** Roving tabindex: only the active treeitem is in the tab order. */\n treeTabIndex(id: string): number {\n const active = this.activeId() ?? this.visibleNodes()[0]?.node.id ?? null;\n return id === active ? 0 : -1;\n }\n\n setActive(id: string): void { this.activeId.set(id); }\n\n /**\n * Arrow / Home / End / Enter / Space handling per the WAI-ARIA tree pattern.\n * Arrow Up/Down move between visible rows; Right expands or steps into a\n * child; Left collapses or steps to the parent; Enter/Space selects.\n */\n onKeydown(event: KeyboardEvent, fn: FlatNode, index: number): void {\n const list = this.visibleNodes();\n switch (event.key) {\n case 'ArrowDown': event.preventDefault(); this._focusStep(list, index, +1); break;\n case 'ArrowUp': event.preventDefault(); this._focusStep(list, index, -1); break;\n case 'Home': event.preventDefault(); this._focusFrom(list, 0, +1); break;\n case 'End': event.preventDefault(); this._focusFrom(list, list.length - 1, -1); break;\n case 'ArrowRight':\n event.preventDefault();\n if (fn.hasChildren && !fn.isExpanded) { this.toggle(fn.node); }\n else if (fn.hasChildren && fn.isExpanded) { this._focusStep(list, index, +1); }\n break;\n case 'ArrowLeft':\n event.preventDefault();\n if (fn.hasChildren && fn.isExpanded) { this.toggle(fn.node); }\n else { this._focusParent(list, index, fn.depth); }\n break;\n case 'Enter':\n case ' ':\n event.preventDefault();\n this.select(fn.node, fn.depth);\n this.setActive(fn.node.id);\n break;\n default:\n return;\n }\n }\n\n /** Move focus by `dir` steps from `from`, skipping disabled rows. */\n private _focusStep(list: readonly FlatNode[], from: number, dir: 1 | -1): void {\n for (let i = from + dir; i >= 0 && i < list.length; i += dir) {\n if (!list[i].node.disabled) { this._focus(list[i].node.id); return; }\n }\n }\n\n /** From `start`, find the first enabled row in direction `dir` and focus it. */\n private _focusFrom(list: readonly FlatNode[], start: number, dir: 1 | -1): void {\n for (let i = start; i >= 0 && i < list.length; i += dir) {\n if (!list[i].node.disabled) { this._focus(list[i].node.id); return; }\n }\n }\n\n /** Focus the parent row (nearest previous row with a smaller depth). */\n private _focusParent(list: readonly FlatNode[], index: number, depth: number): void {\n for (let i = index - 1; i >= 0; i--) {\n if (list[i].depth < depth) { this._focus(list[i].node.id); return; }\n }\n }\n\n /** Set the active id and move DOM focus to that row (after any re-render). */\n private _focus(id: string): void {\n this.setActive(id);\n queueMicrotask(() => {\n const rows = this.host.nativeElement.querySelectorAll<HTMLElement>('[data-tree-id]');\n rows.forEach(el => { if (el.dataset['treeId'] === id) el.focus(); });\n });\n }\n\n private _flatten(nodes: TreeNode[], depth: number, expanded: Set<string>): FlatNode[] {\n const result: FlatNode[] = [];\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const hasChildren = !!node.children?.length;\n const isExp = expanded.has(node.id);\n result.push({ node, depth, isLast: i === nodes.length - 1, hasChildren, isExpanded: isExp });\n if (isExp && node.children?.length) {\n result.push(...this._flatten(node.children, depth + 1, expanded));\n }\n }\n return result;\n }\n\n private _collectExpanded(nodes: TreeNode[], ids: Set<string>): void {\n for (const n of nodes) {\n if (n.expanded) ids.add(n.id);\n if (n.children) this._collectExpanded(n.children, ids);\n }\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@styloviz/tree-view",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "8 premium variants: collapsible tree, file explorer, tri-state checkboxes, org chart, tree table, searchable, drag & drop reordering and lazy load.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"tailwind",
|
|
8
|
+
"tree-view",
|
|
9
|
+
"tree"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "sazzad-bs23",
|
|
13
|
+
"homepage": "https://styloviz.dev",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
|
|
17
|
+
"directory": "projects/tree-view-free"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@angular/common": ">=21.0.0",
|
|
24
|
+
"@angular/core": ">=21.0.0"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
31
|
+
},
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"module": "fesm2022/styloviz-tree-view.mjs",
|
|
34
|
+
"typings": "types/styloviz-tree-view.d.ts",
|
|
35
|
+
"exports": {
|
|
36
|
+
"./package.json": {
|
|
37
|
+
"default": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./types/styloviz-tree-view.d.ts",
|
|
41
|
+
"default": "./fesm2022/styloviz-tree-view.mjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"type": "module",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"tslib": "^2.3.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { OnInit } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
interface TreeNode {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
readonly icon?: string;
|
|
8
|
+
children?: TreeNode[];
|
|
9
|
+
readonly expanded?: boolean;
|
|
10
|
+
readonly disabled?: boolean;
|
|
11
|
+
readonly badge?: string;
|
|
12
|
+
}
|
|
13
|
+
interface FlatNode {
|
|
14
|
+
readonly node: TreeNode;
|
|
15
|
+
readonly depth: number;
|
|
16
|
+
readonly isLast: boolean;
|
|
17
|
+
readonly hasChildren: boolean;
|
|
18
|
+
readonly isExpanded: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface TreeNodeClickEvent {
|
|
21
|
+
readonly node: TreeNode;
|
|
22
|
+
readonly depth: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* SvTreeViewComponent (Free) — Collapsible single-select tree.
|
|
26
|
+
*
|
|
27
|
+
* For the full 8-variant suite (file explorer, checkbox tri-state,
|
|
28
|
+
* org chart, tree table, searchable, draggable, lazy-load) upgrade to
|
|
29
|
+
* `@styloviz-pro/tree-view`.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```html
|
|
33
|
+
* <sv-tree-view [nodes]="nodes" (nodeClick)="onNodeClick($event)" />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare class SvTreeViewComponent implements OnInit {
|
|
37
|
+
/** Tree node data (nested children, optional icons). */
|
|
38
|
+
readonly nodes: _angular_core.InputSignal<TreeNode[]>;
|
|
39
|
+
/** Enable single-row selection highlight. @default true */
|
|
40
|
+
readonly selectable: _angular_core.InputSignal<boolean>;
|
|
41
|
+
/** Show vertical connector guide lines. @default true */
|
|
42
|
+
readonly showLines: _angular_core.InputSignal<boolean>;
|
|
43
|
+
readonly nodeClick: _angular_core.OutputEmitterRef<TreeNodeClickEvent>;
|
|
44
|
+
readonly nodeExpand: _angular_core.OutputEmitterRef<TreeNode>;
|
|
45
|
+
readonly nodeCollapse: _angular_core.OutputEmitterRef<TreeNode>;
|
|
46
|
+
readonly nodeSelect: _angular_core.OutputEmitterRef<TreeNode>;
|
|
47
|
+
readonly expandedIds: _angular_core.WritableSignal<Set<string>>;
|
|
48
|
+
readonly selectedId: _angular_core.WritableSignal<string | null>;
|
|
49
|
+
/** The treeitem currently in the tab order (roving tabindex). */
|
|
50
|
+
readonly activeId: _angular_core.WritableSignal<string | null>;
|
|
51
|
+
private readonly _nodes;
|
|
52
|
+
private readonly host;
|
|
53
|
+
readonly visibleNodes: _angular_core.Signal<FlatNode[]>;
|
|
54
|
+
ngOnInit(): void;
|
|
55
|
+
toggle(node: TreeNode): void;
|
|
56
|
+
select(node: TreeNode, depth: number): void;
|
|
57
|
+
isSelected(id: string): boolean;
|
|
58
|
+
/** Roving tabindex: only the active treeitem is in the tab order. */
|
|
59
|
+
treeTabIndex(id: string): number;
|
|
60
|
+
setActive(id: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* Arrow / Home / End / Enter / Space handling per the WAI-ARIA tree pattern.
|
|
63
|
+
* Arrow Up/Down move between visible rows; Right expands or steps into a
|
|
64
|
+
* child; Left collapses or steps to the parent; Enter/Space selects.
|
|
65
|
+
*/
|
|
66
|
+
onKeydown(event: KeyboardEvent, fn: FlatNode, index: number): void;
|
|
67
|
+
/** Move focus by `dir` steps from `from`, skipping disabled rows. */
|
|
68
|
+
private _focusStep;
|
|
69
|
+
/** From `start`, find the first enabled row in direction `dir` and focus it. */
|
|
70
|
+
private _focusFrom;
|
|
71
|
+
/** Focus the parent row (nearest previous row with a smaller depth). */
|
|
72
|
+
private _focusParent;
|
|
73
|
+
/** Set the active id and move DOM focus to that row (after any re-render). */
|
|
74
|
+
private _focus;
|
|
75
|
+
private _flatten;
|
|
76
|
+
private _collectExpanded;
|
|
77
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvTreeViewComponent, never>;
|
|
78
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvTreeViewComponent, "sv-tree-view", never, { "nodes": { "alias": "nodes"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "showLines": { "alias": "showLines"; "required": false; "isSignal": true; }; }, { "nodeClick": "nodeClick"; "nodeExpand": "nodeExpand"; "nodeCollapse": "nodeCollapse"; "nodeSelect": "nodeSelect"; }, never, never, true, never>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export { SvTreeViewComponent };
|
|
82
|
+
export type { FlatNode, TreeNode, TreeNodeClickEvent };
|
|
83
|
+
//# sourceMappingURL=styloviz-tree-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styloviz-tree-view.d.ts","sources":["../../../../projects/tree-view-free/src/lib/tree-view.component.ts"],"mappings":";;;UAciB,QAAQ;AACvB;AACA;AACA;AACA,eAAW,QAAQ;AACnB;AACA;AACA;AACD;UAEgB,QAAQ;AACvB,mBAAe,QAAQ;AACvB;AACA;AACA;AACA;AACD;UAEgB,kBAAkB;AACjC,mBAAe,QAAQ;AACvB;AACD;AAID;;;;;;;;;;;AAWG;AACH,cAsEa,mBAAoB,YAAW,MAAM;;oBAElC,aAAA,CAAA,WAAA,CAAA,QAAA;;yBAEK,aAAA,CAAA,WAAA;;wBAED,aAAA,CAAA,WAAA;wBAEA,aAAA,CAAA,gBAAA,CAAA,kBAAA;yBACC,aAAA,CAAA,gBAAA,CAAA,QAAA;2BACE,aAAA,CAAA,gBAAA,CAAA,QAAA;yBACF,aAAA,CAAA,gBAAA,CAAA,QAAA;0BAEC,aAAA,CAAA,cAAA,CAAA,GAAA;yBACD,aAAA,CAAA,cAAA;;uBAEF,aAAA,CAAA,cAAA;AAEjB;AACA;2BAEqB,aAAA,CAAA,MAAA,CAAA,QAAA;AAIrB;AASA,iBAAa,QAAQ;iBAaR,QAAQ;AAOrB;;AAKA;AAKA;AAEA;;;;AAIG;AACH,qBAAiB,aAAa,MAAM,QAAQ;;AA6B5C;;AAOA;;AAOA;;AAOA;AAQA;AAcA;oDA/IW,mBAAmB;sDAAnB,mBAAmB;AAqJ/B;;;;","names":[]}
|