@syncfusion/ej2-navigations 34.2.2 → 34.2.4

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.
Files changed (79) hide show
  1. package/dist/ej2-navigations.min.js +2 -2
  2. package/dist/ej2-navigations.umd.min.js +2 -2
  3. package/dist/ej2-navigations.umd.min.js.map +1 -1
  4. package/dist/es6/ej2-navigations.es2015.js +310 -563
  5. package/dist/es6/ej2-navigations.es2015.js.map +1 -1
  6. package/dist/es6/ej2-navigations.es5.js +312 -563
  7. package/dist/es6/ej2-navigations.es5.js.map +1 -1
  8. package/dist/global/ej2-navigations.min.js +2 -2
  9. package/dist/global/ej2-navigations.min.js.map +1 -1
  10. package/dist/global/index.d.ts +1 -1
  11. package/package.json +3 -3
  12. package/src/common/menu-base.js +14 -6
  13. package/src/treeview/treeview-model.d.ts +1 -0
  14. package/src/treeview/treeview.d.ts +7 -210
  15. package/src/treeview/treeview.js +298 -557
  16. package/styles/bds-lite.css +3 -0
  17. package/styles/bds.css +3 -0
  18. package/styles/bootstrap-dark-lite.css +3 -0
  19. package/styles/bootstrap-dark.css +3 -0
  20. package/styles/bootstrap-lite.css +3 -0
  21. package/styles/bootstrap.css +3 -0
  22. package/styles/bootstrap4-lite.css +3 -0
  23. package/styles/bootstrap4.css +3 -0
  24. package/styles/bootstrap5-dark-lite.css +3 -0
  25. package/styles/bootstrap5-dark.css +3 -0
  26. package/styles/bootstrap5-lite.css +3 -0
  27. package/styles/bootstrap5.3-lite.css +3 -0
  28. package/styles/bootstrap5.3.css +3 -0
  29. package/styles/bootstrap5.css +3 -0
  30. package/styles/fabric-dark-lite.css +3 -0
  31. package/styles/fabric-dark.css +3 -0
  32. package/styles/fabric-lite.css +3 -0
  33. package/styles/fabric.css +3 -0
  34. package/styles/fluent-dark-lite.css +3 -0
  35. package/styles/fluent-dark.css +3 -0
  36. package/styles/fluent-lite.css +3 -0
  37. package/styles/fluent.css +3 -0
  38. package/styles/fluent2-lite.css +3 -0
  39. package/styles/fluent2.css +3 -0
  40. package/styles/highcontrast-light-lite.css +3 -0
  41. package/styles/highcontrast-light.css +3 -0
  42. package/styles/highcontrast-lite.css +3 -0
  43. package/styles/highcontrast.css +3 -0
  44. package/styles/material-dark-lite.css +3 -0
  45. package/styles/material-dark.css +3 -0
  46. package/styles/material-lite.css +3 -0
  47. package/styles/material.css +3 -0
  48. package/styles/material3-dark-lite.css +3 -0
  49. package/styles/material3-dark.css +3 -0
  50. package/styles/material3-lite.css +3 -0
  51. package/styles/material3.css +3 -0
  52. package/styles/tailwind-dark-lite.css +3 -0
  53. package/styles/tailwind-dark.css +3 -0
  54. package/styles/tailwind-lite.css +3 -0
  55. package/styles/tailwind.css +3 -0
  56. package/styles/tailwind3-lite.css +3 -0
  57. package/styles/tailwind3.css +3 -0
  58. package/styles/toolbar/_layout.scss +4 -0
  59. package/styles/toolbar/bds.css +3 -0
  60. package/styles/toolbar/bootstrap-dark.css +3 -0
  61. package/styles/toolbar/bootstrap.css +3 -0
  62. package/styles/toolbar/bootstrap4.css +3 -0
  63. package/styles/toolbar/bootstrap5-dark.css +3 -0
  64. package/styles/toolbar/bootstrap5.3.css +3 -0
  65. package/styles/toolbar/bootstrap5.css +3 -0
  66. package/styles/toolbar/fabric-dark.css +3 -0
  67. package/styles/toolbar/fabric.css +3 -0
  68. package/styles/toolbar/fluent-dark.css +3 -0
  69. package/styles/toolbar/fluent.css +3 -0
  70. package/styles/toolbar/fluent2.css +3 -0
  71. package/styles/toolbar/highcontrast-light.css +3 -0
  72. package/styles/toolbar/highcontrast.css +3 -0
  73. package/styles/toolbar/material-dark.css +3 -0
  74. package/styles/toolbar/material.css +3 -0
  75. package/styles/toolbar/material3-dark.css +3 -0
  76. package/styles/toolbar/material3.css +3 -0
  77. package/styles/toolbar/tailwind-dark.css +3 -0
  78. package/styles/toolbar/tailwind.css +3 -0
  79. package/styles/toolbar/tailwind3.css +3 -0
@@ -1430,8 +1430,10 @@ let MenuBase = class MenuBase extends Component {
1430
1430
  if (!active) {
1431
1431
  return;
1432
1432
  }
1433
+ const isInputElement = active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' ||
1434
+ active.contentEditable === 'true';
1433
1435
  const expanded = active.getAttribute('aria-expanded') === 'true';
1434
- if (!expanded) {
1436
+ if (!expanded || isInputElement) {
1435
1437
  return;
1436
1438
  }
1437
1439
  const ul = this.element;
@@ -1545,13 +1547,19 @@ let MenuBase = class MenuBase extends Component {
1545
1547
  const cli = cul.children[fliIdx];
1546
1548
  fliIdx = this.isValidLI(cli, fliIdx, e.action);
1547
1549
  cul.children[fliIdx].classList.add(FOCUSED);
1548
- cul.children[fliIdx].focus();
1550
+ const liToFocus = cul.children[fliIdx];
1549
1551
  if (closest(this.element, '.e-popup')) {
1550
- const focusedLiItem = cul.children[fliIdx];
1551
- const focusableChild = focusedLiItem.querySelector('[tabindex], span[tabindex], a, button, [tabindex]:not([tabindex="-1"])');
1552
- if (!focusableChild) {
1553
- cul.focus();
1552
+ const focusableChild = liToFocus.querySelector('[tabindex], span[tabindex], a, button, [tabindex]:not([tabindex="-1"])');
1553
+ const isSpreadsheet = this.element.id.includes('_cf_menu') && this.element.parentElement.classList.contains('e-cf-menu');
1554
+ if (focusableChild && !isSpreadsheet) {
1555
+ focusableChild.focus();
1554
1556
  }
1557
+ else {
1558
+ liToFocus.focus();
1559
+ }
1560
+ }
1561
+ else {
1562
+ liToFocus.focus();
1555
1563
  }
1556
1564
  }
1557
1565
  isValidLI(cli, index, action) {
@@ -10690,54 +10698,25 @@ let TreeView = TreeView_1 = class TreeView extends Component {
10690
10698
  this.isDropIn = false;
10691
10699
  this.OldCheckedData = [];
10692
10700
  this.isHiddenItem = false;
10693
- // ── Virtualization state ──────────────────────────────────────────────────────────────
10694
- /** Flat ordered list of all currently-visible (non-collapsed) tree nodes. */
10701
+ // Virtualization state
10695
10702
  this.virtualFlatNodes = [];
10696
- /** Maps node id → index in virtualFlatNodes for O(1) lookups. */
10697
10703
  this.virtualNodeMap = new Map();
10698
- /** Set of expanded node IDs for O(1) lookup performance during flattening (avoids array indexOf). */
10699
10704
  this.virtualExpandedNodesSet = new Set();
10700
- /** Set of checked node IDs for O(1) lookup performance during flattening. */
10701
10705
  this.virtualCheckedNodesSet = new Set();
10702
- /** Set of indeterminate node IDs — persists across flattenVisibleNodes() rebuilds. */
10703
10706
  this.virtualIndeterminateNodesSet = new Set();
10704
- /** Set of selected node IDs for O(1) lookup performance during flattening. */
10705
10707
  this.virtualSelectedNodesSet = new Set();
10706
- /** Row height in pixels, measured from the first rendered item or set to 40 as a default. */
10707
10708
  this.virtualItemHeight = 40;
10708
- /** Height of the scrollable container in pixels. */
10709
10709
  this.virtualViewportHeight = 0;
10710
- /** scrollTop captured during the previous scroll handler invocation. */
10711
10710
  this.virtualPrevScrollTop = 0;
10712
- /** Index of the first rendered row in the current window. */
10713
10711
  this.virtualStartIndex = 0;
10714
- /** Index one past the last rendered row in the current window. */
10715
10712
  this.virtualEndIndex = 0;
10716
- /** Extra rows rendered above and below the visible window to smooth scrolling. */
10717
10713
  this.virtualOverscanCount = 3;
10718
- /** RequestAnimationFrame handle used for throttling scroll events. */
10719
10714
  this.virtualScrollRAF = null;
10720
- /** The `.e-mask-parent` overlay element shown during fast scroll. */
10721
10715
  this.virtualMaskElement = null;
10722
- /** Bound scroll handler stored for later removeEventListener. */
10723
10716
  this.virtualScrollBound = null;
10724
- /** Skeleton component instances inside the mask overlay. */
10725
10717
  this.skeletonInstances = [];
10726
- /** Set to true whenever the flat node list must be rebuilt on next render. */
10727
10718
  this.virtualTreeDirty = false;
10728
- /** The data-uid of the currently keyboard-focused node in virtual mode (persists across re-renders). */
10729
10719
  this.virtualFocusedId = null;
10730
- /**
10731
- * Set to true by the document-level `keydown` handler when the user presses
10732
- * the Tab key. The browser will auto-scroll the focused element into view
10733
- * immediately after focus, which fires a `scroll` event on the TreeView
10734
- * container. We want to skip the re-render that the virtual scroll handler
10735
- * would otherwise trigger, because the element being focused is already
10736
- * rendered (it is the single `<li tabindex="0">` of the current window) and
10737
- * re-rendering would tear down the DOM element that is currently holding
10738
- * focus. The flag is consumed (reset to `false`) inside `onVirtualScroll`
10739
- * after the auto-scroll has been handled.
10740
- */
10741
10720
  this.virtualSkipNextScrollRender = false;
10742
10721
  }
10743
10722
  /**
@@ -11034,16 +11013,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
11034
11013
  }
11035
11014
  }
11036
11015
  renderItems(isSorted) {
11037
- // In virtual mode (with a fixed height) create only the container <ul> —
11038
- // initVirtualization will populate the items directly, avoiding the O(n)
11039
- // ListBase.createList cost.
11040
- // updateCheckedStateFromDS / updateParentCheckState are intentionally skipped here:
11041
- // they call getCheckedNodeDetails which traverses the DOM (no DOM exists yet in
11042
- // virtual mode) and would throw on empty recursion results. The initial checked
11043
- // state is derived from the raw data inside flattenNodeRecursive instead.
11044
- //
11045
- // When enableVirtualization is true but height is NOT provided, we fall through
11046
- // to the normal (non-virtualized) rendering path so the tree is still visible.
11047
11016
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
11048
11017
  this.ulElement = this.createElement('ul', {
11049
11018
  className: PARENTITEM + ' ' + 'e-ul',
@@ -12119,7 +12088,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12119
12088
  this.checkDisabledState(id);
12120
12089
  }
12121
12090
  if (this.enableVirtualization) {
12122
- // Clear any indeterminate state — node is now fully checked
12123
12091
  this.virtualIndeterminateNodesSet.delete(id);
12124
12092
  const actualIndex = this.virtualNodeMap.get(id);
12125
12093
  if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
@@ -12140,19 +12108,16 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12140
12108
  const actualIndex = this.virtualNodeMap.get(id);
12141
12109
  if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
12142
12110
  if (isIndeterminate) {
12143
- // Parent is transitioning to indeterminate — preserve children states
12144
- // and mark this node as 'indeterminate' so scroll/collapse re-renders correctly.
12145
12111
  this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].isChecked = 'indeterminate';
12146
- // Also record it in the persistent indeterminate set so flattenVisibleNodes()
12147
- // can restore this state after a full rebuild (e.g. on collapse).
12148
12112
  this.virtualIndeterminateNodesSet.add(id);
12149
12113
  }
12150
12114
  else {
12151
- // Node is being fully unchecked — clear indeterminate tracking too
12152
12115
  this.virtualIndeterminateNodesSet.delete(id);
12153
12116
  this.virtualCheckedNodesSet.delete(id);
12154
12117
  this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].isChecked = false;
12155
- this.treeData[parseInt(id.toString(), 10) - 1].isChecked = false;
12118
+ if (this.treeData && this.fields && this.fields.isChecked) {
12119
+ this.updateField(this.treeData, this.fields, id, this.fields.isChecked, false);
12120
+ }
12156
12121
  if (this.autoCheck && this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].hasChildren) {
12157
12122
  this.setVirtualChildCheckState(id, false);
12158
12123
  }
@@ -12197,8 +12162,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12197
12162
  }
12198
12163
  }
12199
12164
  }
12200
- // Collapsed children are not present in virtualFlatNodes, so walk the raw data
12201
- // to keep this.treeData and checkedNodes in sync for hidden descendants too.
12202
12165
  const collapsedChildren = this.getChildNodes(this.treeData, parentId);
12203
12166
  if (collapsedChildren) {
12204
12167
  for (let i = 0; i < collapsedChildren.length; i++) {
@@ -12342,7 +12305,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12342
12305
  }
12343
12306
  this.isLoaded = true;
12344
12307
  this.isNodeDropped = false;
12345
- // Initialise (or re-initialise) virtual mode after data binding has settled
12346
12308
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
12347
12309
  this.initVirtualization();
12348
12310
  }
@@ -12480,9 +12442,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12480
12442
  this.trigger('nodeClicked', eventArgs);
12481
12443
  }
12482
12444
  expandNode(currLi, icon, loaded, isKeyboardNav) {
12483
- // Virtual mode fast-path: rebuild the flat list and re-render the window.
12484
- // Only engage when height is also set; without a fixed height the tree falls
12485
- // back to normal rendering and the standard expand path must be used.
12486
12445
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
12487
12446
  const id = currLi ? currLi.getAttribute('data-uid') : null;
12488
12447
  if (id) {
@@ -12609,9 +12568,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
12609
12568
  }
12610
12569
  }
12611
12570
  nodeCollapseAction(currLi, icon, colArgs, isKeyboardNav) {
12612
- // Virtual mode fast-path: rebuild the flat list and re-render the window.
12613
- // Only engage when height is also set; without a fixed height the tree falls
12614
- // back to normal rendering and the standard collapse path must be used.
12615
12571
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
12616
12572
  const id = currLi ? currLi.getAttribute('data-uid') : null;
12617
12573
  if (id) {
@@ -13072,7 +13028,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13072
13028
  li.setAttribute('tabindex', '0');
13073
13029
  EventHandler.add(li, 'blur', this.focusOut, this);
13074
13030
  this.updateIdAttr(focusedNode, li);
13075
- // Track focused id for virtual mode re-render restoration
13076
13031
  if (this.enableVirtualization) {
13077
13032
  this.virtualFocusedId = li.getAttribute('data-uid');
13078
13033
  }
@@ -13085,6 +13040,13 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13085
13040
  if (!isNullOrUndefined(id) && this.selectedNodes.indexOf(id) === -1) {
13086
13041
  this.selectedNodes.push(id);
13087
13042
  }
13043
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13044
+ this.virtualSelectedNodesSet.add(id);
13045
+ const actualIndex = this.virtualNodeMap.get(id);
13046
+ if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
13047
+ this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].selected = true;
13048
+ }
13049
+ }
13088
13050
  }
13089
13051
  removeSelect(liEle) {
13090
13052
  if (this.allowMultiSelection) {
@@ -13098,6 +13060,14 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13098
13060
  if (index > -1) {
13099
13061
  this.selectedNodes.splice(index, 1);
13100
13062
  }
13063
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13064
+ const id = liEle.getAttribute('data-uid');
13065
+ this.virtualSelectedNodesSet.delete(id);
13066
+ const actualIndex = this.virtualNodeMap.get(id);
13067
+ if (!isNullOrUndefined(actualIndex) && actualIndex > -1) {
13068
+ this.virtualFlatNodes[parseInt(actualIndex.toString(), 10)].selected = false;
13069
+ }
13070
+ }
13101
13071
  }
13102
13072
  removeSelectAll() {
13103
13073
  const selectedLI = this.element.querySelectorAll('.' + ACTIVE);
@@ -13111,6 +13081,15 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13111
13081
  }
13112
13082
  removeClass(selectedLI, ACTIVE);
13113
13083
  this.setProperties({ selectedNodes: [] }, true);
13084
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13085
+ this.virtualSelectedNodesSet.clear();
13086
+ for (let i = 0; i < this.virtualFlatNodes.length; i++) {
13087
+ const node = this.virtualFlatNodes[parseInt(i.toString(), 10)];
13088
+ if (node.selected) {
13089
+ node.selected = false;
13090
+ }
13091
+ }
13092
+ }
13114
13093
  }
13115
13094
  getSelectEvent(currLi, action, e) {
13116
13095
  const nodeData = this.getNodeData(currLi);
@@ -13178,12 +13157,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13178
13157
  }
13179
13158
  }
13180
13159
  nodeExpandAction(currLi, icon, expandChild, callback, isFromExpandAll, isKeyboardNav) {
13181
- // In virtual mode (with a fixed height) the flat <li> never has a child <ul>
13182
- // in the DOM, so the PARENTITEM check below would always fail and fall into
13183
- // renderChildNodes(), which corrupts the virtual DOM.
13184
- // Delegate directly to expandNode() which has the handleVirtualExpand fast-path.
13185
- // When height is not provided, fall through to normal expand logic below since
13186
- // the tree is rendered with a full nested DOM structure.
13187
13160
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13188
13161
  this.expandNode(currLi, icon, undefined, isKeyboardNav);
13189
13162
  return;
@@ -13300,9 +13273,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13300
13273
  });
13301
13274
  }
13302
13275
  navigateToFocus(isUp) {
13303
- // In virtual mode (with a fixed height) the element itself is the scroll
13304
- // container; use ensureVirtualNodeVisible. When height is not provided,
13305
- // the tree uses normal rendering, so fall through to the standard scroll logic.
13306
13276
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13307
13277
  const focusedLi = this.getFocusedNode();
13308
13278
  if (focusedLi) {
@@ -13732,7 +13702,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13732
13702
  }
13733
13703
  focusNextNode(li, isTowards) {
13734
13704
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
13735
- // Determine the id of the node one step in the requested direction
13736
13705
  const currentId = li ? li.getAttribute('data-uid') : null;
13737
13706
  let currentIdx = -1;
13738
13707
  if (!isNullOrUndefined(currentId)) {
@@ -13744,26 +13713,14 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13744
13713
  return;
13745
13714
  }
13746
13715
  const targetId = this.virtualFlatNodes[parseInt(targetIdx.toString(), 10)].id;
13747
- // ── Fix 1: set virtualFocusedId BEFORE calling ensureVirtualNodeVisible ──
13748
- // renderVirtualItems() is called synchronously inside ensureVirtualNodeVisible
13749
- // when a re-render is needed. If virtualFocusedId is still the old value at
13750
- // that point, focus restoration falls back to the first item. Setting it here
13751
- // ensures renderVirtualItems always restores focus to the correct target node.
13752
13716
  this.virtualFocusedId = targetId;
13753
- // ── Fix 2: clear focus classes from ALL nested list items, not just top-level ones ──
13754
- // With hierarchical virtual DOM, the currently focused <li> may be nested inside
13755
- // a child <ul>. Using ':scope > li' only cleans up direct children and leaves
13756
- // stale e-focused / e-node-focus classes on nested nodes across re-renders.
13757
13717
  const allLi = this.element.querySelectorAll('.' + LISTITEM + '.' + FOCUS);
13758
13718
  for (let k = 0; k < allLi.length; k++) {
13759
13719
  allLi[parseInt(k.toString(), 10)].classList.remove(FOCUS);
13760
13720
  allLi[parseInt(k.toString(), 10)].classList.remove(FOCUSED$1);
13761
13721
  allLi[parseInt(k.toString(), 10)].setAttribute('tabindex', '-1');
13762
13722
  }
13763
- // Ensure the target <li> is in the DOM window (may trigger a re-render).
13764
- // virtualFocusedId is already set above so renderVirtualItems restores focus correctly.
13765
13723
  this.ensureVirtualNodeVisible(targetId);
13766
- // Re-query both nodes after the potential re-render so setFocus gets live DOM elements
13767
13724
  const prevNode = currentId ? this.getElement(currentId) : li;
13768
13725
  const nextNode = this.getElement(targetId);
13769
13726
  if (!isNullOrUndefined(nextNode)) {
@@ -13864,18 +13821,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
13864
13821
  if (!focusedElement) {
13865
13822
  return;
13866
13823
  }
13867
- // Virtual-mode guard: when the browser moves focus into the TreeView (e.g. via
13868
- // the Tab key) it always lands on the single `<li tabindex="0">`. In that
13869
- // case the element is already the focusable one, FOCUS is already applied
13870
- // and the only thing left to do is the keyboard focus styling. Skip the
13871
- // redundant setAttribute/EventHandler.add work so we never re-render the
13872
- // virtual window as a side effect of focusing the existing `tabindex="0"`
13873
- // item.
13874
- // ── Fix: Use document.activeElement to detect browser-focused node ──
13875
- // After renderVirtualItems() restores focus, the browser-focused element is
13876
- // the authoritative source, not getFocusedNode(). If they don't match, it means
13877
- // renderVirtualItems already correctly set up the element, so we must not
13878
- // override its setup by calling setFocus again.
13879
13824
  const actuallyFocusedElement = document.activeElement;
13880
13825
  const isAlreadyFocusedInVirtualMode = this.enableVirtualization
13881
13826
  && actuallyFocusedElement === focusedElement
@@ -14128,12 +14073,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14128
14073
  else {
14129
14074
  txtEle[ListBase.getDomSetter(this.listBaseOption)] = ListBase.getSanitizedText(newText, this.listBaseOption);
14130
14075
  }
14131
- // ── Virtual mode: sync the flat-node snapshot so re-renders show updated text ──
14132
- // virtualFlatNodes is rebuilt from this.treeData (via flattenVisibleNodes) only on
14133
- // expand/collapse/scroll. Between those events the snapshot is the single source of
14134
- // truth used by createVirtualListItem. setValue() above updates this.editData (which
14135
- // IS the treeData object reference), but virtualFlatNodes[idx].text still holds the
14136
- // old value. Update it here so the next renderVirtualItems() call shows the new text.
14137
14076
  if (this.enableVirtualization) {
14138
14077
  const id = liEle.getAttribute('data-uid');
14139
14078
  const idx = this.virtualNodeMap ? this.virtualNodeMap.get(id) : undefined;
@@ -14577,11 +14516,49 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14577
14516
  this.fields.dataSource = this.treeData;
14578
14517
  }
14579
14518
  this.triggerEvent('nodeDropped', nodeData);
14519
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
14520
+ this.setRootData();
14521
+ this.rebuildVirtualCheckCache();
14522
+ this.virtualFlatNodes = this.flattenVisibleNodes();
14523
+ this.rebuildVirtualNodeMap();
14524
+ const scrollTopSnapshot = this.element.scrollTop;
14525
+ const win = this.getVirtualWindowAt(scrollTopSnapshot);
14526
+ this.virtualStartIndex = win.startIndex;
14527
+ this.virtualEndIndex = win.endIndex;
14528
+ this.virtualSkipNextScrollRender = true;
14529
+ this.renderVirtualItems();
14530
+ }
14580
14531
  }
14581
14532
  appendNode(dropTarget, dragLi, dropLi, e, dragObj, offsetY) {
14582
14533
  const checkWrapper = closest(dropTarget, '.' + CHECKBOXWRAP);
14583
14534
  const collapse = closest(e.target, '.' + COLLAPSIBLE);
14584
14535
  const expand = closest(e.target, '.' + EXPANDABLE);
14536
+ if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
14537
+ const routesToSiblingBranch = !dragLi.classList.contains('e-disable') && !checkWrapper &&
14538
+ ((expand && e.event.offsetY < 5) || (collapse && e.event.offsetX < 3) ||
14539
+ (expand && e.event.offsetY > 19) || (collapse && e.event.offsetX > 19) ||
14540
+ (!expand && !collapse));
14541
+ if (routesToSiblingBranch) {
14542
+ if (dropTarget.nodeName === 'LI') {
14543
+ this.virtualMoveAsSibling(dragLi, dropLi, e, dragObj);
14544
+ }
14545
+ else if (dropTarget.firstElementChild && dropTarget.classList.contains(ROOT)) {
14546
+ if (dropTarget.firstElementChild.nodeName === 'UL') {
14547
+ this.virtualMoveAsSibling(dragLi, dropLi, e, dragObj);
14548
+ }
14549
+ }
14550
+ else if ((dropTarget.classList.contains('e-icon-collapsible')) || (dropTarget.classList.contains('e-icon-expandable'))) {
14551
+ this.virtualMoveAsSibling(dragLi, dropLi, e, dragObj);
14552
+ }
14553
+ else {
14554
+ this.virtualMoveAsChild(dragLi, dropLi, dragObj, offsetY, dropTarget);
14555
+ }
14556
+ }
14557
+ else {
14558
+ this.virtualMoveAsChild(dragLi, dropLi, dragObj, offsetY, dropTarget);
14559
+ }
14560
+ return;
14561
+ }
14585
14562
  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))) {
14586
14563
  if (dropTarget.nodeName === 'LI') {
14587
14564
  this.dropAsSiblingNode(dragLi, dropLi, e, dragObj);
@@ -14605,6 +14582,62 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14605
14582
  this.ensureIndeterminate();
14606
14583
  }
14607
14584
  }
14585
+ virtualMoveAsSibling(dragLi, dropLi, e, dragObj) {
14586
+ const dropUl = closest(dropLi, '.' + PARENTITEM);
14587
+ let pre;
14588
+ if (e.target.offsetHeight > 0 && e.event.offsetY > e.target.offsetHeight - 2) {
14589
+ pre = false;
14590
+ }
14591
+ else if (e.event.offsetY < 2) {
14592
+ pre = true;
14593
+ }
14594
+ else if (e.target.classList.contains('e-icon-expandable') || (e.target.classList.contains('e-icon-collapsible'))) {
14595
+ if ((e.event.offsetY < 5) || (e.event.offsetX < 3)) {
14596
+ pre = true;
14597
+ }
14598
+ else if ((e.event.offsetY > 15) || (e.event.offsetX > 17)) {
14599
+ pre = false;
14600
+ }
14601
+ }
14602
+ this.moveData(dragLi, dropLi, dropUl, pre, dragObj);
14603
+ }
14604
+ virtualMoveAsChild(dragLi, dropLi, dragObj, offsetY, dropTarget) {
14605
+ const pos = offsetY;
14606
+ const isSiblingMarker = dropTarget.classList.contains('e-sibling');
14607
+ const refLi = (pos < 7 && !isSiblingMarker) || (isSiblingMarker &&
14608
+ dropLi.lastChild && !dropLi.lastChild.classList.contains('e-sibling'))
14609
+ ? dropLi : null;
14610
+ const dropTargetHt = dropTarget;
14611
+ const isAfter = (dropTargetHt.offsetHeight > 0 && pos > dropTargetHt.offsetHeight - 10);
14612
+ if (refLi) {
14613
+ const dropParentUl = closest(dropLi, '.' + PARENTITEM);
14614
+ this.moveData(dragLi, dropLi, dropParentUl, true, dragObj);
14615
+ }
14616
+ else if (isAfter) {
14617
+ const dropParentUl = closest(dropLi, '.' + PARENTITEM);
14618
+ this.moveData(dragLi, dropLi, dropParentUl, false, dragObj);
14619
+ }
14620
+ else {
14621
+ const dropIcon = select('div.' + EXPANDABLE + ', div.' + COLLAPSIBLE, dropLi);
14622
+ const dropIsCollapsedParent = dropIcon && dropIcon.classList.contains(EXPANDABLE);
14623
+ const dropIsLeaf = isNullOrUndefined(dropIcon);
14624
+ if (this.preventExpand !== true && (dropIsCollapsedParent || dropIsLeaf)) {
14625
+ const dropLiId = dropLi.getAttribute('data-uid');
14626
+ if (dropLiId && this.expandedNodes.indexOf(dropLiId) === -1) {
14627
+ this.expandedNodes.push(dropLiId);
14628
+ }
14629
+ if (this.virtualExpandedNodesSet && dropLiId) {
14630
+ this.virtualExpandedNodesSet.add(dropLiId);
14631
+ }
14632
+ const dropRecord = this.findNodeInTreeData(dropLiId);
14633
+ if (dropRecord && this.fields && this.fields.expanded) {
14634
+ setValue(this.fields.expanded, true, dropRecord);
14635
+ }
14636
+ }
14637
+ const dropUl = select('.' + PARENTITEM, dropLi) || dropLi;
14638
+ this.moveData(dragLi, null, dropUl, true, dragObj);
14639
+ }
14640
+ }
14608
14641
  dropAsSiblingNode(dragLi, dropLi, e, dragObj) {
14609
14642
  const dropUl = closest(dropLi, '.' + PARENTITEM);
14610
14643
  const dragParentUl = closest(dragLi, '.' + PARENTITEM);
@@ -14692,7 +14725,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14692
14725
  delete removedData[0][this.fields.parentID];
14693
14726
  }
14694
14727
  else {
14695
- const currPid = this.isNumberTypeId ? parseFloat(parentId) : parentId;
14728
+ const currPid = this.isNumberTypeId && parentId ? parseFloat(parentId) : parentId;
14696
14729
  setValue(this.fields.parentID, currPid, removedData[0]);
14697
14730
  }
14698
14731
  this.treeData.splice(pos, 0, removedData[0]);
@@ -14834,7 +14867,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14834
14867
  const draggedNodeData = obj ? obj.dragData : null;
14835
14868
  const newParent = dropTarget ? this.parents(dropTarget, '.' + LISTITEM) : null;
14836
14869
  const dragLiParent = obj.dragLi.parentElement;
14837
- let dragParent = obj.dragLi ? closest(dragLiParent, '.' + LISTITEM) : null;
14870
+ let dragParent = obj.dragLi && dragLiParent ? closest(dragLiParent, '.' + LISTITEM) : null;
14838
14871
  let targetParent = null;
14839
14872
  let indexValue = null;
14840
14873
  const iconCss = [DROPNEXT, DROPIN, DROPOUT, NODROP];
@@ -14843,7 +14876,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
14843
14876
  const index = node ? closest(node, '.e-list-parent') : null;
14844
14877
  let i = 0;
14845
14878
  let position = null;
14846
- dragParent = (obj.dragLi && dragParent === null) ? closest(dragLiParent, '.' + ROOT) : dragParent;
14879
+ dragParent = (obj.dragLi && dragParent === null) && dragLiParent ? closest(dragLiParent, '.' + ROOT) : dragParent;
14847
14880
  dragParent = (drop === true) ? this.dragParent : dragParent;
14848
14881
  if (cloneEle) {
14849
14882
  while (i < 4) {
@@ -15510,26 +15543,10 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15510
15543
  keyDownHandler(e) {
15511
15544
  if (e.key === 'Tab' || e.keyCode === 9) {
15512
15545
  this.isKeyboardFocus = true;
15513
- // Mark the upcoming scroll event (if any) as Tab-induced so the
15514
- // virtual scroll handler skips its re-render path. The browser
15515
- // calls `Element.scrollIntoView` semantics on focus, which scrolls
15516
- // a `<li tabindex="0">` in the middle of the tree into view and
15517
- // therefore fires `scroll` on the container. Without this guard
15518
- // the scroll handler would tear down the focused `<li>` and move
15519
- // focus elsewhere.
15520
15546
  if (this.enableVirtualization) {
15521
15547
  this.virtualSkipNextScrollRender = true;
15522
15548
  }
15523
15549
  }
15524
- // ── Fix: Also skip scroll render for arrow key navigation ──────────────────
15525
- // Arrow Down/Up calls focusNextNode() which calls ensureVirtualNodeVisible()
15526
- // to scroll the target node into view. Without this guard, the scroll event
15527
- // would trigger onVirtualScroll() which re-renders the window and resets the
15528
- // focus that focusNextNode() just set. This manifests as tabindex resetting
15529
- // to the first item instead of staying on the arrow-navigated target.
15530
- // Arrow Right/Left (expand/collapse) also calls renderVirtualItems() which
15531
- // calls li.focus() in branch 2; that can trigger a scroll on an edge node,
15532
- // so we consume it here to prevent a stale RAF re-render.
15533
15550
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.keyCode === 40 || e.keyCode === 38
15534
15551
  || e.key === 'ArrowRight' || e.key === 'ArrowLeft' || e.keyCode === 39 || e.keyCode === 37) {
15535
15552
  if (this.enableVirtualization) {
@@ -15888,10 +15905,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15888
15905
  if (this.dataType === 1) {
15889
15906
  this.groupedData = this.getGroupedData(this.treeData, this.fields.parentID);
15890
15907
  }
15891
- // In virtual mode (with a fixed height), rebuild the flat node list and re-render
15892
- // after deletion so the removed nodes are correctly reflected in the hierarchical
15893
- // virtual DOM. When height is not provided, the tree uses normal rendering and
15894
- // the DOM is already updated by removeNode() above.
15895
15908
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
15896
15909
  this.virtualFlatNodes = this.flattenVisibleNodes();
15897
15910
  this.rebuildVirtualNodeMap();
@@ -15954,16 +15967,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15954
15967
  }
15955
15968
  return parentsID;
15956
15969
  }
15957
- // ═══════════════════════════════════════════════════════════════════════════════
15958
- // VIRTUALIZATION — private helpers
15959
- // ═══════════════════════════════════════════════════════════════════════════════
15960
- /**
15961
- * Parses the `height` property (string like '400px' or number) to a pixel value.
15962
- * Returns 0 if the value cannot be determined.
15963
- *
15964
- * @returns {number} Pixel height.
15965
- * @private
15966
- */
15967
15970
  parseHeightToPixels() {
15968
15971
  const h = this.height;
15969
15972
  if (isNullOrUndefined(h)) {
@@ -15975,13 +15978,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15975
15978
  const parsed = parseFloat(h);
15976
15979
  return isNaN(parsed) ? 0 : parsed;
15977
15980
  }
15978
- /**
15979
- * Measures the height of the first rendered list item so subsequent calculations
15980
- * use real layout values. Falls back to the stored `virtualItemHeight` default (40).
15981
- *
15982
- * @returns {number} Row height in pixels.
15983
- * @private
15984
- */
15985
15981
  measureVirtualItemHeight() {
15986
15982
  const firstLi = select('.' + LISTITEM, this.element);
15987
15983
  if (firstLi && firstLi.offsetHeight > 0) {
@@ -15989,13 +15985,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
15989
15985
  }
15990
15986
  return this.virtualItemHeight;
15991
15987
  }
15992
- /**
15993
- * Rebuilds the cached Sets for checked/selected nodes to enable O(1) lookup
15994
- * during the flatten operation instead of O(n) array indexOf calls.
15995
- *
15996
- * @returns {void}
15997
- * @private
15998
- */
15999
15988
  rebuildVirtualCheckCache() {
16000
15989
  this.virtualExpandedNodesSet.clear();
16001
15990
  this.virtualCheckedNodesSet.clear();
@@ -16016,20 +16005,10 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16016
16005
  }
16017
16006
  }
16018
16007
  }
16019
- /**
16020
- * Recursively walks the treeData (or groupedData for flat lists) and builds
16021
- * `virtualFlatNodes` — an ordered list of every node that is currently visible
16022
- * (i.e. its ancestors are all expanded). Uses cached Sets for O(1) state lookups.
16023
- *
16024
- * @returns {VirtualNodeData[]} Ordered flat list of visible nodes.
16025
- * @private
16026
- */
16027
16008
  flattenVisibleNodes() {
16028
- // Build cached Sets for fast lookups
16029
16009
  this.rebuildVirtualCheckCache();
16030
16010
  const result = [];
16031
16011
  if (this.dataType === 1) {
16032
- // Flat data with parentID field — use getChildGroup to find root nodes
16033
16012
  let rootNodes = [];
16034
16013
  if (this.groupedData) {
16035
16014
  rootNodes = this.getChildGroup(this.groupedData, null, true);
@@ -16037,39 +16016,29 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16037
16016
  if (!rootNodes || rootNodes.length === 0) {
16038
16017
  rootNodes = this.rootData;
16039
16018
  }
16040
- // Apply sortOrder to root-level nodes for flat data (same as hierarchical path below)
16041
16019
  rootNodes = this.getSortedData(rootNodes);
16042
16020
  this.flattenNodeRecursive(rootNodes, 1, null, result);
16043
16021
  }
16044
16022
  else {
16045
- // Hierarchical data
16046
16023
  const sorted = this.getSortedData(this.rootData);
16047
16024
  this.flattenNodeRecursive(sorted, 1, null, result);
16048
16025
  }
16049
16026
  return result;
16050
16027
  }
16051
- /**
16052
- * Recursive helper for `flattenVisibleNodes`.
16053
- *
16054
- * @param {Object[]} nodes - Array of raw data objects at this level.
16055
- * @param {number} level - 1-based depth level.
16056
- * @param {string | null} parentId - The parent node id, or null for root.
16057
- * @param {VirtualNodeData[]} result - Accumulator array.
16058
- * @returns {void}
16059
- * @private
16060
- */
16061
16028
  flattenNodeRecursive(nodes, level, parentId, result) {
16062
16029
  if (!nodes || nodes.length === 0) {
16063
16030
  return;
16064
16031
  }
16065
- // Cache field names and settings outside the loop to avoid repeated conversions
16066
- const childFieldName = this.fields.child.toString();
16067
- const expandedFieldName = this.fields.expanded;
16068
- const isCheckedFieldName = this.fields.isChecked;
16069
- const iconCssFieldName = this.fields.iconCss;
16070
- const imageUrlFieldName = this.fields.imageUrl;
16071
- const idFieldName = this.fields.id;
16072
- const textFieldName = this.fields.text;
16032
+ const levelMapper = level === 1 ? this.fields : this.getChildFields(this.fields, level, 1);
16033
+ const childFieldName = levelMapper.child && levelMapper.child.toString();
16034
+ const expandedFieldName = levelMapper.expanded;
16035
+ const isCheckedFieldName = levelMapper.isChecked;
16036
+ const iconCssFieldName = levelMapper.iconCss;
16037
+ const imageUrlFieldName = levelMapper.imageUrl;
16038
+ const idFieldName = levelMapper.id;
16039
+ const textFieldName = levelMapper.text;
16040
+ const selectedFieldName = levelMapper.selected;
16041
+ const hasChildrenFieldName = levelMapper.hasChildren;
16073
16042
  const needsSorting = this.sortOrder !== 'None';
16074
16043
  const isDataTypeFlat = this.dataType === 1;
16075
16044
  const enableVirtualization = this.enableVirtualization;
@@ -16077,70 +16046,93 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16077
16046
  const raw = nodes[i];
16078
16047
  const id = isNullOrUndefined(raw[idFieldName]) ? '' : raw[idFieldName].toString();
16079
16048
  const text = isNullOrUndefined(raw[textFieldName]) ? '' : raw[textFieldName].toString();
16080
- // Use expandedNodes as the single source of truth.
16081
16049
  const inExpandedNodes = this.virtualExpandedNodesSet.has(id);
16082
- const rawExpanded = !inExpandedNodes && getValue(expandedFieldName, raw) === true;
16083
- const isExpanded = inExpandedNodes || rawExpanded;
16050
+ const rawExpanded = !inExpandedNodes && expandedFieldName && getValue(expandedFieldName, raw) === true;
16051
+ const expanded = inExpandedNodes || rawExpanded;
16084
16052
  if (rawExpanded && id) {
16085
16053
  this.expandedNodes.push(id);
16086
16054
  this.virtualExpandedNodesSet.add(id);
16087
16055
  }
16088
- // Inline hasChildRecords check to avoid function call (optimization)
16089
16056
  let hasChildren = false;
16090
- if (isDataTypeFlat) {
16057
+ const explicitHasChildren = hasChildrenFieldName ? getValue(hasChildrenFieldName, raw) : null;
16058
+ if (levelMapper.dataSource instanceof DataManager && !isNullOrUndefined(explicitHasChildren) && explicitHasChildren) {
16059
+ hasChildren = true;
16060
+ }
16061
+ else if (isDataTypeFlat) {
16091
16062
  if (this.groupedData) {
16092
16063
  const groupedChild = this.getChildGroup(this.groupedData, id, false);
16093
16064
  hasChildren = !isNullOrUndefined(groupedChild) && groupedChild.length > 0;
16094
16065
  }
16095
16066
  }
16096
16067
  else {
16097
- const childrenObj = getValue(childFieldName, raw);
16068
+ const childrenObj = childFieldName ? getValue(childFieldName, raw) : null;
16098
16069
  hasChildren = !isNullOrUndefined(childrenObj) && Array.isArray(childrenObj) && childrenObj.length > 0;
16099
16070
  }
16100
- // Virtual-mode only: honour fields.isChecked === true on initial render
16101
- if (enableVirtualization && id && isCheckedFieldName && !this.virtualCheckedNodesSet.has(id)) {
16102
- const rawChecked = getValue(isCheckedFieldName, raw);
16103
- if (!isNullOrUndefined(rawChecked) && rawChecked.toString() === 'true') {
16071
+ let parentChecked = null;
16072
+ if (levelMapper.dataSource instanceof DataManager && level > 1 && this.autoCheck && parentId) {
16073
+ if (this.virtualCheckedNodesSet.has(parentId) ||
16074
+ (this.checkedNodes && this.checkedNodes.indexOf(parentId) !== -1)) {
16075
+ parentChecked = true;
16076
+ }
16077
+ else {
16078
+ parentChecked = false;
16079
+ }
16080
+ }
16081
+ const rawChecked = isCheckedFieldName && getValue(isCheckedFieldName, raw);
16082
+ if (enableVirtualization && id && !this.virtualCheckedNodesSet.has(id)) {
16083
+ if ((!isNullOrUndefined(rawChecked) && rawChecked.toString() === 'true') ||
16084
+ (levelMapper.dataSource instanceof DataManager && parentChecked === true)) {
16104
16085
  this.virtualCheckedNodesSet.add(id);
16105
16086
  if (this.checkedNodes && this.checkedNodes.indexOf(id) === -1) {
16106
16087
  this.checkedNodes.push(id);
16107
16088
  }
16108
16089
  }
16109
16090
  }
16110
- // Use cached Sets for O(1) lookup
16111
- const isSelected = this.virtualSelectedNodesSet.has(id);
16112
- // Resolve checkbox state: indeterminate takes precedence over the checked set,
16113
- // because a node can be in the indeterminate state without being in checkedNodes.
16091
+ else if (enableVirtualization && id && this.virtualCheckedNodesSet.has(id) && levelMapper.dataSource instanceof DataManager) {
16092
+ if (level > 1 && !this.virtualIndeterminateNodesSet.has(parentId) && this.autoCheck &&
16093
+ ((!isNullOrUndefined(rawChecked) && rawChecked.toString() === 'false') || parentChecked === false)) {
16094
+ this.virtualCheckedNodesSet.delete(id);
16095
+ if (this.checkedNodes && this.checkedNodes.indexOf(id) === -1) {
16096
+ this.checkedNodes.splice(this.checkedNodes.indexOf(id), 1);
16097
+ }
16098
+ }
16099
+ }
16100
+ if (this.initialRender && enableVirtualization && id && selectedFieldName && !this.virtualSelectedNodesSet.has(id)) {
16101
+ const rawSelected = getValue(selectedFieldName, raw);
16102
+ if (!isNullOrUndefined(rawSelected) && rawSelected.toString() === 'true') {
16103
+ this.virtualSelectedNodesSet.add(id);
16104
+ if (this.selectedNodes && this.selectedNodes.indexOf(id) === -1) {
16105
+ this.selectedNodes.push(id);
16106
+ }
16107
+ }
16108
+ }
16109
+ const selected = this.virtualSelectedNodesSet.has(id);
16114
16110
  const isChecked = (enableVirtualization && this.virtualIndeterminateNodesSet.has(id))
16115
16111
  ? 'indeterminate'
16116
16112
  : this.virtualCheckedNodesSet.has(id);
16117
- const isDisabled = false; // TODO: Check if any per-node disabled field exists
16118
16113
  const iconCssVal = raw[iconCssFieldName];
16119
16114
  const iconCss = isNullOrUndefined(iconCssVal) ? '' : iconCssVal.toString();
16120
16115
  const imageUrlVal = raw[imageUrlFieldName];
16121
16116
  const imageUrl = isNullOrUndefined(imageUrlVal) ? '' : imageUrlVal.toString();
16122
16117
  const node = {
16123
- id, text, level, parentId, hasChildren, isExpanded,
16124
- isSelected, isChecked, isDisabled, iconCss, imageUrl, rawData: raw
16118
+ id, text, level, parentId, hasChildren, expanded,
16119
+ selected, isChecked, iconCss, imageUrl
16125
16120
  };
16126
16121
  result.push(node);
16127
- if (isExpanded && hasChildren) {
16128
- // Determine children list
16122
+ if (expanded && hasChildren) {
16129
16123
  let children = [];
16130
16124
  if (isDataTypeFlat) {
16131
16125
  if (this.groupedData) {
16132
16126
  children = this.getChildGroup(this.groupedData, id, false);
16133
- // Apply sortOrder to flat-data children (same as hierarchical path)
16134
16127
  if (needsSorting) {
16135
16128
  children = ListBase.getDataSource(children, ListBase.addSorting(this.sortOrder, this.fields.text));
16136
16129
  }
16137
16130
  }
16138
16131
  }
16139
16132
  else {
16140
- const rawChildren = getValue(childFieldName, raw);
16133
+ const rawChildren = childFieldName ? getValue(childFieldName, raw) : null;
16141
16134
  if (!isNullOrUndefined(rawChildren) && Array.isArray(rawChildren)) {
16142
16135
  children = rawChildren;
16143
- // Only sort if needed
16144
16136
  if (needsSorting) {
16145
16137
  children = ListBase.getDataSource(children, ListBase.addSorting(this.sortOrder, this.fields.text));
16146
16138
  }
@@ -16150,35 +16142,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16150
16142
  }
16151
16143
  }
16152
16144
  }
16153
- /**
16154
- * Checks whether a raw datasource node has child records.
16155
- *
16156
- * @param {Object} raw - Raw datasource object.
16157
- * @returns {boolean} True if children exist.
16158
- * @private
16159
- */
16160
- hasChildRecords(raw) {
16161
- if (this.dataType === 1) {
16162
- if (!this.groupedData) {
16163
- return false;
16164
- }
16165
- const rawId = raw[this.fields.id];
16166
- if (isNullOrUndefined(rawId)) {
16167
- return false;
16168
- }
16169
- const groupedChild = this.getChildGroup(this.groupedData, rawId.toString(), false);
16170
- return !isNullOrUndefined(groupedChild) && groupedChild.length > 0;
16171
- }
16172
- const childField = this.fields.child.toString();
16173
- const children = getValue(childField, raw);
16174
- return !isNullOrUndefined(children) && Array.isArray(children) && children.length > 0;
16175
- }
16176
- /**
16177
- * Rebuilds `virtualNodeMap` (id → index) from the current `virtualFlatNodes`.
16178
- *
16179
- * @returns {void}
16180
- * @private
16181
- */
16182
16145
  rebuildVirtualNodeMap() {
16183
16146
  this.virtualNodeMap.clear();
16184
16147
  for (let i = 0; i < this.virtualFlatNodes.length; i++) {
@@ -16211,49 +16174,12 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16211
16174
  const paddingBottom = Math.max(0, (total - end) * itemH);
16212
16175
  return { paddingTop, paddingBottom };
16213
16176
  }
16214
- /**
16215
- * Renders the virtual window into a **nested** `<ul>/<li>` hierarchy identical to
16216
- * the non-virtualized DOM structure.
16217
- *
16218
- * ## Scroll math
16219
- * The flat-index ↔ pixel mapping stays unchanged: every entry in
16220
- * `virtualFlatNodes` represents exactly one `itemHeight` row.
16221
- * `paddingTop = startIndex * itemHeight` and
16222
- * `paddingBottom = (total - endIndex) * itemHeight` keep the scrollbar
16223
- * thumb in the correct position.
16224
- *
16225
- * ## Ancestor injection
16226
- * When `startIndex` points at a child node whose ancestor(s) have already
16227
- * scrolled above the viewport (their flat indices are < startIndex), the
16228
- * ancestors would normally be missing from the DOM, forcing the children to
16229
- * fall back to the root `<ul>` as flat siblings — producing wrong HTML.
16230
- *
16231
- * To fix this, before rendering the window we walk the ancestor chain of the
16232
- * first visible node and collect every ancestor that lies **outside** the
16233
- * window. Those ancestors are injected as real `<li>` containers at the
16234
- * very top of the root `<ul>`. To compensate for their presence, `paddingTop`
16235
- * is reduced by `injectedCount * itemHeight` so the total scroll height
16236
- * remains the same.
16237
- *
16238
- * Once the ancestors are in the DOM they are registered in `renderedParentMap`
16239
- * so the normal window-rendering loop can nest children inside them exactly
16240
- * as it does when both parent and child are in the same window.
16241
- *
16242
- * @returns {void}
16243
- * @private
16244
- */
16245
16177
  renderVirtualItems() {
16246
16178
  if (!this.ulElement) {
16247
16179
  return;
16248
16180
  }
16249
16181
  const ul = this.ulElement;
16250
- // ── Snapshot focused ID BEFORE clearing the DOM ───────────────────────────
16251
- // Removing a focused <li> from the DOM triggers a synchronous `blur` event,
16252
- // which calls focusOut() and sets virtualFocusedId = null. Capturing the id
16253
- // here (before any removeChild) ensures focus is correctly restored to the
16254
- // same node after re-render even when keyboard navigation was in progress.
16255
16182
  const focusedUid = this.virtualFocusedId || null;
16256
- // ── Clear previous render (direct children only) ─────────────────────────
16257
16183
  const existingItems = ul.querySelectorAll(':scope > li');
16258
16184
  for (let i = existingItems.length - 1; i >= 0; i--) {
16259
16185
  ul.removeChild(existingItems[parseInt(i.toString(), 10)]);
@@ -16264,26 +16190,22 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16264
16190
  if (this.virtualStartIndex < this.virtualFlatNodes.length) {
16265
16191
  const firstNode = this.virtualFlatNodes[this.virtualStartIndex];
16266
16192
  if (firstNode.parentId) {
16267
- // Build the ancestor chain from root down to the immediate parent
16268
16193
  const ancestors = [];
16269
16194
  let searchId = firstNode.parentId;
16270
16195
  while (searchId) {
16271
16196
  const ancestorIdx = this.virtualNodeMap.get(searchId);
16272
16197
  if (!isNullOrUndefined(ancestorIdx) && ancestorIdx < this.virtualStartIndex) {
16273
16198
  const ancestor = this.virtualFlatNodes[parseInt(ancestorIdx.toString(), 10)];
16274
- ancestors.unshift(ancestor); // prepend so root comes first
16199
+ ancestors.unshift(ancestor);
16275
16200
  searchId = ancestor.parentId;
16276
16201
  }
16277
16202
  else {
16278
16203
  break;
16279
16204
  }
16280
16205
  }
16281
- // Inject each ancestor as a real <li> into the root <ul>
16282
- // and register it in renderedParentMap for child-nesting below.
16283
16206
  for (let a = 0; a < ancestors.length; a++) {
16284
16207
  const ancestor = ancestors[parseInt(a.toString(), 10)];
16285
16208
  const ancestorLi = this.createVirtualListItem(ancestor, false);
16286
- // Find the correct container: root or a previously injected ancestor's <ul>
16287
16209
  let targetUl = ul;
16288
16210
  if (ancestor.parentId && renderedParentMap.has(ancestor.parentId)) {
16289
16211
  const pe = renderedParentMap.get(ancestor.parentId);
@@ -16302,26 +16224,20 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16302
16224
  }
16303
16225
  }
16304
16226
  }
16305
- // ── Scroll-space padding (adjusted for injected ancestors) ────────────────
16306
16227
  const total = this.virtualFlatNodes.length;
16307
16228
  const start = this.virtualStartIndex;
16308
16229
  const end = this.virtualEndIndex;
16309
16230
  const rawPaddingTop = start * itemH;
16310
- // Subtract the height of injected ancestors so the scroll position is
16311
- // exact: those ancestor rows are now real DOM elements, not hidden space.
16312
16231
  const adjustedPaddingTop = Math.max(0, rawPaddingTop - injectedCount * itemH);
16313
16232
  ul.style.paddingTop = adjustedPaddingTop + 'px';
16314
16233
  ul.style.paddingBottom = Math.max(0, (total - end) * itemH) + 'px';
16315
- // ── Render window items (nested via renderedParentMap) ────────────────────
16316
16234
  const liListBuf = [];
16317
16235
  for (let i = start; i < end; i++) {
16318
16236
  const node = this.virtualFlatNodes[parseInt(i.toString(), 10)];
16319
16237
  const li = this.createVirtualListItem(node, i === start);
16320
- // Find the correct parent container
16321
16238
  let targetUl = ul;
16322
16239
  if (node.parentId && renderedParentMap.has(node.parentId)) {
16323
16240
  const parentEntry = renderedParentMap.get(node.parentId);
16324
- // Lazily create the child <ul> the first time a child needs it
16325
16241
  if (!parentEntry.childUl) {
16326
16242
  parentEntry.childUl = this.createElement('ul', {
16327
16243
  className: PARENTITEM + ' e-ul',
@@ -16333,30 +16249,11 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16333
16249
  }
16334
16250
  targetUl.appendChild(li);
16335
16251
  liListBuf.push(li);
16336
- // Register this node so its children in the same window can nest under it
16337
- if (node.isExpanded && node.hasChildren) {
16252
+ if (node.expanded && node.hasChildren) {
16338
16253
  renderedParentMap.set(node.id, { li, childUl: null });
16339
16254
  }
16340
16255
  }
16341
- // ── Restore focus / tabindex ──────────────────────────────────────────────
16342
- // In virtual mode only the li that carries the 'e-active' class should be
16343
- // focusable (tabindex='0'). All other rendered li elements must be set
16344
- // to tabindex='-1' so the tab order remains unambiguous as the window
16345
- // changes during scrolling.
16346
16256
  let focusRestored = false;
16347
- // 1. Prefer the currently active node if it is in the rendered window.
16348
- for (let k = 0; k < liListBuf.length; k++) {
16349
- const li = liListBuf[parseInt(k.toString(), 10)];
16350
- if (li.classList.contains(ACTIVE)) {
16351
- li.setAttribute('tabindex', '0');
16352
- // ── Fix: ensure FOCUS class set on active node too ──
16353
- // This ensures focusIn() guard condition is met: tabindex='0' && FOCUS class
16354
- addClass([li], FOCUS);
16355
- focusRestored = true;
16356
- break;
16357
- }
16358
- }
16359
- // 2. Otherwise fall back to the previously focused node (if it is in window).
16360
16257
  if (!focusRestored && focusedUid) {
16361
16258
  for (let k = 0; k < liListBuf.length; k++) {
16362
16259
  const li = liListBuf[parseInt(k.toString(), 10)];
@@ -16364,22 +16261,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16364
16261
  li.setAttribute('tabindex', '0');
16365
16262
  addClass([li], FOCUS);
16366
16263
  addClass([li], FOCUSED$1);
16367
- // ── Fix: restore actual browser focus so keyboard events keep working ──
16368
- // After renderVirtualItems() replaces all <li> elements the old element
16369
- // is gone from the DOM and document.activeElement falls back to
16370
- // document.body. Setting tabindex + classes is not enough — the new
16371
- // element also needs a programmatic .focus() call so subsequent keyboard
16372
- // events (Arrow keys, Home, End, etc.) are delivered to the TreeView.
16373
- // Re-attach the blur→focusOut handler so virtualFocusedId is cleared
16374
- // correctly when the user tabs away.
16375
- // ── Use preventScroll:true ────────────────────────────────────────────
16376
- // Without preventScroll the browser scrolls the container to bring this
16377
- // node into view during the re-render, changing scrollTop AFTER the
16378
- // correct paddingTop has been applied. This is the root cause of the
16379
- // KBN expand position-jump bug: the render is correct but the browser
16380
- // silently scrolls the viewport, making the parent node appear to jump.
16381
- // ensureVirtualNodeVisible() handles any legitimate scroll-into-view
16382
- // needs before renderVirtualItems() is called, so scroll here is wrong.
16383
16264
  EventHandler.add(li, 'blur', this.focusOut, this);
16384
16265
  li.focus({ preventScroll: true });
16385
16266
  focusRestored = true;
@@ -16387,53 +16268,36 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16387
16268
  }
16388
16269
  }
16389
16270
  }
16390
- // 3. Finally default to the first item in the window.
16391
16271
  if (!focusRestored && liListBuf.length > 0) {
16392
16272
  const firstLi = liListBuf[0];
16393
16273
  firstLi.setAttribute('tabindex', '0');
16394
16274
  }
16395
- // ── Fix: Ensure all other rendered nodes have tabindex="-1" ──
16396
- // This prevents stale tabindex="0" on multiple nodes which breaks Tab order
16397
16275
  for (let k = 0; k < liListBuf.length; k++) {
16398
16276
  const li = liListBuf[parseInt(k.toString(), 10)];
16399
16277
  if (li.getAttribute('tabindex') !== '0') {
16400
16278
  li.setAttribute('tabindex', '-1');
16401
16279
  }
16402
16280
  }
16403
- // Keep liList in sync so keyboard-navigation helpers stay functional.
16404
16281
  this.liList = liListBuf;
16405
16282
  }
16406
- /**
16407
- * Creates a single `<li>` element for the virtual window representing `node`.
16408
- *
16409
- * @param {VirtualNodeData} node - The node data.
16410
- * @param {boolean} _isFirst - True if this is the first rendered item (unused here but kept for potential override).
16411
- * @returns {HTMLElement} The constructed `<li>` element.
16412
- * @private
16413
- */
16414
16283
  createVirtualListItem(node, _isFirst) {
16415
16284
  const li = this.createElement('li', { className: LISTITEM });
16416
- // Level class for CSS indentation
16417
16285
  li.classList.add('e-level-' + node.level);
16418
16286
  li.setAttribute('data-uid', node.id);
16419
16287
  li.setAttribute('aria-level', node.level.toString());
16420
16288
  li.setAttribute('role', 'treeitem');
16421
16289
  li.setAttribute('tabindex', '-1');
16422
- // Full-row overlay (required for fullRowSelect mode)
16423
16290
  if (this.fullRowSelect) {
16424
16291
  const fullRow = this.createElement('div', { className: FULLROW });
16425
16292
  li.appendChild(fullRow);
16426
16293
  }
16427
- // Text/icon wrapper
16428
16294
  const textWrap = this.createElement('div', { className: TEXTWRAP });
16429
- // Expand/collapse icon
16430
16295
  if (node.hasChildren) {
16431
16296
  const iconSpan = this.createElement('div', {
16432
- className: ICON + ' ' + (node.isExpanded ? COLLAPSIBLE : EXPANDABLE)
16297
+ className: ICON + ' ' + (node.expanded ? COLLAPSIBLE : EXPANDABLE)
16433
16298
  });
16434
16299
  textWrap.appendChild(iconSpan);
16435
16300
  }
16436
- // Checkbox
16437
16301
  if (this.showCheckBox) {
16438
16302
  const checkWrap = createCheckBox(this.createElement, false, {
16439
16303
  checked: node.isChecked === true,
@@ -16447,29 +16311,32 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16447
16311
  }
16448
16312
  textWrap.appendChild(checkWrap);
16449
16313
  }
16450
- // Icon
16451
16314
  if (node.iconCss) {
16452
16315
  const iconEl = this.createElement('span', {
16453
16316
  className: ICON + ' ' + node.iconCss
16454
16317
  });
16455
16318
  textWrap.appendChild(iconEl);
16456
16319
  }
16457
- // Image
16458
16320
  if (node.imageUrl) {
16459
16321
  const img = document.createElement('img');
16460
16322
  img.src = node.imageUrl;
16461
16323
  img.classList.add(IMAGE);
16462
16324
  textWrap.appendChild(img);
16463
16325
  }
16464
- // Text anchor
16465
16326
  const anchor = this.createElement('span', {
16466
16327
  className: 'e-list-text',
16467
16328
  innerHTML: node.text
16468
16329
  });
16469
16330
  textWrap.appendChild(anchor);
16470
16331
  li.appendChild(textWrap);
16471
- // State classes
16472
- if (node.isExpanded) {
16332
+ if (!isNullOrUndefined(this.nodeTemplateFn)) {
16333
+ const rawData = this.findNodeInTreeData(node.id);
16334
+ if (!isNullOrUndefined(rawData)) {
16335
+ anchor.innerHTML = '';
16336
+ this.renderNodeTemplate(rawData, anchor, node.id);
16337
+ }
16338
+ }
16339
+ if (node.expanded) {
16473
16340
  li.classList.add(EXPANDED);
16474
16341
  li.setAttribute('aria-expanded', 'true');
16475
16342
  }
@@ -16477,91 +16344,50 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16477
16344
  li.classList.add(NODECOLLAPSED);
16478
16345
  li.setAttribute('aria-expanded', 'false');
16479
16346
  }
16480
- if (node.isSelected) {
16347
+ if (node.selected) {
16481
16348
  li.classList.add(ACTIVE);
16482
16349
  li.setAttribute('aria-selected', 'true');
16483
16350
  }
16484
- if (node.isDisabled) {
16485
- li.classList.add(DISABLED$1);
16486
- li.setAttribute('aria-disabled', 'true');
16487
- }
16488
16351
  if (node.hasChildren) {
16489
16352
  li.classList.add(CHILD);
16490
16353
  }
16491
- // Wire the focus event so `focusIn()` fires when the user Tabs into this
16492
- // item. The non-virtualized path registers this in `beforeNodeCreate()`,
16493
- // but virtual items never go through that callback, so we must register
16494
- // it here to keep focus styling (e.g. `e-focused`) consistent between
16495
- // virtual and non-virtual modes. `unWireEvents()` already removes this
16496
- // handler during teardown by iterating over the rendered `<li>`s.
16497
16354
  EventHandler.add(li, 'focus', this.focusIn, this);
16498
16355
  return li;
16499
16356
  }
16500
- /**
16501
- * Initialises the virtual-scrolling layout:
16502
- * 1. Adds the `e-virtualization` CSS class and applies a fixed `height` + `overflow-y`.
16503
- * 2. Flattens all visible nodes into `virtualFlatNodes`.
16504
- * 3. Measures item height and viewport height.
16505
- * 4. Computes the initial window and renders it.
16506
- * 5. Wires the scroll event handler.
16507
- *
16508
- * @returns {void}
16509
- * @private
16510
- */
16511
16357
  initVirtualization() {
16512
16358
  addClass([this.element], VIRTUALIZATION);
16513
- // Apply container height
16514
16359
  const px = this.parseHeightToPixels();
16515
16360
  if (px > 0) {
16516
16361
  this.element.style.height = px + 'px';
16517
16362
  this.element.style.overflowY = 'auto';
16518
16363
  }
16519
- // Ensure the inner UL is ready
16520
16364
  if (!this.ulElement) {
16521
16365
  this.ulElement = select('.' + PARENTITEM, this.element);
16522
16366
  }
16523
16367
  if (!this.ulElement) {
16524
16368
  return;
16525
16369
  }
16526
- // Make the UL position-relative for the absolute spacers
16527
16370
  this.ulElement.style.position = 'relative';
16528
- // Rebuild flat list
16529
16371
  this.virtualFlatNodes = this.flattenVisibleNodes();
16530
16372
  this.rebuildVirtualNodeMap();
16531
- // Measure dimensions
16532
16373
  this.virtualViewportHeight = px > 0 ? px : this.element.offsetHeight;
16533
- // Render a single probe item to measure the real item height before the full render
16534
16374
  if (this.virtualFlatNodes.length > 0) {
16535
16375
  const probe = this.createVirtualListItem(this.virtualFlatNodes[0], true);
16536
16376
  this.ulElement.appendChild(probe);
16537
16377
  this.virtualItemHeight = probe.offsetHeight > 0 ? probe.offsetHeight : this.virtualItemHeight;
16538
16378
  this.ulElement.removeChild(probe);
16539
16379
  }
16540
- // Compute window and render all items in the initial viewport
16541
16380
  const win = this.getVirtualWindow();
16542
16381
  this.virtualStartIndex = win.startIndex;
16543
16382
  this.virtualEndIndex = win.endIndex;
16544
16383
  this.renderVirtualItems();
16545
- // Wire scroll
16546
16384
  this.wireVirtualScrollEvents();
16547
16385
  }
16548
- /**
16549
- * Wires the scroll event listener on the TreeView element.
16550
- *
16551
- * @returns {void}
16552
- * @private
16553
- */
16554
16386
  wireVirtualScrollEvents() {
16555
16387
  this.unwireVirtualScrollEvents();
16556
16388
  this.virtualScrollBound = this.onVirtualScroll.bind(this);
16557
16389
  this.element.addEventListener('scroll', this.virtualScrollBound, { passive: true });
16558
16390
  }
16559
- /**
16560
- * Removes the scroll event listener previously wired by `wireVirtualScrollEvents`.
16561
- *
16562
- * @returns {void}
16563
- * @private
16564
- */
16565
16391
  unwireVirtualScrollEvents() {
16566
16392
  if (this.virtualScrollBound) {
16567
16393
  this.element.removeEventListener('scroll', this.virtualScrollBound);
@@ -16572,28 +16398,12 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16572
16398
  this.virtualScrollRAF = null;
16573
16399
  }
16574
16400
  }
16575
- /**
16576
- * Scroll handler for the virtual TreeView. Throttled via `requestAnimationFrame`.
16577
- * Shows the loading mask during fast scrolls, re-renders the window, then hides the mask.
16578
- *
16579
- * @returns {void}
16580
- * @private
16581
- */
16582
16401
  onVirtualScroll() {
16583
16402
  if (this.virtualScrollRAF !== null) {
16584
16403
  cancelAnimationFrame(this.virtualScrollRAF);
16585
16404
  }
16586
- // ── Tab-induced focus guard ────────────────────────────────────────────────
16587
- // When the user Tabs into a `<li tabindex="0">` that is in the middle of
16588
- // the treeview, the browser auto-scrolls the container so the focused
16589
- // element becomes visible. That fires a `scroll` event here, but the
16590
- // virtual window already contains the focused element — re-rendering
16591
- // would destroy the focused `<li>` and break focus. Detect this case
16592
- // via the flag set in `keyDownHandler` and skip the re-render entirely.
16593
16405
  if (this.virtualSkipNextScrollRender) {
16594
16406
  this.virtualSkipNextScrollRender = false;
16595
- // Update prev scrollTop so a subsequent user-driven scroll still
16596
- // calculates the correct delta.
16597
16407
  this.virtualPrevScrollTop = this.element.scrollTop;
16598
16408
  return;
16599
16409
  }
@@ -16610,30 +16420,16 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16610
16420
  this.virtualStartIndex = win.startIndex;
16611
16421
  this.virtualEndIndex = win.endIndex;
16612
16422
  this.renderVirtualItems();
16613
- // Hide the skeleton mask after a short delay so it stays visible long enough
16614
- // for the user to perceive the loading indicator even on short scrolls.
16615
16423
  setTimeout(() => {
16616
16424
  this.hideVirtualMask();
16617
- // Keep the (now hidden) mask's coords fresh for the next show.
16618
16425
  this.updateMaskPosition();
16619
16426
  }, 200);
16620
16427
  });
16621
16428
  }
16622
- /**
16623
- * Creates the skeleton mask overlay used to indicate loading during fast scrolls.
16624
- * Skeleton instances are stored in `skeletonInstances` for later disposal.
16625
- *
16626
- * @returns {void}
16627
- * @private
16628
- */
16629
16429
  createVirtualMask() {
16630
16430
  if (this.virtualMaskElement) {
16631
16431
  return;
16632
16432
  }
16633
- // Use position:fixed so the mask is anchored to the visible viewport (not to the
16634
- // scrolling container). Width/height/left/top are set/refreshed by
16635
- // updateMaskPosition() so the mask always covers the TreeView's visible area
16636
- // regardless of how the page itself is scrolled.
16637
16433
  const rect = this.element.getBoundingClientRect();
16638
16434
  const scrollBarWidth = this.element.offsetWidth - this.element.clientWidth;
16639
16435
  const maskParent = this.createElement('div', {
@@ -16646,20 +16442,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16646
16442
  className: VIRTUAL_MASK_ITEM,
16647
16443
  styles: `display:flex;align-items:center;height:${this.virtualItemHeight}px;padding:0 12px;gap:8px;`
16648
16444
  });
16649
- // Skeleton circle for icon placeholder
16650
- // const iconHolder: HTMLElement = this.createElement('span', {
16651
- // styles: `width:20px;height:20px;`
16652
- // });
16653
- // maskItem.appendChild(iconHolder);
16654
- // const iconSkel: Skeleton = new Skeleton({
16655
- // shape: 'Circle',
16656
- // width: '20px',
16657
- // height: '20px',
16658
- // cssClass: 'e-virtual-skel-icon'
16659
- // });
16660
- // iconSkel.appendTo(iconHolder);
16661
- // this.skeletonInstances.push(iconSkel);
16662
- // Skeleton text bar
16663
16445
  const textHolder = this.createElement('span');
16664
16446
  textHolder.style.flex = '1';
16665
16447
  textHolder.style.height = '14px';
@@ -16677,14 +16459,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16677
16459
  this.virtualMaskElement = maskParent;
16678
16460
  this.element.appendChild(maskParent);
16679
16461
  }
16680
- /**
16681
- * Refreshes the mask position so it always overlays the TreeView's viewport.
16682
- * Called on every show / scroll, since `position:fixed` is relative to the
16683
- * page viewport, not to the TreeView scroll container.
16684
- *
16685
- * @returns {void}
16686
- * @private
16687
- */
16688
16462
  updateMaskPosition() {
16689
16463
  if (!this.virtualMaskElement) {
16690
16464
  return;
@@ -16696,12 +16470,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16696
16470
  this.virtualMaskElement.style.width = (rect.width - scrollBarWidth) + 'px';
16697
16471
  this.virtualMaskElement.style.height = rect.height + 'px';
16698
16472
  }
16699
- /**
16700
- * Destroys all skeleton instances and removes the mask overlay from the DOM.
16701
- *
16702
- * @returns {void}
16703
- * @private
16704
- */
16705
16473
  destroyVirtualMask() {
16706
16474
  for (let i = this.skeletonInstances.length - 1; i >= 0; i--) {
16707
16475
  this.skeletonInstances[parseInt(i.toString(), 10)].destroy();
@@ -16714,52 +16482,26 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16714
16482
  this.virtualMaskElement = null;
16715
16483
  }
16716
16484
  }
16717
- /**
16718
- * Positions and shows the skeleton mask at the current `scrollTop` position.
16719
- *
16720
- * @param {number} _scrollTop - Current scrollTop of the container.
16721
- * @returns {void}
16722
- * @private
16723
- */
16724
16485
  showVirtualMask(_scrollTop) {
16725
16486
  if (!this.virtualMaskElement) {
16726
16487
  this.createVirtualMask();
16727
16488
  }
16728
16489
  if (this.virtualMaskElement) {
16729
- // Refresh the fixed-position coords in case the TreeView was moved/resized
16730
- // since the last time the mask was shown.
16731
16490
  this.updateMaskPosition();
16732
16491
  this.virtualMaskElement.style.display = 'block';
16733
- // Visually hide the real list while the skeleton is shown
16734
16492
  if (this.ulElement) {
16735
16493
  this.ulElement.style.visibility = 'hidden';
16736
16494
  }
16737
16495
  }
16738
16496
  }
16739
- /**
16740
- * Hides the skeleton mask overlay after the virtual window has been rendered.
16741
- *
16742
- * @returns {void}
16743
- * @private
16744
- */
16745
16497
  hideVirtualMask() {
16746
16498
  if (this.virtualMaskElement) {
16747
16499
  this.virtualMaskElement.style.display = 'none';
16748
- // Restore real list visibility
16749
16500
  if (this.ulElement) {
16750
16501
  this.ulElement.style.visibility = '';
16751
16502
  }
16752
16503
  }
16753
16504
  }
16754
- /**
16755
- * Ensures the node identified by `id` is scrolled into view inside the virtual container.
16756
- * If the node falls outside the rendered window, the container's `scrollTop` is adjusted so
16757
- * that the node becomes visible, the window is re-computed and `renderVirtualItems` is called.
16758
- *
16759
- * @param {string} id - The `data-uid` of the target node.
16760
- * @returns {void}
16761
- * @private
16762
- */
16763
16505
  ensureVirtualNodeVisible(id) {
16764
16506
  const idx = this.virtualNodeMap.get(id);
16765
16507
  if (isNullOrUndefined(idx)) {
@@ -16778,20 +16520,52 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16778
16520
  this.element.scrollTop = nodeBottom - vpH;
16779
16521
  }
16780
16522
  else {
16781
- return; // already visible, no re-render needed
16523
+ return;
16782
16524
  }
16783
- // Re-render the new window synchronously so the <li> is in the DOM
16784
16525
  const win = this.getVirtualWindow();
16785
16526
  this.virtualStartIndex = win.startIndex;
16786
16527
  this.virtualEndIndex = win.endIndex;
16787
16528
  this.renderVirtualItems();
16788
16529
  }
16530
+ findNodeInTreeData(id) {
16531
+ if (!this.treeData) {
16532
+ return null;
16533
+ }
16534
+ const idFieldName = this.fields.id.toString();
16535
+ const childFieldName = this.fields.child.toString();
16536
+ const findRecursive = (nodes) => {
16537
+ if (!nodes || nodes.length === 0) {
16538
+ return null;
16539
+ }
16540
+ for (let i = 0; i < nodes.length; i++) {
16541
+ const node = nodes[i];
16542
+ if (node[idFieldName] && node[idFieldName].toString() === id) {
16543
+ return node;
16544
+ }
16545
+ const children = getValue(childFieldName, node);
16546
+ if (children && Array.isArray(children)) {
16547
+ const found = findRecursive(children);
16548
+ if (found) {
16549
+ return found;
16550
+ }
16551
+ }
16552
+ }
16553
+ return null;
16554
+ };
16555
+ return findRecursive(this.treeData);
16556
+ }
16557
+ getNodeLevel(id) {
16558
+ if (!this.virtualFlatNodes) {
16559
+ return 1;
16560
+ }
16561
+ for (let i = 0; i < this.virtualFlatNodes.length; i++) {
16562
+ if (this.virtualFlatNodes[parseInt(i.toString(), 10)].id === id) {
16563
+ return this.virtualFlatNodes[parseInt(i.toString(), 10)].level;
16564
+ }
16565
+ }
16566
+ return 1;
16567
+ }
16789
16568
  handleVirtualExpand(id, isKeyboardNav) {
16790
- // ── Cancel any pending scroll RAF ─────────────────────────────────────────
16791
- // A scroll RAF queued before the expand click would run asynchronously after
16792
- // this synchronous expand completes, re-rendering the window from a stale
16793
- // scrollTop and moving the parent node to the wrong position. Cancel it now
16794
- // so the expand render is authoritative.
16795
16569
  if (this.virtualScrollRAF !== null) {
16796
16570
  cancelAnimationFrame(this.virtualScrollRAF);
16797
16571
  this.virtualScrollRAF = null;
@@ -16799,64 +16573,84 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16799
16573
  if (this.expandedNodes.indexOf(id) === -1) {
16800
16574
  this.expandedNodes.push(id);
16801
16575
  }
16802
- // Also update virtualExpandedNodesSet to keep it in sync
16803
16576
  if (this.virtualExpandedNodesSet) {
16804
16577
  this.virtualExpandedNodesSet.add(id);
16805
16578
  }
16806
- if (this.rootData) {
16807
- const acutalRootData = this.rootData.find((item) => item.id === parseInt(id, 10));
16808
- acutalRootData.isExpanded = true;
16809
- }
16810
- // ── Fix: Pin focused ID to the expanding node for keyboard navigation ──
16811
- // When keyboard-triggered, the expanded node IS definitively the focused node
16812
- // (`id` comes from currLi.getAttribute('data-uid') in expandNode, where currLi
16813
- // is getFocusedNode()). Set unconditionally — the old conditional guard
16814
- // `!this.virtualFocusedId` could leave a stale or null value if a previous
16815
- // RAF render (from arrow-key scroll) hadn't yet completed, causing branch 3
16816
- // (first item) to fire in renderVirtualItems() instead of branch 2.
16817
- // For mouse clicks (isKeyboardNav=false), clear virtualFocusedId so that
16818
- // renderVirtualItems() Branch 2 does NOT apply the FOCUS/FOCUSED classes —
16819
- // mouse-based expand/collapse should never add the e-focused class.
16579
+ if (this.treeData) {
16580
+ const acutalRootData = this.findNodeInTreeData(id);
16581
+ if (acutalRootData && this.fields && this.fields.expanded) {
16582
+ setValue(this.fields.expanded, true, acutalRootData);
16583
+ }
16584
+ }
16820
16585
  if (isKeyboardNav) {
16821
16586
  this.virtualFocusedId = id;
16822
16587
  }
16823
16588
  else {
16824
16589
  this.virtualFocusedId = null;
16825
16590
  }
16591
+ if (this.fields.dataSource instanceof DataManager && !this.isOffline) {
16592
+ const parentNode = this.findNodeInTreeData(id);
16593
+ if (parentNode && !isNullOrUndefined(parentNode)) {
16594
+ const childFieldName = this.fields.child.toString();
16595
+ const childrenObj = getValue(childFieldName, parentNode);
16596
+ const childrenNotLoaded = isNullOrUndefined(childrenObj) || !Array.isArray(childrenObj) ||
16597
+ childrenObj.length === 0;
16598
+ if (childrenNotLoaded) {
16599
+ this.loadVirtualChildData(id, parentNode, () => {
16600
+ this.completeVirtualExpand(id, isKeyboardNav);
16601
+ });
16602
+ return;
16603
+ }
16604
+ }
16605
+ }
16606
+ this.completeVirtualExpand(id, isKeyboardNav);
16607
+ }
16608
+ loadVirtualChildData(parentId, parentNode, callback) {
16609
+ const level = this.getNodeLevel(parentId);
16610
+ const mapper = this.getChildFields(this.fields, level + 1, 1);
16611
+ if (isNullOrUndefined(mapper) || isNullOrUndefined(mapper.dataSource)) {
16612
+ if (callback) {
16613
+ callback();
16614
+ }
16615
+ return;
16616
+ }
16617
+ mapper.dataSource.executeQuery(this.getQuery(mapper, parentId)).then((e) => {
16618
+ const childItems = e.result;
16619
+ if (!isNullOrUndefined(childItems) && childItems.length > 0) {
16620
+ const childFieldName = this.fields.child.toString();
16621
+ setValue(childFieldName, childItems, parentNode);
16622
+ if (this.dataType === 2) {
16623
+ const nodeInTree = this.findNodeInTreeData(parentId);
16624
+ if (nodeInTree) {
16625
+ setValue(childFieldName, childItems, nodeInTree);
16626
+ }
16627
+ }
16628
+ }
16629
+ if (callback) {
16630
+ callback();
16631
+ }
16632
+ }).catch((e) => {
16633
+ this.trigger('actionFailure', { error: e });
16634
+ if (callback) {
16635
+ callback();
16636
+ }
16637
+ });
16638
+ }
16639
+ completeVirtualExpand(id, isKeyboardNav) {
16826
16640
  this.virtualFlatNodes = this.flattenVisibleNodes();
16827
16641
  this.rebuildVirtualNodeMap();
16828
- // ── Snapshot scrollTop before computing the window ────────────────────────
16829
- // Read scrollTop once here and pass it into getVirtualWindow so that any
16830
- // browser scroll-anchoring adjustment that happens when the padding values
16831
- // change cannot affect which window we render.
16832
16642
  const scrollTopSnapshot = this.element.scrollTop;
16833
16643
  const win = this.getVirtualWindowAt(scrollTopSnapshot);
16834
16644
  this.virtualStartIndex = win.startIndex;
16835
16645
  this.virtualEndIndex = win.endIndex;
16836
- // ── Block ALL scroll-triggered re-renders during this expand ─────────────
16837
- // renderVirtualItems() calls li.focus() in branch 2 (keyboard focus restore).
16838
- // The post-render focus() call below can also trigger browser scroll. Both
16839
- // can fire the 'scroll' event and cause onVirtualScroll() to queue a RAF.
16840
- // We set the skip flag ONCE here; it is consumed by the first scroll event.
16841
- // To cover the second potential scroll (from the focus() call below) we use
16842
- // preventScroll:true on that call so it never moves the container at all.
16843
16646
  this.virtualSkipNextScrollRender = true;
16844
16647
  this.renderVirtualItems();
16845
- // renderVirtualItems() replaces all <li> elements so the old icon element that
16846
- // had the PROCESS class is gone. Remove PROCESS from the newly rendered icon
16847
- // so the node can be collapsed/expanded again without being blocked.
16848
16648
  const newLi = this.getElement(id);
16849
16649
  if (newLi) {
16850
16650
  const icon = select('.' + ICON, newLi);
16851
16651
  if (icon) {
16852
16652
  removeClass([icon], PROCESS);
16853
16653
  }
16854
- // ── Use preventScroll:true ────────────────────────────────────────────
16855
- // Without preventScroll the browser scrolls the container to bring this
16856
- // node into view, which fires a second 'scroll' event. The first scroll
16857
- // was already consumed by virtualSkipNextScrollRender (set above), so the
16858
- // second scroll would queue a new RAF that re-renders from the
16859
- // focus-adjusted scrollTop — moving the parent node to the wrong position.
16860
16654
  newLi.focus({ preventScroll: true });
16861
16655
  }
16862
16656
  if (this.isLoaded) {
@@ -16869,9 +16663,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16869
16663
  }
16870
16664
  }
16871
16665
  handleVirtualCollapse(id, isKeyboardNav) {
16872
- // ── Cancel any pending scroll RAF ─────────────────────────────────────────
16873
- // Mirrors handleVirtualExpand: a queued RAF from a previous scroll event
16874
- // must not override the collapse render with a stale window.
16875
16666
  if (this.virtualScrollRAF !== null) {
16876
16667
  cancelAnimationFrame(this.virtualScrollRAF);
16877
16668
  this.virtualScrollRAF = null;
@@ -16880,21 +16671,15 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16880
16671
  if (idx !== -1) {
16881
16672
  this.expandedNodes.splice(idx, 1);
16882
16673
  }
16883
- // Also update virtualExpandedNodesSet to keep it in sync
16884
16674
  if (this.virtualExpandedNodesSet) {
16885
16675
  this.virtualExpandedNodesSet.delete(id);
16886
16676
  }
16887
- if (this.rootData) {
16888
- const acutalRootData = this.rootData.find((item) => item.id === parseInt(id, 10));
16889
- acutalRootData.isExpanded = false;
16890
- }
16891
- // ── Fix: Pin focused ID to the collapsing node for keyboard navigation ──
16892
- // Same reasoning as handleVirtualExpand: when keyboard-triggered, `id` is
16893
- // definitively the focused node. Set unconditionally to avoid the stale/null
16894
- // state that could cause the RAF re-render to fall through to branch 3.
16895
- // For mouse clicks (isKeyboardNav=false), clear virtualFocusedId so that
16896
- // renderVirtualItems() Branch 2 does NOT apply the FOCUS/FOCUSED classes —
16897
- // mouse-based expand/collapse should never add the e-focused class.
16677
+ if (this.treeData) {
16678
+ const acutalRootData = this.findNodeInTreeData(id);
16679
+ if (acutalRootData && this.fields && this.fields.expanded) {
16680
+ setValue(this.fields.expanded, false, acutalRootData);
16681
+ }
16682
+ }
16898
16683
  if (isKeyboardNav) {
16899
16684
  this.virtualFocusedId = id;
16900
16685
  }
@@ -16903,28 +16688,18 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16903
16688
  }
16904
16689
  this.virtualFlatNodes = this.flattenVisibleNodes();
16905
16690
  this.rebuildVirtualNodeMap();
16906
- // ── Snapshot scrollTop before computing the window ────────────────────────
16907
- // Same rationale as handleVirtualExpand: capture scrollTop before any focus()
16908
- // call or padding change can cause the browser to auto-scroll.
16909
16691
  const scrollTopSnapshot = this.element.scrollTop;
16910
16692
  const win = this.getVirtualWindowAt(scrollTopSnapshot);
16911
16693
  this.virtualStartIndex = win.startIndex;
16912
16694
  this.virtualEndIndex = win.endIndex;
16913
- // ── Block ALL scroll-triggered re-renders during this collapse ────────────
16914
- // Same as handleVirtualExpand: use preventScroll:true on the focus() call
16915
- // below so the second potential scroll event is never fired.
16916
16695
  this.virtualSkipNextScrollRender = true;
16917
16696
  this.renderVirtualItems();
16918
- // renderVirtualItems() replaces all <li> elements so the old icon element that
16919
- // had the PROCESS class is gone. Remove PROCESS from the newly rendered icon
16920
- // so the node can be expanded/collapsed again without being blocked.
16921
16697
  const newLi = this.getElement(id);
16922
16698
  if (newLi) {
16923
16699
  const icon = select('.' + ICON, newLi);
16924
16700
  if (icon) {
16925
16701
  removeClass([icon], PROCESS);
16926
16702
  }
16927
- // ── Use preventScroll:true to avoid consuming virtualSkipNextScrollRender ─
16928
16703
  newLi.focus({ preventScroll: true });
16929
16704
  }
16930
16705
  if (this.isLoaded) {
@@ -16936,36 +16711,18 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16936
16711
  }
16937
16712
  }
16938
16713
  }
16939
- /**
16940
- * Updates the selected state of a node in `virtualFlatNodes` and re-renders the window.
16941
- *
16942
- * @param {string} id - The `data-uid` of the target node.
16943
- * @param {boolean} isSelected - Whether the node is now selected.
16944
- * @returns {void}
16945
- * @private
16946
- */
16947
- updateVirtualNodeSelection(id, isSelected) {
16714
+ updateVirtualNodeSelection(id, selected) {
16948
16715
  const idx = this.virtualNodeMap.get(id);
16949
16716
  if (!isNullOrUndefined(idx)) {
16950
- this.virtualFlatNodes[parseInt(idx.toString(), 10)].isSelected = isSelected;
16717
+ this.virtualFlatNodes[parseInt(idx.toString(), 10)].selected = selected;
16951
16718
  this.renderVirtualItems();
16952
16719
  }
16953
16720
  }
16954
- /**
16955
- * Updates the checkbox state of a node in `virtualFlatNodes` and re-renders the window.
16956
- *
16957
- * @param {string} id - The `data-uid` of the target node.
16958
- * @param {'check' | 'uncheck' | 'indeterminate'} state - The new check state.
16959
- * @returns {void}
16960
- * @private
16961
- */
16962
16721
  updateVirtualNodeCheckState(id, state) {
16963
16722
  const idx = this.virtualNodeMap.get(id);
16964
16723
  if (!isNullOrUndefined(idx)) {
16965
16724
  this.virtualFlatNodes[parseInt(idx.toString(), 10)].isChecked =
16966
16725
  state === 'check' ? true : state === 'indeterminate' ? 'indeterminate' : false;
16967
- // Keep the persistent indeterminate set in sync so flattenVisibleNodes()
16968
- // correctly restores state after any full list rebuild (e.g. collapse).
16969
16726
  if (state === 'indeterminate') {
16970
16727
  this.virtualIndeterminateNodesSet.add(id);
16971
16728
  }
@@ -16975,9 +16732,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
16975
16732
  this.renderVirtualItems();
16976
16733
  }
16977
16734
  }
16978
- // ═══════════════════════════════════════════════════════════════════════════════
16979
- // END VIRTUALIZATION
16980
- // ═══════════════════════════════════════════════════════════════════════════════
16981
16735
  /**
16982
16736
  * Called internally if any of the property value changed.
16983
16737
  *
@@ -17121,7 +16875,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17121
16875
  }
17122
16876
  }
17123
16877
  else {
17124
- // Tear down virtualization and do a full re-render
17125
16878
  this.unwireVirtualScrollEvents();
17126
16879
  this.destroyVirtualMask();
17127
16880
  removeClass([this.element], VIRTUALIZATION);
@@ -17132,7 +16885,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17132
16885
  break;
17133
16886
  case 'height':
17134
16887
  if (this.enableVirtualization) {
17135
- // Re-initialise so the new height takes effect
17136
16888
  this.unwireVirtualScrollEvents();
17137
16889
  this.initVirtualization();
17138
16890
  }
@@ -17150,7 +16902,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17150
16902
  * @returns {void}
17151
16903
  */
17152
16904
  destroy() {
17153
- // Tear down virtualization before standard cleanup
17154
16905
  if (this.enableVirtualization) {
17155
16906
  this.unwireVirtualScrollEvents();
17156
16907
  this.destroyVirtualMask();
@@ -17241,10 +16992,6 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17241
16992
  if (isNullOrUndefined(fNode)) {
17242
16993
  this.setNodeFocusable();
17243
16994
  }
17244
- // In virtual mode (with a fixed height), rebuild the flat node list and re-render
17245
- // the window so the newly added nodes appear in the correct hierarchical position.
17246
- // When height is not provided, the tree uses normal rendering and the DOM is
17247
- // already updated by addGivenNodes() above.
17248
16995
  if (this.enableVirtualization && !isNullOrUndefined(this.height)) {
17249
16996
  this.setRootData();
17250
16997
  this.rebuildVirtualCheckCache();
@@ -17313,7 +17060,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17313
17060
  }
17314
17061
  for (let i = 0; i < nodes.length; i++) {
17315
17062
  const node = nodes[parseInt(i.toString(), 10)];
17316
- node['isExpanded'] = false;
17063
+ node['expanded'] = false;
17317
17064
  const children = getValue(this.fields.child.toString(), node);
17318
17065
  if (children && Array.isArray(children)) {
17319
17066
  setAllCollapsed(children);
@@ -17413,7 +17160,7 @@ let TreeView = TreeView_1 = class TreeView extends Component {
17413
17160
  const nodeId = getValue(idFieldName, node);
17414
17161
  if (node.hasChild) {
17415
17162
  allParentIds.push(nodeId);
17416
- node.isExpanded = true;
17163
+ node.expanded = true;
17417
17164
  }
17418
17165
  }
17419
17166
  };