@keenmate/svelte-treeview 4.8.0 → 5.0.0-rc02

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +106 -117
  2. package/ai/INDEX.txt +310 -0
  3. package/ai/advanced-patterns.txt +506 -0
  4. package/ai/basic-setup.txt +336 -0
  5. package/ai/context-menu.txt +349 -0
  6. package/ai/data-handling.txt +390 -0
  7. package/ai/drag-drop.txt +397 -0
  8. package/ai/events-callbacks.txt +382 -0
  9. package/ai/import-patterns.txt +271 -0
  10. package/ai/performance.txt +349 -0
  11. package/ai/search-features.txt +359 -0
  12. package/ai/styling-theming.txt +354 -0
  13. package/ai/tree-editing.txt +423 -0
  14. package/ai/typescript-types.txt +357 -0
  15. package/dist/components/Node.svelte +47 -40
  16. package/dist/components/Node.svelte.d.ts +1 -1
  17. package/dist/components/Tree.svelte +384 -1479
  18. package/dist/components/Tree.svelte.d.ts +30 -28
  19. package/dist/components/TreeProvider.svelte +28 -0
  20. package/dist/components/TreeProvider.svelte.d.ts +28 -0
  21. package/dist/constants.generated.d.ts +1 -1
  22. package/dist/constants.generated.js +1 -1
  23. package/dist/core/TreeController.svelte.d.ts +353 -0
  24. package/dist/core/TreeController.svelte.js +1503 -0
  25. package/dist/core/createTreeController.d.ts +9 -0
  26. package/dist/core/createTreeController.js +11 -0
  27. package/dist/global-api.d.ts +1 -1
  28. package/dist/global-api.js +5 -5
  29. package/dist/index.d.ts +10 -6
  30. package/dist/index.js +7 -3
  31. package/dist/logger.d.ts +7 -6
  32. package/dist/logger.js +0 -2
  33. package/dist/ltree/indexer.js +2 -4
  34. package/dist/ltree/ltree-node.svelte.d.ts +2 -1
  35. package/dist/ltree/ltree-node.svelte.js +1 -0
  36. package/dist/ltree/ltree.svelte.d.ts +1 -1
  37. package/dist/ltree/ltree.svelte.js +168 -175
  38. package/dist/ltree/types.d.ts +12 -8
  39. package/dist/perf-logger.d.ts +2 -1
  40. package/dist/perf-logger.js +0 -2
  41. package/dist/styles/main.scss +78 -78
  42. package/dist/styles.css +41 -41
  43. package/dist/styles.css.map +1 -1
  44. package/dist/vendor/loglevel/index.d.ts +55 -2
  45. package/dist/vendor/loglevel/prefix.d.ts +23 -2
  46. package/package.json +96 -95
  47. package/dist/ltree/ltree-demo.d.ts +0 -2
  48. package/dist/ltree/ltree-demo.js +0 -90
  49. package/dist/vendor/loglevel/loglevel-esm.d.ts +0 -2
  50. package/dist/vendor/loglevel/loglevel-plugin-prefix-esm.d.ts +0 -7
  51. package/dist/vendor/loglevel/loglevel-plugin-prefix.d.ts +0 -2
@@ -0,0 +1,357 @@
1
+ TYPESCRIPT TYPES
2
+ ================
3
+
4
+ CRITICAL: Full TypeScript support
5
+ - Generic types for your data
6
+ - All props and methods typed
7
+ - Type exports from package
8
+
9
+ BASIC TYPED USAGE
10
+ -----------------
11
+ <script lang="ts">
12
+ import { Tree } from '@keenmate/svelte-treeview';
13
+ import type { LTreeNode } from '@keenmate/svelte-treeview';
14
+
15
+ interface MyItem {
16
+ id: string;
17
+ path: string;
18
+ name: string;
19
+ type: 'file' | 'folder';
20
+ }
21
+
22
+ let data: MyItem[] = $state([]);
23
+ let selectedNode: LTreeNode<MyItem> | null = $state(null);
24
+ </script>
25
+
26
+ <Tree
27
+ {data}
28
+ idMember="id"
29
+ pathMember="path"
30
+ bind:selectedNode
31
+ />
32
+
33
+ TYPE EXPORTS
34
+ ------------
35
+ import type {
36
+ // Core types
37
+ LTreeNode, // Tree node interface
38
+ Ltree, // Tree instance interface
39
+ DropPosition, // 'before' | 'after' | 'child'
40
+
41
+ // Context menu
42
+ ContextMenuItem, // Menu item interface
43
+
44
+ // Results
45
+ InsertArrayResult, // Insert operation result
46
+
47
+ // Internal (advanced)
48
+ NodeCallbacks, // Event callback types
49
+ NodeConfig // Node configuration
50
+ } from '@keenmate/svelte-treeview';
51
+
52
+ LTREENODE INTERFACE
53
+ -------------------
54
+ interface LTreeNode<T> {
55
+ // Path information
56
+ path: string; // Full path "1.2.3"
57
+ pathSegment: string; // Last segment "3"
58
+ parentPath: string | null; // Parent "1.2" or null
59
+
60
+ // Structure
61
+ level: number | null; // Depth (0 = root)
62
+ children: Record<string, LTreeNode<T>>;
63
+ hasChildren: boolean;
64
+
65
+ // Your data
66
+ data: T | null; // Original data object
67
+
68
+ // State
69
+ isExpanded: boolean;
70
+ isSelected: boolean;
71
+
72
+ // Drag and drop
73
+ isDraggable: boolean;
74
+ isDropAllowed: boolean;
75
+ allowedDropPositions: DropPosition[] | null | undefined;
76
+ }
77
+
78
+ DROPPOSITION TYPE
79
+ -----------------
80
+ type DropPosition = 'before' | 'after' | 'child';
81
+
82
+ Usage:
83
+ import type { DropPosition } from '@keenmate/svelte-treeview';
84
+
85
+ function getAllowedPositions(node: LTreeNode<MyItem>): DropPosition[] | null {
86
+ if (node.data?.type === 'file') {
87
+ return ['before', 'after'];
88
+ }
89
+ return null; // all positions
90
+ }
91
+
92
+ CONTEXTMENUITEM INTERFACE
93
+ -------------------------
94
+ interface ContextMenuItem {
95
+ icon?: string;
96
+ title: string;
97
+ isDisabled?: boolean;
98
+ callback: () => void | Promise<void>;
99
+ isDivider?: boolean;
100
+ className?: string;
101
+ }
102
+
103
+ Usage:
104
+ function createMenu(node: LTreeNode<MyItem>, closeMenu: () => void): ContextMenuItem[] {
105
+ return [
106
+ {
107
+ icon: '📂',
108
+ title: 'Open',
109
+ callback: () => openItem(node.data)
110
+ },
111
+ { isDivider: true },
112
+ {
113
+ icon: '🗑️',
114
+ title: 'Delete',
115
+ isDisabled: node.data?.isProtected,
116
+ callback: () => deleteItem(node.data)
117
+ }
118
+ ];
119
+ }
120
+
121
+ INSERTARRAYRESULT INTERFACE
122
+ ---------------------------
123
+ interface InsertArrayResult<T> {
124
+ successful: number;
125
+ failed: Array<{
126
+ node: LTreeNode<T>;
127
+ originalData: T;
128
+ error: string;
129
+ }>;
130
+ total: number;
131
+ }
132
+
133
+ Usage:
134
+ let insertResult: InsertArrayResult<MyItem> | undefined = $state();
135
+
136
+ $effect(() => {
137
+ if (insertResult) {
138
+ console.log(`${insertResult.successful} succeeded`);
139
+ console.log(`${insertResult.failed.length} failed`);
140
+ }
141
+ });
142
+
143
+ LTREE INTERFACE
144
+ ---------------
145
+ interface Ltree<T> {
146
+ // Properties
147
+ root: LTreeNode<T>;
148
+ pathSeparator: string;
149
+ flatTreeNodes: LTreeNode<T>[];
150
+ nodeCount: number;
151
+ maxLevel: number;
152
+ filteredNodeCount: number;
153
+
154
+ // Methods
155
+ insertArray(data: T[]): InsertArrayResult<T>;
156
+ getNodeByPath(path: string): LTreeNode<T> | null;
157
+ expandNodes(path: string): Ltree<T>;
158
+ collapseNodes(path: string): Ltree<T>;
159
+ expandAll(path?: string): void;
160
+ collapseAll(path?: string): void;
161
+ filterNodes(searchText: string): void;
162
+ searchNodes(searchText: string): LTreeNode<T>[];
163
+
164
+ // Callbacks
165
+ getNodeAllowedDropPositions(node: LTreeNode<T>): DropPosition[] | null;
166
+ }
167
+
168
+ CALLBACK TYPES
169
+ --------------
170
+ // Node click
171
+ onNodeClicked?: (node: LTreeNode<T>) => void;
172
+
173
+ // Drag events
174
+ onNodeDragStart?: (node: LTreeNode<T>, event: DragEvent) => void;
175
+ onNodeDragOver?: (node: LTreeNode<T>, event: DragEvent) => void;
176
+ onNodeDrop?: (
177
+ dropNode: LTreeNode<T> | null,
178
+ draggedNode: LTreeNode<T>,
179
+ position: DropPosition,
180
+ event: DragEvent,
181
+ operation: 'move' | 'copy'
182
+ ) => void;
183
+
184
+ // Validation
185
+ beforeDropCallback?: (
186
+ dropNode: LTreeNode<T> | null,
187
+ draggedNode: LTreeNode<T>,
188
+ position: DropPosition,
189
+ event: DragEvent,
190
+ operation: 'move' | 'copy'
191
+ ) => boolean | { position?: DropPosition; operation?: 'move' | 'copy' } | Promise<...>;
192
+
193
+ // Value getters
194
+ getDisplayValueCallback?: (node: LTreeNode<T>) => string;
195
+ getSearchValueCallback?: (node: LTreeNode<T>) => string;
196
+ getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null;
197
+
198
+ // Context menu
199
+ contextMenuCallback?: (
200
+ node: LTreeNode<T>,
201
+ closeMenu: () => void
202
+ ) => ContextMenuItem[];
203
+
204
+ // Sort
205
+ sortCallback?: (nodes: LTreeNode<T>[]) => LTreeNode<T>[];
206
+
207
+ GENERIC TREE REFERENCE
208
+ ----------------------
209
+ <script lang="ts">
210
+ import { Tree } from '@keenmate/svelte-treeview';
211
+
212
+ interface MyItem {
213
+ id: string;
214
+ path: string;
215
+ name: string;
216
+ }
217
+
218
+ let treeRef: Tree<MyItem>;
219
+ </script>
220
+
221
+ <Tree bind:this={treeRef} {data} ... />
222
+
223
+ <button onclick={() => {
224
+ // Fully typed methods
225
+ const node = treeRef.getNodeByPath('1.2');
226
+ if (node) {
227
+ console.log(node.data?.name); // string | undefined
228
+ }
229
+ }}>
230
+ Get Node
231
+ </button>
232
+
233
+ STATISTICS TYPE
234
+ ---------------
235
+ interface Statistics {
236
+ nodeCount: number;
237
+ maxLevel: number;
238
+ filteredNodeCount: number;
239
+ isIndexing: boolean;
240
+ pendingIndexCount: number;
241
+ }
242
+
243
+ const stats: Statistics = treeRef.statistics;
244
+
245
+ COMPLEX DATA TYPES
246
+ ------------------
247
+ // Union types
248
+ interface FileItem {
249
+ id: string;
250
+ path: string;
251
+ name: string;
252
+ type: 'file';
253
+ size: number;
254
+ extension: string;
255
+ }
256
+
257
+ interface FolderItem {
258
+ id: string;
259
+ path: string;
260
+ name: string;
261
+ type: 'folder';
262
+ childCount: number;
263
+ }
264
+
265
+ type TreeItem = FileItem | FolderItem;
266
+
267
+ // Type guards
268
+ function isFile(item: TreeItem): item is FileItem {
269
+ return item.type === 'file';
270
+ }
271
+
272
+ function isFolder(item: TreeItem): item is FolderItem {
273
+ return item.type === 'folder';
274
+ }
275
+
276
+ // Usage in callbacks
277
+ <Tree
278
+ data={items as TreeItem[]}
279
+ getDisplayValueCallback={(node) => {
280
+ if (!node.data) return '';
281
+ if (isFile(node.data)) {
282
+ return `${node.data.name} (${node.data.size}KB)`;
283
+ }
284
+ return `${node.data.name} (${node.data.childCount} items)`;
285
+ }}
286
+ />
287
+
288
+ STRICT NULL CHECKS
289
+ ------------------
290
+ Handle nullable data:
291
+
292
+ onNodeClicked={(node) => {
293
+ // node.data can be null
294
+ if (node.data) {
295
+ console.log(node.data.name);
296
+ }
297
+ }}
298
+
299
+ // Or with optional chaining
300
+ onNodeClicked={(node) => {
301
+ console.log(node.data?.name ?? 'Unknown');
302
+ }}
303
+
304
+ TYPE NARROWING IN TEMPLATES
305
+ ---------------------------
306
+ <Tree {data}>
307
+ {#snippet nodeTemplate(node)}
308
+ {#if node.data}
309
+ <span>{node.data.name}</span>
310
+ {#if isFolder(node.data)}
311
+ <span class="count">({node.data.childCount})</span>
312
+ {/if}
313
+ {/if}
314
+ {/snippet}
315
+ </Tree>
316
+
317
+ EXTENDING TYPES
318
+ ---------------
319
+ // Extend base node data
320
+ interface BaseItem {
321
+ id: string;
322
+ path: string;
323
+ name: string;
324
+ }
325
+
326
+ interface ExtendedItem extends BaseItem {
327
+ createdAt: Date;
328
+ updatedAt: Date;
329
+ metadata: Record<string, unknown>;
330
+ }
331
+
332
+ // Use extended type
333
+ let data: ExtendedItem[] = $state.raw([]);
334
+
335
+ <Tree
336
+ {data}
337
+ idMember="id"
338
+ pathMember="path"
339
+ getDisplayValueCallback={(node) => {
340
+ const item = node.data;
341
+ if (!item) return '';
342
+ return `${item.name} (${item.createdAt.toLocaleDateString()})`;
343
+ }}
344
+ />
345
+
346
+ BEST PRACTICES
347
+ --------------
348
+ ✅ Define interface for your data type
349
+ ✅ Use type imports (import type { ... })
350
+ ✅ Handle null/undefined in callbacks
351
+ ✅ Use type guards for union types
352
+ ✅ Type tree reference with generic
353
+
354
+ ❌ Don't use `any` type
355
+ ❌ Don't ignore null checks on node.data
356
+ ❌ Don't cast without type guards
357
+ ❌ Don't forget generic parameter on Tree ref
@@ -4,14 +4,14 @@
4
4
  import {getContext, onDestroy, type Snippet} from "svelte"
5
5
  import type {Ltree, DropPosition, DropOperation} from "../ltree/types.js"
6
6
  import type {RenderCoordinator} from "./RenderCoordinator.svelte.js"
7
- import type {NodeCallbacks, NodeConfig} from "./Tree.svelte"
7
+ import type {NodeCallbacks, NodeConfig} from "../core/TreeController.svelte.js"
8
8
  import { uiLogger } from "../logger.js"
9
9
 
10
10
  // Define component props interface
11
11
  // Callbacks and config come from context, drag state comes as props
12
12
  interface Props {
13
13
  node: LTreeNode<T>;
14
- children?: Snippet<[T]>; // Keep the general children slot for backward compatibility
14
+ children?: Snippet<[LTreeNode<T>]>; // Keep the general children slot for backward compatibility
15
15
 
16
16
  // Progressive rendering
17
17
  progressiveRender?: boolean;
@@ -54,17 +54,24 @@
54
54
  const callbacks = getContext<NodeCallbacks<T>>('NodeCallbacks');
55
55
  const config = getContext<NodeConfig>('NodeConfig');
56
56
 
57
- // Use $derived so values track mutations on the shared config proxy
58
- const shouldToggleOnNodeClick = $derived(config.shouldToggleOnNodeClick);
59
- const expandIconClass = $derived(config.expandIconClass);
60
- const collapseIconClass = $derived(config.collapseIconClass);
61
- const leafIconClass = $derived(config.leafIconClass);
62
- const selectedNodeClass = $derived(config.selectedNodeClass);
63
- const dragOverNodeClass = $derived(config.dragOverNodeClass);
64
- const dragDropMode = $derived(config.dragDropMode);
57
+ // Destructure config for convenience.
58
+ // Works reactively because nodeConfig uses $state() (not .raw()) and is mutated
59
+ // in-place via Object.assign, so the proxy reference stays the same.
60
+ const {
61
+ shouldToggleOnNodeClick,
62
+ expandIconClass,
63
+ collapseIconClass,
64
+ leafIconClass,
65
+ selectedNodeClass,
66
+ dragOverNodeClass,
67
+ allowCopy,
68
+ } = config;
69
+
70
+ // Read dropZoneMode and dropZoneStart through the proxy each time (not
71
+ // destructured) so they stay reactive in flat mode where nodes are NOT
72
+ // recreated on config change.
65
73
  const dropZoneMode = $derived(config.dropZoneMode);
66
74
  const dropZoneStart = $derived(config.dropZoneStart);
67
- const allowCopy = $derived(config.allowCopy);
68
75
 
69
76
  // Compute if THIS node is the one being hovered for drop
70
77
  const isHoveredForDrop = $derived(hoveredNodeForDropPath === node.path);
@@ -72,16 +79,11 @@
72
79
  const tree = getContext<Ltree<T>>("Ltree")
73
80
  const renderCoordinator = getContext<RenderCoordinator | null>("RenderCoordinator")
74
81
 
75
- // Per-node reactive signal — each NodeSignal has its own $state, so
76
- // bumping one signal only re-renders THIS Node, not all siblings.
77
- const nodeSignal = tree.getNodeSignal(String(node.id));
78
- const nodeRev = $derived(nodeSignal?.value ?? 0);
79
-
80
82
  // Drag over state
81
83
  let isDraggedOver = $state(false);
82
84
 
83
85
  // Track glow position for glow mode
84
- let glowPosition = $state<'above' | 'below' | 'child' | null>(null);
86
+ let glowPosition = $state<'before' | 'after' | 'child' | null>(null);
85
87
 
86
88
  // Get allowed drop positions for this node (empty/undefined = all allowed)
87
89
  // Uses tree.getNodeAllowedDropPositions which checks callback > member > node property
@@ -97,28 +99,32 @@
97
99
 
98
100
  // Calculate glow position based on mouse position in the node row
99
101
  // Respects allowedDropPositions - snaps to nearest allowed position
100
- function calculateGlowPosition(event: DragEvent, element: HTMLElement): 'above' | 'below' | 'child' | null {
102
+ // Uses dropZoneStart to determine the child zone threshold
103
+ function calculateGlowPosition(event: DragEvent, element: HTMLElement): 'before' | 'after' | 'child' | null {
101
104
  const rect = element.getBoundingClientRect();
102
105
  const x = event.clientX - rect.left;
103
106
  const y = event.clientY - rect.top;
104
107
  const width = rect.width;
105
108
  const height = rect.height;
106
109
 
107
- // Calculate the ideal position based on mouse position
108
- let idealPosition: DropPosition;
109
110
  // Convert dropZoneStart to pixels: number = percentage, string = as-is (px or %)
110
111
  const startPx = typeof dropZoneStart === 'number'
111
112
  ? (dropZoneStart / 100) * width
112
- : dropZoneStart.endsWith('px')
113
+ : typeof dropZoneStart === 'string' && dropZoneStart.endsWith('px')
113
114
  ? parseFloat(dropZoneStart)
114
- : (parseFloat(dropZoneStart) / 100) * width;
115
+ : typeof dropZoneStart === 'string'
116
+ ? (parseFloat(dropZoneStart) / 100) * width
117
+ : width / 2;
118
+ const childThreshold = isNaN(startPx) ? width / 2 : startPx;
115
119
 
116
- if (x > startPx) {
120
+ // Calculate the ideal position based on mouse position
121
+ let idealPosition: DropPosition;
122
+ if (x > childThreshold) {
117
123
  idealPosition = 'child';
118
124
  } else if (y < height / 2) {
119
- idealPosition = 'above';
125
+ idealPosition = 'before';
120
126
  } else {
121
- idealPosition = 'below';
127
+ idealPosition = 'after';
122
128
  }
123
129
 
124
130
  // If no restrictions, return the ideal position
@@ -138,17 +144,20 @@
138
144
  }
139
145
 
140
146
  // Multiple positions allowed but not the ideal one
141
- // For above/below: pick based on Y position
147
+ // For before/after: pick based on Y position
142
148
  // For child: pick based on what's available
143
- if (allowedPositions.includes('above') && allowedPositions.includes('below')) {
144
- // Both above and below allowed, pick based on Y
145
- return y < height / 2 ? 'above' : 'below';
149
+ if (allowedPositions.includes('before') && allowedPositions.includes('after')) {
150
+ // Both before and after allowed, pick based on Y
151
+ return y < height / 2 ? 'before' : 'after';
146
152
  }
147
153
 
148
154
  // Return the first allowed position
149
155
  return allowedPositions[0];
150
156
  }
151
157
 
158
+ // Resolve isCollapsible via tree's resolution method (callback > member > node property)
159
+ const isCollapsible = $derived(tree.getNodeIsCollapsible(node));
160
+
152
161
  // Convert reactive statements to derived values
153
162
  // In flat mode, children rendering is handled by Tree.svelte, so we skip these computations
154
163
  const childrenArray = $derived(!flatMode ? Object.values(node?.children || []) : [])
@@ -242,12 +251,11 @@
242
251
  });
243
252
 
244
253
  function toggleExpanded() {
245
- if (node.hasChildren) {
254
+ if (node.hasChildren && isCollapsible) {
246
255
  const newState = !node.isExpanded
247
256
  uiLogger.debug(`${newState ? 'Expanding' : 'Collapsing'} node: ${node.path}`)
248
257
  node.isExpanded = newState
249
- tree.bumpNodeRev(node) // re-render expand/collapse icon via {#key nodeRev}
250
- tree.refresh() // structural: recompute visibleFlatNodes
258
+ tree.refresh()
251
259
  }
252
260
  }
253
261
 
@@ -260,7 +268,6 @@
260
268
  }
261
269
  </script>
262
270
 
263
- {#key nodeRev}
264
271
  <!-- svelte-ignore a11y_no_static_element_interactions -->
265
272
  <div
266
273
  class="ltree-node"
@@ -271,7 +278,7 @@
271
278
  <div class="ltree-node-row">
272
279
  <!-- Toggle icon with its own click handler -->
273
280
  <!-- svelte-ignore a11y_click_events_have_key_events -->
274
- {#if hasChildren}
281
+ {#if hasChildren && isCollapsible}
275
282
  <span
276
283
  class="ltree-toggle-icon ltree-clickable {node.isExpanded
277
284
  ? collapseIconClass
@@ -290,12 +297,12 @@
290
297
  class="ltree-node-content {node.isSelected ? selectedNodeClass : ''} {isDraggedOver && dragOverNodeClass ? dragOverNodeClass : ''}"
291
298
  class:ltree-clickable={node.isSelectable}
292
299
  class:ltree-dragged={isDraggedNode}
293
- class:ltree-draggable={node?.isDraggable && dragDropMode !== 'none'}
294
- class:ltree-glow-above={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'above' && isPositionAllowed('above')}
295
- class:ltree-glow-below={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'below' && isPositionAllowed('below')}
300
+ class:ltree-draggable={node?.isDraggable}
301
+ class:ltree-glow-before={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'before' && isPositionAllowed('before')}
302
+ class:ltree-glow-after={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'after' && isPositionAllowed('after')}
296
303
  class:ltree-glow-child={dropZoneMode === 'glow' && isDragInProgress && isHoveredForDrop && glowPosition === 'child' && isPositionAllowed('child')}
297
304
  class:ltree-drop-copy={isDragInProgress && isHoveredForDrop && dropOperation === 'copy'}
298
- draggable={node?.isDraggable && dragDropMode !== 'none'}
305
+ draggable={node?.isDraggable}
299
306
  onclick={(e) => {
300
307
  e.stopPropagation();
301
308
  _onNodeClicked();
@@ -305,7 +312,7 @@
305
312
  callbacks.onNodeRightClicked(node, e);
306
313
  }}
307
314
  ondragstart={(e) => {
308
- if (node?.isDraggable && dragDropMode !== 'none' && e.dataTransfer) {
315
+ if (node?.isDraggable && e.dataTransfer) {
309
316
  e.dataTransfer.effectAllowed = allowCopy ? "copyMove" : "move";
310
317
  e.dataTransfer.setData(
311
318
  "application/svelte-treeview",
@@ -365,6 +372,7 @@
365
372
  {tree.getNodeDisplayValue(node)}
366
373
  {/if}
367
374
  </div>
375
+
368
376
  </div>
369
377
 
370
378
  <!-- In flat mode, children are rendered by Tree.svelte, not recursively here -->
@@ -391,4 +399,3 @@
391
399
  </div>
392
400
  {/if}
393
401
  </div>
394
- {/key}
@@ -5,7 +5,7 @@ import type { DropPosition, DropOperation } from "../ltree/types.js";
5
5
  declare function $$render<T>(): {
6
6
  props: {
7
7
  node: LTreeNode<T>;
8
- children?: Snippet<[T]>;
8
+ children?: Snippet<[LTreeNode<T>]>;
9
9
  progressiveRender?: boolean;
10
10
  renderBatchSize?: number;
11
11
  isDraggedNode?: boolean;