@keenmate/svelte-treeview 5.0.0-rc09 → 5.0.0-rc11
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/CHANGELOG.md +931 -866
- package/README.md +362 -1100
- package/ai/INDEX.txt +349 -349
- package/ai/advanced-patterns.txt +1 -1
- package/ai/drag-drop.txt +397 -397
- package/ai/performance.txt +349 -349
- package/ai/tree-editing.txt +1 -1
- package/component-variables.manifest.json +143 -142
- package/dist/components/ContextMenuDivider.svelte +2 -2
- package/dist/components/ContextMenuItem.svelte +58 -10
- package/dist/components/ContextMenuLevel.svelte +131 -0
- package/dist/components/ContextMenuLevel.svelte.d.ts +32 -0
- package/dist/components/Node.svelte +491 -486
- package/dist/components/Node.svelte.d.ts +1 -1
- package/dist/components/Tree.svelte +1323 -1276
- package/dist/components/Tree.svelte.d.ts +77 -69
- package/dist/components/TreeProvider.svelte +1 -0
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/core/TreeController.svelte.d.ts +84 -53
- package/dist/core/TreeController.svelte.js +540 -126
- package/dist/index.d.ts +1 -1
- package/dist/ltree/ltree-node.svelte.js +2 -2
- package/dist/ltree/ltree.svelte.d.ts +1 -1
- package/dist/ltree/ltree.svelte.js +94 -9
- package/dist/ltree/types.d.ts +10 -0
- package/dist/styles/animations.css +17 -0
- package/dist/styles/{_base.css → base.css} +14 -11
- package/dist/styles/checkbox.css +96 -0
- package/dist/styles/context-menu.css +117 -0
- package/dist/styles/controls.css +4 -0
- package/dist/styles/dark-mode.css +44 -0
- package/dist/styles/debug.css +45 -0
- package/dist/styles/drag-drop.css +170 -0
- package/dist/styles/drop-zones.css +270 -0
- package/dist/styles/floating.css +5 -0
- package/dist/styles/loading.css +36 -0
- package/dist/styles/main.css +38 -31
- package/dist/styles/node.css +63 -0
- package/dist/styles/states.css +123 -0
- package/dist/styles/{_toggle-icons.css → toggle-icons.css} +29 -29
- package/dist/styles/variables.css +217 -0
- package/dist/styles.css +887 -869
- package/package.json +9 -3
- package/dist/styles/_checkbox.css +0 -83
- package/dist/styles/_context-menu.css +0 -134
- package/dist/styles/_debug.css +0 -45
- package/dist/styles/_drag-drop.css +0 -174
- package/dist/styles/_drop-zones.css +0 -270
- package/dist/styles/_loading.css +0 -40
- package/dist/styles/_node.css +0 -60
- package/dist/styles/_states.css +0 -92
- package/dist/styles/_variables.css +0 -189
package/README.md
CHANGED
|
@@ -1,1100 +1,362 @@
|
|
|
1
|
-
# @keenmate/svelte-treeview
|
|
2
|
-
|
|
3
|
-
A high-performance, feature-rich hierarchical tree view component for Svelte 5 with drag & drop support, search functionality, and flexible data structures using LTree.
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
62
|
-
- **
|
|
63
|
-
- **
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
/>
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
{data}
|
|
364
|
-
beforeDropCallback={beforeDrop}
|
|
365
|
-
onNodeDrop={onDrop}
|
|
366
|
-
/>
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Tree Editing
|
|
370
|
-
|
|
371
|
-
The tree provides built-in methods for programmatic editing:
|
|
372
|
-
|
|
373
|
-
```svelte
|
|
374
|
-
<script lang="ts">
|
|
375
|
-
import { Tree } from '@keenmate/svelte-treeview';
|
|
376
|
-
|
|
377
|
-
let treeRef: Tree<MyNode>;
|
|
378
|
-
|
|
379
|
-
// Add a new node
|
|
380
|
-
function addChild() {
|
|
381
|
-
const result = treeRef.addNode(
|
|
382
|
-
selectedNode?.path || '', // parent path (empty = root)
|
|
383
|
-
{ id: Date.now(), path: '', name: 'New Item', sortOrder: 100 }
|
|
384
|
-
);
|
|
385
|
-
if (result.success) {
|
|
386
|
-
console.log('Added:', result.node);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Move a node
|
|
391
|
-
function moveUp() {
|
|
392
|
-
const siblings = treeRef.getSiblings(selectedNode.path);
|
|
393
|
-
const index = siblings.findIndex(s => s.path === selectedNode.path);
|
|
394
|
-
if (index > 0) {
|
|
395
|
-
treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'before');
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Remove a node
|
|
400
|
-
function remove() {
|
|
401
|
-
treeRef.removeNode(selectedNode.path);
|
|
402
|
-
}
|
|
403
|
-
</script>
|
|
404
|
-
|
|
405
|
-
<Tree
|
|
406
|
-
bind:this={treeRef}
|
|
407
|
-
{data}
|
|
408
|
-
idMember="id"
|
|
409
|
-
pathMember="path"
|
|
410
|
-
orderMember="sortOrder"
|
|
411
|
-
/>
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
**Note**: When using `orderMember`, the tree automatically calculates sort order values when moving nodes with 'before' or 'after' positions.
|
|
415
|
-
|
|
416
|
-
### With Context Menus
|
|
417
|
-
|
|
418
|
-
The tree supports context menus with two approaches: **callback-based** (imperative, shared with web components) and **snippet + component** (declarative, Svelte-only).
|
|
419
|
-
|
|
420
|
-
#### Callback-Based Context Menus
|
|
421
|
-
|
|
422
|
-
```svelte
|
|
423
|
-
<script lang="ts">
|
|
424
|
-
import { Tree } from '@keenmate/svelte-treeview';
|
|
425
|
-
import type { ContextMenuEntry } from '@keenmate/svelte-treeview';
|
|
426
|
-
|
|
427
|
-
const data = [
|
|
428
|
-
{ path: '1', name: 'Documents', type: 'folder', canEdit: true, canDelete: true },
|
|
429
|
-
{ path: '1.1', name: 'report.pdf', type: 'file', canEdit: true, canDelete: false },
|
|
430
|
-
{ path: '2', name: 'Images', type: 'folder', canEdit: false, canDelete: true }
|
|
431
|
-
];
|
|
432
|
-
|
|
433
|
-
function createContextMenu(node, close: () => void): ContextMenuEntry[] {
|
|
434
|
-
return [
|
|
435
|
-
{ label: 'Open', icon: '📂', shortcut: 'O',
|
|
436
|
-
onclick: () => { alert(`Opening ${node.data?.name}`); close(); } },
|
|
437
|
-
{ label: 'Edit', icon: '✏️', shortcut: 'E', isVisible: node.data?.canEdit,
|
|
438
|
-
onclick: () => { alert(`Editing ${node.data?.name}`); close(); } },
|
|
439
|
-
{ label: 'Export As...', icon: '📤', children: [
|
|
440
|
-
{ label: 'JSON', shortcut: 'J', onclick: () => { exportAs(node, 'json'); close(); } },
|
|
441
|
-
{ label: 'XML', shortcut: 'X', onclick: () => { exportAs(node, 'xml'); close(); } },
|
|
442
|
-
]},
|
|
443
|
-
{ divider: true, label: 'Danger zone' },
|
|
444
|
-
{ label: 'Delete', icon: '🗑️', className: 'danger',
|
|
445
|
-
isDisabled: !node.data?.canDelete,
|
|
446
|
-
onclick: () => { confirm(`Delete?`) && alert('Deleted!'); close(); } },
|
|
447
|
-
];
|
|
448
|
-
}
|
|
449
|
-
</script>
|
|
450
|
-
|
|
451
|
-
<Tree
|
|
452
|
-
{data}
|
|
453
|
-
idMember="path"
|
|
454
|
-
pathMember="path"
|
|
455
|
-
contextMenuCallback={createContextMenu}
|
|
456
|
-
/>
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
#### Snippet + Component Context Menus
|
|
460
|
-
|
|
461
|
-
```svelte
|
|
462
|
-
<script lang="ts">
|
|
463
|
-
import { Tree, ContextMenuItemC, ContextMenuDividerC } from '@keenmate/svelte-treeview';
|
|
464
|
-
</script>
|
|
465
|
-
|
|
466
|
-
<Tree {data} idMember="path" pathMember="path">
|
|
467
|
-
{#snippet contextMenu(node, close)}
|
|
468
|
-
<ContextMenuItemC label="Copy" icon="📋" shortcut="C"
|
|
469
|
-
onclick={() => { copy(node); close(); }} />
|
|
470
|
-
{#if node.data?.type === 'folder'}
|
|
471
|
-
<ContextMenuItemC label="Export As..." icon="📤">
|
|
472
|
-
<ContextMenuItemC label="JSON" shortcut="J"
|
|
473
|
-
onclick={() => { exportAs(node, 'json'); close(); }} />
|
|
474
|
-
<ContextMenuItemC label="XML" shortcut="X"
|
|
475
|
-
onclick={() => { exportAs(node, 'xml'); close(); }} />
|
|
476
|
-
</ContextMenuItemC>
|
|
477
|
-
{/if}
|
|
478
|
-
<ContextMenuDividerC label="Danger zone" />
|
|
479
|
-
<ContextMenuItemC label="Delete" icon="🗑️" className="danger"
|
|
480
|
-
isDisabled={!!node.data?.readonly}
|
|
481
|
-
onclick={() => { del(node); close(); }} />
|
|
482
|
-
{/snippet}
|
|
483
|
-
</Tree>
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
#### Context Menu Features
|
|
487
|
-
|
|
488
|
-
- **Unified types**: `ContextMenuItem`, `ContextMenuDivider`, `ContextMenuEntry` shared across svelte-treeview and canvas-tree
|
|
489
|
-
- **Keyboard shortcuts**: `shortcut` field renders a hint and activates on keypress when menu is open (supports `Ctrl+`, `Shift+`, `Alt+` modifiers)
|
|
490
|
-
- **Submenus**: `children` array opens nested menus on hover
|
|
491
|
-
- **Named dividers**: `{ divider: true, label: 'Section' }` renders as `---- Section ----`
|
|
492
|
-
- **Visibility control**: `isVisible: false` hides items (callback approach); snippet approach uses `{#if}`
|
|
493
|
-
- **Flexible styling**: `className="danger"` or any custom CSS class
|
|
494
|
-
- **Dynamic menus**: Generate items based on node properties
|
|
495
|
-
- **Icons and disabled states**: Visual organization and context-sensitive availability
|
|
496
|
-
- **Position offset**: `contextMenuXOffset`/`contextMenuYOffset` for cursor clearance
|
|
497
|
-
- **Auto-close**: Closes on scroll, click outside, Escape key, or programmatically
|
|
498
|
-
|
|
499
|
-
## Styling and Customization
|
|
500
|
-
|
|
501
|
-
The component comes with default styles that provide a clean, modern look. You can customize it extensively:
|
|
502
|
-
|
|
503
|
-
### CSS Variables
|
|
504
|
-
|
|
505
|
-
The component uses CSS custom properties for easy theming:
|
|
506
|
-
|
|
507
|
-
```css
|
|
508
|
-
:root {
|
|
509
|
-
--ltree-rem: 10px; /* Base sizing unit — scale all dimensions */
|
|
510
|
-
--ltree-node-indent-per-level: calc(0.8 * var(--ltree-rem)); /* Indent per nesting level */
|
|
511
|
-
--ltree-primary: #0d6efd; /* Tints (multi-select, dragover) derived
|
|
512
|
-
automatically via color-mix() */
|
|
513
|
-
--ltree-success: #198754;
|
|
514
|
-
--ltree-danger: #dc3545;
|
|
515
|
-
--ltree-light: #f8f9fa;
|
|
516
|
-
--ltree-border: #dee2e6;
|
|
517
|
-
--ltree-body-color: #212529;
|
|
518
|
-
}
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
**Note**: All dimensions are `calc(N × var(--ltree-rem))`. Set `--ltree-rem` once (default `10px`) to scale every size proportionally, or set it to `1rem` to make the component follow document font-size.
|
|
522
|
-
|
|
523
|
-
### Scaling and Theme Integration
|
|
524
|
-
|
|
525
|
-
```css
|
|
526
|
-
/* Scale everything 20% larger */
|
|
527
|
-
.my-bigger-tree {
|
|
528
|
-
--ltree-rem: 12px;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/* Shared theme tokens — picked up by every @keenmate/* component */
|
|
532
|
-
:root {
|
|
533
|
-
--base-accent-color: #6366f1;
|
|
534
|
-
--base-font-family: 'Inter', system-ui, sans-serif;
|
|
535
|
-
--base-border-radius-sm: 0.4; /* unitless multiplier (× rem) */
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
See `/examples/theming` for the full CSS variable reference and a live demo.
|
|
540
|
-
|
|
541
|
-
### CSS Classes
|
|
542
|
-
|
|
543
|
-
- `.ltree-tree` - Main tree container
|
|
544
|
-
- `.ltree-node` - Individual node container
|
|
545
|
-
- `.ltree-node-content` - Node content area
|
|
546
|
-
- `.ltree-toggle-icon` - Expand/collapse icons
|
|
547
|
-
- `.ltree-selected-*` - Selected node styles
|
|
548
|
-
- `.ltree-dragover-*` - Drag-over node styles
|
|
549
|
-
- `.ltree-draggable` - Draggable nodes
|
|
550
|
-
- `.ltree-context-menu` - Context menu styling
|
|
551
|
-
- `.ltree-drag-over` - Applied during drag operations
|
|
552
|
-
- `.ltree-drop-valid` / `.ltree-drop-invalid` - Drop target validation
|
|
553
|
-
|
|
554
|
-
### Pre-built Selected Node Styles
|
|
555
|
-
|
|
556
|
-
The component includes several pre-built classes for styling selected nodes:
|
|
557
|
-
|
|
558
|
-
```svelte
|
|
559
|
-
<Tree
|
|
560
|
-
{data}
|
|
561
|
-
idMember="path"
|
|
562
|
-
pathMember="path"
|
|
563
|
-
selectedNodeClass="ltree-selected-bold"
|
|
564
|
-
/>
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
**Available Selected Node Classes:**
|
|
568
|
-
|
|
569
|
-
| Class | Description | Visual Effect |
|
|
570
|
-
|-------|-------------|---------------|
|
|
571
|
-
| `ltree-selected-bold` | Bold text with primary color | **Bold text** in theme primary color |
|
|
572
|
-
| `ltree-selected-border` | Border and background highlight | Solid border with light background |
|
|
573
|
-
| `ltree-selected-brackets` | Decorative brackets around text | > **Node Text** < |
|
|
574
|
-
|
|
575
|
-
**Available Drag-over Node Classes:**
|
|
576
|
-
|
|
577
|
-
| Class | Description | Visual Effect |
|
|
578
|
-
|-------|-------------|---------------|
|
|
579
|
-
| `ltree-dragover-highlight` | Dashed border with success color background | Green dashed border with subtle background |
|
|
580
|
-
| `ltree-dragover-glow` | Blue glow effect | Glowing shadow effect with primary color theme |
|
|
581
|
-
|
|
582
|
-
### Custom Icon Classes
|
|
583
|
-
|
|
584
|
-
```svelte
|
|
585
|
-
<Tree
|
|
586
|
-
{data}
|
|
587
|
-
idMember="path"
|
|
588
|
-
pathMember="path"
|
|
589
|
-
expandIconClass="custom-expand-icon"
|
|
590
|
-
collapseIconClass="custom-collapse-icon"
|
|
591
|
-
leafIconClass="custom-leaf-icon"
|
|
592
|
-
/>
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
## API Reference
|
|
596
|
-
|
|
597
|
-
### Tree Component Props
|
|
598
|
-
|
|
599
|
-
#### Core Properties
|
|
600
|
-
| Prop | Type | Default | Description |
|
|
601
|
-
|------|------|---------|-------------|
|
|
602
|
-
| `data` | `T[]` | **required** | Array of data objects |
|
|
603
|
-
| `idMember` | `string` | **required** | Property name for unique identifiers |
|
|
604
|
-
| `pathMember` | `string` | **required** | Property name for hierarchical paths |
|
|
605
|
-
| `sortCallback` | `(items: LTreeNode<T>[]) => LTreeNode<T>[]` | `undefined` | Function to sort tree nodes |
|
|
606
|
-
|
|
607
|
-
#### Data Mapping Properties
|
|
608
|
-
| Prop | Type | Default | Description |
|
|
609
|
-
|------|------|---------|-------------|
|
|
610
|
-
| `parentPathMember` | `string \| null` | `null` | Property name for parent path references |
|
|
611
|
-
| `levelMember` | `string \| null` | `null` | Property name for node level |
|
|
612
|
-
| `isExpandedMember` | `string \| null` | `null` | Property name for expanded state |
|
|
613
|
-
| `isSelectedMember` | `string \| null` | `null` | Property name for selected state |
|
|
614
|
-
| `isDraggableMember` | `string \| null` | `null` | Property name for draggable state |
|
|
615
|
-
| `isDropAllowedMember` | `string \| null` | `null` | Property name for drop allowed state |
|
|
616
|
-
| `allowedDropPositionsMember` | `string \| null` | `null` | Property name for allowed drop positions array |
|
|
617
|
-
| `isCollapsibleMember` | `string \| null` | `null` | Property name for collapsible state |
|
|
618
|
-
| `getIsCollapsibleCallback` | `(node) => boolean` | `undefined` | Callback to determine if a node is collapsible |
|
|
619
|
-
| `getIsDraggableCallback` | `(node) => boolean` | `undefined` | Callback to determine if a node is draggable |
|
|
620
|
-
| `hasChildrenMember` | `string \| null` | `null` | Property name for children existence |
|
|
621
|
-
| `isSorted` | `boolean \| null` | `null` | Whether items should be sorted |
|
|
622
|
-
|
|
623
|
-
#### Display & Search Properties
|
|
624
|
-
| Prop | Type | Default | Description |
|
|
625
|
-
|------|------|---------|-------------|
|
|
626
|
-
| `displayValueMember` | `string \| null` | `null` | Property name for display text |
|
|
627
|
-
| `getDisplayValueCallback` | `(node) => string` | `undefined` | Function to get display value |
|
|
628
|
-
| `searchValueMember` | `string \| null` | `null` | Property name for search indexing |
|
|
629
|
-
| `getSearchValueCallback` | `(node) => string` | `undefined` | Function to get search value |
|
|
630
|
-
| `shouldUseInternalSearchIndex` | `boolean` | `true` | Enable built-in search functionality |
|
|
631
|
-
| `initializeIndexCallback` | `() => Index` | `undefined` | Function to initialize search index |
|
|
632
|
-
| `searchText` | `string` (bindable) | `undefined` | Current search text |
|
|
633
|
-
|
|
634
|
-
**Note**: When `shouldUseInternalSearchIndex` is enabled, node indexing is performed asynchronously using `requestIdleCallback` (with fallback to `setTimeout`). This ensures the tree renders immediately while search indexing happens during browser idle time, providing better performance for large datasets.
|
|
635
|
-
|
|
636
|
-
**Important**: For internal search indexing to work, you must:
|
|
637
|
-
1. Set `shouldUseInternalSearchIndex={true}`
|
|
638
|
-
2. Provide either `searchValueMember` (property name) or `getSearchValueCallback` (function)
|
|
639
|
-
|
|
640
|
-
Without both requirements, no search indexing will occur.
|
|
641
|
-
|
|
642
|
-
**Performance Tuning**:
|
|
643
|
-
- `indexerBatchSize` controls how many nodes are processed per idle callback. Lower values (10-25) provide smoother UI performance but slower indexing, while higher values (50-100) index faster but may cause brief UI pauses. Default: 25.
|
|
644
|
-
- `indexerTimeout` sets the maximum wait time before forcing indexing when the browser is busy. Lower values (25-50ms) ensure more responsive indexing, while higher values (100-200ms) give more time for genuine idle periods. Default: 50ms.
|
|
645
|
-
|
|
646
|
-
#### Tree Configuration
|
|
647
|
-
| Prop | Type | Default | Description |
|
|
648
|
-
|------|------|---------|-------------|
|
|
649
|
-
| `treeId` | `string \| null` | auto-generated | Unique identifier for the tree |
|
|
650
|
-
| `treePathSeparator` | `string \| null` | `"."` | Separator character for hierarchical paths (e.g., "." for "1.2.3" or "/" for "1/2/3") |
|
|
651
|
-
| `selectedNode` | `LTreeNode<T>` (bindable) | `undefined` | Currently selected node |
|
|
652
|
-
| `insertResult` | `InsertArrayResult<T>` (bindable) | `undefined` | Result of the last data insertion including failed nodes |
|
|
653
|
-
|
|
654
|
-
#### Behavior Properties
|
|
655
|
-
| Prop | Type | Default | Description |
|
|
656
|
-
|------|------|---------|-------------|
|
|
657
|
-
| `expandLevel` | `number \| null` | `2` | Automatically expand nodes up to this level |
|
|
658
|
-
| `clickBehavior` | `ClickBehavior` | `'expand-and-focus'` | Node click behavior: `'select'` (click selects, dblclick expands), `'expand'` (click expands only), `'expand-and-focus'` (click selects + expands) |
|
|
659
|
-
| `showCheckboxes` | `boolean` | `false` | Show selection checkboxes before each node. Clicking a checkbox toggles the node's selection (same as Ctrl+click). |
|
|
660
|
-
| `orderMember` | `string \| null` | `null` | Property name for sort order (enables before/after positioning in drag-drop) |
|
|
661
|
-
| `indexerBatchSize` | `number \| null` | `25` | Number of nodes to process per batch during search indexing |
|
|
662
|
-
| `indexerTimeout` | `number \| null` | `50` | Maximum time (ms) to wait for idle callback before forcing indexing |
|
|
663
|
-
| `isLoading` | `boolean` | `false` | Show loading placeholder instead of tree content |
|
|
664
|
-
| `shouldDisplayDebugInformation` | `boolean` | `false` | Show debug information panel with tree statistics and enable console debug logging |
|
|
665
|
-
| `shouldDisplayContextMenuInDebugMode` | `boolean` | `false` | Display persistent context menu at fixed position for styling development |
|
|
666
|
-
|
|
667
|
-
#### Rendering Properties
|
|
668
|
-
| Prop | Type | Default | Description |
|
|
669
|
-
|------|------|---------|-------------|
|
|
670
|
-
| `useFlatRendering` | `boolean` | `true` | Use flat rendering mode (faster for large trees) |
|
|
671
|
-
| `progressiveRender` | `boolean` | `true` | Progressively render nodes in batches |
|
|
672
|
-
| `initialBatchSize` | `number` | `20` | First batch size for progressive rendering |
|
|
673
|
-
| `maxBatchSize` | `number` | `500` | Maximum batch size cap |
|
|
674
|
-
| `virtualScroll` | `boolean` | `false` | Enable virtual scrolling (flat mode only, renders visible + overscan rows) |
|
|
675
|
-
| `virtualRowHeight` | `number` | auto | Explicit row height in px (auto-measured from first row if not set) |
|
|
676
|
-
| `virtualOverscan` | `number` | `5` | Extra rows rendered above/below viewport |
|
|
677
|
-
| `virtualContainerHeight` | `string` | auto/`'400px'` | CSS height for scroll container (auto-detected from parent if not set) |
|
|
678
|
-
| `isRendering` | `boolean` (bindable) | `false` | Whether the tree is currently rendering (useful for progress indicators) |
|
|
679
|
-
| `onRenderStart` | `() => void` | `undefined` | Called when progressive rendering begins |
|
|
680
|
-
| `onRenderProgress` | `(rendered: number, total: number) => void` | `undefined` | Called after each batch with progress info |
|
|
681
|
-
| `onRenderComplete` | `() => void` | `undefined` | Called when progressive rendering finishes |
|
|
682
|
-
|
|
683
|
-
#### Drag & Drop Properties
|
|
684
|
-
| Prop | Type | Default | Description |
|
|
685
|
-
|------|------|---------|-------------|
|
|
686
|
-
| `dragDropMode` | `DragDropMode` | `'none'` | Controls allowed drag operations: `'none'`, `'self'`, `'cross'`, `'both'` |
|
|
687
|
-
| `dropZoneMode` | `string` | `'glow'` | Drop indicator style: `'floating'` or `'glow'` |
|
|
688
|
-
| `dropZoneLayout` | `string` | `'around'` | Zone arrangement: `'around'`, `'above'`, `'below'`, `'wave'`, `'wave2'` |
|
|
689
|
-
| `dropZoneStart` | `number \| string` | `33` | Where zones start horizontally (number=%, string=CSS value) |
|
|
690
|
-
| `dropZoneMaxWidth` | `number` | `120` | Max width in pixels for wave layouts |
|
|
691
|
-
| `allowCopy` | `boolean` | `false` | Enable Ctrl+drag to copy instead of move |
|
|
692
|
-
| `autoHandleCopy` | `boolean` | `true` | Auto-handle same-tree copies (false for external DB/API) |
|
|
693
|
-
| `allowedDropPositionsMember` | `string \| null` | `null` | Property name for allowed drop positions array |
|
|
694
|
-
| `getAllowedDropPositionsCallback` | `(node) => DropPosition[] \| null` | `undefined` | Callback returning allowed drop positions per node |
|
|
695
|
-
| `beforeDropCallback` | `(dropNode, draggedNode, position, event, operation) => ...` | `undefined` | Async-capable callback to validate/modify drops |
|
|
696
|
-
|
|
697
|
-
#### Event Handler Properties
|
|
698
|
-
| Prop | Type | Default | Description |
|
|
699
|
-
|------|------|---------|-------------|
|
|
700
|
-
| `onNodeClicked` | `(node) => void` | `undefined` | Node click event handler |
|
|
701
|
-
| `onNodeDragStart` | `(node, event) => void` | `undefined` | Drag start event handler |
|
|
702
|
-
| `onNodeDragOver` | `(node, event) => void` | `undefined` | Drag over event handler |
|
|
703
|
-
| `onNodeDrop` | `(dropNode, draggedNode, position, event, operation) => void` | `undefined` | Drop event handler. `dropNode` can be `null` (e.g., drop on empty tree). Position is `'before'`, `'after'`, or `'child'`. Operation is `'move'` or `'copy'` |
|
|
704
|
-
|
|
705
|
-
#### Visual Styling Properties
|
|
706
|
-
| Prop | Type | Default | Description |
|
|
707
|
-
|------|------|---------|-------------|
|
|
708
|
-
| `bodyClass` | `string \| null` | `undefined` | CSS class for tree body |
|
|
709
|
-
| `selectedNodeClass` | `string \| null` | `undefined` | CSS class for selected nodes |
|
|
710
|
-
| `dragOverNodeClass` | `string \| null` | `undefined` | CSS class for nodes being dragged over |
|
|
711
|
-
| `expandIconClass` | `string \| null` | `"ltree-icon-expand"` | CSS class for expand icons |
|
|
712
|
-
| `collapseIconClass` | `string \| null` | `"ltree-icon-collapse"` | CSS class for collapse icons |
|
|
713
|
-
| `leafIconClass` | `string \| null` | `"ltree-icon-leaf"` | CSS class for leaf node icons |
|
|
714
|
-
| `scrollHighlightTimeout` | `number \| null` | `4000` | Duration (ms) for scroll highlight animation |
|
|
715
|
-
| `scrollHighlightClass` | `string \| null` | `'ltree-scroll-highlight'` | CSS class to apply for scroll highlight effect |
|
|
716
|
-
|
|
717
|
-
#### Snippets
|
|
718
|
-
| Snippet | Parameters | Description |
|
|
719
|
-
|---------|------------|-------------|
|
|
720
|
-
| `nodeTemplate` | `(node)` | Custom node template |
|
|
721
|
-
| `treeHeader` | | Tree header content |
|
|
722
|
-
| `treeFooter` | | Tree footer content |
|
|
723
|
-
| `noDataFound` | | Content shown when tree has no data |
|
|
724
|
-
| `dropPlaceholder` | | Content shown in empty drop target tree |
|
|
725
|
-
| `loadingPlaceholder` | | Content shown while `isLoading` is true |
|
|
726
|
-
| `contextMenu` | `(node, closeMenu)` | Context menu template |
|
|
727
|
-
|
|
728
|
-
#### Public Methods
|
|
729
|
-
| Method | Parameters | Description |
|
|
730
|
-
|--------|------------|-------------|
|
|
731
|
-
| `expandNodes` | `nodePath: string` | Expand nodes at specified path |
|
|
732
|
-
| `collapseNodes` | `nodePath: string` | Collapse nodes at specified path |
|
|
733
|
-
| `expandAll` | `nodePath?: string` | Expand all nodes or nodes under path |
|
|
734
|
-
| `collapseAll` | `nodePath?: string` | Collapse all nodes or nodes under path |
|
|
735
|
-
| `filterNodes` | `searchText: string, searchOptions?: SearchOptions` | Filter the tree display using internal search index with optional FlexSearch options |
|
|
736
|
-
| `searchNodes` | `searchText: string \| null \| undefined, searchOptions?: SearchOptions` | Search nodes using internal search index and return matching nodes with optional FlexSearch options |
|
|
737
|
-
| `scrollToPath` | `path: string, options?: ScrollToPathOptions` | Scroll to and highlight a specific node |
|
|
738
|
-
| `update` | `updates: Partial<Props>` | Programmatically update component props from external JavaScript |
|
|
739
|
-
| `addNode` | `parentPath: string, data: T, pathSegment?: string` | Add a new node under the specified parent |
|
|
740
|
-
| `moveNode` | `sourcePath: string, targetPath: string, position: 'before' \| 'after' \| 'child'` | Move a node to a new location |
|
|
741
|
-
| `removeNode` | `path: string, includeDescendants?: boolean` | Remove a node (and optionally its descendants) |
|
|
742
|
-
| `getNodeByPath` | `path: string` | Get a node by its path |
|
|
743
|
-
| `getChildren` | `parentPath: string` | Get direct children of a node |
|
|
744
|
-
| `getSiblings` | `path: string` | Get siblings of a node (including itself) |
|
|
745
|
-
| `updateNode` | `path: string, data: Partial<T>` | Update a node's data properties |
|
|
746
|
-
| `copyNodeWithDescendants` | `sourcePath: string, targetPath: string, position: DropPosition` | Copy a node and its subtree to a new location |
|
|
747
|
-
| `refreshNode` | `path: string` | Force re-render of a specific node |
|
|
748
|
-
| `refreshSiblings` | `path: string` | Force re-render of a node's siblings |
|
|
749
|
-
| `getExpandedPaths` | | Get array of all currently expanded node paths |
|
|
750
|
-
| `setExpandedPaths` | `paths: string[]` | Restore expanded state from saved paths |
|
|
751
|
-
| `getAllData` | | Get all tree data as a flat array |
|
|
752
|
-
| `applyChanges` | | Apply pending changes and refresh the tree |
|
|
753
|
-
| `closeContextMenu` | | Programmatically close the context menu |
|
|
754
|
-
|
|
755
|
-
#### ScrollToPath Options
|
|
756
|
-
|
|
757
|
-
| Option | Type | Default | Description |
|
|
758
|
-
|--------|------|---------|-------------|
|
|
759
|
-
| `expand` | `boolean` | `true` | Automatically expand parent nodes to make target visible |
|
|
760
|
-
| `expandTarget` | `boolean` | `false` | Also expand the target node itself (not just its ancestors) |
|
|
761
|
-
| `highlight` | `boolean` | `true` | Apply temporary highlight animation to the target node |
|
|
762
|
-
| `scrollOptions` | `ScrollIntoViewOptions` | `{ behavior: 'smooth', block: 'center' }` | Native browser scroll options |
|
|
763
|
-
| `containerScroll` | `boolean` | `false` | Scroll only within nearest scrollable ancestor (prevents page scroll) |
|
|
764
|
-
| `containerElement` | `HTMLElement` | `undefined` | Explicit scrollable container element to use for scrolling |
|
|
765
|
-
|
|
766
|
-
**Usage Example:**
|
|
767
|
-
```typescript
|
|
768
|
-
// Basic usage - scroll to path with default options
|
|
769
|
-
await tree.scrollToPath('1.2.3');
|
|
770
|
-
|
|
771
|
-
// Advanced usage - custom options
|
|
772
|
-
await tree.scrollToPath('1.2.3', {
|
|
773
|
-
expand: false, // Don't auto-expand parent nodes
|
|
774
|
-
highlight: false, // Skip highlight animation
|
|
775
|
-
scrollOptions: { // Custom scroll behavior
|
|
776
|
-
behavior: 'instant',
|
|
777
|
-
block: 'start'
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
// Scroll within a scrollable container (prevents page scroll)
|
|
782
|
-
await tree.scrollToPath('1.2.3', { containerScroll: true });
|
|
783
|
-
```
|
|
784
|
-
|
|
785
|
-
**Highlight Classes Example:**
|
|
786
|
-
```svelte
|
|
787
|
-
<!-- Default background highlight -->
|
|
788
|
-
<Tree
|
|
789
|
-
{data}
|
|
790
|
-
idMember="path"
|
|
791
|
-
pathMember="path"
|
|
792
|
-
scrollHighlightClass="ltree-scroll-highlight"
|
|
793
|
-
scrollHighlightTimeout={5000}
|
|
794
|
-
/>
|
|
795
|
-
|
|
796
|
-
<!-- Red arrow highlight -->
|
|
797
|
-
<Tree
|
|
798
|
-
{data}
|
|
799
|
-
idMember="path"
|
|
800
|
-
pathMember="path"
|
|
801
|
-
scrollHighlightClass="ltree-scroll-highlight-arrow"
|
|
802
|
-
scrollHighlightTimeout={3000}
|
|
803
|
-
/>
|
|
804
|
-
|
|
805
|
-
<!-- Custom highlight class -->
|
|
806
|
-
<Tree
|
|
807
|
-
{data}
|
|
808
|
-
idMember="path"
|
|
809
|
-
pathMember="path"
|
|
810
|
-
scrollHighlightClass="my-custom-highlight"
|
|
811
|
-
scrollHighlightTimeout={2000}
|
|
812
|
-
/>
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
**Available Built-in Highlight Classes:**
|
|
816
|
-
- `ltree-scroll-highlight` - Background glow with blue color (default)
|
|
817
|
-
- `ltree-scroll-highlight-arrow` - Red left arrow indicator
|
|
818
|
-
|
|
819
|
-
#### Statistics
|
|
820
|
-
The tree provides real-time statistics about the loaded data:
|
|
821
|
-
|
|
822
|
-
| Property | Type | Description |
|
|
823
|
-
|----------|------|-------------|
|
|
824
|
-
| `statistics` | `{ nodeCount: number; maxLevel: number; filteredNodeCount: number; isIndexing: boolean; pendingIndexCount: number }` | Returns current node count, maximum depth level, filtered nodes count, indexing status, and pending index count |
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
const { nodeCount, maxLevel, filteredNodeCount, isIndexing, pendingIndexCount } = tree.statistics;
|
|
828
|
-
console.log(`Tree has ${nodeCount} nodes with maximum depth of ${maxLevel} levels`);
|
|
829
|
-
if (filteredNodeCount > 0) {
|
|
830
|
-
console.log(`Currently showing ${filteredNodeCount} filtered nodes`);
|
|
831
|
-
}
|
|
832
|
-
if (isIndexing) {
|
|
833
|
-
console.log(`Search indexing in progress: ${pendingIndexCount} nodes pending`);
|
|
834
|
-
}
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
#### External Updates (Vanilla JavaScript)
|
|
838
|
-
|
|
839
|
-
The `update()` method allows you to programmatically update component props from external JavaScript code (outside of Svelte's reactivity system). This is particularly useful for HTML/JavaScript integration or dynamic configuration from non-Svelte code.
|
|
840
|
-
|
|
841
|
-
```javascript
|
|
842
|
-
// Get reference to the tree component
|
|
843
|
-
const treeElement = document.querySelector('#my-tree');
|
|
844
|
-
|
|
845
|
-
// Update multiple props at once
|
|
846
|
-
treeElement.update({
|
|
847
|
-
searchText: 'Production',
|
|
848
|
-
expandLevel: 3,
|
|
849
|
-
shouldDisplayDebugInformation: true,
|
|
850
|
-
data: newDataArray,
|
|
851
|
-
contextMenuXOffset: 10
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
// Update single prop
|
|
855
|
-
treeElement.update({ searchText: 'new search' });
|
|
856
|
-
|
|
857
|
-
// Update data and configuration
|
|
858
|
-
treeElement.update({
|
|
859
|
-
data: fetchedData,
|
|
860
|
-
expandLevel: 5,
|
|
861
|
-
selectedNodeClass: 'custom-selected'
|
|
862
|
-
});
|
|
863
|
-
```
|
|
864
|
-
|
|
865
|
-
**Updatable Properties:**
|
|
866
|
-
All Tree props can be updated except snippets/templates, including:
|
|
867
|
-
- Data and state: `data`, `searchText`, `selectedNode`, `expandLevel`
|
|
868
|
-
- Members: `idMember`, `pathMember`, `displayValueMember`, `searchValueMember`
|
|
869
|
-
- Callbacks: `sortCallback`, `getDisplayValueCallback`, `onNodeClicked`, etc.
|
|
870
|
-
- Visual: `bodyClass`, `selectedNodeClass`, `expandIconClass`, etc.
|
|
871
|
-
- Context menu: `contextMenuCallback`, `contextMenuXOffset`, `contextMenuYOffset`
|
|
872
|
-
- Behavior: `clickBehavior`, `shouldUseInternalSearchIndex`, etc.
|
|
873
|
-
|
|
874
|
-
### Debug Information
|
|
875
|
-
|
|
876
|
-
Enable debug information to see real-time tree statistics and console logging:
|
|
877
|
-
|
|
878
|
-
```svelte
|
|
879
|
-
<Tree
|
|
880
|
-
{data}
|
|
881
|
-
idMember="path"
|
|
882
|
-
pathMember="path"
|
|
883
|
-
shouldDisplayDebugInformation={true}
|
|
884
|
-
/>
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
#### Debug Panel
|
|
888
|
-
The visual debug panel shows:
|
|
889
|
-
- Tree ID
|
|
890
|
-
- Data array length
|
|
891
|
-
- Expand level setting
|
|
892
|
-
- Node count
|
|
893
|
-
- Maximum depth levels
|
|
894
|
-
- Filtered node count (when filtering is active)
|
|
895
|
-
- Search indexing progress (when indexing is active)
|
|
896
|
-
- Currently dragged node
|
|
897
|
-
|
|
898
|
-
#### Console Debug Logging
|
|
899
|
-
When enabled, the component will log detailed information to the browser console including:
|
|
900
|
-
|
|
901
|
-
**Tree Operations:**
|
|
902
|
-
- Data mapping and sorting performance metrics
|
|
903
|
-
- Node filtering and search operations
|
|
904
|
-
- Tree structure changes
|
|
905
|
-
|
|
906
|
-
**Async Search Indexing:**
|
|
907
|
-
- Indexer initialization with batch size
|
|
908
|
-
- Queue management (items added, queue size)
|
|
909
|
-
- Batch processing details (timeout status, items processed, timing)
|
|
910
|
-
- Indexing completion and progress updates
|
|
911
|
-
|
|
912
|
-
This provides valuable insights for performance optimization and troubleshooting, especially when working with large datasets or complex search operations.
|
|
913
|
-
|
|
914
|
-
## Data Structure
|
|
915
|
-
|
|
916
|
-
The component expects hierarchical data with path-based organization:
|
|
917
|
-
|
|
918
|
-
```typescript
|
|
919
|
-
interface NodeData {
|
|
920
|
-
path: string; // e.g., "1.2.3" for hierarchical positioning
|
|
921
|
-
// ... your custom properties
|
|
922
|
-
}
|
|
923
|
-
```
|
|
924
|
-
|
|
925
|
-
### Path Examples
|
|
926
|
-
|
|
927
|
-
- Root level: `"1"`, `"2"`, `"3"`
|
|
928
|
-
- Second level: `"1.1"`, `"1.2"`, `"2.1"`
|
|
929
|
-
- Third level: `"1.1.1"`, `"1.2.1"`, `"2.1.1"`
|
|
930
|
-
|
|
931
|
-
### Sorting Requirements
|
|
932
|
-
|
|
933
|
-
**Important:** For proper tree construction, your `sortCallback` must sort by **level first** to ensure parent nodes are inserted before their children:
|
|
934
|
-
|
|
935
|
-
```typescript
|
|
936
|
-
const sortCallback = (items: LTreeNode<T>[]) => {
|
|
937
|
-
return items.sort((a, b) => {
|
|
938
|
-
// First, sort by level (shallower levels first)
|
|
939
|
-
const aLevel = a.path.split('.').length;
|
|
940
|
-
const bLevel = b.path.split('.').length;
|
|
941
|
-
if (aLevel !== bLevel) {
|
|
942
|
-
return aLevel - bLevel;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// Then sort by your custom criteria
|
|
946
|
-
return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
|
|
947
|
-
});
|
|
948
|
-
};
|
|
949
|
-
```
|
|
950
|
-
|
|
951
|
-
**Why this matters:** If deeper level nodes are processed before their parents, you'll get "Could not find parent node" errors during tree construction. Level-first sorting ensures hierarchical integrity and enables progressive rendering for large datasets.
|
|
952
|
-
|
|
953
|
-
### Insert Result Information
|
|
954
|
-
|
|
955
|
-
The tree provides detailed information about data insertion through the `insertResult` bindable property:
|
|
956
|
-
|
|
957
|
-
```typescript
|
|
958
|
-
interface InsertArrayResult<T> {
|
|
959
|
-
successful: number; // Number of nodes successfully inserted
|
|
960
|
-
failed: Array<{ // Nodes that failed to insert
|
|
961
|
-
node: LTreeNode<T>; // The processed tree node
|
|
962
|
-
originalData: T; // The original data object
|
|
963
|
-
error: string; // Error message (usually "Could not find parent...")
|
|
964
|
-
}>;
|
|
965
|
-
total: number; // Total number of nodes processed
|
|
966
|
-
}
|
|
967
|
-
```
|
|
968
|
-
|
|
969
|
-
#### Usage Example
|
|
970
|
-
|
|
971
|
-
```svelte
|
|
972
|
-
<script lang="ts">
|
|
973
|
-
import { Tree } from '@keenmate/svelte-treeview';
|
|
974
|
-
|
|
975
|
-
let insertResult = $state();
|
|
976
|
-
|
|
977
|
-
const data = [
|
|
978
|
-
{ id: '1', path: '1', name: 'Root' },
|
|
979
|
-
{ id: '1.2', path: '1.2', name: 'Child' }, // Missing parent "1.1"
|
|
980
|
-
{ id: '1.1.1', path: '1.1.1', name: 'Deep' } // Missing parent "1.1"
|
|
981
|
-
];
|
|
982
|
-
|
|
983
|
-
// Check results after tree processes data
|
|
984
|
-
$effect(() => {
|
|
985
|
-
if (insertResult) {
|
|
986
|
-
console.log(`${insertResult.successful} nodes inserted successfully`);
|
|
987
|
-
console.log(`${insertResult.failed.length} nodes failed to insert`);
|
|
988
|
-
|
|
989
|
-
insertResult.failed.forEach(failure => {
|
|
990
|
-
console.log(`Failed: ${failure.originalData.name} - ${failure.error}`);
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
</script>
|
|
995
|
-
|
|
996
|
-
<Tree
|
|
997
|
-
{data}
|
|
998
|
-
idMember="id"
|
|
999
|
-
pathMember="path"
|
|
1000
|
-
displayValueMember="name"
|
|
1001
|
-
bind:insertResult
|
|
1002
|
-
/>
|
|
1003
|
-
```
|
|
1004
|
-
|
|
1005
|
-
#### Benefits
|
|
1006
|
-
|
|
1007
|
-
- **Data Validation**: Identify missing parent nodes in hierarchical data
|
|
1008
|
-
- **Debugging**: Clear error messages with node paths like "Node: 1.1.1 - Could not find parent node: 1.1"
|
|
1009
|
-
- **Data Integrity**: Handle incomplete datasets gracefully
|
|
1010
|
-
- **Search Accuracy**: Failed nodes are excluded from search index, ensuring search results match visible tree
|
|
1011
|
-
- **User Feedback**: Inform users about data issues with detailed failure information
|
|
1012
|
-
|
|
1013
|
-
## Performance
|
|
1014
|
-
|
|
1015
|
-
The component is optimized for large datasets:
|
|
1016
|
-
|
|
1017
|
-
- **Virtual Scroll**: Renders only visible rows (~50 DOM nodes) for trees with 50,000+ nodes
|
|
1018
|
-
- **Flat Rendering Mode**: Single `{#each}` loop instead of recursive components (default, ~12x faster initial render)
|
|
1019
|
-
- **Progressive Rendering**: Batched rendering prevents UI freeze during initial load
|
|
1020
|
-
- **Async Search Indexing**: Uses `requestIdleCallback` for non-blocking search index building
|
|
1021
|
-
- **LTree**: Efficient hierarchical data structure with FlexSearch integration
|
|
1022
|
-
|
|
1023
|
-
### Performance Benchmarks (5500 nodes)
|
|
1024
|
-
|
|
1025
|
-
| Operation | Time |
|
|
1026
|
-
|-----------|------|
|
|
1027
|
-
| Initial render (flat) | ~25ms |
|
|
1028
|
-
| Initial render (virtual) | ~5ms |
|
|
1029
|
-
| Expand/collapse | ~100-150ms |
|
|
1030
|
-
| Search filtering | <50ms |
|
|
1031
|
-
| insertArray | <100ms |
|
|
1032
|
-
|
|
1033
|
-
### Virtual Scroll
|
|
1034
|
-
|
|
1035
|
-
For trees with 10,000+ nodes, enable virtual scroll to keep DOM size constant:
|
|
1036
|
-
|
|
1037
|
-
```svelte
|
|
1038
|
-
<Tree
|
|
1039
|
-
{data}
|
|
1040
|
-
virtualScroll={true}
|
|
1041
|
-
virtualContainerHeight="500px"
|
|
1042
|
-
virtualOverscan={5}
|
|
1043
|
-
/>
|
|
1044
|
-
```
|
|
1045
|
-
|
|
1046
|
-
Virtual scroll auto-measures row height from the first rendered node. Override with `virtualRowHeight={32}` if needed. Requires flat rendering mode (the default).
|
|
1047
|
-
|
|
1048
|
-
### Performance Logging
|
|
1049
|
-
|
|
1050
|
-
Built-in performance measurement for debugging:
|
|
1051
|
-
```typescript
|
|
1052
|
-
import { enablePerfLogging } from '@keenmate/svelte-treeview';
|
|
1053
|
-
enablePerfLogging();
|
|
1054
|
-
|
|
1055
|
-
// Or from browser console:
|
|
1056
|
-
window.components['svelte-treeview'].perf.enable()
|
|
1057
|
-
```
|
|
1058
|
-
|
|
1059
|
-
**Important**: See the [$state.raw() tip](#quick-start) above - using `$state()` instead of `$state.raw()` for tree data can cause 5,000x slowdown!
|
|
1060
|
-
|
|
1061
|
-
## CanvasTree (Canvas-Based Rendering)
|
|
1062
|
-
|
|
1063
|
-
Canvas rendering is available as a separate companion package: [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
|
|
1064
|
-
|
|
1065
|
-
It renders trees on HTML5 Canvas for high-performance visualization with multiple layout modes (tree, balanced, fishbone, radial, box), keyboard navigation, drag & drop, and custom node rendering. Install it separately:
|
|
1066
|
-
|
|
1067
|
-
```bash
|
|
1068
|
-
npm install @keenmate/svelte-treeview-canvas
|
|
1069
|
-
```
|
|
1070
|
-
|
|
1071
|
-
## Development Setup & Contributing
|
|
1072
|
-
|
|
1073
|
-
For developers working on the project, you can use either standard npm commands or the provided Makefile:
|
|
1074
|
-
|
|
1075
|
-
```bash
|
|
1076
|
-
# Using Makefile (recommended for consistency)
|
|
1077
|
-
make setup # or make install
|
|
1078
|
-
make dev
|
|
1079
|
-
|
|
1080
|
-
# Or using standard npm commands
|
|
1081
|
-
npm install
|
|
1082
|
-
npm run dev
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
We welcome contributions! Please see our contributing guidelines for details.
|
|
1086
|
-
|
|
1087
|
-
> **For AI Agents / LLMs**: Comprehensive documentation is available in the `ai/` folder with topic-specific files (basic-setup.txt, drag-drop.txt, performance.txt, etc.). Start with `ai/INDEX.txt` for navigation.
|
|
1088
|
-
|
|
1089
|
-
## License
|
|
1090
|
-
|
|
1091
|
-
MIT License - see LICENSE file for details.
|
|
1092
|
-
|
|
1093
|
-
## Support
|
|
1094
|
-
|
|
1095
|
-
- **GitHub Issues**: [Report bugs or request features](https://github.com/keenmate/svelte-treeview/issues)
|
|
1096
|
-
- **Live demo & docs**: [svelte-treeview.keenmate.dev](https://svelte-treeview.keenmate.dev)
|
|
1097
|
-
|
|
1098
|
-
---
|
|
1099
|
-
|
|
1100
|
-
Built with ❤️ by [KeenMate](https://github.com/keenmate)
|
|
1
|
+
# @keenmate/svelte-treeview
|
|
2
|
+
|
|
3
|
+
A high-performance, feature-rich hierarchical tree view component for Svelte 5 with drag & drop support, search functionality, and flexible data structures using LTree.
|
|
4
|
+
|
|
5
|
+
## What is it
|
|
6
|
+
|
|
7
|
+
`@keenmate/svelte-treeview` is a hierarchical tree-view component for Svelte 5 apps. It renders flat, path-keyed data (`"1"`, `"1.2"`, `"1.2.3"`) into an expandable tree with built-in drag & drop, three-level selection (focus / multi-highlight / checkboxes), context menus, integrated FlexSearch filtering, and virtual scrolling for 50,000+ nodes.
|
|
8
|
+
|
|
9
|
+
It's aimed at Svelte 5 developers building file browsers, org charts, navigation trees, settings dialogs, or any UI that displays hierarchical data. The core (data structure, expand/collapse, search, drag-and-drop logic) is decoupled from the renderer via `TreeProvider` + `TreeController`, so you can plug in custom HTML, Canvas, or SVG renderers on the same engine.
|
|
10
|
+
|
|
11
|
+
The component ships standalone with sensible light/dark defaults and integrates cleanly with [Pure Admin](https://pureadmin.io/) and the wider `@keenmate/*` design-token suite via the `--base-*` CSS variable contract.
|
|
12
|
+
|
|
13
|
+
### How it differs from `@keenmate/web-treeview`
|
|
14
|
+
|
|
15
|
+
There's a vanilla-TypeScript sibling — [`@keenmate/web-treeview`](https://github.com/KeenMate/web-treeview) — built on the same LTree path-based engine. Same logical tree, different DOM strategy. Neither is "more mature"; they target different priorities.
|
|
16
|
+
|
|
17
|
+
| | svelte-treeview | web-treeview |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Framework | Svelte 5 | Vanilla TS web component |
|
|
20
|
+
| Rendering modes | Recursive (default) + flat | Flat only |
|
|
21
|
+
| Children DOM | `.stv__children` wrapper (recursive mode) | None — siblings under `.wtv__tree` |
|
|
22
|
+
| Indent math | `level × indent` | `(level − 1) × indent` (root at zero offset) |
|
|
23
|
+
| Virtual scroll | Flat mode only | Built-in (three-div spacer / `translateY`) |
|
|
24
|
+
| Label markup | `<span class="stv__node-label">` by default — replace via `nodeTemplate` snippet | `<span class="wtv__node-label">` by default — replace via `renderNodeCallback` |
|
|
25
|
+
| Checkbox | `<label>` + custom `.stv__checkbox-box` span | Bare native `<input type="checkbox">` |
|
|
26
|
+
| Update mechanism | Svelte 5 runes + per-node `_rev` keyed `{#each}` | Imperative reconciler diffing `data-rev` / `data-expanded` attributes |
|
|
27
|
+
|
|
28
|
+
svelte-treeview is broader (two rendering modes, easier vertical guide lines via `.stv__children`); web-treeview is purpose-built for virtual scrolling over large datasets with a flatter DOM.
|
|
29
|
+
|
|
30
|
+
## Live Demo
|
|
31
|
+
|
|
32
|
+
Browse interactive code examples and the full API reference at **[svelte-treeview.keenmate.dev](https://svelte-treeview.keenmate.dev)**
|
|
33
|
+
|
|
34
|
+
## What's New in v5.0.0-rc11
|
|
35
|
+
|
|
36
|
+
- **Selection model — three symmetric imperative families (highlight / select / focus)** — The imperative API had drifted into aliases and asymmetries: `selectNode`/`selectNodes` secretly drove the *highlight* set, the highlight set cleared with `clearHighlight()` while checkboxes cleared with `deselectAll()`, and `highlightNodes()` silently replaced rather than added. The surface is now three concerns × one shape — **highlight** (`highlightNode`/`highlightNodes`/`setHighlightedPaths`/`highlightAll`/`clearHighlight`) for the UI multi-select set, **select** (`selectNode`/`selectNodes`/`setSelectedPaths`/`selectAll`/`deselectNode`/`clearSelection`) for the checkbox set, and **focus** (`focusNode`/`clearFocus`) for the single cursor. Two shared types, `HighlightMode` and `TreeMutationOptions { silent? }`, replace the inline literals. Breaking within the RC: `deselectAll` → `clearSelection`, `highlightNodes` is now additive, and `selectNode`/`selectNodes` are now real checkbox setters.
|
|
37
|
+
- **CSS — BEM rename (`ltree` → `stv`) and `is*/should*` boolean props** — Every class and variable now follows the BlissFramework BEM rule with the registered `stv` prefix: `--ltree-*` → `--stv-*`, `.ltree-node` → `.stv__node`, state classes as modifiers (`.stv__node-content--drag-over`), and the highlight family folded in (`.stv__node-content--highlight-bold`). Ten ambiguous boolean props were renamed to read as predicates — `showCheckboxes` → `shouldShowCheckboxes`, `virtualScroll` → `isVirtualScrollEnabled`, `allowCopy` → `isCopyAllowed`, and so on. No aliases — within the RC cycle consumers search-and-replace each prop and CSS selector.
|
|
38
|
+
- **Theming — dark mode rewritten to Strategy B (`color-scheme` + `light-dark()`)** — `dark-mode.css` collapsed from ~140 lines of per-token redeclaration to ~45 lines that flip `color-scheme` on the framework-theme and per-instance selectors, letting the `light-dark(<light>, <dark>)` fallbacks in `variables.css` resolve the dark branch automatically. Consumer `--base-*` overrides now survive the dark branch instead of being clobbered by hardcoded literals, and adding a new themeable variable no longer means touching every signal block.
|
|
39
|
+
- **Highlight & focus styling — marker is a fallback, not a fighter, plus a new glow flavor** — The always-on `.stv__node-content--highlighted` marker used to paint its default background *underneath* any configured `highlightedNodeClass`, so picking "Bold" showed bold text over the marker tint. It's now applied only when no highlight class is set, so a configured class is the sole source of styling — no `!important` needed. `.stv__node-content--focused` became a pure hook (no default outline, honoring the demo's "None" option), and a fifth built-in highlight flavor `--highlight-glow` (a soft accent ring) joined bold/border/brackets/fill.
|
|
40
|
+
- **Drag & drop — pinned nodes are respected, copied/added nodes behave** — Three correctness fixes: multi-drag now filters out locked (`isDraggable=false`) nodes that merely happen to be in the highlight set instead of dragging them along; `addNode` (and therefore `copyNodeWithDescendants`) now seeds per-node flags from the `getIs*Callback`/`*Member` props, so nodes dropped into a second tree are no longer frozen non-draggable and drop-rejecting; and desktop drag-drop now honors `node.isDropAllowed` (previously only the touch path did). Covered by new vitest + Playwright regression suites.
|
|
41
|
+
- **Context menus — positioning ported to `@floating-ui/dom`** — Root menu and submenus are now placed by `computePosition` + `autoUpdate` with `flip()`/`shift()` instead of raw inline `left/top` and CSS `:hover`, so menus flip above the cursor near the viewport bottom and submenus slide sideways instead of clipping. `contextMenuXOffset`/`contextMenuYOffset` keep their exact semantics.
|
|
42
|
+
- **Docs — README cut to ~350 lines with topical docs split out** — The README dropped from 1103 to 347 lines: Advanced Usage, Styling, and API Reference moved into `docs/usage.md`, `docs/theming.md`, `docs/examples.md`, and `docs/accessibility.md`, with a new intro and a Demos & docs link block up top.
|
|
43
|
+
|
|
44
|
+
## What's New in v5.0.0-rc10
|
|
45
|
+
|
|
46
|
+
- **Built-in dark mode — all four canonical signals covered** — a `dark-mode.css` partial flips the tree's surface, text, border, and accent palette when any of the canonical signals is active: OS preference (`prefers-color-scheme: dark`), page `<html style="color-scheme: dark">` resolved via `light-dark()`, framework theme classes (`[data-theme="dark"]`, `[data-bs-theme="dark"]`, `.dark`), and the new per-instance `theme` prop. Symmetric `light` selectors let a single tree force light on an otherwise-dark page. Zero JavaScript — pure CSS resolution.
|
|
47
|
+
- **`theme` prop on `<Tree>` — per-instance dark/light override** — `'dark' | 'light' | null | undefined`. Forwarded to the root `.stv__container` as `data-theme`, where per-instance CSS selectors take over and beat ambient signals. Leave `undefined` to inherit from the page. (rc10 originally landed this on `.ltree-container`; the BEM rename to `.stv__container` shipped in rc12.)
|
|
48
|
+
- **CSS variables rescoped from `:root` to `.stv__container` — subtree theming actually works** — mirrors the `:host`-scoped pattern from sibling `@keenmate/*` components and is the only way `--base-*` theming flows at subtree scope. A wrapper around the tree that sets `--base-accent-color: red` now re-tints the tree (it previously had no effect because the substitution was frozen at `:root`). Multiple trees on the same page can be themed differently via wrapper-scoped `--base-*` overrides. Consumer note: setting `--stv-*` directly on a wrapper no longer cascades — use `--base-*` on the wrapper or target `.stv__container` explicitly.
|
|
49
|
+
- **`--stv-bg` — the tree paints its own surface** — `.stv__container` gets a `background: var(--stv-bg)` so consumers don't need to wrap the tree in a colored container for a visible surface. Override to `transparent` to restore the pre-rc10 layered behavior. Companion `--stv-elevated-bg` reads through the `--base-elevated-bg` chain for floating chrome (context menu).
|
|
50
|
+
- **CSS file layout aligned with the Bliss web-component guidelines** — all `_xxx.css` partials renamed to `xxx.css`, `main.css` now declares `@layer variables, component, overrides;` and imports each partial into the matching layer. Consumers' unlayered overrides automatically beat every rule in the library — no `!important` needed. Note: if your app ships an unlayered universal CSS reset (`* { padding: 0 }` from normalize / Bootstrap / Tailwind preflight / etc.), wrap it in a low-priority `@layer reset` or the tree's defaults won't apply.
|
|
51
|
+
- **`getIsDropAllowedCallback` prop + `getIsDraggableCallback` seeded at insert-time** — callback variant for `isDropAllowedMember`, matching the pattern rc09 introduced for `getIsExpandedCallback` / `getIsSelectableCallback` / `getIsSelectedCallback`. Also, the existing `getIsDraggableCallback` prop is now actually applied during the seed walk — previously it was only consumed lazily in some paths.
|
|
52
|
+
- **Bug fixes — virtual scroll + `clickBehavior='select'` double-click** — virtual scroll no longer gets stuck at bottom after a filter shrinks the tree. Double-click to expand in `clickBehavior='select'` mode finally works (the browser's native `dblclick` event couldn't fire because the first click destroyed the row via the focus re-render; the controller now detects the double manually).
|
|
53
|
+
|
|
54
|
+
## v5.0: Core/Renderer Split + Virtual Scroll
|
|
55
|
+
|
|
56
|
+
> [!IMPORTANT]
|
|
57
|
+
> **In version 5, the tree core (data structure, expand/collapse, search, drag & drop logic) has been completely separated from the renderer.** The architecture is open for you to build your own custom renderers on top of the same core via `TreeProvider` and `TreeController`.
|
|
58
|
+
|
|
59
|
+
**Key changes in v5:**
|
|
60
|
+
- **Core/Renderer split**: Use the built-in HTML `Tree` renderer, or create custom visualizations (Canvas, WebGL, SVG) via `TreeProvider` + `TreeController`
|
|
61
|
+
- **Virtual scroll**: Render 50,000+ node trees smoothly with `isVirtualScrollEnabled={true}` — only ~50 DOM nodes at any time
|
|
62
|
+
- **Canvas companion**: For canvas rendering, install [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
|
|
63
|
+
- **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` (CSS classes and events updated accordingly)
|
|
64
|
+
|
|
65
|
+
### Rendering Modes
|
|
66
|
+
|
|
67
|
+
| Mode | Props | DOM Nodes | Best For |
|
|
68
|
+
|------|-------|-----------|----------|
|
|
69
|
+
| Recursive | `isFlatRenderingEnabled={false}` | All | Small trees (<100 nodes) |
|
|
70
|
+
| Flat (default) | `isFlatRenderingEnabled={true}` | All | Medium trees (100–10K) |
|
|
71
|
+
| Virtual | `isVirtualScrollEnabled={true}` | ~50 | Large trees (10K+) |
|
|
72
|
+
|
|
73
|
+
```svelte
|
|
74
|
+
<!-- Virtual scroll for large trees -->
|
|
75
|
+
<Tree {data} isVirtualScrollEnabled={true} virtualContainerHeight="500px" />
|
|
76
|
+
|
|
77
|
+
<!-- Flat mode (default) with progressive batching -->
|
|
78
|
+
<Tree {data} isProgressiveRender={true} initialBatchSize={20} maxBatchSize={500} />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Features
|
|
82
|
+
|
|
83
|
+
- **Svelte 5 Native**: Built specifically for Svelte 5 with full support for runes and modern Svelte patterns
|
|
84
|
+
- **High Performance**: Flat rendering with progressive loading, virtual scroll for 50,000+ nodes
|
|
85
|
+
- **Drag & Drop**: Built-in drag and drop with position control (before/after/child), touch support, and async validation
|
|
86
|
+
- **Tree Editing**: Built-in methods for add, move, remove operations with automatic path management
|
|
87
|
+
- **Search & Filter**: Integrated FlexSearch for fast, full-text search capabilities
|
|
88
|
+
- **Flexible Data Sources**: Works with any hierarchical data structure
|
|
89
|
+
- **Multi-Select**: Ctrl+click toggle, Shift+click range select (visual or logical mode), `selectedPaths` bindable, selection-aware context menus
|
|
90
|
+
- **Context Menus**: Dynamic right-click menus with shortcuts, submenus, named dividers, and two API approaches (callback or Svelte components)
|
|
91
|
+
- **Visual Customization**: Extensive styling options and icon customization
|
|
92
|
+
- **TypeScript Support**: Full TypeScript support with comprehensive type definitions
|
|
93
|
+
- **Accessibility**: Built with accessibility in mind
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install @keenmate/svelte-treeview
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Importing Styles
|
|
102
|
+
|
|
103
|
+
The component requires CSS to display correctly. Import the styles in your app:
|
|
104
|
+
|
|
105
|
+
**JavaScript import** (in your main.js/main.ts or Vite/Webpack entry):
|
|
106
|
+
```javascript
|
|
107
|
+
import '@keenmate/svelte-treeview/styles.css';
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Svelte component import:**
|
|
111
|
+
```svelte
|
|
112
|
+
<style>
|
|
113
|
+
@import '@keenmate/svelte-treeview/styles.css';
|
|
114
|
+
</style>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Quick Start
|
|
118
|
+
|
|
119
|
+
```svelte
|
|
120
|
+
<script lang="ts">
|
|
121
|
+
import { Tree } from '@keenmate/svelte-treeview';
|
|
122
|
+
|
|
123
|
+
const data = [
|
|
124
|
+
{ path: '1', name: 'Documents', type: 'folder' },
|
|
125
|
+
{ path: '1.1', name: 'Projects', type: 'folder' },
|
|
126
|
+
{ path: '1.1.1', name: 'Project A', type: 'folder' },
|
|
127
|
+
{ path: '1.1.2', name: 'Project B', type: 'folder' },
|
|
128
|
+
{ path: '2', name: 'Pictures', type: 'folder' },
|
|
129
|
+
{ path: '2.1', name: 'Vacation', type: 'folder' }
|
|
130
|
+
];
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<Tree
|
|
134
|
+
{data}
|
|
135
|
+
idMember="path"
|
|
136
|
+
pathMember="path"
|
|
137
|
+
displayValueMember="name"
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> [!TIP]
|
|
142
|
+
> **Performance tip:** When passing large arrays (1000+ items) to the Tree component, use `$state.raw()` instead of `$state()` to avoid severe performance issues. Svelte 5's `$state()` creates deep proxies — with thousands of items this causes up to 5,000x slowdown. The array itself remains reactive; only individual items lose deep reactivity (which Tree doesn't need).
|
|
143
|
+
> ```typescript
|
|
144
|
+
> // BAD - Each item becomes a Proxy
|
|
145
|
+
> let treeData = $state<TreeNode[]>([])
|
|
146
|
+
>
|
|
147
|
+
> // GOOD - Items remain plain objects
|
|
148
|
+
> let treeData = $state.raw<TreeNode[]>([])
|
|
149
|
+
> ```
|
|
150
|
+
|
|
151
|
+
## Demos & docs
|
|
152
|
+
|
|
153
|
+
- 🚀 [Live demo](https://svelte-treeview.keenmate.dev) — interactive examples and the full feature gallery
|
|
154
|
+
- 📘 [Usage / API reference](./docs/usage.md) — every Prop, Method, Event, and Snippet
|
|
155
|
+
- 🎨 [Theming contract](./docs/theming.md) — `--base-*` tokens, `--stv-*` variables, dark mode, cascade layers
|
|
156
|
+
- 📚 [Examples / cookbook](./docs/examples.md) — node templates, search, drag & drop, tree editing, context menus
|
|
157
|
+
- ♿ [Accessibility](./docs/accessibility.md) — keyboard navigation, focus management, selection model
|
|
158
|
+
- 📒 [Release history](./CHANGELOG.md)
|
|
159
|
+
|
|
160
|
+
## Data Structure
|
|
161
|
+
|
|
162
|
+
The component expects hierarchical data with path-based organization:
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
interface NodeData {
|
|
166
|
+
path: string; // e.g., "1.2.3" for hierarchical positioning
|
|
167
|
+
// ... your custom properties
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Path Examples
|
|
172
|
+
|
|
173
|
+
- Root level: `"1"`, `"2"`, `"3"`
|
|
174
|
+
- Second level: `"1.1"`, `"1.2"`, `"2.1"`
|
|
175
|
+
- Third level: `"1.1.1"`, `"1.2.1"`, `"2.1.1"`
|
|
176
|
+
|
|
177
|
+
### Sorting Requirements
|
|
178
|
+
|
|
179
|
+
**Important:** For proper tree construction, your `sortCallback` must sort by **level first** to ensure parent nodes are inserted before their children:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const sortCallback = (items: LTreeNode<T>[]) => {
|
|
183
|
+
return items.sort((a, b) => {
|
|
184
|
+
// First, sort by level (shallower levels first)
|
|
185
|
+
const aLevel = a.path.split('.').length;
|
|
186
|
+
const bLevel = b.path.split('.').length;
|
|
187
|
+
if (aLevel !== bLevel) {
|
|
188
|
+
return aLevel - bLevel;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Then sort by your custom criteria
|
|
192
|
+
return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Why this matters:** If deeper level nodes are processed before their parents, you'll get "Could not find parent node" errors during tree construction. Level-first sorting ensures hierarchical integrity and enables progressive rendering for large datasets.
|
|
198
|
+
|
|
199
|
+
### Insert Result Information
|
|
200
|
+
|
|
201
|
+
The tree provides detailed information about data insertion through the `insertResult` bindable property:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
interface InsertArrayResult<T> {
|
|
205
|
+
successful: number; // Number of nodes successfully inserted
|
|
206
|
+
failed: Array<{ // Nodes that failed to insert
|
|
207
|
+
node: LTreeNode<T>; // The processed tree node
|
|
208
|
+
originalData: T; // The original data object
|
|
209
|
+
error: string; // Error message (usually "Could not find parent...")
|
|
210
|
+
}>;
|
|
211
|
+
total: number; // Total number of nodes processed
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Usage Example
|
|
216
|
+
|
|
217
|
+
```svelte
|
|
218
|
+
<script lang="ts">
|
|
219
|
+
import { Tree } from '@keenmate/svelte-treeview';
|
|
220
|
+
|
|
221
|
+
let insertResult = $state();
|
|
222
|
+
|
|
223
|
+
const data = [
|
|
224
|
+
{ id: '1', path: '1', name: 'Root' },
|
|
225
|
+
{ id: '1.2', path: '1.2', name: 'Child' }, // Missing parent "1.1"
|
|
226
|
+
{ id: '1.1.1', path: '1.1.1', name: 'Deep' } // Missing parent "1.1"
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
// Check results after tree processes data
|
|
230
|
+
$effect(() => {
|
|
231
|
+
if (insertResult) {
|
|
232
|
+
console.log(`${insertResult.successful} nodes inserted successfully`);
|
|
233
|
+
console.log(`${insertResult.failed.length} nodes failed to insert`);
|
|
234
|
+
|
|
235
|
+
insertResult.failed.forEach(failure => {
|
|
236
|
+
console.log(`Failed: ${failure.originalData.name} - ${failure.error}`);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
</script>
|
|
241
|
+
|
|
242
|
+
<Tree
|
|
243
|
+
{data}
|
|
244
|
+
idMember="id"
|
|
245
|
+
pathMember="path"
|
|
246
|
+
displayValueMember="name"
|
|
247
|
+
bind:insertResult
|
|
248
|
+
/>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Benefits
|
|
252
|
+
|
|
253
|
+
- **Data Validation**: Identify missing parent nodes in hierarchical data
|
|
254
|
+
- **Debugging**: Clear error messages with node paths like "Node: 1.1.1 - Could not find parent node: 1.1"
|
|
255
|
+
- **Data Integrity**: Handle incomplete datasets gracefully
|
|
256
|
+
- **Search Accuracy**: Failed nodes are excluded from search index, ensuring search results match visible tree
|
|
257
|
+
- **User Feedback**: Inform users about data issues with detailed failure information
|
|
258
|
+
|
|
259
|
+
## Performance
|
|
260
|
+
|
|
261
|
+
The component is optimized for large datasets:
|
|
262
|
+
|
|
263
|
+
- **Virtual Scroll**: Renders only visible rows (~50 DOM nodes) for trees with 50,000+ nodes
|
|
264
|
+
- **Flat Rendering Mode**: Single `{#each}` loop instead of recursive components (default, ~12x faster initial render)
|
|
265
|
+
- **Progressive Rendering**: Batched rendering prevents UI freeze during initial load
|
|
266
|
+
- **Async Search Indexing**: Uses `requestIdleCallback` for non-blocking search index building
|
|
267
|
+
- **LTree**: Efficient hierarchical data structure with FlexSearch integration
|
|
268
|
+
|
|
269
|
+
### Performance Benchmarks (5500 nodes)
|
|
270
|
+
|
|
271
|
+
| Operation | Time |
|
|
272
|
+
|-----------|------|
|
|
273
|
+
| Initial render (flat) | ~25ms |
|
|
274
|
+
| Initial render (virtual) | ~5ms |
|
|
275
|
+
| Expand/collapse | ~100-150ms |
|
|
276
|
+
| Search filtering | <50ms |
|
|
277
|
+
| insertArray | <100ms |
|
|
278
|
+
|
|
279
|
+
### Virtual Scroll
|
|
280
|
+
|
|
281
|
+
For trees with 10,000+ nodes, enable virtual scroll to keep DOM size constant:
|
|
282
|
+
|
|
283
|
+
```svelte
|
|
284
|
+
<Tree
|
|
285
|
+
{data}
|
|
286
|
+
isVirtualScrollEnabled={true}
|
|
287
|
+
virtualContainerHeight="500px"
|
|
288
|
+
virtualOverscan={5}
|
|
289
|
+
/>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Virtual scroll auto-measures row height from the first rendered node. Override with `virtualRowHeight={32}` if needed. Requires flat rendering mode (the default).
|
|
293
|
+
|
|
294
|
+
### Performance Logging
|
|
295
|
+
|
|
296
|
+
Built-in performance measurement for debugging:
|
|
297
|
+
```typescript
|
|
298
|
+
import { enablePerfLogging } from '@keenmate/svelte-treeview';
|
|
299
|
+
enablePerfLogging();
|
|
300
|
+
|
|
301
|
+
// Or from browser console:
|
|
302
|
+
window.components['svelte-treeview'].perf.enable()
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Important**: See the [$state.raw() tip](#quick-start) above - using `$state()` instead of `$state.raw()` for tree data can cause 5,000x slowdown!
|
|
306
|
+
|
|
307
|
+
## CanvasTree (Canvas-Based Rendering)
|
|
308
|
+
|
|
309
|
+
Canvas rendering is available as a separate companion package: [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
|
|
310
|
+
|
|
311
|
+
It renders trees on HTML5 Canvas for high-performance visualization with multiple layout modes (tree, balanced, fishbone, radial, box), keyboard navigation, drag & drop, and custom node rendering. Install it separately:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npm install @keenmate/svelte-treeview-canvas
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Development Setup & Contributing
|
|
318
|
+
|
|
319
|
+
For developers working on the project, you can use either standard npm commands or the provided Makefile:
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Using Makefile (recommended for consistency)
|
|
323
|
+
make setup # or make install
|
|
324
|
+
make dev
|
|
325
|
+
|
|
326
|
+
# Or using standard npm commands
|
|
327
|
+
npm install
|
|
328
|
+
npm run dev
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
We welcome contributions! Please see our contributing guidelines for details.
|
|
332
|
+
|
|
333
|
+
> **For AI Agents / LLMs**: Comprehensive documentation is available in the `ai/` folder with topic-specific files (basic-setup.txt, drag-drop.txt, performance.txt, etc.). Start with `ai/INDEX.txt` for navigation.
|
|
334
|
+
|
|
335
|
+
## About
|
|
336
|
+
|
|
337
|
+
Authored and maintained by [KeenMate](https://keenmate.com/).
|
|
338
|
+
The component ships standalone with sensible light/dark defaults;
|
|
339
|
+
when mounted inside [Pure Admin](https://pureadmin.io/) — or any
|
|
340
|
+
host that publishes the `--base-*` taxonomy via
|
|
341
|
+
[`@keenmate/theme-designer`](https://www.npmjs.com/package/@keenmate/theme-designer) — it adopts the host's colors,
|
|
342
|
+
typography, and sizing automatically. There is no runtime
|
|
343
|
+
dependency on Pure Admin; the integration is opt-in via CSS
|
|
344
|
+
variables.
|
|
345
|
+
|
|
346
|
+
## Built with BlissFramework
|
|
347
|
+
|
|
348
|
+
Follows the [BlissFramework component guidelines](https://blissframework.dev/)
|
|
349
|
+
for structure, theming, color-scheme, and accessibility.
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
MIT License - see LICENSE file for details.
|
|
354
|
+
|
|
355
|
+
## Support
|
|
356
|
+
|
|
357
|
+
- **GitHub Issues**: [Report bugs or request features](https://github.com/keenmate/svelte-treeview/issues)
|
|
358
|
+
- **Live demo & docs**: [svelte-treeview.keenmate.dev](https://svelte-treeview.keenmate.dev)
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
Built with ❤️ by [KeenMate](https://github.com/keenmate)
|