@keenmate/svelte-treeview 4.6.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 +89 -36
- package/dist/components/Node.svelte +78 -33
- package/dist/components/Tree.svelte +6 -0
- package/dist/components/Tree.svelte.d.ts +6 -0
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- 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 +16 -1
- package/dist/ltree/types.d.ts +5 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,8 +2,54 @@
|
|
|
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
|
|
|
@@ -83,40 +129,6 @@ let treeData = $state.raw<TreeNode[]>([])
|
|
|
83
129
|
|
|
84
130
|
The array itself remains reactive - only individual items lose deep reactivity (which Tree doesn't need).
|
|
85
131
|
|
|
86
|
-
## 📢 New in v4.6: Progressive Flat Rendering
|
|
87
|
-
|
|
88
|
-
> [!NOTE]
|
|
89
|
-
> **The tree now uses progressive flat rendering by default for significantly improved performance.**
|
|
90
|
-
|
|
91
|
-
**What this means:**
|
|
92
|
-
- The tree renders immediately with the first batch of nodes (~200 by default)
|
|
93
|
-
- Remaining nodes are rendered progressively in subsequent frames
|
|
94
|
-
- For large trees (5000+ nodes), you'll see nodes appear over ~100-500ms instead of a single long freeze
|
|
95
|
-
- The UI remains responsive during rendering
|
|
96
|
-
|
|
97
|
-
**Configuration options:**
|
|
98
|
-
```svelte
|
|
99
|
-
<Tree
|
|
100
|
-
{data}
|
|
101
|
-
useFlatRendering={true} <!-- Default: true (flat mode) -->
|
|
102
|
-
progressiveRender={true} <!-- Default: true (batched rendering) -->
|
|
103
|
-
initialBatchSize={20} <!-- First batch size (default: 20) -->
|
|
104
|
-
maxBatchSize={500} <!-- Maximum batch size cap (default: 500) -->
|
|
105
|
-
/>
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Exponential batching:** The first batch renders 20 nodes instantly, then doubles each frame (20 → 40 → 80 → 160 → 320 → 500...) for optimal perceived performance.
|
|
109
|
-
|
|
110
|
-
**To use the legacy recursive rendering:**
|
|
111
|
-
```svelte
|
|
112
|
-
<Tree
|
|
113
|
-
{data}
|
|
114
|
-
useFlatRendering={false} <!-- Uses recursive Node components -->
|
|
115
|
-
/>
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Recursive mode may be preferred for very small trees or when you need the `{#key changeTracker}` behavior that recreates all nodes on any change.
|
|
119
|
-
|
|
120
132
|
## 🎯 Quick Start
|
|
121
133
|
|
|
122
134
|
```svelte
|
|
@@ -312,6 +324,45 @@ When using `dropZoneMode="floating"` (default), users can choose where to drop:
|
|
|
312
324
|
- **Below**: Insert as sibling after the target node
|
|
313
325
|
- **Child**: Insert as child of the target node
|
|
314
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
|
+
/>
|
|
350
|
+
```
|
|
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
|
+
|
|
315
366
|
#### Async Drop Validation
|
|
316
367
|
|
|
317
368
|
Use `beforeDropCallback` to validate or modify drops, including async operations like confirmation dialogs:
|
|
@@ -602,6 +653,7 @@ The component includes several pre-built classes for styling selected nodes:
|
|
|
602
653
|
| `isSelectedMember` | `string \| null` | `null` | Property name for selected state |
|
|
603
654
|
| `isDraggableMember` | `string \| null` | `null` | Property name for draggable state |
|
|
604
655
|
| `isDropAllowedMember` | `string \| null` | `null` | Property name for drop allowed state |
|
|
656
|
+
| `allowedDropPositionsMember` | `string \| null` | `null` | Property name for allowed drop positions array |
|
|
605
657
|
| `hasChildrenMember` | `string \| null` | `null` | Property name for children existence |
|
|
606
658
|
| `isSorted` | `boolean \| null` | `null` | Whether items should be sorted |
|
|
607
659
|
|
|
@@ -656,6 +708,7 @@ Without both requirements, no search indexing will occur.
|
|
|
656
708
|
| `onNodeClicked` | `(node) => void` | `undefined` | Node click event handler |
|
|
657
709
|
| `onNodeDragStart` | `(node, event) => void` | `undefined` | Drag start event handler |
|
|
658
710
|
| `onNodeDragOver` | `(node, event) => void` | `undefined` | Drag over event handler |
|
|
711
|
+
| `getAllowedDropPositionsCallback` | `(node) => DropPosition[] \| null` | `undefined` | Callback returning allowed drop positions per node |
|
|
659
712
|
| `beforeDropCallback` | `(dropNode, draggedNode, position, event, operation) => boolean \| { position?, operation? } \| Promise<...>` | `undefined` | Async-capable callback to validate/modify drops before they happen |
|
|
660
713
|
| `onNodeDrop` | `(dropNode, draggedNode, position, event, operation) => void` | `undefined` | Drop event handler. Position is 'above', 'below', or 'child'. Operation is 'move' or 'copy' |
|
|
661
714
|
|
|
@@ -89,24 +89,63 @@
|
|
|
89
89
|
// Track glow position for glow mode
|
|
90
90
|
let glowPosition = $state<'above' | 'below' | 'child' | null>(null);
|
|
91
91
|
|
|
92
|
+
// Get allowed drop positions for this node (empty/undefined = all allowed)
|
|
93
|
+
// Uses tree.getNodeAllowedDropPositions which checks callback > member > node property
|
|
94
|
+
const allowedPositions = $derived(tree.getNodeAllowedDropPositions(node));
|
|
95
|
+
|
|
96
|
+
// Check if a position is allowed for this node
|
|
97
|
+
function isPositionAllowed(position: DropPosition): boolean {
|
|
98
|
+
if (!allowedPositions || allowedPositions.length === 0) {
|
|
99
|
+
return true; // All positions allowed by default
|
|
100
|
+
}
|
|
101
|
+
return allowedPositions.includes(position);
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
// Calculate glow position based on mouse position in the node row
|
|
93
|
-
|
|
105
|
+
// Respects allowedDropPositions - snaps to nearest allowed position
|
|
106
|
+
function calculateGlowPosition(event: DragEvent, element: HTMLElement): 'above' | 'below' | 'child' | null {
|
|
94
107
|
const rect = element.getBoundingClientRect();
|
|
95
108
|
const x = event.clientX - rect.left;
|
|
96
109
|
const y = event.clientY - rect.top;
|
|
97
110
|
const width = rect.width;
|
|
98
111
|
const height = rect.height;
|
|
99
112
|
|
|
100
|
-
//
|
|
113
|
+
// Calculate the ideal position based on mouse position
|
|
114
|
+
let idealPosition: DropPosition;
|
|
101
115
|
if (x > width / 2) {
|
|
102
|
-
|
|
116
|
+
idealPosition = 'child';
|
|
117
|
+
} else if (y < height / 2) {
|
|
118
|
+
idealPosition = 'above';
|
|
119
|
+
} else {
|
|
120
|
+
idealPosition = 'below';
|
|
103
121
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
|
|
123
|
+
// If no restrictions, return the ideal position
|
|
124
|
+
if (!allowedPositions || allowedPositions.length === 0) {
|
|
125
|
+
return idealPosition;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// If the ideal position is allowed, use it
|
|
129
|
+
if (allowedPositions.includes(idealPosition)) {
|
|
130
|
+
return idealPosition;
|
|
107
131
|
}
|
|
108
|
-
|
|
109
|
-
|
|
132
|
+
|
|
133
|
+
// Otherwise, snap to the nearest allowed position
|
|
134
|
+
// Priority: if only one position allowed, use that
|
|
135
|
+
if (allowedPositions.length === 1) {
|
|
136
|
+
return allowedPositions[0];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Multiple positions allowed but not the ideal one
|
|
140
|
+
// For above/below: pick based on Y position
|
|
141
|
+
// For child: pick based on what's available
|
|
142
|
+
if (allowedPositions.includes('above') && allowedPositions.includes('below')) {
|
|
143
|
+
// Both above and below allowed, pick based on Y
|
|
144
|
+
return y < height / 2 ? 'above' : 'below';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Return the first allowed position
|
|
148
|
+
return allowedPositions[0];
|
|
110
149
|
}
|
|
111
150
|
|
|
112
151
|
// Convert reactive statements to derived values
|
|
@@ -244,9 +283,9 @@
|
|
|
244
283
|
class:ltree-clickable={node.isSelectable}
|
|
245
284
|
class:ltree-dragged={isDraggedNode}
|
|
246
285
|
class:ltree-draggable={node?.isDraggable}
|
|
247
|
-
class:ltree-glow-above={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'above'}
|
|
248
|
-
class:ltree-glow-below={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'below'}
|
|
249
|
-
class:ltree-glow-child={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'child'}
|
|
286
|
+
class:ltree-glow-above={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'above' && isPositionAllowed('above')}
|
|
287
|
+
class:ltree-glow-below={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'below' && isPositionAllowed('below')}
|
|
288
|
+
class:ltree-glow-child={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'child' && isPositionAllowed('child')}
|
|
250
289
|
class:ltree-drop-copy={isDragInProgress && isHoveredForDrop && dropOperation === 'copy'}
|
|
251
290
|
draggable={node?.isDraggable}
|
|
252
291
|
onclick={(e) => {
|
|
@@ -320,33 +359,39 @@
|
|
|
320
359
|
</div>
|
|
321
360
|
|
|
322
361
|
<!-- Drop zones: positioned relative to .ltree-node-row (outside content to avoid padding issues) -->
|
|
323
|
-
<!-- Only render floating drop zones when in 'floating' mode -->
|
|
362
|
+
<!-- Only render floating drop zones when in 'floating' mode, filtered by allowedDropPositions -->
|
|
324
363
|
{#if dropZoneMode === 'floating' && isDragInProgress && isHoveredForDrop}
|
|
325
364
|
<div
|
|
326
365
|
class="ltree-drop-zones ltree-drop-zones-{dropZoneLayout}"
|
|
327
366
|
style="--drop-zone-start: {formattedDropZoneStart}; --drop-zone-max-width: {dropZoneMaxWidth}px;"
|
|
328
367
|
>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
368
|
+
{#if isPositionAllowed('above')}
|
|
369
|
+
<div
|
|
370
|
+
class="ltree-drop-zone ltree-drop-above"
|
|
371
|
+
class:ltree-drop-zone-active={hoveredZone === 'above'}
|
|
372
|
+
ondragover={(e) => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = (allowCopy && e.ctrlKey) ? 'copy' : 'move'; hoveredZone = 'above'; callbacks.onNodeDragOver(node, e); }}
|
|
373
|
+
ondragleave={() => { hoveredZone = null; }}
|
|
374
|
+
ondrop={(e) => { e.stopPropagation(); if (e.dataTransfer) e.dataTransfer.dropEffect = (allowCopy && e.ctrlKey) ? 'copy' : 'move'; hoveredZone = null; callbacks.onZoneDrop(node, 'above', e); }}
|
|
375
|
+
>↑ Above</div>
|
|
376
|
+
{/if}
|
|
377
|
+
{#if isPositionAllowed('below')}
|
|
378
|
+
<div
|
|
379
|
+
class="ltree-drop-zone ltree-drop-below"
|
|
380
|
+
class:ltree-drop-zone-active={hoveredZone === 'below'}
|
|
381
|
+
ondragover={(e) => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = (allowCopy && e.ctrlKey) ? 'copy' : 'move'; hoveredZone = 'below'; callbacks.onNodeDragOver(node, e); }}
|
|
382
|
+
ondragleave={() => { hoveredZone = null; }}
|
|
383
|
+
ondrop={(e) => { e.stopPropagation(); if (e.dataTransfer) e.dataTransfer.dropEffect = (allowCopy && e.ctrlKey) ? 'copy' : 'move'; hoveredZone = null; callbacks.onZoneDrop(node, 'below', e); }}
|
|
384
|
+
>↓ Below</div>
|
|
385
|
+
{/if}
|
|
386
|
+
{#if isPositionAllowed('child')}
|
|
387
|
+
<div
|
|
388
|
+
class="ltree-drop-zone ltree-drop-child"
|
|
389
|
+
class:ltree-drop-zone-active={hoveredZone === 'child'}
|
|
390
|
+
ondragover={(e) => { e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = (allowCopy && e.ctrlKey) ? 'copy' : 'move'; hoveredZone = 'child'; callbacks.onNodeDragOver(node, e); }}
|
|
391
|
+
ondragleave={() => { hoveredZone = null; }}
|
|
392
|
+
ondrop={(e) => { e.stopPropagation(); if (e.dataTransfer) e.dataTransfer.dropEffect = (allowCopy && e.ctrlKey) ? 'copy' : 'move'; hoveredZone = null; callbacks.onZoneDrop(node, 'child', e); }}
|
|
393
|
+
>→ Child</div>
|
|
394
|
+
{/if}
|
|
350
395
|
</div>
|
|
351
396
|
{/if}
|
|
352
397
|
</div>
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
isSelectedMember?: string | null | undefined;
|
|
95
95
|
isDraggableMember?: string | null | undefined;
|
|
96
96
|
isDropAllowedMember?: string | null | undefined;
|
|
97
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
98
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
97
99
|
hasChildrenMember?: string | null | undefined;
|
|
98
100
|
isSorted?: boolean | null | undefined;
|
|
99
101
|
|
|
@@ -208,6 +210,8 @@
|
|
|
208
210
|
isSelectedMember,
|
|
209
211
|
isDraggableMember,
|
|
210
212
|
isDropAllowedMember,
|
|
213
|
+
allowedDropPositionsMember,
|
|
214
|
+
getAllowedDropPositionsCallback,
|
|
211
215
|
|
|
212
216
|
displayValueMember,
|
|
213
217
|
getDisplayValueCallback,
|
|
@@ -636,11 +640,13 @@
|
|
|
636
640
|
isSelectedMember,
|
|
637
641
|
isDraggableMember,
|
|
638
642
|
isDropAllowedMember,
|
|
643
|
+
allowedDropPositionsMember,
|
|
639
644
|
|
|
640
645
|
displayValueMember,
|
|
641
646
|
getDisplayValueCallback,
|
|
642
647
|
searchValueMember,
|
|
643
648
|
getSearchValueCallback,
|
|
649
|
+
getAllowedDropPositionsCallback,
|
|
644
650
|
orderMember,
|
|
645
651
|
treeId,
|
|
646
652
|
treePathSeparator,
|
|
@@ -13,6 +13,8 @@ declare function $$render<T>(): {
|
|
|
13
13
|
isSelectedMember?: string | null | undefined;
|
|
14
14
|
isDraggableMember?: string | null | undefined;
|
|
15
15
|
isDropAllowedMember?: string | null | undefined;
|
|
16
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
17
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
16
18
|
hasChildrenMember?: string | null | undefined;
|
|
17
19
|
isSorted?: boolean | null | undefined;
|
|
18
20
|
displayValueMember?: string | null | undefined;
|
|
@@ -158,6 +160,8 @@ declare function $$render<T>(): {
|
|
|
158
160
|
isSelectedMember?: string | null | undefined;
|
|
159
161
|
isDraggableMember?: string | null | undefined;
|
|
160
162
|
isDropAllowedMember?: string | null | undefined;
|
|
163
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
164
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
161
165
|
hasChildrenMember?: string | null | undefined;
|
|
162
166
|
isSorted?: boolean | null | undefined;
|
|
163
167
|
displayValueMember?: string | null | undefined;
|
|
@@ -313,6 +317,8 @@ declare class __sveltets_Render<T> {
|
|
|
313
317
|
isSelectedMember?: string | null | undefined;
|
|
314
318
|
isDraggableMember?: string | null | undefined;
|
|
315
319
|
isDropAllowedMember?: string | null | undefined;
|
|
320
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
321
|
+
getAllowedDropPositionsCallback?: ((node: LTreeNode<T>) => DropPosition[] | null | undefined) | undefined;
|
|
316
322
|
hasChildrenMember?: string | null | undefined;
|
|
317
323
|
isSorted?: boolean | null | undefined;
|
|
318
324
|
displayValueMember?: string | null | undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Auto-generated file - do not edit manually
|
|
2
2
|
// Generated by scripts/generate-constants.js
|
|
3
|
-
export const VERSION = "4.
|
|
3
|
+
export const VERSION = "4.7.0";
|
|
4
4
|
export const PACKAGE_NAME = "@keenmate/svelte-treeview";
|
|
5
5
|
export const AUTHOR = "KeenMate";
|
|
6
6
|
export const LICENSE = "MIT";
|
package/dist/ltree/indexer.js
CHANGED
|
@@ -26,7 +26,8 @@ export class Indexer {
|
|
|
26
26
|
}
|
|
27
27
|
// Add items to the processing queue
|
|
28
28
|
addToQueue(items) {
|
|
29
|
-
|
|
29
|
+
// Use concat instead of push(...items) to avoid stack overflow with large arrays
|
|
30
|
+
this.processingQueue = this.processingQueue.concat(items);
|
|
30
31
|
this.totalItemsAdded += items.length;
|
|
31
32
|
indexLogger.debug(`[${this.treeId}] Added ${items.length} items to queue. Queue size: ${this.processingQueue.length}`);
|
|
32
33
|
// Start processing if not already running
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type NodeId = string | number;
|
|
2
|
+
export type DropPosition = 'above' | 'below' | 'child';
|
|
2
3
|
export declare enum VisualState {
|
|
3
4
|
indeterminate = "indeterminate",
|
|
4
5
|
selected = "true",
|
|
@@ -18,6 +19,7 @@ export interface LTreeNode<T> {
|
|
|
18
19
|
priority: number | null | undefined;
|
|
19
20
|
isDraggable: boolean;
|
|
20
21
|
isDropAllowed: boolean;
|
|
22
|
+
allowedDropPositions: DropPosition[] | null | undefined;
|
|
21
23
|
isInsertAllowed: boolean;
|
|
22
24
|
isNestAllowed: boolean;
|
|
23
25
|
isCheckboxVisible: boolean | null | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Index } from 'flexsearch';
|
|
2
2
|
import { type LTreeNode } from './ltree-node.svelte';
|
|
3
3
|
import type { Ltree } from './types.js';
|
|
4
|
-
export declare function createLTree<T>(_idMember: string, _pathMember: string, _parentPathMember?: string | null | undefined, _levelMember?: string | null | undefined, _hasChildrenMember?: string | null | undefined, _isExpandedMember?: string | null | undefined, _isSelectableMember?: string | null | undefined, _isDraggableMember?: string | null | undefined, _isDropAllowedMember?: string | null | undefined, _displayValueMember?: string | null | undefined, _getDisplayValueCallback?: (node: LTreeNode<T>) => string, _searchValueMember?: string | null | undefined, _getSearchValueCallback?: (node: LTreeNode<T>) => string, _orderMember?: string | null | undefined, _treeId?: string, _treePathSeparator?: string | null | undefined, _expandLevel?: number | null | undefined, _shouldUseInternalSearchIndex?: boolean | null | undefined, _initializeIndexCallback?: () => Index, _indexerBatchSize?: number | null | undefined, _indexerTimeout?: number | null | undefined, opts?: Partial<Ltree<T>>): Ltree<T>;
|
|
4
|
+
export declare function createLTree<T>(_idMember: string, _pathMember: string, _parentPathMember?: string | null | undefined, _levelMember?: string | null | undefined, _hasChildrenMember?: string | null | undefined, _isExpandedMember?: string | null | undefined, _isSelectableMember?: string | null | undefined, _isDraggableMember?: string | null | undefined, _isDropAllowedMember?: string | null | undefined, _allowedDropPositionsMember?: string | null | undefined, _displayValueMember?: string | null | undefined, _getDisplayValueCallback?: (node: LTreeNode<T>) => string, _searchValueMember?: string | null | undefined, _getSearchValueCallback?: (node: LTreeNode<T>) => string, _getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => import('./types').DropPosition[] | null | undefined, _orderMember?: string | null | undefined, _treeId?: string, _treePathSeparator?: string | null | undefined, _expandLevel?: number | null | undefined, _shouldUseInternalSearchIndex?: boolean | null | undefined, _initializeIndexCallback?: () => Index, _indexerBatchSize?: number | null | undefined, _indexerTimeout?: number | null | undefined, opts?: Partial<Ltree<T>>): Ltree<T>;
|
|
@@ -5,7 +5,7 @@ import { getLevel, getParentPath, getPathSegments, getRelativePath } from '../he
|
|
|
5
5
|
import { createSearchIndex } from './flex.js';
|
|
6
6
|
import { Indexer } from './indexer.js';
|
|
7
7
|
import { perfStart, perfEnd, perfSummary } from '../perf-logger.js';
|
|
8
|
-
export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMember, _hasChildrenMember, _isExpandedMember, _isSelectableMember, _isDraggableMember, _isDropAllowedMember, _displayValueMember, _getDisplayValueCallback, _searchValueMember, _getSearchValueCallback, _orderMember, _treeId, _treePathSeparator, _expandLevel, _shouldUseInternalSearchIndex, _initializeIndexCallback, _indexerBatchSize, _indexerTimeout, opts) {
|
|
8
|
+
export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMember, _hasChildrenMember, _isExpandedMember, _isSelectableMember, _isDraggableMember, _isDropAllowedMember, _allowedDropPositionsMember, _displayValueMember, _getDisplayValueCallback, _searchValueMember, _getSearchValueCallback, _getAllowedDropPositionsCallback, _orderMember, _treeId, _treePathSeparator, _expandLevel, _shouldUseInternalSearchIndex, _initializeIndexCallback, _indexerBatchSize, _indexerTimeout, opts) {
|
|
9
9
|
let shouldCalculateParentPath = isEmptyString(_parentPathMember);
|
|
10
10
|
let shouldCalculateLevel = isEmptyString(_levelMember);
|
|
11
11
|
let shouldCalculateHasChildren = isEmptyString(_hasChildrenMember);
|
|
@@ -13,6 +13,7 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
13
13
|
let shouldCalculateIsSelectable = isEmptyString(_isSelectableMember);
|
|
14
14
|
let shouldCalculateIsDraggable = isEmptyString(_isDraggableMember);
|
|
15
15
|
let shouldCalculateIsDropAllowed = isEmptyString(_isDropAllowedMember);
|
|
16
|
+
let shouldCalculateAllowedDropPositions = isEmptyString(_allowedDropPositionsMember);
|
|
16
17
|
let shouldCalculateDisplayValue = isEmptyString(_displayValueMember);
|
|
17
18
|
let shouldCalculateSearchValue = isEmptyString(_searchValueMember);
|
|
18
19
|
// this is absolutely crucial to keep order of sorted items. Segments are just numbers and numbers as properties are always sorted
|
|
@@ -57,11 +58,13 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
57
58
|
isSelectableMember: _isSelectableMember,
|
|
58
59
|
isDraggableMember: _isDraggableMember,
|
|
59
60
|
isDropAllowedMember: _isDropAllowedMember,
|
|
61
|
+
allowedDropPositionsMember: _allowedDropPositionsMember,
|
|
60
62
|
hasChildrenMember: _hasChildrenMember,
|
|
61
63
|
displayValueMember: _displayValueMember,
|
|
62
64
|
getDisplayValueCallback: _getDisplayValueCallback,
|
|
63
65
|
searchValueMember: _searchValueMember,
|
|
64
66
|
getSearchValueCallback: _getSearchValueCallback,
|
|
67
|
+
getAllowedDropPositionsCallback: _getAllowedDropPositionsCallback,
|
|
65
68
|
orderMember: _orderMember,
|
|
66
69
|
isSorted: false,
|
|
67
70
|
// Properties for filtering
|
|
@@ -171,6 +174,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
171
174
|
node.isDraggable = row[_isDraggableMember];
|
|
172
175
|
if (!shouldCalculateIsDropAllowed)
|
|
173
176
|
node.isDropAllowed = row[_isDropAllowedMember];
|
|
177
|
+
if (!shouldCalculateAllowedDropPositions)
|
|
178
|
+
node.allowedDropPositions = row[_allowedDropPositionsMember];
|
|
174
179
|
if (!shouldCalculateHasChildren)
|
|
175
180
|
node.hasChildren = row[_hasChildrenMember];
|
|
176
181
|
node.data = row;
|
|
@@ -532,6 +537,16 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
532
537
|
return this.getSearchValueCallback(node);
|
|
533
538
|
return '[N/A]';
|
|
534
539
|
},
|
|
540
|
+
getNodeAllowedDropPositions(node) {
|
|
541
|
+
// Priority: callback > member > node property
|
|
542
|
+
if (this.getAllowedDropPositionsCallback) {
|
|
543
|
+
return this.getAllowedDropPositionsCallback(node);
|
|
544
|
+
}
|
|
545
|
+
if (!shouldCalculateAllowedDropPositions && node.data) {
|
|
546
|
+
return node.data[_allowedDropPositionsMember];
|
|
547
|
+
}
|
|
548
|
+
return node.allowedDropPositions;
|
|
549
|
+
},
|
|
535
550
|
refresh() {
|
|
536
551
|
this._emitTreeChanged();
|
|
537
552
|
},
|
package/dist/ltree/types.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { SearchOptions } from 'flexsearch';
|
|
2
|
-
import type { LTreeNode } from './ltree-node.svelte';
|
|
3
|
-
export type { LTreeNode } from './ltree-node.svelte';
|
|
2
|
+
import type { LTreeNode, DropPosition } from './ltree-node.svelte';
|
|
3
|
+
export type { LTreeNode, DropPosition } from './ltree-node.svelte';
|
|
4
4
|
export type Tuple<T, U> = [T, U];
|
|
5
|
-
export type DropPosition = 'above' | 'below' | 'child';
|
|
6
5
|
export type DragDropMode = 'none' | 'self' | 'cross' | 'both';
|
|
7
6
|
export type DropZoneLayout = 'around' | 'above' | 'below' | 'wave' | 'wave2';
|
|
8
7
|
export type DropOperation = 'move' | 'copy';
|
|
@@ -69,7 +68,10 @@ export interface Ltree<T> {
|
|
|
69
68
|
isSelectableMember: string | null | undefined;
|
|
70
69
|
isDraggableMember: string | null | undefined;
|
|
71
70
|
isDropAllowedMember: string | null | undefined;
|
|
71
|
+
allowedDropPositionsMember: string | null | undefined;
|
|
72
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
72
73
|
shouldDisplayDebugInformation: boolean | null | undefined;
|
|
74
|
+
getNodeAllowedDropPositions(node: LTreeNode<T>): DropPosition[] | null | undefined;
|
|
73
75
|
get tree(): LTreeNode<T>[];
|
|
74
76
|
/** Flat array of all visible nodes in render order (depth-first, respects isExpanded) */
|
|
75
77
|
get visibleFlatNodes(): LTreeNode<T>[];
|