@keenmate/svelte-treeview 4.5.0 ā 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -45
- package/dist/components/Node.svelte +136 -136
- package/dist/components/Node.svelte.d.ts +3 -22
- package/dist/components/Tree.svelte +341 -41
- package/dist/components/Tree.svelte.d.ts +64 -7
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/ltree/indexer.js +2 -1
- package/dist/ltree/ltree-node.svelte.d.ts +2 -0
- package/dist/ltree/ltree-node.svelte.js +1 -0
- package/dist/ltree/ltree.svelte.d.ts +1 -1
- package/dist/ltree/ltree.svelte.js +87 -6
- package/dist/ltree/types.d.ts +7 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
A high-performance, feature-rich hierarchical tree view component for Svelte 5 with drag & drop support, search functionality, and flexible data structures using LTree.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
## š¢ New in v4.7: Per-Node Drop Position Restrictions
|
|
6
|
+
|
|
7
|
+
> [!NOTE]
|
|
8
|
+
> **You can now restrict which drop positions (above/below/child) are allowed per node.**
|
|
9
|
+
|
|
10
|
+
Use `getAllowedDropPositionsCallback` for dynamic logic or `allowedDropPositionsMember` for server data:
|
|
11
|
+
```typescript
|
|
12
|
+
// Files can only have siblings, trash only accepts children
|
|
13
|
+
function getAllowedDropPositions(node) {
|
|
14
|
+
if (node.data?.type === 'file') return ['above', 'below'];
|
|
15
|
+
if (node.data?.type === 'trash') return ['child'];
|
|
16
|
+
return undefined; // all positions allowed (default)
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## š¢ v4.6: Progressive Flat Rendering
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
> **The tree now uses progressive flat rendering by default for significantly improved performance.**
|
|
24
|
+
|
|
25
|
+
**What this means:**
|
|
26
|
+
- The tree renders immediately with the first batch of nodes (~20 by default)
|
|
27
|
+
- Remaining nodes are rendered progressively in subsequent frames
|
|
28
|
+
- For large trees (5000+ nodes), you'll see nodes appear over ~100-500ms instead of a single long freeze
|
|
29
|
+
- The UI remains responsive during rendering
|
|
30
|
+
|
|
31
|
+
**Configuration options:**
|
|
32
|
+
```svelte
|
|
33
|
+
<Tree
|
|
34
|
+
{data}
|
|
35
|
+
useFlatRendering={true} <!-- Default: true (flat mode) -->
|
|
36
|
+
progressiveRender={true} <!-- Default: true (batched rendering) -->
|
|
37
|
+
initialBatchSize={20} <!-- First batch size (default: 20) -->
|
|
38
|
+
maxBatchSize={500} <!-- Maximum batch size cap (default: 500) -->
|
|
39
|
+
/>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Exponential batching:** The first batch renders 20 nodes instantly, then doubles each frame (20 ā 40 ā 80 ā 160 ā 320 ā 500...) for optimal perceived performance.
|
|
43
|
+
|
|
44
|
+
**To use the legacy recursive rendering:**
|
|
45
|
+
```svelte
|
|
46
|
+
<Tree
|
|
47
|
+
{data}
|
|
48
|
+
useFlatRendering={false} <!-- Uses recursive Node components -->
|
|
49
|
+
/>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Recursive mode may be preferred for very small trees or when you need the `{#key changeTracker}` behavior that recreates all nodes on any change.
|
|
7
53
|
|
|
8
54
|
## š Features
|
|
9
55
|
|
|
10
56
|
- **Svelte 5 Native**: Built specifically for Svelte 5 with full support for runes and modern Svelte patterns
|
|
11
|
-
- **High Performance**:
|
|
12
|
-
- **Drag & Drop**: Built-in drag and drop
|
|
57
|
+
- **High Performance**: Flat rendering mode with progressive loading for 5000+ nodes
|
|
58
|
+
- **Drag & Drop**: Built-in drag and drop with position control (above/below/child), touch support, and async validation
|
|
59
|
+
- **Tree Editing**: Built-in methods for add, move, remove operations with automatic path management
|
|
13
60
|
- **Search & Filter**: Integrated FlexSearch for fast, full-text search capabilities
|
|
14
61
|
- **Flexible Data Sources**: Works with any hierarchical data structure
|
|
15
62
|
- **Context Menus**: Dynamic right-click menus with callback-based generation, icons, disabled states
|
|
@@ -237,49 +284,163 @@ For complete FlexSearch documentation, visit: [FlexSearch Options](https://githu
|
|
|
237
284
|
```svelte
|
|
238
285
|
<script lang="ts">
|
|
239
286
|
import { Tree } from '@keenmate/svelte-treeview';
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
{ path: 'zone1', name: 'Drop Zone 1' },
|
|
248
|
-
{ path: 'zone2', name: 'Drop Zone 2' }
|
|
287
|
+
|
|
288
|
+
let treeRef: Tree<MyNode>;
|
|
289
|
+
|
|
290
|
+
const data = [
|
|
291
|
+
{ path: '1', name: 'Folder 1', isDraggable: true },
|
|
292
|
+
{ path: '1.1', name: 'Item 1', isDraggable: true },
|
|
293
|
+
{ path: '2', name: 'Folder 2', isDraggable: true }
|
|
249
294
|
];
|
|
250
|
-
|
|
295
|
+
|
|
251
296
|
function onDragStart(node, event) {
|
|
252
297
|
console.log('Dragging:', node.data.name);
|
|
253
298
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
299
|
+
|
|
300
|
+
// Same-tree moves are auto-handled - this callback is for notification/custom logic
|
|
301
|
+
function onDrop(dropNode, draggedNode, position, event, operation) {
|
|
302
|
+
console.log(`Dropped ${draggedNode.data.name} ${position} ${dropNode?.data.name}`);
|
|
303
|
+
// position is 'above', 'below', or 'child'
|
|
304
|
+
// operation is 'move' or 'copy' (Ctrl+drag)
|
|
258
305
|
}
|
|
259
306
|
</script>
|
|
260
307
|
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
308
|
+
<Tree
|
|
309
|
+
bind:this={treeRef}
|
|
310
|
+
{data}
|
|
311
|
+
idMember="path"
|
|
312
|
+
pathMember="path"
|
|
313
|
+
orderMember="sortOrder"
|
|
314
|
+
dragOverNodeClass="ltree-dragover-highlight"
|
|
315
|
+
onNodeDragStart={onDragStart}
|
|
316
|
+
onNodeDrop={onDrop}
|
|
317
|
+
/>
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### Drop Position Control
|
|
321
|
+
|
|
322
|
+
When using `dropZoneMode="floating"` (default), users can choose where to drop:
|
|
323
|
+
- **Above**: Insert as sibling before the target node
|
|
324
|
+
- **Below**: Insert as sibling after the target node
|
|
325
|
+
- **Child**: Insert as child of the target node
|
|
326
|
+
|
|
327
|
+
#### Per-Node Drop Position Restrictions
|
|
328
|
+
|
|
329
|
+
You can restrict which drop positions are allowed per node. This is useful for:
|
|
330
|
+
- **Trash/Recycle Bin**: Only allow dropping INTO (child), not above/below
|
|
331
|
+
- **Files**: Only allow above/below (can't drop INTO a file)
|
|
332
|
+
- **Folders**: Allow all positions (default)
|
|
333
|
+
|
|
334
|
+
```svelte
|
|
335
|
+
<script lang="ts">
|
|
336
|
+
import { Tree, type DropPosition, type LTreeNode } from '@keenmate/svelte-treeview';
|
|
337
|
+
|
|
338
|
+
// Dynamic callback approach
|
|
339
|
+
function getAllowedDropPositions(node: LTreeNode<MyItem>): DropPosition[] | null {
|
|
340
|
+
if (node.data?.type === 'file') return ['above', 'below'];
|
|
341
|
+
if (node.data?.type === 'trash') return ['child'];
|
|
342
|
+
return undefined; // all positions allowed
|
|
343
|
+
}
|
|
344
|
+
</script>
|
|
345
|
+
|
|
346
|
+
<Tree
|
|
347
|
+
{data}
|
|
348
|
+
getAllowedDropPositionsCallback={getAllowedDropPositions}
|
|
349
|
+
/>
|
|
281
350
|
```
|
|
282
351
|
|
|
352
|
+
Or use the member approach for server-side data:
|
|
353
|
+
```svelte
|
|
354
|
+
<Tree
|
|
355
|
+
{data}
|
|
356
|
+
allowedDropPositionsMember="allowedDropPositions"
|
|
357
|
+
/>
|
|
358
|
+
|
|
359
|
+
<!-- Where data items have: { allowedDropPositions: ['child'] } -->
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
When restrictions are applied:
|
|
363
|
+
- **Glow mode**: Snaps to the nearest allowed position
|
|
364
|
+
- **Floating mode**: Only renders buttons for allowed positions
|
|
365
|
+
|
|
366
|
+
#### Async Drop Validation
|
|
367
|
+
|
|
368
|
+
Use `beforeDropCallback` to validate or modify drops, including async operations like confirmation dialogs:
|
|
369
|
+
|
|
370
|
+
```svelte
|
|
371
|
+
<script lang="ts">
|
|
372
|
+
async function beforeDrop(dropNode, draggedNode, position, event, operation) {
|
|
373
|
+
// Cancel specific drops
|
|
374
|
+
if (draggedNode.data.locked) {
|
|
375
|
+
return false; // Cancel the drop
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Show confirmation dialog (async)
|
|
379
|
+
if (position === 'child' && !dropNode.data.isFolder) {
|
|
380
|
+
const confirmed = await showConfirmDialog('Drop as sibling instead?');
|
|
381
|
+
if (!confirmed) return false;
|
|
382
|
+
return { position: 'below' }; // Override position
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Proceed normally
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
</script>
|
|
389
|
+
|
|
390
|
+
<Tree
|
|
391
|
+
{data}
|
|
392
|
+
beforeDropCallback={beforeDrop}
|
|
393
|
+
onNodeDrop={onDrop}
|
|
394
|
+
/>
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Tree Editing
|
|
398
|
+
|
|
399
|
+
The tree provides built-in methods for programmatic editing:
|
|
400
|
+
|
|
401
|
+
```svelte
|
|
402
|
+
<script lang="ts">
|
|
403
|
+
import { Tree } from '@keenmate/svelte-treeview';
|
|
404
|
+
|
|
405
|
+
let treeRef: Tree<MyNode>;
|
|
406
|
+
|
|
407
|
+
// Add a new node
|
|
408
|
+
function addChild() {
|
|
409
|
+
const result = treeRef.addNode(
|
|
410
|
+
selectedNode?.path || '', // parent path (empty = root)
|
|
411
|
+
{ id: Date.now(), path: '', name: 'New Item', sortOrder: 100 }
|
|
412
|
+
);
|
|
413
|
+
if (result.success) {
|
|
414
|
+
console.log('Added:', result.node);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Move a node
|
|
419
|
+
function moveUp() {
|
|
420
|
+
const siblings = treeRef.getSiblings(selectedNode.path);
|
|
421
|
+
const index = siblings.findIndex(s => s.path === selectedNode.path);
|
|
422
|
+
if (index > 0) {
|
|
423
|
+
treeRef.moveNode(selectedNode.path, siblings[index - 1].path, 'above');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Remove a node
|
|
428
|
+
function remove() {
|
|
429
|
+
treeRef.removeNode(selectedNode.path);
|
|
430
|
+
}
|
|
431
|
+
</script>
|
|
432
|
+
|
|
433
|
+
<Tree
|
|
434
|
+
bind:this={treeRef}
|
|
435
|
+
{data}
|
|
436
|
+
idMember="id"
|
|
437
|
+
pathMember="path"
|
|
438
|
+
orderMember="sortOrder"
|
|
439
|
+
/>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**Note**: When using `orderMember`, the tree automatically calculates sort order values when moving nodes with 'above' or 'below' positions.
|
|
443
|
+
|
|
283
444
|
### With Context Menus
|
|
284
445
|
|
|
285
446
|
The tree supports context menus with two approaches: callback-based (recommended) and snippet-based.
|
|
@@ -492,6 +653,7 @@ The component includes several pre-built classes for styling selected nodes:
|
|
|
492
653
|
| `isSelectedMember` | `string \| null` | `null` | Property name for selected state |
|
|
493
654
|
| `isDraggableMember` | `string \| null` | `null` | Property name for draggable state |
|
|
494
655
|
| `isDropAllowedMember` | `string \| null` | `null` | Property name for drop allowed state |
|
|
656
|
+
| `allowedDropPositionsMember` | `string \| null` | `null` | Property name for allowed drop positions array |
|
|
495
657
|
| `hasChildrenMember` | `string \| null` | `null` | Property name for children existence |
|
|
496
658
|
| `isSorted` | `boolean \| null` | `null` | Whether items should be sorted |
|
|
497
659
|
|
|
@@ -531,6 +693,10 @@ Without both requirements, no search indexing will occur.
|
|
|
531
693
|
|------|------|---------|-------------|
|
|
532
694
|
| `expandLevel` | `number \| null` | `2` | Automatically expand nodes up to this level |
|
|
533
695
|
| `shouldToggleOnNodeClick` | `boolean` | `true` | Toggle expansion on node click |
|
|
696
|
+
| `useFlatRendering` | `boolean` | `true` | Use flat rendering mode (faster for large trees) |
|
|
697
|
+
| `progressiveRender` | `boolean` | `true` | Progressively render nodes in batches |
|
|
698
|
+
| `renderBatchSize` | `number` | `50` | Number of nodes to render per batch |
|
|
699
|
+
| `orderMember` | `string \| null` | `null` | Property name for sort order (enables above/below positioning in drag-drop) |
|
|
534
700
|
| `indexerBatchSize` | `number \| null` | `25` | Number of nodes to process per batch during search indexing |
|
|
535
701
|
| `indexerTimeout` | `number \| null` | `50` | Maximum time (ms) to wait for idle callback before forcing indexing |
|
|
536
702
|
| `shouldDisplayDebugInformation` | `boolean` | `false` | Show debug information panel with tree statistics and enable console debug logging for tree operations and async search indexing |
|
|
@@ -542,7 +708,9 @@ Without both requirements, no search indexing will occur.
|
|
|
542
708
|
| `onNodeClicked` | `(node) => void` | `undefined` | Node click event handler |
|
|
543
709
|
| `onNodeDragStart` | `(node, event) => void` | `undefined` | Drag start event handler |
|
|
544
710
|
| `onNodeDragOver` | `(node, event) => void` | `undefined` | Drag over event handler |
|
|
545
|
-
| `
|
|
711
|
+
| `getAllowedDropPositionsCallback` | `(node) => DropPosition[] \| null` | `undefined` | Callback returning allowed drop positions per node |
|
|
712
|
+
| `beforeDropCallback` | `(dropNode, draggedNode, position, event, operation) => boolean \| { position?, operation? } \| Promise<...>` | `undefined` | Async-capable callback to validate/modify drops before they happen |
|
|
713
|
+
| `onNodeDrop` | `(dropNode, draggedNode, position, event, operation) => void` | `undefined` | Drop event handler. Position is 'above', 'below', or 'child'. Operation is 'move' or 'copy' |
|
|
546
714
|
|
|
547
715
|
#### Visual Styling Properties
|
|
548
716
|
| Prop | Type | Default | Description |
|
|
@@ -577,6 +745,12 @@ Without both requirements, no search indexing will occur.
|
|
|
577
745
|
| `searchNodes` | `searchText: string \| null \| undefined, searchOptions?: SearchOptions` | Search nodes using internal search index and return matching nodes with optional FlexSearch options |
|
|
578
746
|
| `scrollToPath` | `path: string, options?: ScrollToPathOptions` | Scroll to and highlight a specific node |
|
|
579
747
|
| `update` | `updates: Partial<Props>` | Programmatically update component props from external JavaScript |
|
|
748
|
+
| `addNode` | `parentPath: string, data: T, pathSegment?: string` | Add a new node under the specified parent |
|
|
749
|
+
| `moveNode` | `sourcePath: string, targetPath: string, position: 'above' \| 'below' \| 'child'` | Move a node to a new location |
|
|
750
|
+
| `removeNode` | `path: string, includeDescendants?: boolean` | Remove a node (and optionally its descendants) |
|
|
751
|
+
| `getNodeByPath` | `path: string` | Get a node by its path |
|
|
752
|
+
| `getChildren` | `parentPath: string` | Get direct children of a node |
|
|
753
|
+
| `getSiblings` | `path: string` | Get siblings of a node (including itself) |
|
|
580
754
|
|
|
581
755
|
#### ScrollToPath Options
|
|
582
756
|
|
|
@@ -742,8 +916,17 @@ Triggered when drag operation starts.
|
|
|
742
916
|
#### onNodeDragOver(node, event)
|
|
743
917
|
Triggered when dragging over a potential drop target.
|
|
744
918
|
|
|
745
|
-
####
|
|
746
|
-
|
|
919
|
+
#### beforeDropCallback(dropNode, draggedNode, position, event, operation)
|
|
920
|
+
Called before a drop is processed. Can be async for showing dialogs.
|
|
921
|
+
- Return `false` to cancel the drop
|
|
922
|
+
- Return `{ position: 'above'|'below'|'child' }` to override position
|
|
923
|
+
- Return `{ operation: 'move'|'copy' }` to override operation
|
|
924
|
+
- Return `true` or `undefined` to proceed normally
|
|
925
|
+
|
|
926
|
+
#### onNodeDrop(dropNode, draggedNode, position, event, operation)
|
|
927
|
+
Triggered when a node is dropped. For same-tree moves, the tree auto-handles the move and this callback is for notification.
|
|
928
|
+
- `position`: 'above', 'below', or 'child'
|
|
929
|
+
- `operation`: 'move' or 'copy' (Ctrl+drag)
|
|
747
930
|
|
|
748
931
|
### Slots
|
|
749
932
|
|
|
@@ -917,15 +1100,34 @@ interface InsertArrayResult<T> {
|
|
|
917
1100
|
|
|
918
1101
|
The component is optimized for large datasets:
|
|
919
1102
|
|
|
1103
|
+
- **Flat Rendering Mode**: Single `{#each}` loop instead of recursive components (default, ~12x faster initial render)
|
|
1104
|
+
- **Progressive Rendering**: Batched rendering prevents UI freeze during initial load
|
|
1105
|
+
- **Context-Based Callbacks**: Stable function references eliminate unnecessary re-renders
|
|
920
1106
|
- **LTree**: Efficient hierarchical data structure
|
|
921
1107
|
- **Async Search Indexing**: Uses `requestIdleCallback` for non-blocking search index building
|
|
922
|
-
- **Accurate Search Results**: Search index only includes successfully inserted nodes
|
|
923
|
-
- **Consistent Visual Hierarchy**: Optimized CSS-based indentation prevents exponential spacing growth
|
|
1108
|
+
- **Accurate Search Results**: Search index only includes successfully inserted nodes
|
|
924
1109
|
- **Search Indexing**: Uses FlexSearch for fast search operations
|
|
925
1110
|
|
|
926
|
-
###
|
|
1111
|
+
### Performance Benchmarks (5500 nodes)
|
|
1112
|
+
|
|
1113
|
+
| Operation | Time |
|
|
1114
|
+
|-----------|------|
|
|
1115
|
+
| Initial render | ~25ms |
|
|
1116
|
+
| Expand/collapse | ~100-150ms |
|
|
1117
|
+
| Search filtering | <50ms |
|
|
1118
|
+
|
|
1119
|
+
### v4.5+ Performance Improvements
|
|
1120
|
+
|
|
1121
|
+
**Flat Rendering Mode** (default) - Renders all visible nodes in a single loop:
|
|
1122
|
+
```svelte
|
|
1123
|
+
<Tree
|
|
1124
|
+
{data}
|
|
1125
|
+
useFlatRendering={true}
|
|
1126
|
+
progressiveRender={true}
|
|
1127
|
+
/>
|
|
1128
|
+
```
|
|
927
1129
|
|
|
928
|
-
**Optimized `insertArray` algorithm** - Fixed O(n²) bottleneck
|
|
1130
|
+
**Optimized `insertArray` algorithm** - Fixed O(n²) bottleneck. Now loads 17,000+ nodes in under 100ms.
|
|
929
1131
|
|
|
930
1132
|
**Performance Logging** - Built-in performance measurement for debugging:
|
|
931
1133
|
```typescript
|