@keenmate/svelte-treeview 4.5.0 → 4.6.0
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 +194 -45
- package/dist/components/Node.svelte +64 -109
- package/dist/components/Node.svelte.d.ts +3 -22
- package/dist/components/Tree.svelte +335 -41
- package/dist/components/Tree.svelte.d.ts +58 -7
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/ltree/ltree.svelte.js +71 -5
- package/dist/ltree/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -45,11 +45,22 @@ declare function $$render<T>(): {
|
|
|
45
45
|
shouldDisplayContextMenuInDebugMode?: boolean;
|
|
46
46
|
isLoading?: boolean;
|
|
47
47
|
progressiveRender?: boolean;
|
|
48
|
-
|
|
48
|
+
initialBatchSize?: number;
|
|
49
|
+
maxBatchSize?: number;
|
|
49
50
|
isRendering?: boolean;
|
|
50
51
|
onRenderStart?: () => void;
|
|
51
52
|
onRenderProgress?: (stats: RenderStats) => void;
|
|
52
53
|
onRenderComplete?: (stats: RenderStats) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Use flat/centralized rendering instead of recursive node rendering.
|
|
56
|
+
* This significantly improves performance for large trees by:
|
|
57
|
+
* - Removing the {#key changeTracker} block that destroys all nodes on any change
|
|
58
|
+
* - Using a single flat loop instead of recursive component instantiation
|
|
59
|
+
* - Allowing Svelte's keyed {#each} to efficiently diff only changed nodes
|
|
60
|
+
*/
|
|
61
|
+
useFlatRendering?: boolean;
|
|
62
|
+
/** Indentation per level in flat rendering mode (CSS value, default: '1.5rem') */
|
|
63
|
+
flatIndentSize?: string;
|
|
53
64
|
dragDropMode?: DragDropMode;
|
|
54
65
|
dropZoneMode?: "floating" | "glow";
|
|
55
66
|
dropZoneLayout?: "around" | "above" | "below" | "wave" | "wave2";
|
|
@@ -64,11 +75,15 @@ declare function $$render<T>(): {
|
|
|
64
75
|
* Called before a drop is processed. Return false to cancel the drop.
|
|
65
76
|
* Return { position, operation } to override the drop position or operation.
|
|
66
77
|
* Return true or undefined to proceed normally.
|
|
78
|
+
* Can be async - return a Promise to show dialogs or perform async validation.
|
|
67
79
|
*/
|
|
68
80
|
beforeDropCallback?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => boolean | {
|
|
69
81
|
position?: DropPosition;
|
|
70
82
|
operation?: DropOperation;
|
|
71
|
-
} | void
|
|
83
|
+
} | void | Promise<boolean | {
|
|
84
|
+
position?: DropPosition;
|
|
85
|
+
operation?: DropOperation;
|
|
86
|
+
} | void>;
|
|
72
87
|
onNodeDrop?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => void;
|
|
73
88
|
contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[];
|
|
74
89
|
bodyClass?: string | null | undefined;
|
|
@@ -125,7 +140,10 @@ declare function $$render<T>(): {
|
|
|
125
140
|
getAllData: () => T[];
|
|
126
141
|
closeContextMenu: () => void;
|
|
127
142
|
scrollToPath: (path: string, options?: {
|
|
143
|
+
/** Expand ancestors to make the node visible (default: true) */
|
|
128
144
|
expand?: boolean;
|
|
145
|
+
/** Also expand the target node itself to show its children (default: false for performance) */
|
|
146
|
+
expandTarget?: boolean;
|
|
129
147
|
highlight?: boolean;
|
|
130
148
|
scrollOptions?: ScrollIntoViewOptions;
|
|
131
149
|
/** Scroll only within the nearest scrollable container (prevents page scroll) */
|
|
@@ -172,11 +190,22 @@ declare function $$render<T>(): {
|
|
|
172
190
|
shouldDisplayContextMenuInDebugMode?: boolean;
|
|
173
191
|
isLoading?: boolean;
|
|
174
192
|
progressiveRender?: boolean;
|
|
175
|
-
|
|
193
|
+
initialBatchSize?: number;
|
|
194
|
+
maxBatchSize?: number;
|
|
176
195
|
isRendering?: boolean;
|
|
177
196
|
onRenderStart?: () => void;
|
|
178
197
|
onRenderProgress?: (stats: RenderStats) => void;
|
|
179
198
|
onRenderComplete?: (stats: RenderStats) => void;
|
|
199
|
+
/**
|
|
200
|
+
* Use flat/centralized rendering instead of recursive node rendering.
|
|
201
|
+
* This significantly improves performance for large trees by:
|
|
202
|
+
* - Removing the {#key changeTracker} block that destroys all nodes on any change
|
|
203
|
+
* - Using a single flat loop instead of recursive component instantiation
|
|
204
|
+
* - Allowing Svelte's keyed {#each} to efficiently diff only changed nodes
|
|
205
|
+
*/
|
|
206
|
+
useFlatRendering?: boolean;
|
|
207
|
+
/** Indentation per level in flat rendering mode (CSS value, default: '1.5rem') */
|
|
208
|
+
flatIndentSize?: string;
|
|
180
209
|
dragDropMode?: DragDropMode;
|
|
181
210
|
dropZoneMode?: "floating" | "glow";
|
|
182
211
|
dropZoneLayout?: "around" | "above" | "below" | "wave" | "wave2";
|
|
@@ -191,11 +220,15 @@ declare function $$render<T>(): {
|
|
|
191
220
|
* Called before a drop is processed. Return false to cancel the drop.
|
|
192
221
|
* Return { position, operation } to override the drop position or operation.
|
|
193
222
|
* Return true or undefined to proceed normally.
|
|
223
|
+
* Can be async - return a Promise to show dialogs or perform async validation.
|
|
194
224
|
*/
|
|
195
225
|
beforeDropCallback?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => boolean | {
|
|
196
226
|
position?: DropPosition;
|
|
197
227
|
operation?: DropOperation;
|
|
198
|
-
} | void
|
|
228
|
+
} | void | Promise<boolean | {
|
|
229
|
+
position?: DropPosition;
|
|
230
|
+
operation?: DropOperation;
|
|
231
|
+
} | void>;
|
|
199
232
|
onNodeDrop?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => void;
|
|
200
233
|
contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[];
|
|
201
234
|
bodyClass?: string | null | undefined;
|
|
@@ -262,7 +295,10 @@ declare class __sveltets_Render<T> {
|
|
|
262
295
|
getAllData: () => T[];
|
|
263
296
|
closeContextMenu: () => void;
|
|
264
297
|
scrollToPath: (path: string, options?: {
|
|
298
|
+
/** Expand ancestors to make the node visible (default: true) */
|
|
265
299
|
expand?: boolean;
|
|
300
|
+
/** Also expand the target node itself to show its children (default: false for performance) */
|
|
301
|
+
expandTarget?: boolean;
|
|
266
302
|
highlight?: boolean;
|
|
267
303
|
scrollOptions?: ScrollIntoViewOptions;
|
|
268
304
|
/** Scroll only within the nearest scrollable container (prevents page scroll) */
|
|
@@ -309,11 +345,22 @@ declare class __sveltets_Render<T> {
|
|
|
309
345
|
shouldDisplayContextMenuInDebugMode?: boolean;
|
|
310
346
|
isLoading?: boolean;
|
|
311
347
|
progressiveRender?: boolean;
|
|
312
|
-
|
|
348
|
+
initialBatchSize?: number;
|
|
349
|
+
maxBatchSize?: number;
|
|
313
350
|
isRendering?: boolean;
|
|
314
351
|
onRenderStart?: (() => void) | undefined;
|
|
315
352
|
onRenderProgress?: ((stats: RenderStats) => void) | undefined;
|
|
316
353
|
onRenderComplete?: ((stats: RenderStats) => void) | undefined;
|
|
354
|
+
/**
|
|
355
|
+
* Use flat/centralized rendering instead of recursive node rendering.
|
|
356
|
+
* This significantly improves performance for large trees by:
|
|
357
|
+
* - Removing the {#key changeTracker} block that destroys all nodes on any change
|
|
358
|
+
* - Using a single flat loop instead of recursive component instantiation
|
|
359
|
+
* - Allowing Svelte's keyed {#each} to efficiently diff only changed nodes
|
|
360
|
+
*/
|
|
361
|
+
useFlatRendering?: boolean;
|
|
362
|
+
/** Indentation per level in flat rendering mode (CSS value, default: '1.5rem') */
|
|
363
|
+
flatIndentSize?: string;
|
|
317
364
|
dragDropMode?: DragDropMode;
|
|
318
365
|
dropZoneMode?: "floating" | "glow";
|
|
319
366
|
dropZoneLayout?: "around" | "above" | "below" | "wave" | "wave2";
|
|
@@ -328,11 +375,15 @@ declare class __sveltets_Render<T> {
|
|
|
328
375
|
* Called before a drop is processed. Return false to cancel the drop.
|
|
329
376
|
* Return { position, operation } to override the drop position or operation.
|
|
330
377
|
* Return true or undefined to proceed normally.
|
|
378
|
+
* Can be async - return a Promise to show dialogs or perform async validation.
|
|
331
379
|
*/
|
|
332
380
|
beforeDropCallback?: ((dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => boolean | void | {
|
|
333
381
|
position?: DropPosition;
|
|
334
382
|
operation?: DropOperation;
|
|
335
|
-
}
|
|
383
|
+
} | Promise<boolean | void | {
|
|
384
|
+
position?: DropPosition;
|
|
385
|
+
operation?: DropOperation;
|
|
386
|
+
}>) | undefined;
|
|
336
387
|
onNodeDrop?: ((dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => void) | undefined;
|
|
337
388
|
contextMenuCallback?: ((node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[]) | undefined;
|
|
338
389
|
bodyClass?: string | null | undefined;
|
|
@@ -345,7 +396,7 @@ declare class __sveltets_Render<T> {
|
|
|
345
396
|
scrollHighlightClass?: string | null | undefined;
|
|
346
397
|
contextMenuXOffset?: number | null | undefined;
|
|
347
398
|
contextMenuYOffset?: number | null | undefined;
|
|
348
|
-
}, "treeId" | "data" | "
|
|
399
|
+
}, "treeId" | "data" | "shouldToggleOnNodeClick" | "expandIconClass" | "collapseIconClass" | "leafIconClass" | "selectedNodeClass" | "dragOverNodeClass" | "dropZoneMode" | "treePathSeparator" | "idMember" | "pathMember" | "parentPathMember" | "levelMember" | "hasChildrenMember" | "isExpandedMember" | "displayValueMember" | "getDisplayValueCallback" | "searchValueMember" | "getSearchValueCallback" | "orderMember" | "isSorted" | "sortCallback" | "isDraggableMember" | "isDropAllowedMember" | "shouldDisplayDebugInformation" | "isSelectedMember" | "selectedNode" | "expandLevel" | "shouldUseInternalSearchIndex" | "initializeIndexCallback" | "searchText" | "indexerBatchSize" | "indexerTimeout" | "shouldDisplayContextMenuInDebugMode" | "dragDropMode" | "onNodeClicked" | "onNodeDragStart" | "onNodeDragOver" | "beforeDropCallback" | "onNodeDrop" | "contextMenuCallback" | "bodyClass" | "scrollHighlightTimeout" | "scrollHighlightClass" | "contextMenuXOffset" | "contextMenuYOffset">>) => void;
|
|
349
400
|
};
|
|
350
401
|
}
|
|
351
402
|
interface $$IsomorphicComponent {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Auto-generated file - do not edit manually
|
|
2
2
|
// Generated by scripts/generate-constants.js
|
|
3
|
-
export const VERSION = "4.
|
|
3
|
+
export const VERSION = "4.6.0";
|
|
4
4
|
export const PACKAGE_NAME = "@keenmate/svelte-treeview";
|
|
5
5
|
export const AUTHOR = "KeenMate";
|
|
6
6
|
export const LICENSE = "MIT";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { default as Tree } from "./components/Tree.svelte";
|
|
|
2
2
|
export type { LTreeNode, NodeId, VisualState } from "./ltree/ltree-node.svelte";
|
|
3
3
|
export type { Ltree, DropPosition, DragDropMode, DropOperation, ContextMenuItem, InsertArrayResult, TreeChange, ApplyChangesResult } from "./ltree/types";
|
|
4
4
|
export type { RenderStats } from "./components/RenderCoordinator.svelte";
|
|
5
|
+
export type { NodeCallbacks, NodeConfig } from "./components/Tree.svelte";
|
|
5
6
|
export { enableLogging, disableLogging, setLogLevel, setCategoryLevel, LOGGING_CATEGORIES } from "./logger";
|
|
6
7
|
export { enablePerfLogging, disablePerfLogging, setPerfThreshold, isPerfLoggingEnabled, perfStart, perfEnd, perfMeasure, perfSummary } from "./perf-logger";
|
|
7
8
|
export type { GlobalTreeviewAPI } from "./global-api";
|
|
@@ -31,6 +31,9 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
31
31
|
let flatTreeNodes = [];
|
|
32
32
|
let filteredTree = null;
|
|
33
33
|
let isFiltered = false;
|
|
34
|
+
// Cache for visibleFlatNodes - only recompute when tree changes
|
|
35
|
+
let cachedVisibleFlatNodes = [];
|
|
36
|
+
let cachedVisibleFlatNodesTracker = null;
|
|
34
37
|
// Async search indexing infrastructure
|
|
35
38
|
let indexer = null;
|
|
36
39
|
// Initialize indexer when search index is available
|
|
@@ -74,6 +77,54 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
74
77
|
}
|
|
75
78
|
return Object.values(root.children);
|
|
76
79
|
},
|
|
80
|
+
/**
|
|
81
|
+
* Returns a flat array of all visible nodes in render order (depth-first).
|
|
82
|
+
* A node is visible if all its ancestors are expanded.
|
|
83
|
+
* This is optimized for flat/centralized rendering without recursion.
|
|
84
|
+
*
|
|
85
|
+
* Note: This getter depends on changeTracker to ensure reactivity when
|
|
86
|
+
* nodes are expanded/collapsed or the tree structure changes.
|
|
87
|
+
* Results are cached to avoid recomputation on repeated access.
|
|
88
|
+
*/
|
|
89
|
+
get visibleFlatNodes() {
|
|
90
|
+
// Explicitly read changeTracker to create reactive dependency
|
|
91
|
+
const _tracker = changeTracker;
|
|
92
|
+
// Return cached result if changeTracker hasn't changed
|
|
93
|
+
if (_tracker === cachedVisibleFlatNodesTracker && cachedVisibleFlatNodes.length > 0) {
|
|
94
|
+
// console.log(`[visibleFlatNodes] Cache HIT - returning ${cachedVisibleFlatNodes.length} nodes`);
|
|
95
|
+
return cachedVisibleFlatNodes;
|
|
96
|
+
}
|
|
97
|
+
const computeStart = performance.now();
|
|
98
|
+
const startRoot = this.isFiltered ? filteredRoot : root;
|
|
99
|
+
if (!startRoot?.children || !_tracker) {
|
|
100
|
+
cachedVisibleFlatNodes = [];
|
|
101
|
+
cachedVisibleFlatNodesTracker = _tracker;
|
|
102
|
+
return cachedVisibleFlatNodes;
|
|
103
|
+
}
|
|
104
|
+
const result = [];
|
|
105
|
+
const self = this;
|
|
106
|
+
function traverse(node) {
|
|
107
|
+
// Get children and optionally sort them
|
|
108
|
+
let children = Object.values(node.children);
|
|
109
|
+
if (self.isSorted && children.length > 0) {
|
|
110
|
+
children = self.sortCallback(children);
|
|
111
|
+
}
|
|
112
|
+
for (const child of children) {
|
|
113
|
+
result.push(child);
|
|
114
|
+
// Only traverse into children if this node is expanded
|
|
115
|
+
if (child.isExpanded && child.hasChildren) {
|
|
116
|
+
traverse(child);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
traverse(startRoot);
|
|
121
|
+
const computeTime = performance.now() - computeStart;
|
|
122
|
+
console.log(`[visibleFlatNodes] Computed ${result.length} nodes in ${computeTime.toFixed(2)}ms`);
|
|
123
|
+
// Cache the result
|
|
124
|
+
cachedVisibleFlatNodes = result;
|
|
125
|
+
cachedVisibleFlatNodesTracker = _tracker;
|
|
126
|
+
return result;
|
|
127
|
+
},
|
|
77
128
|
get statistics() {
|
|
78
129
|
const filteredNodeCount = isFiltered ? filteredTree?.length || 0 : 0;
|
|
79
130
|
const indexerStatus = indexer?.getStatus() || { isProcessing: false, queueSize: 0 };
|
|
@@ -408,31 +459,46 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
408
459
|
}
|
|
409
460
|
},
|
|
410
461
|
expandNodes: function (path, noEmitChanges = false) {
|
|
462
|
+
perfStart(`[${_treeId}] expandNodes`);
|
|
411
463
|
let node = this.isFiltered ? filteredRoot : root;
|
|
464
|
+
let hasChanges = false;
|
|
412
465
|
const segments = path.split(this.treePathSeparator);
|
|
413
466
|
for (let i = 0; i < segments.length; i++) {
|
|
414
467
|
const segment = segmentPrefix + segments[i];
|
|
415
468
|
if (node.children.hasOwnProperty(segment)) {
|
|
416
469
|
node = node.children[segment];
|
|
417
|
-
|
|
470
|
+
// Only mark as changed if actually changing from collapsed to expanded
|
|
471
|
+
if (!node.isExpanded) {
|
|
472
|
+
node.isExpanded = true;
|
|
473
|
+
hasChanges = true;
|
|
474
|
+
}
|
|
418
475
|
}
|
|
419
476
|
}
|
|
420
|
-
if
|
|
477
|
+
// Only emit changes if something actually changed
|
|
478
|
+
if (!noEmitChanges && hasChanges) {
|
|
479
|
+
console.log(`[Tree ${_treeId}] expandNodes triggering re-render for path: ${path}`);
|
|
421
480
|
this._emitTreeChanged();
|
|
422
481
|
}
|
|
482
|
+
perfEnd(`[${_treeId}] expandNodes`);
|
|
423
483
|
return this; // Return the API object for chaining
|
|
424
484
|
},
|
|
425
485
|
collapseNodes: function (path, noEmitChanges = false) {
|
|
426
486
|
let node = this.isFiltered ? filteredRoot : this.root;
|
|
487
|
+
let hasChanges = false;
|
|
427
488
|
const segments = path.split(this.treePathSeparator);
|
|
428
489
|
for (let i = 0; i < segments.length; i++) {
|
|
429
490
|
const segment = segmentPrefix + segments[i];
|
|
430
491
|
if (node.children.hasOwnProperty(segment)) {
|
|
431
492
|
node = node.children[segment];
|
|
432
|
-
|
|
493
|
+
// Only mark as changed if actually changing from expanded to collapsed
|
|
494
|
+
if (node.isExpanded) {
|
|
495
|
+
node.isExpanded = false;
|
|
496
|
+
hasChanges = true;
|
|
497
|
+
}
|
|
433
498
|
}
|
|
434
499
|
}
|
|
435
|
-
if
|
|
500
|
+
// Only emit changes if something actually changed
|
|
501
|
+
if (!noEmitChanges && hasChanges) {
|
|
436
502
|
this._emitTreeChanged();
|
|
437
503
|
}
|
|
438
504
|
return this; // Return the API object for chaining
|
|
@@ -613,8 +679,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
613
679
|
}
|
|
614
680
|
}
|
|
615
681
|
const newPath = newParentPath ? `${newParentPath}${this.treePathSeparator}${newSegment}` : newSegment;
|
|
616
|
-
// Update source node's path and parentPath
|
|
617
682
|
const oldPath = sourceNode.path;
|
|
683
|
+
// Update source node's path and parentPath
|
|
618
684
|
sourceNode.path = newPath;
|
|
619
685
|
sourceNode.pathSegment = newSegment;
|
|
620
686
|
sourceNode.parentPath = newParentPath || null;
|
package/dist/ltree/types.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export interface Ltree<T> {
|
|
|
71
71
|
isDropAllowedMember: string | null | undefined;
|
|
72
72
|
shouldDisplayDebugInformation: boolean | null | undefined;
|
|
73
73
|
get tree(): LTreeNode<T>[];
|
|
74
|
+
/** Flat array of all visible nodes in render order (depth-first, respects isExpanded) */
|
|
75
|
+
get visibleFlatNodes(): LTreeNode<T>[];
|
|
74
76
|
get statistics(): {
|
|
75
77
|
nodeCount: number;
|
|
76
78
|
maxLevel: number;
|