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