@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,390 @@
|
|
|
1
|
+
DATA HANDLING
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
CRITICAL: Path-based hierarchical data structure
|
|
5
|
+
- Each item MUST have a unique path
|
|
6
|
+
- Path defines parent-child relationships
|
|
7
|
+
- Parent path derived by removing last segment
|
|
8
|
+
- Default separator is "." (configurable)
|
|
9
|
+
|
|
10
|
+
PATH STRUCTURE
|
|
11
|
+
--------------
|
|
12
|
+
The path is the PRIMARY way to define tree hierarchy:
|
|
13
|
+
|
|
14
|
+
Root level (no parent):
|
|
15
|
+
{ path: '1' }
|
|
16
|
+
{ path: '2' }
|
|
17
|
+
{ path: '3' }
|
|
18
|
+
|
|
19
|
+
Second level (parent is path minus last segment):
|
|
20
|
+
{ path: '1.1' } → parent is '1'
|
|
21
|
+
{ path: '1.2' } → parent is '1'
|
|
22
|
+
{ path: '2.1' } → parent is '2'
|
|
23
|
+
|
|
24
|
+
Third level:
|
|
25
|
+
{ path: '1.1.1' } → parent is '1.1'
|
|
26
|
+
{ path: '1.2.3' } → parent is '1.2'
|
|
27
|
+
|
|
28
|
+
MINIMAL DATA EXAMPLE
|
|
29
|
+
--------------------
|
|
30
|
+
const data = [
|
|
31
|
+
{ id: '1', path: '1', name: 'Documents' },
|
|
32
|
+
{ id: '1.1', path: '1.1', name: 'Projects' },
|
|
33
|
+
{ id: '1.1.1', path: '1.1.1', name: 'Project A' },
|
|
34
|
+
{ id: '1.1.2', path: '1.1.2', name: 'Project B' },
|
|
35
|
+
{ id: '1.2', path: '1.2', name: 'Reports' },
|
|
36
|
+
{ id: '2', path: '2', name: 'Pictures' },
|
|
37
|
+
{ id: '2.1', path: '2.1', name: 'Vacation' }
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
<Tree
|
|
41
|
+
{data}
|
|
42
|
+
idMember="id"
|
|
43
|
+
pathMember="path"
|
|
44
|
+
displayValueMember="name"
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
REQUIRED MEMBERS
|
|
48
|
+
----------------
|
|
49
|
+
| Prop | Type | Description |
|
|
50
|
+
|------|------|-------------|
|
|
51
|
+
| idMember | string | Property for unique identifier |
|
|
52
|
+
| pathMember | string | Property for hierarchical path |
|
|
53
|
+
|
|
54
|
+
OPTIONAL MEMBERS
|
|
55
|
+
----------------
|
|
56
|
+
| Prop | Type | Description |
|
|
57
|
+
|------|------|-------------|
|
|
58
|
+
| displayValueMember | string | Property for display text |
|
|
59
|
+
| parentPathMember | string | Property for parent path (usually derived) |
|
|
60
|
+
| levelMember | string | Property for depth level (usually calculated) |
|
|
61
|
+
| isExpandedMember | string | Property for expanded state |
|
|
62
|
+
| isSelectedMember | string | Property for selected state |
|
|
63
|
+
| isDraggableMember | string | Property for draggable state |
|
|
64
|
+
| isDropAllowedMember | string | Property for drop allowed state |
|
|
65
|
+
| isCollapsibleMember | string | Property for collapsible state |
|
|
66
|
+
| hasChildrenMember | string | Property for children indicator |
|
|
67
|
+
| searchValueMember | string | Property for search indexing |
|
|
68
|
+
| orderMember | string | Property for sibling sort order |
|
|
69
|
+
| allowedDropPositionsMember | string | Property for allowed drop positions |
|
|
70
|
+
|
|
71
|
+
CUSTOM PATH SEPARATOR
|
|
72
|
+
---------------------
|
|
73
|
+
Default separator is "." but can be changed:
|
|
74
|
+
|
|
75
|
+
// File system style paths
|
|
76
|
+
<Tree
|
|
77
|
+
{data}
|
|
78
|
+
treePathSeparator="/"
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
const data = [
|
|
82
|
+
{ id: 'root', path: 'files', name: 'Files' },
|
|
83
|
+
{ id: 'docs', path: 'files/documents', name: 'Documents' },
|
|
84
|
+
{ id: 'readme', path: 'files/documents/readme', name: 'README.md' }
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// Multi-character separator
|
|
88
|
+
<Tree treePathSeparator="::" />
|
|
89
|
+
// Paths: "root::child::grandchild"
|
|
90
|
+
|
|
91
|
+
DISPLAY VALUE CALLBACK
|
|
92
|
+
----------------------
|
|
93
|
+
For computed display values:
|
|
94
|
+
|
|
95
|
+
<Tree
|
|
96
|
+
{data}
|
|
97
|
+
getDisplayValueCallback={(node) => {
|
|
98
|
+
const item = node.data;
|
|
99
|
+
return `${item.name} (${item.count} items)`;
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
Or combining multiple properties:
|
|
104
|
+
|
|
105
|
+
getDisplayValueCallback={(node) => {
|
|
106
|
+
return `${node.data.firstName} ${node.data.lastName}`;
|
|
107
|
+
}}
|
|
108
|
+
|
|
109
|
+
SEARCH VALUE CONFIGURATION
|
|
110
|
+
--------------------------
|
|
111
|
+
What gets indexed for search:
|
|
112
|
+
|
|
113
|
+
// Simple: use a property
|
|
114
|
+
<Tree searchValueMember="name" />
|
|
115
|
+
|
|
116
|
+
// Complex: use callback
|
|
117
|
+
<Tree
|
|
118
|
+
getSearchValueCallback={(node) => {
|
|
119
|
+
const item = node.data;
|
|
120
|
+
return `${item.name} ${item.description} ${item.tags.join(' ')}`;
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
SORTING DATA
|
|
125
|
+
------------
|
|
126
|
+
IMPORTANT: For proper tree construction, parent nodes must be inserted before children.
|
|
127
|
+
|
|
128
|
+
Default sort handles this automatically, but custom sorts must maintain this:
|
|
129
|
+
|
|
130
|
+
const sortCallback = (nodes) => {
|
|
131
|
+
return nodes.sort((a, b) => {
|
|
132
|
+
// FIRST: Sort by level (parents before children)
|
|
133
|
+
const aLevel = a.path.split('.').length;
|
|
134
|
+
const bLevel = b.path.split('.').length;
|
|
135
|
+
if (aLevel !== bLevel) {
|
|
136
|
+
return aLevel - bLevel;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// THEN: Sort by your criteria
|
|
140
|
+
return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
<Tree {data} sortCallback={sortCallback} />
|
|
145
|
+
|
|
146
|
+
Pre-sorted data (skip internal sorting):
|
|
147
|
+
|
|
148
|
+
<Tree {data} isSorted={true} />
|
|
149
|
+
|
|
150
|
+
DRAGGABLE/DROP CONFIGURATION
|
|
151
|
+
----------------------------
|
|
152
|
+
Control drag and drop per node:
|
|
153
|
+
|
|
154
|
+
// Using member properties
|
|
155
|
+
const data = [
|
|
156
|
+
{ path: '1', name: 'Folder', isDraggable: true, isDropAllowed: true },
|
|
157
|
+
{ path: '1.1', name: 'Locked Item', isDraggable: false, isDropAllowed: false }
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
<Tree
|
|
161
|
+
{data}
|
|
162
|
+
isDraggableMember="isDraggable"
|
|
163
|
+
isDropAllowedMember="isDropAllowed"
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
DROP POSITION RESTRICTIONS
|
|
167
|
+
--------------------------
|
|
168
|
+
Restrict which drop positions are allowed per node:
|
|
169
|
+
|
|
170
|
+
// Using member property
|
|
171
|
+
const data = [
|
|
172
|
+
{ path: '1', name: 'Trash', allowedDropPositions: ['child'] },
|
|
173
|
+
{ path: '2', name: 'File.txt', allowedDropPositions: ['before', 'after'] },
|
|
174
|
+
{ path: '3', name: 'Folder' } // undefined = all positions
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
<Tree allowedDropPositionsMember="allowedDropPositions" />
|
|
178
|
+
|
|
179
|
+
// Using callback
|
|
180
|
+
<Tree
|
|
181
|
+
getAllowedDropPositionsCallback={(node) => {
|
|
182
|
+
if (node.data.type === 'file') return ['before', 'after'];
|
|
183
|
+
if (node.data.type === 'trash') return ['child'];
|
|
184
|
+
return undefined; // all positions
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
|
|
188
|
+
EXPANDED STATE
|
|
189
|
+
--------------
|
|
190
|
+
Control initial expanded state:
|
|
191
|
+
|
|
192
|
+
// Via expandLevel prop (recommended)
|
|
193
|
+
<Tree expandLevel={3} /> // Expand first 3 levels
|
|
194
|
+
|
|
195
|
+
// Via data property
|
|
196
|
+
const data = [
|
|
197
|
+
{ path: '1', name: 'Folder', isExpanded: true },
|
|
198
|
+
{ path: '1.1', name: 'Subfolder', isExpanded: false }
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
<Tree isExpandedMember="isExpanded" />
|
|
202
|
+
|
|
203
|
+
INSERT RESULT
|
|
204
|
+
-------------
|
|
205
|
+
Track data insertion success/failure:
|
|
206
|
+
|
|
207
|
+
<script>
|
|
208
|
+
let insertResult = $state();
|
|
209
|
+
|
|
210
|
+
$effect(() => {
|
|
211
|
+
if (insertResult) {
|
|
212
|
+
console.log('Successful:', insertResult.successful);
|
|
213
|
+
console.log('Failed:', insertResult.failed.length);
|
|
214
|
+
|
|
215
|
+
insertResult.failed.forEach(f => {
|
|
216
|
+
console.log(`Failed: ${f.originalData.name} - ${f.error}`);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
</script>
|
|
221
|
+
|
|
222
|
+
<Tree {data} bind:insertResult />
|
|
223
|
+
|
|
224
|
+
Common failure: "Could not find parent node"
|
|
225
|
+
- Caused by missing parent in data
|
|
226
|
+
- Or wrong sort order (child before parent)
|
|
227
|
+
|
|
228
|
+
LTREENODE STRUCTURE
|
|
229
|
+
-------------------
|
|
230
|
+
Internal node structure (what you get in callbacks):
|
|
231
|
+
|
|
232
|
+
interface LTreeNode<T> {
|
|
233
|
+
path: string; // Full path "1.2.3"
|
|
234
|
+
pathSegment: string; // Last segment "3"
|
|
235
|
+
parentPath: string | null; // Parent path "1.2" or null
|
|
236
|
+
level: number | null; // Depth level (0 = root)
|
|
237
|
+
data: T | null; // Your original data object
|
|
238
|
+
children: Record<string, LTreeNode<T>>;
|
|
239
|
+
isExpanded: boolean;
|
|
240
|
+
isSelected: boolean;
|
|
241
|
+
isDraggable: boolean;
|
|
242
|
+
isDropAllowed: boolean;
|
|
243
|
+
allowedDropPositions: DropPosition[] | null;
|
|
244
|
+
hasChildren: boolean;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
Accessing data in callbacks:
|
|
248
|
+
|
|
249
|
+
onNodeClicked={(node) => {
|
|
250
|
+
console.log('Path:', node.path);
|
|
251
|
+
console.log('Level:', node.level);
|
|
252
|
+
console.log('Name:', node.data.name); // Your data
|
|
253
|
+
console.log('Has children:', node.hasChildren);
|
|
254
|
+
}}
|
|
255
|
+
|
|
256
|
+
COMPLEX DATA EXAMPLE
|
|
257
|
+
--------------------
|
|
258
|
+
interface Employee {
|
|
259
|
+
employeeId: string;
|
|
260
|
+
orgPath: string;
|
|
261
|
+
fullName: string;
|
|
262
|
+
email: string;
|
|
263
|
+
title: string;
|
|
264
|
+
department: string;
|
|
265
|
+
reportsTo: string;
|
|
266
|
+
isManager: boolean;
|
|
267
|
+
hireDate: Date;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const employees: Employee[] = [
|
|
271
|
+
{
|
|
272
|
+
employeeId: 'E001',
|
|
273
|
+
orgPath: '1',
|
|
274
|
+
fullName: 'John CEO',
|
|
275
|
+
email: 'john@company.com',
|
|
276
|
+
title: 'CEO',
|
|
277
|
+
department: 'Executive',
|
|
278
|
+
reportsTo: null,
|
|
279
|
+
isManager: true,
|
|
280
|
+
hireDate: new Date('2020-01-01')
|
|
281
|
+
},
|
|
282
|
+
// ...
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
<Tree
|
|
286
|
+
data={employees}
|
|
287
|
+
idMember="employeeId"
|
|
288
|
+
pathMember="orgPath"
|
|
289
|
+
displayValueMember="fullName"
|
|
290
|
+
searchValueMember="email"
|
|
291
|
+
getDisplayValueCallback={(node) =>
|
|
292
|
+
`${node.data.fullName} - ${node.data.title}`
|
|
293
|
+
}
|
|
294
|
+
getSearchValueCallback={(node) =>
|
|
295
|
+
`${node.data.fullName} ${node.data.email} ${node.data.department}`
|
|
296
|
+
}
|
|
297
|
+
/>
|
|
298
|
+
|
|
299
|
+
DYNAMIC DATA UPDATES
|
|
300
|
+
--------------------
|
|
301
|
+
Update entire array (triggers re-render):
|
|
302
|
+
|
|
303
|
+
// Replace all data
|
|
304
|
+
data = newData;
|
|
305
|
+
|
|
306
|
+
// Add item
|
|
307
|
+
data = [...data, newItem];
|
|
308
|
+
|
|
309
|
+
// Remove item
|
|
310
|
+
data = data.filter(d => d.id !== idToRemove);
|
|
311
|
+
|
|
312
|
+
// Update item
|
|
313
|
+
data = data.map(d =>
|
|
314
|
+
d.id === targetId ? { ...d, name: 'Updated' } : d
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
IMPORTANT: Always reassign the array for reactivity
|
|
318
|
+
|
|
319
|
+
❌ data.push(newItem); // Won't trigger update
|
|
320
|
+
✅ data = [...data, newItem]; // Triggers update
|
|
321
|
+
|
|
322
|
+
PRIORITY ORDER
|
|
323
|
+
--------------
|
|
324
|
+
When both member and callback are set, callback wins:
|
|
325
|
+
|
|
326
|
+
getDisplayValueCallback > displayValueMember > default
|
|
327
|
+
|
|
328
|
+
Example:
|
|
329
|
+
<Tree
|
|
330
|
+
displayValueMember="name" // Ignored
|
|
331
|
+
getDisplayValueCallback={(n) => n.data.title} // Used
|
|
332
|
+
/>
|
|
333
|
+
|
|
334
|
+
COMMON DATA PATTERNS
|
|
335
|
+
--------------------
|
|
336
|
+
File system:
|
|
337
|
+
{
|
|
338
|
+
id: 'file-123',
|
|
339
|
+
path: 'documents/projects/report.pdf',
|
|
340
|
+
name: 'report.pdf',
|
|
341
|
+
type: 'file',
|
|
342
|
+
size: 1024
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
Organization chart:
|
|
346
|
+
{
|
|
347
|
+
employeeId: 'E100',
|
|
348
|
+
orgPath: '1.2.3',
|
|
349
|
+
name: 'Jane Smith',
|
|
350
|
+
title: 'Manager',
|
|
351
|
+
department: 'Engineering'
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
Category tree:
|
|
355
|
+
{
|
|
356
|
+
categoryId: 5,
|
|
357
|
+
categoryPath: '1.2',
|
|
358
|
+
categoryName: 'Electronics',
|
|
359
|
+
productCount: 150
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
DEBUGGING DATA
|
|
363
|
+
--------------
|
|
364
|
+
Enable debug mode:
|
|
365
|
+
|
|
366
|
+
<Tree shouldDisplayDebugInformation={true} />
|
|
367
|
+
|
|
368
|
+
Check data in console:
|
|
369
|
+
|
|
370
|
+
console.log('Data count:', data.length);
|
|
371
|
+
console.log('Sample item:', data[0]);
|
|
372
|
+
|
|
373
|
+
// After tree renders
|
|
374
|
+
console.log('Tree stats:', treeRef.statistics);
|
|
375
|
+
console.log('Insert result:', insertResult);
|
|
376
|
+
|
|
377
|
+
BEST PRACTICES
|
|
378
|
+
--------------
|
|
379
|
+
✅ Use unique paths for each item
|
|
380
|
+
✅ Ensure parent nodes exist before children
|
|
381
|
+
✅ Use sortCallback that sorts by level first
|
|
382
|
+
✅ Use $state.raw() for large datasets
|
|
383
|
+
✅ Use callbacks for computed values
|
|
384
|
+
✅ Handle insertResult errors gracefully
|
|
385
|
+
|
|
386
|
+
❌ Don't use duplicate paths
|
|
387
|
+
❌ Don't insert children before parents
|
|
388
|
+
❌ Don't mutate data array directly
|
|
389
|
+
❌ Don't use $state() for 1000+ items
|
|
390
|
+
❌ Don't mix path separators in same tree
|