@syncfusion/ej2-navigations 34.2.3 → 34.2.5

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.
@@ -3819,6 +3819,7 @@ let Toolbar = class Toolbar extends Component {
3819
3819
  super(options, element);
3820
3820
  this.resizeContext = this.resize.bind(this);
3821
3821
  this.orientationChangeContext = this.orientationChange.bind(this);
3822
+ this.lastObservedWidth = 0;
3822
3823
  /**
3823
3824
  * Contains the keyboard configuration of the Toolbar.
3824
3825
  */
@@ -3850,6 +3851,10 @@ let Toolbar = class Toolbar extends Component {
3850
3851
  }
3851
3852
  });
3852
3853
  this.unwireEvents();
3854
+ this.lastObservedWidth = 0;
3855
+ if (this.resizeObserver && this.element.closest('.e-splitter')) {
3856
+ this.resizeObserver.disconnect();
3857
+ }
3853
3858
  this.tempId.forEach((ele) => {
3854
3859
  if (!isNullOrUndefined(this.element.querySelector(ele))) {
3855
3860
  document.body.appendChild(this.element.querySelector(ele)).style.display = 'none';
@@ -4428,6 +4433,17 @@ let Toolbar = class Toolbar extends Component {
4428
4433
  this.scrollEvent = this.docEvent.bind(this);
4429
4434
  this.renderControl();
4430
4435
  this.wireEvents();
4436
+ if (this.element.closest('.e-splitter')) {
4437
+ this.lastObservedWidth = this.element.offsetWidth;
4438
+ this.resizeObserver = new window.ResizeObserver(() => {
4439
+ if (this.element.offsetWidth === this.lastObservedWidth) {
4440
+ return;
4441
+ }
4442
+ this.lastObservedWidth = this.element.offsetWidth;
4443
+ this.refreshOverflow();
4444
+ });
4445
+ this.resizeObserver.observe(this.element);
4446
+ }
4431
4447
  this.renderComplete();
4432
4448
  if (this.isReact && this.portals && this.portals.length > 0) {
4433
4449
  this.renderReactTemplates(() => {
@@ -10698,55 +10714,28 @@ let TreeView = TreeView_1 = class TreeView extends Component {
10698
10714
  this.isDropIn = false;
10699
10715
  this.OldCheckedData = [];
10700
10716
  this.isHiddenItem = false;
10701
- // ── Virtualization state ──────────────────────────────────────────────────────────────
10702
- /** Flat ordered list of all currently-visible (non-collapsed) tree nodes. */
10717
+ // Virtualization state
10703
10718
  this.virtualFlatNodes = [];
10704
- /** Maps node id → index in virtualFlatNodes for O(1) lookups. */
10705
10719
  this.virtualNodeMap = new Map();
10706
- /** Set of expanded node IDs for O(1) lookup performance during flattening (avoids array indexOf). */
10707
10720
  this.virtualExpandedNodesSet = new Set();
10708
- /** Set of checked node IDs for O(1) lookup performance during flattening. */
10709
10721
  this.virtualCheckedNodesSet = new Set();
10710
- /** Set of indeterminate node IDs — persists across flattenVisibleNodes() rebuilds. */
10711
10722
  this.virtualIndeterminateNodesSet = new Set();
10712
- /** Set of selected node IDs for O(1) lookup performance during flattening. */
10713
10723
  this.virtualSelectedNodesSet = new Set();
10714
- /** Row height in pixels, measured from the first rendered item or set to 40 as a default. */
10715
10724
  this.virtualItemHeight = 40;
10716
- /** Height of the scrollable container in pixels. */
10717
10725
  this.virtualViewportHeight = 0;
10718
- /** scrollTop captured during the previous scroll handler invocation. */
10719
10726
  this.virtualPrevScrollTop = 0;
10720
- /** Index of the first rendered row in the current window. */
10721
10727
  this.virtualStartIndex = 0;
10722
- /** Index one past the last rendered row in the current window. */
10723
10728
  this.virtualEndIndex = 0;
10724
- /** Extra rows rendered above and below the visible window to smooth scrolling. */
10725
10729
  this.virtualOverscanCount = 3;
10726
- /** RequestAnimationFrame handle used for throttling scroll events. */
10727
10730
  this.virtualScrollRAF = null;
10728
- /** The `.e-mask-parent` overlay element shown during fast scroll. */
10729
10731
  this.virtualMaskElement = null;
10730
- /** Bound scroll handler stored for later removeEventListener. */
10731
10732
  this.virtualScrollBound = null;
10732
- /** Skeleton component instances inside the mask overlay. */
10733
10733
  this.skeletonInstances = [];
10734
- /** Set to true whenever the flat node list must be rebuilt on next render. */
10735
10734
  this.virtualTreeDirty = false;
10736
- /** The data-uid of the currently keyboard-focused node in virtual mode (persists across re-renders). */
10737
10735
  this.virtualFocusedId = null;
10738
- /**
10739
- * Set to true by the document-level `keydown` handler when the user presses
10740
- * the Tab key. The browser will auto-scroll the focused element into view
10741
- * immediately after focus, which fires a `scroll` event on the TreeView
10742
- * container. We want to skip the re-render that the virtual scroll handler
10743
- * would otherwise trigger, because the element being focused is already
10744
- * rendered (it is the single `<li tabindex="0">` of the current window) and
10745
- * re-rendering would tear down the DOM element that is currently holding
10746
- * focus. The flag is consumed (reset to `false`) inside `onVirtualScroll`
10747
- * after the auto-scroll has been handled.
10748
- */
10749
10736
  this.virtualSkipNextScrollRender = false;
10737
+ this.filterCheckedNodes = [];
10738
+ this.filterIndeterminateNodes = [];
10750
10739
  }
10751
10740
  /**
10752
10741
  * Get component name.
@@ -11041,17 +11030,12 @@ let TreeView = TreeView_1 = class TreeView extends Component {
11041
11030
  return false;
11042
11031
  }
11043
11032
  }
11033
+ isRemoteDataLoadOnDemand() {
11034
+ const remoteDataSource = (this.fields.dataSource instanceof DataManager) ||
11035
+ (this.fields.child && this.fields.child.dataSource instanceof DataManager ? true : false);
11036
+ return !remoteDataSource && !this.loadOnDemand;
11037
+ }
11044
11038
  renderItems(isSorted) {
11045
- // In virtual mode (with a fixed height) create only the container <ul> —
11046
- // initVirtualization will populate the items directly, avoiding the O(n)
11047
- // ListBase.createList cost.
11048
- // updateCheckedStateFromDS / updateParentCheckState are intentionally skipped here:
11049
- // they call getCheckedNodeDetails which traverses the DOM (no DOM exists yet in
11050
- // virtual mode) and would throw on empty recursion results. The initial checked
11051
- // state is derived from the raw data inside flattenNodeRecursive instead.
11052
- //
11053
- // When enableVirtualization is true but height is NOT provided, we fall through
11054
- // to the normal (non-virtualized) rendering path so the tree is still visible.
11055
11039
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
11056
11040
  this.ulElement = this.createElement('ul', {
11057
11041
  className: PARENTITEM + ' ' + 'e-ul',
@@ -11239,7 +11223,9 @@ let TreeView = TreeView_1 = class TreeView extends Component {
11239
11223
  if (this.checkedNodes.indexOf(childId) !== -1) {
11240
11224
  count++;
11241
11225
  }
11242
- else if (this.element.classList.contains('e-filtering') && OldCheckedNodes.findIndex((e) => e['id'] === childId) !== -1) {
11226
+ else if (this.element.classList.contains('e-filtering') && (this.isRemoteDataLoadOnDemand() ?
11227
+ this.filterCheckedNodes.findIndex((id) => id === childId) !== -1 :
11228
+ OldCheckedNodes.findIndex((e) => e['id'] === childId) !== -1)) {
11243
11229
  count++;
11244
11230
  }
11245
11231
  }
@@ -11552,6 +11538,23 @@ let TreeView = TreeView_1 = class TreeView extends Component {
11552
11538
  }
11553
11539
  });
11554
11540
  let parentNodeChecked = false;
11541
+ if (this.isRemoteDataLoadOnDemand() && this.element.classList.contains('e-filtering') && this.autoCheck) {
11542
+ if (this.filterCheckedNodes.indexOf(dataUid) !== -1) {
11543
+ this.changeState(checkBoxEle, 'check', null, true, true);
11544
+ }
11545
+ else if (this.filterIndeterminateNodes.indexOf(dataUid) !== -1) {
11546
+ this.changeState(checkBoxEle, 'indeterminate', null, true, true);
11547
+ }
11548
+ else {
11549
+ this.changeState(checkBoxEle, 'uncheck', null, true, true);
11550
+ }
11551
+ const parentUL = closest(element, '.' + PARENTITEM);
11552
+ if (!isNullOrUndefined(parentUL)) {
11553
+ const currentParent = closest(parentUL, '.' + LISTITEM);
11554
+ this.ensureParentCheckState(currentParent);
11555
+ }
11556
+ return;
11557
+ }
11555
11558
  if (this.element.classList.contains('e-filtering')) {
11556
11559
  const oldCheckedNodes = new DataManager(this.OldCheckedData).executeLocal(new Query().where('parentID', 'equal', dataUid, true));
11557
11560
  checkedCount = oldCheckedNodes.length;
@@ -11657,7 +11660,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
11657
11660
  }
11658
11661
  });
11659
11662
  }
11660
- getAllChildNodes(data, parentId) {
11663
+ getAllChildNodes(data, parentId, includeAllDescendants) {
11661
11664
  if (isNullOrUndefined(data) || isNullOrUndefined(parentId)) {
11662
11665
  return [];
11663
11666
  }
@@ -11676,15 +11679,53 @@ let TreeView = TreeView_1 = class TreeView extends Component {
11676
11679
  const parentFieldId = this.fields.id;
11677
11680
  const childField = this.fields.child.toString();
11678
11681
  const target = parentId.toString();
11679
- const node = data.find((n) => {
11680
- const idVal = getValue(parentFieldId, n);
11681
- return !isNullOrUndefined(idVal) && idVal.toString() === target;
11682
- });
11682
+ const findNodeInTree = (nodes, id) => {
11683
+ if (!Array.isArray(nodes)) {
11684
+ return undefined;
11685
+ }
11686
+ for (let i = 0; i < nodes.length; i++) {
11687
+ const n = nodes[parseInt(i.toString(), 10)];
11688
+ const idVal = getValue(parentFieldId, n);
11689
+ if (!isNullOrUndefined(idVal) && idVal.toString() === id) {
11690
+ return n;
11691
+ }
11692
+ const subs = getValue(childField, n);
11693
+ const found = findNodeInTree(subs, id);
11694
+ if (found) {
11695
+ return found;
11696
+ }
11697
+ }
11698
+ return undefined;
11699
+ };
11700
+ const node = findNodeInTree(data, target);
11683
11701
  if (!node) {
11684
11702
  return [];
11685
11703
  }
11686
11704
  const children = getValue(childField, node);
11687
- return Array.isArray(children) ? children : [];
11705
+ if (!Array.isArray(children)) {
11706
+ return [];
11707
+ }
11708
+ if (!includeAllDescendants) {
11709
+ return children;
11710
+ }
11711
+ const result = children.slice();
11712
+ const collectNested = (nodes) => {
11713
+ if (!Array.isArray(nodes)) {
11714
+ return;
11715
+ }
11716
+ for (let i = 0; i < nodes.length; i++) {
11717
+ const n = nodes[parseInt(i.toString(), 10)];
11718
+ const grandChildren = getValue(childField, n);
11719
+ if (Array.isArray(grandChildren) && grandChildren.length) {
11720
+ for (let j = 0; j < grandChildren.length; j++) {
11721
+ result.push(grandChildren[parseInt(j.toString(), 10)]);
11722
+ }
11723
+ collectNested(grandChildren);
11724
+ }
11725
+ }
11726
+ };
11727
+ collectNested(children);
11728
+ return result;
11688
11729
  }
11689
11730
  return [];
11690
11731
  }
@@ -12127,7 +12168,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12127
12168
  this.checkDisabledState(id);
12128
12169
  }
12129
12170
  if (this.enableVirtualization) {
12130
- // Clear any indeterminate state — node is now fully checked
12131
12171
  this.virtualIndeterminateNodesSet.delete(id);
12132
12172
  const actualIndex = this.virtualNodeMap.get(id);
12133
12173
  if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
@@ -12148,20 +12188,15 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12148
12188
  const actualIndex = this.virtualNodeMap.get(id);
12149
12189
  if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
12150
12190
  if (isIndeterminate) {
12151
- // Parent is transitioning to indeterminate — preserve children states
12152
- // and mark this node as 'indeterminate' so scroll/collapse re-renders correctly.
12153
12191
  this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].isChecked = 'indeterminate';
12154
- // Also record it in the persistent indeterminate set so flattenVisibleNodes()
12155
- // can restore this state after a full rebuild (e.g. on collapse).
12156
12192
  this.virtualIndeterminateNodesSet.add(id);
12157
12193
  }
12158
12194
  else {
12159
- // Node is being fully unchecked — clear indeterminate tracking too
12160
12195
  this.virtualIndeterminateNodesSet.delete(id);
12161
12196
  this.virtualCheckedNodesSet.delete(id);
12162
12197
  this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].isChecked = false;
12163
12198
  if (this.treeData && this.fields && this.fields.isChecked) {
12164
- setValue(this.fields.isChecked, false, this.treeData[parseInt(id.toString(), 10) - 1]);
12199
+ this.updateField(this.treeData, this.fields, id, this.fields.isChecked, false);
12165
12200
  }
12166
12201
  if (this.autoCheck && this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].hasChildren) {
12167
12202
  this.setVirtualChildCheckState(id, false);
@@ -12207,8 +12242,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12207
12242
  }
12208
12243
  }
12209
12244
  }
12210
- // Collapsed children are not present in virtualFlatNodes, so walk the raw data
12211
- // to keep this.treeData and checkedNodes in sync for hidden descendants too.
12212
12245
  const collapsedChildren = this.getChildNodes(this.treeData, parentId);
12213
12246
  if (collapsedChildren) {
12214
12247
  for (let i = 0; i < collapsedChildren.length; i++) {
@@ -12352,7 +12385,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12352
12385
  }
12353
12386
  this.isLoaded = true;
12354
12387
  this.isNodeDropped = false;
12355
- // Initialise (or re-initialise) virtual mode after data binding has settled
12356
12388
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
12357
12389
  this.initVirtualization();
12358
12390
  }
@@ -12490,9 +12522,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12490
12522
  this.trigger('nodeClicked', eventArgs);
12491
12523
  }
12492
12524
  expandNode(currLi, icon, loaded, isKeyboardNav) {
12493
- // Virtual mode fast-path: rebuild the flat list and re-render the window.
12494
- // Only engage when height is also set; without a fixed height the tree falls
12495
- // back to normal rendering and the standard expand path must be used.
12496
12525
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
12497
12526
  const id = currLi ? currLi.getAttribute('data-uid') : null;
12498
12527
  if (id) {
@@ -12619,9 +12648,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12619
12648
  }
12620
12649
  }
12621
12650
  nodeCollapseAction(currLi, icon, colArgs, isKeyboardNav) {
12622
- // Virtual mode fast-path: rebuild the flat list and re-render the window.
12623
- // Only engage when height is also set; without a fixed height the tree falls
12624
- // back to normal rendering and the standard collapse path must be used.
12625
12651
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
12626
12652
  const id = currLi ? currLi.getAttribute('data-uid') : null;
12627
12653
  if (id) {
@@ -13082,7 +13108,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13082
13108
  li.setAttribute('tabindex', '0');
13083
13109
  EventHandler.add(li, 'blur', this.focusOut, this);
13084
13110
  this.updateIdAttr(focusedNode, li);
13085
- // Track focused id for virtual mode re-render restoration
13086
13111
  if (this.enableVirtualization) {
13087
13112
  this.virtualFocusedId = li.getAttribute('data-uid');
13088
13113
  }
@@ -13098,7 +13123,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13098
13123
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13099
13124
  this.virtualSelectedNodesSet.add(id);
13100
13125
  const actualIndex = this.virtualNodeMap.get(id);
13101
- if (actualIndex && actualIndex > -1) {
13126
+ if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
13102
13127
  this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].selected = true;
13103
13128
  }
13104
13129
  }
@@ -13119,7 +13144,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13119
13144
  const id = liEle.getAttribute('data-uid');
13120
13145
  this.virtualSelectedNodesSet.delete(id);
13121
13146
  const actualIndex = this.virtualNodeMap.get(id);
13122
- if (actualIndex && actualIndex > -1) {
13147
+ if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
13123
13148
  this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].selected = false;
13124
13149
  }
13125
13150
  }
@@ -13212,12 +13237,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13212
13237
  }
13213
13238
  }
13214
13239
  nodeExpandAction(currLi, icon, expandChild, callback, isFromExpandAll, isKeyboardNav) {
13215
- // In virtual mode (with a fixed height) the flat <li> never has a child <ul>
13216
- // in the DOM, so the PARENTITEM check below would always fail and fall into
13217
- // renderChildNodes(), which corrupts the virtual DOM.
13218
- // Delegate directly to expandNode() which has the handleVirtualExpand fast-path.
13219
- // When height is not provided, fall through to normal expand logic below since
13220
- // the tree is rendered with a full nested DOM structure.
13221
13240
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13222
13241
  this.expandNode(currLi, icon, undefined, isKeyboardNav);
13223
13242
  return;
@@ -13334,9 +13353,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13334
13353
  });
13335
13354
  }
13336
13355
  navigateToFocus(isUp) {
13337
- // In virtual mode (with a fixed height) the element itself is the scroll
13338
- // container; use ensureVirtualNodeVisible. When height is not provided,
13339
- // the tree uses normal rendering, so fall through to the standard scroll logic.
13340
13356
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13341
13357
  const focusedLi = this.getFocusedNode();
13342
13358
  if (focusedLi) {
@@ -13431,6 +13447,83 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13431
13447
  }
13432
13448
  }
13433
13449
  }
13450
+ if (this.isRemoteDataLoadOnDemand() && this.element.classList.contains('e-filtering') && this.autoCheck) {
13451
+ const nodeUid = li.getAttribute('data-uid');
13452
+ if (isNullOrUndefined(nodeUid)) {
13453
+ return;
13454
+ }
13455
+ const checkedIndex = this.filterCheckedNodes.indexOf(nodeUid);
13456
+ const indeterminateIndex = this.filterIndeterminateNodes.indexOf(nodeUid);
13457
+ const shouldCheck = !isCheck;
13458
+ if (shouldCheck) {
13459
+ if (checkedIndex === -1) {
13460
+ this.filterCheckedNodes.push(nodeUid);
13461
+ }
13462
+ if (indeterminateIndex !== -1) {
13463
+ this.filterIndeterminateNodes.splice(indeterminateIndex, 1);
13464
+ }
13465
+ }
13466
+ else {
13467
+ if (checkedIndex !== -1) {
13468
+ this.filterCheckedNodes.splice(checkedIndex, 1);
13469
+ }
13470
+ }
13471
+ let parentLi = closest(closest(li, '.' + PARENTITEM), '.' + LISTITEM);
13472
+ while (!isNullOrUndefined(parentLi)) {
13473
+ const parentUid = parentLi.getAttribute('data-uid');
13474
+ if (!isNullOrUndefined(parentUid)) {
13475
+ const parentChildren = this.getAllChildNodes(this.DDTTreeData, parentUid, true);
13476
+ if (parentChildren && parentChildren.length > 0) {
13477
+ let firstChildState;
13478
+ let mismatchFound = false;
13479
+ for (let i = 0; i < parentChildren.length; i++) {
13480
+ const childData = parentChildren[parseInt(i.toString(), 10)];
13481
+ const childId = childData && childData[this.fields.id]
13482
+ ? childData[this.fields.id].toString()
13483
+ : null;
13484
+ if (isNullOrUndefined(childId)) {
13485
+ continue;
13486
+ }
13487
+ const childCheckedState = this.filterIndeterminateNodes.indexOf(childId) !== -1
13488
+ ? null
13489
+ : this.filterCheckedNodes.indexOf(childId) !== -1;
13490
+ if (firstChildState === void 0) {
13491
+ firstChildState = childCheckedState;
13492
+ }
13493
+ else if (childCheckedState !== firstChildState) {
13494
+ mismatchFound = true;
13495
+ break;
13496
+ }
13497
+ }
13498
+ if (mismatchFound || firstChildState === null) {
13499
+ if (this.filterIndeterminateNodes.indexOf(parentUid) === -1) {
13500
+ this.filterIndeterminateNodes.push(parentUid);
13501
+ }
13502
+ const parentCheckedIndex = this.filterCheckedNodes.indexOf(parentUid);
13503
+ if (parentCheckedIndex !== -1) {
13504
+ this.filterCheckedNodes.splice(parentCheckedIndex, 1);
13505
+ }
13506
+ }
13507
+ else {
13508
+ const parentIndeterminateIndex = this.filterIndeterminateNodes.indexOf(parentUid);
13509
+ if (parentIndeterminateIndex !== -1) {
13510
+ this.filterIndeterminateNodes.splice(parentIndeterminateIndex, 1);
13511
+ }
13512
+ const parentCheckedIndex = this.filterCheckedNodes.indexOf(parentUid);
13513
+ if (firstChildState === true) {
13514
+ if (parentCheckedIndex === -1) {
13515
+ this.filterCheckedNodes.push(parentUid);
13516
+ }
13517
+ }
13518
+ else if (parentCheckedIndex !== -1) {
13519
+ this.filterCheckedNodes.splice(parentCheckedIndex, 1);
13520
+ }
13521
+ }
13522
+ }
13523
+ }
13524
+ parentLi = closest(closest(parentLi, '.' + PARENTITEM), '.' + LISTITEM);
13525
+ }
13526
+ }
13434
13527
  this.changeState(checkWrap, isCheck ? 'uncheck' : 'check', e, true);
13435
13528
  this.updateActiveClass(li, isCheck);
13436
13529
  if (this.autoCheck) {
@@ -13766,7 +13859,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13766
13859
  }
13767
13860
  focusNextNode(li, isTowards) {
13768
13861
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13769
- // Determine the id of the node one step in the requested direction
13770
13862
  const currentId = li ? li.getAttribute('data-uid') : null;
13771
13863
  let currentIdx = -1;
13772
13864
  if (!isNullOrUndefined(currentId)) {
@@ -13778,26 +13870,14 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13778
13870
  return;
13779
13871
  }
13780
13872
  const targetId = this.virtualFlatNodes[parseInt(targetIdx.toString(), 10)].id;
13781
- // ── Fix 1: set virtualFocusedId BEFORE calling ensureVirtualNodeVisible ──
13782
- // renderVirtualItems() is called synchronously inside ensureVirtualNodeVisible
13783
- // when a re-render is needed. If virtualFocusedId is still the old value at
13784
- // that point, focus restoration falls back to the first item. Setting it here
13785
- // ensures renderVirtualItems always restores focus to the correct target node.
13786
13873
  this.virtualFocusedId = targetId;
13787
- // ── Fix 2: clear focus classes from ALL nested list items, not just top-level ones ──
13788
- // With hierarchical virtual DOM, the currently focused <li> may be nested inside
13789
- // a child <ul>. Using ':scope > li' only cleans up direct children and leaves
13790
- // stale e-focused / e-node-focus classes on nested nodes across re-renders.
13791
13874
  const allLi = this.element.querySelectorAll('.' + LISTITEM + '.' + FOCUS);
13792
13875
  for (let k = 0; k < allLi.length; k++) {
13793
13876
  allLi[parseInt(k.toString(), 10)].classList.remove(FOCUS);
13794
13877
  allLi[parseInt(k.toString(), 10)].classList.remove(FOCUSED$1);
13795
13878
  allLi[parseInt(k.toString(), 10)].setAttribute('tabindex', '-1');
13796
13879
  }
13797
- // Ensure the target <li> is in the DOM window (may trigger a re-render).
13798
- // virtualFocusedId is already set above so renderVirtualItems restores focus correctly.
13799
13880
  this.ensureVirtualNodeVisible(targetId);
13800
- // Re-query both nodes after the potential re-render so setFocus gets live DOM elements
13801
13881
  const prevNode = currentId ? this.getElement(currentId) : li;
13802
13882
  const nextNode = this.getElement(targetId);
13803
13883
  if (!isNullOrUndefined(nextNode)) {
@@ -13898,18 +13978,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13898
13978
  if (!focusedElement) {
13899
13979
  return;
13900
13980
  }
13901
- // Virtual-mode guard: when the browser moves focus into the TreeView (e.g. via
13902
- // the Tab key) it always lands on the single `<li tabindex="0">`. In that
13903
- // case the element is already the focusable one, FOCUS is already applied
13904
- // and the only thing left to do is the keyboard focus styling. Skip the
13905
- // redundant setAttribute/EventHandler.add work so we never re-render the
13906
- // virtual window as a side effect of focusing the existing `tabindex="0"`
13907
- // item.
13908
- // ── Fix: Use document.activeElement to detect browser-focused node ──
13909
- // After renderVirtualItems() restores focus, the browser-focused element is
13910
- // the authoritative source, not getFocusedNode(). If they don't match, it means
13911
- // renderVirtualItems already correctly set up the element, so we must not
13912
- // override its setup by calling setFocus again.
13913
13981
  const actuallyFocusedElement = document.activeElement;
13914
13982
  const isAlreadyFocusedInVirtualMode = this.enableVirtualization
13915
13983
  && actuallyFocusedElement === focusedElement
@@ -14162,12 +14230,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14162
14230
  else {
14163
14231
  txtEle[ListBase.getDomSetter(this.listBaseOption)] = ListBase.getSanitizedText(newText, this.listBaseOption);
14164
14232
  }
14165
- // ── Virtual mode: sync the flat-node snapshot so re-renders show updated text ──
14166
- // virtualFlatNodes is rebuilt from this.treeData (via flattenVisibleNodes) only on
14167
- // expand/collapse/scroll. Between those events the snapshot is the single source of
14168
- // truth used by createVirtualListItem. setValue() above updates this.editData (which
14169
- // IS the treeData object reference), but virtualFlatNodes[idx].text still holds the
14170
- // old value. Update it here so the next renderVirtualItems() call shows the new text.
14171
14233
  if (this.enableVirtualization) {
14172
14234
  const id = liEle.getAttribute('data-uid');
14173
14235
  const idx = this.virtualNodeMap ? this.virtualNodeMap.get(id) : undefined;
@@ -14611,11 +14673,49 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14611
14673
  this.fields.dataSource = this.treeData;
14612
14674
  }
14613
14675
  this.triggerEvent('nodeDropped', nodeData);
14676
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
14677
+ this.setRootData();
14678
+ this.rebuildVirtualCheckCache();
14679
+ this.virtualFlatNodes = this.flattenVisibleNodes();
14680
+ this.rebuildVirtualNodeMap();
14681
+ const scrollTopSnapshot = this.element.scrollTop;
14682
+ const win = this.getVirtualWindowAt(scrollTopSnapshot);
14683
+ this.virtualStartIndex = win.startIndex;
14684
+ this.virtualEndIndex = win.endIndex;
14685
+ this.virtualSkipNextScrollRender = true;
14686
+ this.renderVirtualItems();
14687
+ }
14614
14688
  }
14615
14689
  appendNode(dropTarget, dragLi, dropLi, e, dragObj, offsetY) {
14616
14690
  const checkWrapper = closest(dropTarget, '.' + CHECKBOXWRAP);
14617
14691
  const collapse = closest(e.target, '.' + COLLAPSIBLE);
14618
14692
  const expand = closest(e.target, '.' + EXPANDABLE);
14693
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
14694
+ const routesToSiblingBranch = !dragLi.classList.contains('e-disable') && !checkWrapper &&
14695
+ ((expand && e.event.offsetY < 5) || (collapse && e.event.offsetX < 3) ||
14696
+ (expand && e.event.offsetY > 19) || (collapse && e.event.offsetX > 19) ||
14697
+ (!expand && !collapse));
14698
+ if (routesToSiblingBranch) {
14699
+ if (dropTarget.nodeName === 'LI') {
14700
+ this.virtualMoveAsSibling(dragLi, dropLi, e, dragObj);
14701
+ }
14702
+ else if (dropTarget.firstElementChild && dropTarget.classList.contains(ROOT)) {
14703
+ if (dropTarget.firstElementChild.nodeName === 'UL') {
14704
+ this.virtualMoveAsSibling(dragLi, dropLi, e, dragObj);
14705
+ }
14706
+ }
14707
+ else if ((dropTarget.classList.contains('e-icon-collapsible')) || (dropTarget.classList.contains('e-icon-expandable'))) {
14708
+ this.virtualMoveAsSibling(dragLi, dropLi, e, dragObj);
14709
+ }
14710
+ else {
14711
+ this.virtualMoveAsChild(dragLi, dropLi, dragObj, offsetY, dropTarget);
14712
+ }
14713
+ }
14714
+ else {
14715
+ this.virtualMoveAsChild(dragLi, dropLi, dragObj, offsetY, dropTarget);
14716
+ }
14717
+ return;
14718
+ }
14619
14719
  if (!dragLi.classList.contains('e-disable') && !checkWrapper && ((expand && e.event.offsetY < 5) || (collapse && e.event.offsetX < 3) || (expand && e.event.offsetY > 19) || (collapse && e.event.offsetX > 19) || (!expand && !collapse))) {
14620
14720
  if (dropTarget.nodeName === 'LI') {
14621
14721
  this.dropAsSiblingNode(dragLi, dropLi, e, dragObj);
@@ -14639,6 +14739,62 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14639
14739
  this.ensureIndeterminate();
14640
14740
  }
14641
14741
  }
14742
+ virtualMoveAsSibling(dragLi, dropLi, e, dragObj) {
14743
+ const dropUl = closest(dropLi, '.' + PARENTITEM);
14744
+ let pre;
14745
+ if (e.target.offsetHeight > 0 && e.event.offsetY > e.target.offsetHeight - 2) {
14746
+ pre = false;
14747
+ }
14748
+ else if (e.event.offsetY < 2) {
14749
+ pre = true;
14750
+ }
14751
+ else if (e.target.classList.contains('e-icon-expandable') || (e.target.classList.contains('e-icon-collapsible'))) {
14752
+ if ((e.event.offsetY < 5) || (e.event.offsetX < 3)) {
14753
+ pre = true;
14754
+ }
14755
+ else if ((e.event.offsetY > 15) || (e.event.offsetX > 17)) {
14756
+ pre = false;
14757
+ }
14758
+ }
14759
+ this.moveData(dragLi, dropLi, dropUl, pre, dragObj);
14760
+ }
14761
+ virtualMoveAsChild(dragLi, dropLi, dragObj, offsetY, dropTarget) {
14762
+ const pos = offsetY;
14763
+ const isSiblingMarker = dropTarget.classList.contains('e-sibling');
14764
+ const refLi = (pos < 7 && !isSiblingMarker) || (isSiblingMarker &&
14765
+ dropLi.lastChild && !dropLi.lastChild.classList.contains('e-sibling'))
14766
+ ? dropLi : null;
14767
+ const dropTargetHt = dropTarget;
14768
+ const isAfter = (dropTargetHt.offsetHeight > 0 && pos > dropTargetHt.offsetHeight - 10);
14769
+ if (refLi) {
14770
+ const dropParentUl = closest(dropLi, '.' + PARENTITEM);
14771
+ this.moveData(dragLi, dropLi, dropParentUl, true, dragObj);
14772
+ }
14773
+ else if (isAfter) {
14774
+ const dropParentUl = closest(dropLi, '.' + PARENTITEM);
14775
+ this.moveData(dragLi, dropLi, dropParentUl, false, dragObj);
14776
+ }
14777
+ else {
14778
+ const dropIcon = select('div.' + EXPANDABLE + ', div.' + COLLAPSIBLE, dropLi);
14779
+ const dropIsCollapsedParent = dropIcon && dropIcon.classList.contains(EXPANDABLE);
14780
+ const dropIsLeaf = isNullOrUndefined(dropIcon);
14781
+ if (this.preventExpand !== true && (dropIsCollapsedParent || dropIsLeaf)) {
14782
+ const dropLiId = dropLi.getAttribute('data-uid');
14783
+ if (dropLiId && this.expandedNodes.indexOf(dropLiId) === -1) {
14784
+ this.expandedNodes.push(dropLiId);
14785
+ }
14786
+ if (this.virtualExpandedNodesSet && dropLiId) {
14787
+ this.virtualExpandedNodesSet.add(dropLiId);
14788
+ }
14789
+ const dropRecord = this.findNodeInTreeData(dropLiId);
14790
+ if (dropRecord && this.fields && this.fields.expanded) {
14791
+ setValue(this.fields.expanded, true, dropRecord);
14792
+ }
14793
+ }
14794
+ const dropUl = select('.' + PARENTITEM, dropLi) || dropLi;
14795
+ this.moveData(dragLi, null, dropUl, true, dragObj);
14796
+ }
14797
+ }
14642
14798
  dropAsSiblingNode(dragLi, dropLi, e, dragObj) {
14643
14799
  const dropUl = closest(dropLi, '.' + PARENTITEM);
14644
14800
  const dragParentUl = closest(dragLi, '.' + PARENTITEM);
@@ -14726,7 +14882,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14726
14882
  delete removedData[0][this.fields.parentID];
14727
14883
  }
14728
14884
  else {
14729
- const currPid = this.isNumberTypeId ? parseFloat(parentId) : parentId;
14885
+ const currPid = this.isNumberTypeId && parentId ? parseFloat(parentId) : parentId;
14730
14886
  setValue(this.fields.parentID, currPid, removedData[0]);
14731
14887
  }
14732
14888
  this.treeData.splice(pos, 0, removedData[0]);
@@ -14868,7 +15024,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14868
15024
  const draggedNodeData = obj ? obj.dragData : null;
14869
15025
  const newParent = dropTarget ? this.parents(dropTarget, '.' + LISTITEM) : null;
14870
15026
  const dragLiParent = obj.dragLi.parentElement;
14871
- let dragParent = obj.dragLi ? closest(dragLiParent, '.' + LISTITEM) : null;
15027
+ let dragParent = obj.dragLi && dragLiParent ? closest(dragLiParent, '.' + LISTITEM) : null;
14872
15028
  let targetParent = null;
14873
15029
  let indexValue = null;
14874
15030
  const iconCss = [DROPNEXT, DROPIN, DROPOUT, NODROP];
@@ -14877,7 +15033,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14877
15033
  const index = node ? closest(node, '.e-list-parent') : null;
14878
15034
  let i = 0;
14879
15035
  let position = null;
14880
- dragParent = (obj.dragLi && dragParent === null) ? closest(dragLiParent, '.' + ROOT) : dragParent;
15036
+ dragParent = (obj.dragLi && dragParent === null) && dragLiParent ? closest(dragLiParent, '.' + ROOT) : dragParent;
14881
15037
  dragParent = (drop === true) ? this.dragParent : dragParent;
14882
15038
  if (cloneEle) {
14883
15039
  while (i < 4) {
@@ -15544,26 +15700,10 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15544
15700
  keyDownHandler(e) {
15545
15701
  if (e.key === 'Tab' || e.keyCode === 9) {
15546
15702
  this.isKeyboardFocus = true;
15547
- // Mark the upcoming scroll event (if any) as Tab-induced so the
15548
- // virtual scroll handler skips its re-render path. The browser
15549
- // calls `Element.scrollIntoView` semantics on focus, which scrolls
15550
- // a `<li tabindex="0">` in the middle of the tree into view and
15551
- // therefore fires `scroll` on the container. Without this guard
15552
- // the scroll handler would tear down the focused `<li>` and move
15553
- // focus elsewhere.
15554
15703
  if (this.enableVirtualization) {
15555
15704
  this.virtualSkipNextScrollRender = true;
15556
15705
  }
15557
15706
  }
15558
- // ── Fix: Also skip scroll render for arrow key navigation ──────────────────
15559
- // Arrow Down/Up calls focusNextNode() which calls ensureVirtualNodeVisible()
15560
- // to scroll the target node into view. Without this guard, the scroll event
15561
- // would trigger onVirtualScroll() which re-renders the window and resets the
15562
- // focus that focusNextNode() just set. This manifests as tabindex resetting
15563
- // to the first item instead of staying on the arrow-navigated target.
15564
- // Arrow Right/Left (expand/collapse) also calls renderVirtualItems() which
15565
- // calls li.focus() in branch 2; that can trigger a scroll on an edge node,
15566
- // so we consume it here to prevent a stale RAF re-render.
15567
15707
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.keyCode === 40 || e.keyCode === 38
15568
15708
  || e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.keyCode === 39 || e.keyCode === 37) {
15569
15709
  if (this.enableVirtualization) {
@@ -15922,10 +16062,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15922
16062
  if (this.dataType === 1) {
15923
16063
  this.groupedData = this.getGroupedData(this.treeData, this.fields.parentID);
15924
16064
  }
15925
- // In virtual mode (with a fixed height), rebuild the flat node list and re-render
15926
- // after deletion so the removed nodes are correctly reflected in the hierarchical
15927
- // virtual DOM. When height is not provided, the tree uses normal rendering and
15928
- // the DOM is already updated by removeNode() above.
15929
16065
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
15930
16066
  this.virtualFlatNodes = this.flattenVisibleNodes();
15931
16067
  this.rebuildVirtualNodeMap();
@@ -15988,16 +16124,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15988
16124
  }
15989
16125
  return parentsID;
15990
16126
  }
15991
- // ═══════════════════════════════════════════════════════════════════════════════
15992
- // VIRTUALIZATION — private helpers
15993
- // ═══════════════════════════════════════════════════════════════════════════════
15994
- /**
15995
- * Parses the `height` property (string like '400px' or number) to a pixel value.
15996
- * Returns 0 if the value cannot be determined.
15997
- *
15998
- * @returns {number} Pixel height.
15999
- * @private
16000
- */
16001
16127
  parseHeightToPixels() {
16002
16128
  const h = this.height;
16003
16129
  if (isNullOrUndefined(h)) {
@@ -16009,13 +16135,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16009
16135
  const parsed = parseFloat(h);
16010
16136
  return isNaN(parsed) ? 0 : parsed;
16011
16137
  }
16012
- /**
16013
- * Measures the height of the first rendered list item so subsequent calculations
16014
- * use real layout values. Falls back to the stored `virtualItemHeight` default (40).
16015
- *
16016
- * @returns {number} Row height in pixels.
16017
- * @private
16018
- */
16019
16138
  measureVirtualItemHeight() {
16020
16139
  const firstLi = select('.' + LISTITEM, this.element);
16021
16140
  if (firstLi && firstLi.offsetHeight > 0) {
@@ -16023,13 +16142,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16023
16142
  }
16024
16143
  return this.virtualItemHeight;
16025
16144
  }
16026
- /**
16027
- * Rebuilds the cached Sets for checked/selected nodes to enable O(1) lookup
16028
- * during the flatten operation instead of O(n) array indexOf calls.
16029
- *
16030
- * @returns {void}
16031
- * @private
16032
- */
16033
16145
  rebuildVirtualCheckCache() {
16034
16146
  this.virtualExpandedNodesSet.clear();
16035
16147
  this.virtualCheckedNodesSet.clear();
@@ -16050,20 +16162,10 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16050
16162
  }
16051
16163
  }
16052
16164
  }
16053
- /**
16054
- * Recursively walks the treeData (or groupedData for flat lists) and builds
16055
- * `virtualFlatNodes` — an ordered list of every node that is currently visible
16056
- * (i.e. its ancestors are all expanded). Uses cached Sets for O(1) state lookups.
16057
- *
16058
- * @returns {VirtualNodeData[]} Ordered flat list of visible nodes.
16059
- * @private
16060
- */
16061
16165
  flattenVisibleNodes() {
16062
- // Build cached Sets for fast lookups
16063
16166
  this.rebuildVirtualCheckCache();
16064
16167
  const result = [];
16065
16168
  if (this.dataType === 1) {
16066
- // Flat data with parentID field — use getChildGroup to find root nodes
16067
16169
  let rootNodes = [];
16068
16170
  if (this.groupedData) {
16069
16171
  rootNodes = this.getChildGroup(this.groupedData, null, true);
@@ -16071,40 +16173,29 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16071
16173
  if (!rootNodes || rootNodes.length === 0) {
16072
16174
  rootNodes = this.rootData;
16073
16175
  }
16074
- // Apply sortOrder to root-level nodes for flat data (same as hierarchical path below)
16075
16176
  rootNodes = this.getSortedData(rootNodes);
16076
16177
  this.flattenNodeRecursive(rootNodes, 1, null, result);
16077
16178
  }
16078
16179
  else {
16079
- // Hierarchical data
16080
16180
  const sorted = this.getSortedData(this.rootData);
16081
16181
  this.flattenNodeRecursive(sorted, 1, null, result);
16082
16182
  }
16083
16183
  return result;
16084
16184
  }
16085
- /**
16086
- * Recursive helper for `flattenVisibleNodes`.
16087
- *
16088
- * @param {Object[]} nodes - Array of raw data objects at this level.
16089
- * @param {number} level - 1-based depth level.
16090
- * @param {string | null} parentId - The parent node id, or null for root.
16091
- * @param {VirtualNodeData[]} result - Accumulator array.
16092
- * @returns {void}
16093
- * @private
16094
- */
16095
16185
  flattenNodeRecursive(nodes, level, parentId, result) {
16096
16186
  if (!nodes || nodes.length === 0) {
16097
16187
  return;
16098
16188
  }
16099
- // Cache field names and settings outside the loop to avoid repeated conversions
16100
- const childFieldName = this.fields.child.toString();
16101
- const expandedFieldName = this.fields.expanded;
16102
- const isCheckedFieldName = this.fields.isChecked;
16103
- const iconCssFieldName = this.fields.iconCss;
16104
- const imageUrlFieldName = this.fields.imageUrl;
16105
- const idFieldName = this.fields.id;
16106
- const textFieldName = this.fields.text;
16107
- const selectedFieldName = this.fields.selected;
16189
+ const levelMapper = level === 1 ? this.fields : this.getChildFields(this.fields, level, 1);
16190
+ const childFieldName = levelMapper.child && levelMapper.child.toString();
16191
+ const expandedFieldName = levelMapper.expanded;
16192
+ const isCheckedFieldName = levelMapper.isChecked;
16193
+ const iconCssFieldName = levelMapper.iconCss;
16194
+ const imageUrlFieldName = levelMapper.imageUrl;
16195
+ const idFieldName = levelMapper.id;
16196
+ const textFieldName = levelMapper.text;
16197
+ const selectedFieldName = levelMapper.selected;
16198
+ const hasChildrenFieldName = levelMapper.hasChildren;
16108
16199
  const needsSorting = this.sortOrder !== 'None';
16109
16200
  const isDataTypeFlat = this.dataType === 1;
16110
16201
  const enableVirtualization = this.enableVirtualization;
@@ -16112,36 +16203,57 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16112
16203
  const raw = nodes[i];
16113
16204
  const id = isNullOrUndefined(raw[idFieldName]) ? '' : raw[idFieldName].toString();
16114
16205
  const text = isNullOrUndefined(raw[textFieldName]) ? '' : raw[textFieldName].toString();
16115
- // Use expandedNodes as the single source of truth.
16116
16206
  const inExpandedNodes = this.virtualExpandedNodesSet.has(id);
16117
- const rawExpanded = !inExpandedNodes && getValue(expandedFieldName, raw) === true;
16207
+ const rawExpanded = !inExpandedNodes && expandedFieldName && getValue(expandedFieldName, raw) === true;
16118
16208
  const expanded = inExpandedNodes || rawExpanded;
16119
16209
  if (rawExpanded && id) {
16120
16210
  this.expandedNodes.push(id);
16121
16211
  this.virtualExpandedNodesSet.add(id);
16122
16212
  }
16123
- // Inline hasChildRecords check to avoid function call (optimization)
16124
16213
  let hasChildren = false;
16125
- if (isDataTypeFlat) {
16214
+ const explicitHasChildren = hasChildrenFieldName ? getValue(hasChildrenFieldName, raw) : null;
16215
+ if (levelMapper.dataSource instanceof DataManager && !isNullOrUndefined(explicitHasChildren) && explicitHasChildren) {
16216
+ hasChildren = true;
16217
+ }
16218
+ else if (isDataTypeFlat) {
16126
16219
  if (this.groupedData) {
16127
16220
  const groupedChild = this.getChildGroup(this.groupedData, id, false);
16128
16221
  hasChildren = !isNullOrUndefined(groupedChild) && groupedChild.length > 0;
16129
16222
  }
16130
16223
  }
16131
16224
  else {
16132
- const childrenObj = getValue(childFieldName, raw);
16225
+ const childrenObj = childFieldName ? getValue(childFieldName, raw) : null;
16133
16226
  hasChildren = !isNullOrUndefined(childrenObj) && Array.isArray(childrenObj) && childrenObj.length > 0;
16134
16227
  }
16135
- // Virtual-mode only: honour fields.isChecked === true on initial render
16136
- if (enableVirtualization && id && isCheckedFieldName && !this.virtualCheckedNodesSet.has(id)) {
16137
- const rawChecked = getValue(isCheckedFieldName, raw);
16138
- if (!isNullOrUndefined(rawChecked) && rawChecked.toString() === 'true') {
16228
+ let parentChecked = null;
16229
+ if (levelMapper.dataSource instanceof DataManager && level > 1 && this.autoCheck && parentId) {
16230
+ if (this.virtualCheckedNodesSet.has(parentId) ||
16231
+ (this.checkedNodes && this.checkedNodes.indexOf(parentId) !== -1)) {
16232
+ parentChecked = true;
16233
+ }
16234
+ else {
16235
+ parentChecked = false;
16236
+ }
16237
+ }
16238
+ const rawChecked = isCheckedFieldName && getValue(isCheckedFieldName, raw);
16239
+ if (enableVirtualization && id && !this.virtualCheckedNodesSet.has(id)) {
16240
+ if ((!isNullOrUndefined(rawChecked) && rawChecked.toString() === 'true') ||
16241
+ (levelMapper.dataSource instanceof DataManager && parentChecked === true)) {
16139
16242
  this.virtualCheckedNodesSet.add(id);
16140
16243
  if (this.checkedNodes && this.checkedNodes.indexOf(id) === -1) {
16141
16244
  this.checkedNodes.push(id);
16142
16245
  }
16143
16246
  }
16144
16247
  }
16248
+ else if (enableVirtualization && id && this.virtualCheckedNodesSet.has(id) && levelMapper.dataSource instanceof DataManager) {
16249
+ if (level > 1 && !this.virtualIndeterminateNodesSet.has(parentId) && this.autoCheck &&
16250
+ ((!isNullOrUndefined(rawChecked) && rawChecked.toString() === 'false') || parentChecked === false)) {
16251
+ this.virtualCheckedNodesSet.delete(id);
16252
+ if (this.checkedNodes && this.checkedNodes.indexOf(id) === -1) {
16253
+ this.checkedNodes.splice(this.checkedNodes.indexOf(id), 1);
16254
+ }
16255
+ }
16256
+ }
16145
16257
  if (this.initialRender && enableVirtualization && id && selectedFieldName && !this.virtualSelectedNodesSet.has(id)) {
16146
16258
  const rawSelected = getValue(selectedFieldName, raw);
16147
16259
  if (!isNullOrUndefined(rawSelected) && rawSelected.toString() === 'true') {
@@ -16151,10 +16263,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16151
16263
  }
16152
16264
  }
16153
16265
  }
16154
- // Use cached Sets for O(1) lookup
16155
16266
  const selected = this.virtualSelectedNodesSet.has(id);
16156
- // Resolve checkbox state: indeterminate takes precedence over the checked set,
16157
- // because a node can be in the indeterminate state without being in checkedNodes.
16158
16267
  const isChecked = (enableVirtualization && this.virtualIndeterminateNodesSet.has(id))
16159
16268
  ? 'indeterminate'
16160
16269
  : this.virtualCheckedNodesSet.has(id);
@@ -16168,22 +16277,19 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16168
16277
  };
16169
16278
  result.push(node);
16170
16279
  if (expanded && hasChildren) {
16171
- // Determine children list
16172
16280
  let children = [];
16173
16281
  if (isDataTypeFlat) {
16174
16282
  if (this.groupedData) {
16175
16283
  children = this.getChildGroup(this.groupedData, id, false);
16176
- // Apply sortOrder to flat-data children (same as hierarchical path)
16177
16284
  if (needsSorting) {
16178
16285
  children = ListBase.getDataSource(children, ListBase.addSorting(this.sortOrder, this.fields.text));
16179
16286
  }
16180
16287
  }
16181
16288
  }
16182
16289
  else {
16183
- const rawChildren = getValue(childFieldName, raw);
16290
+ const rawChildren = childFieldName ? getValue(childFieldName, raw) : null;
16184
16291
  if (!isNullOrUndefined(rawChildren) && Array.isArray(rawChildren)) {
16185
16292
  children = rawChildren;
16186
- // Only sort if needed
16187
16293
  if (needsSorting) {
16188
16294
  children = ListBase.getDataSource(children, ListBase.addSorting(this.sortOrder, this.fields.text));
16189
16295
  }
@@ -16193,35 +16299,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16193
16299
  }
16194
16300
  }
16195
16301
  }
16196
- /**
16197
- * Checks whether a raw datasource node has child records.
16198
- *
16199
- * @param {Object} raw - Raw datasource object.
16200
- * @returns {boolean} True if children exist.
16201
- * @private
16202
- */
16203
- hasChildRecords(raw) {
16204
- if (this.dataType === 1) {
16205
- if (!this.groupedData) {
16206
- return false;
16207
- }
16208
- const rawId = raw[this.fields.id];
16209
- if (isNullOrUndefined(rawId)) {
16210
- return false;
16211
- }
16212
- const groupedChild = this.getChildGroup(this.groupedData, rawId.toString(), false);
16213
- return !isNullOrUndefined(groupedChild) && groupedChild.length > 0;
16214
- }
16215
- const childField = this.fields.child.toString();
16216
- const children = getValue(childField, raw);
16217
- return !isNullOrUndefined(children) && Array.isArray(children) && children.length > 0;
16218
- }
16219
- /**
16220
- * Rebuilds `virtualNodeMap` (id → index) from the current `virtualFlatNodes`.
16221
- *
16222
- * @returns {void}
16223
- * @private
16224
- */
16225
16302
  rebuildVirtualNodeMap() {
16226
16303
  this.virtualNodeMap.clear();
16227
16304
  for (let i = 0; i < this.virtualFlatNodes.length; i++) {
@@ -16254,49 +16331,12 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16254
16331
  const paddingBottom = Math.max(0, (total - end) * itemH);
16255
16332
  return { paddingTop, paddingBottom };
16256
16333
  }
16257
- /**
16258
- * Renders the virtual window into a **nested** `<ul>/<li>` hierarchy identical to
16259
- * the non-virtualized DOM structure.
16260
- *
16261
- * ## Scroll math
16262
- * The flat-index ↔ pixel mapping stays unchanged: every entry in
16263
- * `virtualFlatNodes` represents exactly one `itemHeight` row.
16264
- * `paddingTop = startIndex * itemHeight` and
16265
- * `paddingBottom = (total - endIndex) * itemHeight` keep the scrollbar
16266
- * thumb in the correct position.
16267
- *
16268
- * ## Ancestor injection
16269
- * When `startIndex` points at a child node whose ancestor(s) have already
16270
- * scrolled above the viewport (their flat indices are < startIndex), the
16271
- * ancestors would normally be missing from the DOM, forcing the children to
16272
- * fall back to the root `<ul>` as flat siblings — producing wrong HTML.
16273
- *
16274
- * To fix this, before rendering the window we walk the ancestor chain of the
16275
- * first visible node and collect every ancestor that lies **outside** the
16276
- * window. Those ancestors are injected as real `<li>` containers at the
16277
- * very top of the root `<ul>`. To compensate for their presence, `paddingTop`
16278
- * is reduced by `injectedCount * itemHeight` so the total scroll height
16279
- * remains the same.
16280
- *
16281
- * Once the ancestors are in the DOM they are registered in `renderedParentMap`
16282
- * so the normal window-rendering loop can nest children inside them exactly
16283
- * as it does when both parent and child are in the same window.
16284
- *
16285
- * @returns {void}
16286
- * @private
16287
- */
16288
16334
  renderVirtualItems() {
16289
16335
  if (!this.ulElement) {
16290
16336
  return;
16291
16337
  }
16292
16338
  const ul = this.ulElement;
16293
- // ── Snapshot focused ID BEFORE clearing the DOM ───────────────────────────
16294
- // Removing a focused <li> from the DOM triggers a synchronous `blur` event,
16295
- // which calls focusOut() and sets virtualFocusedId = null. Capturing the id
16296
- // here (before any removeChild) ensures focus is correctly restored to the
16297
- // same node after re-render even when keyboard navigation was in progress.
16298
16339
  const focusedUid = this.virtualFocusedId || null;
16299
- // ── Clear previous render (direct children only) ─────────────────────────
16300
16340
  const existingItems = ul.querySelectorAll(':scope > li');
16301
16341
  for (let i = existingItems.length - 1; i >= 0; i--) {
16302
16342
  ul.removeChild(existingItems[parseInt(i.toString(), 10)]);
@@ -16307,26 +16347,22 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16307
16347
  if (this.virtualStartIndex < this.virtualFlatNodes.length) {
16308
16348
  const firstNode = this.virtualFlatNodes[this.virtualStartIndex];
16309
16349
  if (firstNode.parentId) {
16310
- // Build the ancestor chain from root down to the immediate parent
16311
16350
  const ancestors = [];
16312
16351
  let searchId = firstNode.parentId;
16313
16352
  while (searchId) {
16314
16353
  const ancestorIdx = this.virtualNodeMap.get(searchId);
16315
16354
  if (!isNullOrUndefined(ancestorIdx) && ancestorIdx < this.virtualStartIndex) {
16316
16355
  const ancestor = this.virtualFlatNodes[parseInt(ancestorIdx.toString(), 10)];
16317
- ancestors.unshift(ancestor); // prepend so root comes first
16356
+ ancestors.unshift(ancestor);
16318
16357
  searchId = ancestor.parentId;
16319
16358
  }
16320
16359
  else {
16321
16360
  break;
16322
16361
  }
16323
16362
  }
16324
- // Inject each ancestor as a real <li> into the root <ul>
16325
- // and register it in renderedParentMap for child-nesting below.
16326
16363
  for (let a = 0; a < ancestors.length; a++) {
16327
16364
  const ancestor = ancestors[parseInt(a.toString(), 10)];
16328
16365
  const ancestorLi = this.createVirtualListItem(ancestor, false);
16329
- // Find the correct container: root or a previously injected ancestor's <ul>
16330
16366
  let targetUl = ul;
16331
16367
  if (ancestor.parentId && renderedParentMap.has(ancestor.parentId)) {
16332
16368
  const pe = renderedParentMap.get(ancestor.parentId);
@@ -16345,26 +16381,20 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16345
16381
  }
16346
16382
  }
16347
16383
  }
16348
- // ── Scroll-space padding (adjusted for injected ancestors) ────────────────
16349
16384
  const total = this.virtualFlatNodes.length;
16350
16385
  const start = this.virtualStartIndex;
16351
16386
  const end = this.virtualEndIndex;
16352
16387
  const rawPaddingTop = start * itemH;
16353
- // Subtract the height of injected ancestors so the scroll position is
16354
- // exact: those ancestor rows are now real DOM elements, not hidden space.
16355
16388
  const adjustedPaddingTop = Math.max(0, rawPaddingTop - injectedCount * itemH);
16356
16389
  ul.style.paddingTop = adjustedPaddingTop + 'px';
16357
16390
  ul.style.paddingBottom = Math.max(0, (total - end) * itemH) + 'px';
16358
- // ── Render window items (nested via renderedParentMap) ────────────────────
16359
16391
  const liListBuf = [];
16360
16392
  for (let i = start; i < end; i++) {
16361
16393
  const node = this.virtualFlatNodes[parseInt(i.toString(), 10)];
16362
16394
  const li = this.createVirtualListItem(node, i === start);
16363
- // Find the correct parent container
16364
16395
  let targetUl = ul;
16365
16396
  if (node.parentId && renderedParentMap.has(node.parentId)) {
16366
16397
  const parentEntry = renderedParentMap.get(node.parentId);
16367
- // Lazily create the child <ul> the first time a child needs it
16368
16398
  if (!parentEntry.childUl) {
16369
16399
  parentEntry.childUl = this.createElement('ul', {
16370
16400
  className: PARENTITEM + ' e-ul',
@@ -16376,30 +16406,11 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16376
16406
  }
16377
16407
  targetUl.appendChild(li);
16378
16408
  liListBuf.push(li);
16379
- // Register this node so its children in the same window can nest under it
16380
16409
  if (node.expanded && node.hasChildren) {
16381
16410
  renderedParentMap.set(node.id, { li, childUl: null });
16382
16411
  }
16383
16412
  }
16384
- // ── Restore focus / tabindex ──────────────────────────────────────────────
16385
- // In virtual mode only the li that carries the 'e-active' class should be
16386
- // focusable (tabindex='0'). All other rendered li elements must be set
16387
- // to tabindex='-1' so the tab order remains unambiguous as the window
16388
- // changes during scrolling.
16389
16413
  let focusRestored = false;
16390
- // 1. Prefer the currently active node if it is in the rendered window.
16391
- for (let k = 0; k < liListBuf.length; k++) {
16392
- const li = liListBuf[parseInt(k.toString(), 10)];
16393
- if (li.classList.contains(ACTIVE)) {
16394
- li.setAttribute('tabindex', '0');
16395
- // ── Fix: ensure FOCUS class set on active node too ──
16396
- // This ensures focusIn() guard condition is met: tabindex='0' && FOCUS class
16397
- addClass([li], FOCUS);
16398
- focusRestored = true;
16399
- break;
16400
- }
16401
- }
16402
- // 2. Otherwise fall back to the previously focused node (if it is in window).
16403
16414
  if (!focusRestored && focusedUid) {
16404
16415
  for (let k = 0; k < liListBuf.length; k++) {
16405
16416
  const li = liListBuf[parseInt(k.toString(), 10)];
@@ -16407,22 +16418,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16407
16418
  li.setAttribute('tabindex', '0');
16408
16419
  addClass([li], FOCUS);
16409
16420
  addClass([li], FOCUSED$1);
16410
- // ── Fix: restore actual browser focus so keyboard events keep working ──
16411
- // After renderVirtualItems() replaces all <li> elements the old element
16412
- // is gone from the DOM and document.activeElement falls back to
16413
- // document.body. Setting tabindex + classes is not enough — the new
16414
- // element also needs a programmatic .focus() call so subsequent keyboard
16415
- // events (Arrow keys, Home, End, etc.) are delivered to the TreeView.
16416
- // Re-attach the blur→focusOut handler so virtualFocusedId is cleared
16417
- // correctly when the user tabs away.
16418
- // ── Use preventScroll:true ────────────────────────────────────────────
16419
- // Without preventScroll the browser scrolls the container to bring this
16420
- // node into view during the re-render, changing scrollTop AFTER the
16421
- // correct paddingTop has been applied. This is the root cause of the
16422
- // KBN expand position-jump bug: the render is correct but the browser
16423
- // silently scrolls the viewport, making the parent node appear to jump.
16424
- // ensureVirtualNodeVisible() handles any legitimate scroll-into-view
16425
- // needs before renderVirtualItems() is called, so scroll here is wrong.
16426
16421
  EventHandler.add(li, 'blur', this.focusOut, this);
16427
16422
  li.focus({ preventScroll: true });
16428
16423
  focusRestored = true;
@@ -16430,53 +16425,36 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16430
16425
  }
16431
16426
  }
16432
16427
  }
16433
- // 3. Finally default to the first item in the window.
16434
16428
  if (!focusRestored && liListBuf.length > 0) {
16435
16429
  const firstLi = liListBuf[0];
16436
16430
  firstLi.setAttribute('tabindex', '0');
16437
16431
  }
16438
- // ── Fix: Ensure all other rendered nodes have tabindex="-1" ──
16439
- // This prevents stale tabindex="0" on multiple nodes which breaks Tab order
16440
16432
  for (let k = 0; k < liListBuf.length; k++) {
16441
16433
  const li = liListBuf[parseInt(k.toString(), 10)];
16442
16434
  if (li.getAttribute('tabindex') !== '0') {
16443
16435
  li.setAttribute('tabindex', '-1');
16444
16436
  }
16445
16437
  }
16446
- // Keep liList in sync so keyboard-navigation helpers stay functional.
16447
16438
  this.liList = liListBuf;
16448
16439
  }
16449
- /**
16450
- * Creates a single `<li>` element for the virtual window representing `node`.
16451
- *
16452
- * @param {VirtualNodeData} node - The node data.
16453
- * @param {boolean} _isFirst - True if this is the first rendered item (unused here but kept for potential override).
16454
- * @returns {HTMLElement} The constructed `<li>` element.
16455
- * @private
16456
- */
16457
16440
  createVirtualListItem(node, _isFirst) {
16458
16441
  const li = this.createElement('li', { className: LISTITEM });
16459
- // Level class for CSS indentation
16460
16442
  li.classList.add('e-level-' + node.level);
16461
16443
  li.setAttribute('data-uid', node.id);
16462
16444
  li.setAttribute('aria-level', node.level.toString());
16463
16445
  li.setAttribute('role', 'treeitem');
16464
16446
  li.setAttribute('tabindex', '-1');
16465
- // Full-row overlay (required for fullRowSelect mode)
16466
16447
  if (this.fullRowSelect) {
16467
16448
  const fullRow = this.createElement('div', { className: FULLROW });
16468
16449
  li.appendChild(fullRow);
16469
16450
  }
16470
- // Text/icon wrapper
16471
16451
  const textWrap = this.createElement('div', { className: TEXTWRAP });
16472
- // Expand/collapse icon
16473
16452
  if (node.hasChildren) {
16474
16453
  const iconSpan = this.createElement('div', {
16475
16454
  className: ICON + ' ' + (node.expanded ? COLLAPSIBLE : EXPANDABLE)
16476
16455
  });
16477
16456
  textWrap.appendChild(iconSpan);
16478
16457
  }
16479
- // Checkbox
16480
16458
  if (this.showCheckBox) {
16481
16459
  const checkWrap = createCheckBox(this.createElement, false, {
16482
16460
  checked: node.isChecked === true,
@@ -16490,28 +16468,24 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16490
16468
  }
16491
16469
  textWrap.appendChild(checkWrap);
16492
16470
  }
16493
- // Icon
16494
16471
  if (node.iconCss) {
16495
16472
  const iconEl = this.createElement('span', {
16496
16473
  className: ICON + ' ' + node.iconCss
16497
16474
  });
16498
16475
  textWrap.appendChild(iconEl);
16499
16476
  }
16500
- // Image
16501
16477
  if (node.imageUrl) {
16502
16478
  const img = document.createElement('img');
16503
16479
  img.src = node.imageUrl;
16504
16480
  img.classList.add(IMAGE);
16505
16481
  textWrap.appendChild(img);
16506
16482
  }
16507
- // Text anchor
16508
16483
  const anchor = this.createElement('span', {
16509
16484
  className: 'e-list-text',
16510
16485
  innerHTML: node.text
16511
16486
  });
16512
16487
  textWrap.appendChild(anchor);
16513
16488
  li.appendChild(textWrap);
16514
- // nodeTemplate
16515
16489
  if (!isNullOrUndefined(this.nodeTemplateFn)) {
16516
16490
  const rawData = this.findNodeInTreeData(node.id);
16517
16491
  if (!isNullOrUndefined(rawData)) {
@@ -16519,7 +16493,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16519
16493
  this.renderNodeTemplate(rawData, anchor, node.id);
16520
16494
  }
16521
16495
  }
16522
- // State classes
16523
16496
  if (node.expanded) {
16524
16497
  li.classList.add(EXPANDED);
16525
16498
  li.setAttribute('aria-expanded', 'true');
@@ -16535,80 +16508,43 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16535
16508
  if (node.hasChildren) {
16536
16509
  li.classList.add(CHILD);
16537
16510
  }
16538
- // Wire the focus event so `focusIn()` fires when the user Tabs into this
16539
- // item. The non-virtualized path registers this in `beforeNodeCreate()`,
16540
- // but virtual items never go through that callback, so we must register
16541
- // it here to keep focus styling (e.g. `e-focused`) consistent between
16542
- // virtual and non-virtual modes. `unWireEvents()` already removes this
16543
- // handler during teardown by iterating over the rendered `<li>`s.
16544
16511
  EventHandler.add(li, 'focus', this.focusIn, this);
16545
16512
  return li;
16546
16513
  }
16547
- /**
16548
- * Initialises the virtual-scrolling layout:
16549
- * 1. Adds the `e-virtualization` CSS class and applies a fixed `height` + `overflow-y`.
16550
- * 2. Flattens all visible nodes into `virtualFlatNodes`.
16551
- * 3. Measures item height and viewport height.
16552
- * 4. Computes the initial window and renders it.
16553
- * 5. Wires the scroll event handler.
16554
- *
16555
- * @returns {void}
16556
- * @private
16557
- */
16558
16514
  initVirtualization() {
16559
16515
  addClass([this.element], VIRTUALIZATION);
16560
- // Apply container height
16561
16516
  const px = this.parseHeightToPixels();
16562
16517
  if (px > 0) {
16563
16518
  this.element.style.height = px + 'px';
16564
16519
  this.element.style.overflowY = 'auto';
16565
16520
  }
16566
- // Ensure the inner UL is ready
16567
16521
  if (!this.ulElement) {
16568
16522
  this.ulElement = select('.' + PARENTITEM, this.element);
16569
16523
  }
16570
16524
  if (!this.ulElement) {
16571
16525
  return;
16572
16526
  }
16573
- // Make the UL position-relative for the absolute spacers
16574
16527
  this.ulElement.style.position = 'relative';
16575
- // Rebuild flat list
16576
16528
  this.virtualFlatNodes = this.flattenVisibleNodes();
16577
16529
  this.rebuildVirtualNodeMap();
16578
- // Measure dimensions
16579
16530
  this.virtualViewportHeight = px > 0 ? px : this.element.offsetHeight;
16580
- // Render a single probe item to measure the real item height before the full render
16581
16531
  if (this.virtualFlatNodes.length > 0) {
16582
16532
  const probe = this.createVirtualListItem(this.virtualFlatNodes[0], true);
16583
16533
  this.ulElement.appendChild(probe);
16584
16534
  this.virtualItemHeight = probe.offsetHeight > 0 ? probe.offsetHeight : this.virtualItemHeight;
16585
16535
  this.ulElement.removeChild(probe);
16586
16536
  }
16587
- // Compute window and render all items in the initial viewport
16588
16537
  const win = this.getVirtualWindow();
16589
16538
  this.virtualStartIndex = win.startIndex;
16590
16539
  this.virtualEndIndex = win.endIndex;
16591
16540
  this.renderVirtualItems();
16592
- // Wire scroll
16593
16541
  this.wireVirtualScrollEvents();
16594
16542
  }
16595
- /**
16596
- * Wires the scroll event listener on the TreeView element.
16597
- *
16598
- * @returns {void}
16599
- * @private
16600
- */
16601
16543
  wireVirtualScrollEvents() {
16602
16544
  this.unwireVirtualScrollEvents();
16603
16545
  this.virtualScrollBound = this.onVirtualScroll.bind(this);
16604
16546
  this.element.addEventListener('scroll', this.virtualScrollBound, { passive: true });
16605
16547
  }
16606
- /**
16607
- * Removes the scroll event listener previously wired by `wireVirtualScrollEvents`.
16608
- *
16609
- * @returns {void}
16610
- * @private
16611
- */
16612
16548
  unwireVirtualScrollEvents() {
16613
16549
  if (this.virtualScrollBound) {
16614
16550
  this.element.removeEventListener('scroll', this.virtualScrollBound);
@@ -16619,28 +16555,12 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16619
16555
  this.virtualScrollRAF = null;
16620
16556
  }
16621
16557
  }
16622
- /**
16623
- * Scroll handler for the virtual TreeView. Throttled via `requestAnimationFrame`.
16624
- * Shows the loading mask during fast scrolls, re-renders the window, then hides the mask.
16625
- *
16626
- * @returns {void}
16627
- * @private
16628
- */
16629
16558
  onVirtualScroll() {
16630
16559
  if (this.virtualScrollRAF !== null) {
16631
16560
  cancelAnimationFrame(this.virtualScrollRAF);
16632
16561
  }
16633
- // ── Tab-induced focus guard ────────────────────────────────────────────────
16634
- // When the user Tabs into a `<li tabindex="0">` that is in the middle of
16635
- // the treeview, the browser auto-scrolls the container so the focused
16636
- // element becomes visible. That fires a `scroll` event here, but the
16637
- // virtual window already contains the focused element — re-rendering
16638
- // would destroy the focused `<li>` and break focus. Detect this case
16639
- // via the flag set in `keyDownHandler` and skip the re-render entirely.
16640
16562
  if (this.virtualSkipNextScrollRender) {
16641
16563
  this.virtualSkipNextScrollRender = false;
16642
- // Update prev scrollTop so a subsequent user-driven scroll still
16643
- // calculates the correct delta.
16644
16564
  this.virtualPrevScrollTop = this.element.scrollTop;
16645
16565
  return;
16646
16566
  }
@@ -16657,30 +16577,16 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16657
16577
  this.virtualStartIndex = win.startIndex;
16658
16578
  this.virtualEndIndex = win.endIndex;
16659
16579
  this.renderVirtualItems();
16660
- // Hide the skeleton mask after a short delay so it stays visible long enough
16661
- // for the user to perceive the loading indicator even on short scrolls.
16662
16580
  setTimeout(() => {
16663
16581
  this.hideVirtualMask();
16664
- // Keep the (now hidden) mask's coords fresh for the next show.
16665
16582
  this.updateMaskPosition();
16666
16583
  }, 200);
16667
16584
  });
16668
16585
  }
16669
- /**
16670
- * Creates the skeleton mask overlay used to indicate loading during fast scrolls.
16671
- * Skeleton instances are stored in `skeletonInstances` for later disposal.
16672
- *
16673
- * @returns {void}
16674
- * @private
16675
- */
16676
16586
  createVirtualMask() {
16677
16587
  if (this.virtualMaskElement) {
16678
16588
  return;
16679
16589
  }
16680
- // Use position:fixed so the mask is anchored to the visible viewport (not to the
16681
- // scrolling container). Width/height/left/top are set/refreshed by
16682
- // updateMaskPosition() so the mask always covers the TreeView's visible area
16683
- // regardless of how the page itself is scrolled.
16684
16590
  const rect = this.element.getBoundingClientRect();
16685
16591
  const scrollBarWidth = this.element.offsetWidth - this.element.clientWidth;
16686
16592
  const maskParent = this.createElement('div', {
@@ -16693,20 +16599,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16693
16599
  className: VIRTUAL_MASK_ITEM,
16694
16600
  styles: `display:flex;align-items:center;height:${this.virtualItemHeight}px;padding:0 12px;gap:8px;`
16695
16601
  });
16696
- // Skeleton circle for icon placeholder
16697
- // const iconHolder: HTMLElement = this.createElement('span', {
16698
- // styles: `width:20px;height:20px;`
16699
- // });
16700
- // maskItem.appendChild(iconHolder);
16701
- // const iconSkel: Skeleton = new Skeleton({
16702
- // shape: 'Circle',
16703
- // width: '20px',
16704
- // height: '20px',
16705
- // cssClass: 'e-virtual-skel-icon'
16706
- // });
16707
- // iconSkel.appendTo(iconHolder);
16708
- // this.skeletonInstances.push(iconSkel);
16709
- // Skeleton text bar
16710
16602
  const textHolder = this.createElement('span');
16711
16603
  textHolder.style.flex = '1';
16712
16604
  textHolder.style.height = '14px';
@@ -16724,14 +16616,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16724
16616
  this.virtualMaskElement = maskParent;
16725
16617
  this.element.appendChild(maskParent);
16726
16618
  }
16727
- /**
16728
- * Refreshes the mask position so it always overlays the TreeView's viewport.
16729
- * Called on every show / scroll, since `position:fixed` is relative to the
16730
- * page viewport, not to the TreeView scroll container.
16731
- *
16732
- * @returns {void}
16733
- * @private
16734
- */
16735
16619
  updateMaskPosition() {
16736
16620
  if (!this.virtualMaskElement) {
16737
16621
  return;
@@ -16743,12 +16627,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16743
16627
  this.virtualMaskElement.style.width = (rect.width - scrollBarWidth) + 'px';
16744
16628
  this.virtualMaskElement.style.height = rect.height + 'px';
16745
16629
  }
16746
- /**
16747
- * Destroys all skeleton instances and removes the mask overlay from the DOM.
16748
- *
16749
- * @returns {void}
16750
- * @private
16751
- */
16752
16630
  destroyVirtualMask() {
16753
16631
  for (let i = this.skeletonInstances.length - 1; i >= 0; i--) {
16754
16632
  this.skeletonInstances[parseInt(i.toString(), 10)].destroy();
@@ -16761,52 +16639,26 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16761
16639
  this.virtualMaskElement = null;
16762
16640
  }
16763
16641
  }
16764
- /**
16765
- * Positions and shows the skeleton mask at the current `scrollTop` position.
16766
- *
16767
- * @param {number} _scrollTop - Current scrollTop of the container.
16768
- * @returns {void}
16769
- * @private
16770
- */
16771
16642
  showVirtualMask(_scrollTop) {
16772
16643
  if (!this.virtualMaskElement) {
16773
16644
  this.createVirtualMask();
16774
16645
  }
16775
16646
  if (this.virtualMaskElement) {
16776
- // Refresh the fixed-position coords in case the TreeView was moved/resized
16777
- // since the last time the mask was shown.
16778
16647
  this.updateMaskPosition();
16779
16648
  this.virtualMaskElement.style.display = 'block';
16780
- // Visually hide the real list while the skeleton is shown
16781
16649
  if (this.ulElement) {
16782
16650
  this.ulElement.style.visibility = 'hidden';
16783
16651
  }
16784
16652
  }
16785
16653
  }
16786
- /**
16787
- * Hides the skeleton mask overlay after the virtual window has been rendered.
16788
- *
16789
- * @returns {void}
16790
- * @private
16791
- */
16792
16654
  hideVirtualMask() {
16793
16655
  if (this.virtualMaskElement) {
16794
16656
  this.virtualMaskElement.style.display = 'none';
16795
- // Restore real list visibility
16796
16657
  if (this.ulElement) {
16797
16658
  this.ulElement.style.visibility = '';
16798
16659
  }
16799
16660
  }
16800
16661
  }
16801
- /**
16802
- * Ensures the node identified by `id` is scrolled into view inside the virtual container.
16803
- * If the node falls outside the rendered window, the container's `scrollTop` is adjusted so
16804
- * that the node becomes visible, the window is re-computed and `renderVirtualItems` is called.
16805
- *
16806
- * @param {string} id - The `data-uid` of the target node.
16807
- * @returns {void}
16808
- * @private
16809
- */
16810
16662
  ensureVirtualNodeVisible(id) {
16811
16663
  const idx = this.virtualNodeMap.get(id);
16812
16664
  if (isNullOrUndefined(idx)) {
@@ -16825,25 +16677,13 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16825
16677
  this.element.scrollTop = nodeBottom - vpH;
16826
16678
  }
16827
16679
  else {
16828
- return; // already visible, no re-render needed
16680
+ return;
16829
16681
  }
16830
- // Re-render the new window synchronously so the <li> is in the DOM
16831
16682
  const win = this.getVirtualWindow();
16832
16683
  this.virtualStartIndex = win.startIndex;
16833
16684
  this.virtualEndIndex = win.endIndex;
16834
16685
  this.renderVirtualItems();
16835
16686
  }
16836
- /**
16837
- * Recursively walks the treeData (and any nested `childFieldName` arrays) to
16838
- * find the node whose `idFieldName` matches the given `id` (compared as
16839
- * strings). Used by the virtual-mode expand/collapse handlers so they can
16840
- * locate nodes at any depth — not just the root level — before writing the
16841
- * `expanded` flag back to the datasource.
16842
- *
16843
- * @param {string} id - The id (as a string) of the node to locate.
16844
- * @returns {Object | null} The matching raw datasource node, or null if not found.
16845
- * @private
16846
- */
16847
16687
  findNodeInTreeData(id) {
16848
16688
  if (!this.treeData) {
16849
16689
  return null;
@@ -16871,12 +16711,18 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16871
16711
  };
16872
16712
  return findRecursive(this.treeData);
16873
16713
  }
16714
+ getNodeLevel(id) {
16715
+ if (!this.virtualFlatNodes) {
16716
+ return 1;
16717
+ }
16718
+ for (let i = 0; i < this.virtualFlatNodes.length; i++) {
16719
+ if (this.virtualFlatNodes[parseInt(i.toString(), 10)].id === id) {
16720
+ return this.virtualFlatNodes[parseInt(i.toString(), 10)].level;
16721
+ }
16722
+ }
16723
+ return 1;
16724
+ }
16874
16725
  handleVirtualExpand(id, isKeyboardNav) {
16875
- // ── Cancel any pending scroll RAF ─────────────────────────────────────────
16876
- // A scroll RAF queued before the expand click would run asynchronously after
16877
- // this synchronous expand completes, re-rendering the window from a stale
16878
- // scrollTop and moving the parent node to the wrong position. Cancel it now
16879
- // so the expand render is authoritative.
16880
16726
  if (this.virtualScrollRAF !== null) {
16881
16727
  cancelAnimationFrame(this.virtualScrollRAF);
16882
16728
  this.virtualScrollRAF = null;
@@ -16884,7 +16730,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16884
16730
  if (this.expandedNodes.indexOf(id) === -1) {
16885
16731
  this.expandedNodes.push(id);
16886
16732
  }
16887
- // Also update virtualExpandedNodesSet to keep it in sync
16888
16733
  if (this.virtualExpandedNodesSet) {
16889
16734
  this.virtualExpandedNodesSet.add(id);
16890
16735
  }
@@ -16894,56 +16739,75 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16894
16739
  setValue(this.fields.expanded, true, acutalRootData);
16895
16740
  }
16896
16741
  }
16897
- // ── Fix: Pin focused ID to the expanding node for keyboard navigation ──
16898
- // When keyboard-triggered, the expanded node IS definitively the focused node
16899
- // (`id` comes from currLi.getAttribute('data-uid') in expandNode, where currLi
16900
- // is getFocusedNode()). Set unconditionally — the old conditional guard
16901
- // `!this.virtualFocusedId` could leave a stale or null value if a previous
16902
- // RAF render (from arrow-key scroll) hadn't yet completed, causing branch 3
16903
- // (first item) to fire in renderVirtualItems() instead of branch 2.
16904
- // For mouse clicks (isKeyboardNav=false), clear virtualFocusedId so that
16905
- // renderVirtualItems() Branch 2 does NOT apply the FOCUS/FOCUSED classes —
16906
- // mouse-based expand/collapse should never add the e-focused class.
16907
16742
  if (isKeyboardNav) {
16908
16743
  this.virtualFocusedId = id;
16909
16744
  }
16910
16745
  else {
16911
16746
  this.virtualFocusedId = null;
16912
16747
  }
16748
+ if (this.fields.dataSource instanceof DataManager && !this.isOffline) {
16749
+ const parentNode = this.findNodeInTreeData(id);
16750
+ if (parentNode && !isNullOrUndefined(parentNode)) {
16751
+ const childFieldName = this.fields.child.toString();
16752
+ const childrenObj = getValue(childFieldName, parentNode);
16753
+ const childrenNotLoaded = isNullOrUndefined(childrenObj) || !Array.isArray(childrenObj) ||
16754
+ childrenObj.length === 0;
16755
+ if (childrenNotLoaded) {
16756
+ this.loadVirtualChildData(id, parentNode, () => {
16757
+ this.completeVirtualExpand(id, isKeyboardNav);
16758
+ });
16759
+ return;
16760
+ }
16761
+ }
16762
+ }
16763
+ this.completeVirtualExpand(id, isKeyboardNav);
16764
+ }
16765
+ loadVirtualChildData(parentId, parentNode, callback) {
16766
+ const level = this.getNodeLevel(parentId);
16767
+ const mapper = this.getChildFields(this.fields, level + 1, 1);
16768
+ if (isNullOrUndefined(mapper) || isNullOrUndefined(mapper.dataSource)) {
16769
+ if (callback) {
16770
+ callback();
16771
+ }
16772
+ return;
16773
+ }
16774
+ mapper.dataSource.executeQuery(this.getQuery(mapper, parentId)).then((e) => {
16775
+ const childItems = e.result;
16776
+ if (!isNullOrUndefined(childItems) && childItems.length > 0) {
16777
+ const childFieldName = this.fields.child.toString();
16778
+ setValue(childFieldName, childItems, parentNode);
16779
+ if (this.dataType === 2) {
16780
+ const nodeInTree = this.findNodeInTreeData(parentId);
16781
+ if (nodeInTree) {
16782
+ setValue(childFieldName, childItems, nodeInTree);
16783
+ }
16784
+ }
16785
+ }
16786
+ if (callback) {
16787
+ callback();
16788
+ }
16789
+ }).catch((e) => {
16790
+ this.trigger('actionFailure', { error: e });
16791
+ if (callback) {
16792
+ callback();
16793
+ }
16794
+ });
16795
+ }
16796
+ completeVirtualExpand(id, isKeyboardNav) {
16913
16797
  this.virtualFlatNodes = this.flattenVisibleNodes();
16914
16798
  this.rebuildVirtualNodeMap();
16915
- // ── Snapshot scrollTop before computing the window ────────────────────────
16916
- // Read scrollTop once here and pass it into getVirtualWindow so that any
16917
- // browser scroll-anchoring adjustment that happens when the padding values
16918
- // change cannot affect which window we render.
16919
16799
  const scrollTopSnapshot = this.element.scrollTop;
16920
16800
  const win = this.getVirtualWindowAt(scrollTopSnapshot);
16921
16801
  this.virtualStartIndex = win.startIndex;
16922
16802
  this.virtualEndIndex = win.endIndex;
16923
- // ── Block ALL scroll-triggered re-renders during this expand ─────────────
16924
- // renderVirtualItems() calls li.focus() in branch 2 (keyboard focus restore).
16925
- // The post-render focus() call below can also trigger browser scroll. Both
16926
- // can fire the 'scroll' event and cause onVirtualScroll() to queue a RAF.
16927
- // We set the skip flag ONCE here; it is consumed by the first scroll event.
16928
- // To cover the second potential scroll (from the focus() call below) we use
16929
- // preventScroll:true on that call so it never moves the container at all.
16930
16803
  this.virtualSkipNextScrollRender = true;
16931
16804
  this.renderVirtualItems();
16932
- // renderVirtualItems() replaces all <li> elements so the old icon element that
16933
- // had the PROCESS class is gone. Remove PROCESS from the newly rendered icon
16934
- // so the node can be collapsed/expanded again without being blocked.
16935
16805
  const newLi = this.getElement(id);
16936
16806
  if (newLi) {
16937
16807
  const icon = select('.' + ICON, newLi);
16938
16808
  if (icon) {
16939
16809
  removeClass([icon], PROCESS);
16940
16810
  }
16941
- // ── Use preventScroll:true ────────────────────────────────────────────
16942
- // Without preventScroll the browser scrolls the container to bring this
16943
- // node into view, which fires a second 'scroll' event. The first scroll
16944
- // was already consumed by virtualSkipNextScrollRender (set above), so the
16945
- // second scroll would queue a new RAF that re-renders from the
16946
- // focus-adjusted scrollTop — moving the parent node to the wrong position.
16947
16811
  newLi.focus({ preventScroll: true });
16948
16812
  }
16949
16813
  if (this.isLoaded) {
@@ -16956,9 +16820,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16956
16820
  }
16957
16821
  }
16958
16822
  handleVirtualCollapse(id, isKeyboardNav) {
16959
- // ── Cancel any pending scroll RAF ─────────────────────────────────────────
16960
- // Mirrors handleVirtualExpand: a queued RAF from a previous scroll event
16961
- // must not override the collapse render with a stale window.
16962
16823
  if (this.virtualScrollRAF !== null) {
16963
16824
  cancelAnimationFrame(this.virtualScrollRAF);
16964
16825
  this.virtualScrollRAF = null;
@@ -16967,7 +16828,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16967
16828
  if (idx !== -1) {
16968
16829
  this.expandedNodes.splice(idx, 1);
16969
16830
  }
16970
- // Also update virtualExpandedNodesSet to keep it in sync
16971
16831
  if (this.virtualExpandedNodesSet) {
16972
16832
  this.virtualExpandedNodesSet.delete(id);
16973
16833
  }
@@ -16977,13 +16837,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16977
16837
  setValue(this.fields.expanded, false, acutalRootData);
16978
16838
  }
16979
16839
  }
16980
- // ── Fix: Pin focused ID to the collapsing node for keyboard navigation ──
16981
- // Same reasoning as handleVirtualExpand: when keyboard-triggered, `id` is
16982
- // definitively the focused node. Set unconditionally to avoid the stale/null
16983
- // state that could cause the RAF re-render to fall through to branch 3.
16984
- // For mouse clicks (isKeyboardNav=false), clear virtualFocusedId so that
16985
- // renderVirtualItems() Branch 2 does NOT apply the FOCUS/FOCUSED classes —
16986
- // mouse-based expand/collapse should never add the e-focused class.
16987
16840
  if (isKeyboardNav) {
16988
16841
  this.virtualFocusedId = id;
16989
16842
  }
@@ -16992,28 +16845,18 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16992
16845
  }
16993
16846
  this.virtualFlatNodes = this.flattenVisibleNodes();
16994
16847
  this.rebuildVirtualNodeMap();
16995
- // ── Snapshot scrollTop before computing the window ────────────────────────
16996
- // Same rationale as handleVirtualExpand: capture scrollTop before any focus()
16997
- // call or padding change can cause the browser to auto-scroll.
16998
16848
  const scrollTopSnapshot = this.element.scrollTop;
16999
16849
  const win = this.getVirtualWindowAt(scrollTopSnapshot);
17000
16850
  this.virtualStartIndex = win.startIndex;
17001
16851
  this.virtualEndIndex = win.endIndex;
17002
- // ── Block ALL scroll-triggered re-renders during this collapse ────────────
17003
- // Same as handleVirtualExpand: use preventScroll:true on the focus() call
17004
- // below so the second potential scroll event is never fired.
17005
16852
  this.virtualSkipNextScrollRender = true;
17006
16853
  this.renderVirtualItems();
17007
- // renderVirtualItems() replaces all <li> elements so the old icon element that
17008
- // had the PROCESS class is gone. Remove PROCESS from the newly rendered icon
17009
- // so the node can be expanded/collapsed again without being blocked.
17010
16854
  const newLi = this.getElement(id);
17011
16855
  if (newLi) {
17012
16856
  const icon = select('.' + ICON, newLi);
17013
16857
  if (icon) {
17014
16858
  removeClass([icon], PROCESS);
17015
16859
  }
17016
- // ── Use preventScroll:true to avoid consuming virtualSkipNextScrollRender ─
17017
16860
  newLi.focus({ preventScroll: true });
17018
16861
  }
17019
16862
  if (this.isLoaded) {
@@ -17025,14 +16868,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17025
16868
  }
17026
16869
  }
17027
16870
  }
17028
- /**
17029
- * Updates the selected state of a node in `virtualFlatNodes` and re-renders the window.
17030
- *
17031
- * @param {string} id - The `data-uid` of the target node.
17032
- * @param {boolean} selected - Whether the node is now selected.
17033
- * @returns {void}
17034
- * @private
17035
- */
17036
16871
  updateVirtualNodeSelection(id, selected) {
17037
16872
  const idx = this.virtualNodeMap.get(id);
17038
16873
  if (!isNullOrUndefined(idx)) {
@@ -17040,21 +16875,11 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17040
16875
  this.renderVirtualItems();
17041
16876
  }
17042
16877
  }
17043
- /**
17044
- * Updates the checkbox state of a node in `virtualFlatNodes` and re-renders the window.
17045
- *
17046
- * @param {string} id - The `data-uid` of the target node.
17047
- * @param {'check' | 'uncheck' | 'indeterminate'} state - The new check state.
17048
- * @returns {void}
17049
- * @private
17050
- */
17051
16878
  updateVirtualNodeCheckState(id, state) {
17052
16879
  const idx = this.virtualNodeMap.get(id);
17053
16880
  if (!isNullOrUndefined(idx)) {
17054
16881
  this.virtualFlatNodes[parseInt(idx.toString(), 10)].isChecked =
17055
16882
  state === 'check' ? true : state === 'indeterminate' ? 'indeterminate' : false;
17056
- // Keep the persistent indeterminate set in sync so flattenVisibleNodes()
17057
- // correctly restores state after any full list rebuild (e.g. collapse).
17058
16883
  if (state === 'indeterminate') {
17059
16884
  this.virtualIndeterminateNodesSet.add(id);
17060
16885
  }
@@ -17064,9 +16889,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17064
16889
  this.renderVirtualItems();
17065
16890
  }
17066
16891
  }
17067
- // ═══════════════════════════════════════════════════════════════════════════════
17068
- // END VIRTUALIZATION
17069
- // ═══════════════════════════════════════════════════════════════════════════════
17070
16892
  /**
17071
16893
  * Called internally if any of the property value changed.
17072
16894
  *
@@ -17210,7 +17032,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17210
17032
  }
17211
17033
  }
17212
17034
  else {
17213
- // Tear down virtualization and do a full re-render
17214
17035
  this.unwireVirtualScrollEvents();
17215
17036
  this.destroyVirtualMask();
17216
17037
  removeClass([this.element], VIRTUALIZATION);
@@ -17221,7 +17042,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17221
17042
  break;
17222
17043
  case 'height':
17223
17044
  if (this.enableVirtualization) {
17224
- // Re-initialise so the new height takes effect
17225
17045
  this.unwireVirtualScrollEvents();
17226
17046
  this.initVirtualization();
17227
17047
  }
@@ -17239,7 +17059,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17239
17059
  * @returns {void}
17240
17060
  */
17241
17061
  destroy() {
17242
- // Tear down virtualization before standard cleanup
17243
17062
  if (this.enableVirtualization) {
17244
17063
  this.unwireVirtualScrollEvents();
17245
17064
  this.destroyVirtualMask();
@@ -17330,10 +17149,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17330
17149
  if (isNullOrUndefined(fNode)) {
17331
17150
  this.setNodeFocusable();
17332
17151
  }
17333
- // In virtual mode (with a fixed height), rebuild the flat node list and re-render
17334
- // the window so the newly added nodes appear in the correct hierarchical position.
17335
- // When height is not provided, the tree uses normal rendering and the DOM is
17336
- // already updated by addGivenNodes() above.
17337
17152
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
17338
17153
  this.setRootData();
17339
17154
  this.rebuildVirtualCheckCache();