@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.
Files changed (75) hide show
  1. package/README.md +148 -175
  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/ContextMenuDivider.svelte +13 -0
  16. package/dist/components/ContextMenuDivider.svelte.d.ts +6 -0
  17. package/dist/components/ContextMenuItem.svelte +69 -0
  18. package/dist/components/ContextMenuItem.svelte.d.ts +14 -0
  19. package/dist/components/Node.svelte +51 -57
  20. package/dist/components/Node.svelte.d.ts +2 -2
  21. package/dist/components/Tree.svelte +258 -49
  22. package/dist/components/Tree.svelte.d.ts +38 -14
  23. package/dist/constants.generated.d.ts +1 -1
  24. package/dist/constants.generated.js +1 -1
  25. package/dist/core/TreeController.svelte.d.ts +55 -5
  26. package/dist/core/TreeController.svelte.js +215 -26
  27. package/dist/global-api.d.ts +1 -1
  28. package/dist/global-api.js +5 -5
  29. package/dist/index.d.ts +7 -10
  30. package/dist/index.js +5 -8
  31. package/dist/logger.d.ts +7 -6
  32. package/dist/logger.js +0 -2
  33. package/dist/ltree/indexer.js +1 -1
  34. package/dist/ltree/ltree-node.svelte.d.ts +1 -0
  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 +94 -59
  38. package/dist/ltree/types.d.ts +17 -5
  39. package/dist/perf-logger.d.ts +2 -1
  40. package/dist/perf-logger.js +0 -2
  41. package/dist/styles/main.scss +129 -12
  42. package/dist/styles.css +101 -9
  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/canvas/CanvasTree.svelte +0 -1657
  48. package/dist/canvas/CanvasTree.svelte.d.ts +0 -148
  49. package/dist/canvas/canvas-interaction.d.ts +0 -67
  50. package/dist/canvas/canvas-interaction.js +0 -590
  51. package/dist/canvas/canvas-layout-balanced.d.ts +0 -18
  52. package/dist/canvas/canvas-layout-balanced.js +0 -212
  53. package/dist/canvas/canvas-layout-box.d.ts +0 -10
  54. package/dist/canvas/canvas-layout-box.js +0 -194
  55. package/dist/canvas/canvas-layout-fishbone.d.ts +0 -11
  56. package/dist/canvas/canvas-layout-fishbone.js +0 -349
  57. package/dist/canvas/canvas-layout-radial.d.ts +0 -21
  58. package/dist/canvas/canvas-layout-radial.js +0 -233
  59. package/dist/canvas/canvas-layout-sunburst.d.ts +0 -27
  60. package/dist/canvas/canvas-layout-sunburst.js +0 -457
  61. package/dist/canvas/canvas-layout.d.ts +0 -11
  62. package/dist/canvas/canvas-layout.js +0 -509
  63. package/dist/canvas/canvas-renderer.d.ts +0 -46
  64. package/dist/canvas/canvas-renderer.js +0 -828
  65. package/dist/canvas/canvas-text.d.ts +0 -11
  66. package/dist/canvas/canvas-text.js +0 -64
  67. package/dist/canvas/canvas-theme.d.ts +0 -58
  68. package/dist/canvas/canvas-theme.js +0 -128
  69. package/dist/canvas/types.d.ts +0 -187
  70. package/dist/canvas/types.js +0 -1
  71. package/dist/ltree/ltree-demo.d.ts +0 -2
  72. package/dist/ltree/ltree-demo.js +0 -90
  73. package/dist/vendor/loglevel/loglevel-esm.d.ts +0 -2
  74. package/dist/vendor/loglevel/loglevel-plugin-prefix-esm.d.ts +0 -7
  75. 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: Core/Renderer Split
9
+ ## What's New in v5.0.0-rc03
10
10
 
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.
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
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
- > [!NOTE]
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
- 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
- ```
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
- ## v4.6: Progressive Flat Rendering
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
- > [!NOTE]
37
- > **The tree now uses progressive flat rendering by default for significantly improved performance.**
30
+ ### Rendering Modes
38
31
 
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
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
- <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
- ```
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
- **To use the legacy recursive rendering:**
59
- ```svelte
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 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
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 callback-based generation, icons, disabled states
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.name)}
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.icon}</span>
163
- <strong>{node.data.name}</strong>
164
- {#if node.data.size}
165
- <small class="text-muted ms-2">({node.data.size})</small>
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.name);
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.name} ${position} ${dropNode?.data.name}`);
286
- // position is 'above', 'below', or 'child'
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"` (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
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 above/below
315
- - **Files**: Only allow above/below (can't drop INTO a file)
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 ['above', 'below'];
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: 'below' }; // Override 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, 'above');
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 'above' or 'below' positions.
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 (recommended) and snippet-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 { ContextMenuItem } from '@keenmate/svelte-treeview';
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): ContextMenuItem[] {
446
- const items: ContextMenuItem[] = [];
447
-
448
- // Always available
449
- items.push({
450
- icon: '📂',
451
- title: 'Open',
452
- callback: () => alert(`Opening ${node.data.name}`)
453
- });
454
-
455
- // Conditional actions based on node data
456
- if (node.data.canEdit) {
457
- items.push({
458
- icon: '✏️',
459
- title: 'Edit',
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-Based Context Menus
449
+ #### Snippet + Component Context Menus
498
450
 
499
451
  ```svelte
500
- <Tree
501
- {data}
502
- idMember="path"
503
- pathMember="path"
504
- >
505
- {#snippet contextMenu(node, closeMenu)}
506
- <div class="context-menu-item" onclick={() => { alert(`Open ${node.data.name}`); closeMenu(); }}>
507
- 📂 Open
508
- </div>
509
- <div class="context-menu-divider"></div>
510
- <div class="context-menu-item" onclick={() => { alert(`Delete ${node.data.name}`); closeMenu(); }}>
511
- 🗑️ Delete
512
- </div>
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
- - **Dynamic menus**: Generate menu items based on node properties
520
- - **Icons and dividers**: Visual organization and identification
521
- - **Disabled states**: Context-sensitive menu availability
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[]` | default sort | Function to sort items (optional) |
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` | `false` | Enable built-in search functionality |
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 above/below positioning in drag-drop) |
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
- | `flatIndentSize` | `string` | `'1.5rem'` | Indentation size per level in flat mode |
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 `'above'`, `'below'`, or `'child'`. Operation is `'move'` or `'copy'` |
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` | | No data template |
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: 'above' \| 'below' \| 'child'` | Move a node to a new location |
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.name.localeCompare(b.data.name);
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
- - **Accurate Search Results**: Search index only includes successfully inserted nodes
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
- ### v4.5+ Performance Improvements
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
- useFlatRendering={true}
1036
- progressiveRender={true}
1025
+ virtualScroll={true}
1026
+ virtualContainerHeight="500px"
1027
+ virtualOverscan={5}
1037
1028
  />
1038
1029
  ```
1039
1030
 
1040
- **Optimized `insertArray` algorithm** - Fixed O(n²) bottleneck. Now loads 17,000+ nodes in under 100ms.
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
- **Performance Logging** - Built-in performance measurement for debugging:
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
- `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
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
- In fishbone layout, keyboard navigation follows the fishbone structure:
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
- - **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
- />
1052
+ ```bash
1053
+ npm install @keenmate/svelte-treeview-canvas
1081
1054
  ```
1082
1055
 
1083
1056
  ## Development Setup & Contributing