@keenmate/svelte-treeview 5.0.0-rc05 → 5.0.0-rc07
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 +18 -3
- package/README.md +7 -1
- package/dist/components/Tree.svelte +5 -0
- package/dist/components/Tree.svelte.d.ts +5 -2
- package/dist/constants.generated.d.ts +1 -1
- package/dist/constants.generated.js +1 -1
- package/dist/core/TreeController.svelte.d.ts +1 -0
- package/dist/core/TreeController.svelte.js +14 -1
- 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 +28 -9
- package/dist/ltree/types.d.ts +1 -2
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,10 +7,18 @@ 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-
|
|
10
|
+
## [5.0.0-rc07] - 2026-05-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`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.
|
|
14
|
+
- **`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.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- **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.
|
|
18
|
+
|
|
19
|
+
## [5.0.0-rc06] - 2026-03-31
|
|
11
20
|
|
|
12
21
|
### Added
|
|
13
|
-
- **`clickBehavior` prop** (`'select'` | `'expand'` | `'expand-and-focus'`, default `'expand-and-focus'`): Controls what happens on node click. `'expand-and-focus'` selects and expands (previous default). `'select'` selects on single click, expands on double-click. `'expand'` expands only without selecting. Replaces the boolean `shouldToggleOnNodeClick` prop. Matches canvas package's `ClickBehavior` type.
|
|
14
22
|
- **`showCheckboxes` prop**: Renders a checkbox before each selectable node with custom styling and indeterminate support.
|
|
15
23
|
- **`checkboxMode` prop** (`'independent'` | `'cascade'`): Controls whether checking a parent cascades to all descendants. Indeterminate state shown when partial.
|
|
16
24
|
- **`beforeCheckboxToggleCallback` interceptor**: Cancel or override checkbox toggles.
|
|
@@ -27,7 +35,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
27
35
|
- **Svelte proxy equality warning on checkbox toggle**: `_setFocusedNode` now compares by path instead of object identity to avoid `state_proxy_equality_mismatch`.
|
|
28
36
|
|
|
29
37
|
### Breaking
|
|
30
|
-
- **`shouldToggleOnNodeClick` removed**: Replace `shouldToggleOnNodeClick={true}` with `clickBehavior="expand-and-focus"` (default) and `shouldToggleOnNodeClick={false}` with `clickBehavior="select"`.
|
|
31
38
|
- **`selectedNode` → `focusedNode`**: Renamed prop and bindable. The single focused node (last clicked / arrow-keyed to).
|
|
32
39
|
- **`selectedPaths` repurposed**: Now represents checkbox-only data state. For click/highlight multi-select, use `highlightedPaths`.
|
|
33
40
|
- **`highlightedPaths` (new)**: Replaces old `selectedPaths` for Ctrl+click / Shift+click UI highlight.
|
|
@@ -36,6 +43,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
36
43
|
- **`onSelectionChange` repurposed**: Now fires on checkbox selection changes only. Use `onHighlightChange` for highlight changes.
|
|
37
44
|
- **`selectNode()` / `selectNodes()` deprecated**: Use `highlightNode()` / `highlightNodes()` instead.
|
|
38
45
|
|
|
46
|
+
## [5.0.0-rc05] - 2026-03-26
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- **`clickBehavior` prop** (`'select'` | `'expand'` | `'expand-and-focus'`, default `'expand-and-focus'`): Controls what happens on node click. Replaces the boolean `shouldToggleOnNodeClick` prop. Matches canvas package's `ClickBehavior` type.
|
|
50
|
+
|
|
51
|
+
### Breaking
|
|
52
|
+
- **`shouldToggleOnNodeClick` removed**: Replace `shouldToggleOnNodeClick={true}` with `clickBehavior="expand-and-focus"` (default) and `shouldToggleOnNodeClick={false}` with `clickBehavior="select"`.
|
|
53
|
+
|
|
39
54
|
## [5.0.0-rc04] - 2026-03-12
|
|
40
55
|
|
|
41
56
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,13 @@ 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-
|
|
9
|
+
## What's New in v5.0.0-rc07
|
|
10
|
+
|
|
11
|
+
- **`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`.
|
|
12
|
+
- **`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.
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
15
|
+
## What's New in v5.0.0-rc06
|
|
10
16
|
|
|
11
17
|
- **Three-level selection**: `focusedNode` (click/arrows), `highlightedPaths` (Ctrl/Shift+click), `selectedPaths` (checkbox data state). Highlight and checkbox are independent — build a selection, then check/uncheck all at once.
|
|
12
18
|
- **`clickBehavior` prop**: `'select'` | `'expand'` | `'expand-and-focus'` (default). Replaces `shouldToggleOnNodeClick`.
|
|
@@ -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,
|
|
@@ -656,6 +659,7 @@
|
|
|
656
659
|
| "levelMember"
|
|
657
660
|
| "hasChildrenMember"
|
|
658
661
|
| "isExpandedMember"
|
|
662
|
+
| "isSelectableMember"
|
|
659
663
|
| "isSelectedMember"
|
|
660
664
|
| "isDraggableMember"
|
|
661
665
|
| "getIsDraggableCallback"
|
|
@@ -728,6 +732,7 @@
|
|
|
728
732
|
if (updates.levelMember !== undefined) levelMember = updates.levelMember;
|
|
729
733
|
if (updates.hasChildrenMember !== undefined) hasChildrenMember = updates.hasChildrenMember;
|
|
730
734
|
if (updates.isExpandedMember !== undefined) isExpandedMember = updates.isExpandedMember;
|
|
735
|
+
if (updates.isSelectableMember !== undefined) isSelectableMember = updates.isSelectableMember;
|
|
731
736
|
if (updates.isSelectedMember !== undefined) isSelectedMember = updates.isSelectedMember;
|
|
732
737
|
if (updates.isDraggableMember !== undefined) isDraggableMember = updates.isDraggableMember;
|
|
733
738
|
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;
|
|
@@ -195,6 +196,7 @@ declare function $$render<T>(): {
|
|
|
195
196
|
parentPathMember?: string | null | undefined;
|
|
196
197
|
levelMember?: string | null | undefined;
|
|
197
198
|
isExpandedMember?: string | null | undefined;
|
|
199
|
+
isSelectableMember?: string | null | undefined;
|
|
198
200
|
isSelectedMember?: string | null | undefined;
|
|
199
201
|
isDraggableMember?: string | null | undefined;
|
|
200
202
|
getIsDraggableCallback?: (node: LTreeNode<T>) => boolean;
|
|
@@ -315,7 +317,7 @@ declare function $$render<T>(): {
|
|
|
315
317
|
onTreeKeydown?: (event: KeyboardEvent, controller: TreeController<T>) => boolean | void;
|
|
316
318
|
/** Override individual navigation methods (e.g. for custom ArrowDown/Up behavior) */
|
|
317
319
|
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;
|
|
320
|
+
}, "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
321
|
};
|
|
320
322
|
bindings: "data" | "focusedNode" | "highlightedPaths" | "selectedPaths" | "searchText" | "insertResult" | "isRendering";
|
|
321
323
|
slots: {};
|
|
@@ -389,6 +391,7 @@ declare class __sveltets_Render<T> {
|
|
|
389
391
|
parentPathMember?: string | null | undefined;
|
|
390
392
|
levelMember?: string | null | undefined;
|
|
391
393
|
isExpandedMember?: string | null | undefined;
|
|
394
|
+
isSelectableMember?: string | null | undefined;
|
|
392
395
|
isSelectedMember?: string | null | undefined;
|
|
393
396
|
isDraggableMember?: string | null | undefined;
|
|
394
397
|
getIsDraggableCallback?: ((node: LTreeNode<T>) => boolean) | undefined;
|
|
@@ -509,7 +512,7 @@ declare class __sveltets_Render<T> {
|
|
|
509
512
|
onTreeKeydown?: ((event: KeyboardEvent, controller: TreeController<T>) => boolean | void) | undefined;
|
|
510
513
|
/** Override individual navigation methods (e.g. for custom ArrowDown/Up behavior) */
|
|
511
514
|
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" | "
|
|
515
|
+
}, "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
516
|
};
|
|
514
517
|
}
|
|
515
518
|
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-rc07";
|
|
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;
|
|
@@ -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
|
|
@@ -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) => {
|
|
@@ -467,10 +488,6 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
467
488
|
}
|
|
468
489
|
node = node.children[segment];
|
|
469
490
|
}
|
|
470
|
-
// Mark as end of path and store data
|
|
471
|
-
if (node.hasChildren) {
|
|
472
|
-
size++;
|
|
473
|
-
}
|
|
474
491
|
node.hasChildren = true;
|
|
475
492
|
node.data = data;
|
|
476
493
|
if (!noEmitChanges) {
|
|
@@ -1188,6 +1205,8 @@ export function createLTree(_idMember, _pathMember, _parentPathMember, _levelMem
|
|
|
1188
1205
|
node.isExpanded = (node.level ?? 0) <= _expandLevel;
|
|
1189
1206
|
if (!shouldCalculateIsSelectable)
|
|
1190
1207
|
node.isSelectable = getField(row, _isSelectableMember);
|
|
1208
|
+
if (!shouldCalculateIsSelected)
|
|
1209
|
+
node.isSelected = getField(row, _isSelectedMember);
|
|
1191
1210
|
if (!shouldCalculateIsDraggable)
|
|
1192
1211
|
node.isDraggable = getField(row, _isDraggableMember);
|
|
1193
1212
|
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;
|
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-rc07",
|
|
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",
|