@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.
- package/dist/ej2-navigations.min.js +2 -2
- package/dist/ej2-navigations.umd.min.js +2 -2
- package/dist/ej2-navigations.umd.min.js.map +1 -1
- package/dist/es6/ej2-navigations.es2015.js +310 -563
- package/dist/es6/ej2-navigations.es2015.js.map +1 -1
- package/dist/es6/ej2-navigations.es5.js +312 -563
- package/dist/es6/ej2-navigations.es5.js.map +1 -1
- package/dist/global/ej2-navigations.min.js +2 -2
- package/dist/global/ej2-navigations.min.js.map +1 -1
- package/dist/global/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/common/menu-base.js +14 -6
- package/src/treeview/treeview-model.d.ts +1 -0
- package/src/treeview/treeview.d.ts +7 -210
- package/src/treeview/treeview.js +298 -557
- package/styles/bds-lite.css +3 -0
- package/styles/bds.css +3 -0
- package/styles/bootstrap-dark-lite.css +3 -0
- package/styles/bootstrap-dark.css +3 -0
- package/styles/bootstrap-lite.css +3 -0
- package/styles/bootstrap.css +3 -0
- package/styles/bootstrap4-lite.css +3 -0
- package/styles/bootstrap4.css +3 -0
- package/styles/bootstrap5-dark-lite.css +3 -0
- package/styles/bootstrap5-dark.css +3 -0
- package/styles/bootstrap5-lite.css +3 -0
- package/styles/bootstrap5.3-lite.css +3 -0
- package/styles/bootstrap5.3.css +3 -0
- package/styles/bootstrap5.css +3 -0
- package/styles/fabric-dark-lite.css +3 -0
- package/styles/fabric-dark.css +3 -0
- package/styles/fabric-lite.css +3 -0
- package/styles/fabric.css +3 -0
- package/styles/fluent-dark-lite.css +3 -0
- package/styles/fluent-dark.css +3 -0
- package/styles/fluent-lite.css +3 -0
- package/styles/fluent.css +3 -0
- package/styles/fluent2-lite.css +3 -0
- package/styles/fluent2.css +3 -0
- package/styles/highcontrast-light-lite.css +3 -0
- package/styles/highcontrast-light.css +3 -0
- package/styles/highcontrast-lite.css +3 -0
- package/styles/highcontrast.css +3 -0
- package/styles/material-dark-lite.css +3 -0
- package/styles/material-dark.css +3 -0
- package/styles/material-lite.css +3 -0
- package/styles/material.css +3 -0
- package/styles/material3-dark-lite.css +3 -0
- package/styles/material3-dark.css +3 -0
- package/styles/material3-lite.css +3 -0
- package/styles/material3.css +3 -0
- package/styles/tailwind-dark-lite.css +3 -0
- package/styles/tailwind-dark.css +3 -0
- package/styles/tailwind-lite.css +3 -0
- package/styles/tailwind.css +3 -0
- package/styles/tailwind3-lite.css +3 -0
- package/styles/tailwind3.css +3 -0
- package/styles/toolbar/_layout.scss +4 -0
- package/styles/toolbar/bds.css +3 -0
- package/styles/toolbar/bootstrap-dark.css +3 -0
- package/styles/toolbar/bootstrap.css +3 -0
- package/styles/toolbar/bootstrap4.css +3 -0
- package/styles/toolbar/bootstrap5-dark.css +3 -0
- package/styles/toolbar/bootstrap5.3.css +3 -0
- package/styles/toolbar/bootstrap5.css +3 -0
- package/styles/toolbar/fabric-dark.css +3 -0
- package/styles/toolbar/fabric.css +3 -0
- package/styles/toolbar/fluent-dark.css +3 -0
- package/styles/toolbar/fluent.css +3 -0
- package/styles/toolbar/fluent2.css +3 -0
- package/styles/toolbar/highcontrast-light.css +3 -0
- package/styles/toolbar/highcontrast.css +3 -0
- package/styles/toolbar/material-dark.css +3 -0
- package/styles/toolbar/material.css +3 -0
- package/styles/toolbar/material3-dark.css +3 -0
- package/styles/toolbar/material3.css +3 -0
- package/styles/toolbar/tailwind-dark.css +3 -0
- package/styles/toolbar/tailwind.css +3 -0
- 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]
|
|
1550
|
+
const liToFocus = cul.children[fliIdx];
|
|
1549
1551
|
if (closest(this.element, '.e-popup')) {
|
|
1550
|
-
const
|
|
1551
|
-
const
|
|
1552
|
-
if (!
|
|
1553
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
16066
|
-
const childFieldName =
|
|
16067
|
-
const expandedFieldName =
|
|
16068
|
-
const isCheckedFieldName =
|
|
16069
|
-
const iconCssFieldName =
|
|
16070
|
-
const imageUrlFieldName =
|
|
16071
|
-
const idFieldName =
|
|
16072
|
-
const textFieldName =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
16101
|
-
if (
|
|
16102
|
-
|
|
16103
|
-
|
|
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
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
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,
|
|
16124
|
-
|
|
16118
|
+
id, text, level, parentId, hasChildren, expanded,
|
|
16119
|
+
selected, isChecked, iconCss, imageUrl
|
|
16125
16120
|
};
|
|
16126
16121
|
result.push(node);
|
|
16127
|
-
if (
|
|
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);
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
16472
|
-
|
|
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.
|
|
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;
|
|
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.
|
|
16807
|
-
const acutalRootData = this.
|
|
16808
|
-
acutalRootData.
|
|
16809
|
-
|
|
16810
|
-
|
|
16811
|
-
|
|
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.
|
|
16888
|
-
const acutalRootData = this.
|
|
16889
|
-
acutalRootData.
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
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)].
|
|
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['
|
|
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.
|
|
17163
|
+
node.expanded = true;
|
|
17417
17164
|
}
|
|
17418
17165
|
}
|
|
17419
17166
|
};
|