@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.
- package/README.md +106 -117
- package/ai/INDEX.txt +310 -0
- package/ai/advanced-patterns.txt +506 -0
- package/ai/basic-setup.txt +336 -0
- package/ai/context-menu.txt +349 -0
- package/ai/data-handling.txt +390 -0
- package/ai/drag-drop.txt +397 -0
- package/ai/events-callbacks.txt +382 -0
- package/ai/import-patterns.txt +271 -0
- package/ai/performance.txt +349 -0
- package/ai/search-features.txt +359 -0
- package/ai/styling-theming.txt +354 -0
- package/ai/tree-editing.txt +423 -0
- package/ai/typescript-types.txt +357 -0
- package/dist/components/Node.svelte +47 -40
- package/dist/components/Node.svelte.d.ts +1 -1
- package/dist/components/Tree.svelte +384 -1479
- package/dist/components/Tree.svelte.d.ts +30 -28
- package/dist/components/TreeProvider.svelte +28 -0
- package/dist/components/TreeProvider.svelte.d.ts +28 -0
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/core/TreeController.svelte.d.ts +353 -0
- package/dist/core/TreeController.svelte.js +1503 -0
- package/dist/core/createTreeController.d.ts +9 -0
- package/dist/core/createTreeController.js +11 -0
- package/dist/global-api.d.ts +1 -1
- package/dist/global-api.js +5 -5
- package/dist/index.d.ts +10 -6
- package/dist/index.js +7 -3
- package/dist/logger.d.ts +7 -6
- package/dist/logger.js +0 -2
- package/dist/ltree/indexer.js +2 -4
- package/dist/ltree/ltree-node.svelte.d.ts +2 -1
- package/dist/ltree/ltree-node.svelte.js +1 -0
- package/dist/ltree/ltree.svelte.d.ts +1 -1
- package/dist/ltree/ltree.svelte.js +168 -175
- package/dist/ltree/types.d.ts +12 -8
- package/dist/perf-logger.d.ts +2 -1
- package/dist/perf-logger.js +0 -2
- package/dist/styles/main.scss +78 -78
- package/dist/styles.css +41 -41
- package/dist/styles.css.map +1 -1
- package/dist/vendor/loglevel/index.d.ts +55 -2
- package/dist/vendor/loglevel/prefix.d.ts +23 -2
- package/package.json +96 -95
- package/dist/ltree/ltree-demo.d.ts +0 -2
- package/dist/ltree/ltree-demo.js +0 -90
- package/dist/vendor/loglevel/loglevel-esm.d.ts +0 -2
- package/dist/vendor/loglevel/loglevel-plugin-prefix-esm.d.ts +0 -7
- package/dist/vendor/loglevel/loglevel-plugin-prefix.d.ts +0 -2
package/ai/drag-drop.txt
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
DRAG AND DROP
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
CRITICAL: Drag and drop is DISABLED by default
|
|
5
|
+
- Set dragDropMode="both" to enable
|
|
6
|
+
- Three drop positions: before, after, child
|
|
7
|
+
- Same-tree and cross-tree moves
|
|
8
|
+
- Touch support for mobile
|
|
9
|
+
- Copy operations with Ctrl+drag
|
|
10
|
+
- Async validation with beforeDropCallback
|
|
11
|
+
- Per-node drop position restrictions
|
|
12
|
+
|
|
13
|
+
BASIC DRAG AND DROP
|
|
14
|
+
-------------------
|
|
15
|
+
<script>
|
|
16
|
+
function handleDrop(dropNode, draggedNode, position, event, operation) {
|
|
17
|
+
console.log(`Dropped ${draggedNode.data.name}`);
|
|
18
|
+
console.log(`Position: ${position}`); // 'before', 'after', 'child'
|
|
19
|
+
console.log(`Target: ${dropNode?.data.name}`);
|
|
20
|
+
console.log(`Operation: ${operation}`); // 'move' or 'copy'
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<Tree
|
|
25
|
+
{data}
|
|
26
|
+
idMember="id"
|
|
27
|
+
pathMember="path"
|
|
28
|
+
dragDropMode="both"
|
|
29
|
+
dragOverNodeClass="ltree-dragover-glow"
|
|
30
|
+
onNodeDrop={handleDrop}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
DROP POSITIONS
|
|
34
|
+
--------------
|
|
35
|
+
Three positions available when dropping:
|
|
36
|
+
|
|
37
|
+
'before' - Insert as sibling BEFORE the target node
|
|
38
|
+
'after' - Insert as sibling AFTER the target node
|
|
39
|
+
'child' - Insert as CHILD of the target node
|
|
40
|
+
|
|
41
|
+
The position is calculated based on mouse Y position:
|
|
42
|
+
- Top 25% of node → 'before'
|
|
43
|
+
- Middle 50% of node → 'child'
|
|
44
|
+
- Bottom 25% of node → 'after'
|
|
45
|
+
|
|
46
|
+
DROP ZONE MODES
|
|
47
|
+
---------------
|
|
48
|
+
Two visual modes for drop zones:
|
|
49
|
+
|
|
50
|
+
// Glow mode (default) - border/shadow indicators
|
|
51
|
+
<Tree dropZoneMode="glow" dragOverNodeClass="ltree-dragover-glow" />
|
|
52
|
+
|
|
53
|
+
// Floating mode - popup buttons
|
|
54
|
+
<Tree dropZoneMode="floating" />
|
|
55
|
+
|
|
56
|
+
Glow mode classes:
|
|
57
|
+
- ltree-glow-before - top border glow (before position)
|
|
58
|
+
- ltree-glow-after - bottom border glow (after position)
|
|
59
|
+
- ltree-glow-child - full border glow (child position)
|
|
60
|
+
|
|
61
|
+
DROP ZONE LAYOUTS (Floating Mode)
|
|
62
|
+
---------------------------------
|
|
63
|
+
<Tree
|
|
64
|
+
dropZoneMode="floating"
|
|
65
|
+
dropZoneLayout="around" <!-- Default: before on top, after/child on bottom -->
|
|
66
|
+
dropZoneStart={33} <!-- Start position (percentage) -->
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
Layout options:
|
|
70
|
+
- 'around' - Before on top, After/Child on bottom
|
|
71
|
+
- 'above' - All 3 zones in row above node
|
|
72
|
+
- 'below' - All 3 zones in row below node
|
|
73
|
+
- 'wave' - Stacked vertically
|
|
74
|
+
- 'wave2' - Diagonal wave pattern
|
|
75
|
+
|
|
76
|
+
ZONE AUTO-EXPAND
|
|
77
|
+
When some drop positions are restricted (via allowedDropPositions),
|
|
78
|
+
hidden zones auto-expand using CSS :not(:has()) rules:
|
|
79
|
+
- 3 zones visible: normal thirds
|
|
80
|
+
- 2 zones visible: each gets half the space
|
|
81
|
+
- 1 zone visible: full width
|
|
82
|
+
Works in 'around', 'above', and 'below' layouts.
|
|
83
|
+
|
|
84
|
+
DROP POSITION RESTRICTIONS
|
|
85
|
+
--------------------------
|
|
86
|
+
CRITICAL: Restrict which positions are allowed per node
|
|
87
|
+
|
|
88
|
+
Use case examples:
|
|
89
|
+
- Trash folder: only 'child' (drop INTO, not around)
|
|
90
|
+
- Files: only 'before'/'after' (can't drop INTO a file)
|
|
91
|
+
- Folders: all positions (default)
|
|
92
|
+
|
|
93
|
+
Method 1: Callback (dynamic logic)
|
|
94
|
+
|
|
95
|
+
<Tree
|
|
96
|
+
getAllowedDropPositionsCallback={(node) => {
|
|
97
|
+
if (node.data.type === 'file') return ['before', 'after'];
|
|
98
|
+
if (node.data.type === 'trash') return ['child'];
|
|
99
|
+
return undefined; // all positions allowed
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
Method 2: Member property (server data)
|
|
104
|
+
|
|
105
|
+
const data = [
|
|
106
|
+
{ path: '1', name: 'Trash', allowedDropPositions: ['child'] },
|
|
107
|
+
{ path: '2', name: 'File.txt', allowedDropPositions: ['before', 'after'] },
|
|
108
|
+
{ path: '3', name: 'Folder' } // undefined = all positions
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
<Tree allowedDropPositionsMember="allowedDropPositions" />
|
|
112
|
+
|
|
113
|
+
Behavior when restricted:
|
|
114
|
+
- Glow mode: snaps to nearest allowed position
|
|
115
|
+
- Floating mode: only shows buttons for allowed positions
|
|
116
|
+
|
|
117
|
+
DRAG DROP MODE
|
|
118
|
+
--------------
|
|
119
|
+
Control which drag operations are allowed:
|
|
120
|
+
|
|
121
|
+
<Tree dragDropMode="none" /> <!-- Default: disabled -->
|
|
122
|
+
|
|
123
|
+
Options:
|
|
124
|
+
- 'none' - Drag and drop disabled (DEFAULT)
|
|
125
|
+
- 'self' - Only within same tree
|
|
126
|
+
- 'cross' - Only between different trees
|
|
127
|
+
- 'both' - Both self and cross-tree
|
|
128
|
+
|
|
129
|
+
SAME-TREE MOVE (Auto-handled)
|
|
130
|
+
-----------------------------
|
|
131
|
+
When dropping within the same tree, the move is auto-handled:
|
|
132
|
+
|
|
133
|
+
<Tree
|
|
134
|
+
{data}
|
|
135
|
+
orderMember="sortOrder"
|
|
136
|
+
onNodeDrop={(dropNode, draggedNode, position, event, operation) => {
|
|
137
|
+
// Move already happened! This is for notification only.
|
|
138
|
+
console.log(`Moved ${draggedNode.data.name} ${position} ${dropNode.data.name}`);
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
IMPORTANT: Set orderMember for proper sibling ordering
|
|
143
|
+
|
|
144
|
+
CROSS-TREE DRAG
|
|
145
|
+
---------------
|
|
146
|
+
<script>
|
|
147
|
+
let leftData = $state.raw([...]);
|
|
148
|
+
let rightData = $state.raw([...]);
|
|
149
|
+
|
|
150
|
+
function handleLeftDrop(dropNode, draggedNode, position, event, operation) {
|
|
151
|
+
// Handle item coming from right tree
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleRightDrop(dropNode, draggedNode, position, event, operation) {
|
|
155
|
+
// Handle item coming from left tree
|
|
156
|
+
}
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<Tree data={leftData} treeId="left" onNodeDrop={handleLeftDrop} />
|
|
160
|
+
<Tree data={rightData} treeId="right" onNodeDrop={handleRightDrop} />
|
|
161
|
+
|
|
162
|
+
ASYNC DROP VALIDATION
|
|
163
|
+
---------------------
|
|
164
|
+
Use beforeDropCallback for validation or modification:
|
|
165
|
+
|
|
166
|
+
<script>
|
|
167
|
+
async function beforeDrop(dropNode, draggedNode, position, event, operation) {
|
|
168
|
+
// Cancel the drop
|
|
169
|
+
if (draggedNode.data.locked) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Show confirmation dialog
|
|
174
|
+
if (position === 'child' && !dropNode.data.isFolder) {
|
|
175
|
+
const confirmed = await showConfirmDialog('Convert to folder?');
|
|
176
|
+
if (!confirmed) return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Modify position
|
|
180
|
+
if (position === 'child' && dropNode.data.type === 'file') {
|
|
181
|
+
return { position: 'after' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Proceed normally
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<Tree beforeDropCallback={beforeDrop} onNodeDrop={handleDrop} />
|
|
190
|
+
|
|
191
|
+
Return values:
|
|
192
|
+
- false → Cancel the drop
|
|
193
|
+
- true / undefined → Proceed normally
|
|
194
|
+
- { position: 'before' } → Override position
|
|
195
|
+
- { operation: 'copy' } → Override operation
|
|
196
|
+
- Promise<...> → Async validation
|
|
197
|
+
|
|
198
|
+
COPY OPERATION (Ctrl+Drag)
|
|
199
|
+
--------------------------
|
|
200
|
+
Enable copying with Ctrl+drag:
|
|
201
|
+
|
|
202
|
+
<Tree
|
|
203
|
+
allowCopy={true}
|
|
204
|
+
autoHandleCopy={true} <!-- Default: auto-creates copy -->
|
|
205
|
+
onNodeDrop={(dropNode, draggedNode, position, event, operation) => {
|
|
206
|
+
if (operation === 'copy') {
|
|
207
|
+
console.log('Copied node');
|
|
208
|
+
} else {
|
|
209
|
+
console.log('Moved node');
|
|
210
|
+
}
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
|
|
214
|
+
autoHandleCopy options:
|
|
215
|
+
- true: Tree creates copy with generated ID (good for batch/offline)
|
|
216
|
+
- false: Your callback handles copy (good for DB/API integration)
|
|
217
|
+
|
|
218
|
+
DRAGGABLE CONFIGURATION
|
|
219
|
+
-----------------------
|
|
220
|
+
Control which nodes can be dragged:
|
|
221
|
+
|
|
222
|
+
// All nodes draggable (default)
|
|
223
|
+
<Tree />
|
|
224
|
+
|
|
225
|
+
// Using member property
|
|
226
|
+
const data = [
|
|
227
|
+
{ path: '1', name: 'Draggable', isDraggable: true },
|
|
228
|
+
{ path: '2', name: 'Fixed', isDraggable: false }
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
<Tree isDraggableMember="isDraggable" />
|
|
232
|
+
|
|
233
|
+
DROP ALLOWED CONFIGURATION
|
|
234
|
+
--------------------------
|
|
235
|
+
Control which nodes accept drops:
|
|
236
|
+
|
|
237
|
+
// Using member property
|
|
238
|
+
const data = [
|
|
239
|
+
{ path: '1', name: 'Can receive drops', isDropAllowed: true },
|
|
240
|
+
{ path: '2', name: 'Cannot receive drops', isDropAllowed: false }
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
<Tree isDropAllowedMember="isDropAllowed" />
|
|
244
|
+
|
|
245
|
+
ORDER MEMBER (SIBLING ORDERING)
|
|
246
|
+
-------------------------------
|
|
247
|
+
CRITICAL: For proper before/after positioning
|
|
248
|
+
|
|
249
|
+
const data = [
|
|
250
|
+
{ path: '1.1', name: 'First', sortOrder: 10 },
|
|
251
|
+
{ path: '1.2', name: 'Second', sortOrder: 20 },
|
|
252
|
+
{ path: '1.3', name: 'Third', sortOrder: 30 }
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
<Tree orderMember="sortOrder" />
|
|
256
|
+
|
|
257
|
+
When moving nodes:
|
|
258
|
+
- Tree auto-calculates new order values
|
|
259
|
+
- Inserts between adjacent values
|
|
260
|
+
- Uses midpoint: (10 + 20) / 2 = 15
|
|
261
|
+
|
|
262
|
+
ROOT DROP ZONE
|
|
263
|
+
--------------
|
|
264
|
+
Drop zone at bottom for adding root-level items:
|
|
265
|
+
|
|
266
|
+
When dragging, a root drop zone appears at tree bottom
|
|
267
|
+
- Dropping there adds item as root node
|
|
268
|
+
- dropNode is null in callback
|
|
269
|
+
|
|
270
|
+
<Tree
|
|
271
|
+
onNodeDrop={(dropNode, draggedNode, position, event) => {
|
|
272
|
+
if (dropNode === null) {
|
|
273
|
+
console.log('Dropped at root level');
|
|
274
|
+
}
|
|
275
|
+
}}
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
EMPTY TREE DROP
|
|
279
|
+
---------------
|
|
280
|
+
Custom placeholder for empty trees:
|
|
281
|
+
|
|
282
|
+
<Tree {data}>
|
|
283
|
+
{#snippet dropPlaceholder()}
|
|
284
|
+
<div class="drop-here">
|
|
285
|
+
Drop items here to start
|
|
286
|
+
</div>
|
|
287
|
+
{/snippet}
|
|
288
|
+
</Tree>
|
|
289
|
+
|
|
290
|
+
When dropping to empty tree:
|
|
291
|
+
- dropNode is null
|
|
292
|
+
- position is 'child'
|
|
293
|
+
|
|
294
|
+
TOUCH DRAG SUPPORT
|
|
295
|
+
------------------
|
|
296
|
+
Enabled by default on touch devices:
|
|
297
|
+
|
|
298
|
+
- Long-press (300ms) initiates drag
|
|
299
|
+
- Distinguishes from tap and scroll
|
|
300
|
+
- Ghost element follows finger
|
|
301
|
+
- Haptic feedback on supported devices
|
|
302
|
+
- Move >10px before long-press cancels (allows scrolling)
|
|
303
|
+
|
|
304
|
+
Same callback as desktop:
|
|
305
|
+
<Tree onNodeDrop={handleDrop} />
|
|
306
|
+
|
|
307
|
+
DRAG EVENTS
|
|
308
|
+
-----------
|
|
309
|
+
<Tree
|
|
310
|
+
onNodeDragStart={(node, event) => {
|
|
311
|
+
console.log('Started dragging:', node.data.name);
|
|
312
|
+
}}
|
|
313
|
+
onNodeDragOver={(node, event) => {
|
|
314
|
+
console.log('Dragging over:', node.data.name);
|
|
315
|
+
}}
|
|
316
|
+
onNodeDrop={(dropNode, draggedNode, position, event, operation) => {
|
|
317
|
+
console.log('Dropped');
|
|
318
|
+
}}
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
VISUAL FEEDBACK CLASSES
|
|
322
|
+
-----------------------
|
|
323
|
+
Drag-over styling:
|
|
324
|
+
|
|
325
|
+
<Tree dragOverNodeClass="ltree-dragover-glow" />
|
|
326
|
+
|
|
327
|
+
Built-in classes:
|
|
328
|
+
- ltree-dragover-highlight - Dashed border
|
|
329
|
+
- ltree-dragover-glow - Shadow glow effect
|
|
330
|
+
|
|
331
|
+
Copy operation class:
|
|
332
|
+
- ltree-drop-copy - Applied during Ctrl+drag
|
|
333
|
+
|
|
334
|
+
PROGRAMMATIC MOVE
|
|
335
|
+
-----------------
|
|
336
|
+
Move nodes programmatically:
|
|
337
|
+
|
|
338
|
+
<script>
|
|
339
|
+
let treeRef;
|
|
340
|
+
|
|
341
|
+
function moveUp(nodePath) {
|
|
342
|
+
const siblings = treeRef.getSiblings(nodePath);
|
|
343
|
+
const index = siblings.findIndex(s => s.path === nodePath);
|
|
344
|
+
if (index > 0) {
|
|
345
|
+
treeRef.moveNode(nodePath, siblings[index - 1].path, 'before');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
</script>
|
|
349
|
+
|
|
350
|
+
<Tree bind:this={treeRef} />
|
|
351
|
+
|
|
352
|
+
COMMON PATTERNS
|
|
353
|
+
---------------
|
|
354
|
+
File manager:
|
|
355
|
+
<Tree
|
|
356
|
+
dragDropMode="self"
|
|
357
|
+
orderMember="sortOrder"
|
|
358
|
+
getAllowedDropPositionsCallback={(node) =>
|
|
359
|
+
node.data.type === 'file' ? ['before', 'after'] : undefined
|
|
360
|
+
}
|
|
361
|
+
onNodeDrop={handleDrop}
|
|
362
|
+
/>
|
|
363
|
+
|
|
364
|
+
Two-tree transfer:
|
|
365
|
+
<Tree data={available} treeId="available" dragDropMode="cross" />
|
|
366
|
+
<Tree data={selected} treeId="selected" dragDropMode="cross" />
|
|
367
|
+
|
|
368
|
+
With confirmation:
|
|
369
|
+
<Tree
|
|
370
|
+
beforeDropCallback={async (drop, drag, pos) => {
|
|
371
|
+
return await confirm(`Move ${drag.data.name}?`);
|
|
372
|
+
}}
|
|
373
|
+
/>
|
|
374
|
+
|
|
375
|
+
DEBUGGING
|
|
376
|
+
---------
|
|
377
|
+
Enable drag logging:
|
|
378
|
+
|
|
379
|
+
import { setCategoryLevel } from '@keenmate/svelte-treeview';
|
|
380
|
+
setCategoryLevel('LTREE:DRAG', 'debug');
|
|
381
|
+
|
|
382
|
+
Debug panel:
|
|
383
|
+
<Tree shouldDisplayDebugInformation={true} />
|
|
384
|
+
// Shows currently dragged node
|
|
385
|
+
|
|
386
|
+
BEST PRACTICES
|
|
387
|
+
--------------
|
|
388
|
+
✅ Set orderMember for sibling ordering
|
|
389
|
+
✅ Use beforeDropCallback for validation
|
|
390
|
+
✅ Handle cross-tree drops explicitly
|
|
391
|
+
✅ Use getAllowedDropPositionsCallback for restrictions
|
|
392
|
+
✅ Provide visual feedback (dragOverNodeClass)
|
|
393
|
+
|
|
394
|
+
❌ Don't forget orderMember (causes position issues)
|
|
395
|
+
❌ Don't modify data in beforeDropCallback (use onNodeDrop)
|
|
396
|
+
❌ Don't assume same-tree vs cross-tree without checking
|
|
397
|
+
❌ Don't block UI in sync beforeDropCallback
|