@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
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
TREE EDITING
|
|
2
|
+
============
|
|
3
|
+
|
|
4
|
+
CRITICAL: Built-in methods for programmatic tree editing
|
|
5
|
+
- addNode: Add new nodes
|
|
6
|
+
- moveNode: Move nodes with subtrees
|
|
7
|
+
- removeNode: Delete nodes
|
|
8
|
+
- Automatic path management
|
|
9
|
+
|
|
10
|
+
ADD NODE
|
|
11
|
+
--------
|
|
12
|
+
<script>
|
|
13
|
+
let treeRef;
|
|
14
|
+
|
|
15
|
+
function addChild(parentPath) {
|
|
16
|
+
const result = treeRef.addNode(
|
|
17
|
+
parentPath, // Parent path (empty string = root)
|
|
18
|
+
{
|
|
19
|
+
id: Date.now().toString(),
|
|
20
|
+
path: '', // Will be auto-calculated
|
|
21
|
+
name: 'New Item',
|
|
22
|
+
sortOrder: 100
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (result.success) {
|
|
27
|
+
console.log('Added node:', result.node);
|
|
28
|
+
console.log('New path:', result.node.path);
|
|
29
|
+
} else {
|
|
30
|
+
console.log('Failed:', result.error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<Tree bind:this={treeRef} {data} ... />
|
|
36
|
+
<button onclick={() => addChild('1.2')}>Add to 1.2</button>
|
|
37
|
+
<button onclick={() => addChild('')}>Add to Root</button>
|
|
38
|
+
|
|
39
|
+
addNode signature:
|
|
40
|
+
addNode(parentPath: string, data: T, pathSegment?: string)
|
|
41
|
+
|
|
42
|
+
- parentPath: Where to add (empty = root)
|
|
43
|
+
- data: Your data object (path auto-calculated)
|
|
44
|
+
- pathSegment: Optional custom segment (otherwise auto-generated)
|
|
45
|
+
|
|
46
|
+
Returns: { success: boolean, node?: LTreeNode<T>, error?: string }
|
|
47
|
+
|
|
48
|
+
MOVE NODE
|
|
49
|
+
---------
|
|
50
|
+
<script>
|
|
51
|
+
function moveNode(sourcePath, targetPath, position) {
|
|
52
|
+
const result = treeRef.moveNode(sourcePath, targetPath, position);
|
|
53
|
+
|
|
54
|
+
if (result.success) {
|
|
55
|
+
console.log('Moved to:', result.newPath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<button onclick={() => moveNode('1.2', '2.1', 'before')}>
|
|
61
|
+
Move 1.2 before 2.1
|
|
62
|
+
</button>
|
|
63
|
+
|
|
64
|
+
<button onclick={() => moveNode('1.2', '2', 'child')}>
|
|
65
|
+
Move 1.2 as child of 2
|
|
66
|
+
</button>
|
|
67
|
+
|
|
68
|
+
moveNode signature:
|
|
69
|
+
moveNode(sourcePath: string, targetPath: string, position: 'before' | 'after' | 'child')
|
|
70
|
+
|
|
71
|
+
- sourcePath: Node to move (includes all descendants)
|
|
72
|
+
- targetPath: Target node
|
|
73
|
+
- position: Where relative to target
|
|
74
|
+
|
|
75
|
+
Positions:
|
|
76
|
+
- 'before': Insert as sibling before target
|
|
77
|
+
- 'after': Insert as sibling after target
|
|
78
|
+
- 'child': Insert as child of target
|
|
79
|
+
|
|
80
|
+
REMOVE NODE
|
|
81
|
+
-----------
|
|
82
|
+
<script>
|
|
83
|
+
function deleteNode(path) {
|
|
84
|
+
const result = treeRef.removeNode(path, true);
|
|
85
|
+
|
|
86
|
+
if (result.success) {
|
|
87
|
+
console.log('Removed:', result.removedCount, 'nodes');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<button onclick={() => deleteNode('1.2')}>Delete 1.2</button>
|
|
93
|
+
|
|
94
|
+
removeNode signature:
|
|
95
|
+
removeNode(path: string, includeDescendants?: boolean)
|
|
96
|
+
|
|
97
|
+
- path: Node to remove
|
|
98
|
+
- includeDescendants: Remove children too (default: true)
|
|
99
|
+
|
|
100
|
+
Returns: { success: boolean, removedCount: number, error?: string }
|
|
101
|
+
|
|
102
|
+
ORDER MEMBER
|
|
103
|
+
------------
|
|
104
|
+
CRITICAL: For proper sibling ordering, set orderMember
|
|
105
|
+
|
|
106
|
+
<Tree orderMember="sortOrder" ... />
|
|
107
|
+
|
|
108
|
+
const data = [
|
|
109
|
+
{ path: '1.1', name: 'First', sortOrder: 10 },
|
|
110
|
+
{ path: '1.2', name: 'Second', sortOrder: 20 },
|
|
111
|
+
{ path: '1.3', name: 'Third', sortOrder: 30 }
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
When moving nodes with orderMember:
|
|
115
|
+
- Tree auto-calculates new order values
|
|
116
|
+
- Uses midpoint between adjacent values
|
|
117
|
+
- Example: Moving between 10 and 20 → sortOrder = 15
|
|
118
|
+
|
|
119
|
+
HELPER METHODS
|
|
120
|
+
--------------
|
|
121
|
+
Get node by path:
|
|
122
|
+
const node = treeRef.getNodeByPath('1.2.3');
|
|
123
|
+
console.log(node?.data.name);
|
|
124
|
+
|
|
125
|
+
Get children of a node:
|
|
126
|
+
const children = treeRef.getChildren('1.2');
|
|
127
|
+
console.log('Children count:', children.length);
|
|
128
|
+
|
|
129
|
+
Get siblings (including self):
|
|
130
|
+
const siblings = treeRef.getSiblings('1.2.3');
|
|
131
|
+
const index = siblings.findIndex(s => s.path === '1.2.3');
|
|
132
|
+
|
|
133
|
+
Refresh siblings (re-sort):
|
|
134
|
+
treeRef.refreshSiblings('1.2'); // Parent path
|
|
135
|
+
|
|
136
|
+
Refresh specific node:
|
|
137
|
+
treeRef.refreshNode('1.2.3');
|
|
138
|
+
|
|
139
|
+
MOVE UP/DOWN
|
|
140
|
+
------------
|
|
141
|
+
<script>
|
|
142
|
+
function moveUp(nodePath) {
|
|
143
|
+
const siblings = treeRef.getSiblings(nodePath);
|
|
144
|
+
const index = siblings.findIndex(s => s.path === nodePath);
|
|
145
|
+
|
|
146
|
+
if (index > 0) {
|
|
147
|
+
treeRef.moveNode(nodePath, siblings[index - 1].path, 'before');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function moveDown(nodePath) {
|
|
152
|
+
const siblings = treeRef.getSiblings(nodePath);
|
|
153
|
+
const index = siblings.findIndex(s => s.path === nodePath);
|
|
154
|
+
|
|
155
|
+
if (index < siblings.length - 1) {
|
|
156
|
+
treeRef.moveNode(nodePath, siblings[index + 1].path, 'after');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
</script>
|
|
160
|
+
|
|
161
|
+
UPDATE NODE
|
|
162
|
+
-----------
|
|
163
|
+
Update a node's data properties:
|
|
164
|
+
|
|
165
|
+
<script>
|
|
166
|
+
function updateNodeData(path) {
|
|
167
|
+
treeRef.updateNode(path, {
|
|
168
|
+
name: 'Updated Name',
|
|
169
|
+
status: 'modified'
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
updateNode signature:
|
|
175
|
+
updateNode(path: string, data: Partial<T>)
|
|
176
|
+
|
|
177
|
+
- path: Node to update
|
|
178
|
+
- data: Partial data to merge into existing data
|
|
179
|
+
|
|
180
|
+
GET/SET EXPANDED STATE
|
|
181
|
+
----------------------
|
|
182
|
+
Save and restore expanded state:
|
|
183
|
+
|
|
184
|
+
<script>
|
|
185
|
+
// Save expanded state
|
|
186
|
+
const expandedPaths = treeRef.getExpandedPaths();
|
|
187
|
+
localStorage.setItem('expandedPaths', JSON.stringify(expandedPaths));
|
|
188
|
+
|
|
189
|
+
// Restore expanded state
|
|
190
|
+
const saved = JSON.parse(localStorage.getItem('expandedPaths') || '[]');
|
|
191
|
+
treeRef.setExpandedPaths(saved);
|
|
192
|
+
</script>
|
|
193
|
+
|
|
194
|
+
GET ALL DATA
|
|
195
|
+
------------
|
|
196
|
+
Get all tree data as flat array (with any modifications):
|
|
197
|
+
|
|
198
|
+
const allItems = treeRef.getAllData();
|
|
199
|
+
|
|
200
|
+
APPLY CHANGES
|
|
201
|
+
-------------
|
|
202
|
+
Force-apply pending changes and refresh the tree:
|
|
203
|
+
|
|
204
|
+
treeRef.applyChanges();
|
|
205
|
+
|
|
206
|
+
COPY NODE
|
|
207
|
+
---------
|
|
208
|
+
<script>
|
|
209
|
+
function copyNode(sourcePath, targetPath, position) {
|
|
210
|
+
const result = treeRef.copyNodeWithDescendants(
|
|
211
|
+
sourcePath,
|
|
212
|
+
targetPath,
|
|
213
|
+
position
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (result.success) {
|
|
217
|
+
console.log('Copied to:', result.newPath);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
</script>
|
|
221
|
+
|
|
222
|
+
copyNodeWithDescendants signature:
|
|
223
|
+
copyNodeWithDescendants(
|
|
224
|
+
sourcePath: string,
|
|
225
|
+
targetParentPath?: string,
|
|
226
|
+
siblingPath?: string,
|
|
227
|
+
position?: 'before' | 'after'
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
NEW ITEM WORKFLOW
|
|
231
|
+
-----------------
|
|
232
|
+
Complete workflow for adding items:
|
|
233
|
+
|
|
234
|
+
<script>
|
|
235
|
+
let treeRef;
|
|
236
|
+
let selectedNode = $state(null);
|
|
237
|
+
|
|
238
|
+
function addSibling() {
|
|
239
|
+
if (!selectedNode) return;
|
|
240
|
+
|
|
241
|
+
const parentPath = selectedNode.parentPath || '';
|
|
242
|
+
const result = treeRef.addNode(parentPath, {
|
|
243
|
+
id: crypto.randomUUID(),
|
|
244
|
+
path: '',
|
|
245
|
+
name: 'New Sibling',
|
|
246
|
+
sortOrder: Date.now()
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (result.success) {
|
|
250
|
+
// Select the new node
|
|
251
|
+
selectedNode = result.node;
|
|
252
|
+
// Optionally scroll to it
|
|
253
|
+
treeRef.scrollToPath(result.node.path);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function addChild() {
|
|
258
|
+
if (!selectedNode) return;
|
|
259
|
+
|
|
260
|
+
const result = treeRef.addNode(selectedNode.path, {
|
|
261
|
+
id: crypto.randomUUID(),
|
|
262
|
+
path: '',
|
|
263
|
+
name: 'New Child',
|
|
264
|
+
sortOrder: Date.now()
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (result.success) {
|
|
268
|
+
// Expand parent to show new child
|
|
269
|
+
treeRef.expandNodes(selectedNode.path);
|
|
270
|
+
// Select the new node
|
|
271
|
+
selectedNode = result.node;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function deleteSelected() {
|
|
276
|
+
if (!selectedNode) return;
|
|
277
|
+
|
|
278
|
+
if (confirm(`Delete ${selectedNode.data.name}?`)) {
|
|
279
|
+
const parentPath = selectedNode.parentPath;
|
|
280
|
+
treeRef.removeNode(selectedNode.path);
|
|
281
|
+
selectedNode = null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
</script>
|
|
285
|
+
|
|
286
|
+
<Tree bind:this={treeRef} bind:selectedNode />
|
|
287
|
+
|
|
288
|
+
<button onclick={addSibling} disabled={!selectedNode}>Add Sibling</button>
|
|
289
|
+
<button onclick={addChild} disabled={!selectedNode}>Add Child</button>
|
|
290
|
+
<button onclick={deleteSelected} disabled={!selectedNode}>Delete</button>
|
|
291
|
+
|
|
292
|
+
INLINE EDITING
|
|
293
|
+
--------------
|
|
294
|
+
Edit node name inline:
|
|
295
|
+
|
|
296
|
+
<script>
|
|
297
|
+
let editingPath = $state(null);
|
|
298
|
+
let editValue = $state('');
|
|
299
|
+
|
|
300
|
+
function startEdit(node) {
|
|
301
|
+
editingPath = node.path;
|
|
302
|
+
editValue = node.data.name;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function saveEdit() {
|
|
306
|
+
if (editingPath) {
|
|
307
|
+
// Update your data
|
|
308
|
+
data = data.map(item =>
|
|
309
|
+
item.path === editingPath
|
|
310
|
+
? { ...item, name: editValue }
|
|
311
|
+
: item
|
|
312
|
+
);
|
|
313
|
+
editingPath = null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
</script>
|
|
317
|
+
|
|
318
|
+
<Tree {data}>
|
|
319
|
+
{#snippet nodeTemplate(node)}
|
|
320
|
+
{#if editingPath === node.path}
|
|
321
|
+
<input
|
|
322
|
+
bind:value={editValue}
|
|
323
|
+
onblur={saveEdit}
|
|
324
|
+
onkeydown={(e) => e.key === 'Enter' && saveEdit()}
|
|
325
|
+
autofocus
|
|
326
|
+
/>
|
|
327
|
+
{:else}
|
|
328
|
+
<span ondblclick={() => startEdit(node)}>
|
|
329
|
+
{node.data.name}
|
|
330
|
+
</span>
|
|
331
|
+
{/if}
|
|
332
|
+
{/snippet}
|
|
333
|
+
</Tree>
|
|
334
|
+
|
|
335
|
+
BATCH OPERATIONS
|
|
336
|
+
----------------
|
|
337
|
+
Multiple operations at once:
|
|
338
|
+
|
|
339
|
+
<script>
|
|
340
|
+
function batchUpdate() {
|
|
341
|
+
// Disable reactivity during batch
|
|
342
|
+
const operations = [
|
|
343
|
+
() => treeRef.addNode('1', { name: 'Item A' }),
|
|
344
|
+
() => treeRef.addNode('1', { name: 'Item B' }),
|
|
345
|
+
() => treeRef.moveNode('2.1', '1', 'child')
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
operations.forEach(op => op());
|
|
349
|
+
|
|
350
|
+
// Refresh after batch
|
|
351
|
+
treeRef.refreshSiblings('1');
|
|
352
|
+
}
|
|
353
|
+
</script>
|
|
354
|
+
|
|
355
|
+
UNDO/REDO SUPPORT
|
|
356
|
+
-----------------
|
|
357
|
+
Track operations for undo:
|
|
358
|
+
|
|
359
|
+
<script>
|
|
360
|
+
let history = $state([]);
|
|
361
|
+
let historyIndex = $state(-1);
|
|
362
|
+
|
|
363
|
+
function recordOperation(type, data, undo) {
|
|
364
|
+
// Clear redo history
|
|
365
|
+
history = history.slice(0, historyIndex + 1);
|
|
366
|
+
// Add new operation
|
|
367
|
+
history = [...history, { type, data, undo }];
|
|
368
|
+
historyIndex = history.length - 1;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function addNode(parentPath, nodeData) {
|
|
372
|
+
const result = treeRef.addNode(parentPath, nodeData);
|
|
373
|
+
if (result.success) {
|
|
374
|
+
recordOperation('add', result.node, () => {
|
|
375
|
+
treeRef.removeNode(result.node.path);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function undo() {
|
|
381
|
+
if (historyIndex >= 0) {
|
|
382
|
+
history[historyIndex].undo();
|
|
383
|
+
historyIndex--;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
</script>
|
|
387
|
+
|
|
388
|
+
SYNC WITH SERVER
|
|
389
|
+
----------------
|
|
390
|
+
<script>
|
|
391
|
+
async function addNodeWithSync(parentPath, nodeData) {
|
|
392
|
+
// Optimistic update
|
|
393
|
+
const result = treeRef.addNode(parentPath, nodeData);
|
|
394
|
+
|
|
395
|
+
if (result.success) {
|
|
396
|
+
try {
|
|
397
|
+
// Sync to server
|
|
398
|
+
await api.createNode({
|
|
399
|
+
...nodeData,
|
|
400
|
+
path: result.node.path
|
|
401
|
+
});
|
|
402
|
+
} catch (error) {
|
|
403
|
+
// Rollback on failure
|
|
404
|
+
treeRef.removeNode(result.node.path);
|
|
405
|
+
showError('Failed to save');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
</script>
|
|
410
|
+
|
|
411
|
+
BEST PRACTICES
|
|
412
|
+
--------------
|
|
413
|
+
✅ Use orderMember for proper sibling ordering
|
|
414
|
+
✅ Handle operation results (success/error)
|
|
415
|
+
✅ Expand parent after adding child
|
|
416
|
+
✅ Update selection after operations
|
|
417
|
+
✅ Sync with server if needed
|
|
418
|
+
✅ Consider undo/redo for complex apps
|
|
419
|
+
|
|
420
|
+
❌ Don't forget to set orderMember
|
|
421
|
+
❌ Don't assume operations always succeed
|
|
422
|
+
❌ Don't mutate data directly (use methods)
|
|
423
|
+
❌ Don't forget to refresh UI after batch operations
|