@keenmate/svelte-treeview 4.8.0 → 5.0.0-rc02

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.
Files changed (51) hide show
  1. package/README.md +106 -117
  2. package/ai/INDEX.txt +310 -0
  3. package/ai/advanced-patterns.txt +506 -0
  4. package/ai/basic-setup.txt +336 -0
  5. package/ai/context-menu.txt +349 -0
  6. package/ai/data-handling.txt +390 -0
  7. package/ai/drag-drop.txt +397 -0
  8. package/ai/events-callbacks.txt +382 -0
  9. package/ai/import-patterns.txt +271 -0
  10. package/ai/performance.txt +349 -0
  11. package/ai/search-features.txt +359 -0
  12. package/ai/styling-theming.txt +354 -0
  13. package/ai/tree-editing.txt +423 -0
  14. package/ai/typescript-types.txt +357 -0
  15. package/dist/components/Node.svelte +47 -40
  16. package/dist/components/Node.svelte.d.ts +1 -1
  17. package/dist/components/Tree.svelte +384 -1479
  18. package/dist/components/Tree.svelte.d.ts +30 -28
  19. package/dist/components/TreeProvider.svelte +28 -0
  20. package/dist/components/TreeProvider.svelte.d.ts +28 -0
  21. package/dist/constants.generated.d.ts +1 -1
  22. package/dist/constants.generated.js +1 -1
  23. package/dist/core/TreeController.svelte.d.ts +353 -0
  24. package/dist/core/TreeController.svelte.js +1503 -0
  25. package/dist/core/createTreeController.d.ts +9 -0
  26. package/dist/core/createTreeController.js +11 -0
  27. package/dist/global-api.d.ts +1 -1
  28. package/dist/global-api.js +5 -5
  29. package/dist/index.d.ts +10 -6
  30. package/dist/index.js +7 -3
  31. package/dist/logger.d.ts +7 -6
  32. package/dist/logger.js +0 -2
  33. package/dist/ltree/indexer.js +2 -4
  34. package/dist/ltree/ltree-node.svelte.d.ts +2 -1
  35. package/dist/ltree/ltree-node.svelte.js +1 -0
  36. package/dist/ltree/ltree.svelte.d.ts +1 -1
  37. package/dist/ltree/ltree.svelte.js +168 -175
  38. package/dist/ltree/types.d.ts +12 -8
  39. package/dist/perf-logger.d.ts +2 -1
  40. package/dist/perf-logger.js +0 -2
  41. package/dist/styles/main.scss +78 -78
  42. package/dist/styles.css +41 -41
  43. package/dist/styles.css.map +1 -1
  44. package/dist/vendor/loglevel/index.d.ts +55 -2
  45. package/dist/vendor/loglevel/prefix.d.ts +23 -2
  46. package/package.json +96 -95
  47. package/dist/ltree/ltree-demo.d.ts +0 -2
  48. package/dist/ltree/ltree-demo.js +0 -90
  49. package/dist/vendor/loglevel/loglevel-esm.d.ts +0 -2
  50. package/dist/vendor/loglevel/loglevel-plugin-prefix-esm.d.ts +0 -7
  51. package/dist/vendor/loglevel/loglevel-plugin-prefix.d.ts +0 -2
package/README.md CHANGED
@@ -6,90 +6,38 @@ 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
- ## New in v4.8: Virtual Scroll & Unified Rendering
9
+ ## v5.0: Core/Renderer Split + Virtual Scroll
10
10
 
11
- > [!NOTE]
12
- > **New virtual scroll mode renders only visible nodes handle 50,000+ items without DOM bloat.**
11
+ > [!IMPORTANT]
12
+ > **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`.
13
13
 
14
- Three rendering modes, one consistent look:
14
+ **Key changes in v5:**
15
+ - **Core/Renderer split**: Use the built-in HTML `Tree` renderer, or create custom visualizations (Canvas, WebGL, SVG) via `TreeProvider` + `TreeController`
16
+ - **Virtual scroll**: Render 50,000+ node trees smoothly with `virtualScroll={true}` — only ~50 DOM nodes at any time
17
+ - **Canvas companion**: For canvas rendering, install [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
18
+ - **Drop position naming**: `'above'`/`'below'` renamed to `'before'`/`'after'` (CSS classes and events updated accordingly)
15
19
 
16
- | Mode | Best for | How it works |
17
- |------|----------|-------------|
18
- | **Recursive** | Small trees (<500 nodes) | Traditional nested Svelte components |
19
- | **Progressive** (default) | Medium trees (500–10,000) | Flat rendering with exponential batching |
20
- | **Virtual Scroll** | Large trees (10,000+) | Only visible rows + overscan are in the DOM |
20
+ ### Rendering Modes
21
21
 
22
- ```svelte
23
- <Tree
24
- {data}
25
- virtualScroll={true}
26
- virtualRowHeight={28}
27
- virtualOverscan={5}
28
- virtualContainerHeight="600px"
29
- />
30
- ```
31
-
32
- **Also in this release:**
33
- - **Search result navigation** — dual-mode filter/search with result counter, prev/next (Enter/Shift+Enter), round-robin cycling
34
- - **Floating drop zones auto-expand** when positions are restricted — pure CSS `:not(:has())`, zero JS overhead
35
- - **Cross-tree drop positioning fixed** — above/below placement now works correctly between trees
36
- - **Unified indentation & gaps** across all three rendering modes
37
- - **Drag & drop disabled by default** — set `dragDropMode="both"` to enable
38
-
39
- ## v4.7: Per-Node Drop Position Restrictions
40
-
41
- > [!NOTE]
42
- > **You can now restrict which drop positions (above/below/child) are allowed per node.**
22
+ | Mode | Props | DOM Nodes | Best For |
23
+ |------|-------|-----------|----------|
24
+ | Recursive | `useFlatRendering={false}` | All | Small trees (<100 nodes) |
25
+ | Flat (default) | `useFlatRendering={true}` | All | Medium trees (100–10K) |
26
+ | Virtual | `virtualScroll={true}` | ~50 | Large trees (10K+) |
43
27
 
44
- Use `getAllowedDropPositionsCallback` for dynamic logic or `allowedDropPositionsMember` for server data:
45
- ```typescript
46
- // Files can only have siblings, trash only accepts children
47
- function getAllowedDropPositions(node) {
48
- if (node.data?.type === 'file') return ['above', 'below'];
49
- if (node.data?.type === 'trash') return ['child'];
50
- return undefined; // all positions allowed (default)
51
- }
52
- ```
53
-
54
- ## v4.6: Progressive Flat Rendering
55
-
56
- > [!NOTE]
57
- > **The tree now uses progressive flat rendering by default for significantly improved performance.**
58
-
59
- **What this means:**
60
- - The tree renders immediately with the first batch of nodes (~20 by default)
61
- - Remaining nodes are rendered progressively in subsequent frames
62
- - For large trees (5000+ nodes), you'll see nodes appear over ~100-500ms instead of a single long freeze
63
- - The UI remains responsive during rendering
64
-
65
- **Configuration options:**
66
28
  ```svelte
67
- <Tree
68
- {data}
69
- useFlatRendering={true} <!-- Default: true (flat mode) -->
70
- progressiveRender={true} <!-- Default: true (batched rendering) -->
71
- initialBatchSize={20} <!-- First batch size (default: 20) -->
72
- maxBatchSize={500} <!-- Maximum batch size cap (default: 500) -->
73
- />
74
- ```
75
-
76
- **Exponential batching:** The first batch renders 20 nodes instantly, then doubles each frame (20 → 40 → 80 → 160 → 320 → 500...) for optimal perceived performance.
29
+ <!-- Virtual scroll for large trees -->
30
+ <Tree {data} virtualScroll={true} virtualContainerHeight="500px" />
77
31
 
78
- **To use the legacy recursive rendering:**
79
- ```svelte
80
- <Tree
81
- {data}
82
- useFlatRendering={false} <!-- Uses recursive Node components -->
83
- />
32
+ <!-- Flat mode (default) with progressive batching -->
33
+ <Tree {data} progressiveRender={true} initialBatchSize={20} maxBatchSize={500} />
84
34
  ```
85
35
 
86
- Recursive mode may be preferred for very small trees or when you need the `{#key changeTracker}` behavior that recreates all nodes on any change.
87
-
88
36
  ## Features
89
37
 
90
38
  - **Svelte 5 Native**: Built specifically for Svelte 5 with full support for runes and modern Svelte patterns
91
- - **High Performance**: Flat rendering mode with progressive loading for 5000+ nodes
92
- - **Drag & Drop**: Built-in drag and drop with position control (above/below/child), touch support, and async validation
39
+ - **High Performance**: Flat rendering with progressive loading, virtual scroll for 50,000+ nodes
40
+ - **Drag & Drop**: Built-in drag and drop with position control (before/after/child), touch support, and async validation
93
41
  - **Tree Editing**: Built-in methods for add, move, remove operations with automatic path management
94
42
  - **Search & Filter**: Integrated FlexSearch for fast, full-text search capabilities
95
43
  - **Flexible Data Sources**: Works with any hierarchical data structure
@@ -175,14 +123,14 @@ import '@keenmate/svelte-treeview/styles.scss';
175
123
  idMember="path"
176
124
  pathMember="path"
177
125
  selectedNodeClass="ltree-selected-bold"
178
- onNodeClicked={(node) => console.log('Clicked:', node.data.name)}
126
+ onNodeClicked={(node) => console.log('Clicked:', node.data?.name)}
179
127
  >
180
128
  {#snippet nodeTemplate(node)}
181
129
  <div class="d-flex align-items-center">
182
- <span class="me-2">{node.data.icon}</span>
183
- <strong>{node.data.name}</strong>
184
- {#if node.data.size}
185
- <small class="text-muted ms-2">({node.data.size})</small>
130
+ <span class="me-2">{node.data?.icon}</span>
131
+ <strong>{node.data?.name}</strong>
132
+ {#if node.data?.size}
133
+ <small class="text-muted ms-2">({node.data?.size})</small>
186
134
  {/if}
187
135
  </div>
188
136
  {/snippet}
@@ -297,13 +245,13 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
297
245
  ];
298
246
 
299
247
  function onDragStart(node, event) {
300
- console.log('Dragging:', node.data.name);
248
+ console.log('Dragging:', node.data?.name);
301
249
  }
302
250
 
303
251
  // Same-tree moves are auto-handled - this callback is for notification/custom logic
304
252
  function onDrop(dropNode, draggedNode, position, event, operation) {
305
- console.log(`Dropped ${draggedNode.data.name} ${position} ${dropNode?.data.name}`);
306
- // position is 'above', 'below', or 'child'
253
+ console.log(`Dropped ${draggedNode.data?.name} ${position} ${dropNode?.data?.name}`);
254
+ // position is 'before', 'after', or 'child'
307
255
  // operation is 'move' or 'copy' (Ctrl+drag)
308
256
  }
309
257
  </script>
@@ -323,16 +271,16 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
323
271
 
324
272
  #### Drop Position Control
325
273
 
326
- When using `dropZoneMode="floating"` (default), users can choose where to drop:
327
- - **Above**: Insert as sibling before the target node
328
- - **Below**: Insert as sibling after the target node
274
+ When using `dropZoneMode="floating"`, users can choose where to drop:
275
+ - **Before**: Insert as sibling before the target node
276
+ - **After**: Insert as sibling after the target node
329
277
  - **Child**: Insert as child of the target node
330
278
 
331
279
  #### Per-Node Drop Position Restrictions
332
280
 
333
281
  You can restrict which drop positions are allowed per node. This is useful for:
334
- - **Trash/Recycle Bin**: Only allow dropping INTO (child), not above/below
335
- - **Files**: Only allow above/below (can't drop INTO a file)
282
+ - **Trash/Recycle Bin**: Only allow dropping INTO (child), not before/after
283
+ - **Files**: Only allow before/after (can't drop INTO a file)
336
284
  - **Folders**: Allow all positions (default)
337
285
 
338
286
  ```svelte
@@ -341,7 +289,7 @@ You can restrict which drop positions are allowed per node. This is useful for:
341
289
 
342
290
  // Dynamic callback approach
343
291
  function getAllowedDropPositions(node: LTreeNode<MyItem>): DropPosition[] | null {
344
- if (node.data?.type === 'file') return ['above', 'below'];
292
+ if (node.data?.type === 'file') return ['before', 'after'];
345
293
  if (node.data?.type === 'trash') return ['child'];
346
294
  return undefined; // all positions allowed
347
295
  }
@@ -383,7 +331,7 @@ Use `beforeDropCallback` to validate or modify drops, including async operations
383
331
  if (position === 'child' && !dropNode.data.isFolder) {
384
332
  const confirmed = await showConfirmDialog('Drop as sibling instead?');
385
333
  if (!confirmed) return false;
386
- return { position: 'below' }; // Override position
334
+ return { position: 'after' }; // Override position
387
335
  }
388
336
 
389
337
  // Proceed normally
@@ -424,7 +372,7 @@ The tree provides built-in methods for programmatic editing:
424
372
  const siblings = treeRef.getSiblings(selectedNode.path);
425
373
  const index = siblings.findIndex(s => s.path === selectedNode.path);
426
374
  if (index > 0) {
427
- treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'above');
375
+ treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'before');
428
376
  }
429
377
  }
430
378
 
@@ -443,7 +391,7 @@ The tree provides built-in methods for programmatic editing:
443
391
  />
444
392
  ```
445
393
 
446
- **Note**: When using `orderMember`, the tree automatically calculates sort order values when moving nodes with 'above' or 'below' positions.
394
+ **Note**: When using `orderMember`, the tree automatically calculates sort order values when moving nodes with 'before' or 'after' positions.
447
395
 
448
396
  ### With Context Menus
449
397
 
@@ -462,30 +410,30 @@ The tree supports context menus with two approaches: callback-based (recommended
462
410
  { path: '2', name: 'Images', type: 'folder', canEdit: false, canDelete: true }
463
411
  ];
464
412
 
465
- function createContextMenu(node): ContextMenuItem[] {
413
+ function createContextMenu(node, closeMenu: () => void): ContextMenuItem[] {
466
414
  const items: ContextMenuItem[] = [];
467
415
 
468
416
  // Always available
469
417
  items.push({
470
418
  icon: '📂',
471
419
  title: 'Open',
472
- callback: () => alert(`Opening ${node.data.name}`)
420
+ callback: () => alert(`Opening ${node.data?.name}`)
473
421
  });
474
422
 
475
423
  // Conditional actions based on node data
476
- if (node.data.canEdit) {
424
+ if (node.data?.canEdit) {
477
425
  items.push({
478
426
  icon: '✏️',
479
427
  title: 'Edit',
480
- callback: () => alert(`Editing ${node.data.name}`)
428
+ callback: () => alert(`Editing ${node.data?.name}`)
481
429
  });
482
430
  }
483
431
 
484
- if (node.data.canDelete) {
432
+ if (node.data?.canDelete) {
485
433
  items.push({
486
434
  icon: '🗑️',
487
435
  title: 'Delete',
488
- callback: () => confirm(`Delete ${node.data.name}?`) && alert('Deleted!')
436
+ callback: () => confirm(`Delete ${node.data?.name}?`) && alert('Deleted!')
489
437
  });
490
438
  }
491
439
 
@@ -523,11 +471,11 @@ The tree supports context menus with two approaches: callback-based (recommended
523
471
  pathMember="path"
524
472
  >
525
473
  {#snippet contextMenu(node, closeMenu)}
526
- <div class="context-menu-item" onclick={() => { alert(`Open ${node.data.name}`); closeMenu(); }}>
474
+ <div class="context-menu-item" onclick={() => { alert(`Open ${node.data?.name}`); closeMenu(); }}>
527
475
  📂 Open
528
476
  </div>
529
477
  <div class="context-menu-divider"></div>
530
- <div class="context-menu-item" onclick={() => { alert(`Delete ${node.data.name}`); closeMenu(); }}>
478
+ <div class="context-menu-item" onclick={() => { alert(`Delete ${node.data?.name}`); closeMenu(); }}>
531
479
  🗑️ Delete
532
480
  </div>
533
481
  {/snippet}
@@ -645,12 +593,11 @@ The component includes several pre-built classes for styling selected nodes:
645
593
  | `data` | `T[]` | **required** | Array of data objects |
646
594
  | `idMember` | `string` | **required** | Property name for unique identifiers |
647
595
  | `pathMember` | `string` | **required** | Property name for hierarchical paths |
648
- | `sortCallback` | `(items: T[]) => T[]` | default sort | Function to sort items (optional) |
596
+ | `sortCallback` | `(items: LTreeNode<T>[]) => LTreeNode<T>[]` | `undefined` | Function to sort tree nodes |
649
597
 
650
598
  #### Data Mapping Properties
651
599
  | Prop | Type | Default | Description |
652
600
  |------|------|---------|-------------|
653
- | `treeId` | `string \| null` | `null` | Unique identifier for the tree |
654
601
  | `parentPathMember` | `string \| null` | `null` | Property name for parent path references |
655
602
  | `levelMember` | `string \| null` | `null` | Property name for node level |
656
603
  | `isExpandedMember` | `string \| null` | `null` | Property name for expanded state |
@@ -658,6 +605,9 @@ The component includes several pre-built classes for styling selected nodes:
658
605
  | `isDraggableMember` | `string \| null` | `null` | Property name for draggable state |
659
606
  | `isDropAllowedMember` | `string \| null` | `null` | Property name for drop allowed state |
660
607
  | `allowedDropPositionsMember` | `string \| null` | `null` | Property name for allowed drop positions array |
608
+ | `isCollapsibleMember` | `string \| null` | `null` | Property name for collapsible state |
609
+ | `getIsCollapsibleCallback` | `(node) => boolean` | `undefined` | Callback to determine if a node is collapsible |
610
+ | `getIsDraggableCallback` | `(node) => boolean` | `undefined` | Callback to determine if a node is draggable |
661
611
  | `hasChildrenMember` | `string \| null` | `null` | Property name for children existence |
662
612
  | `isSorted` | `boolean \| null` | `null` | Whether items should be sorted |
663
613
 
@@ -668,7 +618,7 @@ The component includes several pre-built classes for styling selected nodes:
668
618
  | `getDisplayValueCallback` | `(node) => string` | `undefined` | Function to get display value |
669
619
  | `searchValueMember` | `string \| null` | `null` | Property name for search indexing |
670
620
  | `getSearchValueCallback` | `(node) => string` | `undefined` | Function to get search value |
671
- | `shouldUseInternalSearchIndex` | `boolean` | `false` | Enable built-in search functionality |
621
+ | `shouldUseInternalSearchIndex` | `boolean` | `true` | Enable built-in search functionality |
672
622
  | `initializeIndexCallback` | `() => Index` | `undefined` | Function to initialize search index |
673
623
  | `searchText` | `string` (bindable) | `undefined` | Current search text |
674
624
 
@@ -697,9 +647,10 @@ Without both requirements, no search indexing will occur.
697
647
  |------|------|---------|-------------|
698
648
  | `expandLevel` | `number \| null` | `2` | Automatically expand nodes up to this level |
699
649
  | `shouldToggleOnNodeClick` | `boolean` | `true` | Toggle expansion on node click |
700
- | `orderMember` | `string \| null` | `null` | Property name for sort order (enables above/below positioning in drag-drop) |
650
+ | `orderMember` | `string \| null` | `null` | Property name for sort order (enables before/after positioning in drag-drop) |
701
651
  | `indexerBatchSize` | `number \| null` | `25` | Number of nodes to process per batch during search indexing |
702
652
  | `indexerTimeout` | `number \| null` | `50` | Maximum time (ms) to wait for idle callback before forcing indexing |
653
+ | `isLoading` | `boolean` | `false` | Show loading placeholder instead of tree content |
703
654
  | `shouldDisplayDebugInformation` | `boolean` | `false` | Show debug information panel with tree statistics and enable console debug logging |
704
655
  | `shouldDisplayContextMenuInDebugMode` | `boolean` | `false` | Display persistent context menu at fixed position for styling development |
705
656
 
@@ -710,6 +661,14 @@ Without both requirements, no search indexing will occur.
710
661
  | `progressiveRender` | `boolean` | `true` | Progressively render nodes in batches |
711
662
  | `initialBatchSize` | `number` | `20` | First batch size for progressive rendering |
712
663
  | `maxBatchSize` | `number` | `500` | Maximum batch size cap |
664
+ | `virtualScroll` | `boolean` | `false` | Enable virtual scrolling (flat mode only, renders visible + overscan rows) |
665
+ | `virtualRowHeight` | `number` | auto | Explicit row height in px (auto-measured from first row if not set) |
666
+ | `virtualOverscan` | `number` | `5` | Extra rows rendered above/below viewport |
667
+ | `virtualContainerHeight` | `string` | auto/`'400px'` | CSS height for scroll container (auto-detected from parent if not set) |
668
+ | `isRendering` | `boolean` (bindable) | `false` | Whether the tree is currently rendering (useful for progress indicators) |
669
+ | `onRenderStart` | `() => void` | `undefined` | Called when progressive rendering begins |
670
+ | `onRenderProgress` | `(rendered: number, total: number) => void` | `undefined` | Called after each batch with progress info |
671
+ | `onRenderComplete` | `() => void` | `undefined` | Called when progressive rendering finishes |
713
672
 
714
673
  #### Drag & Drop Properties
715
674
  | Prop | Type | Default | Description |
@@ -731,7 +690,7 @@ Without both requirements, no search indexing will occur.
731
690
  | `onNodeClicked` | `(node) => void` | `undefined` | Node click event handler |
732
691
  | `onNodeDragStart` | `(node, event) => void` | `undefined` | Drag start event handler |
733
692
  | `onNodeDragOver` | `(node, event) => void` | `undefined` | Drag over event handler |
734
- | `onNodeDrop` | `(dropNode, draggedNode, position, event, operation) => void` | `undefined` | Drop event handler. Position is `'above'`, `'below'`, or `'child'`. Operation is `'move'` or `'copy'` |
693
+ | `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'` |
735
694
 
736
695
  #### Visual Styling Properties
737
696
  | Prop | Type | Default | Description |
@@ -750,9 +709,10 @@ Without both requirements, no search indexing will occur.
750
709
  |---------|------------|-------------|
751
710
  | `nodeTemplate` | `(node)` | Custom node template |
752
711
  | `treeHeader` | | Tree header content |
753
- | `treeBody` | | Tree body content |
754
712
  | `treeFooter` | | Tree footer content |
755
- | `noDataFound` | | No data template |
713
+ | `noDataFound` | | Content shown when tree has no data |
714
+ | `dropPlaceholder` | | Content shown in empty drop target tree |
715
+ | `loadingPlaceholder` | | Content shown while `isLoading` is true |
756
716
  | `contextMenu` | `(node, closeMenu)` | Context menu template |
757
717
 
758
718
  #### Public Methods
@@ -767,19 +727,31 @@ Without both requirements, no search indexing will occur.
767
727
  | `scrollToPath` | `path: string, options?: ScrollToPathOptions` | Scroll to and highlight a specific node |
768
728
  | `update` | `updates: Partial<Props>` | Programmatically update component props from external JavaScript |
769
729
  | `addNode` | `parentPath: string, data: T, pathSegment?: string` | Add a new node under the specified parent |
770
- | `moveNode` | `sourcePath: string, targetPath: string, position: 'above' \| 'below' \| 'child'` | Move a node to a new location |
730
+ | `moveNode` | `sourcePath: string, targetPath: string, position: 'before' \| 'after' \| 'child'` | Move a node to a new location |
771
731
  | `removeNode` | `path: string, includeDescendants?: boolean` | Remove a node (and optionally its descendants) |
772
732
  | `getNodeByPath` | `path: string` | Get a node by its path |
773
733
  | `getChildren` | `parentPath: string` | Get direct children of a node |
774
734
  | `getSiblings` | `path: string` | Get siblings of a node (including itself) |
735
+ | `updateNode` | `path: string, data: Partial<T>` | Update a node's data properties |
736
+ | `copyNodeWithDescendants` | `sourcePath: string, targetPath: string, position: DropPosition` | Copy a node and its subtree to a new location |
737
+ | `refreshNode` | `path: string` | Force re-render of a specific node |
738
+ | `refreshSiblings` | `path: string` | Force re-render of a node's siblings |
739
+ | `getExpandedPaths` | | Get array of all currently expanded node paths |
740
+ | `setExpandedPaths` | `paths: string[]` | Restore expanded state from saved paths |
741
+ | `getAllData` | | Get all tree data as a flat array |
742
+ | `applyChanges` | | Apply pending changes and refresh the tree |
743
+ | `closeContextMenu` | | Programmatically close the context menu |
775
744
 
776
745
  #### ScrollToPath Options
777
746
 
778
747
  | Option | Type | Default | Description |
779
748
  |--------|------|---------|-------------|
780
749
  | `expand` | `boolean` | `true` | Automatically expand parent nodes to make target visible |
750
+ | `expandTarget` | `boolean` | `false` | Also expand the target node itself (not just its ancestors) |
781
751
  | `highlight` | `boolean` | `true` | Apply temporary highlight animation to the target node |
782
752
  | `scrollOptions` | `ScrollIntoViewOptions` | `{ behavior: 'smooth', block: 'center' }` | Native browser scroll options |
753
+ | `containerScroll` | `boolean` | `false` | Scroll only within nearest scrollable ancestor (prevents page scroll) |
754
+ | `containerElement` | `HTMLElement` | `undefined` | Explicit scrollable container element to use for scrolling |
783
755
 
784
756
  **Usage Example:**
785
757
  ```typescript
@@ -795,6 +767,9 @@ await tree.scrollToPath('1.2.3', {
795
767
  block: 'start'
796
768
  }
797
769
  });
770
+
771
+ // Scroll within a scrollable container (prevents page scroll)
772
+ await tree.scrollToPath('1.2.3', { containerScroll: true });
798
773
  ```
799
774
 
800
775
  **Highlight Classes Example:**
@@ -958,7 +933,7 @@ const sortCallback = (items: LTreeNode<T>[]) => {
958
933
  }
959
934
 
960
935
  // Then sort by your custom criteria
961
- return a.data.name.localeCompare(b.data.name);
936
+ return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
962
937
  });
963
938
  };
964
939
  ```
@@ -1029,36 +1004,40 @@ interface InsertArrayResult<T> {
1029
1004
 
1030
1005
  The component is optimized for large datasets:
1031
1006
 
1007
+ - **Virtual Scroll**: Renders only visible rows (~50 DOM nodes) for trees with 50,000+ nodes
1032
1008
  - **Flat Rendering Mode**: Single `{#each}` loop instead of recursive components (default, ~12x faster initial render)
1033
1009
  - **Progressive Rendering**: Batched rendering prevents UI freeze during initial load
1034
- - **Context-Based Callbacks**: Stable function references eliminate unnecessary re-renders
1035
- - **LTree**: Efficient hierarchical data structure
1036
1010
  - **Async Search Indexing**: Uses `requestIdleCallback` for non-blocking search index building
1037
- - **Accurate Search Results**: Search index only includes successfully inserted nodes
1038
- - **Search Indexing**: Uses FlexSearch for fast search operations
1011
+ - **LTree**: Efficient hierarchical data structure with FlexSearch integration
1039
1012
 
1040
1013
  ### Performance Benchmarks (5500 nodes)
1041
1014
 
1042
1015
  | Operation | Time |
1043
1016
  |-----------|------|
1044
- | Initial render | ~25ms |
1017
+ | Initial render (flat) | ~25ms |
1018
+ | Initial render (virtual) | ~5ms |
1045
1019
  | Expand/collapse | ~100-150ms |
1046
1020
  | Search filtering | <50ms |
1021
+ | insertArray | <100ms |
1022
+
1023
+ ### Virtual Scroll
1047
1024
 
1048
- ### v4.5+ Performance Improvements
1025
+ For trees with 10,000+ nodes, enable virtual scroll to keep DOM size constant:
1049
1026
 
1050
- **Flat Rendering Mode** (default) - Renders all visible nodes in a single loop:
1051
1027
  ```svelte
1052
1028
  <Tree
1053
1029
  {data}
1054
- useFlatRendering={true}
1055
- progressiveRender={true}
1030
+ virtualScroll={true}
1031
+ virtualContainerHeight="500px"
1032
+ virtualOverscan={5}
1056
1033
  />
1057
1034
  ```
1058
1035
 
1059
- **Optimized `insertArray` algorithm** - Fixed O(n²) bottleneck. Now loads 17,000+ nodes in under 100ms.
1036
+ Virtual scroll auto-measures row height from the first rendered node. Override with `virtualRowHeight={32}` if needed. Requires flat rendering mode (the default).
1037
+
1038
+ ### Performance Logging
1060
1039
 
1061
- **Performance Logging** - Built-in performance measurement for debugging:
1040
+ Built-in performance measurement for debugging:
1062
1041
  ```typescript
1063
1042
  import { enablePerfLogging } from '@keenmate/svelte-treeview';
1064
1043
  enablePerfLogging();
@@ -1069,6 +1048,16 @@ window.components['svelte-treeview'].perf.enable()
1069
1048
 
1070
1049
  **Important**: See the [$state.raw() tip](#quick-start) above - using `$state()` instead of `$state.raw()` for tree data can cause 5,000x slowdown!
1071
1050
 
1051
+ ## CanvasTree (Canvas-Based Rendering)
1052
+
1053
+ Canvas rendering is available as a separate companion package: [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
1054
+
1055
+ 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:
1056
+
1057
+ ```bash
1058
+ npm install @keenmate/svelte-treeview-canvas
1059
+ ```
1060
+
1072
1061
  ## Development Setup & Contributing
1073
1062
 
1074
1063
  For developers working on the project, you can use either standard npm commands or the provided Makefile: