@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,349 @@
1
+ PERFORMANCE
2
+ ===========
3
+
4
+ CRITICAL: Use $state.raw() for large datasets
5
+ - Svelte 5's $state() creates deep proxies
6
+ - With 1000+ items, this causes massive slowdown
7
+ - $state.raw() avoids proxy overhead
8
+
9
+ $STATE.RAW() REQUIREMENT
10
+ ------------------------
11
+ ❌ SLOW - 5000x slower with large datasets:
12
+ let treeData = $state<TreeNode[]>([]);
13
+
14
+ ✅ FAST - Items remain plain objects:
15
+ let treeData = $state.raw<TreeNode[]>([]);
16
+
17
+ Why it matters:
18
+ - $state() creates Proxy for every nested object
19
+ - Tree's insertArray() accesses multiple properties per item
20
+ - 8000 items × 10 properties = 80,000 proxy operations
21
+ - Proxy overhead: ~2.2ms per item vs ~0.0004ms plain
22
+
23
+ Symptoms of wrong usage:
24
+ - Tree takes 15-90+ seconds to render
25
+ - Console shows "[Violation] 'message' handler took XXXXms"
26
+ - Same data loads instantly in isolated test
27
+
28
+ The fix does NOT affect reactivity:
29
+ - Array changes still trigger updates
30
+ - Only individual items lose deep reactivity
31
+ - Tree doesn't need deep item reactivity
32
+
33
+ FLAT RENDERING MODE
34
+ -------------------
35
+ Default mode (v4.6+) - significantly faster:
36
+
37
+ <Tree useFlatRendering={true} /> <!-- Default -->
38
+
39
+ Flat mode benefits:
40
+ - Single {#each} loop instead of recursive components
41
+ - ~12x faster initial render (300ms → 25ms for 5500 nodes)
42
+ - Better memory usage
43
+
44
+ Legacy recursive mode:
45
+ <Tree useFlatRendering={false} />
46
+
47
+ Use recursive mode only if:
48
+ - Very small trees (<100 nodes)
49
+ - Need {#key changeTracker} behavior
50
+
51
+ PROGRESSIVE RENDERING
52
+ ---------------------
53
+ Renders nodes in batches to prevent UI freeze:
54
+
55
+ <Tree
56
+ progressiveRender={true} <!-- Default -->
57
+ initialBatchSize={20} <!-- First batch (instant) -->
58
+ maxBatchSize={500} <!-- Maximum batch size -->
59
+ />
60
+
61
+ Exponential batching:
62
+ - First batch: 20 nodes (instant feedback)
63
+ - Second batch: 40 nodes
64
+ - Third batch: 80 nodes
65
+ - Doubles until maxBatchSize (500)
66
+
67
+ Result: UI stays responsive during large tree loads
68
+
69
+ ASYNC SEARCH INDEXING
70
+ ---------------------
71
+ Search indexing uses requestIdleCallback:
72
+
73
+ <Tree
74
+ shouldUseInternalSearchIndex={true}
75
+ indexerBatchSize={25} <!-- Nodes per idle callback -->
76
+ indexerTimeout={50} <!-- Max wait before forcing -->
77
+ />
78
+
79
+ Benefits:
80
+ - Tree renders immediately
81
+ - Indexing runs during browser idle time
82
+ - Large datasets don't freeze UI
83
+
84
+ Check indexing status:
85
+ const stats = treeRef.statistics;
86
+ console.log('Indexing:', stats.isIndexing);
87
+ console.log('Pending:', stats.pendingIndexCount);
88
+
89
+ VIRTUAL SCROLL MODE
90
+ -------------------
91
+ For very large trees (10,000+ nodes), virtual scroll only renders
92
+ visible rows + overscan, keeping DOM size at ~50 nodes:
93
+
94
+ <Tree
95
+ virtualScroll={true}
96
+ virtualContainerHeight="500px" <!-- CSS height (auto-detected if not set) -->
97
+ virtualOverscan={5} <!-- Extra rows above/below viewport -->
98
+ />
99
+
100
+ How it works:
101
+ - Fixed-height container with overflow-y: auto
102
+ - Full-height spacer div for correct scrollbar proportions
103
+ - translateY(offset) positions visible window
104
+ - rAF-throttled scroll handler prevents excessive updates
105
+ - Auto-measures row height from first rendered node
106
+
107
+ Virtual scroll requires flat rendering (useFlatRendering=true, the default).
108
+
109
+ scrollToPath in virtual mode:
110
+ - Uses index-based scrolling (finds node in flat array)
111
+ - Centers target in viewport
112
+ - Waits for scroll + re-render (multiple rAF cycles)
113
+ - Applies highlight with retry
114
+
115
+ RENDERING MODE COMPARISON
116
+ -------------------------
117
+ | Mode | DOM Nodes | Best For |
118
+ |------|-----------|----------|
119
+ | Recursive | All nodes | Small trees (<100) |
120
+ | Flat | All nodes | Medium trees (100-10K) |
121
+ | Virtual | ~50 nodes | Large trees (10K+) |
122
+
123
+ RENDER PROGRESS CALLBACKS
124
+ -------------------------
125
+ Monitor progressive rendering:
126
+
127
+ <Tree
128
+ {data}
129
+ progressiveRender={true}
130
+ bind:isRendering
131
+ onRenderStart={() => console.log('Render started')}
132
+ onRenderProgress={(rendered, total) =>
133
+ console.log(`Rendered ${rendered}/${total}`)
134
+ }
135
+ onRenderComplete={() => console.log('Render complete')}
136
+ />
137
+
138
+ isRendering is a bindable boolean — useful for showing spinners/progress bars.
139
+
140
+ SORTING PERFORMANCE
141
+ -------------------
142
+ Level-first sorting is critical:
143
+
144
+ // REQUIRED: Sort by level first
145
+ sortCallback={(nodes) => {
146
+ return nodes.sort((a, b) => {
147
+ const aLevel = a.path.split('.').length;
148
+ const bLevel = b.path.split('.').length;
149
+ if (aLevel !== bLevel) return aLevel - bLevel;
150
+ return (a.data?.name ?? '').localeCompare(b.data?.name ?? '');
151
+ });
152
+ }}
153
+
154
+ For pre-sorted data (skip internal sort):
155
+ <Tree isSorted={true} />
156
+
157
+ PERFORMANCE LOGGING
158
+ -------------------
159
+ Enable performance measurements:
160
+
161
+ import { enablePerfLogging, setPerfThreshold } from '@keenmate/svelte-treeview';
162
+
163
+ enablePerfLogging();
164
+ setPerfThreshold(100); // Only log operations > 100ms
165
+
166
+ Or from browser console:
167
+ window.components['svelte-treeview'].perf.enable()
168
+
169
+ Logged operations:
170
+ - insertArray (conversion/sort/insert phases)
171
+ - filterNodes
172
+ - expandAll
173
+ - collapseAll
174
+
175
+ Output includes:
176
+ - Duration
177
+ - Item count
178
+ - Per-item time
179
+ - Items/sec throughput
180
+
181
+ CATEGORY LOGGING
182
+ ----------------
183
+ import { setCategoryLevel } from '@keenmate/svelte-treeview';
184
+
185
+ // Enable specific categories
186
+ setCategoryLevel('LTREE:RENDER', 'debug');
187
+ setCategoryLevel('LTREE:DATA', 'debug');
188
+
189
+ Categories:
190
+ - LTREE:INIT - Initialization
191
+ - LTREE:DATA - Data operations
192
+ - LTREE:RENDER - Rendering
193
+ - LTREE:INDEX - Search indexing
194
+ - LTREE:DRAG - Drag and drop
195
+ - LTREE:UI - User interactions
196
+
197
+ BENCHMARKS
198
+ ----------
199
+ Typical performance (5500 nodes):
200
+
201
+ | Operation | Time |
202
+ |-----------|------|
203
+ | Initial render (flat) | ~25ms |
204
+ | Initial render (recursive) | ~300ms |
205
+ | Initial render (virtual) | ~5ms |
206
+ | Expand/collapse | ~100-150ms |
207
+ | Search filtering | <50ms |
208
+ | insertArray | <100ms |
209
+
210
+ MEMORY OPTIMIZATION
211
+ -------------------
212
+ For very large datasets (10,000+ nodes):
213
+
214
+ 1. Use $state.raw():
215
+ let data = $state.raw<Item[]>([]);
216
+
217
+ 2. Enable progressive render:
218
+ <Tree progressiveRender={true} />
219
+
220
+ 3. Limit initial expand:
221
+ <Tree expandLevel={1} />
222
+
223
+ 4. Use virtual scrolling for 10K+ nodes:
224
+ <Tree virtualScroll={true} virtualContainerHeight="500px" />
225
+
226
+ AVOID O(n²) PATTERNS
227
+ --------------------
228
+ In sortCallback, avoid:
229
+
230
+ ❌ nodes.forEach(n => {
231
+ const siblings = nodes.filter(s => s.parentPath === n.parentPath);
232
+ });
233
+
234
+ ✅ Pre-compute:
235
+ const siblingMap = new Map();
236
+ nodes.forEach(n => {
237
+ const parent = n.parentPath || '';
238
+ if (!siblingMap.has(parent)) siblingMap.set(parent, []);
239
+ siblingMap.get(parent).push(n);
240
+ });
241
+
242
+ BATCH DATA UPDATES
243
+ ------------------
244
+ Instead of multiple small updates:
245
+
246
+ ❌ for (const item of items) {
247
+ data = [...data, item]; // Re-renders each time
248
+ }
249
+
250
+ ✅ data = [...data, ...items]; // Single re-render
251
+
252
+ EXPAND/COLLAPSE OPTIMIZATION
253
+ ----------------------------
254
+ For large trees, be specific:
255
+
256
+ // Slow: expands ALL nodes
257
+ treeRef.expandAll();
258
+
259
+ // Fast: expand specific subtree
260
+ treeRef.expandAll('1.2'); // Only under 1.2
261
+
262
+ // Fast: expand to level
263
+ <Tree expandLevel={2} />
264
+
265
+ SEARCH OPTIMIZATION
266
+ -------------------
267
+ Debounce search input:
268
+
269
+ let inputValue = $state('');
270
+ let searchText = $state('');
271
+ let timer;
272
+
273
+ function handleInput(e) {
274
+ inputValue = e.target.value;
275
+ clearTimeout(timer);
276
+ timer = setTimeout(() => {
277
+ searchText = inputValue;
278
+ }, 300);
279
+ }
280
+
281
+ Simplify search value callback:
282
+
283
+ // Avoid heavy computation
284
+ ❌ getSearchValueCallback={(node) => {
285
+ return expensiveOperation(node.data);
286
+ }}
287
+
288
+ // Keep it simple
289
+ ✅ getSearchValueCallback={(node) => {
290
+ return `${node.data.name} ${node.data.email}`;
291
+ }}
292
+
293
+ DEBUG MODE OVERHEAD
294
+ -------------------
295
+ Disable in production:
296
+
297
+ <Tree shouldDisplayDebugInformation={false} />
298
+
299
+ Debug mode adds:
300
+ - Statistics calculations
301
+ - Console logging
302
+ - Debug panel rendering
303
+
304
+ COMMON PERFORMANCE ISSUES
305
+ -------------------------
306
+ Issue: Tree takes forever to render
307
+ Fix: Use $state.raw() instead of $state()
308
+
309
+ Issue: UI freezes during load
310
+ Fix: Enable progressiveRender={true}
311
+
312
+ Issue: Slow search
313
+ Fix: Simplify getSearchValueCallback, use debouncing
314
+
315
+ Issue: Slow sorting
316
+ Fix: Avoid O(n²) patterns, use isSorted={true} for pre-sorted data
317
+
318
+ Issue: Memory issues with huge datasets
319
+ Fix: Limit expandLevel, use virtualScroll={true}
320
+
321
+ PROFILING
322
+ ---------
323
+ Use browser DevTools:
324
+
325
+ 1. Performance tab → Record
326
+ 2. Load tree or perform operation
327
+ 3. Look for long tasks
328
+
329
+ Common bottlenecks:
330
+ - Proxy access (use $state.raw())
331
+ - Recursive component creation (use flat mode)
332
+ - Search indexing (runs async, wait for completion)
333
+
334
+ BEST PRACTICES
335
+ --------------
336
+ ✅ Use $state.raw() for 1000+ items
337
+ ✅ Keep progressiveRender={true}
338
+ ✅ Keep useFlatRendering={true}
339
+ ✅ Limit expandLevel for large trees
340
+ ✅ Debounce search input
341
+ ✅ Use isSorted={true} for pre-sorted data
342
+ ✅ Use virtualScroll={true} for 10K+ nodes
343
+ ✅ Profile before optimizing
344
+
345
+ ❌ Don't use $state() for large datasets
346
+ ❌ Don't disable progressive rendering
347
+ ❌ Don't expand all levels for large trees
348
+ ❌ Don't do heavy computation in callbacks
349
+ ❌ Don't block UI with sync operations
@@ -0,0 +1,359 @@
1
+ SEARCH FEATURES
2
+ ===============
3
+
4
+ CRITICAL: Search requires explicit configuration
5
+ - Set shouldUseInternalSearchIndex={true}
6
+ - Provide searchValueMember OR getSearchValueCallback
7
+ - Optional: FlexSearch for advanced search
8
+
9
+ BASIC SEARCH SETUP
10
+ ------------------
11
+ <script>
12
+ let searchText = $state('');
13
+ </script>
14
+
15
+ <input bind:value={searchText} placeholder="Search..." />
16
+
17
+ <Tree
18
+ {data}
19
+ idMember="id"
20
+ pathMember="path"
21
+ bind:searchText
22
+ shouldUseInternalSearchIndex={true}
23
+ searchValueMember="name"
24
+ />
25
+
26
+ SEARCH VALUE CONFIGURATION
27
+ --------------------------
28
+ What text gets indexed for search:
29
+
30
+ // Simple: use a property
31
+ <Tree searchValueMember="name" />
32
+
33
+ // Complex: use callback for computed values
34
+ <Tree
35
+ getSearchValueCallback={(node) => {
36
+ const item = node.data;
37
+ return `${item.name} ${item.description} ${item.tags.join(' ')}`;
38
+ }}
39
+ />
40
+
41
+ ASYNC SEARCH INDEXING
42
+ ---------------------
43
+ IMPORTANT: Search indexing happens asynchronously
44
+
45
+ - Tree renders immediately
46
+ - Indexing runs during browser idle time
47
+ - Uses requestIdleCallback (or setTimeout fallback)
48
+ - Large datasets won't freeze UI
49
+
50
+ Check indexing status:
51
+ const stats = treeRef.statistics;
52
+ console.log('Is indexing:', stats.isIndexing);
53
+ console.log('Pending:', stats.pendingIndexCount);
54
+
55
+ INDEXER CONFIGURATION
56
+ ---------------------
57
+ <Tree
58
+ indexerBatchSize={25} <!-- Nodes per batch (default: 25) -->
59
+ indexerTimeout={50} <!-- Max wait time ms (default: 50) -->
60
+ />
61
+
62
+ indexerBatchSize:
63
+ - Lower (10-25): Smoother UI, slower indexing
64
+ - Higher (50-100): Faster indexing, brief UI pauses
65
+
66
+ indexerTimeout:
67
+ - Lower (25-50ms): More responsive indexing
68
+ - Higher (100-200ms): More genuine idle periods
69
+
70
+ FILTER VS SEARCH
71
+ ----------------
72
+ filterNodes() - Hides non-matching nodes in tree display
73
+
74
+ searchText binding uses filterNodes internally:
75
+ <Tree bind:searchText ... />
76
+
77
+ searchNodes() - Returns matching nodes without filtering
78
+
79
+ <script>
80
+ function search(query) {
81
+ const results = treeRef.searchNodes(query);
82
+ console.log('Found:', results.length, 'nodes');
83
+ return results;
84
+ }
85
+ </script>
86
+
87
+ FLEXSEARCH OPTIONS
88
+ ------------------
89
+ Both methods accept FlexSearch options:
90
+
91
+ // Search with options
92
+ const results = treeRef.searchNodes('query', {
93
+ suggest: true, // Enable suggestions for typos
94
+ limit: 10, // Max results
95
+ threshold: 0.8, // Similarity threshold
96
+ bool: 'and' // AND logic for multiple terms
97
+ });
98
+
99
+ // Filter with options
100
+ treeRef.filterNodes('query', {
101
+ suggest: true,
102
+ limit: 50
103
+ });
104
+
105
+ SEARCH RESULTS NAVIGATION
106
+ -------------------------
107
+ <script>
108
+ let searchText = $state('');
109
+ let results = $state([]);
110
+ let currentIndex = $state(0);
111
+ let treeRef;
112
+
113
+ $effect(() => {
114
+ if (searchText) {
115
+ results = treeRef.searchNodes(searchText);
116
+ currentIndex = 0;
117
+ if (results.length > 0) {
118
+ goToResult(0);
119
+ }
120
+ }
121
+ });
122
+
123
+ async function goToResult(index) {
124
+ currentIndex = index;
125
+ await treeRef.scrollToPath(results[index].path, {
126
+ expand: true,
127
+ highlight: true,
128
+ containerScroll: true
129
+ });
130
+ }
131
+
132
+ function next() {
133
+ const nextIndex = (currentIndex + 1) % results.length;
134
+ goToResult(nextIndex);
135
+ }
136
+
137
+ function prev() {
138
+ const prevIndex = (currentIndex - 1 + results.length) % results.length;
139
+ goToResult(prevIndex);
140
+ }
141
+ </script>
142
+
143
+ <input bind:value={searchText} />
144
+ <span>{currentIndex + 1} of {results.length}</span>
145
+ <button onclick={prev}>Prev</button>
146
+ <button onclick={next}>Next</button>
147
+
148
+ CLEAR SEARCH
149
+ ------------
150
+ // Clear search text (shows all nodes)
151
+ searchText = '';
152
+
153
+ // Or programmatically
154
+ treeRef.filterNodes('');
155
+
156
+ CUSTOM SEARCH INDEX
157
+ -------------------
158
+ Initialize your own FlexSearch index:
159
+
160
+ <script>
161
+ import { Index } from 'flexsearch';
162
+
163
+ function initializeIndex() {
164
+ return new Index({
165
+ tokenize: 'forward',
166
+ resolution: 9,
167
+ cache: true
168
+ });
169
+ }
170
+ </script>
171
+
172
+ <Tree
173
+ initializeIndexCallback={initializeIndex}
174
+ shouldUseInternalSearchIndex={true}
175
+ searchValueMember="name"
176
+ />
177
+
178
+ SEARCH WITHOUT INTERNAL INDEX
179
+ -----------------------------
180
+ Handle search externally:
181
+
182
+ <script>
183
+ let data = $state.raw([...]);
184
+ let filteredData = $state.raw([...]);
185
+ let searchText = $state('');
186
+
187
+ $effect(() => {
188
+ if (searchText) {
189
+ filteredData = data.filter(item =>
190
+ item.name.toLowerCase().includes(searchText.toLowerCase())
191
+ );
192
+ } else {
193
+ filteredData = data;
194
+ }
195
+ });
196
+ </script>
197
+
198
+ <input bind:value={searchText} />
199
+ <Tree data={filteredData} ... />
200
+
201
+ Note: This approach loses the tree hierarchy when filtering
202
+
203
+ SEARCH HIGHLIGHT
204
+ ----------------
205
+ Highlight matching text in node template:
206
+
207
+ <Tree {data} bind:searchText>
208
+ {#snippet nodeTemplate(node)}
209
+ {@html highlightMatch(node.data.name, searchText)}
210
+ {/snippet}
211
+ </Tree>
212
+
213
+ <script>
214
+ function highlightMatch(text, query) {
215
+ if (!query) return text;
216
+ const regex = new RegExp(`(${escapeRegex(query)})`, 'gi');
217
+ return text.replace(regex, '<mark>$1</mark>');
218
+ }
219
+
220
+ function escapeRegex(string) {
221
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
222
+ }
223
+ </script>
224
+
225
+ SEARCH DEBOUNCING
226
+ -----------------
227
+ Debounce search input for better performance:
228
+
229
+ <script>
230
+ let inputValue = $state('');
231
+ let searchText = $state('');
232
+ let debounceTimer;
233
+
234
+ function handleInput(e) {
235
+ inputValue = e.target.value;
236
+ clearTimeout(debounceTimer);
237
+ debounceTimer = setTimeout(() => {
238
+ searchText = inputValue;
239
+ }, 300);
240
+ }
241
+ </script>
242
+
243
+ <input value={inputValue} oninput={handleInput} />
244
+ <Tree bind:searchText ... />
245
+
246
+ SEARCH STATISTICS
247
+ -----------------
248
+ Track search results:
249
+
250
+ const stats = treeRef.statistics;
251
+ console.log('Total nodes:', stats.nodeCount);
252
+ console.log('Filtered visible:', stats.filteredNodeCount);
253
+
254
+ // After search
255
+ $effect(() => {
256
+ if (searchText) {
257
+ console.log(`Showing ${stats.filteredNodeCount} of ${stats.nodeCount}`);
258
+ }
259
+ });
260
+
261
+ MULTIPLE SEARCH FIELDS
262
+ ----------------------
263
+ Index multiple properties:
264
+
265
+ <Tree
266
+ getSearchValueCallback={(node) => {
267
+ const item = node.data;
268
+ return [
269
+ item.name,
270
+ item.email,
271
+ item.department,
272
+ item.tags.join(' ')
273
+ ].join(' ');
274
+ }}
275
+ />
276
+
277
+ SEARCH WITH EMPTY TREE
278
+ ----------------------
279
+ Handle no results state:
280
+
281
+ <Tree {data} bind:searchText>
282
+ {#snippet noDataFound()}
283
+ {#if searchText}
284
+ <p>No results for "{searchText}"</p>
285
+ {:else}
286
+ <p>No items available</p>
287
+ {/if}
288
+ {/snippet}
289
+ </Tree>
290
+
291
+ COMMON SEARCH PATTERNS
292
+ ----------------------
293
+ File search:
294
+ <Tree
295
+ searchValueMember="name"
296
+ getSearchValueCallback={(node) =>
297
+ `${node.data.name} ${node.data.path} ${node.data.extension}`
298
+ }
299
+ />
300
+
301
+ User search:
302
+ <Tree
303
+ getSearchValueCallback={(node) =>
304
+ `${node.data.firstName} ${node.data.lastName} ${node.data.email}`
305
+ }
306
+ />
307
+
308
+ Product search:
309
+ <Tree
310
+ getSearchValueCallback={(node) =>
311
+ `${node.data.name} ${node.data.sku} ${node.data.category} ${node.data.description}`
312
+ }
313
+ />
314
+
315
+ DEBUGGING SEARCH
316
+ ----------------
317
+ Enable search indexing logs:
318
+
319
+ import { setCategoryLevel } from '@keenmate/svelte-treeview';
320
+ setCategoryLevel('LTREE:INDEX', 'debug');
321
+
322
+ // Console shows:
323
+ // - Indexer initialization
324
+ // - Queue management
325
+ // - Batch processing
326
+ // - Indexing completion
327
+
328
+ Check index status:
329
+ console.log('Stats:', treeRef.statistics);
330
+
331
+ BEST PRACTICES
332
+ --------------
333
+ ✅ Set shouldUseInternalSearchIndex={true}
334
+ ✅ Provide searchValueMember or getSearchValueCallback
335
+ ✅ Use debouncing for search input
336
+ ✅ Handle loading state during indexing
337
+ ✅ Provide feedback for no results
338
+
339
+ ❌ Don't search before indexing completes
340
+ ❌ Don't use heavy callbacks (slows indexing)
341
+ ❌ Don't forget to clear search
342
+ ❌ Don't rely on immediate search after data load
343
+
344
+ TROUBLESHOOTING
345
+ ---------------
346
+ Search not working:
347
+ ✅ Check shouldUseInternalSearchIndex={true}
348
+ ✅ Check searchValueMember or getSearchValueCallback is set
349
+ ✅ Wait for indexing to complete (check statistics.isIndexing)
350
+
351
+ Search slow:
352
+ ✅ Reduce indexerBatchSize
353
+ ✅ Simplify getSearchValueCallback
354
+ ✅ Use $state.raw() for large datasets
355
+
356
+ No results found:
357
+ ✅ Check searchValueMember matches data property
358
+ ✅ Check getSearchValueCallback returns string
359
+ ✅ Check data actually contains search term