@keenmate/svelte-treeview 4.5.0 → 4.7.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 +247 -45
- package/dist/components/Node.svelte +136 -136
- package/dist/components/Node.svelte.d.ts +3 -22
- package/dist/components/Tree.svelte +341 -41
- package/dist/components/Tree.svelte.d.ts +64 -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/indexer.js +2 -1
- package/dist/ltree/ltree-node.svelte.d.ts +2 -0
- package/dist/ltree/ltree-node.svelte.js +1 -0
- package/dist/ltree/ltree.svelte.d.ts +1 -1
- package/dist/ltree/ltree.svelte.js +87 -6
- package/dist/ltree/types.d.ts +7 -3
- package/package.json +1 -1
|
@@ -13,6 +13,8 @@ declare function $$render<T>(): {
|
|
|
13
13
|
isSelectedMember?: string | null | undefined;
|
|
14
14
|
isDraggableMember?: string | null | undefined;
|
|
15
15
|
isDropAllowedMember?: string | null | undefined;
|
|
16
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
17
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
16
18
|
hasChildrenMember?: string | null | undefined;
|
|
17
19
|
isSorted?: boolean | null | undefined;
|
|
18
20
|
displayValueMember?: string | null | undefined;
|
|
@@ -45,11 +47,22 @@ declare function $$render<T>(): {
|
|
|
45
47
|
shouldDisplayContextMenuInDebugMode?: boolean;
|
|
46
48
|
isLoading?: boolean;
|
|
47
49
|
progressiveRender?: boolean;
|
|
48
|
-
|
|
50
|
+
initialBatchSize?: number;
|
|
51
|
+
maxBatchSize?: number;
|
|
49
52
|
isRendering?: boolean;
|
|
50
53
|
onRenderStart?: () => void;
|
|
51
54
|
onRenderProgress?: (stats: RenderStats) => void;
|
|
52
55
|
onRenderComplete?: (stats: RenderStats) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Use flat/centralized rendering instead of recursive node rendering.
|
|
58
|
+
* This significantly improves performance for large trees by:
|
|
59
|
+
* - Removing the {#key changeTracker} block that destroys all nodes on any change
|
|
60
|
+
* - Using a single flat loop instead of recursive component instantiation
|
|
61
|
+
* - Allowing Svelte's keyed {#each} to efficiently diff only changed nodes
|
|
62
|
+
*/
|
|
63
|
+
useFlatRendering?: boolean;
|
|
64
|
+
/** Indentation per level in flat rendering mode (CSS value, default: '1.5rem') */
|
|
65
|
+
flatIndentSize?: string;
|
|
53
66
|
dragDropMode?: DragDropMode;
|
|
54
67
|
dropZoneMode?: "floating" | "glow";
|
|
55
68
|
dropZoneLayout?: "around" | "above" | "below" | "wave" | "wave2";
|
|
@@ -64,11 +77,15 @@ declare function $$render<T>(): {
|
|
|
64
77
|
* Called before a drop is processed. Return false to cancel the drop.
|
|
65
78
|
* Return { position, operation } to override the drop position or operation.
|
|
66
79
|
* Return true or undefined to proceed normally.
|
|
80
|
+
* Can be async - return a Promise to show dialogs or perform async validation.
|
|
67
81
|
*/
|
|
68
82
|
beforeDropCallback?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => boolean | {
|
|
69
83
|
position?: DropPosition;
|
|
70
84
|
operation?: DropOperation;
|
|
71
|
-
} | void
|
|
85
|
+
} | void | Promise<boolean | {
|
|
86
|
+
position?: DropPosition;
|
|
87
|
+
operation?: DropOperation;
|
|
88
|
+
} | void>;
|
|
72
89
|
onNodeDrop?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => void;
|
|
73
90
|
contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[];
|
|
74
91
|
bodyClass?: string | null | undefined;
|
|
@@ -125,7 +142,10 @@ declare function $$render<T>(): {
|
|
|
125
142
|
getAllData: () => T[];
|
|
126
143
|
closeContextMenu: () => void;
|
|
127
144
|
scrollToPath: (path: string, options?: {
|
|
145
|
+
/** Expand ancestors to make the node visible (default: true) */
|
|
128
146
|
expand?: boolean;
|
|
147
|
+
/** Also expand the target node itself to show its children (default: false for performance) */
|
|
148
|
+
expandTarget?: boolean;
|
|
129
149
|
highlight?: boolean;
|
|
130
150
|
scrollOptions?: ScrollIntoViewOptions;
|
|
131
151
|
/** Scroll only within the nearest scrollable container (prevents page scroll) */
|
|
@@ -140,6 +160,8 @@ declare function $$render<T>(): {
|
|
|
140
160
|
isSelectedMember?: string | null | undefined;
|
|
141
161
|
isDraggableMember?: string | null | undefined;
|
|
142
162
|
isDropAllowedMember?: string | null | undefined;
|
|
163
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
164
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
143
165
|
hasChildrenMember?: string | null | undefined;
|
|
144
166
|
isSorted?: boolean | null | undefined;
|
|
145
167
|
displayValueMember?: string | null | undefined;
|
|
@@ -172,11 +194,22 @@ declare function $$render<T>(): {
|
|
|
172
194
|
shouldDisplayContextMenuInDebugMode?: boolean;
|
|
173
195
|
isLoading?: boolean;
|
|
174
196
|
progressiveRender?: boolean;
|
|
175
|
-
|
|
197
|
+
initialBatchSize?: number;
|
|
198
|
+
maxBatchSize?: number;
|
|
176
199
|
isRendering?: boolean;
|
|
177
200
|
onRenderStart?: () => void;
|
|
178
201
|
onRenderProgress?: (stats: RenderStats) => void;
|
|
179
202
|
onRenderComplete?: (stats: RenderStats) => void;
|
|
203
|
+
/**
|
|
204
|
+
* Use flat/centralized rendering instead of recursive node rendering.
|
|
205
|
+
* This significantly improves performance for large trees by:
|
|
206
|
+
* - Removing the {#key changeTracker} block that destroys all nodes on any change
|
|
207
|
+
* - Using a single flat loop instead of recursive component instantiation
|
|
208
|
+
* - Allowing Svelte's keyed {#each} to efficiently diff only changed nodes
|
|
209
|
+
*/
|
|
210
|
+
useFlatRendering?: boolean;
|
|
211
|
+
/** Indentation per level in flat rendering mode (CSS value, default: '1.5rem') */
|
|
212
|
+
flatIndentSize?: string;
|
|
180
213
|
dragDropMode?: DragDropMode;
|
|
181
214
|
dropZoneMode?: "floating" | "glow";
|
|
182
215
|
dropZoneLayout?: "around" | "above" | "below" | "wave" | "wave2";
|
|
@@ -191,11 +224,15 @@ declare function $$render<T>(): {
|
|
|
191
224
|
* Called before a drop is processed. Return false to cancel the drop.
|
|
192
225
|
* Return { position, operation } to override the drop position or operation.
|
|
193
226
|
* Return true or undefined to proceed normally.
|
|
227
|
+
* Can be async - return a Promise to show dialogs or perform async validation.
|
|
194
228
|
*/
|
|
195
229
|
beforeDropCallback?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => boolean | {
|
|
196
230
|
position?: DropPosition;
|
|
197
231
|
operation?: DropOperation;
|
|
198
|
-
} | void
|
|
232
|
+
} | void | Promise<boolean | {
|
|
233
|
+
position?: DropPosition;
|
|
234
|
+
operation?: DropOperation;
|
|
235
|
+
} | void>;
|
|
199
236
|
onNodeDrop?: (dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => void;
|
|
200
237
|
contextMenuCallback?: (node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[];
|
|
201
238
|
bodyClass?: string | null | undefined;
|
|
@@ -262,7 +299,10 @@ declare class __sveltets_Render<T> {
|
|
|
262
299
|
getAllData: () => T[];
|
|
263
300
|
closeContextMenu: () => void;
|
|
264
301
|
scrollToPath: (path: string, options?: {
|
|
302
|
+
/** Expand ancestors to make the node visible (default: true) */
|
|
265
303
|
expand?: boolean;
|
|
304
|
+
/** Also expand the target node itself to show its children (default: false for performance) */
|
|
305
|
+
expandTarget?: boolean;
|
|
266
306
|
highlight?: boolean;
|
|
267
307
|
scrollOptions?: ScrollIntoViewOptions;
|
|
268
308
|
/** Scroll only within the nearest scrollable container (prevents page scroll) */
|
|
@@ -277,6 +317,8 @@ declare class __sveltets_Render<T> {
|
|
|
277
317
|
isSelectedMember?: string | null | undefined;
|
|
278
318
|
isDraggableMember?: string | null | undefined;
|
|
279
319
|
isDropAllowedMember?: string | null | undefined;
|
|
320
|
+
allowedDropPositionsMember?: string | null | undefined;
|
|
321
|
+
getAllowedDropPositionsCallback?: ((node: LTreeNode<T>) => DropPosition[] | null | undefined) | undefined;
|
|
280
322
|
hasChildrenMember?: string | null | undefined;
|
|
281
323
|
isSorted?: boolean | null | undefined;
|
|
282
324
|
displayValueMember?: string | null | undefined;
|
|
@@ -309,11 +351,22 @@ declare class __sveltets_Render<T> {
|
|
|
309
351
|
shouldDisplayContextMenuInDebugMode?: boolean;
|
|
310
352
|
isLoading?: boolean;
|
|
311
353
|
progressiveRender?: boolean;
|
|
312
|
-
|
|
354
|
+
initialBatchSize?: number;
|
|
355
|
+
maxBatchSize?: number;
|
|
313
356
|
isRendering?: boolean;
|
|
314
357
|
onRenderStart?: (() => void) | undefined;
|
|
315
358
|
onRenderProgress?: ((stats: RenderStats) => void) | undefined;
|
|
316
359
|
onRenderComplete?: ((stats: RenderStats) => void) | undefined;
|
|
360
|
+
/**
|
|
361
|
+
* Use flat/centralized rendering instead of recursive node rendering.
|
|
362
|
+
* This significantly improves performance for large trees by:
|
|
363
|
+
* - Removing the {#key changeTracker} block that destroys all nodes on any change
|
|
364
|
+
* - Using a single flat loop instead of recursive component instantiation
|
|
365
|
+
* - Allowing Svelte's keyed {#each} to efficiently diff only changed nodes
|
|
366
|
+
*/
|
|
367
|
+
useFlatRendering?: boolean;
|
|
368
|
+
/** Indentation per level in flat rendering mode (CSS value, default: '1.5rem') */
|
|
369
|
+
flatIndentSize?: string;
|
|
317
370
|
dragDropMode?: DragDropMode;
|
|
318
371
|
dropZoneMode?: "floating" | "glow";
|
|
319
372
|
dropZoneLayout?: "around" | "above" | "below" | "wave" | "wave2";
|
|
@@ -328,11 +381,15 @@ declare class __sveltets_Render<T> {
|
|
|
328
381
|
* Called before a drop is processed. Return false to cancel the drop.
|
|
329
382
|
* Return { position, operation } to override the drop position or operation.
|
|
330
383
|
* Return true or undefined to proceed normally.
|
|
384
|
+
* Can be async - return a Promise to show dialogs or perform async validation.
|
|
331
385
|
*/
|
|
332
386
|
beforeDropCallback?: ((dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => boolean | void | {
|
|
333
387
|
position?: DropPosition;
|
|
334
388
|
operation?: DropOperation;
|
|
335
|
-
}
|
|
389
|
+
} | Promise<boolean | void | {
|
|
390
|
+
position?: DropPosition;
|
|
391
|
+
operation?: DropOperation;
|
|
392
|
+
}>) | undefined;
|
|
336
393
|
onNodeDrop?: ((dropNode: LTreeNode<T> | null, draggedNode: LTreeNode<T>, position: DropPosition, event: DragEvent | TouchEvent, operation: DropOperation) => void) | undefined;
|
|
337
394
|
contextMenuCallback?: ((node: LTreeNode<T>, closeMenuCallback: () => void) => ContextMenuItem[]) | undefined;
|
|
338
395
|
bodyClass?: string | null | undefined;
|
|
@@ -345,7 +402,7 @@ declare class __sveltets_Render<T> {
|
|
|
345
402
|
scrollHighlightClass?: string | null | undefined;
|
|
346
403
|
contextMenuXOffset?: number | null | undefined;
|
|
347
404
|
contextMenuYOffset?: number | null | undefined;
|
|
348
|
-
}, "treeId" | "data" | "
|
|
405
|
+
}, "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
406
|
};
|
|
350
407
|
}
|
|
351
408
|
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.7.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";
|
package/dist/ltree/indexer.js
CHANGED
|
@@ -26,7 +26,8 @@ export class Indexer {
|
|
|
26
26
|
}
|
|
27
27
|
// Add items to the processing queue
|
|
28
28
|
addToQueue(items) {
|
|
29
|
-
|
|
29
|
+
// Use concat instead of push(...items) to avoid stack overflow with large arrays
|
|
30
|
+
this.processingQueue = this.processingQueue.concat(items);
|
|
30
31
|
this.totalItemsAdded += items.length;
|
|
31
32
|
indexLogger.debug(`[${this.treeId}] Added ${items.length} items to queue. Queue size: ${this.processingQueue.length}`);
|
|
32
33
|
// Start processing if not already running
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type NodeId = string | number;
|
|
2
|
+
export type DropPosition = 'above' | 'below' | 'child';
|
|
2
3
|
export declare enum VisualState {
|
|
3
4
|
indeterminate = "indeterminate",
|
|
4
5
|
selected = "true",
|
|
@@ -18,6 +19,7 @@ export interface LTreeNode<T> {
|
|
|
18
19
|
priority: number | null | undefined;
|
|
19
20
|
isDraggable: boolean;
|
|
20
21
|
isDropAllowed: boolean;
|
|
22
|
+
allowedDropPositions: DropPosition[] | null | undefined;
|
|
21
23
|
isInsertAllowed: boolean;
|
|
22
24
|
isNestAllowed: boolean;
|
|
23
25
|
isCheckboxVisible: boolean | null | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Index } from 'flexsearch';
|
|
2
2
|
import { type LTreeNode } from './ltree-node.svelte';
|
|
3
3
|
import type { Ltree } from './types.js';
|
|
4
|
-
export declare function createLTree<T>(_idMember: string, _pathMember: string, _parentPathMember?: string | null | undefined, _levelMember?: string | null | undefined, _hasChildrenMember?: string | null | undefined, _isExpandedMember?: string | null | undefined, _isSelectableMember?: string | null | undefined, _isDraggableMember?: string | null | undefined, _isDropAllowedMember?: string | null | undefined, _displayValueMember?: string | null | undefined, _getDisplayValueCallback?: (node: LTreeNode<T>) => string, _searchValueMember?: string | null | undefined, _getSearchValueCallback?: (node: LTreeNode<T>) => string, _orderMember?: string | null | undefined, _treeId?: string, _treePathSeparator?: string | null | undefined, _expandLevel?: number | null | undefined, _shouldUseInternalSearchIndex?: boolean | null | undefined, _initializeIndexCallback?: () => Index, _indexerBatchSize?: number | null | undefined, _indexerTimeout?: number | null | undefined, opts?: Partial<Ltree<T>>): Ltree<T>;
|
|
4
|
+
export declare function createLTree<T>(_idMember: string, _pathMember: string, _parentPathMember?: string | null | undefined, _levelMember?: string | null | undefined, _hasChildrenMember?: string | null | undefined, _isExpandedMember?: string | null | undefined, _isSelectableMember?: string | null | undefined, _isDraggableMember?: string | null | undefined, _isDropAllowedMember?: string | null | undefined, _allowedDropPositionsMember?: string | null | undefined, _displayValueMember?: string | null | undefined, _getDisplayValueCallback?: (node: LTreeNode<T>) => string, _searchValueMember?: string | null | undefined, _getSearchValueCallback?: (node: LTreeNode<T>) => string, _getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => import('./types').DropPosition[] | null | undefined, _orderMember?: string | null | undefined, _treeId?: string, _treePathSeparator?: string | null | undefined, _expandLevel?: number | null | undefined, _shouldUseInternalSearchIndex?: boolean | null | undefined, _initializeIndexCallback?: () => Index, _indexerBatchSize?: number | null | undefined, _indexerTimeout?: number | null | undefined, opts?: Partial<Ltree<T>>): Ltree<T>;
|
|
@@ -5,7 +5,7 @@ import { getLevel, getParentPath, getPathSegments, getRelativePath } from '../he
|
|
|
5
5
|
import { createSearchIndex } from './flex.js';
|
|
6
6
|
import { Indexer } from './indexer.js';
|
|
7
7
|
import { perfStart, perfEnd, perfSummary } from '../perf-logger.js';
|
|
8
|
-
export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMember, _hasChildrenMember, _isExpandedMember, _isSelectableMember, _isDraggableMember, _isDropAllowedMember, _displayValueMember, _getDisplayValueCallback, _searchValueMember, _getSearchValueCallback, _orderMember, _treeId, _treePathSeparator, _expandLevel, _shouldUseInternalSearchIndex, _initializeIndexCallback, _indexerBatchSize, _indexerTimeout, opts) {
|
|
8
|
+
export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMember, _hasChildrenMember, _isExpandedMember, _isSelectableMember, _isDraggableMember, _isDropAllowedMember, _allowedDropPositionsMember, _displayValueMember, _getDisplayValueCallback, _searchValueMember, _getSearchValueCallback, _getAllowedDropPositionsCallback, _orderMember, _treeId, _treePathSeparator, _expandLevel, _shouldUseInternalSearchIndex, _initializeIndexCallback, _indexerBatchSize, _indexerTimeout, opts) {
|
|
9
9
|
let shouldCalculateParentPath = isEmptyString(_parentPathMember);
|
|
10
10
|
let shouldCalculateLevel = isEmptyString(_levelMember);
|
|
11
11
|
let shouldCalculateHasChildren = isEmptyString(_hasChildrenMember);
|
|
@@ -13,6 +13,7 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
13
13
|
let shouldCalculateIsSelectable = isEmptyString(_isSelectableMember);
|
|
14
14
|
let shouldCalculateIsDraggable = isEmptyString(_isDraggableMember);
|
|
15
15
|
let shouldCalculateIsDropAllowed = isEmptyString(_isDropAllowedMember);
|
|
16
|
+
let shouldCalculateAllowedDropPositions = isEmptyString(_allowedDropPositionsMember);
|
|
16
17
|
let shouldCalculateDisplayValue = isEmptyString(_displayValueMember);
|
|
17
18
|
let shouldCalculateSearchValue = isEmptyString(_searchValueMember);
|
|
18
19
|
// this is absolutely crucial to keep order of sorted items. Segments are just numbers and numbers as properties are always sorted
|
|
@@ -31,6 +32,9 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
31
32
|
let flatTreeNodes = [];
|
|
32
33
|
let filteredTree = null;
|
|
33
34
|
let isFiltered = false;
|
|
35
|
+
// Cache for visibleFlatNodes - only recompute when tree changes
|
|
36
|
+
let cachedVisibleFlatNodes = [];
|
|
37
|
+
let cachedVisibleFlatNodesTracker = null;
|
|
34
38
|
// Async search indexing infrastructure
|
|
35
39
|
let indexer = null;
|
|
36
40
|
// Initialize indexer when search index is available
|
|
@@ -54,11 +58,13 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
54
58
|
isSelectableMember: _isSelectableMember,
|
|
55
59
|
isDraggableMember: _isDraggableMember,
|
|
56
60
|
isDropAllowedMember: _isDropAllowedMember,
|
|
61
|
+
allowedDropPositionsMember: _allowedDropPositionsMember,
|
|
57
62
|
hasChildrenMember: _hasChildrenMember,
|
|
58
63
|
displayValueMember: _displayValueMember,
|
|
59
64
|
getDisplayValueCallback: _getDisplayValueCallback,
|
|
60
65
|
searchValueMember: _searchValueMember,
|
|
61
66
|
getSearchValueCallback: _getSearchValueCallback,
|
|
67
|
+
getAllowedDropPositionsCallback: _getAllowedDropPositionsCallback,
|
|
62
68
|
orderMember: _orderMember,
|
|
63
69
|
isSorted: false,
|
|
64
70
|
// Properties for filtering
|
|
@@ -74,6 +80,54 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
74
80
|
}
|
|
75
81
|
return Object.values(root.children);
|
|
76
82
|
},
|
|
83
|
+
/**
|
|
84
|
+
* Returns a flat array of all visible nodes in render order (depth-first).
|
|
85
|
+
* A node is visible if all its ancestors are expanded.
|
|
86
|
+
* This is optimized for flat/centralized rendering without recursion.
|
|
87
|
+
*
|
|
88
|
+
* Note: This getter depends on changeTracker to ensure reactivity when
|
|
89
|
+
* nodes are expanded/collapsed or the tree structure changes.
|
|
90
|
+
* Results are cached to avoid recomputation on repeated access.
|
|
91
|
+
*/
|
|
92
|
+
get visibleFlatNodes() {
|
|
93
|
+
// Explicitly read changeTracker to create reactive dependency
|
|
94
|
+
const _tracker = changeTracker;
|
|
95
|
+
// Return cached result if changeTracker hasn't changed
|
|
96
|
+
if (_tracker === cachedVisibleFlatNodesTracker && cachedVisibleFlatNodes.length > 0) {
|
|
97
|
+
// console.log(`[visibleFlatNodes] Cache HIT - returning ${cachedVisibleFlatNodes.length} nodes`);
|
|
98
|
+
return cachedVisibleFlatNodes;
|
|
99
|
+
}
|
|
100
|
+
const computeStart = performance.now();
|
|
101
|
+
const startRoot = this.isFiltered ? filteredRoot : root;
|
|
102
|
+
if (!startRoot?.children || !_tracker) {
|
|
103
|
+
cachedVisibleFlatNodes = [];
|
|
104
|
+
cachedVisibleFlatNodesTracker = _tracker;
|
|
105
|
+
return cachedVisibleFlatNodes;
|
|
106
|
+
}
|
|
107
|
+
const result = [];
|
|
108
|
+
const self = this;
|
|
109
|
+
function traverse(node) {
|
|
110
|
+
// Get children and optionally sort them
|
|
111
|
+
let children = Object.values(node.children);
|
|
112
|
+
if (self.isSorted && children.length > 0) {
|
|
113
|
+
children = self.sortCallback(children);
|
|
114
|
+
}
|
|
115
|
+
for (const child of children) {
|
|
116
|
+
result.push(child);
|
|
117
|
+
// Only traverse into children if this node is expanded
|
|
118
|
+
if (child.isExpanded && child.hasChildren) {
|
|
119
|
+
traverse(child);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
traverse(startRoot);
|
|
124
|
+
const computeTime = performance.now() - computeStart;
|
|
125
|
+
console.log(`[visibleFlatNodes] Computed ${result.length} nodes in ${computeTime.toFixed(2)}ms`);
|
|
126
|
+
// Cache the result
|
|
127
|
+
cachedVisibleFlatNodes = result;
|
|
128
|
+
cachedVisibleFlatNodesTracker = _tracker;
|
|
129
|
+
return result;
|
|
130
|
+
},
|
|
77
131
|
get statistics() {
|
|
78
132
|
const filteredNodeCount = isFiltered ? filteredTree?.length || 0 : 0;
|
|
79
133
|
const indexerStatus = indexer?.getStatus() || { isProcessing: false, queueSize: 0 };
|
|
@@ -120,6 +174,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
120
174
|
node.isDraggable = row[_isDraggableMember];
|
|
121
175
|
if (!shouldCalculateIsDropAllowed)
|
|
122
176
|
node.isDropAllowed = row[_isDropAllowedMember];
|
|
177
|
+
if (!shouldCalculateAllowedDropPositions)
|
|
178
|
+
node.allowedDropPositions = row[_allowedDropPositionsMember];
|
|
123
179
|
if (!shouldCalculateHasChildren)
|
|
124
180
|
node.hasChildren = row[_hasChildrenMember];
|
|
125
181
|
node.data = row;
|
|
@@ -408,31 +464,46 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
408
464
|
}
|
|
409
465
|
},
|
|
410
466
|
expandNodes: function (path, noEmitChanges = false) {
|
|
467
|
+
perfStart(`[${_treeId}] expandNodes`);
|
|
411
468
|
let node = this.isFiltered ? filteredRoot : root;
|
|
469
|
+
let hasChanges = false;
|
|
412
470
|
const segments = path.split(this.treePathSeparator);
|
|
413
471
|
for (let i = 0; i < segments.length; i++) {
|
|
414
472
|
const segment = segmentPrefix + segments[i];
|
|
415
473
|
if (node.children.hasOwnProperty(segment)) {
|
|
416
474
|
node = node.children[segment];
|
|
417
|
-
|
|
475
|
+
// Only mark as changed if actually changing from collapsed to expanded
|
|
476
|
+
if (!node.isExpanded) {
|
|
477
|
+
node.isExpanded = true;
|
|
478
|
+
hasChanges = true;
|
|
479
|
+
}
|
|
418
480
|
}
|
|
419
481
|
}
|
|
420
|
-
if
|
|
482
|
+
// Only emit changes if something actually changed
|
|
483
|
+
if (!noEmitChanges && hasChanges) {
|
|
484
|
+
console.log(`[Tree ${_treeId}] expandNodes triggering re-render for path: ${path}`);
|
|
421
485
|
this._emitTreeChanged();
|
|
422
486
|
}
|
|
487
|
+
perfEnd(`[${_treeId}] expandNodes`);
|
|
423
488
|
return this; // Return the API object for chaining
|
|
424
489
|
},
|
|
425
490
|
collapseNodes: function (path, noEmitChanges = false) {
|
|
426
491
|
let node = this.isFiltered ? filteredRoot : this.root;
|
|
492
|
+
let hasChanges = false;
|
|
427
493
|
const segments = path.split(this.treePathSeparator);
|
|
428
494
|
for (let i = 0; i < segments.length; i++) {
|
|
429
495
|
const segment = segmentPrefix + segments[i];
|
|
430
496
|
if (node.children.hasOwnProperty(segment)) {
|
|
431
497
|
node = node.children[segment];
|
|
432
|
-
|
|
498
|
+
// Only mark as changed if actually changing from expanded to collapsed
|
|
499
|
+
if (node.isExpanded) {
|
|
500
|
+
node.isExpanded = false;
|
|
501
|
+
hasChanges = true;
|
|
502
|
+
}
|
|
433
503
|
}
|
|
434
504
|
}
|
|
435
|
-
if
|
|
505
|
+
// Only emit changes if something actually changed
|
|
506
|
+
if (!noEmitChanges && hasChanges) {
|
|
436
507
|
this._emitTreeChanged();
|
|
437
508
|
}
|
|
438
509
|
return this; // Return the API object for chaining
|
|
@@ -466,6 +537,16 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
466
537
|
return this.getSearchValueCallback(node);
|
|
467
538
|
return '[N/A]';
|
|
468
539
|
},
|
|
540
|
+
getNodeAllowedDropPositions(node) {
|
|
541
|
+
// Priority: callback > member > node property
|
|
542
|
+
if (this.getAllowedDropPositionsCallback) {
|
|
543
|
+
return this.getAllowedDropPositionsCallback(node);
|
|
544
|
+
}
|
|
545
|
+
if (!shouldCalculateAllowedDropPositions && node.data) {
|
|
546
|
+
return node.data[_allowedDropPositionsMember];
|
|
547
|
+
}
|
|
548
|
+
return node.allowedDropPositions;
|
|
549
|
+
},
|
|
469
550
|
refresh() {
|
|
470
551
|
this._emitTreeChanged();
|
|
471
552
|
},
|
|
@@ -613,8 +694,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
613
694
|
}
|
|
614
695
|
}
|
|
615
696
|
const newPath = newParentPath ? `${newParentPath}${this.treePathSeparator}${newSegment}` : newSegment;
|
|
616
|
-
// Update source node's path and parentPath
|
|
617
697
|
const oldPath = sourceNode.path;
|
|
698
|
+
// Update source node's path and parentPath
|
|
618
699
|
sourceNode.path = newPath;
|
|
619
700
|
sourceNode.pathSegment = newSegment;
|
|
620
701
|
sourceNode.parentPath = newParentPath || null;
|
package/dist/ltree/types.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { SearchOptions } from 'flexsearch';
|
|
2
|
-
import type { LTreeNode } from './ltree-node.svelte';
|
|
3
|
-
export type { LTreeNode } from './ltree-node.svelte';
|
|
2
|
+
import type { LTreeNode, DropPosition } from './ltree-node.svelte';
|
|
3
|
+
export type { LTreeNode, DropPosition } from './ltree-node.svelte';
|
|
4
4
|
export type Tuple<T, U> = [T, U];
|
|
5
|
-
export type DropPosition = 'above' | 'below' | 'child';
|
|
6
5
|
export type DragDropMode = 'none' | 'self' | 'cross' | 'both';
|
|
7
6
|
export type DropZoneLayout = 'around' | 'above' | 'below' | 'wave' | 'wave2';
|
|
8
7
|
export type DropOperation = 'move' | 'copy';
|
|
@@ -69,8 +68,13 @@ export interface Ltree<T> {
|
|
|
69
68
|
isSelectableMember: string | null | undefined;
|
|
70
69
|
isDraggableMember: string | null | undefined;
|
|
71
70
|
isDropAllowedMember: string | null | undefined;
|
|
71
|
+
allowedDropPositionsMember: string | null | undefined;
|
|
72
|
+
getAllowedDropPositionsCallback?: (node: LTreeNode<T>) => DropPosition[] | null | undefined;
|
|
72
73
|
shouldDisplayDebugInformation: boolean | null | undefined;
|
|
74
|
+
getNodeAllowedDropPositions(node: LTreeNode<T>): DropPosition[] | null | undefined;
|
|
73
75
|
get tree(): LTreeNode<T>[];
|
|
76
|
+
/** Flat array of all visible nodes in render order (depth-first, respects isExpanded) */
|
|
77
|
+
get visibleFlatNodes(): LTreeNode<T>[];
|
|
74
78
|
get statistics(): {
|
|
75
79
|
nodeCount: number;
|
|
76
80
|
maxLevel: number;
|