@keenmate/svelte-treeview 5.0.0-rc06 → 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 CHANGED
@@ -7,6 +7,15 @@ 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-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
+
10
19
  ## [5.0.0-rc06] - 2026-03-31
11
20
 
12
21
  ### Added
package/README.md CHANGED
@@ -6,6 +6,12 @@ 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-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
+
9
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.
@@ -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" | "isSelectedMember" | "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;
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,4 +1,4 @@
1
- export declare const VERSION = "5.0.0-rc06";
1
+ export declare const VERSION = "5.0.0-rc07";
2
2
  export declare const PACKAGE_NAME = "@keenmate/svelte-treeview";
3
3
  export declare const AUTHOR = "KeenMate";
4
4
  export declare const LICENSE = "MIT";
@@ -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-rc06";
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 - refresh tree when indexing is done
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)
@@ -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-rc06",
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",