@keenmate/svelte-treeview 5.0.0-rc01 → 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 (71) hide show
  1. package/README.md +99 -121
  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 +30 -54
  16. package/dist/components/Node.svelte.d.ts +2 -2
  17. package/dist/components/Tree.svelte +136 -11
  18. package/dist/components/Tree.svelte.d.ts +28 -10
  19. package/dist/constants.generated.d.ts +1 -1
  20. package/dist/constants.generated.js +1 -1
  21. package/dist/core/TreeController.svelte.d.ts +47 -3
  22. package/dist/core/TreeController.svelte.js +202 -23
  23. package/dist/global-api.d.ts +1 -1
  24. package/dist/global-api.js +5 -5
  25. package/dist/index.d.ts +5 -10
  26. package/dist/index.js +3 -8
  27. package/dist/logger.d.ts +7 -6
  28. package/dist/logger.js +0 -2
  29. package/dist/ltree/indexer.js +1 -1
  30. package/dist/ltree/ltree-node.svelte.d.ts +1 -0
  31. package/dist/ltree/ltree-node.svelte.js +1 -0
  32. package/dist/ltree/ltree.svelte.d.ts +1 -1
  33. package/dist/ltree/ltree.svelte.js +94 -59
  34. package/dist/ltree/types.d.ts +5 -2
  35. package/dist/perf-logger.d.ts +2 -1
  36. package/dist/perf-logger.js +0 -2
  37. package/dist/styles/main.scss +52 -5
  38. package/dist/styles.css +42 -5
  39. package/dist/styles.css.map +1 -1
  40. package/dist/vendor/loglevel/index.d.ts +55 -2
  41. package/dist/vendor/loglevel/prefix.d.ts +23 -2
  42. package/package.json +96 -95
  43. package/dist/canvas/CanvasTree.svelte +0 -1657
  44. package/dist/canvas/CanvasTree.svelte.d.ts +0 -148
  45. package/dist/canvas/canvas-interaction.d.ts +0 -67
  46. package/dist/canvas/canvas-interaction.js +0 -590
  47. package/dist/canvas/canvas-layout-balanced.d.ts +0 -18
  48. package/dist/canvas/canvas-layout-balanced.js +0 -212
  49. package/dist/canvas/canvas-layout-box.d.ts +0 -10
  50. package/dist/canvas/canvas-layout-box.js +0 -194
  51. package/dist/canvas/canvas-layout-fishbone.d.ts +0 -11
  52. package/dist/canvas/canvas-layout-fishbone.js +0 -349
  53. package/dist/canvas/canvas-layout-radial.d.ts +0 -21
  54. package/dist/canvas/canvas-layout-radial.js +0 -233
  55. package/dist/canvas/canvas-layout-sunburst.d.ts +0 -27
  56. package/dist/canvas/canvas-layout-sunburst.js +0 -457
  57. package/dist/canvas/canvas-layout.d.ts +0 -11
  58. package/dist/canvas/canvas-layout.js +0 -509
  59. package/dist/canvas/canvas-renderer.d.ts +0 -46
  60. package/dist/canvas/canvas-renderer.js +0 -828
  61. package/dist/canvas/canvas-text.d.ts +0 -11
  62. package/dist/canvas/canvas-text.js +0 -64
  63. package/dist/canvas/canvas-theme.d.ts +0 -58
  64. package/dist/canvas/canvas-theme.js +0 -128
  65. package/dist/canvas/types.d.ts +0 -187
  66. package/dist/canvas/types.js +0 -1
  67. package/dist/ltree/ltree-demo.d.ts +0 -2
  68. package/dist/ltree/ltree-demo.js +0 -90
  69. package/dist/vendor/loglevel/loglevel-esm.d.ts +0 -2
  70. package/dist/vendor/loglevel/loglevel-plugin-prefix-esm.d.ts +0 -7
  71. package/dist/vendor/loglevel/loglevel-plugin-prefix.d.ts +0 -2
package/README.md CHANGED
@@ -6,70 +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
- ## v5.0: Core/Renderer Split
9
+ ## v5.0: Core/Renderer Split + Virtual Scroll
10
10
 
11
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 library now ships with multiple built-in renderers — the classic HTML `Tree`, a high-performance `CanvasTree` (with tree, balanced, fishbone, radial, box, and sunburst layouts) — and the architecture is open for you to build your own custom renderers on top of the same core.
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
- This means you can:
15
- - Use the built-in renderers as-is for common use cases
16
- - Create entirely custom visualizations (WebGL, SVG, etc.) powered by the same tree core
17
- - Mix and match use the HTML tree for editing and a canvas renderer for overview
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)
18
19
 
19
- ## New in v4.7: Per-Node Drop Position Restrictions
20
+ ### Rendering Modes
20
21
 
21
- > [!NOTE]
22
- > **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+) |
23
27
 
24
- Use `getAllowedDropPositionsCallback` for dynamic logic or `allowedDropPositionsMember` for server data:
25
- ```typescript
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
- ```
33
-
34
- ## v4.6: Progressive Flat Rendering
35
-
36
- > [!NOTE]
37
- > **The tree now uses progressive flat rendering by default for significantly improved performance.**
38
-
39
- **What this means:**
40
- - The tree renders immediately with the first batch of nodes (~20 by default)
41
- - Remaining nodes are rendered progressively in subsequent frames
42
- - For large trees (5000+ nodes), you'll see nodes appear over ~100-500ms instead of a single long freeze
43
- - The UI remains responsive during rendering
44
-
45
- **Configuration options:**
46
28
  ```svelte
47
- <Tree
48
- {data}
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
- ```
29
+ <!-- Virtual scroll for large trees -->
30
+ <Tree {data} virtualScroll={true} virtualContainerHeight="500px" />
55
31
 
56
- **Exponential batching:** The first batch renders 20 nodes instantly, then doubles each frame (20 → 40 → 80 → 160 → 320 → 500...) for optimal perceived performance.
57
-
58
- **To use the legacy recursive rendering:**
59
- ```svelte
60
- <Tree
61
- {data}
62
- useFlatRendering={false} <!-- Uses recursive Node components -->
63
- />
32
+ <!-- Flat mode (default) with progressive batching -->
33
+ <Tree {data} progressiveRender={true} initialBatchSize={20} maxBatchSize={500} />
64
34
  ```
65
35
 
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
36
  ## Features
69
37
 
70
38
  - **Svelte 5 Native**: Built specifically for Svelte 5 with full support for runes and modern Svelte patterns
71
- - **High Performance**: Flat rendering mode with progressive loading for 5000+ nodes
72
- - **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
73
41
  - **Tree Editing**: Built-in methods for add, move, remove operations with automatic path management
74
42
  - **Search & Filter**: Integrated FlexSearch for fast, full-text search capabilities
75
43
  - **Flexible Data Sources**: Works with any hierarchical data structure
@@ -155,14 +123,14 @@ import '@keenmate/svelte-treeview/styles.scss';
155
123
  idMember="path"
156
124
  pathMember="path"
157
125
  selectedNodeClass="ltree-selected-bold"
158
- onNodeClicked={(node) => console.log('Clicked:', node.data.name)}
126
+ onNodeClicked={(node) => console.log('Clicked:', node.data?.name)}
159
127
  >
160
128
  {#snippet nodeTemplate(node)}
161
129
  <div class="d-flex align-items-center">
162
- <span class="me-2">{node.data.icon}</span>
163
- <strong>{node.data.name}</strong>
164
- {#if node.data.size}
165
- <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>
166
134
  {/if}
167
135
  </div>
168
136
  {/snippet}
@@ -277,13 +245,13 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
277
245
  ];
278
246
 
279
247
  function onDragStart(node, event) {
280
- console.log('Dragging:', node.data.name);
248
+ console.log('Dragging:', node.data?.name);
281
249
  }
282
250
 
283
251
  // Same-tree moves are auto-handled - this callback is for notification/custom logic
284
252
  function onDrop(dropNode, draggedNode, position, event, operation) {
285
- console.log(`Dropped ${draggedNode.data.name} ${position} ${dropNode?.data.name}`);
286
- // position is 'above', 'below', or 'child'
253
+ console.log(`Dropped ${draggedNode.data?.name} ${position} ${dropNode?.data?.name}`);
254
+ // position is 'before', 'after', or 'child'
287
255
  // operation is 'move' or 'copy' (Ctrl+drag)
288
256
  }
289
257
  </script>
@@ -303,16 +271,16 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
303
271
 
304
272
  #### Drop Position Control
305
273
 
306
- When using `dropZoneMode="floating"` (default), users can choose where to drop:
307
- - **Above**: Insert as sibling before the target node
308
- - **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
309
277
  - **Child**: Insert as child of the target node
310
278
 
311
279
  #### Per-Node Drop Position Restrictions
312
280
 
313
281
  You can restrict which drop positions are allowed per node. This is useful for:
314
- - **Trash/Recycle Bin**: Only allow dropping INTO (child), not above/below
315
- - **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)
316
284
  - **Folders**: Allow all positions (default)
317
285
 
318
286
  ```svelte
@@ -321,7 +289,7 @@ You can restrict which drop positions are allowed per node. This is useful for:
321
289
 
322
290
  // Dynamic callback approach
323
291
  function getAllowedDropPositions(node: LTreeNode<MyItem>): DropPosition[] | null {
324
- if (node.data?.type === 'file') return ['above', 'below'];
292
+ if (node.data?.type === 'file') return ['before', 'after'];
325
293
  if (node.data?.type === 'trash') return ['child'];
326
294
  return undefined; // all positions allowed
327
295
  }
@@ -363,7 +331,7 @@ Use `beforeDropCallback` to validate or modify drops, including async operations
363
331
  if (position === 'child' && !dropNode.data.isFolder) {
364
332
  const confirmed = await showConfirmDialog('Drop as sibling instead?');
365
333
  if (!confirmed) return false;
366
- return { position: 'below' }; // Override position
334
+ return { position: 'after' }; // Override position
367
335
  }
368
336
 
369
337
  // Proceed normally
@@ -404,7 +372,7 @@ The tree provides built-in methods for programmatic editing:
404
372
  const siblings = treeRef.getSiblings(selectedNode.path);
405
373
  const index = siblings.findIndex(s => s.path === selectedNode.path);
406
374
  if (index > 0) {
407
- treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'above');
375
+ treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'before');
408
376
  }
409
377
  }
410
378
 
@@ -423,7 +391,7 @@ The tree provides built-in methods for programmatic editing:
423
391
  />
424
392
  ```
425
393
 
426
- **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.
427
395
 
428
396
  ### With Context Menus
429
397
 
@@ -442,30 +410,30 @@ The tree supports context menus with two approaches: callback-based (recommended
442
410
  { path: '2', name: 'Images', type: 'folder', canEdit: false, canDelete: true }
443
411
  ];
444
412
 
445
- function createContextMenu(node): ContextMenuItem[] {
413
+ function createContextMenu(node, closeMenu: () => void): ContextMenuItem[] {
446
414
  const items: ContextMenuItem[] = [];
447
415
 
448
416
  // Always available
449
417
  items.push({
450
418
  icon: '📂',
451
419
  title: 'Open',
452
- callback: () => alert(`Opening ${node.data.name}`)
420
+ callback: () => alert(`Opening ${node.data?.name}`)
453
421
  });
454
422
 
455
423
  // Conditional actions based on node data
456
- if (node.data.canEdit) {
424
+ if (node.data?.canEdit) {
457
425
  items.push({
458
426
  icon: '✏️',
459
427
  title: 'Edit',
460
- callback: () => alert(`Editing ${node.data.name}`)
428
+ callback: () => alert(`Editing ${node.data?.name}`)
461
429
  });
462
430
  }
463
431
 
464
- if (node.data.canDelete) {
432
+ if (node.data?.canDelete) {
465
433
  items.push({
466
434
  icon: '🗑️',
467
435
  title: 'Delete',
468
- callback: () => confirm(`Delete ${node.data.name}?`) && alert('Deleted!')
436
+ callback: () => confirm(`Delete ${node.data?.name}?`) && alert('Deleted!')
469
437
  });
470
438
  }
471
439
 
@@ -503,11 +471,11 @@ The tree supports context menus with two approaches: callback-based (recommended
503
471
  pathMember="path"
504
472
  >
505
473
  {#snippet contextMenu(node, closeMenu)}
506
- <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(); }}>
507
475
  📂 Open
508
476
  </div>
509
477
  <div class="context-menu-divider"></div>
510
- <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(); }}>
511
479
  🗑️ Delete
512
480
  </div>
513
481
  {/snippet}
@@ -625,12 +593,11 @@ The component includes several pre-built classes for styling selected nodes:
625
593
  | `data` | `T[]` | **required** | Array of data objects |
626
594
  | `idMember` | `string` | **required** | Property name for unique identifiers |
627
595
  | `pathMember` | `string` | **required** | Property name for hierarchical paths |
628
- | `sortCallback` | `(items: T[]) => T[]` | default sort | Function to sort items (optional) |
596
+ | `sortCallback` | `(items: LTreeNode<T>[]) => LTreeNode<T>[]` | `undefined` | Function to sort tree nodes |
629
597
 
630
598
  #### Data Mapping Properties
631
599
  | Prop | Type | Default | Description |
632
600
  |------|------|---------|-------------|
633
- | `treeId` | `string \| null` | `null` | Unique identifier for the tree |
634
601
  | `parentPathMember` | `string \| null` | `null` | Property name for parent path references |
635
602
  | `levelMember` | `string \| null` | `null` | Property name for node level |
636
603
  | `isExpandedMember` | `string \| null` | `null` | Property name for expanded state |
@@ -638,6 +605,9 @@ The component includes several pre-built classes for styling selected nodes:
638
605
  | `isDraggableMember` | `string \| null` | `null` | Property name for draggable state |
639
606
  | `isDropAllowedMember` | `string \| null` | `null` | Property name for drop allowed state |
640
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 |
641
611
  | `hasChildrenMember` | `string \| null` | `null` | Property name for children existence |
642
612
  | `isSorted` | `boolean \| null` | `null` | Whether items should be sorted |
643
613
 
@@ -648,7 +618,7 @@ The component includes several pre-built classes for styling selected nodes:
648
618
  | `getDisplayValueCallback` | `(node) => string` | `undefined` | Function to get display value |
649
619
  | `searchValueMember` | `string \| null` | `null` | Property name for search indexing |
650
620
  | `getSearchValueCallback` | `(node) => string` | `undefined` | Function to get search value |
651
- | `shouldUseInternalSearchIndex` | `boolean` | `false` | Enable built-in search functionality |
621
+ | `shouldUseInternalSearchIndex` | `boolean` | `true` | Enable built-in search functionality |
652
622
  | `initializeIndexCallback` | `() => Index` | `undefined` | Function to initialize search index |
653
623
  | `searchText` | `string` (bindable) | `undefined` | Current search text |
654
624
 
@@ -677,9 +647,10 @@ Without both requirements, no search indexing will occur.
677
647
  |------|------|---------|-------------|
678
648
  | `expandLevel` | `number \| null` | `2` | Automatically expand nodes up to this level |
679
649
  | `shouldToggleOnNodeClick` | `boolean` | `true` | Toggle expansion on node click |
680
- | `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) |
681
651
  | `indexerBatchSize` | `number \| null` | `25` | Number of nodes to process per batch during search indexing |
682
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 |
683
654
  | `shouldDisplayDebugInformation` | `boolean` | `false` | Show debug information panel with tree statistics and enable console debug logging |
684
655
  | `shouldDisplayContextMenuInDebugMode` | `boolean` | `false` | Display persistent context menu at fixed position for styling development |
685
656
 
@@ -690,7 +661,14 @@ Without both requirements, no search indexing will occur.
690
661
  | `progressiveRender` | `boolean` | `true` | Progressively render nodes in batches |
691
662
  | `initialBatchSize` | `number` | `20` | First batch size for progressive rendering |
692
663
  | `maxBatchSize` | `number` | `500` | Maximum batch size cap |
693
- | `flatIndentSize` | `string` | `'1.5rem'` | Indentation size per level in flat mode |
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 |
694
672
 
695
673
  #### Drag & Drop Properties
696
674
  | Prop | Type | Default | Description |
@@ -712,7 +690,7 @@ Without both requirements, no search indexing will occur.
712
690
  | `onNodeClicked` | `(node) => void` | `undefined` | Node click event handler |
713
691
  | `onNodeDragStart` | `(node, event) => void` | `undefined` | Drag start event handler |
714
692
  | `onNodeDragOver` | `(node, event) => void` | `undefined` | Drag over event handler |
715
- | `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'` |
716
694
 
717
695
  #### Visual Styling Properties
718
696
  | Prop | Type | Default | Description |
@@ -731,9 +709,10 @@ Without both requirements, no search indexing will occur.
731
709
  |---------|------------|-------------|
732
710
  | `nodeTemplate` | `(node)` | Custom node template |
733
711
  | `treeHeader` | | Tree header content |
734
- | `treeBody` | | Tree body content |
735
712
  | `treeFooter` | | Tree footer content |
736
- | `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 |
737
716
  | `contextMenu` | `(node, closeMenu)` | Context menu template |
738
717
 
739
718
  #### Public Methods
@@ -748,19 +727,31 @@ Without both requirements, no search indexing will occur.
748
727
  | `scrollToPath` | `path: string, options?: ScrollToPathOptions` | Scroll to and highlight a specific node |
749
728
  | `update` | `updates: Partial<Props>` | Programmatically update component props from external JavaScript |
750
729
  | `addNode` | `parentPath: string, data: T, pathSegment?: string` | Add a new node under the specified parent |
751
- | `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 |
752
731
  | `removeNode` | `path: string, includeDescendants?: boolean` | Remove a node (and optionally its descendants) |
753
732
  | `getNodeByPath` | `path: string` | Get a node by its path |
754
733
  | `getChildren` | `parentPath: string` | Get direct children of a node |
755
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 |
756
744
 
757
745
  #### ScrollToPath Options
758
746
 
759
747
  | Option | Type | Default | Description |
760
748
  |--------|------|---------|-------------|
761
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) |
762
751
  | `highlight` | `boolean` | `true` | Apply temporary highlight animation to the target node |
763
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 |
764
755
 
765
756
  **Usage Example:**
766
757
  ```typescript
@@ -776,6 +767,9 @@ await tree.scrollToPath('1.2.3', {
776
767
  block: 'start'
777
768
  }
778
769
  });
770
+
771
+ // Scroll within a scrollable container (prevents page scroll)
772
+ await tree.scrollToPath('1.2.3', { containerScroll: true });
779
773
  ```
780
774
 
781
775
  **Highlight Classes Example:**
@@ -939,7 +933,7 @@ const sortCallback = (items: LTreeNode<T>[]) => {
939
933
  }
940
934
 
941
935
  // Then sort by your custom criteria
942
- return a.data.name.localeCompare(b.data.name);
936
+ return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
943
937
  });
944
938
  };
945
939
  ```
@@ -1010,36 +1004,40 @@ interface InsertArrayResult<T> {
1010
1004
 
1011
1005
  The component is optimized for large datasets:
1012
1006
 
1007
+ - **Virtual Scroll**: Renders only visible rows (~50 DOM nodes) for trees with 50,000+ nodes
1013
1008
  - **Flat Rendering Mode**: Single `{#each}` loop instead of recursive components (default, ~12x faster initial render)
1014
1009
  - **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
1010
  - **Async Search Indexing**: Uses `requestIdleCallback` for non-blocking search index building
1018
- - **Accurate Search Results**: Search index only includes successfully inserted nodes
1019
- - **Search Indexing**: Uses FlexSearch for fast search operations
1011
+ - **LTree**: Efficient hierarchical data structure with FlexSearch integration
1020
1012
 
1021
1013
  ### Performance Benchmarks (5500 nodes)
1022
1014
 
1023
1015
  | Operation | Time |
1024
1016
  |-----------|------|
1025
- | Initial render | ~25ms |
1017
+ | Initial render (flat) | ~25ms |
1018
+ | Initial render (virtual) | ~5ms |
1026
1019
  | Expand/collapse | ~100-150ms |
1027
1020
  | Search filtering | <50ms |
1021
+ | insertArray | <100ms |
1028
1022
 
1029
- ### v4.5+ Performance Improvements
1023
+ ### Virtual Scroll
1024
+
1025
+ For trees with 10,000+ nodes, enable virtual scroll to keep DOM size constant:
1030
1026
 
1031
- **Flat Rendering Mode** (default) - Renders all visible nodes in a single loop:
1032
1027
  ```svelte
1033
1028
  <Tree
1034
1029
  {data}
1035
- useFlatRendering={true}
1036
- progressiveRender={true}
1030
+ virtualScroll={true}
1031
+ virtualContainerHeight="500px"
1032
+ virtualOverscan={5}
1037
1033
  />
1038
1034
  ```
1039
1035
 
1040
- **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
1041
1039
 
1042
- **Performance Logging** - Built-in performance measurement for debugging:
1040
+ Built-in performance measurement for debugging:
1043
1041
  ```typescript
1044
1042
  import { enablePerfLogging } from '@keenmate/svelte-treeview';
1045
1043
  enablePerfLogging();
@@ -1052,32 +1050,12 @@ window.components['svelte-treeview'].perf.enable()
1052
1050
 
1053
1051
  ## CanvasTree (Canvas-Based Rendering)
1054
1052
 
1055
- `CanvasTree` renders the tree on an HTML5 Canvas for high-performance visualization of large hierarchies. It supports multiple layout modes, keyboard navigation, drag & drop, and custom node rendering.
1056
-
1057
- ### Layout Modes
1053
+ Canvas rendering is available as a separate companion package: [`@keenmate/svelte-treeview-canvas`](https://github.com/keenmate/svelte-treeview-canvas)
1058
1054
 
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 |
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:
1066
1056
 
1067
- ### Fishbone Navigation
1068
-
1069
- In fishbone layout, keyboard navigation follows the fishbone structure:
1070
-
1071
- - **Left/Right**: Navigate between same-side nodes along the spine axis. Spine nodes stay on their side; branch nodes traverse same-depth peers across all spine branches.
1072
- - **Up/Down**: Navigate parent/child within a branch. From spine nodes, enters branch children on the pressed side.
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
- />
1057
+ ```bash
1058
+ npm install @keenmate/svelte-treeview-canvas
1081
1059
  ```
1082
1060
 
1083
1061
  ## Development Setup & Contributing