@keenmate/svelte-treeview 5.0.0-rc01 → 5.0.0-rc03
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/README.md +148 -175
- package/ai/INDEX.txt +310 -0
- package/ai/advanced-patterns.txt +506 -0
- package/ai/basic-setup.txt +336 -0
- package/ai/context-menu.txt +349 -0
- package/ai/data-handling.txt +390 -0
- package/ai/drag-drop.txt +397 -0
- package/ai/events-callbacks.txt +382 -0
- package/ai/import-patterns.txt +271 -0
- package/ai/performance.txt +349 -0
- package/ai/search-features.txt +359 -0
- package/ai/styling-theming.txt +354 -0
- package/ai/tree-editing.txt +423 -0
- package/ai/typescript-types.txt +357 -0
- package/dist/components/ContextMenuDivider.svelte +13 -0
- package/dist/components/ContextMenuDivider.svelte.d.ts +6 -0
- package/dist/components/ContextMenuItem.svelte +69 -0
- package/dist/components/ContextMenuItem.svelte.d.ts +14 -0
- package/dist/components/Node.svelte +51 -57
- package/dist/components/Node.svelte.d.ts +2 -2
- package/dist/components/Tree.svelte +258 -49
- package/dist/components/Tree.svelte.d.ts +38 -14
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/core/TreeController.svelte.d.ts +55 -5
- package/dist/core/TreeController.svelte.js +215 -26
- package/dist/global-api.d.ts +1 -1
- package/dist/global-api.js +5 -5
- package/dist/index.d.ts +7 -10
- package/dist/index.js +5 -8
- package/dist/logger.d.ts +7 -6
- package/dist/logger.js +0 -2
- package/dist/ltree/indexer.js +1 -1
- package/dist/ltree/ltree-node.svelte.d.ts +1 -0
- package/dist/ltree/ltree-node.svelte.js +1 -0
- package/dist/ltree/ltree.svelte.d.ts +1 -1
- package/dist/ltree/ltree.svelte.js +94 -59
- package/dist/ltree/types.d.ts +17 -5
- package/dist/perf-logger.d.ts +2 -1
- package/dist/perf-logger.js +0 -2
- package/dist/styles/main.scss +129 -12
- package/dist/styles.css +101 -9
- package/dist/styles.css.map +1 -1
- package/dist/vendor/loglevel/index.d.ts +55 -2
- package/dist/vendor/loglevel/prefix.d.ts +23 -2
- package/package.json +96 -95
- package/dist/canvas/CanvasTree.svelte +0 -1657
- package/dist/canvas/CanvasTree.svelte.d.ts +0 -148
- package/dist/canvas/canvas-interaction.d.ts +0 -67
- package/dist/canvas/canvas-interaction.js +0 -590
- package/dist/canvas/canvas-layout-balanced.d.ts +0 -18
- package/dist/canvas/canvas-layout-balanced.js +0 -212
- package/dist/canvas/canvas-layout-box.d.ts +0 -10
- package/dist/canvas/canvas-layout-box.js +0 -194
- package/dist/canvas/canvas-layout-fishbone.d.ts +0 -11
- package/dist/canvas/canvas-layout-fishbone.js +0 -349
- package/dist/canvas/canvas-layout-radial.d.ts +0 -21
- package/dist/canvas/canvas-layout-radial.js +0 -233
- package/dist/canvas/canvas-layout-sunburst.d.ts +0 -27
- package/dist/canvas/canvas-layout-sunburst.js +0 -457
- package/dist/canvas/canvas-layout.d.ts +0 -11
- package/dist/canvas/canvas-layout.js +0 -509
- package/dist/canvas/canvas-renderer.d.ts +0 -46
- package/dist/canvas/canvas-renderer.js +0 -828
- package/dist/canvas/canvas-text.d.ts +0 -11
- package/dist/canvas/canvas-text.js +0 -64
- package/dist/canvas/canvas-theme.d.ts +0 -58
- package/dist/canvas/canvas-theme.js +0 -128
- package/dist/canvas/types.d.ts +0 -187
- package/dist/canvas/types.js +0 -1
- package/dist/ltree/ltree-demo.d.ts +0 -2
- package/dist/ltree/ltree-demo.js +0 -90
- package/dist/vendor/loglevel/loglevel-esm.d.ts +0 -2
- package/dist/vendor/loglevel/loglevel-plugin-prefix-esm.d.ts +0 -7
- package/dist/vendor/loglevel/loglevel-plugin-prefix.d.ts +0 -2
package/README.md
CHANGED
|
@@ -6,74 +6,52 @@ A high-performance, feature-rich hierarchical tree view component for Svelte 5 w
|
|
|
6
6
|
|
|
7
7
|
Browse interactive code examples and the full API reference at **[svelte-treeview.keenmate.dev](https://svelte-treeview.keenmate.dev)**
|
|
8
8
|
|
|
9
|
-
## v5.0
|
|
9
|
+
## What's New in v5.0.0-rc03
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
## New in v4.7: Per-Node Drop Position Restrictions
|
|
11
|
+
- **Unified Context Menu API**: New shared type system (`ContextMenuItem`, `ContextMenuDivider`, `ContextMenuEntry`) used by both svelte-treeview and canvas-tree. Breaking: `title` → `label`, `callback` → `onclick`, `isDivider` → separate `ContextMenuDivider` type.
|
|
12
|
+
- **Context menu features**: Keyboard shortcuts, nested submenus, named dividers (`──── Section ────`), `isVisible`/`isDisabled` per item, `className="danger"`, async `onclick`.
|
|
13
|
+
- **Svelte context menu components**: `ContextMenuItemC` and `ContextMenuDividerC` for declarative snippet-based menus alongside the callback approach.
|
|
14
|
+
- **Accordion expand**: `accordionExpand={true}` — expanding a node auto-collapses its siblings.
|
|
15
|
+
- **Toggle icon mode**: `toggleIconMode="rotate"` (default) smoothly rotates the expand icon vs `"swap"` which switches between two icons.
|
|
16
|
+
- **Fix**: Expand/collapse icon not updating in flat rendering mode.
|
|
17
|
+
- **Fix**: `vite.config.ts` no longer requires `@types/node`.
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
> **You can now restrict which drop positions (above/below/child) are allowed per node.**
|
|
19
|
+
## v5.0: Core/Renderer Split + Virtual Scroll
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Files can only have siblings, trash only accepts children
|
|
27
|
-
function getAllowedDropPositions(node) {
|
|
28
|
-
if (node.data?.type === 'file') return ['above', 'below'];
|
|
29
|
-
if (node.data?.type === 'trash') return ['child'];
|
|
30
|
-
return undefined; // all positions allowed (default)
|
|
31
|
-
}
|
|
32
|
-
```
|
|
21
|
+
> [!IMPORTANT]
|
|
22
|
+
> **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`.
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
**Key changes in v5:**
|
|
25
|
+
- **Core/Renderer split**: Use the built-in HTML `Tree` renderer, or create custom visualizations (Canvas, WebGL, SVG) via `TreeProvider` + `TreeController`
|
|
26
|
+
- **Virtual scroll**: Render 50,000+ node trees smoothly with `virtualScroll={true}` — only ~50 DOM nodes at any time
|
|
27
|
+
- **Canvas companion**: For canvas rendering, install [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
|
|
28
|
+
- **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` (CSS classes and events updated accordingly)
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
> **The tree now uses progressive flat rendering by default for significantly improved performance.**
|
|
30
|
+
### Rendering Modes
|
|
38
31
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
| Mode | Props | DOM Nodes | Best For |
|
|
33
|
+
|------|-------|-----------|----------|
|
|
34
|
+
| Recursive | `useFlatRendering={false}` | All | Small trees (<100 nodes) |
|
|
35
|
+
| Flat (default) | `useFlatRendering={true}` | All | Medium trees (100–10K) |
|
|
36
|
+
| Virtual | `virtualScroll={true}` | ~50 | Large trees (10K+) |
|
|
44
37
|
|
|
45
|
-
**Configuration options:**
|
|
46
38
|
```svelte
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
useFlatRendering={true} <!-- Default: true (flat mode) -->
|
|
50
|
-
progressiveRender={true} <!-- Default: true (batched rendering) -->
|
|
51
|
-
initialBatchSize={20} <!-- First batch size (default: 20) -->
|
|
52
|
-
maxBatchSize={500} <!-- Maximum batch size cap (default: 500) -->
|
|
53
|
-
/>
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Exponential batching:** The first batch renders 20 nodes instantly, then doubles each frame (20 → 40 → 80 → 160 → 320 → 500...) for optimal perceived performance.
|
|
39
|
+
<!-- Virtual scroll for large trees -->
|
|
40
|
+
<Tree {data} virtualScroll={true} virtualContainerHeight="500px" />
|
|
57
41
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<Tree
|
|
61
|
-
{data}
|
|
62
|
-
useFlatRendering={false} <!-- Uses recursive Node components -->
|
|
63
|
-
/>
|
|
42
|
+
<!-- Flat mode (default) with progressive batching -->
|
|
43
|
+
<Tree {data} progressiveRender={true} initialBatchSize={20} maxBatchSize={500} />
|
|
64
44
|
```
|
|
65
45
|
|
|
66
|
-
Recursive mode may be preferred for very small trees or when you need the `{#key changeTracker}` behavior that recreates all nodes on any change.
|
|
67
|
-
|
|
68
46
|
## Features
|
|
69
47
|
|
|
70
48
|
- **Svelte 5 Native**: Built specifically for Svelte 5 with full support for runes and modern Svelte patterns
|
|
71
|
-
- **High Performance**: Flat rendering
|
|
72
|
-
- **Drag & Drop**: Built-in drag and drop with position control (
|
|
49
|
+
- **High Performance**: Flat rendering with progressive loading, virtual scroll for 50,000+ nodes
|
|
50
|
+
- **Drag & Drop**: Built-in drag and drop with position control (before/after/child), touch support, and async validation
|
|
73
51
|
- **Tree Editing**: Built-in methods for add, move, remove operations with automatic path management
|
|
74
52
|
- **Search & Filter**: Integrated FlexSearch for fast, full-text search capabilities
|
|
75
53
|
- **Flexible Data Sources**: Works with any hierarchical data structure
|
|
76
|
-
- **Context Menus**: Dynamic right-click menus with
|
|
54
|
+
- **Context Menus**: Dynamic right-click menus with shortcuts, submenus, named dividers, and two API approaches (callback or Svelte components)
|
|
77
55
|
- **Visual Customization**: Extensive styling options and icon customization
|
|
78
56
|
- **TypeScript Support**: Full TypeScript support with comprehensive type definitions
|
|
79
57
|
- **Accessibility**: Built with accessibility in mind
|
|
@@ -155,14 +133,14 @@ import '@keenmate/svelte-treeview/styles.scss';
|
|
|
155
133
|
idMember="path"
|
|
156
134
|
pathMember="path"
|
|
157
135
|
selectedNodeClass="ltree-selected-bold"
|
|
158
|
-
onNodeClicked={(node) => console.log('Clicked:', node.data
|
|
136
|
+
onNodeClicked={(node) => console.log('Clicked:', node.data?.name)}
|
|
159
137
|
>
|
|
160
138
|
{#snippet nodeTemplate(node)}
|
|
161
139
|
<div class="d-flex align-items-center">
|
|
162
|
-
<span class="me-2">{node.data
|
|
163
|
-
<strong>{node.data
|
|
164
|
-
{#if node.data
|
|
165
|
-
<small class="text-muted ms-2">({node.data
|
|
140
|
+
<span class="me-2">{node.data?.icon}</span>
|
|
141
|
+
<strong>{node.data?.name}</strong>
|
|
142
|
+
{#if node.data?.size}
|
|
143
|
+
<small class="text-muted ms-2">({node.data?.size})</small>
|
|
166
144
|
{/if}
|
|
167
145
|
</div>
|
|
168
146
|
{/snippet}
|
|
@@ -277,13 +255,13 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
|
|
|
277
255
|
];
|
|
278
256
|
|
|
279
257
|
function onDragStart(node, event) {
|
|
280
|
-
console.log('Dragging:', node.data
|
|
258
|
+
console.log('Dragging:', node.data?.name);
|
|
281
259
|
}
|
|
282
260
|
|
|
283
261
|
// Same-tree moves are auto-handled - this callback is for notification/custom logic
|
|
284
262
|
function onDrop(dropNode, draggedNode, position, event, operation) {
|
|
285
|
-
console.log(`Dropped ${draggedNode.data
|
|
286
|
-
// position is '
|
|
263
|
+
console.log(`Dropped ${draggedNode.data?.name} ${position} ${dropNode?.data?.name}`);
|
|
264
|
+
// position is 'before', 'after', or 'child'
|
|
287
265
|
// operation is 'move' or 'copy' (Ctrl+drag)
|
|
288
266
|
}
|
|
289
267
|
</script>
|
|
@@ -303,16 +281,16 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
|
|
|
303
281
|
|
|
304
282
|
#### Drop Position Control
|
|
305
283
|
|
|
306
|
-
When using `dropZoneMode="floating"
|
|
307
|
-
- **
|
|
308
|
-
- **
|
|
284
|
+
When using `dropZoneMode="floating"`, users can choose where to drop:
|
|
285
|
+
- **Before**: Insert as sibling before the target node
|
|
286
|
+
- **After**: Insert as sibling after the target node
|
|
309
287
|
- **Child**: Insert as child of the target node
|
|
310
288
|
|
|
311
289
|
#### Per-Node Drop Position Restrictions
|
|
312
290
|
|
|
313
291
|
You can restrict which drop positions are allowed per node. This is useful for:
|
|
314
|
-
- **Trash/Recycle Bin**: Only allow dropping INTO (child), not
|
|
315
|
-
- **Files**: Only allow
|
|
292
|
+
- **Trash/Recycle Bin**: Only allow dropping INTO (child), not before/after
|
|
293
|
+
- **Files**: Only allow before/after (can't drop INTO a file)
|
|
316
294
|
- **Folders**: Allow all positions (default)
|
|
317
295
|
|
|
318
296
|
```svelte
|
|
@@ -321,7 +299,7 @@ You can restrict which drop positions are allowed per node. This is useful for:
|
|
|
321
299
|
|
|
322
300
|
// Dynamic callback approach
|
|
323
301
|
function getAllowedDropPositions(node: LTreeNode<MyItem>): DropPosition[] | null {
|
|
324
|
-
if (node.data?.type === 'file') return ['
|
|
302
|
+
if (node.data?.type === 'file') return ['before', 'after'];
|
|
325
303
|
if (node.data?.type === 'trash') return ['child'];
|
|
326
304
|
return undefined; // all positions allowed
|
|
327
305
|
}
|
|
@@ -363,7 +341,7 @@ Use `beforeDropCallback` to validate or modify drops, including async operations
|
|
|
363
341
|
if (position === 'child' && !dropNode.data.isFolder) {
|
|
364
342
|
const confirmed = await showConfirmDialog('Drop as sibling instead?');
|
|
365
343
|
if (!confirmed) return false;
|
|
366
|
-
return { position: '
|
|
344
|
+
return { position: 'after' }; // Override position
|
|
367
345
|
}
|
|
368
346
|
|
|
369
347
|
// Proceed normally
|
|
@@ -404,7 +382,7 @@ The tree provides built-in methods for programmatic editing:
|
|
|
404
382
|
const siblings = treeRef.getSiblings(selectedNode.path);
|
|
405
383
|
const index = siblings.findIndex(s => s.path === selectedNode.path);
|
|
406
384
|
if (index > 0) {
|
|
407
|
-
treeRef.moveNode(selectedNode.path, siblings[index - 1].path, '
|
|
385
|
+
treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'before');
|
|
408
386
|
}
|
|
409
387
|
}
|
|
410
388
|
|
|
@@ -423,18 +401,18 @@ The tree provides built-in methods for programmatic editing:
|
|
|
423
401
|
/>
|
|
424
402
|
```
|
|
425
403
|
|
|
426
|
-
**Note**: When using `orderMember`, the tree automatically calculates sort order values when moving nodes with '
|
|
404
|
+
**Note**: When using `orderMember`, the tree automatically calculates sort order values when moving nodes with 'before' or 'after' positions.
|
|
427
405
|
|
|
428
406
|
### With Context Menus
|
|
429
407
|
|
|
430
|
-
The tree supports context menus with two approaches: callback-based (
|
|
408
|
+
The tree supports context menus with two approaches: **callback-based** (imperative, shared with web components) and **snippet + component** (declarative, Svelte-only).
|
|
431
409
|
|
|
432
410
|
#### Callback-Based Context Menus
|
|
433
411
|
|
|
434
412
|
```svelte
|
|
435
413
|
<script lang="ts">
|
|
436
414
|
import { Tree } from '@keenmate/svelte-treeview';
|
|
437
|
-
import type {
|
|
415
|
+
import type { ContextMenuEntry } from '@keenmate/svelte-treeview';
|
|
438
416
|
|
|
439
417
|
const data = [
|
|
440
418
|
{ path: '1', name: 'Documents', type: 'folder', canEdit: true, canDelete: true },
|
|
@@ -442,45 +420,21 @@ The tree supports context menus with two approaches: callback-based (recommended
|
|
|
442
420
|
{ path: '2', name: 'Images', type: 'folder', canEdit: false, canDelete: true }
|
|
443
421
|
];
|
|
444
422
|
|
|
445
|
-
function createContextMenu(node):
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
callback: () => alert(`Editing ${node.data.name}`)
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (node.data.canDelete) {
|
|
465
|
-
items.push({
|
|
466
|
-
icon: '🗑️',
|
|
467
|
-
title: 'Delete',
|
|
468
|
-
callback: () => confirm(`Delete ${node.data.name}?`) && alert('Deleted!')
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Divider
|
|
473
|
-
items.push({ isDivider: true });
|
|
474
|
-
|
|
475
|
-
// Disabled item example
|
|
476
|
-
items.push({
|
|
477
|
-
icon: '🔒',
|
|
478
|
-
title: 'Restricted Action',
|
|
479
|
-
isDisabled: true,
|
|
480
|
-
callback: () => {}
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
return items;
|
|
423
|
+
function createContextMenu(node, close: () => void): ContextMenuEntry[] {
|
|
424
|
+
return [
|
|
425
|
+
{ label: 'Open', icon: '📂', shortcut: 'O',
|
|
426
|
+
onclick: () => { alert(`Opening ${node.data?.name}`); close(); } },
|
|
427
|
+
{ label: 'Edit', icon: '✏️', shortcut: 'E', isVisible: node.data?.canEdit,
|
|
428
|
+
onclick: () => { alert(`Editing ${node.data?.name}`); close(); } },
|
|
429
|
+
{ label: 'Export As...', icon: '📤', children: [
|
|
430
|
+
{ label: 'JSON', shortcut: 'J', onclick: () => { exportAs(node, 'json'); close(); } },
|
|
431
|
+
{ label: 'XML', shortcut: 'X', onclick: () => { exportAs(node, 'xml'); close(); } },
|
|
432
|
+
]},
|
|
433
|
+
{ divider: true, label: 'Danger zone' },
|
|
434
|
+
{ label: 'Delete', icon: '🗑️', className: 'danger',
|
|
435
|
+
isDisabled: !node.data?.canDelete,
|
|
436
|
+
onclick: () => { confirm(`Delete?`) && alert('Deleted!'); close(); } },
|
|
437
|
+
];
|
|
484
438
|
}
|
|
485
439
|
</script>
|
|
486
440
|
|
|
@@ -489,39 +443,48 @@ The tree supports context menus with two approaches: callback-based (recommended
|
|
|
489
443
|
idMember="path"
|
|
490
444
|
pathMember="path"
|
|
491
445
|
contextMenuCallback={createContextMenu}
|
|
492
|
-
contextMenuXOffset={8}
|
|
493
|
-
contextMenuYOffset={0}
|
|
494
446
|
/>
|
|
495
447
|
```
|
|
496
448
|
|
|
497
|
-
#### Snippet
|
|
449
|
+
#### Snippet + Component Context Menus
|
|
498
450
|
|
|
499
451
|
```svelte
|
|
500
|
-
<
|
|
501
|
-
{
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
>
|
|
505
|
-
{#snippet contextMenu(node,
|
|
506
|
-
<
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
452
|
+
<script lang="ts">
|
|
453
|
+
import { Tree, ContextMenuItemC, ContextMenuDividerC } from '@keenmate/svelte-treeview';
|
|
454
|
+
</script>
|
|
455
|
+
|
|
456
|
+
<Tree {data} idMember="path" pathMember="path">
|
|
457
|
+
{#snippet contextMenu(node, close)}
|
|
458
|
+
<ContextMenuItemC label="Copy" icon="📋" shortcut="C"
|
|
459
|
+
onclick={() => { copy(node); close(); }} />
|
|
460
|
+
{#if node.data?.type === 'folder'}
|
|
461
|
+
<ContextMenuItemC label="Export As..." icon="📤">
|
|
462
|
+
<ContextMenuItemC label="JSON" shortcut="J"
|
|
463
|
+
onclick={() => { exportAs(node, 'json'); close(); }} />
|
|
464
|
+
<ContextMenuItemC label="XML" shortcut="X"
|
|
465
|
+
onclick={() => { exportAs(node, 'xml'); close(); }} />
|
|
466
|
+
</ContextMenuItemC>
|
|
467
|
+
{/if}
|
|
468
|
+
<ContextMenuDividerC label="Danger zone" />
|
|
469
|
+
<ContextMenuItemC label="Delete" icon="🗑️" className="danger"
|
|
470
|
+
isDisabled={!!node.data?.readonly}
|
|
471
|
+
onclick={() => { del(node); close(); }} />
|
|
513
472
|
{/snippet}
|
|
514
473
|
</Tree>
|
|
515
474
|
```
|
|
516
475
|
|
|
517
476
|
#### Context Menu Features
|
|
518
477
|
|
|
519
|
-
- **
|
|
520
|
-
- **
|
|
521
|
-
- **
|
|
478
|
+
- **Unified types**: `ContextMenuItem`, `ContextMenuDivider`, `ContextMenuEntry` shared across svelte-treeview and canvas-tree
|
|
479
|
+
- **Keyboard shortcuts**: `shortcut` field renders a hint and activates on keypress when menu is open (supports `Ctrl+`, `Shift+`, `Alt+` modifiers)
|
|
480
|
+
- **Submenus**: `children` array opens nested menus on hover
|
|
481
|
+
- **Named dividers**: `{ divider: true, label: 'Section' }` renders as `---- Section ----`
|
|
482
|
+
- **Visibility control**: `isVisible: false` hides items (callback approach); snippet approach uses `{#if}`
|
|
483
|
+
- **Flexible styling**: `className="danger"` or any custom CSS class
|
|
484
|
+
- **Dynamic menus**: Generate items based on node properties
|
|
485
|
+
- **Icons and disabled states**: Visual organization and context-sensitive availability
|
|
522
486
|
- **Position offset**: `contextMenuXOffset`/`contextMenuYOffset` for cursor clearance
|
|
523
|
-
- **Auto-close**: Closes on scroll, click outside, or programmatically
|
|
524
|
-
- **Type safety**: Full TypeScript support with `ContextMenuItem` interface
|
|
487
|
+
- **Auto-close**: Closes on scroll, click outside, Escape key, or programmatically
|
|
525
488
|
|
|
526
489
|
## Styling and Customization
|
|
527
490
|
|
|
@@ -625,12 +588,11 @@ The component includes several pre-built classes for styling selected nodes:
|
|
|
625
588
|
| `data` | `T[]` | **required** | Array of data objects |
|
|
626
589
|
| `idMember` | `string` | **required** | Property name for unique identifiers |
|
|
627
590
|
| `pathMember` | `string` | **required** | Property name for hierarchical paths |
|
|
628
|
-
| `sortCallback` | `(items: T[]) => T[]` |
|
|
591
|
+
| `sortCallback` | `(items: LTreeNode<T>[]) => LTreeNode<T>[]` | `undefined` | Function to sort tree nodes |
|
|
629
592
|
|
|
630
593
|
#### Data Mapping Properties
|
|
631
594
|
| Prop | Type | Default | Description |
|
|
632
595
|
|------|------|---------|-------------|
|
|
633
|
-
| `treeId` | `string \| null` | `null` | Unique identifier for the tree |
|
|
634
596
|
| `parentPathMember` | `string \| null` | `null` | Property name for parent path references |
|
|
635
597
|
| `levelMember` | `string \| null` | `null` | Property name for node level |
|
|
636
598
|
| `isExpandedMember` | `string \| null` | `null` | Property name for expanded state |
|
|
@@ -638,6 +600,9 @@ The component includes several pre-built classes for styling selected nodes:
|
|
|
638
600
|
| `isDraggableMember` | `string \| null` | `null` | Property name for draggable state |
|
|
639
601
|
| `isDropAllowedMember` | `string \| null` | `null` | Property name for drop allowed state |
|
|
640
602
|
| `allowedDropPositionsMember` | `string \| null` | `null` | Property name for allowed drop positions array |
|
|
603
|
+
| `isCollapsibleMember` | `string \| null` | `null` | Property name for collapsible state |
|
|
604
|
+
| `getIsCollapsibleCallback` | `(node) => boolean` | `undefined` | Callback to determine if a node is collapsible |
|
|
605
|
+
| `getIsDraggableCallback` | `(node) => boolean` | `undefined` | Callback to determine if a node is draggable |
|
|
641
606
|
| `hasChildrenMember` | `string \| null` | `null` | Property name for children existence |
|
|
642
607
|
| `isSorted` | `boolean \| null` | `null` | Whether items should be sorted |
|
|
643
608
|
|
|
@@ -648,7 +613,7 @@ The component includes several pre-built classes for styling selected nodes:
|
|
|
648
613
|
| `getDisplayValueCallback` | `(node) => string` | `undefined` | Function to get display value |
|
|
649
614
|
| `searchValueMember` | `string \| null` | `null` | Property name for search indexing |
|
|
650
615
|
| `getSearchValueCallback` | `(node) => string` | `undefined` | Function to get search value |
|
|
651
|
-
| `shouldUseInternalSearchIndex` | `boolean` | `
|
|
616
|
+
| `shouldUseInternalSearchIndex` | `boolean` | `true` | Enable built-in search functionality |
|
|
652
617
|
| `initializeIndexCallback` | `() => Index` | `undefined` | Function to initialize search index |
|
|
653
618
|
| `searchText` | `string` (bindable) | `undefined` | Current search text |
|
|
654
619
|
|
|
@@ -677,9 +642,10 @@ Without both requirements, no search indexing will occur.
|
|
|
677
642
|
|------|------|---------|-------------|
|
|
678
643
|
| `expandLevel` | `number \| null` | `2` | Automatically expand nodes up to this level |
|
|
679
644
|
| `shouldToggleOnNodeClick` | `boolean` | `true` | Toggle expansion on node click |
|
|
680
|
-
| `orderMember` | `string \| null` | `null` | Property name for sort order (enables
|
|
645
|
+
| `orderMember` | `string \| null` | `null` | Property name for sort order (enables before/after positioning in drag-drop) |
|
|
681
646
|
| `indexerBatchSize` | `number \| null` | `25` | Number of nodes to process per batch during search indexing |
|
|
682
647
|
| `indexerTimeout` | `number \| null` | `50` | Maximum time (ms) to wait for idle callback before forcing indexing |
|
|
648
|
+
| `isLoading` | `boolean` | `false` | Show loading placeholder instead of tree content |
|
|
683
649
|
| `shouldDisplayDebugInformation` | `boolean` | `false` | Show debug information panel with tree statistics and enable console debug logging |
|
|
684
650
|
| `shouldDisplayContextMenuInDebugMode` | `boolean` | `false` | Display persistent context menu at fixed position for styling development |
|
|
685
651
|
|
|
@@ -690,7 +656,14 @@ Without both requirements, no search indexing will occur.
|
|
|
690
656
|
| `progressiveRender` | `boolean` | `true` | Progressively render nodes in batches |
|
|
691
657
|
| `initialBatchSize` | `number` | `20` | First batch size for progressive rendering |
|
|
692
658
|
| `maxBatchSize` | `number` | `500` | Maximum batch size cap |
|
|
693
|
-
| `
|
|
659
|
+
| `virtualScroll` | `boolean` | `false` | Enable virtual scrolling (flat mode only, renders visible + overscan rows) |
|
|
660
|
+
| `virtualRowHeight` | `number` | auto | Explicit row height in px (auto-measured from first row if not set) |
|
|
661
|
+
| `virtualOverscan` | `number` | `5` | Extra rows rendered above/below viewport |
|
|
662
|
+
| `virtualContainerHeight` | `string` | auto/`'400px'` | CSS height for scroll container (auto-detected from parent if not set) |
|
|
663
|
+
| `isRendering` | `boolean` (bindable) | `false` | Whether the tree is currently rendering (useful for progress indicators) |
|
|
664
|
+
| `onRenderStart` | `() => void` | `undefined` | Called when progressive rendering begins |
|
|
665
|
+
| `onRenderProgress` | `(rendered: number, total: number) => void` | `undefined` | Called after each batch with progress info |
|
|
666
|
+
| `onRenderComplete` | `() => void` | `undefined` | Called when progressive rendering finishes |
|
|
694
667
|
|
|
695
668
|
#### Drag & Drop Properties
|
|
696
669
|
| Prop | Type | Default | Description |
|
|
@@ -712,7 +685,7 @@ Without both requirements, no search indexing will occur.
|
|
|
712
685
|
| `onNodeClicked` | `(node) => void` | `undefined` | Node click event handler |
|
|
713
686
|
| `onNodeDragStart` | `(node, event) => void` | `undefined` | Drag start event handler |
|
|
714
687
|
| `onNodeDragOver` | `(node, event) => void` | `undefined` | Drag over event handler |
|
|
715
|
-
| `onNodeDrop` | `(dropNode, draggedNode, position, event, operation) => void` | `undefined` | Drop event handler. Position is `'
|
|
688
|
+
| `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'` |
|
|
716
689
|
|
|
717
690
|
#### Visual Styling Properties
|
|
718
691
|
| Prop | Type | Default | Description |
|
|
@@ -731,9 +704,10 @@ Without both requirements, no search indexing will occur.
|
|
|
731
704
|
|---------|------------|-------------|
|
|
732
705
|
| `nodeTemplate` | `(node)` | Custom node template |
|
|
733
706
|
| `treeHeader` | | Tree header content |
|
|
734
|
-
| `treeBody` | | Tree body content |
|
|
735
707
|
| `treeFooter` | | Tree footer content |
|
|
736
|
-
| `noDataFound` | |
|
|
708
|
+
| `noDataFound` | | Content shown when tree has no data |
|
|
709
|
+
| `dropPlaceholder` | | Content shown in empty drop target tree |
|
|
710
|
+
| `loadingPlaceholder` | | Content shown while `isLoading` is true |
|
|
737
711
|
| `contextMenu` | `(node, closeMenu)` | Context menu template |
|
|
738
712
|
|
|
739
713
|
#### Public Methods
|
|
@@ -748,19 +722,31 @@ Without both requirements, no search indexing will occur.
|
|
|
748
722
|
| `scrollToPath` | `path: string, options?: ScrollToPathOptions` | Scroll to and highlight a specific node |
|
|
749
723
|
| `update` | `updates: Partial<Props>` | Programmatically update component props from external JavaScript |
|
|
750
724
|
| `addNode` | `parentPath: string, data: T, pathSegment?: string` | Add a new node under the specified parent |
|
|
751
|
-
| `moveNode` | `sourcePath: string, targetPath: string, position: '
|
|
725
|
+
| `moveNode` | `sourcePath: string, targetPath: string, position: 'before' \| 'after' \| 'child'` | Move a node to a new location |
|
|
752
726
|
| `removeNode` | `path: string, includeDescendants?: boolean` | Remove a node (and optionally its descendants) |
|
|
753
727
|
| `getNodeByPath` | `path: string` | Get a node by its path |
|
|
754
728
|
| `getChildren` | `parentPath: string` | Get direct children of a node |
|
|
755
729
|
| `getSiblings` | `path: string` | Get siblings of a node (including itself) |
|
|
730
|
+
| `updateNode` | `path: string, data: Partial<T>` | Update a node's data properties |
|
|
731
|
+
| `copyNodeWithDescendants` | `sourcePath: string, targetPath: string, position: DropPosition` | Copy a node and its subtree to a new location |
|
|
732
|
+
| `refreshNode` | `path: string` | Force re-render of a specific node |
|
|
733
|
+
| `refreshSiblings` | `path: string` | Force re-render of a node's siblings |
|
|
734
|
+
| `getExpandedPaths` | | Get array of all currently expanded node paths |
|
|
735
|
+
| `setExpandedPaths` | `paths: string[]` | Restore expanded state from saved paths |
|
|
736
|
+
| `getAllData` | | Get all tree data as a flat array |
|
|
737
|
+
| `applyChanges` | | Apply pending changes and refresh the tree |
|
|
738
|
+
| `closeContextMenu` | | Programmatically close the context menu |
|
|
756
739
|
|
|
757
740
|
#### ScrollToPath Options
|
|
758
741
|
|
|
759
742
|
| Option | Type | Default | Description |
|
|
760
743
|
|--------|------|---------|-------------|
|
|
761
744
|
| `expand` | `boolean` | `true` | Automatically expand parent nodes to make target visible |
|
|
745
|
+
| `expandTarget` | `boolean` | `false` | Also expand the target node itself (not just its ancestors) |
|
|
762
746
|
| `highlight` | `boolean` | `true` | Apply temporary highlight animation to the target node |
|
|
763
747
|
| `scrollOptions` | `ScrollIntoViewOptions` | `{ behavior: 'smooth', block: 'center' }` | Native browser scroll options |
|
|
748
|
+
| `containerScroll` | `boolean` | `false` | Scroll only within nearest scrollable ancestor (prevents page scroll) |
|
|
749
|
+
| `containerElement` | `HTMLElement` | `undefined` | Explicit scrollable container element to use for scrolling |
|
|
764
750
|
|
|
765
751
|
**Usage Example:**
|
|
766
752
|
```typescript
|
|
@@ -776,6 +762,9 @@ await tree.scrollToPath('1.2.3', {
|
|
|
776
762
|
block: 'start'
|
|
777
763
|
}
|
|
778
764
|
});
|
|
765
|
+
|
|
766
|
+
// Scroll within a scrollable container (prevents page scroll)
|
|
767
|
+
await tree.scrollToPath('1.2.3', { containerScroll: true });
|
|
779
768
|
```
|
|
780
769
|
|
|
781
770
|
**Highlight Classes Example:**
|
|
@@ -939,7 +928,7 @@ const sortCallback = (items: LTreeNode<T>[]) => {
|
|
|
939
928
|
}
|
|
940
929
|
|
|
941
930
|
// Then sort by your custom criteria
|
|
942
|
-
return a.data
|
|
931
|
+
return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
|
|
943
932
|
});
|
|
944
933
|
};
|
|
945
934
|
```
|
|
@@ -1010,36 +999,40 @@ interface InsertArrayResult<T> {
|
|
|
1010
999
|
|
|
1011
1000
|
The component is optimized for large datasets:
|
|
1012
1001
|
|
|
1002
|
+
- **Virtual Scroll**: Renders only visible rows (~50 DOM nodes) for trees with 50,000+ nodes
|
|
1013
1003
|
- **Flat Rendering Mode**: Single `{#each}` loop instead of recursive components (default, ~12x faster initial render)
|
|
1014
1004
|
- **Progressive Rendering**: Batched rendering prevents UI freeze during initial load
|
|
1015
|
-
- **Context-Based Callbacks**: Stable function references eliminate unnecessary re-renders
|
|
1016
|
-
- **LTree**: Efficient hierarchical data structure
|
|
1017
1005
|
- **Async Search Indexing**: Uses `requestIdleCallback` for non-blocking search index building
|
|
1018
|
-
- **
|
|
1019
|
-
- **Search Indexing**: Uses FlexSearch for fast search operations
|
|
1006
|
+
- **LTree**: Efficient hierarchical data structure with FlexSearch integration
|
|
1020
1007
|
|
|
1021
1008
|
### Performance Benchmarks (5500 nodes)
|
|
1022
1009
|
|
|
1023
1010
|
| Operation | Time |
|
|
1024
1011
|
|-----------|------|
|
|
1025
|
-
| Initial render | ~25ms |
|
|
1012
|
+
| Initial render (flat) | ~25ms |
|
|
1013
|
+
| Initial render (virtual) | ~5ms |
|
|
1026
1014
|
| Expand/collapse | ~100-150ms |
|
|
1027
1015
|
| Search filtering | <50ms |
|
|
1016
|
+
| insertArray | <100ms |
|
|
1017
|
+
|
|
1018
|
+
### Virtual Scroll
|
|
1028
1019
|
|
|
1029
|
-
|
|
1020
|
+
For trees with 10,000+ nodes, enable virtual scroll to keep DOM size constant:
|
|
1030
1021
|
|
|
1031
|
-
**Flat Rendering Mode** (default) - Renders all visible nodes in a single loop:
|
|
1032
1022
|
```svelte
|
|
1033
1023
|
<Tree
|
|
1034
1024
|
{data}
|
|
1035
|
-
|
|
1036
|
-
|
|
1025
|
+
virtualScroll={true}
|
|
1026
|
+
virtualContainerHeight="500px"
|
|
1027
|
+
virtualOverscan={5}
|
|
1037
1028
|
/>
|
|
1038
1029
|
```
|
|
1039
1030
|
|
|
1040
|
-
|
|
1031
|
+
Virtual scroll auto-measures row height from the first rendered node. Override with `virtualRowHeight={32}` if needed. Requires flat rendering mode (the default).
|
|
1032
|
+
|
|
1033
|
+
### Performance Logging
|
|
1041
1034
|
|
|
1042
|
-
|
|
1035
|
+
Built-in performance measurement for debugging:
|
|
1043
1036
|
```typescript
|
|
1044
1037
|
import { enablePerfLogging } from '@keenmate/svelte-treeview';
|
|
1045
1038
|
enablePerfLogging();
|
|
@@ -1052,32 +1045,12 @@ window.components['svelte-treeview'].perf.enable()
|
|
|
1052
1045
|
|
|
1053
1046
|
## CanvasTree (Canvas-Based Rendering)
|
|
1054
1047
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
### Layout Modes
|
|
1058
|
-
|
|
1059
|
-
| Mode | Description |
|
|
1060
|
-
|------|-------------|
|
|
1061
|
-
| `tree` | Standard hierarchical tree (default) |
|
|
1062
|
-
| `balanced` | Root centered with two symmetric arms |
|
|
1063
|
-
| `fishbone` | Spine with alternating branches above/below |
|
|
1064
|
-
| `radial` | Star / concentric rings from center |
|
|
1065
|
-
| `box` | Space-filling treemap |
|
|
1066
|
-
|
|
1067
|
-
### Fishbone Navigation
|
|
1048
|
+
Canvas rendering is available as a separate companion package: [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
|
|
1068
1049
|
|
|
1069
|
-
|
|
1050
|
+
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:
|
|
1070
1051
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
- **Cross-spine** (optional): When `fishboneCrossNav={true}`, Up/Down crosses to the opposite side of the spine when no more nodes exist in the current direction.
|
|
1074
|
-
|
|
1075
|
-
```svelte
|
|
1076
|
-
<CanvasTree
|
|
1077
|
-
{data}
|
|
1078
|
-
layoutMode="fishbone"
|
|
1079
|
-
fishboneCrossNav={false} <!-- default: stops at spine boundary -->
|
|
1080
|
-
/>
|
|
1052
|
+
```bash
|
|
1053
|
+
npm install @keenmate/svelte-treeview-canvas
|
|
1081
1054
|
```
|
|
1082
1055
|
|
|
1083
1056
|
## Development Setup & Contributing
|