@keenmate/svelte-treeview 5.0.0-rc06 → 5.0.0-rc08
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/CHANGELOG.md +19 -0
- package/README.md +12 -25
- package/dist/components/Tree.svelte +43 -14
- package/dist/components/Tree.svelte.d.ts +69 -16
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/core/TreeController.svelte.d.ts +40 -14
- package/dist/core/TreeController.svelte.js +58 -32
- package/dist/ltree/ltree-node.svelte.d.ts +0 -5
- package/dist/ltree/ltree-node.svelte.js +0 -5
- package/dist/ltree/ltree.svelte.d.ts +1 -1
- package/dist/ltree/ltree.svelte.js +180 -50
- package/dist/ltree/types.d.ts +15 -6
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [5.0.0-rc08] - 2026-05-27
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`{ silent: true }` option on highlight/selection methods**: `highlightNode`, `highlightNodes`, `clearHighlight`, `deselectAll` (and deprecated `selectNode`/`selectNodes`) now accept `{ silent: true }` to update state without firing `onNodeClick` / `onHighlightChange` / `onSelectionChange`. Intended for URL-restore flows (deep links loading form data from query params) where firing the change callback would re-trigger form loaders and clobber the data the URL just supplied. Silent mode also skips focusing the tree container so it doesn't steal focus from whatever the user is interacting with.
|
|
14
|
+
- **Array variants on expand/collapse methods**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now accept `string | string[]`. Single emit per call regardless of array length.
|
|
15
|
+
- **`{ exclusive: true }` option on `expandNodes` and `expandAll`**: Opens the target path(s) and collapses anything currently expanded that isn't on the union-of-spines (and, for `expandAll`, not under a target subtree). Equivalent to `collapseAll() + expandNodes(path)` but in a single pass with one emit — downstream listeners (transition animations, URL sync, virtualized renderers) don't see the intermediate fully-collapsed state. Non-collapsible nodes (`isCollapsible === false`) are never touched.
|
|
16
|
+
- **`{ noEmit: true }` option on all four expand/collapse methods**: Skips the change emit, enabling batching multiple operations and emitting once at the end via `tree.refresh()`.
|
|
17
|
+
- **`/examples/silent-highlight` demo page**: URL-restore scenario with loud vs. silent toggle showing how form data is preserved in silent mode.
|
|
18
|
+
- **`/examples/expand-collapse` demo page**: Demonstrates array variants and exclusive focus mode.
|
|
19
|
+
|
|
20
|
+
## [5.0.0-rc07] - 2026-05-23
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- **`isSelectedMember` prop**: Data field name that seeds `node.isSelected` at `insertArray` time. The controller walks the tree after insert and pre-populates the bindable `selectedPaths` Set with every path where the field is truthy — independent of `isSelectableMember`, so non-selectable nodes can still ship checked.
|
|
24
|
+
- **`isSelectableMember` prop now publicly wired**: Previously declared on the core types but not exposed through `Tree.svelte`'s prop/update interface. Now end-to-end usable. Controls whether a node renders a checkbox and carries the `ltree-clickable` class.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Filter race with async indexing**: A `bind:searchText` change that landed while the FlexSearch index was still being built (via `requestIdleCallback` batches) saw an empty index, hid the entire tree, and never recovered — no mechanism re-applied the filter once indexing completed. `filterNodes` now remembers the active query and the indexer's `onComplete` callback re-runs it, so the visible filter catches up automatically regardless of `indexerBatchSize`, dataset size, or how fast the user types.
|
|
28
|
+
|
|
10
29
|
## [5.0.0-rc06] - 2026-03-31
|
|
11
30
|
|
|
12
31
|
### Added
|
package/README.md
CHANGED
|
@@ -6,31 +6,18 @@ A high-performance, feature-rich hierarchical tree view component for Svelte 5 w
|
|
|
6
6
|
|
|
7
7
|
Browse interactive code examples and the full API reference at **[svelte-treeview.keenmate.dev](https://svelte-treeview.keenmate.dev)**
|
|
8
8
|
|
|
9
|
-
## What's New in v5.0.0-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **Multi-select**: Ctrl+click toggles nodes, Shift+click selects ranges, plain click replaces selection. New `selectedPaths` bindable (`Set<string>`), `onSelectionChanged` event, and `rangeSelectionMode` prop (`'visual'` vs `'logical'`).
|
|
22
|
-
- **Public multi-select API**: `selectNode(path, mode)`, `selectNodes(paths)`, `deselectAll()`, `getSelectedNodes()`, `isNodeSelected(path)` on TreeController.
|
|
23
|
-
- **Selection-aware context menus**: `contextMenuCallback` now receives `selectedNodes` as 3rd parameter for bulk actions.
|
|
24
|
-
|
|
25
|
-
### v5.0.0-rc03
|
|
26
|
-
|
|
27
|
-
- **Unified Context Menu API**: New shared type system (`ContextMenuItem`, `ContextMenuDivider`, `ContextMenuEntry`) used by both svelte-treeview and canvas-tree. Breaking: `title` → `label`, `callback` → `onclick`, `isDivider` → separate `ContextMenuDivider` type.
|
|
28
|
-
- **Context menu features**: Keyboard shortcuts, nested submenus, named dividers (`──── Section ────`), `isVisible`/`isDisabled` per item, `className="danger"`, async `onclick`.
|
|
29
|
-
- **Svelte context menu components**: `ContextMenuItemC` and `ContextMenuDividerC` for declarative snippet-based menus alongside the callback approach.
|
|
30
|
-
- **Accordion expand**: `accordionExpand={true}` — expanding a node auto-collapses its siblings.
|
|
31
|
-
- **Toggle icon mode**: `toggleIconMode="rotate"` (default) smoothly rotates the expand icon vs `"swap"` which switches between two icons.
|
|
32
|
-
- **Fix**: Expand/collapse icon not updating in flat rendering mode.
|
|
33
|
-
- **Fix**: `vite.config.ts` no longer requires `@types/node`.
|
|
9
|
+
## What's New in v5.0.0-rc08
|
|
10
|
+
|
|
11
|
+
- **`{ silent: true }` on highlight/selection methods**: Update tree state from URL params or other external sources without firing `onNodeClick` / `onHighlightChange` / `onSelectionChange` — perfect for deep links where you don't want the change callback to re-trigger your form loader. Applies to `highlightNode`, `highlightNodes`, `clearHighlight`, and `deselectAll`.
|
|
12
|
+
- **Array variants for expand/collapse**: `expandNodes`, `collapseNodes`, `expandAll`, and `collapseAll` now take `string | string[]`. Open or close several places in one call.
|
|
13
|
+
- **Exclusive focus mode**: Pass `{ exclusive: true }` to `expandNodes` / `expandAll` to open the target path and collapse everything else in a single pass — no two-step flicker compared to `collapseAll() + expandNodes(path)`.
|
|
14
|
+
- **`noEmit` option for batching**: Suppress the change emit on individual expand/collapse calls when chaining several operations; emit once at the end.
|
|
15
|
+
|
|
16
|
+
## What's New in v5.0.0-rc07
|
|
17
|
+
|
|
18
|
+
- **`isSelectedMember` prop**: Seed `selectedPaths` directly from your data — point the prop at a boolean field and every node where it's truthy lands pre-checked after `insertArray`.
|
|
19
|
+
- **`isSelectableMember` fully wired through `Tree`**: The prop was already on the core types but wasn't reachable via the component. Now end-to-end usable to control checkbox rendering and clickability per node.
|
|
20
|
+
- **Fix: filter race during async indexing**: `bind:searchText` no longer hides the entire tree if the FlexSearch index is still being built when the filter is applied. The filter now re-applies itself automatically once indexing completes, regardless of `indexerBatchSize` or dataset size.
|
|
34
21
|
|
|
35
22
|
## v5.0: Core/Renderer Split + Virtual Scroll
|
|
36
23
|
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
parentPathMember?: string | null | undefined;
|
|
32
32
|
levelMember?: string | null | undefined;
|
|
33
33
|
isExpandedMember?: string | null | undefined;
|
|
34
|
+
isSelectableMember?: string | null | undefined;
|
|
34
35
|
isSelectedMember?: string | null | undefined;
|
|
35
36
|
isDraggableMember?: string | null | undefined;
|
|
36
37
|
getIsDraggableCallback?: (node: LTreeNode<T>) => boolean;
|
|
@@ -184,6 +185,7 @@
|
|
|
184
185
|
hasChildrenMember,
|
|
185
186
|
|
|
186
187
|
isExpandedMember,
|
|
188
|
+
isSelectableMember,
|
|
187
189
|
isSelectedMember,
|
|
188
190
|
isDraggableMember,
|
|
189
191
|
getIsDraggableCallback,
|
|
@@ -304,6 +306,7 @@
|
|
|
304
306
|
levelMember,
|
|
305
307
|
hasChildrenMember,
|
|
306
308
|
isExpandedMember,
|
|
309
|
+
isSelectableMember,
|
|
307
310
|
isSelectedMember,
|
|
308
311
|
isDraggableMember,
|
|
309
312
|
getIsDraggableCallback,
|
|
@@ -504,20 +507,32 @@
|
|
|
504
507
|
);
|
|
505
508
|
|
|
506
509
|
// ── Export public methods (thin proxies) ────────────────────────────
|
|
507
|
-
export async function expandNodes(
|
|
508
|
-
|
|
510
|
+
export async function expandNodes(
|
|
511
|
+
nodePath: string | string[],
|
|
512
|
+
options?: { exclusive?: boolean; noEmit?: boolean }
|
|
513
|
+
) {
|
|
514
|
+
controller.expandNodes(nodePath, options);
|
|
509
515
|
}
|
|
510
516
|
|
|
511
|
-
export async function collapseNodes(
|
|
512
|
-
|
|
517
|
+
export async function collapseNodes(
|
|
518
|
+
nodePath: string | string[],
|
|
519
|
+
options?: { noEmit?: boolean }
|
|
520
|
+
) {
|
|
521
|
+
controller.collapseNodes(nodePath, options);
|
|
513
522
|
}
|
|
514
523
|
|
|
515
|
-
export function expandAll(
|
|
516
|
-
|
|
524
|
+
export function expandAll(
|
|
525
|
+
nodePath?: string | string[] | null | undefined,
|
|
526
|
+
options?: { exclusive?: boolean; noEmit?: boolean }
|
|
527
|
+
) {
|
|
528
|
+
controller.expandAll(nodePath, options);
|
|
517
529
|
}
|
|
518
530
|
|
|
519
|
-
export function collapseAll(
|
|
520
|
-
|
|
531
|
+
export function collapseAll(
|
|
532
|
+
nodePath?: string | string[] | null | undefined,
|
|
533
|
+
options?: { noEmit?: boolean }
|
|
534
|
+
) {
|
|
535
|
+
controller.collapseAll(nodePath, options);
|
|
521
536
|
}
|
|
522
537
|
|
|
523
538
|
export function filterNodes(searchTextVal: string, searchOptions?: SearchOptions): void {
|
|
@@ -610,16 +625,28 @@
|
|
|
610
625
|
}
|
|
611
626
|
|
|
612
627
|
// Multi-select methods
|
|
613
|
-
export function selectNode(path: string, mode: 'replace' | 'toggle' | 'range' = 'replace') {
|
|
614
|
-
controller.selectNode(path, mode);
|
|
628
|
+
export function selectNode(path: string, mode: 'replace' | 'toggle' | 'range' = 'replace', options?: { silent?: boolean }) {
|
|
629
|
+
controller.selectNode(path, mode, options);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function selectNodes(paths: string[], options?: { silent?: boolean }) {
|
|
633
|
+
controller.selectNodes(paths, options);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export function highlightNode(path: string, mode: 'replace' | 'toggle' | 'range' = 'replace', options?: { silent?: boolean }) {
|
|
637
|
+
controller.highlightNode(path, mode, options);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function highlightNodes(paths: string[], options?: { silent?: boolean }) {
|
|
641
|
+
controller.highlightNodes(paths, options);
|
|
615
642
|
}
|
|
616
643
|
|
|
617
|
-
export function
|
|
618
|
-
controller.
|
|
644
|
+
export function clearHighlight(options?: { silent?: boolean }) {
|
|
645
|
+
controller.clearHighlight(options);
|
|
619
646
|
}
|
|
620
647
|
|
|
621
|
-
export function deselectAll() {
|
|
622
|
-
controller.deselectAll();
|
|
648
|
+
export function deselectAll(options?: { silent?: boolean }) {
|
|
649
|
+
controller.deselectAll(options);
|
|
623
650
|
}
|
|
624
651
|
|
|
625
652
|
export function getSelectedNodes(): LTreeNode<T>[] {
|
|
@@ -656,6 +683,7 @@
|
|
|
656
683
|
| "levelMember"
|
|
657
684
|
| "hasChildrenMember"
|
|
658
685
|
| "isExpandedMember"
|
|
686
|
+
| "isSelectableMember"
|
|
659
687
|
| "isSelectedMember"
|
|
660
688
|
| "isDraggableMember"
|
|
661
689
|
| "getIsDraggableCallback"
|
|
@@ -728,6 +756,7 @@
|
|
|
728
756
|
if (updates.levelMember !== undefined) levelMember = updates.levelMember;
|
|
729
757
|
if (updates.hasChildrenMember !== undefined) hasChildrenMember = updates.hasChildrenMember;
|
|
730
758
|
if (updates.isExpandedMember !== undefined) isExpandedMember = updates.isExpandedMember;
|
|
759
|
+
if (updates.isSelectableMember !== undefined) isSelectableMember = updates.isSelectableMember;
|
|
731
760
|
if (updates.isSelectedMember !== undefined) isSelectedMember = updates.isSelectedMember;
|
|
732
761
|
if (updates.isDraggableMember !== undefined) isDraggableMember = updates.isDraggableMember;
|
|
733
762
|
if (updates.getIsDraggableCallback !== undefined) getIsDraggableCallback = updates.getIsDraggableCallback;
|
|
@@ -11,6 +11,7 @@ declare function $$render<T>(): {
|
|
|
11
11
|
parentPathMember?: string | null | undefined;
|
|
12
12
|
levelMember?: string | null | undefined;
|
|
13
13
|
isExpandedMember?: string | null | undefined;
|
|
14
|
+
isSelectableMember?: string | null | undefined;
|
|
14
15
|
isSelectedMember?: string | null | undefined;
|
|
15
16
|
isDraggableMember?: string | null | undefined;
|
|
16
17
|
getIsDraggableCallback?: (node: LTreeNode<T>) => boolean;
|
|
@@ -133,10 +134,20 @@ declare function $$render<T>(): {
|
|
|
133
134
|
navigationOverrides?: TreeNavigationOverrides<T>;
|
|
134
135
|
};
|
|
135
136
|
exports: {
|
|
136
|
-
expandNodes: (nodePath: string
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
expandNodes: (nodePath: string | string[], options?: {
|
|
138
|
+
exclusive?: boolean;
|
|
139
|
+
noEmit?: boolean;
|
|
140
|
+
}) => Promise<void>;
|
|
141
|
+
collapseNodes: (nodePath: string | string[], options?: {
|
|
142
|
+
noEmit?: boolean;
|
|
143
|
+
}) => Promise<void>;
|
|
144
|
+
expandAll: (nodePath?: string | string[] | null | undefined, options?: {
|
|
145
|
+
exclusive?: boolean;
|
|
146
|
+
noEmit?: boolean;
|
|
147
|
+
}) => void;
|
|
148
|
+
collapseAll: (nodePath?: string | string[] | null | undefined, options?: {
|
|
149
|
+
noEmit?: boolean;
|
|
150
|
+
}) => void;
|
|
140
151
|
filterNodes: (searchTextVal: string, searchOptions?: SearchOptions) => void;
|
|
141
152
|
searchNodes: (searchTextVal: string | null | undefined, searchOptions?: SearchOptions) => LTreeNode<T>[];
|
|
142
153
|
getChildren: (parentPath: string) => LTreeNode<T>[];
|
|
@@ -177,9 +188,24 @@ declare function $$render<T>(): {
|
|
|
177
188
|
setExpandedPaths: (paths: string[]) => void;
|
|
178
189
|
getAllData: () => T[];
|
|
179
190
|
closeContextMenu: () => void;
|
|
180
|
-
selectNode: (path: string, mode?: "replace" | "toggle" | "range"
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
selectNode: (path: string, mode?: "replace" | "toggle" | "range", options?: {
|
|
192
|
+
silent?: boolean;
|
|
193
|
+
}) => void;
|
|
194
|
+
selectNodes: (paths: string[], options?: {
|
|
195
|
+
silent?: boolean;
|
|
196
|
+
}) => void;
|
|
197
|
+
highlightNode: (path: string, mode?: "replace" | "toggle" | "range", options?: {
|
|
198
|
+
silent?: boolean;
|
|
199
|
+
}) => void;
|
|
200
|
+
highlightNodes: (paths: string[], options?: {
|
|
201
|
+
silent?: boolean;
|
|
202
|
+
}) => void;
|
|
203
|
+
clearHighlight: (options?: {
|
|
204
|
+
silent?: boolean;
|
|
205
|
+
}) => void;
|
|
206
|
+
deselectAll: (options?: {
|
|
207
|
+
silent?: boolean;
|
|
208
|
+
}) => void;
|
|
183
209
|
getSelectedNodes: () => LTreeNode<T>[];
|
|
184
210
|
isNodeSelected: (path: string) => boolean;
|
|
185
211
|
scrollToPath: (path: string, options?: {
|
|
@@ -195,6 +221,7 @@ declare function $$render<T>(): {
|
|
|
195
221
|
parentPathMember?: string | null | undefined;
|
|
196
222
|
levelMember?: string | null | undefined;
|
|
197
223
|
isExpandedMember?: string | null | undefined;
|
|
224
|
+
isSelectableMember?: string | null | undefined;
|
|
198
225
|
isSelectedMember?: string | null | undefined;
|
|
199
226
|
isDraggableMember?: string | null | undefined;
|
|
200
227
|
getIsDraggableCallback?: (node: LTreeNode<T>) => boolean;
|
|
@@ -315,7 +342,7 @@ declare function $$render<T>(): {
|
|
|
315
342
|
onTreeKeydown?: (event: KeyboardEvent, controller: TreeController<T>) => boolean | void;
|
|
316
343
|
/** Override individual navigation methods (e.g. for custom ArrowDown/Up behavior) */
|
|
317
344
|
navigationOverrides?: TreeNavigationOverrides<T>;
|
|
318
|
-
}, "treeId" | "treePathSeparator" | "idMember" | "pathMember" | "parentPathMember" | "levelMember" | "hasChildrenMember" | "isExpandedMember" | "isSelectedMember" | "isDraggableMember" | "getIsDraggableCallback" | "isDropAllowedMember" | "displayValueMember" | "getDisplayValueCallback" | "searchValueMember" | "getSearchValueCallback" | "isCollapsibleMember" | "getIsCollapsibleCallback" | "orderMember" | "isSorted" | "sortCallback" | "data" | "focusedNode" | "highlightedPaths" | "selectedPaths" | "expandLevel" | "clickBehavior" | "showCheckboxes" | "checkboxMode" | "beforeCheckboxToggleCallback" | "rangeSelectionMode" | "shouldUseInternalSearchIndex" | "initializeIndexCallback" | "searchText" | "indexerBatchSize" | "indexerTimeout" | "shouldDisplayDebugInformation" | "shouldDisplayContextMenuInDebugMode" | "onNodeClick" | "onHighlightChange" | "onSelectionChange" | "onNodeDragStart" | "onNodeDragOver" | "onNodeDrop" | "beforeDropCallback" | "beforeCopyCallback" | "beforeCutCallback" | "beforePasteCallback" | "getContextMenuItemsCallback" | "virtualScroll" | "virtualRowHeight" | "virtualOverscan" | "virtualContainerHeight" | "dragDropMode" | "dropZoneMode" | "bodyClass" | "expandIconClass" | "collapseIconClass" | "leafIconClass" | "toggleIconMode" | "highlightedNodeClass" | "focusedNodeClass" | "dragOverNodeClass" | "scrollHighlightTimeout" | "scrollHighlightClass" | "contextMenuXOffset" | "contextMenuYOffset" | "accordionExpand">>) => void;
|
|
345
|
+
}, "treeId" | "treePathSeparator" | "idMember" | "pathMember" | "parentPathMember" | "levelMember" | "hasChildrenMember" | "isExpandedMember" | "isSelectableMember" | "isSelectedMember" | "isDraggableMember" | "getIsDraggableCallback" | "isDropAllowedMember" | "displayValueMember" | "getDisplayValueCallback" | "searchValueMember" | "getSearchValueCallback" | "isCollapsibleMember" | "getIsCollapsibleCallback" | "orderMember" | "isSorted" | "sortCallback" | "data" | "focusedNode" | "highlightedPaths" | "selectedPaths" | "expandLevel" | "clickBehavior" | "showCheckboxes" | "checkboxMode" | "beforeCheckboxToggleCallback" | "rangeSelectionMode" | "shouldUseInternalSearchIndex" | "initializeIndexCallback" | "searchText" | "indexerBatchSize" | "indexerTimeout" | "shouldDisplayDebugInformation" | "shouldDisplayContextMenuInDebugMode" | "onNodeClick" | "onHighlightChange" | "onSelectionChange" | "onNodeDragStart" | "onNodeDragOver" | "onNodeDrop" | "beforeDropCallback" | "beforeCopyCallback" | "beforeCutCallback" | "beforePasteCallback" | "getContextMenuItemsCallback" | "virtualScroll" | "virtualRowHeight" | "virtualOverscan" | "virtualContainerHeight" | "dragDropMode" | "dropZoneMode" | "bodyClass" | "expandIconClass" | "collapseIconClass" | "leafIconClass" | "toggleIconMode" | "highlightedNodeClass" | "focusedNodeClass" | "dragOverNodeClass" | "scrollHighlightTimeout" | "scrollHighlightClass" | "contextMenuXOffset" | "contextMenuYOffset" | "accordionExpand">>) => void;
|
|
319
346
|
};
|
|
320
347
|
bindings: "data" | "focusedNode" | "highlightedPaths" | "selectedPaths" | "searchText" | "insertResult" | "isRendering";
|
|
321
348
|
slots: {};
|
|
@@ -327,10 +354,20 @@ declare class __sveltets_Render<T> {
|
|
|
327
354
|
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
328
355
|
bindings(): "data" | "focusedNode" | "highlightedPaths" | "selectedPaths" | "searchText" | "insertResult" | "isRendering";
|
|
329
356
|
exports(): {
|
|
330
|
-
expandNodes: (nodePath: string
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
357
|
+
expandNodes: (nodePath: string | string[], options?: {
|
|
358
|
+
exclusive?: boolean;
|
|
359
|
+
noEmit?: boolean;
|
|
360
|
+
} | undefined) => Promise<void>;
|
|
361
|
+
collapseNodes: (nodePath: string | string[], options?: {
|
|
362
|
+
noEmit?: boolean;
|
|
363
|
+
} | undefined) => Promise<void>;
|
|
364
|
+
expandAll: (nodePath?: string | string[] | null | undefined, options?: {
|
|
365
|
+
exclusive?: boolean;
|
|
366
|
+
noEmit?: boolean;
|
|
367
|
+
} | undefined) => void;
|
|
368
|
+
collapseAll: (nodePath?: string | string[] | null | undefined, options?: {
|
|
369
|
+
noEmit?: boolean;
|
|
370
|
+
} | undefined) => void;
|
|
334
371
|
filterNodes: (searchTextVal: string, searchOptions?: SearchOptions) => void;
|
|
335
372
|
searchNodes: (searchTextVal: string | null | undefined, searchOptions?: SearchOptions) => LTreeNode<T>[];
|
|
336
373
|
getChildren: (parentPath: string) => LTreeNode<T>[];
|
|
@@ -371,9 +408,24 @@ declare class __sveltets_Render<T> {
|
|
|
371
408
|
setExpandedPaths: (paths: string[]) => void;
|
|
372
409
|
getAllData: () => T[];
|
|
373
410
|
closeContextMenu: () => void;
|
|
374
|
-
selectNode: (path: string, mode?: "replace" | "toggle" | "range"
|
|
375
|
-
|
|
376
|
-
|
|
411
|
+
selectNode: (path: string, mode?: "replace" | "toggle" | "range", options?: {
|
|
412
|
+
silent?: boolean;
|
|
413
|
+
} | undefined) => void;
|
|
414
|
+
selectNodes: (paths: string[], options?: {
|
|
415
|
+
silent?: boolean;
|
|
416
|
+
} | undefined) => void;
|
|
417
|
+
highlightNode: (path: string, mode?: "replace" | "toggle" | "range", options?: {
|
|
418
|
+
silent?: boolean;
|
|
419
|
+
} | undefined) => void;
|
|
420
|
+
highlightNodes: (paths: string[], options?: {
|
|
421
|
+
silent?: boolean;
|
|
422
|
+
} | undefined) => void;
|
|
423
|
+
clearHighlight: (options?: {
|
|
424
|
+
silent?: boolean;
|
|
425
|
+
} | undefined) => void;
|
|
426
|
+
deselectAll: (options?: {
|
|
427
|
+
silent?: boolean;
|
|
428
|
+
} | undefined) => void;
|
|
377
429
|
getSelectedNodes: () => LTreeNode<T>[];
|
|
378
430
|
isNodeSelected: (path: string) => boolean;
|
|
379
431
|
scrollToPath: (path: string, options?: {
|
|
@@ -389,6 +441,7 @@ declare class __sveltets_Render<T> {
|
|
|
389
441
|
parentPathMember?: string | null | undefined;
|
|
390
442
|
levelMember?: string | null | undefined;
|
|
391
443
|
isExpandedMember?: string | null | undefined;
|
|
444
|
+
isSelectableMember?: string | null | undefined;
|
|
392
445
|
isSelectedMember?: string | null | undefined;
|
|
393
446
|
isDraggableMember?: string | null | undefined;
|
|
394
447
|
getIsDraggableCallback?: ((node: LTreeNode<T>) => boolean) | undefined;
|
|
@@ -509,7 +562,7 @@ declare class __sveltets_Render<T> {
|
|
|
509
562
|
onTreeKeydown?: ((event: KeyboardEvent, controller: TreeController<T>) => boolean | void) | undefined;
|
|
510
563
|
/** Override individual navigation methods (e.g. for custom ArrowDown/Up behavior) */
|
|
511
564
|
navigationOverrides?: Partial<import("../core/navigation.js").TreeNavigation<T>> | undefined;
|
|
512
|
-
}, "treeId" | "data" | "treePathSeparator" | "idMember" | "pathMember" | "parentPathMember" | "levelMember" | "hasChildrenMember" | "isExpandedMember" | "displayValueMember" | "getDisplayValueCallback" | "searchValueMember" | "getSearchValueCallback" | "orderMember" | "isSorted" | "sortCallback" | "isDraggableMember" | "getIsDraggableCallback" | "isDropAllowedMember" | "isCollapsibleMember" | "getIsCollapsibleCallback" | "shouldDisplayDebugInformation" | "onNodeDrop" | "beforeDropCallback" | "beforeCopyCallback" | "beforeCutCallback" | "beforePasteCallback" | "beforeCheckboxToggleCallback" | "getContextMenuItemsCallback" | "
|
|
565
|
+
}, "treeId" | "data" | "treePathSeparator" | "idMember" | "pathMember" | "parentPathMember" | "levelMember" | "hasChildrenMember" | "isExpandedMember" | "displayValueMember" | "getDisplayValueCallback" | "searchValueMember" | "getSearchValueCallback" | "orderMember" | "isSorted" | "sortCallback" | "isSelectableMember" | "isSelectedMember" | "isDraggableMember" | "getIsDraggableCallback" | "isDropAllowedMember" | "isCollapsibleMember" | "getIsCollapsibleCallback" | "shouldDisplayDebugInformation" | "onNodeDrop" | "beforeDropCallback" | "beforeCopyCallback" | "beforeCutCallback" | "beforePasteCallback" | "beforeCheckboxToggleCallback" | "getContextMenuItemsCallback" | "focusedNode" | "highlightedPaths" | "selectedPaths" | "expandLevel" | "clickBehavior" | "showCheckboxes" | "checkboxMode" | "rangeSelectionMode" | "initializeIndexCallback" | "searchText" | "shouldUseInternalSearchIndex" | "indexerBatchSize" | "indexerTimeout" | "shouldDisplayContextMenuInDebugMode" | "virtualScroll" | "virtualRowHeight" | "virtualOverscan" | "virtualContainerHeight" | "dragDropMode" | "dropZoneMode" | "accordionExpand" | "onNodeClick" | "onNodeDragStart" | "onNodeDragOver" | "onHighlightChange" | "onSelectionChange" | "bodyClass" | "highlightedNodeClass" | "focusedNodeClass" | "dragOverNodeClass" | "expandIconClass" | "collapseIconClass" | "leafIconClass" | "toggleIconMode" | "scrollHighlightTimeout" | "scrollHighlightClass" | "contextMenuXOffset" | "contextMenuYOffset">>) => void;
|
|
513
566
|
};
|
|
514
567
|
}
|
|
515
568
|
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 = "5.0.0-
|
|
3
|
+
export const VERSION = "5.0.0-rc08";
|
|
4
4
|
export const PACKAGE_NAME = "@keenmate/svelte-treeview";
|
|
5
5
|
export const AUTHOR = "KeenMate";
|
|
6
6
|
export const LICENSE = "MIT";
|
|
@@ -56,6 +56,7 @@ export interface TreeControllerProps<T> {
|
|
|
56
56
|
parentPathMember?: string | null | undefined;
|
|
57
57
|
levelMember?: string | null | undefined;
|
|
58
58
|
isExpandedMember?: string | null | undefined;
|
|
59
|
+
isSelectableMember?: string | null | undefined;
|
|
59
60
|
isSelectedMember?: string | null | undefined;
|
|
60
61
|
isDraggableMember?: string | null | undefined;
|
|
61
62
|
getIsDraggableCallback?: (node: LTreeNode<T>) => boolean;
|
|
@@ -297,10 +298,20 @@ export declare class TreeController<T> {
|
|
|
297
298
|
};
|
|
298
299
|
constructor(props: TreeControllerProps<T>);
|
|
299
300
|
handleVirtualScroll: (event: Event) => void;
|
|
300
|
-
expandNodes(nodePath: string
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
expandNodes(nodePath: string | string[], options?: {
|
|
302
|
+
exclusive?: boolean;
|
|
303
|
+
noEmit?: boolean;
|
|
304
|
+
}): Promise<void>;
|
|
305
|
+
collapseNodes(nodePath: string | string[], options?: {
|
|
306
|
+
noEmit?: boolean;
|
|
307
|
+
}): Promise<void>;
|
|
308
|
+
expandAll(nodePath?: string | string[] | null | undefined, options?: {
|
|
309
|
+
exclusive?: boolean;
|
|
310
|
+
noEmit?: boolean;
|
|
311
|
+
}): void;
|
|
312
|
+
collapseAll(nodePath?: string | string[] | null | undefined, options?: {
|
|
313
|
+
noEmit?: boolean;
|
|
314
|
+
}): void;
|
|
304
315
|
filterNodes(searchTextVal: string, searchOptions?: SearchOptions): void;
|
|
305
316
|
searchNodes(searchTextVal: string | null | undefined, searchOptions?: SearchOptions): LTreeNode<T>[];
|
|
306
317
|
getChildren(parentPath: string): LTreeNode<T>[];
|
|
@@ -440,12 +451,21 @@ export declare class TreeController<T> {
|
|
|
440
451
|
/** Get ALL nodes between two paths (inclusive), in depth-first tree order.
|
|
441
452
|
* Includes collapsed/hidden nodes — "logical" range selection. */
|
|
442
453
|
private _getAllNodesBetween;
|
|
443
|
-
/** Highlight a node with the given mode
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
454
|
+
/** Highlight a node with the given mode.
|
|
455
|
+
* Pass `{ silent: true }` to update state without firing `onNodeClick` / `onHighlightChange`
|
|
456
|
+
* (useful when restoring state from URL params or other external sources). */
|
|
457
|
+
highlightNode(path: string, mode?: 'replace' | 'toggle' | 'range', options?: {
|
|
458
|
+
silent?: boolean;
|
|
459
|
+
}): void;
|
|
460
|
+
/** Highlight multiple nodes by paths (replaces current highlights).
|
|
461
|
+
* Pass `{ silent: true }` to skip `onHighlightChange`. */
|
|
462
|
+
highlightNodes(paths: string[], options?: {
|
|
463
|
+
silent?: boolean;
|
|
464
|
+
}): void;
|
|
465
|
+
/** Clear all highlights. Pass `{ silent: true }` to skip `onHighlightChange`. */
|
|
466
|
+
clearHighlight(options?: {
|
|
467
|
+
silent?: boolean;
|
|
468
|
+
}): void;
|
|
449
469
|
/** Get all highlighted nodes */
|
|
450
470
|
getHighlightedNodes(): LTreeNode<T>[];
|
|
451
471
|
/** Check if a specific node path is highlighted */
|
|
@@ -454,12 +474,18 @@ export declare class TreeController<T> {
|
|
|
454
474
|
getSelectedNodes(): LTreeNode<T>[];
|
|
455
475
|
/** Check if a specific node path is selected (checked) */
|
|
456
476
|
isNodeSelected(path: string): boolean;
|
|
457
|
-
/** Clear all checkbox selections */
|
|
458
|
-
deselectAll(
|
|
477
|
+
/** Clear all checkbox selections. Pass `{ silent: true }` to skip `onSelectionChange`. */
|
|
478
|
+
deselectAll(options?: {
|
|
479
|
+
silent?: boolean;
|
|
480
|
+
}): void;
|
|
459
481
|
/** @deprecated Use highlightNode() instead */
|
|
460
|
-
selectNode(path: string, mode?: 'replace' | 'toggle' | 'range'
|
|
482
|
+
selectNode(path: string, mode?: 'replace' | 'toggle' | 'range', options?: {
|
|
483
|
+
silent?: boolean;
|
|
484
|
+
}): void;
|
|
461
485
|
/** @deprecated Use highlightNodes() instead */
|
|
462
|
-
selectNodes(paths: string[]
|
|
486
|
+
selectNodes(paths: string[], options?: {
|
|
487
|
+
silent?: boolean;
|
|
488
|
+
}): void;
|
|
463
489
|
private _onNodeRightClicked;
|
|
464
490
|
private isDropAllowedByMode;
|
|
465
491
|
private calculateDropPosition;
|
|
@@ -249,7 +249,7 @@ export class TreeController {
|
|
|
249
249
|
this.onRenderCompleteHandler = props.onRenderComplete;
|
|
250
250
|
// ── Create LTree ────────────────────────────────────────────────
|
|
251
251
|
// svelte-ignore non_reactive_update
|
|
252
|
-
this.tree = createLTree(props.idMember, props.pathMember, props.parentPathMember, props.levelMember, props.hasChildrenMember, props.isExpandedMember, props.isSelectedMember, props.isDraggableMember, props.getIsDraggableCallback, props.isDropAllowedMember, props.allowedDropPositionsMember, props.displayValueMember, props.getDisplayValueCallback, props.searchValueMember, props.getSearchValueCallback, props.getAllowedDropPositionsCallback, props.isCollapsibleMember, props.getIsCollapsibleCallback, props.orderMember, this.treeId, this.treePathSeparator, props.expandLevel, props.shouldUseInternalSearchIndex, props.initializeIndexCallback, props.indexerBatchSize, props.indexerTimeout, {
|
|
252
|
+
this.tree = createLTree(props.idMember, props.pathMember, props.parentPathMember, props.levelMember, props.hasChildrenMember, props.isExpandedMember, props.isSelectableMember, props.isSelectedMember, props.isDraggableMember, props.getIsDraggableCallback, props.isDropAllowedMember, props.allowedDropPositionsMember, props.displayValueMember, props.getDisplayValueCallback, props.searchValueMember, props.getSearchValueCallback, props.getAllowedDropPositionsCallback, props.isCollapsibleMember, props.getIsCollapsibleCallback, props.orderMember, this.treeId, this.treePathSeparator, props.expandLevel, props.shouldUseInternalSearchIndex, props.initializeIndexCallback, props.indexerBatchSize, props.indexerTimeout, {
|
|
253
253
|
shouldDisplayDebugInformation: props.shouldDisplayDebugInformation,
|
|
254
254
|
isSorted: props.isSorted,
|
|
255
255
|
sortCallback: props.sortCallback
|
|
@@ -352,6 +352,19 @@ export class TreeController {
|
|
|
352
352
|
this.vsMeasuredRowHeight = null;
|
|
353
353
|
this.vsDetectedHeight = null;
|
|
354
354
|
this.insertResult = this.tree.insertArray(this.data);
|
|
355
|
+
// Seed selectedPaths from node.isSelected flags written by insertArray
|
|
356
|
+
if (this.tree.isSelectedMember) {
|
|
357
|
+
const seeded = new Set();
|
|
358
|
+
const walk = (node) => {
|
|
359
|
+
if (node.isSelected)
|
|
360
|
+
seeded.add(node.path);
|
|
361
|
+
for (const key in node.children)
|
|
362
|
+
walk(node.children[key]);
|
|
363
|
+
};
|
|
364
|
+
for (const key in this.tree.root.children)
|
|
365
|
+
walk(this.tree.root.children[key]);
|
|
366
|
+
this.selectedPaths = seeded;
|
|
367
|
+
}
|
|
355
368
|
}
|
|
356
369
|
});
|
|
357
370
|
// Progressive rendering for flat mode
|
|
@@ -497,17 +510,17 @@ export class TreeController {
|
|
|
497
510
|
});
|
|
498
511
|
};
|
|
499
512
|
// ── Public API methods ──────────────────────────────────────────────
|
|
500
|
-
async expandNodes(nodePath) {
|
|
501
|
-
this.tree.expandNodes(nodePath);
|
|
513
|
+
async expandNodes(nodePath, options) {
|
|
514
|
+
this.tree.expandNodes(nodePath, options);
|
|
502
515
|
}
|
|
503
|
-
async collapseNodes(nodePath) {
|
|
504
|
-
this.tree.collapseNodes(nodePath);
|
|
516
|
+
async collapseNodes(nodePath, options) {
|
|
517
|
+
this.tree.collapseNodes(nodePath, options);
|
|
505
518
|
}
|
|
506
|
-
expandAll(nodePath) {
|
|
507
|
-
this.tree?.expandAll(nodePath);
|
|
519
|
+
expandAll(nodePath, options) {
|
|
520
|
+
this.tree?.expandAll(nodePath, options);
|
|
508
521
|
}
|
|
509
|
-
collapseAll(nodePath) {
|
|
510
|
-
this.tree?.collapseAll(nodePath);
|
|
522
|
+
collapseAll(nodePath, options) {
|
|
523
|
+
this.tree?.collapseAll(nodePath, options);
|
|
511
524
|
}
|
|
512
525
|
filterNodes(searchTextVal, searchOptions) {
|
|
513
526
|
this.tree?.filterNodes(searchTextVal, searchOptions);
|
|
@@ -1314,12 +1327,13 @@ export class TreeController {
|
|
|
1314
1327
|
this.onSelectionChangeHandler = updates.onSelectionChange;
|
|
1315
1328
|
}
|
|
1316
1329
|
// ── Internal event handlers ─────────────────────────────────────────
|
|
1317
|
-
async _onNodeClicked(node, modifiers) {
|
|
1330
|
+
async _onNodeClicked(node, modifiers, options) {
|
|
1318
1331
|
if (this.contextMenuVisible) {
|
|
1319
1332
|
this.closeContextMenu();
|
|
1320
1333
|
}
|
|
1321
1334
|
const ctrl = modifiers?.ctrl ?? false;
|
|
1322
1335
|
const shift = modifiers?.shift ?? false;
|
|
1336
|
+
const silent = options?.silent ?? false;
|
|
1323
1337
|
uiLogger.debug(`[highlight] Click on ${node.path}`, { ctrl, shift, lastAnchor: this.lastHighlightedPath, prevCount: this.highlightedPaths.size });
|
|
1324
1338
|
if (ctrl) {
|
|
1325
1339
|
// Toggle this node in/out of highlight
|
|
@@ -1365,11 +1379,17 @@ export class TreeController {
|
|
|
1365
1379
|
}
|
|
1366
1380
|
// Update focus
|
|
1367
1381
|
this._setFocusedNode(node);
|
|
1368
|
-
|
|
1369
|
-
|
|
1382
|
+
if (!silent) {
|
|
1383
|
+
this.onNodeClickHandler?.(node);
|
|
1384
|
+
this._notifyHighlightChanged();
|
|
1385
|
+
}
|
|
1370
1386
|
this.tree.refresh();
|
|
1371
|
-
// Focus the tree container so keyboard navigation works after clicking a node
|
|
1372
|
-
|
|
1387
|
+
// Focus the tree container so keyboard navigation works after clicking a node.
|
|
1388
|
+
// Skip in silent mode — programmatic highlight (e.g. from URL params) shouldn't
|
|
1389
|
+
// steal focus from whatever the user is currently interacting with.
|
|
1390
|
+
if (!silent) {
|
|
1391
|
+
this.containerElement?.focus();
|
|
1392
|
+
}
|
|
1373
1393
|
}
|
|
1374
1394
|
/** Get all descendant paths of a node (depth-first) */
|
|
1375
1395
|
_getDescendantPaths(node) {
|
|
@@ -1635,23 +1655,26 @@ export class TreeController {
|
|
|
1635
1655
|
return result;
|
|
1636
1656
|
}
|
|
1637
1657
|
// ── Public highlight methods (UI selection) ────────────────────────
|
|
1638
|
-
/** Highlight a node with the given mode
|
|
1639
|
-
|
|
1658
|
+
/** Highlight a node with the given mode.
|
|
1659
|
+
* Pass `{ silent: true }` to update state without firing `onNodeClick` / `onHighlightChange`
|
|
1660
|
+
* (useful when restoring state from URL params or other external sources). */
|
|
1661
|
+
highlightNode(path, mode = 'replace', options) {
|
|
1640
1662
|
const node = this.tree.getNodeByPath(path);
|
|
1641
1663
|
if (!node)
|
|
1642
1664
|
return;
|
|
1643
1665
|
if (mode === 'toggle') {
|
|
1644
|
-
this._onNodeClicked(node, { ctrl: true, shift: false });
|
|
1666
|
+
this._onNodeClicked(node, { ctrl: true, shift: false }, options);
|
|
1645
1667
|
}
|
|
1646
1668
|
else if (mode === 'range') {
|
|
1647
|
-
this._onNodeClicked(node, { ctrl: false, shift: true });
|
|
1669
|
+
this._onNodeClicked(node, { ctrl: false, shift: true }, options);
|
|
1648
1670
|
}
|
|
1649
1671
|
else {
|
|
1650
|
-
this._onNodeClicked(node);
|
|
1672
|
+
this._onNodeClicked(node, undefined, options);
|
|
1651
1673
|
}
|
|
1652
1674
|
}
|
|
1653
|
-
/** Highlight multiple nodes by paths (replaces current highlights)
|
|
1654
|
-
|
|
1675
|
+
/** Highlight multiple nodes by paths (replaces current highlights).
|
|
1676
|
+
* Pass `{ silent: true }` to skip `onHighlightChange`. */
|
|
1677
|
+
highlightNodes(paths, options) {
|
|
1655
1678
|
this._clearAllHighlightFlags();
|
|
1656
1679
|
const newPaths = new Set();
|
|
1657
1680
|
let lastNode = null;
|
|
@@ -1669,15 +1692,17 @@ export class TreeController {
|
|
|
1669
1692
|
this._setFocusedNode(lastNode);
|
|
1670
1693
|
this.lastHighlightedPath = lastNode.path;
|
|
1671
1694
|
}
|
|
1672
|
-
|
|
1695
|
+
if (!options?.silent)
|
|
1696
|
+
this._notifyHighlightChanged();
|
|
1673
1697
|
this.tree.refresh();
|
|
1674
1698
|
}
|
|
1675
|
-
/** Clear all highlights */
|
|
1676
|
-
clearHighlight() {
|
|
1699
|
+
/** Clear all highlights. Pass `{ silent: true }` to skip `onHighlightChange`. */
|
|
1700
|
+
clearHighlight(options) {
|
|
1677
1701
|
this._clearAllHighlightFlags();
|
|
1678
1702
|
this.highlightedPaths = new Set();
|
|
1679
1703
|
this.lastHighlightedPath = null;
|
|
1680
|
-
|
|
1704
|
+
if (!options?.silent)
|
|
1705
|
+
this._notifyHighlightChanged();
|
|
1681
1706
|
this.tree.refresh();
|
|
1682
1707
|
}
|
|
1683
1708
|
/** Get all highlighted nodes */
|
|
@@ -1709,20 +1734,21 @@ export class TreeController {
|
|
|
1709
1734
|
isNodeSelected(path) {
|
|
1710
1735
|
return this.selectedPaths.has(path);
|
|
1711
1736
|
}
|
|
1712
|
-
/** Clear all checkbox selections */
|
|
1713
|
-
deselectAll() {
|
|
1737
|
+
/** Clear all checkbox selections. Pass `{ silent: true }` to skip `onSelectionChange`. */
|
|
1738
|
+
deselectAll(options) {
|
|
1714
1739
|
this._clearAllSelectionFlags();
|
|
1715
1740
|
this.selectedPaths = new Set();
|
|
1716
|
-
|
|
1741
|
+
if (!options?.silent)
|
|
1742
|
+
this._notifySelectionChanged();
|
|
1717
1743
|
this.tree.refresh();
|
|
1718
1744
|
}
|
|
1719
1745
|
/** @deprecated Use highlightNode() instead */
|
|
1720
|
-
selectNode(path, mode = 'replace') {
|
|
1721
|
-
this.highlightNode(path, mode);
|
|
1746
|
+
selectNode(path, mode = 'replace', options) {
|
|
1747
|
+
this.highlightNode(path, mode, options);
|
|
1722
1748
|
}
|
|
1723
1749
|
/** @deprecated Use highlightNodes() instead */
|
|
1724
|
-
selectNodes(paths) {
|
|
1725
|
-
this.highlightNodes(paths);
|
|
1750
|
+
selectNodes(paths, options) {
|
|
1751
|
+
this.highlightNodes(paths, options);
|
|
1726
1752
|
}
|
|
1727
1753
|
_onNodeRightClicked(node, event) {
|
|
1728
1754
|
if (!this.hasContextMenuSnippet && !this.getContextMenuItemsHandler) {
|
|
@@ -15,15 +15,10 @@ export interface LTreeNode<T> {
|
|
|
15
15
|
children: Record<string, LTreeNode<T>>;
|
|
16
16
|
hasChildren: boolean;
|
|
17
17
|
data: T | null | undefined;
|
|
18
|
-
useCallback: boolean;
|
|
19
|
-
priority: number | null | undefined;
|
|
20
18
|
isDraggable: boolean;
|
|
21
19
|
isCollapsible: boolean;
|
|
22
20
|
isDropAllowed: boolean;
|
|
23
21
|
allowedDropPositions: DropPosition[] | null | undefined;
|
|
24
|
-
isInsertAllowed: boolean;
|
|
25
|
-
isNestAllowed: boolean;
|
|
26
|
-
isCheckboxVisible: boolean | null | undefined;
|
|
27
22
|
visualState: VisualState;
|
|
28
23
|
isExpanded: boolean;
|
|
29
24
|
isFocused: boolean;
|
|
@@ -15,15 +15,10 @@ export function createLTreeNode(data) {
|
|
|
15
15
|
children: {},
|
|
16
16
|
hasChildren: false,
|
|
17
17
|
data: undefined,
|
|
18
|
-
useCallback: false,
|
|
19
|
-
priority: undefined,
|
|
20
18
|
isDraggable: true,
|
|
21
19
|
isCollapsible: true,
|
|
22
20
|
isDropAllowed: true,
|
|
23
21
|
allowedDropPositions: undefined,
|
|
24
|
-
isInsertAllowed: true,
|
|
25
|
-
isNestAllowed: true,
|
|
26
|
-
isCheckboxVisible: false,
|
|
27
22
|
visualState: VisualState.notSelected,
|
|
28
23
|
isExpanded: false,
|
|
29
24
|
isFocused: false,
|
|
@@ -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, _getIsDraggableCallback?: (node: LTreeNode<T>) => boolean, _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.js').DropPosition[] | null | undefined, _isCollapsibleMember?: string | null | undefined, _getIsCollapsibleCallback?: (node: LTreeNode<T>) => boolean, _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, _isSelectedMember?: string | null | undefined, _isDraggableMember?: string | null | undefined, _getIsDraggableCallback?: (node: LTreeNode<T>) => boolean, _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.js').DropPosition[] | null | undefined, _isCollapsibleMember?: string | null | undefined, _getIsCollapsibleCallback?: (node: LTreeNode<T>) => boolean, _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>;
|
|
@@ -9,12 +9,13 @@ import { perfStart, perfEnd, perfSummary } from '../perf-logger.js';
|
|
|
9
9
|
function getField(item, member) {
|
|
10
10
|
return item[member];
|
|
11
11
|
}
|
|
12
|
-
export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMember, _hasChildrenMember, _isExpandedMember, _isSelectableMember, _isDraggableMember, _getIsDraggableCallback, _isDropAllowedMember, _allowedDropPositionsMember, _displayValueMember, _getDisplayValueCallback, _searchValueMember, _getSearchValueCallback, _getAllowedDropPositionsCallback, _isCollapsibleMember, _getIsCollapsibleCallback, _orderMember, _treeId, _treePathSeparator, _expandLevel, _shouldUseInternalSearchIndex, _initializeIndexCallback, _indexerBatchSize, _indexerTimeout, opts) {
|
|
12
|
+
export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMember, _hasChildrenMember, _isExpandedMember, _isSelectableMember, _isSelectedMember, _isDraggableMember, _getIsDraggableCallback, _isDropAllowedMember, _allowedDropPositionsMember, _displayValueMember, _getDisplayValueCallback, _searchValueMember, _getSearchValueCallback, _getAllowedDropPositionsCallback, _isCollapsibleMember, _getIsCollapsibleCallback, _orderMember, _treeId, _treePathSeparator, _expandLevel, _shouldUseInternalSearchIndex, _initializeIndexCallback, _indexerBatchSize, _indexerTimeout, opts) {
|
|
13
13
|
let shouldCalculateParentPath = isEmptyString(_parentPathMember);
|
|
14
14
|
let shouldCalculateLevel = isEmptyString(_levelMember);
|
|
15
15
|
let shouldCalculateHasChildren = isEmptyString(_hasChildrenMember);
|
|
16
16
|
let shouldCalculateIsExpanded = isEmptyString(_isExpandedMember);
|
|
17
17
|
let shouldCalculateIsSelectable = isEmptyString(_isSelectableMember);
|
|
18
|
+
let shouldCalculateIsSelected = isEmptyString(_isSelectedMember);
|
|
18
19
|
let shouldCalculateIsDraggable = isEmptyString(_isDraggableMember);
|
|
19
20
|
let shouldCalculateIsDropAllowed = isEmptyString(_isDropAllowedMember);
|
|
20
21
|
let shouldCalculateAllowedDropPositions = isEmptyString(_allowedDropPositionsMember);
|
|
@@ -31,7 +32,6 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
31
32
|
if (_shouldUseInternalSearchIndex)
|
|
32
33
|
searchIndex = _initializeIndexCallback ? _initializeIndexCallback() : createSearchIndex();
|
|
33
34
|
let changeTracker = $state(Symbol());
|
|
34
|
-
let size = 0;
|
|
35
35
|
let nodeCount = 0;
|
|
36
36
|
let maxLevel = 0;
|
|
37
37
|
let flatTreeNodes = [];
|
|
@@ -42,6 +42,13 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
42
42
|
let cachedVisibleFlatNodesTracker = null;
|
|
43
43
|
// Async search indexing infrastructure
|
|
44
44
|
let indexer = null;
|
|
45
|
+
// Last search text passed to filterNodes. The FlexSearch index is built
|
|
46
|
+
// asynchronously via the Indexer, so a filter applied while the index is
|
|
47
|
+
// still warming up returns 0 matches and hides the entire tree. We track
|
|
48
|
+
// the active query here and re-apply it from the indexer's onComplete
|
|
49
|
+
// callback so the visible filter eventually reflects the final index.
|
|
50
|
+
let _lastFilterSearchText = null;
|
|
51
|
+
let _lastFilterSearchOptions = undefined;
|
|
45
52
|
// Initialize indexer when search index is available
|
|
46
53
|
if (_shouldUseInternalSearchIndex && searchIndex) {
|
|
47
54
|
indexer = new Indexer(_treeId || 'unknown', searchIndex, shouldCalculateSearchValue, _searchValueMember, _getSearchValueCallback, _indexerBatchSize || 25, // batch size with fallback
|
|
@@ -62,6 +69,7 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
62
69
|
levelMember: _levelMember,
|
|
63
70
|
isExpandedMember: _isExpandedMember,
|
|
64
71
|
isSelectableMember: _isSelectableMember,
|
|
72
|
+
isSelectedMember: _isSelectedMember,
|
|
65
73
|
isDraggableMember: _isDraggableMember,
|
|
66
74
|
getIsDraggableCallback: _getIsDraggableCallback,
|
|
67
75
|
isDropAllowedMember: _isDropAllowedMember,
|
|
@@ -104,7 +112,6 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
104
112
|
const _tracker = changeTracker;
|
|
105
113
|
// Return cached result if changeTracker hasn't changed
|
|
106
114
|
if (_tracker === cachedVisibleFlatNodesTracker && cachedVisibleFlatNodes.length > 0) {
|
|
107
|
-
// console.log(`[visibleFlatNodes] Cache HIT - returning ${cachedVisibleFlatNodes.length} nodes`);
|
|
108
115
|
return cachedVisibleFlatNodes;
|
|
109
116
|
}
|
|
110
117
|
const computeStart = performance.now();
|
|
@@ -192,6 +199,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
192
199
|
node.isExpanded = (node.level ?? 0) <= _expandLevel;
|
|
193
200
|
if (!shouldCalculateIsSelectable)
|
|
194
201
|
node.isSelectable = getField(row, _isSelectableMember);
|
|
202
|
+
if (!shouldCalculateIsSelected)
|
|
203
|
+
node.isSelected = getField(row, _isSelectedMember);
|
|
195
204
|
if (!shouldCalculateIsDraggable)
|
|
196
205
|
node.isDraggable = getField(row, _isDraggableMember);
|
|
197
206
|
if (!shouldCalculateIsCollapsible)
|
|
@@ -270,7 +279,14 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
270
279
|
if (itemsToIndex.length > 0 && indexer) {
|
|
271
280
|
indexer.setCallbacks(undefined, // no progress callback for now
|
|
272
281
|
() => {
|
|
273
|
-
// Completion callback -
|
|
282
|
+
// Completion callback. Re-apply any active filter before
|
|
283
|
+
// emitting — a filterNodes() call that landed while indexing
|
|
284
|
+
// was still in flight saw an incomplete index, so the visible
|
|
285
|
+
// tree may be missing matching paths until we refresh.
|
|
286
|
+
if (!isEmptyString(_lastFilterSearchText)) {
|
|
287
|
+
// isEmptyString guarantees non-empty string here.
|
|
288
|
+
this.filterNodes(_lastFilterSearchText, _lastFilterSearchOptions);
|
|
289
|
+
}
|
|
274
290
|
if (!noEmitChanges) {
|
|
275
291
|
this._emitTreeChanged();
|
|
276
292
|
}
|
|
@@ -318,6 +334,12 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
318
334
|
return null;
|
|
319
335
|
},
|
|
320
336
|
filterNodes(_searchText, _searchOptions) {
|
|
337
|
+
// Remember the active query so the indexer's onComplete callback can
|
|
338
|
+
// re-apply it once the FlexSearch index has finished warming up —
|
|
339
|
+
// otherwise a filter typed while indexing is still in flight hides
|
|
340
|
+
// the entire tree and never recovers.
|
|
341
|
+
_lastFilterSearchText = _searchText;
|
|
342
|
+
_lastFilterSearchOptions = _searchOptions;
|
|
321
343
|
if (isEmptyString(_searchText)) {
|
|
322
344
|
// Clear filter when search is empty
|
|
323
345
|
filteredRoot.children = {};
|
|
@@ -349,7 +371,6 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
349
371
|
perfStart(`[${_treeId}] createFilteredTree`);
|
|
350
372
|
filteredRoot.children = {};
|
|
351
373
|
filteredTree = null;
|
|
352
|
-
// isFiltered = false;
|
|
353
374
|
// 1. Expand all target paths to include their parents
|
|
354
375
|
const allRequiredPaths = new Set();
|
|
355
376
|
targetPaths.forEach((path) => {
|
|
@@ -416,28 +437,89 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
416
437
|
this.isFiltered = false;
|
|
417
438
|
this._emitTreeChanged();
|
|
418
439
|
},
|
|
419
|
-
expandAll(nodePath) {
|
|
440
|
+
expandAll(nodePath, options) {
|
|
420
441
|
perfStart(`[${_treeId}] expandAll`);
|
|
442
|
+
const self = this;
|
|
443
|
+
const exclusive = options?.exclusive ?? false;
|
|
444
|
+
const noEmit = options?.noEmit ?? false;
|
|
421
445
|
function setExpandedRecursive(node, value) {
|
|
422
446
|
node.isExpanded = value;
|
|
423
447
|
for (const key in node.children) {
|
|
424
448
|
setExpandedRecursive(node.children[key], value);
|
|
425
449
|
}
|
|
426
450
|
}
|
|
427
|
-
|
|
451
|
+
const paths = Array.isArray(nodePath)
|
|
452
|
+
? nodePath.filter((p) => !isEmptyString(p))
|
|
453
|
+
: isEmptyString(nodePath)
|
|
454
|
+
? []
|
|
455
|
+
: [nodePath];
|
|
456
|
+
if (paths.length === 0) {
|
|
457
|
+
// Whole-tree expand. exclusive has no meaning (nothing to exclude from).
|
|
428
458
|
setExpandedRecursive(root, true);
|
|
429
459
|
}
|
|
430
460
|
else {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
461
|
+
if (exclusive) {
|
|
462
|
+
// Build spine: every ancestor path of every target must stay expanded
|
|
463
|
+
// so the target subtree is reachable.
|
|
464
|
+
const spineSet = new Set();
|
|
465
|
+
for (const p of paths) {
|
|
466
|
+
const segs = p.split(this.treePathSeparator);
|
|
467
|
+
for (let i = 0; i < segs.length; i++) {
|
|
468
|
+
spineSet.add(segs.slice(0, i + 1).join(this.treePathSeparator));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const isUnderTarget = (path) => {
|
|
472
|
+
for (const tp of paths) {
|
|
473
|
+
if (path === tp || path.startsWith(tp + this.treePathSeparator))
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
return false;
|
|
477
|
+
};
|
|
478
|
+
// Walk currently-expanded subtree; collapse anything not on a spine
|
|
479
|
+
// and not under a target. Only descends into expanded branches.
|
|
480
|
+
const trim = (node) => {
|
|
481
|
+
for (const key in node.children) {
|
|
482
|
+
const child = node.children[key];
|
|
483
|
+
if (!child.isExpanded)
|
|
484
|
+
continue;
|
|
485
|
+
if (spineSet.has(child.path) || isUnderTarget(child.path)) {
|
|
486
|
+
trim(child);
|
|
487
|
+
}
|
|
488
|
+
else if (self.getNodeIsCollapsible(child)) {
|
|
489
|
+
child.isExpanded = false;
|
|
490
|
+
trim(child);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
trim(root);
|
|
495
|
+
}
|
|
496
|
+
// Expand spine + entire subtree under each target.
|
|
497
|
+
for (const p of paths) {
|
|
498
|
+
let node = root;
|
|
499
|
+
const segments = p.split(this.treePathSeparator);
|
|
500
|
+
for (let i = 0; i < segments.length; i++) {
|
|
501
|
+
const segment = segmentPrefix + segments[i];
|
|
502
|
+
if (node && node.children.hasOwnProperty(segment)) {
|
|
503
|
+
node = node.children[segment];
|
|
504
|
+
node.isExpanded = true;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
node = undefined;
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (node)
|
|
512
|
+
setExpandedRecursive(node, true);
|
|
513
|
+
}
|
|
434
514
|
}
|
|
435
|
-
|
|
515
|
+
if (!noEmit)
|
|
516
|
+
this._emitTreeChanged();
|
|
436
517
|
perfEnd(`[${_treeId}] expandAll`);
|
|
437
518
|
},
|
|
438
|
-
collapseAll(nodePath) {
|
|
519
|
+
collapseAll(nodePath, options) {
|
|
439
520
|
perfStart(`[${_treeId}] collapseAll`);
|
|
440
521
|
const self = this;
|
|
522
|
+
const noEmit = options?.noEmit ?? false;
|
|
441
523
|
function collapseRecursive(node) {
|
|
442
524
|
if (node.isExpanded && self.getNodeIsCollapsible(node)) {
|
|
443
525
|
node.isExpanded = false;
|
|
@@ -446,15 +528,23 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
446
528
|
collapseRecursive(node.children[key]);
|
|
447
529
|
}
|
|
448
530
|
}
|
|
449
|
-
|
|
531
|
+
const paths = Array.isArray(nodePath)
|
|
532
|
+
? nodePath.filter((p) => !isEmptyString(p))
|
|
533
|
+
: isEmptyString(nodePath)
|
|
534
|
+
? []
|
|
535
|
+
: [nodePath];
|
|
536
|
+
if (paths.length === 0) {
|
|
450
537
|
collapseRecursive(root);
|
|
451
538
|
}
|
|
452
539
|
else {
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
540
|
+
for (const p of paths) {
|
|
541
|
+
const target = this.getNodeByPath(p);
|
|
542
|
+
if (target)
|
|
543
|
+
collapseRecursive(target);
|
|
544
|
+
}
|
|
456
545
|
}
|
|
457
|
-
|
|
546
|
+
if (!noEmit)
|
|
547
|
+
this._emitTreeChanged();
|
|
458
548
|
perfEnd(`[${_treeId}] collapseAll`);
|
|
459
549
|
},
|
|
460
550
|
insert: function (path, data, noEmitChanges = false) {
|
|
@@ -467,59 +557,97 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
467
557
|
}
|
|
468
558
|
node = node.children[segment];
|
|
469
559
|
}
|
|
470
|
-
// Mark as end of path and store data
|
|
471
|
-
if (node.hasChildren) {
|
|
472
|
-
size++;
|
|
473
|
-
}
|
|
474
560
|
node.hasChildren = true;
|
|
475
561
|
node.data = data;
|
|
476
562
|
if (!noEmitChanges) {
|
|
477
563
|
this._emitTreeChanged();
|
|
478
564
|
}
|
|
479
565
|
},
|
|
480
|
-
expandNodes: function (path,
|
|
566
|
+
expandNodes: function (path, options) {
|
|
481
567
|
perfStart(`[${_treeId}] expandNodes`);
|
|
482
|
-
|
|
568
|
+
const self = this;
|
|
569
|
+
const exclusive = options?.exclusive ?? false;
|
|
570
|
+
const noEmit = options?.noEmit ?? false;
|
|
571
|
+
const rootNode = this.isFiltered ? filteredRoot : root;
|
|
572
|
+
const paths = Array.isArray(path) ? path : [path];
|
|
483
573
|
let hasChanges = false;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
574
|
+
if (exclusive) {
|
|
575
|
+
// Build spine set: union of every ancestor path across all inputs.
|
|
576
|
+
const spineSet = new Set();
|
|
577
|
+
for (const p of paths) {
|
|
578
|
+
const segs = p.split(this.treePathSeparator);
|
|
579
|
+
for (let i = 0; i < segs.length; i++) {
|
|
580
|
+
spineSet.add(segs.slice(0, i + 1).join(this.treePathSeparator));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Walk currently-expanded subtree, collapse anything off-spine.
|
|
584
|
+
const trim = (node) => {
|
|
585
|
+
for (const key in node.children) {
|
|
586
|
+
const child = node.children[key];
|
|
587
|
+
if (!child.isExpanded)
|
|
588
|
+
continue;
|
|
589
|
+
if (spineSet.has(child.path)) {
|
|
590
|
+
trim(child);
|
|
591
|
+
}
|
|
592
|
+
else if (self.getNodeIsCollapsible(child)) {
|
|
593
|
+
child.isExpanded = false;
|
|
594
|
+
hasChanges = true;
|
|
595
|
+
trim(child);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
trim(rootNode);
|
|
600
|
+
}
|
|
601
|
+
for (const p of paths) {
|
|
602
|
+
let node = rootNode;
|
|
603
|
+
const segments = p.split(this.treePathSeparator);
|
|
604
|
+
for (let i = 0; i < segments.length; i++) {
|
|
605
|
+
const segment = segmentPrefix + segments[i];
|
|
606
|
+
if (node && node.children.hasOwnProperty(segment)) {
|
|
607
|
+
node = node.children[segment];
|
|
608
|
+
if (!node.isExpanded) {
|
|
609
|
+
node.isExpanded = true;
|
|
610
|
+
hasChanges = true;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
break;
|
|
493
615
|
}
|
|
494
616
|
}
|
|
495
617
|
}
|
|
496
|
-
|
|
497
|
-
if (!noEmitChanges && hasChanges) {
|
|
618
|
+
if (!noEmit && hasChanges) {
|
|
498
619
|
this._emitTreeChanged();
|
|
499
620
|
}
|
|
500
621
|
perfEnd(`[${_treeId}] expandNodes`);
|
|
501
|
-
return this;
|
|
622
|
+
return this;
|
|
502
623
|
},
|
|
503
|
-
collapseNodes: function (path,
|
|
504
|
-
|
|
624
|
+
collapseNodes: function (path, options) {
|
|
625
|
+
const noEmit = options?.noEmit ?? false;
|
|
626
|
+
const rootNode = this.isFiltered ? filteredRoot : this.root;
|
|
627
|
+
const paths = Array.isArray(path) ? path : [path];
|
|
505
628
|
let hasChanges = false;
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
629
|
+
for (const p of paths) {
|
|
630
|
+
let node = rootNode;
|
|
631
|
+
const segments = p.split(this.treePathSeparator);
|
|
632
|
+
for (let i = 0; i < segments.length; i++) {
|
|
633
|
+
const segment = segmentPrefix + segments[i];
|
|
634
|
+
if (node && node.children.hasOwnProperty(segment)) {
|
|
635
|
+
node = node.children[segment];
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
node = undefined;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (node && node.isExpanded) {
|
|
643
|
+
node.isExpanded = false;
|
|
644
|
+
hasChanges = true;
|
|
511
645
|
}
|
|
512
646
|
}
|
|
513
|
-
|
|
514
|
-
if (node.isExpanded) {
|
|
515
|
-
node.isExpanded = false;
|
|
516
|
-
hasChanges = true;
|
|
517
|
-
}
|
|
518
|
-
// Only emit changes if something actually changed
|
|
519
|
-
if (!noEmitChanges && hasChanges) {
|
|
647
|
+
if (!noEmit && hasChanges) {
|
|
520
648
|
this._emitTreeChanged();
|
|
521
649
|
}
|
|
522
|
-
return this;
|
|
650
|
+
return this;
|
|
523
651
|
},
|
|
524
652
|
// Private helper methods
|
|
525
653
|
getNodeByPath: function (path, _root) {
|
|
@@ -1188,6 +1316,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
1188
1316
|
node.isExpanded = (node.level ?? 0) <= _expandLevel;
|
|
1189
1317
|
if (!shouldCalculateIsSelectable)
|
|
1190
1318
|
node.isSelectable = getField(row, _isSelectableMember);
|
|
1319
|
+
if (!shouldCalculateIsSelected)
|
|
1320
|
+
node.isSelected = getField(row, _isSelectedMember);
|
|
1191
1321
|
if (!shouldCalculateIsDraggable)
|
|
1192
1322
|
node.isDraggable = getField(row, _isDraggableMember);
|
|
1193
1323
|
if (!shouldCalculateIsCollapsible)
|
package/dist/ltree/types.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SearchOptions } from 'flexsearch';
|
|
2
2
|
import type { LTreeNode, DropPosition } from './ltree-node.svelte.js';
|
|
3
3
|
export type { LTreeNode, DropPosition } from './ltree-node.svelte.js';
|
|
4
|
-
export type Tuple<T, U> = [T, U];
|
|
5
4
|
export type DragDropMode = 'none' | 'self' | 'cross' | 'both';
|
|
6
5
|
export type ToggleIconMode = 'rotate' | 'swap';
|
|
7
6
|
export type ClickBehavior = 'select' | 'expand' | 'expand-and-focus';
|
|
@@ -87,10 +86,10 @@ export interface Ltree<T> {
|
|
|
87
86
|
orderMember?: string | null | undefined;
|
|
88
87
|
isSorted: boolean | null | undefined;
|
|
89
88
|
sortCallback?: (items: LTreeNode<T>[]) => LTreeNode<T>[];
|
|
90
|
-
indexingCompleteCallback?: () => void;
|
|
91
89
|
filteredTree: LTreeNode<T>[] | null;
|
|
92
90
|
isFiltered: boolean;
|
|
93
91
|
isSelectableMember: string | null | undefined;
|
|
92
|
+
isSelectedMember: string | null | undefined;
|
|
94
93
|
isDraggableMember: string | null | undefined;
|
|
95
94
|
getIsDraggableCallback?: (node: LTreeNode<T>) => boolean;
|
|
96
95
|
isDropAllowedMember: string | null | undefined;
|
|
@@ -118,12 +117,22 @@ export interface Ltree<T> {
|
|
|
118
117
|
searchNodes(_searchText: string | null | undefined, _searchOptions?: SearchOptions): LTreeNode<T>[];
|
|
119
118
|
createFilteredTree(targetPaths: string[]): void;
|
|
120
119
|
clearFilter(): void;
|
|
121
|
-
expandAll(nodePath?: string | null | undefined
|
|
122
|
-
|
|
120
|
+
expandAll(nodePath?: string | string[] | null | undefined, options?: {
|
|
121
|
+
exclusive?: boolean;
|
|
122
|
+
noEmit?: boolean;
|
|
123
|
+
}): void;
|
|
124
|
+
collapseAll(nodePath?: string | string[] | null | undefined, options?: {
|
|
125
|
+
noEmit?: boolean;
|
|
126
|
+
}): void;
|
|
123
127
|
insert(path: string, data: T, noEmitChanges?: boolean): void;
|
|
124
128
|
getNodeByPath(path: string, _root?: LTreeNode<T> | null | undefined): LTreeNode<T> | null;
|
|
125
|
-
expandNodes(path: string
|
|
126
|
-
|
|
129
|
+
expandNodes(path: string | string[], options?: {
|
|
130
|
+
exclusive?: boolean;
|
|
131
|
+
noEmit?: boolean;
|
|
132
|
+
}): Ltree<T>;
|
|
133
|
+
collapseNodes(path: string | string[], options?: {
|
|
134
|
+
noEmit?: boolean;
|
|
135
|
+
}): Ltree<T>;
|
|
127
136
|
getNodeDisplayValue(node: LTreeNode<T>): string;
|
|
128
137
|
getNodeSearchValue(node: LTreeNode<T>): string;
|
|
129
138
|
_defaultSort(self: Ltree<T>, items: LTreeNode<T>[]): LTreeNode<T>[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keenmate/svelte-treeview",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-rc08",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev --port 17777",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
14
14
|
"test": "vitest",
|
|
15
15
|
"test:run": "vitest run",
|
|
16
|
+
"test:e2e": "playwright test",
|
|
17
|
+
"test:e2e:ui": "playwright test --ui",
|
|
18
|
+
"test:e2e:headed": "playwright test --headed",
|
|
19
|
+
"test:e2e:install": "playwright install chromium",
|
|
16
20
|
"format": "prettier --write .",
|
|
17
21
|
"lint": "prettier --check . && eslint ."
|
|
18
22
|
},
|
|
@@ -45,6 +49,7 @@
|
|
|
45
49
|
"devDependencies": {
|
|
46
50
|
"@eslint/compat": "^1.2.5",
|
|
47
51
|
"@eslint/js": "^9.18.0",
|
|
52
|
+
"@playwright/test": "^1.59.1",
|
|
48
53
|
"@sveltejs/adapter-auto": "^6.0.0",
|
|
49
54
|
"@sveltejs/adapter-static": "^3.0.0",
|
|
50
55
|
"@sveltejs/kit": "^2.22.0",
|