@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,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