@syncfusion/ej2-treegrid 31.2.12 → 32.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/ej2-treegrid.min.js +1 -10
  2. package/dist/ej2-treegrid.umd.min.js +1 -10
  3. package/dist/ej2-treegrid.umd.min.js.map +1 -1
  4. package/dist/es6/ej2-treegrid.es2015.js +1069 -324
  5. package/dist/es6/ej2-treegrid.es2015.js.map +1 -1
  6. package/dist/es6/ej2-treegrid.es5.js +1075 -327
  7. package/dist/es6/ej2-treegrid.es5.js.map +1 -1
  8. package/dist/global/ej2-treegrid.min.js +1 -10
  9. package/dist/global/ej2-treegrid.min.js.map +1 -1
  10. package/dist/global/index.d.ts +0 -9
  11. package/package.json +5 -5
  12. package/src/treegrid/actions/batch-edit.js +22 -4
  13. package/src/treegrid/actions/context-menu.d.ts +1 -0
  14. package/src/treegrid/actions/context-menu.js +16 -0
  15. package/src/treegrid/actions/crud-actions.js +12 -5
  16. package/src/treegrid/actions/edit.d.ts +4 -1
  17. package/src/treegrid/actions/edit.js +59 -3
  18. package/src/treegrid/actions/freeze-column.js +1 -1
  19. package/src/treegrid/actions/rowdragdrop.js +3 -0
  20. package/src/treegrid/actions/selection.d.ts +226 -8
  21. package/src/treegrid/actions/selection.js +759 -288
  22. package/src/treegrid/actions/summary.js +1 -1
  23. package/src/treegrid/actions/virtual-scroll.js +6 -3
  24. package/src/treegrid/base/data.js +2 -1
  25. package/src/treegrid/base/treegrid-model.d.ts +16 -0
  26. package/src/treegrid/base/treegrid.d.ts +50 -4
  27. package/src/treegrid/base/treegrid.js +158 -19
  28. package/src/treegrid/models/column.d.ts +24 -0
  29. package/src/treegrid/models/column.js +12 -0
  30. package/src/treegrid/renderer/virtual-tree-content-render.d.ts +5 -0
  31. package/src/treegrid/renderer/virtual-tree-content-render.js +24 -2
  32. package/styles/bds-lite.scss +17 -18
  33. package/styles/bds.scss +18 -19
  34. package/styles/bootstrap-dark-lite.scss +17 -18
  35. package/styles/bootstrap-dark.scss +18 -19
  36. package/styles/bootstrap-lite.scss +17 -18
  37. package/styles/bootstrap.scss +18 -19
  38. package/styles/bootstrap4-lite.scss +17 -18
  39. package/styles/bootstrap4.scss +18 -19
  40. package/styles/bootstrap5-dark-lite.scss +17 -18
  41. package/styles/bootstrap5-dark.scss +18 -19
  42. package/styles/bootstrap5-lite.scss +18 -18
  43. package/styles/bootstrap5.3-lite.css +81 -1
  44. package/styles/bootstrap5.3-lite.scss +18 -18
  45. package/styles/bootstrap5.3.css +81 -1
  46. package/styles/bootstrap5.3.scss +19 -19
  47. package/styles/bootstrap5.scss +19 -19
  48. package/styles/fabric-dark-lite.scss +18 -18
  49. package/styles/fabric-dark.scss +19 -19
  50. package/styles/fabric-lite.scss +18 -18
  51. package/styles/fabric.scss +19 -19
  52. package/styles/fluent-dark-lite.scss +18 -18
  53. package/styles/fluent-dark.scss +19 -19
  54. package/styles/fluent-lite.scss +18 -18
  55. package/styles/fluent.scss +19 -19
  56. package/styles/fluent2-lite.css +152 -4
  57. package/styles/fluent2-lite.scss +18 -18
  58. package/styles/fluent2.css +152 -4
  59. package/styles/fluent2.scss +19 -19
  60. package/styles/highcontrast-light-lite.scss +18 -18
  61. package/styles/highcontrast-light.scss +19 -19
  62. package/styles/highcontrast-lite.scss +18 -18
  63. package/styles/highcontrast.scss +19 -19
  64. package/styles/material-dark-lite.scss +18 -18
  65. package/styles/material-dark.scss +19 -19
  66. package/styles/material-lite.scss +17 -18
  67. package/styles/material.scss +18 -19
  68. package/styles/material3-dark-lite.css +140 -1
  69. package/styles/material3-dark-lite.scss +18 -18
  70. package/styles/material3-dark.css +140 -1
  71. package/styles/material3-dark.scss +19 -21
  72. package/styles/material3-lite.css +140 -1
  73. package/styles/material3-lite.scss +18 -18
  74. package/styles/material3.css +140 -1
  75. package/styles/material3.scss +19 -21
  76. package/styles/tailwind-dark-lite.scss +18 -18
  77. package/styles/tailwind-dark.scss +19 -19
  78. package/styles/tailwind-lite.scss +18 -18
  79. package/styles/tailwind.scss +19 -19
  80. package/styles/tailwind3-lite.css +119 -1
  81. package/styles/tailwind3-lite.scss +18 -18
  82. package/styles/tailwind3.css +119 -1
  83. package/styles/tailwind3.scss +19 -19
  84. package/styles/treegrid/_all.scss +2 -2
  85. package/styles/treegrid/_bds-definition.scss +2 -0
  86. package/styles/treegrid/_bigger.scss +2 -0
  87. package/styles/treegrid/_bootstrap-dark-definition.scss +2 -0
  88. package/styles/treegrid/_bootstrap-definition.scss +2 -0
  89. package/styles/treegrid/_bootstrap4-definition.scss +2 -0
  90. package/styles/treegrid/_bootstrap5-dark-definition.scss +26 -1
  91. package/styles/treegrid/_bootstrap5-definition.scss +2 -0
  92. package/styles/treegrid/_bootstrap5.3-definition.scss +2 -0
  93. package/styles/treegrid/_fabric-dark-definition.scss +2 -0
  94. package/styles/treegrid/_fabric-definition.scss +2 -0
  95. package/styles/treegrid/_fluent-dark-definition.scss +27 -1
  96. package/styles/treegrid/_fluent-definition.scss +2 -0
  97. package/styles/treegrid/_fluent2-definition.scss +2 -0
  98. package/styles/treegrid/_highcontrast-definition.scss +2 -0
  99. package/styles/treegrid/_highcontrast-light-definition.scss +2 -0
  100. package/styles/treegrid/_layout.scss +5 -2
  101. package/styles/treegrid/_material-dark-definition.scss +2 -0
  102. package/styles/treegrid/_material-definition.scss +2 -0
  103. package/styles/treegrid/_material3-dark-definition.scss +26 -1
  104. package/styles/treegrid/_material3-definition.scss +2 -0
  105. package/styles/treegrid/_tailwind-dark-definition.scss +26 -1
  106. package/styles/treegrid/_tailwind-definition.scss +2 -0
  107. package/styles/treegrid/_tailwind3-definition.scss +2 -0
  108. package/styles/treegrid/_theme-variables.scss +1 -0
  109. package/styles/treegrid/bds.scss +19 -19
  110. package/styles/treegrid/bootstrap-dark.scss +19 -19
  111. package/styles/treegrid/bootstrap.scss +19 -19
  112. package/styles/treegrid/bootstrap4.scss +19 -19
  113. package/styles/treegrid/bootstrap5-dark.scss +19 -19
  114. package/styles/treegrid/bootstrap5.3.css +81 -1
  115. package/styles/treegrid/bootstrap5.3.scss +19 -19
  116. package/styles/treegrid/bootstrap5.scss +19 -19
  117. package/styles/treegrid/fabric-dark.scss +19 -19
  118. package/styles/treegrid/fabric.scss +19 -19
  119. package/styles/treegrid/fluent-dark.scss +19 -19
  120. package/styles/treegrid/fluent.scss +19 -19
  121. package/styles/treegrid/fluent2.css +152 -4
  122. package/styles/treegrid/fluent2.scss +19 -19
  123. package/styles/treegrid/highcontrast-light.scss +19 -19
  124. package/styles/treegrid/highcontrast.scss +19 -19
  125. package/styles/treegrid/icons/_bds.scss +1 -0
  126. package/styles/treegrid/icons/_bootstrap-dark.scss +1 -0
  127. package/styles/treegrid/icons/_bootstrap.scss +1 -0
  128. package/styles/treegrid/icons/_bootstrap4.scss +1 -0
  129. package/styles/treegrid/icons/_bootstrap5-dark.scss +1 -1
  130. package/styles/treegrid/icons/_bootstrap5.3.scss +1 -0
  131. package/styles/treegrid/icons/_bootstrap5.scss +1 -0
  132. package/styles/treegrid/icons/_fabric-dark.scss +1 -0
  133. package/styles/treegrid/icons/_fabric.scss +1 -0
  134. package/styles/treegrid/icons/_fluent-dark.scss +1 -1
  135. package/styles/treegrid/icons/_fluent.scss +1 -0
  136. package/styles/treegrid/icons/_fluent2.scss +1 -0
  137. package/styles/treegrid/icons/_fusionnew.scss +1 -0
  138. package/styles/treegrid/icons/_highcontrast-light.scss +1 -0
  139. package/styles/treegrid/icons/_highcontrast.scss +1 -0
  140. package/styles/treegrid/icons/_material-dark.scss +1 -0
  141. package/styles/treegrid/icons/_material.scss +1 -0
  142. package/styles/treegrid/icons/_material3-dark.scss +1 -1
  143. package/styles/treegrid/icons/_material3.scss +1 -0
  144. package/styles/treegrid/icons/_tailwind-dark.scss +1 -0
  145. package/styles/treegrid/icons/_tailwind.scss +1 -0
  146. package/styles/treegrid/icons/_tailwind3.scss +1 -0
  147. package/styles/treegrid/material-dark.scss +19 -19
  148. package/styles/treegrid/material.scss +19 -19
  149. package/styles/treegrid/material3-dark.css +140 -1
  150. package/styles/treegrid/material3-dark.scss +19 -20
  151. package/styles/treegrid/material3.css +140 -1
  152. package/styles/treegrid/material3.scss +19 -20
  153. package/styles/treegrid/tailwind-dark.scss +19 -19
  154. package/styles/treegrid/tailwind.scss +19 -19
  155. package/styles/treegrid/tailwind3.css +119 -1
  156. package/styles/treegrid/tailwind3.scss +19 -19
@@ -70,6 +70,18 @@ class Column {
70
70
  * @default null
71
71
  */
72
72
  this.filter = {};
73
+ /**
74
+ * Allows treegrid to perform row spanning on the specified column.
75
+ *
76
+ * @default true
77
+ */
78
+ this.enableRowSpan = true;
79
+ /**
80
+ * Allows treegrid to perform column spanning on the specified column.
81
+ *
82
+ * @default true
83
+ */
84
+ this.enableColumnSpan = true;
73
85
  merge(this, options);
74
86
  }
75
87
  /**
@@ -875,27 +887,56 @@ function isHidden(el) {
875
887
  */
876
888
  class Selection {
877
889
  /**
878
- * Constructor for Selection module
890
+ * Creates an instance of Selection.
879
891
  *
880
- * @param {TreeGrid} parent - Tree Grid instance
892
+ * @param {TreeGrid} parent - The TreeGrid instance this selection module is associated with.
881
893
  */
882
894
  constructor(parent) {
895
+ this.headerCheckboxFrameEl = null;
896
+ this.checkboxColIndexCache = -2;
897
+ this.parentSelectionCounters = {};
898
+ this.selectedUidMap = new Map(); // Quick lookup for whether an item is selected
899
+ this.totalSelectableCount = 0;
900
+ this.headerSelectionState = 'uncheck';
901
+ this.checkedItemCount = 0;
902
+ this.visibleUidIndex = {};
883
903
  this.parent = parent;
884
904
  this.selectedItems = [];
885
- this.selectedIndexes = [];
905
+ this.selectedIndexes = []; // Initialize here
886
906
  this.filteredList = [];
887
907
  this.searchingRecords = [];
888
908
  this.addEventListener();
889
909
  }
890
910
  /**
891
- * For internal use only - Get the module name.
911
+ * Gets the module name.
892
912
  *
893
- * @private
894
- * @returns {string} Returns Selection module name
913
+ * @returns {string} The name of the module ('selection').
895
914
  */
896
- getModuleName() {
897
- return 'selection';
915
+ getModuleName() { return 'selection'; }
916
+ /**
917
+ * Builds a map from visible record uniqueID to its visible index.
918
+ * This map is crucial for finding the *current visible index* of a record.
919
+ *
920
+ * @returns {void}
921
+ */
922
+ buildVisibleUidMap() {
923
+ this.visibleUidIndex = {};
924
+ const view = this.parent.grid.currentViewData;
925
+ if (!view) {
926
+ return;
927
+ }
928
+ for (let i = 0, len = view.length; i < len; i++) {
929
+ const rec = view[parseInt(i.toString(), 10)];
930
+ if (rec && rec.uniqueID) {
931
+ this.visibleUidIndex[rec.uniqueID] = i; // Map uid -> visible row index
932
+ }
933
+ }
898
934
  }
935
+ /**
936
+ * Adds required event listeners for selection handling.
937
+ *
938
+ * @returns {void}
939
+ */
899
940
  addEventListener() {
900
941
  this.parent.on('dataBoundArg', this.headerCheckbox, this);
901
942
  this.parent.on('columnCheckbox', this.columnCheckbox, this);
@@ -903,6 +944,11 @@ class Selection {
903
944
  this.parent.grid.on('colgroup-refresh', this.headerCheckbox, this);
904
945
  this.parent.on('checkboxSelection', this.checkboxSelection, this);
905
946
  }
947
+ /**
948
+ * Removes previously added event listeners.
949
+ *
950
+ * @returns {void}
951
+ */
906
952
  removeEventListener() {
907
953
  if (this.parent.isDestroyed) {
908
954
  return;
@@ -914,14 +960,20 @@ class Selection {
914
960
  this.parent.off('updateGridActions', this.updateGridActions);
915
961
  }
916
962
  /**
917
- * To destroy the Selection
963
+ * Destroys the selection module and clears internal caches.
918
964
  *
919
965
  * @returns {void}
920
- * @hidden
921
966
  */
922
967
  destroy() {
968
+ this.resetSelectionCaches();
923
969
  this.removeEventListener();
924
970
  }
971
+ /**
972
+ * Handles checkbox click events from the DOM and dispatches selection logic.
973
+ *
974
+ * @param {Object} args - Event args containing the click target.
975
+ * @returns {void}
976
+ */
925
977
  checkboxSelection(args) {
926
978
  const target = getObject('target', args);
927
979
  const checkWrap = parentsUntil(target, 'e-checkbox-wrapper');
@@ -929,16 +981,23 @@ class Selection {
929
981
  if (checkWrap && checkWrap.querySelectorAll('.e-treecheckselect').length > 0) {
930
982
  checkBox = checkWrap.querySelector('input[type="checkbox"]');
931
983
  const rowIndex = [];
932
- rowIndex.push(target.closest('tr').rowIndex);
984
+ if (this.parent.frozenRows) {
985
+ rowIndex.push(parseInt(target.closest('tr').getAttribute('aria-rowindex'), 10) - 1);
986
+ }
987
+ else {
988
+ rowIndex.push(target.closest('tr').rowIndex);
989
+ }
933
990
  this.selectCheckboxes(rowIndex);
934
- this.triggerChkChangeEvent(checkBox, checkBox.nextElementSibling.classList.contains('e-check'), target.closest('tr'));
991
+ const newCheckState = checkBox.nextElementSibling.classList.contains('e-check');
992
+ this.triggerChkChangeEvent(checkBox, newCheckState, target.closest('tr'));
935
993
  }
936
994
  else if (checkWrap && checkWrap.querySelectorAll('.e-treeselectall').length > 0 && this.parent.autoCheckHierarchy) {
937
- const checkBoxvalue = !checkWrap.querySelector('.e-frame').classList.contains('e-check')
938
- && !checkWrap.querySelector('.e-frame').classList.contains('e-stop');
939
- this.headerSelection(checkBoxvalue);
995
+ const frame = checkWrap.querySelector('.e-frame');
996
+ const currentStateIsUncheck = !frame.classList.contains('e-check') && !frame.classList.contains('e-stop');
997
+ const targetState = currentStateIsUncheck; // If currently uncheck, target state is to check all.
998
+ this.headerSelection(targetState);
940
999
  checkBox = checkWrap.querySelector('input[type="checkbox"]');
941
- this.triggerChkChangeEvent(checkBox, checkBoxvalue, target.closest('tr'));
1000
+ this.triggerChkChangeEvent(checkBox, targetState, target.closest('tr'));
942
1001
  }
943
1002
  if (!isNullOrUndefined(this.parent['parentQuery']) && this.parent.selectionSettings.persistSelection
944
1003
  && this.parent['columnModel'].filter((col) => { return col.type === 'checkbox'; }).length > 0
@@ -949,63 +1008,99 @@ class Selection {
949
1008
  }
950
1009
  }
951
1010
  }
1011
+ /**
1012
+ * Triggers the checkboxChange event with the appropriate arguments.
1013
+ *
1014
+ * @param {HTMLInputElement} checkBox - The checkbox input element that changed.
1015
+ * @param {boolean} checkState - The new checked state.
1016
+ * @param {HTMLTableRowElement} rowElement - The row element where the change occurred.
1017
+ * @returns {void}
1018
+ */
952
1019
  triggerChkChangeEvent(checkBox, checkState, rowElement) {
953
1020
  const data = this.parent.getCurrentViewRecords()[rowElement.rowIndex];
954
- const args = { checked: checkState, target: checkBox, rowElement: rowElement,
1021
+ const args = {
1022
+ checked: checkState, target: checkBox, rowElement: rowElement,
955
1023
  rowData: checkBox.classList.contains('e-treeselectall')
956
- ? this.parent.getCheckedRecords() : data };
1024
+ ? this.parent.getCheckedRecords() : data
1025
+ };
957
1026
  this.parent.trigger(checkboxChange, args);
958
1027
  }
1028
+ /**
1029
+ * Determines the index of the checkbox column in the header.
1030
+ *
1031
+ * @returns {number} The index of the checkbox column, or -1 if not found.
1032
+ */
959
1033
  getCheckboxcolumnIndex() {
1034
+ if (this.checkboxColIndexCache !== -2) {
1035
+ return this.checkboxColIndexCache;
1036
+ }
960
1037
  let mappingUid;
961
- let columnIndex;
1038
+ let columnIndex = -1;
962
1039
  const stackedHeader = 'stackedHeader';
963
1040
  const columnModel = 'columnModel';
964
1041
  const columns = this.parent[`${stackedHeader}`] ? this.parent[`${columnModel}`] : (this.parent.columns);
965
1042
  for (let col = 0; col < columns.length; col++) {
966
1043
  if (columns[parseInt(col.toString(), 10)].showCheckbox) {
967
1044
  mappingUid = columns[parseInt(col.toString(), 10)].uid;
1045
+ break;
968
1046
  }
969
1047
  }
970
- const headerCelllength = this.parent.getHeaderContent().querySelectorAll('.e-headercelldiv').length;
971
- for (let j = 0; j < headerCelllength; j++) {
972
- const headercell = this.parent.getHeaderContent().querySelectorAll('.e-headercelldiv')[parseInt(j.toString(), 10)];
1048
+ const headerDivs = this.parent.getHeaderContent().querySelectorAll('.e-headercelldiv');
1049
+ for (let j = 0; j < headerDivs.length; j++) {
1050
+ const headercell = headerDivs[parseInt(j.toString(), 10)];
973
1051
  if (headercell.getAttribute('data-mappinguid') === mappingUid) {
974
1052
  columnIndex = j;
1053
+ break;
975
1054
  }
976
1055
  }
977
- return columnIndex;
1056
+ this.checkboxColIndexCache = isNullOrUndefined(columnIndex) ? -1 : columnIndex;
1057
+ return this.checkboxColIndexCache;
978
1058
  }
1059
+ /**
1060
+ * Renders and initializes the header checkbox element.
1061
+ *
1062
+ * @returns {void}
1063
+ */
979
1064
  headerCheckbox() {
1065
+ this.buildVisibleUidMap();
1066
+ this.totalSelectableCount = this.countSelectableRecords(this.resolveHeaderSelectionList(true)); // Use all flatData for initial count
980
1067
  this.columnIndex = this.getCheckboxcolumnIndex();
981
- if (this.columnIndex > -1 && this.parent.getHeaderContent().querySelectorAll('.e-treeselectall').length === 0) {
1068
+ if (this.columnIndex > -1) {
982
1069
  const headerElement = this.parent.getHeaderContent().querySelectorAll('.e-headercelldiv')[this.columnIndex];
983
- const value = false;
984
- const rowChkBox = this.parent.createElement('input', { className: 'e-treeselectall', attrs: { 'type': 'checkbox' } });
985
- const checkWrap = createCheckBox(this.parent.createElement, false, { checked: value, label: ' ' });
986
- checkWrap.classList.add('e-hierarchycheckbox');
987
- checkWrap.insertBefore(rowChkBox.cloneNode(), checkWrap.firstChild);
988
- if (!isNullOrUndefined(headerElement)) {
989
- headerElement.insertBefore(checkWrap, headerElement.firstChild);
990
- }
991
- if (this.parent.autoCheckHierarchy) {
992
- this.headerSelection();
1070
+ if (headerElement && headerElement.querySelectorAll('.e-treeselectall').length === 0) {
1071
+ const value = false; // Initial state can be false.
1072
+ const rowChkBox = this.parent.createElement('input', { className: 'e-treeselectall', attrs: { 'type': 'checkbox' } });
1073
+ const checkWrap = createCheckBox(this.parent.createElement, false, { checked: value, label: ' ' });
1074
+ checkWrap.classList.add('e-hierarchycheckbox');
1075
+ checkWrap.insertBefore(rowChkBox.cloneNode(), checkWrap.firstChild);
1076
+ if (!isNullOrUndefined(headerElement)) {
1077
+ headerElement.insertBefore(checkWrap, headerElement.firstChild);
1078
+ }
1079
+ this.headerCheckboxFrameEl = checkWrap.querySelector('.e-frame'); // Assign the frame element
1080
+ if (this.parent.autoCheckHierarchy) {
1081
+ this.headerSelection();
1082
+ } // Update header state based on data
993
1083
  }
994
- }
995
- else if (this.columnIndex > -1 && this.parent.getHeaderContent().querySelectorAll('.e-treeselectall').length > 0) {
996
- const checkWrap = this.parent.getHeaderContent().querySelectorAll('.e-checkbox-wrapper')[0];
997
- const checkBoxvalue = checkWrap.querySelector('.e-frame').classList.contains('e-check');
998
- if (this.parent.autoCheckHierarchy && checkBoxvalue) {
999
- this.headerSelection(checkBoxvalue);
1084
+ else if (headerElement && headerElement.querySelectorAll('.e-treeselectall').length > 0) {
1085
+ this.headerCheckboxFrameEl = headerElement.querySelector('.e-frame');
1086
+ if (this.parent.autoCheckHierarchy) {
1087
+ this.headerSelection();
1088
+ } // Update status based on current selections
1000
1089
  }
1001
1090
  }
1002
1091
  }
1092
+ /**
1093
+ * Renders a checkbox element for a column cell.
1094
+ *
1095
+ * @param {QueryCellInfoEventArgs} args - The QueryCellInfoEventArgs for the cell.
1096
+ * @returns {Element} The rendered checkbox wrapper element.
1097
+ */
1003
1098
  renderColumnCheckbox(args) {
1004
1099
  const rowChkBox = this.parent.createElement('input', { className: 'e-treecheckselect', attrs: { 'type': 'checkbox', 'aria-label': 'checkbox' } });
1005
1100
  const data = args.data;
1006
1101
  args.cell.classList.add('e-treegridcheckbox');
1007
1102
  args.cell.setAttribute('aria-label', 'checkbox');
1008
- const value = (isNullOrUndefined(data.checkboxState) || data.checkboxState === 'uncheck') ? false : true;
1103
+ const value = (data.checkboxState === 'check');
1009
1104
  const checkWrap = createCheckBox(this.parent.createElement, false, { checked: value, label: ' ' });
1010
1105
  checkWrap.classList.add('e-hierarchycheckbox');
1011
1106
  if (this.parent.allowTextWrap) {
@@ -1016,9 +1111,25 @@ class Selection {
1016
1111
  removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
1017
1112
  checkWrap.querySelector('.e-frame').classList.add('e-stop');
1018
1113
  }
1114
+ else if (data.checkboxState === 'uncheck') {
1115
+ const checkbox = checkWrap.querySelectorAll('.e-frame')[0];
1116
+ removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
1117
+ checkWrap.querySelector('.e-frame').classList.add('e-uncheck');
1118
+ }
1119
+ else if (data.checkboxState === 'check') {
1120
+ const checkbox = checkWrap.querySelectorAll('.e-frame')[0];
1121
+ removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
1122
+ checkWrap.querySelector('.e-frame').classList.add('e-check');
1123
+ }
1019
1124
  checkWrap.insertBefore(rowChkBox.cloneNode(), checkWrap.firstChild);
1020
1125
  return checkWrap;
1021
1126
  }
1127
+ /**
1128
+ * Injects the checkbox into a column cell during QueryCellInfo.
1129
+ *
1130
+ * @param {QueryCellInfoEventArgs} container - The cell event args.
1131
+ * @returns {void}
1132
+ */
1022
1133
  columnCheckbox(container) {
1023
1134
  const checkWrap = this.renderColumnCheckbox(container);
1024
1135
  const containerELe = container.cell.querySelector('.e-treecolumn-container');
@@ -1038,112 +1149,247 @@ class Selection {
1038
1149
  container.cell.appendChild(divEle);
1039
1150
  }
1040
1151
  }
1152
+ /**
1153
+ * Selects or toggles checkboxes for the provided row indexes.
1154
+ *
1155
+ * @param {number[]} rowIndexes - Array of row indexes to toggle selection for.
1156
+ * @returns {void}
1157
+ */
1041
1158
  selectCheckboxes(rowIndexes) {
1042
- if (isNullOrUndefined(rowIndexes)) {
1043
- const error = 'The provided value for the rowIndexes is undefined. Please ensure the rowIndexes contains number.';
1044
- this.parent.trigger(actionFailure, { error: error });
1045
- }
1046
1159
  for (let i = 0; i < rowIndexes.length; i++) {
1047
- let record = this.parent.getCurrentViewRecords()[rowIndexes[parseInt(i.toString(), 10)]];
1048
- const flatRecord = getParentData(this.parent, record.uniqueID);
1049
- record = flatRecord;
1050
- const checkboxState = (record.checkboxState === 'uncheck') ? 'check' : 'uncheck';
1051
- record.checkboxState = checkboxState;
1052
- const keys = Object.keys(record);
1053
- for (let j = 0; j < keys.length; j++) {
1054
- if (Object.prototype.hasOwnProperty.call(flatRecord, keys[parseInt(j.toString(), 10)])) {
1055
- flatRecord[keys[parseInt(j.toString(), 10)]] = record[keys[parseInt(j.toString(), 10)]];
1056
- }
1057
- }
1058
- this.traverSelection(record, checkboxState, false);
1059
- if (this.parent.autoCheckHierarchy) {
1060
- this.headerSelection();
1061
- }
1160
+ const viewRec = this.parent.getCurrentViewRecords()[rowIndexes[parseInt(i.toString(), 10)]];
1161
+ const flatRec = getParentData(this.parent, viewRec.uniqueID);
1162
+ const nextState = (flatRec.checkboxState === 'check') ? 'uncheck' : 'check';
1163
+ flatRec.checkboxState = nextState;
1164
+ this.traverSelection(flatRec, nextState, false);
1062
1165
  }
1063
1166
  }
1064
- traverSelection(record, checkboxState, ischildItem) {
1065
- let length = 0;
1066
- this.updateSelectedItems(record, checkboxState);
1067
- if (!ischildItem && record.parentItem && this.parent.autoCheckHierarchy) {
1068
- this.updateParentSelection(record.parentItem);
1069
- }
1070
- if (record.childRecords && this.parent.autoCheckHierarchy) {
1071
- let childRecords = record.childRecords;
1072
- if (!isNullOrUndefined(this.parent.filterModule) &&
1073
- this.parent.filterModule.filteredResult.length > 0 && this.parent.autoCheckHierarchy) {
1074
- childRecords = this.getFilteredChildRecords(childRecords);
1075
- }
1076
- length = childRecords.length;
1077
- for (let count = 0; count < length; count++) {
1078
- if (!childRecords[parseInt(count.toString(), 10)].isSummaryRow) {
1079
- if (childRecords[parseInt(count.toString(), 10)].hasChildRecords) {
1080
- this.traverSelection(childRecords[parseInt(count.toString(), 10)], checkboxState, true);
1081
- }
1082
- else {
1083
- this.updateSelectedItems(childRecords[parseInt(count.toString(), 10)], checkboxState);
1084
- }
1167
+ /**
1168
+ * Traverses selection for a record and cascades selections to children/parents as necessary.
1169
+ *
1170
+ * @param {ITreeData} record - The record to process.
1171
+ * @param {string} checkboxState - The desired checkbox state ('check'|'uncheck'|'indeterminate').
1172
+ * @param {boolean} isChildItem - True if this invocation is for a child during recursion.
1173
+ * @returns {void}
1174
+ */
1175
+ traverSelection(record, checkboxState, isChildItem) {
1176
+ const previousState = record.checkboxState;
1177
+ if (!isChildItem) {
1178
+ this.buildVisibleUidMap();
1179
+ }
1180
+ let effectiveChildren = Array.isArray(record.childRecords) ? record.childRecords : [];
1181
+ if ((!effectiveChildren || effectiveChildren.length === 0) && this.parent.autoCheckHierarchy) {
1182
+ effectiveChildren = this.getChildrenFromFlat(record);
1183
+ }
1184
+ if (this.parent.filterModule && this.parent.filterModule.filteredResult.length > 0
1185
+ && effectiveChildren && effectiveChildren.length) {
1186
+ effectiveChildren = this.getFilteredChildRecords(effectiveChildren);
1187
+ }
1188
+ if (!this.parent.autoCheckHierarchy || !effectiveChildren || effectiveChildren.length === 0) {
1189
+ this.updateSelectedItems(record, checkboxState);
1190
+ if (!isChildItem) {
1191
+ if (record.parentItem && this.parent.autoCheckHierarchy) {
1192
+ this.updateParentSelection(record.parentItem);
1193
+ }
1194
+ this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(), '');
1195
+ this.refreshVisibleCheckboxes();
1196
+ if (this.parent.autoCheckHierarchy) {
1197
+ this.updateHeaderCheckboxState();
1085
1198
  }
1086
1199
  }
1200
+ return;
1201
+ }
1202
+ let childCount = 0;
1203
+ let checkedCount = 0;
1204
+ let indeterminateCount = 0;
1205
+ for (let i = 0; i < effectiveChildren.length; i++) {
1206
+ const child = effectiveChildren[parseInt(i.toString(), 10)];
1207
+ if (!child || child.isSummaryRow) {
1208
+ continue;
1209
+ }
1210
+ childCount++;
1211
+ this.updateSelectedItems(child, checkboxState, true);
1212
+ if (child.hasChildRecords) {
1213
+ this.traverSelection(child, checkboxState, true);
1214
+ }
1215
+ if (child.checkboxState === 'check') {
1216
+ checkedCount++;
1217
+ }
1218
+ else if (child.checkboxState === 'indeterminate') {
1219
+ indeterminateCount++;
1220
+ }
1221
+ }
1222
+ if (record.uniqueID) {
1223
+ this.parentSelectionCounters[record.uniqueID] = {
1224
+ total: childCount,
1225
+ checked: checkedCount,
1226
+ indeterminate: indeterminateCount
1227
+ };
1228
+ }
1229
+ const summary = this.parentSelectionCounters[record.uniqueID];
1230
+ let finalState = this.deriveParentState(record, summary);
1231
+ if (checkboxState === 'check' && summary.total > 0 && summary.checked === summary.total && summary.indeterminate === 0) {
1232
+ finalState = 'check';
1233
+ }
1234
+ this.updateSelectedItems(record, finalState);
1235
+ if (!isChildItem && record.parentItem && this.parent.autoCheckHierarchy) {
1236
+ this.updateParentSelection(record.parentItem, previousState, finalState);
1237
+ }
1238
+ if (!isChildItem) {
1239
+ const bulkList = this.resolveHeaderSelectionList();
1240
+ this.updateSelectedCollectionsAfterBulk(bulkList, ''); // This will rebuild selectedItems & selectedIndexes based on total state
1241
+ this.refreshVisibleCheckboxes();
1242
+ this.updateHeaderCheckboxState();
1087
1243
  }
1088
1244
  }
1245
+ /**
1246
+ * Filters provided child records against the current filter result.
1247
+ *
1248
+ * @param {ITreeData[]} childRecords - The array of child records to filter.
1249
+ * @returns {ITreeData[]} The filtered child records array.
1250
+ */
1089
1251
  getFilteredChildRecords(childRecords) {
1090
1252
  const filteredChildRecords = childRecords.filter((e) => {
1091
1253
  return this.parent.filterModule.filteredResult.indexOf(e) > -1;
1092
1254
  });
1093
1255
  return filteredChildRecords;
1094
1256
  }
1095
- updateParentSelection(parentRecord) {
1096
- let length = 0;
1097
- let childRecords = [];
1098
- const record = getParentData(this.parent, parentRecord.uniqueID);
1099
- if (record && record.childRecords) {
1100
- childRecords = record.childRecords;
1101
- }
1102
- if (!isNullOrUndefined(this.parent.filterModule) &&
1103
- this.parent.filterModule.filteredResult.length > 0 && this.parent.autoCheckHierarchy) {
1104
- childRecords = this.getFilteredChildRecords(childRecords);
1105
- }
1106
- length = childRecords && childRecords.length;
1107
- let indeter = 0;
1108
- let checkChildRecords = 0;
1109
- if (!isNullOrUndefined(record)) {
1110
- for (let i = 0; i < childRecords.length; i++) {
1111
- const currentRecord = getParentData(this.parent, childRecords[parseInt(i.toString(), 10)].uniqueID);
1112
- const checkBoxRecord = currentRecord;
1113
- if (!isNullOrUndefined(checkBoxRecord)) {
1114
- if (checkBoxRecord.checkboxState === 'indeterminate') {
1115
- indeter++;
1116
- }
1117
- else if (checkBoxRecord.checkboxState === 'check') {
1118
- checkChildRecords++;
1119
- }
1120
- }
1121
- }
1122
- if (indeter > 0 || (checkChildRecords > 0 && checkChildRecords !== length)) {
1123
- record.checkboxState = 'indeterminate';
1257
+ /**
1258
+ * Derives children for a record from flatData using the parentItem link.
1259
+ * Used when childRecords is missing or empty.
1260
+ *
1261
+ * @param {ITreeData} record - The record for which to find child elements.
1262
+ * @returns {ITreeData[]} An array of child records derived from flatData.
1263
+ */
1264
+ getChildrenFromFlat(record) {
1265
+ const all = (this.parent.flatData);
1266
+ if (!all || !record) {
1267
+ return [];
1268
+ }
1269
+ const pid = record.uniqueID;
1270
+ const out = [];
1271
+ for (let i = 0; i < all.length; i++) {
1272
+ const r = all[parseInt(i.toString(), 10)];
1273
+ if (!r || r.isSummaryRow) {
1274
+ continue;
1124
1275
  }
1125
- else if (checkChildRecords === 0 && (!record.hasFilteredChildRecords || isNullOrUndefined(record.hasFilteredChildRecords)) && !isNullOrUndefined(this.parent['dataResults']['actionArgs']) &&
1126
- (this.parent['dataResults']['actionArgs'].requestType === 'searching' || this.parent['dataResults']['actionArgs'].requestType === 'filtering') && record.checkboxState === 'check') {
1127
- record.checkboxState = 'check';
1276
+ const p = r.parentItem;
1277
+ if (p && p.uniqueID === pid) {
1278
+ out.push(r);
1128
1279
  }
1129
- else if ((checkChildRecords === 0 && indeter === 0) || (checkChildRecords === 0 && record.hasFilteredChildRecords && !isNullOrUndefined(this.parent['dataResults']['actionArgs']) &&
1130
- (this.parent['dataResults']['actionArgs'].requestType === 'searching' || this.parent['dataResults']['actionArgs'].requestType === 'filtering') && record.checkboxState === 'check')) {
1131
- record.checkboxState = 'uncheck';
1280
+ }
1281
+ return out;
1282
+ }
1283
+ /**
1284
+ * Updates parent selection by rebuilding summary and applying deltas, then bubbling up if required.
1285
+ *
1286
+ * @param {ITreeData} parentRecord - The parent record reference.
1287
+ * @param {string} [previousChildState] - Previous state of the child that changed.
1288
+ * @param {string} [nextChildState] - Next state of the child that changed.
1289
+ * @returns {void}
1290
+ */
1291
+ updateParentSelection(parentRecord, previousChildState, nextChildState) {
1292
+ const parent = getParentData(this.parent, parentRecord.uniqueID);
1293
+ if (!parent) {
1294
+ return;
1295
+ }
1296
+ const summary = this.buildSelectionSummary(parent);
1297
+ if (previousChildState) {
1298
+ this.applySummaryDelta(summary, previousChildState, -1);
1299
+ }
1300
+ if (nextChildState) {
1301
+ this.applySummaryDelta(summary, nextChildState, 1);
1302
+ }
1303
+ if (parent.uniqueID) {
1304
+ this.parentSelectionCounters[parent.uniqueID] = summary;
1305
+ }
1306
+ const desiredState = this.deriveParentState(parent, summary);
1307
+ if (parent.checkboxState === desiredState) {
1308
+ return;
1309
+ }
1310
+ const parentPrev = parent.checkboxState;
1311
+ parent.checkboxState = desiredState;
1312
+ this.updateSelectedItems(parent, desiredState);
1313
+ if (parent.parentItem) {
1314
+ this.updateParentSelection(parent.parentItem, parentPrev, desiredState);
1315
+ }
1316
+ }
1317
+ /**
1318
+ * Builds a selection summary for a record's children.
1319
+ *
1320
+ * @param {Object} record - The record whose children should be summarized.
1321
+ * @param {boolean} [ignoreFilter] - If true, ignore current filter when computing summary.
1322
+ * @returns {{ total: number, checked: number, indeterminate: number }} The computed summary.
1323
+ */
1324
+ buildSelectionSummary(record, ignoreFilter) {
1325
+ const summary = { total: 0, checked: 0, indeterminate: 0 };
1326
+ let children = [];
1327
+ if (record && Array.isArray(record.childRecords) && record.childRecords.length) {
1328
+ children = record.childRecords;
1329
+ }
1330
+ else {
1331
+ children = this.getChildrenFromFlat(record);
1332
+ }
1333
+ if (!ignoreFilter && this.parent.filterModule && this.parent.filterModule.filteredResult.length > 0) {
1334
+ children = this.getFilteredChildRecords(children);
1335
+ }
1336
+ for (let i = 0; i < children.length; i++) {
1337
+ const child = children[parseInt(i.toString(), 10)];
1338
+ if (!child || child.isSummaryRow) {
1339
+ continue;
1132
1340
  }
1133
- else {
1134
- record.checkboxState = 'check';
1341
+ summary.total++;
1342
+ if (child.checkboxState === 'check') {
1343
+ summary.checked++;
1135
1344
  }
1136
- this.updateSelectedItems(record, record.checkboxState);
1137
- if (record.parentItem) {
1138
- this.updateParentSelection(record.parentItem);
1345
+ else if (child.checkboxState === 'indeterminate') {
1346
+ summary.indeterminate++;
1139
1347
  }
1140
1348
  }
1349
+ return summary;
1350
+ }
1351
+ /**
1352
+ * Applies a delta to a selection summary based on a state change.
1353
+ *
1354
+ * @param {Object} summary - The summary to modify. Object with numeric properties: total, checked, indeterminate.
1355
+ * @param {string} state - The state that changed ('check' | 'indeterminate').
1356
+ * @param {number} delta - The delta to apply (e.g. +1 or -1).
1357
+ * @returns {void}
1358
+ */
1359
+ applySummaryDelta(summary, state, delta) {
1360
+ if (state === 'check') {
1361
+ summary.checked = Math.max(0, summary.checked + delta);
1362
+ }
1363
+ else if (state === 'indeterminate') {
1364
+ summary.indeterminate = Math.max(0, summary.indeterminate + delta);
1365
+ }
1141
1366
  }
1367
+ /**
1368
+ * Derives the parent's checkbox state based on children summary counts.
1369
+ *
1370
+ * @param {ITreeData} record The parent record.
1371
+ * @param {{ total: number, checked: number, indeterminate: number }} summary The children summary.
1372
+ * @returns {'check'|'indeterminate'|'uncheck'} The derived checkbox state.
1373
+ */
1374
+ deriveParentState(record, summary) {
1375
+ const total = summary.total;
1376
+ const checked = summary.checked;
1377
+ const indeterminate = summary.indeterminate;
1378
+ if (indeterminate > 0 || (checked > 0 && checked !== total)) {
1379
+ return 'indeterminate';
1380
+ }
1381
+ if (checked === total && total > 0) {
1382
+ return 'check';
1383
+ }
1384
+ return 'uncheck';
1385
+ }
1386
+ /**
1387
+ * Handles header checkbox (select all / clear all) behavior.
1388
+ *
1389
+ * @param {boolean} [checkAll] - Optional explicit flag to check or uncheck all.
1390
+ * @returns {void}
1391
+ */
1142
1392
  headerSelection(checkAll) {
1143
- let index = -1;
1144
- let length = 0;
1145
- //This property used to maintain the check state of the currentview data after clear filtering
1146
- let multiFilterCheckState = false;
1147
1393
  if (!isNullOrUndefined(this.parent.filterModule) && this.parent.filterModule.filteredResult.length > 0) {
1148
1394
  const filterResult = this.parent.filterModule.filteredResult;
1149
1395
  if (this.filteredList.length === 0) {
@@ -1153,160 +1399,397 @@ class Selection {
1153
1399
  this.searchingRecords = filterResult;
1154
1400
  }
1155
1401
  else {
1156
- if (this.filteredList !== filterResult) {
1402
+ if (this.filteredList !== filterResult && !this.parent.grid.searchSettings.key.length) {
1157
1403
  this.filteredList = filterResult;
1158
- multiFilterCheckState = true;
1159
- }
1160
- else {
1161
- multiFilterCheckState = false;
1404
+ this.searchingRecords = [];
1162
1405
  }
1163
1406
  }
1164
1407
  }
1165
- if (this.filteredList.length > 0) {
1166
- if (!this.parent.filterSettings.columns.length && this.filteredList.length && !this.parent.grid.searchSettings.key.length) {
1167
- this.filteredList = [];
1408
+ if (this.searchingRecords.length > 0 && !isNullOrUndefined(checkAll)) {
1409
+ this.filteredList = this.searchingRecords;
1410
+ }
1411
+ else if (this.filteredList.length > 0 && !this.parent.filterSettings.columns.length
1412
+ && !this.parent.grid.searchSettings.key.length) {
1413
+ this.filteredList = [];
1414
+ }
1415
+ const records = this.resolveHeaderSelectionList(true);
1416
+ if (!isNullOrUndefined(checkAll)) {
1417
+ this.resetSelectionCaches();
1418
+ const targetState = checkAll ? 'check' : 'uncheck';
1419
+ this.headerSelectionState = targetState;
1420
+ this.processHeaderSelection(records, targetState);
1421
+ this.finalizeParentsAfterBulk(records);
1422
+ this.updateSelectedCollectionsAfterBulk(records, '');
1423
+ this.refreshVisibleCheckboxes();
1424
+ this.updateHeaderCheckboxState();
1425
+ return;
1426
+ }
1427
+ this.totalSelectableCount = this.countSelectableRecords(records);
1428
+ this.updateHeaderCheckboxState();
1429
+ }
1430
+ /**
1431
+ * Finalizes parent states after a bulk header operation (e.g., Select All).
1432
+ * This ensures parent states (checked/indeterminate) are correct after cascades.
1433
+ *
1434
+ * @param {ITreeData[]} records - The records that were processed in the bulk operation.
1435
+ * @returns {void}
1436
+ */
1437
+ finalizeParentsAfterBulk(records) {
1438
+ const all = records;
1439
+ for (let i = 0; i < all.length; i++) {
1440
+ const rec = all[parseInt(i.toString(), 10)];
1441
+ if (!rec || !rec.hasChildRecords) {
1442
+ continue;
1168
1443
  }
1169
- if (this.searchingRecords.length && !isNullOrUndefined(checkAll)) {
1170
- this.filteredList = this.searchingRecords;
1444
+ const summary = this.buildSelectionSummary(rec, true);
1445
+ this.parentSelectionCounters[rec.uniqueID] = summary;
1446
+ let finalState = this.deriveParentState(rec, summary);
1447
+ if (this.headerSelectionState === 'check' &&
1448
+ summary.total > 0 && summary.checked === summary.total && summary.indeterminate === 0) {
1449
+ finalState = 'check';
1450
+ }
1451
+ else if (this.headerSelectionState === 'uncheck') {
1452
+ finalState = 'uncheck';
1453
+ }
1454
+ if (rec.checkboxState !== finalState) {
1455
+ this.updateSelectedItems(rec, finalState);
1171
1456
  }
1172
1457
  }
1173
- let data;
1174
- if (!(isNullOrUndefined(this.parent.filterModule)) &&
1175
- this.parent.filterModule.filteredResult.length === 0 && this.parent.getCurrentViewRecords().length === 0 &&
1176
- this.parent.filterSettings.columns.length > 0) {
1177
- data = this.filteredList;
1178
- }
1179
- else {
1180
- data = (!isNullOrUndefined(this.parent.filterModule) &&
1181
- (this.filteredList.length > 0)) ? this.filteredList : this.parent.flatData;
1458
+ }
1459
+ /**
1460
+ * Processes header selection for each record, setting their state silently in the data model.
1461
+ * Called during bulk operations like "select all".
1462
+ *
1463
+ * @param {ITreeData[]} records - The records to process.
1464
+ * @param {string} targetState - The target state to set on each record.
1465
+ * @returns {void}
1466
+ */
1467
+ processHeaderSelection(records, targetState) {
1468
+ for (let i = 0; i < records.length; i++) {
1469
+ const record = records[parseInt(i.toString(), 10)];
1470
+ if (!record) {
1471
+ continue;
1472
+ }
1473
+ const previousState = record.checkboxState;
1474
+ if (previousState === targetState) {
1475
+ continue;
1476
+ }
1477
+ record.checkboxState = targetState;
1478
+ this.updateSelectedItems(record, targetState, true);
1182
1479
  }
1183
- data = isRemoteData(this.parent) ? this.parent.getCurrentViewRecords() : data;
1184
- if (!isNullOrUndefined(checkAll)) {
1185
- for (let i = 0; i < data.length; i++) {
1186
- if (checkAll) {
1187
- if (data[parseInt(i.toString(), 10)].checkboxState === 'check') {
1480
+ }
1481
+ /**
1482
+ * Rebuilds `selectedItems`, `selectedUidMap`, and `selectedIndexes` based on the current data states in the model.
1483
+ * This method is called after bulk operations (like headerSelection, grid actions, etc.) to synchronize internal collections.
1484
+ * It ensures `selectedItems` retains original selection order *as much as possible* for currently checked items
1485
+ * and `selectedIndexes` reflects their *current visible order*.
1486
+ *
1487
+ * @param {ITreeData[]} records - The records that were processed (or the full data set if re-evaluating everything).
1488
+ * @param {string} requestType - The data action type such as filtering, searching, refresh,etc.
1489
+ * @returns {void}
1490
+ */
1491
+ updateSelectedCollectionsAfterBulk(records, requestType) {
1492
+ const hasFilter = !!(this.parent.filterModule && this.parent.filterModule.filteredResult &&
1493
+ this.parent.filterModule.filteredResult.length);
1494
+ const hasSearch = !!(this.parent.grid && this.parent.grid.searchSettings &&
1495
+ this.parent.grid.searchSettings.key && this.parent.grid.searchSettings.key.length);
1496
+ const isFilterOrSearch = hasFilter || hasSearch || requestType === 'refresh' || requestType === 'searching';
1497
+ const currentlySelectedItemsInOrder = isFilterOrSearch ? records : this.selectedItems.slice();
1498
+ const newSelectedItems = [];
1499
+ const newSelectedUidMap = new Map();
1500
+ const newSelectedIndexes = [];
1501
+ for (const item of currentlySelectedItemsInOrder) {
1502
+ if (item.hasChildRecords && isFilterOrSearch && item.level === 0) {
1503
+ this.updateParentSelection(item);
1504
+ }
1505
+ if (item.uniqueID && item.checkboxState === 'check') {
1506
+ newSelectedItems.push(item);
1507
+ newSelectedUidMap.set(item.uniqueID, true);
1508
+ }
1509
+ }
1510
+ if (!isFilterOrSearch) {
1511
+ const allFlatData = this.parent.flatData;
1512
+ if (allFlatData) {
1513
+ for (const record of allFlatData) {
1514
+ if (!record || record.isSummaryRow) {
1188
1515
  continue;
1189
1516
  }
1190
- if (multiFilterCheckState) {
1191
- continue;
1517
+ if (record.uniqueID && record.checkboxState === 'check' && !newSelectedUidMap.has(record.uniqueID)) {
1518
+ newSelectedItems.push(record);
1519
+ newSelectedUidMap.set(record.uniqueID, true);
1192
1520
  }
1193
- data[parseInt(i.toString(), 10)].checkboxState = 'check';
1194
- this.updateSelectedItems(data[parseInt(i.toString(), 10)], data[parseInt(i.toString(), 10)].checkboxState);
1195
1521
  }
1196
- else {
1197
- index = this.selectedItems.indexOf(data[parseInt(i.toString(), 10)]);
1198
- if (index > -1) {
1199
- data[parseInt(i.toString(), 10)].checkboxState = 'uncheck';
1200
- this.updateSelectedItems(data[parseInt(i.toString(), 10)], data[parseInt(i.toString(), 10)].checkboxState);
1201
- if (this.parent.autoCheckHierarchy) {
1202
- this.updateParentSelection(data[parseInt(i.toString(), 10)]);
1203
- }
1522
+ }
1523
+ }
1524
+ this.selectedItems = newSelectedItems;
1525
+ this.selectedUidMap = newSelectedUidMap;
1526
+ this.buildVisibleUidMap();
1527
+ for (const item of this.selectedItems) {
1528
+ const visibleIdx = this.visibleUidIndex[item.uniqueID];
1529
+ if (visibleIdx !== undefined) {
1530
+ newSelectedIndexes.push(visibleIdx);
1531
+ }
1532
+ }
1533
+ this.selectedIndexes = newSelectedIndexes;
1534
+ this.checkedItemCount = this.selectedItems.length;
1535
+ this.totalSelectableCount =
1536
+ this.countSelectableRecords(records);
1537
+ }
1538
+ /**
1539
+ * Refreshes visible checkbox DOM elements to reflect the current data state.
1540
+ * This method exclusively updates the UI representation of checkboxes.
1541
+ *
1542
+ * @returns {void}
1543
+ */
1544
+ refreshVisibleCheckboxes() {
1545
+ this.buildVisibleUidMap();
1546
+ const data = this.parent.getCurrentViewRecords();
1547
+ const uidMap = this.parent.uniqueIDCollection;
1548
+ for (let i = 0; data && i < data.length; i++) {
1549
+ const viewRec = data[parseInt(i.toString(), 10)];
1550
+ if (!viewRec) {
1551
+ continue;
1552
+ }
1553
+ const uid = viewRec.uniqueID;
1554
+ const srcRec = (uidMap && uid != null) ? uidMap[String(uid)] : viewRec;
1555
+ const state = (srcRec && srcRec.checkboxState) ? srcRec.checkboxState : 'uncheck';
1556
+ let rowEl = null;
1557
+ const rowUid = viewRec.uid;
1558
+ if (rowUid) {
1559
+ rowEl = this.parent.grid.getRowElementByUID(rowUid);
1560
+ }
1561
+ if (!rowEl) {
1562
+ const rows = this.parent.getRows();
1563
+ rowEl = rows && rows[parseInt(i.toString(), 10)];
1564
+ if ((this.parent.frozenRows || this.parent.getFrozenColumns()) && !rowEl) {
1565
+ const movableRows = this.parent.getDataRows();
1566
+ rowEl = movableRows && movableRows[parseInt(i.toString(), 10)];
1567
+ }
1568
+ }
1569
+ if (rowEl) {
1570
+ const frame = rowEl.querySelector('.e-hierarchycheckbox .e-frame');
1571
+ if (frame) {
1572
+ removeClass([frame], ['e-check', 'e-stop', 'e-uncheck']);
1573
+ frame.classList.add(state === 'indeterminate' ? 'e-stop' : ('e-' + state));
1574
+ const input = rowEl.querySelector('.e-treecheckselect');
1575
+ if (input) {
1576
+ input.setAttribute('aria-checked', state === 'check' ? 'true' :
1577
+ (state === 'uncheck' ? 'false' : 'mixed'));
1204
1578
  }
1205
1579
  }
1206
1580
  }
1207
1581
  }
1208
- if (checkAll === false && this.parent.enableVirtualization) {
1209
- this.selectedItems = [];
1210
- this.selectedIndexes = [];
1211
- data.filter((rec) => {
1212
- rec.checkboxState = 'uncheck';
1213
- this.updateSelectedItems(rec, rec.checkboxState);
1214
- });
1582
+ }
1583
+ /**
1584
+ * Resets internal selection caches to their initial state.
1585
+ * This is usually called before a bulk selection operation (like "select all").
1586
+ *
1587
+ * @returns {void}
1588
+ */
1589
+ resetSelectionCaches() {
1590
+ this.parentSelectionCounters = {};
1591
+ this.selectedUidMap = new Map();
1592
+ this.selectedItems = [];
1593
+ this.selectedIndexes = [];
1594
+ this.totalSelectableCount = 0;
1595
+ this.headerSelectionState = 'uncheck';
1596
+ this.checkedItemCount = 0;
1597
+ }
1598
+ /**
1599
+ * Counts selectable (non-summary) records in the provided array.
1600
+ *
1601
+ * @param {ITreeData[]} records - The records to count.
1602
+ * @returns {number} The number of selectable records.
1603
+ */
1604
+ countSelectableRecords(records) {
1605
+ let count = 0;
1606
+ if (!records) {
1607
+ return count;
1215
1608
  }
1216
- length = this.selectedItems.length;
1217
- const checkbox = this.parent.getHeaderContent().querySelectorAll('.e-frame')[0];
1218
- if (length > 0 && data.length > 0) {
1219
- if (length !== data.length && !checkAll) {
1220
- removeClass([checkbox], ['e-check']);
1221
- checkbox.classList.add('e-stop');
1609
+ for (let i = 0; i < records.length; i++) {
1610
+ const rec = records[parseInt(i.toString(), 10)];
1611
+ if (rec && !rec.isSummaryRow) {
1612
+ count++;
1613
+ }
1614
+ }
1615
+ return count;
1616
+ }
1617
+ /**
1618
+ * Resolves the list of records used for header selection operations (e.g., for `select all`).
1619
+ *
1620
+ * @param {boolean} [includeAll] - If true and data is local, returns flatData (all records for full dataset actions).
1621
+ * @returns {ITreeData[]} The array of records to consider for header operations.
1622
+ */
1623
+ resolveHeaderSelectionList(includeAll) {
1624
+ let dataToProcess = [];
1625
+ if (!isRemoteData(this.parent)) {
1626
+ const hasFilter = !!(this.parent.filterModule &&
1627
+ this.parent.filterModule.filteredResult &&
1628
+ this.parent.filterModule.filteredResult.length);
1629
+ const hasSearch = !!(this.parent.grid &&
1630
+ this.parent.grid.searchSettings &&
1631
+ this.parent.grid.searchSettings.key &&
1632
+ this.parent.grid.searchSettings.key.length);
1633
+ if (includeAll) {
1634
+ if (hasFilter) {
1635
+ dataToProcess = this.filteredList && this.filteredList.length
1636
+ ? this.filteredList
1637
+ : this.parent.filterModule.filteredResult;
1638
+ }
1639
+ else if (hasSearch && this.searchingRecords && this.searchingRecords.length) {
1640
+ dataToProcess = this.searchingRecords;
1641
+ }
1642
+ else {
1643
+ dataToProcess = this.parent.flatData;
1644
+ }
1222
1645
  }
1223
1646
  else {
1224
- removeClass([checkbox], ['e-stop']);
1225
- checkbox.classList.add('e-check');
1647
+ if (hasFilter) {
1648
+ dataToProcess = this.filteredList && this.filteredList.length
1649
+ ? this.filteredList
1650
+ : this.parent.filterModule.filteredResult;
1651
+ }
1652
+ else if (hasSearch && this.searchingRecords && this.searchingRecords.length) {
1653
+ dataToProcess = this.searchingRecords;
1654
+ }
1655
+ else {
1656
+ dataToProcess = this.parent.flatData;
1657
+ }
1226
1658
  }
1227
1659
  }
1228
1660
  else {
1229
- removeClass([checkbox], ['e-check', 'e-stop']);
1661
+ dataToProcess = this.parent.getCurrentViewRecords();
1230
1662
  }
1663
+ return dataToProcess;
1231
1664
  }
1232
- updateSelectedItems(currentRecord, checkState) {
1233
- const record = this.parent.grid.currentViewData.filter((e) => {
1234
- return e.uniqueID === currentRecord.uniqueID;
1235
- });
1236
- let checkedRecord;
1237
- const recordIndex = this.parent.grid.currentViewData.indexOf(record[0]);
1238
- const checkboxRecord = getParentData(this.parent, currentRecord.uniqueID);
1239
- const tr = this.parent.getRows()[parseInt(recordIndex.toString(), 10)];
1240
- let checkbox;
1241
- if (recordIndex > -1) {
1242
- let movableTr;
1243
- if (this.parent.frozenRows || this.parent.getFrozenColumns()) {
1244
- movableTr = this.parent.getDataRows()[parseInt(recordIndex.toString(), 10)];
1245
- }
1246
- checkbox = tr.querySelectorAll('.e-hierarchycheckbox .e-frame')[0] ? tr.querySelectorAll('.e-hierarchycheckbox .e-frame')[0]
1247
- : movableTr.querySelectorAll('.e-hierarchycheckbox .e-frame')[0];
1248
- if (!isNullOrUndefined(checkbox)) {
1249
- removeClass([checkbox], ['e-check', 'e-stop', 'e-uncheck']);
1665
+ /**
1666
+ * Updates the header checkbox state (checked/indeterminate/unchecked) based on current selections.
1667
+ *
1668
+ * @returns {void}
1669
+ */
1670
+ updateHeaderCheckboxState() {
1671
+ const frame = this.headerCheckboxFrameEl;
1672
+ if (!frame) {
1673
+ return;
1674
+ }
1675
+ const recordsForHeaderLogic = this.resolveHeaderSelectionList(true);
1676
+ this.totalSelectableCount = this.countSelectableRecords(recordsForHeaderLogic);
1677
+ let checkedCountForHeaderLogic = 0;
1678
+ for (const record of recordsForHeaderLogic) {
1679
+ if (record && !record.isSummaryRow && record.checkboxState === 'check') {
1680
+ checkedCountForHeaderLogic++;
1250
1681
  }
1251
1682
  }
1252
- checkedRecord = checkboxRecord;
1253
- if (isNullOrUndefined(checkedRecord)) {
1254
- checkedRecord = currentRecord;
1683
+ removeClass([frame], ['e-check', 'e-stop', 'e-uncheck']);
1684
+ if (this.totalSelectableCount === 0) {
1685
+ frame.classList.add('e-uncheck');
1255
1686
  }
1256
- checkedRecord.checkboxState = checkState;
1257
- if (checkState === 'check' && isNullOrUndefined(currentRecord.isSummaryRow)) {
1258
- if (recordIndex !== -1 && this.selectedIndexes.indexOf(recordIndex) === -1) {
1259
- this.selectedIndexes.push(recordIndex);
1260
- }
1261
- if (this.selectedItems.indexOf(checkedRecord) === -1 && (recordIndex !== -1 &&
1262
- (!isNullOrUndefined(this.parent.filterModule) && this.parent.filterModule.filteredResult.length > 0))) {
1263
- this.selectedItems.push(checkedRecord);
1264
- }
1265
- if (this.selectedItems.indexOf(checkedRecord) === -1 && (this.parent.enableVirtualization || this.parent.allowPaging) && ((!isNullOrUndefined(this.parent.filterModule) && this.parent.filterModule.filteredResult.length > 0))) {
1266
- this.selectedItems.push(checkedRecord);
1267
- }
1268
- if (this.selectedItems.indexOf(checkedRecord) === -1 && (!isNullOrUndefined(this.parent.filterModule) &&
1269
- this.parent.filterModule.filteredResult.length === 0)) {
1270
- this.selectedItems.push(checkedRecord);
1687
+ else if (checkedCountForHeaderLogic === 0) {
1688
+ frame.classList.add('e-uncheck');
1689
+ }
1690
+ else if (checkedCountForHeaderLogic === this.totalSelectableCount) {
1691
+ frame.classList.add('e-check');
1692
+ }
1693
+ else {
1694
+ frame.classList.add('e-stop');
1695
+ }
1696
+ }
1697
+ /**
1698
+ * Updates selection arrays (selectedItems, selectedUidMap, selectedIndexes) and visible DOM for a single record.
1699
+ * This is the core method for managing the state of a single checkbox.
1700
+ *
1701
+ * @param {ITreeData} currentRecord - The record to update.
1702
+ * @param {string} checkState - The new checkbox state ('check' | 'uncheck' | 'indeterminate').
1703
+ * @param {boolean} [silent] - If true, update is silent (only updates data model, no collection management or DOM update).
1704
+ * @returns {void}
1705
+ */
1706
+ updateSelectedItems(currentRecord, checkState, silent) {
1707
+ this.buildVisibleUidMap();
1708
+ const uid = currentRecord.uniqueID;
1709
+ const uidMap = this.parent.uniqueIDCollection;
1710
+ const checkboxRecord = (uidMap && uid != null) ? (uidMap[String(uid)] ?
1711
+ uidMap[String(uid)] : currentRecord) : currentRecord;
1712
+ const isSummary = currentRecord.isSummaryRow === true;
1713
+ const previousState = checkboxRecord.checkboxState;
1714
+ const currentVisibleIndex = this.visibleUidIndex[String(uid)];
1715
+ checkboxRecord.checkboxState = checkState;
1716
+ if (silent) {
1717
+ return;
1718
+ }
1719
+ if (!isSummary && previousState !== checkState) {
1720
+ if (checkState === 'check') {
1721
+ this.checkedItemCount++;
1722
+ if (!this.selectedUidMap.has(String(uid))) {
1723
+ if (checkboxRecord.uniqueID) {
1724
+ this.selectedUidMap.set(String(checkboxRecord.uniqueID), true);
1725
+ }
1726
+ this.selectedItems.push(checkboxRecord);
1727
+ if (currentVisibleIndex !== undefined && this.selectedIndexes.indexOf(currentVisibleIndex) === -1) {
1728
+ this.selectedIndexes.push(currentVisibleIndex);
1729
+ }
1730
+ }
1271
1731
  }
1272
- if (this.selectedItems.indexOf(checkedRecord) === -1 && isNullOrUndefined(this.parent.filterModule)) {
1273
- this.selectedItems.push(checkedRecord);
1732
+ else if (previousState === 'check' || previousState === 'indeterminate') {
1733
+ if (this.checkedItemCount > 0) {
1734
+ this.checkedItemCount--;
1735
+ }
1736
+ if (checkboxRecord && checkboxRecord.uniqueID && this.selectedUidMap.has(String(checkboxRecord.uniqueID))) {
1737
+ this.selectedUidMap.delete(String(checkboxRecord.uniqueID));
1738
+ const itemIdx = this.selectedItems.indexOf(checkboxRecord);
1739
+ if (itemIdx !== -1) {
1740
+ this.selectedItems.splice(itemIdx, 1);
1741
+ }
1742
+ if (currentVisibleIndex !== undefined) {
1743
+ const indexInSelectedIndexes = this.selectedIndexes.indexOf(currentVisibleIndex);
1744
+ if (indexInSelectedIndexes > -1) {
1745
+ this.selectedIndexes.splice(indexInSelectedIndexes, 1);
1746
+ }
1747
+ }
1748
+ }
1274
1749
  }
1275
1750
  }
1276
- else if ((checkState === 'uncheck' || checkState === 'indeterminate') && isNullOrUndefined(currentRecord.isSummaryRow)) {
1277
- const index = this.selectedItems.indexOf(checkedRecord);
1278
- if (index !== -1) {
1279
- this.selectedItems.splice(index, 1);
1280
- }
1281
- if (this.selectedIndexes.indexOf(recordIndex) !== -1) {
1282
- const checkedIndex = this.selectedIndexes.indexOf(recordIndex);
1283
- this.selectedIndexes.splice(checkedIndex, 1);
1751
+ let rowEl = null;
1752
+ const rowUid = currentRecord.uid;
1753
+ if (rowUid) {
1754
+ rowEl = this.parent.grid.getRowElementByUID(rowUid);
1755
+ }
1756
+ if (!rowEl) {
1757
+ const recordVisibleIndex = currentVisibleIndex !== undefined ? currentVisibleIndex : (typeof this.visibleUidIndex[String(uid)] === 'number' ? this.visibleUidIndex[String(uid)] : -1);
1758
+ if (recordVisibleIndex > -1) {
1759
+ rowEl = this.parent.getRows()[parseInt(recordVisibleIndex.toString(), 10)];
1760
+ if (!rowEl && (this.parent.frozenRows || this.parent.getFrozenColumns())) {
1761
+ rowEl = this.parent.getDataRows()[parseInt(recordVisibleIndex.toString(), 10)];
1762
+ }
1284
1763
  }
1285
1764
  }
1286
- const checkBoxclass = checkState === 'indeterminate' ? 'e-stop' : 'e-' + checkState;
1287
- if (recordIndex > -1) {
1288
- if (!isNullOrUndefined(checkbox)) {
1289
- checkbox.classList.add(checkBoxclass);
1290
- tr.querySelector('.e-treecheckselect').setAttribute('aria-checked', checkState === 'check' ? 'true' : checkState === 'uncheck' ? 'false' : 'mixed');
1765
+ if (rowEl) {
1766
+ const frame = rowEl.querySelector('.e-hierarchycheckbox .e-frame');
1767
+ if (frame) {
1768
+ removeClass([frame], ['e-check', 'e-stop', 'e-uncheck']);
1769
+ frame.classList.add(checkState === 'indeterminate' ? 'e-stop' : ('e-' + checkState));
1770
+ }
1771
+ const input = rowEl.querySelector('.e-treecheckselect');
1772
+ if (input) {
1773
+ input.setAttribute('aria-checked', checkState === 'check' ? 'true' :
1774
+ (checkState === 'uncheck' ? 'false' : 'mixed'));
1291
1775
  }
1292
1776
  }
1293
1777
  }
1778
+ /**
1779
+ * Handles various grid actions and updates selection state accordingly.
1780
+ * This method ensures that selection state is maintained and UI is refreshed after grid operations.
1781
+ *
1782
+ * @param {CellSaveEventArgs} args - Action arguments containing requestType and data.
1783
+ * @returns {void}
1784
+ */
1294
1785
  updateGridActions(args) {
1295
1786
  const requestType = args.requestType;
1296
- let childData;
1297
- let childLength;
1298
1787
  if (isCheckboxcolumn(this.parent)) {
1299
1788
  if (this.parent.autoCheckHierarchy) {
1300
1789
  if ((requestType === 'sorting' || requestType === 'paging')) {
1301
- const rows = this.parent.grid.getRows();
1302
- childData = this.parent.getCurrentViewRecords();
1303
- childLength = childData.length;
1304
- this.selectedIndexes = [];
1305
- for (let i = 0; i < childLength; i++) {
1306
- if (!rows[parseInt(i.toString(), 10)].classList.contains('e-summaryrow')) {
1307
- this.updateSelectedItems(childData[parseInt(i.toString(), 10)], childData[parseInt(i.toString(), 10)].checkboxState);
1308
- }
1309
- }
1790
+ this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(), '');
1791
+ this.refreshVisibleCheckboxes();
1792
+ this.updateHeaderCheckboxState();
1310
1793
  }
1311
1794
  else if (requestType === 'delete' || args.action === 'add') {
1312
1795
  let updatedData = [];
@@ -1318,71 +1801,68 @@ class Selection {
1318
1801
  }
1319
1802
  for (let i = 0; i < updatedData.length; i++) {
1320
1803
  if (requestType === 'delete') {
1321
- const index = this.parent.flatData.indexOf(updatedData[parseInt(i.toString(), 10)]);
1322
- const checkedIndex = this.selectedIndexes.indexOf(index);
1323
- this.selectedIndexes.splice(checkedIndex, 1);
1324
- this.updateSelectedItems(updatedData[parseInt(i.toString(), 10)], 'uncheck');
1804
+ this.updateSelectedItems(updatedData[parseInt(i.toString(), 10)], 'uncheck', false);
1325
1805
  }
1326
1806
  if (!isNullOrUndefined(updatedData[parseInt(i.toString(), 10)].parentItem)) {
1327
1807
  this.updateParentSelection(updatedData[parseInt(i.toString(), 10)].parentItem);
1328
1808
  }
1329
1809
  }
1810
+ this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(true), '');
1811
+ this.refreshVisibleCheckboxes();
1812
+ if (this.parent.autoCheckHierarchy) {
1813
+ this.updateHeaderCheckboxState();
1814
+ }
1330
1815
  }
1331
1816
  else if (args.requestType === 'add' && this.parent.autoCheckHierarchy) {
1332
1817
  args.data.checkboxState = 'uncheck';
1333
1818
  }
1334
- else if (requestType === 'filtering' || requestType === 'searching' || requestType === 'refresh'
1335
- && !isRemoteData(this.parent)) {
1336
- this.selectedItems = [];
1337
- this.selectedIndexes = [];
1338
- childData = (!isNullOrUndefined(this.parent.filterModule) && this.parent.filterModule.filteredResult.length > 0) ?
1339
- this.parent.filterModule.filteredResult : this.parent.flatData;
1340
- childData.forEach((record) => {
1341
- if (this.parent.enableVirtualization) {
1342
- if (record.hasChildRecords && record.childRecords.length > 0) {
1343
- this.updateParentSelection(record);
1344
- }
1345
- else {
1346
- this.updateSelectedItems(record, record.checkboxState);
1347
- }
1348
- let child = findChildrenRecords(record);
1349
- child = this.getFilteredChildRecords(child);
1350
- for (let i = 0; i < child.length; i++) {
1351
- if (child[parseInt(i.toString(), 10)].hasChildRecords) {
1352
- this.updateParentSelection(child[parseInt(i.toString(), 10)]);
1353
- }
1354
- else if (!(child[parseInt(i.toString(), 10)].hasChildRecords) &&
1355
- !isNullOrUndefined(child[parseInt(i.toString(), 10)])) {
1356
- this.updateSelectedItems(child[parseInt(i.toString(), 10)], child[parseInt(i.toString(), 10)].checkboxState);
1357
- }
1358
- }
1359
- }
1360
- else {
1361
- if (record.hasChildRecords) {
1362
- this.updateParentSelection(record);
1363
- }
1364
- else {
1365
- this.updateSelectedItems(record, record.checkboxState);
1366
- }
1367
- }
1368
- });
1369
- this.headerSelection();
1819
+ else if (requestType === 'filtering' || requestType === 'searching' || requestType === 'refresh') {
1820
+ this.updateSelectedCollectionsAfterBulk(this.resolveHeaderSelectionList(), requestType);
1821
+ this.refreshVisibleCheckboxes();
1822
+ if (this.parent.autoCheckHierarchy) {
1823
+ this.updateHeaderCheckboxState();
1824
+ }
1370
1825
  }
1371
1826
  }
1372
1827
  else {
1373
- if ((requestType === 'filtering' || requestType === 'searching' || requestType === 'refresh')
1374
- && !isRemoteData(this.parent)) {
1828
+ if ((requestType === 'filtering' || requestType === 'searching' || requestType === 'refresh' ||
1829
+ requestType === 'sorting' || requestType === 'paging' || requestType === 'expanding' ||
1830
+ requestType === 'expand' || requestType === 'collapsing' || requestType === 'collapse') && !isRemoteData(this.parent)) {
1375
1831
  this.selectedItems = [];
1832
+ this.selectedUidMap = new Map();
1376
1833
  this.selectedIndexes = [];
1834
+ this.refreshVisibleCheckboxes();
1835
+ if (this.parent.autoCheckHierarchy) {
1836
+ this.updateHeaderCheckboxState();
1837
+ }
1377
1838
  }
1378
1839
  }
1379
1840
  }
1380
1841
  }
1381
- getCheckedrecords() {
1382
- return this.selectedItems;
1383
- }
1842
+ /**
1843
+ * Retrieves checked record objects.
1844
+ * This array maintains the `ITreeData` objects in the order they were selected.
1845
+ *
1846
+ * @returns {ITreeData[]} Array of checked records.
1847
+ */
1848
+ getCheckedrecords() { return this.selectedItems; }
1849
+ /**
1850
+ * Retrieves visible indexes of checked rows in the current view, in the order they were selected.
1851
+ * This method dynamically generates the list of visible indexes by iterating through `selectedItems`
1852
+ * (which preserves selection order) and finding their *current* visible index.
1853
+ *
1854
+ * @returns {number[]} Array of checked row indexes in selection order.
1855
+ */
1384
1856
  getCheckedRowIndexes() {
1385
- return this.selectedIndexes;
1857
+ this.buildVisibleUidMap();
1858
+ const orderedVisibleIndexes = [];
1859
+ for (const selectedItem of this.selectedItems) {
1860
+ const uid = selectedItem.uniqueID;
1861
+ if (uid !== undefined && this.visibleUidIndex[uid] !== undefined) {
1862
+ orderedVisibleIndexes.push(this.visibleUidIndex[uid]);
1863
+ }
1864
+ }
1865
+ return orderedVisibleIndexes;
1386
1866
  }
1387
1867
  }
1388
1868
 
@@ -1944,6 +2424,7 @@ class DataManipulation {
1944
2424
  */
1945
2425
  destroy() {
1946
2426
  this.removeEventListener();
2427
+ this.hierarchyData = null;
1947
2428
  }
1948
2429
  /**
1949
2430
  * @hidden
@@ -2544,7 +3025,7 @@ class DataManipulation {
2544
3025
  this.parent.parentData.push(currentData);
2545
3026
  }
2546
3027
  currentData.uniqueID = getUid(this.parent.element.id + '_data_');
2547
- setValue('uniqueIDCollection.' + currentData.uniqueID, currentData, this.parent);
3028
+ this.parent.uniqueIDCollection[currentData.uniqueID] = currentData;
2548
3029
  if (!isNullOrUndefined(parentRecords)) {
2549
3030
  const parentData = extend$1({}, parentRecords);
2550
3031
  delete parentData.childRecords;
@@ -3075,6 +3556,7 @@ function editAction(details, control, isSelfReference, addRowIndex, selectedInde
3075
3556
  const key = control.grid.getPrimaryKeyFieldNames()[0];
3076
3557
  const treeData = control.dataSource instanceof DataManager ?
3077
3558
  control.dataSource.dataSource.json : control.dataSource;
3559
+ const gridData = control.grid.dataSource;
3078
3560
  let modifiedData = [];
3079
3561
  const originalData = value;
3080
3562
  let isSkip = false;
@@ -3102,9 +3584,9 @@ function editAction(details, control, isSelfReference, addRowIndex, selectedInde
3102
3584
  const keys = modifiedData[parseInt(k.toString(), 10)].taskData ?
3103
3585
  Object.keys(modifiedData[parseInt(k.toString(), 10)].taskData) :
3104
3586
  Object.keys(modifiedData[parseInt(k.toString(), 10)]);
3105
- i = treeData.length;
3587
+ i = treeData.length === 0 && gridData.length === 1 ? gridData.length : treeData.length;
3106
3588
  while (i-- && i >= 0) {
3107
- if (treeData[parseInt(i.toString(), 10)][`${key}`] === modifiedData[parseInt(k.toString(), 10)][`${key}`]) {
3589
+ if ((treeData.length === 0 && gridData.length === 1 && gridData[parseInt(i.toString(), 10)][`${key}`] === modifiedData[parseInt(k.toString(), 10)][`${key}`]) || treeData[parseInt(i.toString(), 10)][`${key}`] === modifiedData[parseInt(k.toString(), 10)][`${key}`]) {
3108
3590
  if (action === 'delete') {
3109
3591
  const currentData = treeData[parseInt(i.toString(), 10)];
3110
3592
  treeData.splice(i, 1);
@@ -3148,7 +3630,10 @@ function editAction(details, control, isSelfReference, addRowIndex, selectedInde
3148
3630
  else if (action === 'add' || action === 'batchsave') {
3149
3631
  let index;
3150
3632
  if (control.editSettings.newRowPosition === 'Child') {
3151
- if (isSelfReference) {
3633
+ if (treeData.length === 0 && gridData.length === 1) {
3634
+ treeData.push(originalData.taskData);
3635
+ }
3636
+ else if (isSelfReference) {
3152
3637
  originalData.taskData[`${control.parentIdMapping}`] = treeData[parseInt(i.toString(), 10)][`${control.idMapping}`];
3153
3638
  treeData.splice(i + 1, 0, originalData.taskData);
3154
3639
  }
@@ -3223,6 +3708,9 @@ function addAction(details, treeData, control, isSelfReference, addRowIndex, sel
3223
3708
  value = extend$1({}, addRowRecord);
3224
3709
  value = getPlainData(value);
3225
3710
  }
3711
+ else if (currentViewRecords.length === 0) {
3712
+ value = getPlainData(value);
3713
+ }
3226
3714
  else {
3227
3715
  value = extend$1({}, currentViewRecords[addRowIndex + 1]);
3228
3716
  value = getPlainData(value);
@@ -3236,8 +3724,8 @@ function addAction(details, treeData, control, isSelfReference, addRowIndex, sel
3236
3724
  }
3237
3725
  else {
3238
3726
  const primaryKeys = control.grid.getPrimaryKeyFieldNames()[0];
3239
- const currentdata = currentViewRecords[parseInt(addRowIndex.toString(), 10)];
3240
- if (!isNullOrUndefined(currentdata) && currentdata[`${primaryKeys}`] === details.value[`${primaryKeys}`] || selectedIndex !== -1) {
3727
+ const currentdata = currentViewRecords.length > 0 ? currentViewRecords[parseInt(addRowIndex.toString(), 10)] : [];
3728
+ if (!isNullOrUndefined(currentdata) && currentdata[`${primaryKeys}`] === details.value[`${primaryKeys}`] || selectedIndex !== -1 && treeData.length !== 0) {
3241
3729
  value = extend$1({}, currentdata);
3242
3730
  }
3243
3731
  else {
@@ -3527,7 +4015,9 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3527
4015
  constructor(options, element) {
3528
4016
  super(options, element);
3529
4017
  this.dataResults = {};
4018
+ /** @hidden */
3530
4019
  this.uniqueIDCollection = {};
4020
+ /** @hidden */
3531
4021
  this.uniqueIDFilterCollection = {};
3532
4022
  this.changedRecords = 'changedRecords';
3533
4023
  this.deletedRecords = 'deletedRecords';
@@ -3537,6 +4027,8 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3537
4027
  this.modifiedRecords = [];
3538
4028
  this.stackedHeader = false;
3539
4029
  this.freezeColumnRefresh = true;
4030
+ this.componentRefresh = Component.prototype.refresh;
4031
+ this.isComponentRefresh = false;
3540
4032
  this.objectEqualityChecker = (old, current) => {
3541
4033
  if (old) {
3542
4034
  const keys = Object.keys(old);
@@ -3585,7 +4077,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3585
4077
  /* eslint-disable */
3586
4078
  excelExport(excelExportProperties, isMultipleExport, workbook, isBlob) {
3587
4079
  /* eslint-enable */
3588
- return this.excelExportModule.Map(excelExportProperties, isMultipleExport, workbook, isBlob, false);
4080
+ return this.allowExcelExport ? this.excelExportModule.Map(excelExportProperties, isMultipleExport, workbook, isBlob, false) : null;
3589
4081
  }
3590
4082
  /**
3591
4083
  * Exports the TreeGrid data to a CSV file.
@@ -3599,7 +4091,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3599
4091
  /* eslint-disable */
3600
4092
  csvExport(excelExportProperties, isMultipleExport, workbook, isBlob) {
3601
4093
  /* eslint-enable */
3602
- return this.excelExportModule.Map(excelExportProperties, isMultipleExport, workbook, isBlob, true);
4094
+ return this.allowExcelExport ? this.excelExportModule.Map(excelExportProperties, isMultipleExport, workbook, isBlob, true) : null;
3603
4095
  }
3604
4096
  /**
3605
4097
  * Exports the TreeGrid data to a PDF document.
@@ -3611,7 +4103,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3611
4103
  * @returns {Promise<any>} - Returns a promise that resolves with the result of the export action.
3612
4104
  */
3613
4105
  pdfExport(pdfExportProperties, isMultipleExport, pdfDoc, isBlob) {
3614
- return this.pdfExportModule.Map(pdfExportProperties, isMultipleExport, pdfDoc, isBlob);
4106
+ return this.allowPdfExport ? this.pdfExportModule.Map(pdfExportProperties, isMultipleExport, pdfDoc, isBlob) : null;
3615
4107
  }
3616
4108
  /**
3617
4109
  * Sends a POST request to export the TreeGrid to an Excel file on the server side.
@@ -3731,6 +4223,26 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3731
4223
  getModuleName() {
3732
4224
  return 'treegrid';
3733
4225
  }
4226
+ /**
4227
+ * Initiates a complete refresh of the TreeGrid's column and layout.
4228
+ *
4229
+ * This method forces a full re-render of the TreeGrid, ensuring that any dynamic
4230
+ * changes to columns or layout are immediately reflected.
4231
+ *
4232
+ * @returns {void}
4233
+ */
4234
+ refreshLayout() {
4235
+ this.componentRefresh();
4236
+ }
4237
+ /**
4238
+ * @param {Object} prop - Defines the property
4239
+ * @param {boolean} muteOnChange - Defines the mute on change
4240
+ * @returns {void}
4241
+ * @private
4242
+ */
4243
+ setProperties(prop, muteOnChange) {
4244
+ super.setProperties(prop, muteOnChange);
4245
+ }
3734
4246
  /**
3735
4247
  * For internal use only - Initialize the event handler;
3736
4248
  *
@@ -3738,6 +4250,9 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3738
4250
  * @returns {void}
3739
4251
  */
3740
4252
  preRender() {
4253
+ if (this.isComponentRefresh) {
4254
+ this.grid = new Grid();
4255
+ }
3741
4256
  this.TreeGridLocale();
3742
4257
  this.initProperties();
3743
4258
  this.defaultLocale = {
@@ -3907,7 +4422,10 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3907
4422
  }
3908
4423
  }
3909
4424
  else {
3910
- this.clearSelection();
4425
+ const contentTableBody = this.grid.getContent().querySelector('.e-table tbody');
4426
+ if (parentTarget && contentTableBody && parentTarget !== contentTableBody.lastElementChild) {
4427
+ this.clearSelection();
4428
+ }
3911
4429
  }
3912
4430
  }
3913
4431
  }
@@ -3978,6 +4496,8 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
3978
4496
  this.isExpandAll = false;
3979
4497
  this.isCollapseAll = false;
3980
4498
  this.freezeColumnRefresh = true;
4499
+ this.componentRefresh = Component.prototype.refresh;
4500
+ this.isComponentRefresh = false;
3981
4501
  this.keyConfigs = {
3982
4502
  ctrlDownArrow: 'ctrl+downarrow',
3983
4503
  ctrlUpArrow: 'ctrl+uparrow',
@@ -4296,6 +4816,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
4296
4816
  if (this.isIndentEnabled) {
4297
4817
  this.refreshToolbarItems();
4298
4818
  }
4819
+ this.updateColumnModel();
4299
4820
  this.wireEvents();
4300
4821
  this.renderComplete();
4301
4822
  const destroyTemplate = 'destroyTemplate';
@@ -4345,6 +4866,15 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
4345
4866
  if (this.allowSelection && !isNullOrUndefined(this.rowTemplate)) {
4346
4867
  failureCases.push('Selection is not supported in RowTemplate');
4347
4868
  }
4869
+ if (!this.allowExcelExport && this.action === 'csvExport') {
4870
+ failureCases.push('CSV export is not allowed when allowExcelExport is disabled.');
4871
+ }
4872
+ if (!this.allowPdfExport && this.action === 'pdfExport') {
4873
+ failureCases.push('PDF export is not allowed when allowPdfExport is disabled');
4874
+ }
4875
+ if (!this.allowExcelExport && this.action === 'excelExport') {
4876
+ failureCases.push('Excel export is not allowed when allowExcelExport is disabled.');
4877
+ }
4348
4878
  if (this.treeColumnIndex >= this.columns.length) {
4349
4879
  failureCases.push('TreeColumnIndex value should not exceed the total column count.');
4350
4880
  }
@@ -4499,6 +5029,8 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
4499
5029
  this.grid.frozenRows = this.frozenRows;
4500
5030
  this.grid.frozenColumns = this.frozenColumns;
4501
5031
  this.grid.clipMode = getActualProperties(this.clipMode);
5032
+ this.grid.enableColumnSpan = this.enableColumnSpan;
5033
+ this.grid.enableRowSpan = this.enableRowSpan;
4502
5034
  const templateInstance = 'templateDotnetInstance';
4503
5035
  this.grid[`${templateInstance}`] = this[`${templateInstance}`];
4504
5036
  const isJsComponent = 'isJsComponent';
@@ -4973,12 +5505,12 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
4973
5505
  this.grid.refresh();
4974
5506
  }
4975
5507
  if (args.action === 'filter') {
4976
- if (!args.isCollapseMaintain && this.filterModule['currentFilterObject'] !== '' && this.enableVirtualization && !this.initialRender && !(isRemoteData(this) && this.enableVirtualization)) {
5508
+ if (!args.isCollapseMaintain && this.filterModule['currentFilterObject'] !== '' && this.enableVirtualization && !this.initialRender && !this.expandStateMapping && !(isRemoteData(this) && this.enableVirtualization)) {
4977
5509
  this.expandAll();
4978
5510
  }
4979
5511
  }
4980
5512
  if (args.requestType === 'searching') {
4981
- if (!args.isCollapseMaintain && this.searchSettings.key !== '' && this.enableVirtualization && !this.initialRender && !(isRemoteData(this) && this.enableVirtualization)) {
5513
+ if (!args.isCollapseMaintain && this.searchSettings.key !== '' && this.enableVirtualization && !this.initialRender && !this.expandStateMapping && !(isRemoteData(this) && this.enableVirtualization)) {
4982
5514
  this.expandAll();
4983
5515
  }
4984
5516
  }
@@ -5313,11 +5845,14 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
5313
5845
  onPropertyChanged(newProp) {
5314
5846
  const properties = Object.keys(newProp);
5315
5847
  let requireRefresh = false;
5316
- if (properties.indexOf('columns') > -1 && !isNullOrUndefined(newProp.columns)) {
5317
- this.refreshColumns();
5318
- }
5319
5848
  for (const prop of properties) {
5320
5849
  switch (prop) {
5850
+ case 'columns':
5851
+ if (!isNullOrUndefined(newProp.columns)) {
5852
+ this.refreshColumns();
5853
+ }
5854
+ requireRefresh = true;
5855
+ break;
5321
5856
  case 'treeColumnIndex':
5322
5857
  this.grid.refreshColumns();
5323
5858
  break;
@@ -5528,8 +6063,19 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
5528
6063
  }
5529
6064
  this.grid.editSettings = this.getGridEditSettings();
5530
6065
  break;
6066
+ case 'enableRowSpan':
6067
+ case 'enableColumnSpan':
6068
+ this.grid.enableRowSpan = this.enableRowSpan;
6069
+ this.grid.enableColumnSpan = this.enableColumnSpan;
6070
+ this.refreshColumns();
6071
+ break;
5531
6072
  }
5532
- if (requireRefresh) {
6073
+ }
6074
+ if (requireRefresh) {
6075
+ if (this.isFrozenGrid()) {
6076
+ this.refreshLayout();
6077
+ }
6078
+ else {
5533
6079
  this.grid.refresh();
5534
6080
  }
5535
6081
  }
@@ -5550,6 +6096,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
5550
6096
  * @returns {void}
5551
6097
  */
5552
6098
  destroy() {
6099
+ this.isComponentRefresh = true;
5553
6100
  const treeGridElement = this.element;
5554
6101
  if (!treeGridElement) {
5555
6102
  return;
@@ -5560,15 +6107,20 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
5560
6107
  this.unwireEvents();
5561
6108
  }
5562
6109
  this.removeListener();
5563
- if (hasTreeGridChild) {
5564
- super.destroy();
6110
+ if (this.dataModule) {
6111
+ this.dataModule.destroy();
5565
6112
  }
5566
6113
  if (this.grid) {
6114
+ this.grid.dataSource = null;
5567
6115
  this.grid.destroy();
5568
6116
  }
5569
- if (this.dataModule) {
5570
- this.dataModule.destroy();
6117
+ if (hasTreeGridChild) {
6118
+ super.destroy();
5571
6119
  }
6120
+ this.infiniteScrollData = null;
6121
+ this.remoteCollapsedData = null;
6122
+ this.remoteExpandedData = null;
6123
+ this.parentData = null;
5572
6124
  const modules = ['dataModule', 'sortModule', 'renderModule', 'filterModule', 'printModule', 'clipboardModule',
5573
6125
  'excelExportModule', 'pdfExportModule', 'toolbarModule', 'summaryModule', 'reorderModule', 'resizeModule',
5574
6126
  'pagerModule', 'keyboardModule', 'columnMenuModule', 'contextMenuModule', 'editModule', 'virtualScrollModule',
@@ -5578,6 +6130,9 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
5578
6130
  this[modules[parseInt(i.toString(), 10)]] = null;
5579
6131
  }
5580
6132
  }
6133
+ this.dataResults = null;
6134
+ this.uniqueIDCollection = {};
6135
+ this.uniqueIDFilterCollection = {};
5581
6136
  this.element.innerHTML = '';
5582
6137
  this.grid = null;
5583
6138
  }
@@ -5718,7 +6273,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
5718
6273
  /**
5719
6274
  * Adds a new record to the TreeGrid at the specified position or default location.
5720
6275
  *
5721
- * @param {Object} data - Object containing the data for the new record. If omitted, an empty row is added.
6276
+ * @param {Object | Object[]} data - Object containing data for a single record, or an array of objects for creating multiple records. If omitted, an empty row is added.
5722
6277
  * @param {number} index - The index at which the new row should be added.
5723
6278
  * @param {RowPosition} position - Specifies the position of the new row (e.g., before, after or child).
5724
6279
  *
@@ -6527,7 +7082,9 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
6527
7082
  if (isNullOrUndefined(refreshUI) || refreshUI) {
6528
7083
  this.grid.columns = this.getGridColumns(this.columns);
6529
7084
  this.getTreeColumn();
6530
- this.grid.refreshColumns();
7085
+ if (!this.isFrozenGrid()) {
7086
+ this.grid.refreshColumns();
7087
+ }
6531
7088
  }
6532
7089
  else {
6533
7090
  this.grid.setProperties({ columns: this.getGridColumns(this.columns) }, true);
@@ -7059,7 +7616,8 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
7059
7616
  if (rows.length) {
7060
7617
  for (let i = 0; i < rows.length; i++) {
7061
7618
  if (action === 'collapse') {
7062
- if (!isNullOrUndefined(this.getCurrentViewRecords()[rows[parseInt(i.toString(), 10)].rowIndex])) {
7619
+ const currentRecordIndx = this.frozenRows ? this.getCurrentViewRecords()[parseInt(rows[parseInt(i.toString(), 10)].getAttribute('aria-rowindex'), 10) - 1] : this.getCurrentViewRecords()[rows[parseInt(i.toString(), 10)].rowIndex];
7620
+ if (!isNullOrUndefined(currentRecordIndx)) {
7063
7621
  this.collapseRow(rows[parseInt(i.toString(), 10)]);
7064
7622
  }
7065
7623
  }
@@ -7183,8 +7741,9 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
7183
7741
  }
7184
7742
  const lastrowIdx = this.getVisibleRecords()[this.getVisibleRecords().length - 1]['index'];
7185
7743
  const lastRow = this.getRowByIndex(lastrowIdx);
7186
- if (this.grid.getContentTable().clientHeight <= this.grid.getContent().clientHeight && !isNullOrUndefined(lastRow) && !lastRow.cells[0].classList.contains('e-lastrowcell')) {
7187
- this.lastRowBorder(lastRow, true);
7744
+ const borderElement = lastRow ? lastRow.nextElementSibling ? lastRow.nextElementSibling.classList.contains('e-detailrow') ? lastRow.nextElementSibling : lastRow : lastRow : null;
7745
+ if (this.grid.getContentTable().clientHeight <= this.grid.getContent().clientHeight && !isNullOrUndefined(borderElement) && !borderElement.cells[0].classList.contains('e-lastrowcell')) {
7746
+ this.lastRowBorder(borderElement, true);
7188
7747
  }
7189
7748
  }
7190
7749
  if (isCountRequired(this) && action === 'expand') {
@@ -7345,12 +7904,16 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
7345
7904
  }
7346
7905
  localExpand(action, row, record) {
7347
7906
  let rows;
7907
+ const detailRow = row.nextElementSibling ? row.nextElementSibling.classList.contains('e-detailrow') ? row.nextElementSibling : null : null;
7348
7908
  const childRecords = this.grid.currentViewData.filter((e) => {
7349
7909
  return e.parentUniqueID === record.uniqueID;
7350
7910
  });
7351
7911
  if (this.isPixelHeight() && row.cells[0].classList.contains('e-lastrowcell')) {
7352
7912
  this.lastRowBorder(row, false);
7353
7913
  }
7914
+ else if (this.isPixelHeight() && detailRow && detailRow.cells[0].classList.contains('e-lastrowcell')) {
7915
+ this.lastRowBorder(row.nextElementSibling, false);
7916
+ }
7354
7917
  let movableRows;
7355
7918
  let freezeRightRows;
7356
7919
  let gridRows = this.getRows();
@@ -7553,6 +8116,7 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
7553
8116
  this.on('updateResults', this.updateResultModel, this);
7554
8117
  this.grid.on('initial-end', this.afterGridRender, this);
7555
8118
  this.grid.on('partial-filter-update', this.partialFilterUpdate, this);
8119
+ this.grid.on('get-row-cells', this.getCellsByTableName, this);
7556
8120
  }
7557
8121
  updateResultModel(returnResult) {
7558
8122
  this.dataResults = returnResult;
@@ -7569,6 +8133,16 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
7569
8133
  this.grid.off('initial-end', this.afterGridRender);
7570
8134
  this.grid.off('last-rowcell-border-updated', this.lastRowCellBorderUpdated);
7571
8135
  this.grid.off('partial-filter-update', this.partialFilterUpdate);
8136
+ this.grid.off('get-row-cells', this.getCellsByTableName);
8137
+ }
8138
+ getCellsByTableName(args) {
8139
+ if (!Array.isArray(args.elements)) {
8140
+ args.elements = [];
8141
+ }
8142
+ if (args.rowIndex < this.grid.getDataRows().length) {
8143
+ const cells = [].slice.call(this.grid.getDataRows()[parseInt(args.rowIndex.toString(), 10)].getElementsByClassName('e-rowcell'));
8144
+ Array.prototype.push.apply(args.elements, cells);
8145
+ }
7572
8146
  }
7573
8147
  partialFilterUpdate(args) {
7574
8148
  const gridFiltered = args.gridFiltered;
@@ -7993,6 +8567,53 @@ let TreeGrid = TreeGrid_1 = class TreeGrid extends Component {
7993
8567
  this.rowDragAndDropModule[this.indentOutdentAction](record, 'outdent');
7994
8568
  }
7995
8569
  }
8570
+ /**
8571
+ * Calculates and returns the optimal page size that fits the current height of the TreeGrid's container.
8572
+ *
8573
+ * @param {number | string } containerHeight - (Optional) The height of the container - i.e. the complete TreeGrid height, which can be a number (in pixels) or a string.
8574
+ * @returns {number} returns the page size
8575
+ */
8576
+ getPageSizeByHeight(containerHeight) {
8577
+ if (isNullOrUndefined(containerHeight)) {
8578
+ const treegridControlElement = document.getElementById(this.element.id);
8579
+ if (treegridControlElement) {
8580
+ containerHeight = treegridControlElement.clientHeight;
8581
+ }
8582
+ else {
8583
+ const root = this.element;
8584
+ containerHeight = root ? (root.offsetHeight || root.clientHeight || 0) : 0;
8585
+ }
8586
+ }
8587
+ if ((this.allowTextWrap && this.textWrapSettings.wrapMode === 'Header') || (!this.allowTextWrap)) {
8588
+ let pagesize = 0;
8589
+ if (typeof containerHeight === 'string' && containerHeight.indexOf('%') !== -1) {
8590
+ containerHeight = parseInt(containerHeight, 10) / 100 * this.element.clientHeight;
8591
+ }
8592
+ const nonContentHeight = this.grid['getNoncontentHeight']() + this.grid.getRowHeight();
8593
+ if (containerHeight > nonContentHeight) {
8594
+ let contentHeight = 0;
8595
+ let calcNonContentHeight = this.grid['getNoncontentHeight']();
8596
+ const pagerMsg = document.getElementsByClassName('e-pagerexternalmsg')[0];
8597
+ if (pagerMsg) {
8598
+ calcNonContentHeight += pagerMsg.clientHeight;
8599
+ }
8600
+ contentHeight = containerHeight - calcNonContentHeight;
8601
+ pagesize = (contentHeight / this.grid.getRowHeight());
8602
+ }
8603
+ if (this.frozenRows > 0) {
8604
+ pagesize = pagesize + this.frozenRows;
8605
+ }
8606
+ if (pagesize > 0) {
8607
+ return Math.floor(pagesize);
8608
+ }
8609
+ else {
8610
+ return 0;
8611
+ }
8612
+ }
8613
+ else {
8614
+ return 0;
8615
+ }
8616
+ }
7996
8617
  };
7997
8618
  __decorate$c([
7998
8619
  Property(0)
@@ -8207,6 +8828,12 @@ __decorate$c([
8207
8828
  __decorate$c([
8208
8829
  Property(false)
8209
8830
  ], TreeGrid.prototype, "allowPdfExport", void 0);
8831
+ __decorate$c([
8832
+ Property(false)
8833
+ ], TreeGrid.prototype, "enableColumnSpan", void 0);
8834
+ __decorate$c([
8835
+ Property(false)
8836
+ ], TreeGrid.prototype, "enableRowSpan", void 0);
8210
8837
  __decorate$c([
8211
8838
  Event()
8212
8839
  ], TreeGrid.prototype, "created", void 0);
@@ -8708,6 +9335,9 @@ class RowDD {
8708
9335
  this.selectedItem = isNullOrUndefined(record) ?
8709
9336
  tObj.getCurrentViewRecords()[parseInt(selectedItemIndex.toString(), 10)] : record;
8710
9337
  const primaryKeyField = this.parent.getPrimaryKeyFieldNames()[0];
9338
+ if (!primaryKeyField) {
9339
+ return;
9340
+ }
8711
9341
  const rowIndex = this.parent.grid.getRowIndexByPrimaryKey(this.selectedItem[`${primaryKeyField}`]);
8712
9342
  this.selectedRow = this.parent[this.selectedRows] = selectedItemIndex !== -1 ?
8713
9343
  this.parent.getSelectedRows()[0]
@@ -11645,7 +12275,7 @@ class Aggregate {
11645
12275
  const value = types[parseInt(i.toString(), 10)] !== 'Custom' ? val[`${key}`] : val;
11646
12276
  single[`${disp}`] = single[`${disp}`] || {};
11647
12277
  single[`${disp}`][`${key}`] = value;
11648
- single[`${disp}`][types[parseInt(i.toString(), 10)]] = !isNullOrUndefined(val) ? formatFn(value) : ' ';
12278
+ single[`${disp}`][types[parseInt(i.toString(), 10)]] = (!isNullOrUndefined(val) && !isNullOrUndefined(value)) ? formatFn(value) : ' ';
11649
12279
  }
11650
12280
  helper.format = summaryColumn.getFormatter();
11651
12281
  const cellElement = createElement('td', {
@@ -11869,6 +12499,7 @@ class ContextMenu {
11869
12499
  addEventListener() {
11870
12500
  this.parent.on('contextMenuOpen', this.contextMenuOpen, this);
11871
12501
  this.parent.on('contextMenuClick', this.contextMenuClick, this);
12502
+ this.parent.on('contextMenuItemClick', this.contextMenuItemClick, this);
11872
12503
  }
11873
12504
  /**
11874
12505
  * @hidden
@@ -11880,6 +12511,21 @@ class ContextMenu {
11880
12511
  }
11881
12512
  this.parent.off('contextMenuOpen', this.contextMenuOpen);
11882
12513
  this.parent.off('contextMenuClick', this.contextMenuClick);
12514
+ this.parent.off('contextMenuItemClick', this.contextMenuItemClick);
12515
+ }
12516
+ contextMenuItemClick(args) {
12517
+ const id = args.item && args.item.id ? args.item.id : '';
12518
+ const delId = this.parent.element.id + '_gridcontrol_cmenu_Delete';
12519
+ if (id !== delId) {
12520
+ return;
12521
+ }
12522
+ if (this.parent.getSelectedRecords()[0].hasChildRecords || this.parent.getSelectedRecords().length > 1) {
12523
+ this.parent.deleteRecord();
12524
+ }
12525
+ else {
12526
+ this.parent.deleteRow(this.parent.getSelectedRows()[0]);
12527
+ }
12528
+ args.cancel = true;
11883
12529
  }
11884
12530
  contextMenuOpen(args) {
11885
12531
  const addRow = select('#' + this.parent.element.id + '_gridcontrol_cmenu_AddRow', args.element);
@@ -12261,13 +12907,22 @@ class BatchEdit {
12261
12907
  focusModule.getContent().matrix.matrix = this.matrix;
12262
12908
  }
12263
12909
  else {
12264
- actualIndex = table.getElementsByClassName('e-batchrow')[0].rowIndex;
12910
+ if (this.parent.frozenRows) {
12911
+ actualIndex = this.batchIndex;
12912
+ }
12913
+ else if (this.parent.editModule.isAddedMultipleRowsByMethod) {
12914
+ actualIndex = e.index;
12915
+ }
12916
+ else {
12917
+ actualIndex = table.getElementsByClassName('e-batchrow')[0].rowIndex;
12918
+ }
12265
12919
  // if (this.parent.frozenRows || this.parent.frozenColumns) {
12266
12920
  // actualIndex = this.batchIndex;
12267
12921
  // }
12268
12922
  }
12269
12923
  focusModule.getContent().matrix.current = [actualIndex, focusModule.getContent().matrix.current[1]];
12270
- if (this.parent.editModule['isAddedRowByMethod'] && !isNullOrUndefined(this.parent.editModule['addRowIndex']) && !this.parent.editModule['isAddedRowByContextMenu']) {
12924
+ if (this.parent.editModule['isAddedRowByMethod'] && !isNullOrUndefined(this.parent.editModule['addRowIndex']) &&
12925
+ !this.parent.editModule['isAddedRowByContextMenu'] && !this.parent.editModule.isAddedMultipleRowsByMethod) {
12271
12926
  const newlyAddedRecords = this.parent.getBatchChanges()['addedRecords'];
12272
12927
  const index = parseInt(this.parent.getContentTable().getElementsByClassName('e-insertedrow')[newlyAddedRecords.length - 1].getAttribute('aria-rowindex'), 10) - 1;
12273
12928
  this.batchRecords.splice(index, 0, newlyAddedRecords[newlyAddedRecords.length - 1]);
@@ -12475,7 +13130,7 @@ class BatchEdit {
12475
13130
  }
12476
13131
  else {
12477
13132
  const totalRecords = extendArray(data);
12478
- if (totalRecords.length) {
13133
+ if (totalRecords.length && currentViewRecords.length !== 0) {
12479
13134
  const startIndex = totalRecords.map((e) => { return e[`${primarykey}`]; })
12480
13135
  .indexOf(currentViewRecords[0][`${primarykey}`]);
12481
13136
  const endIndex = startIndex + this.parent.grid.pageSettings.pageSize;
@@ -12507,7 +13162,12 @@ class BatchEdit {
12507
13162
  this.parent.editModule['previousNewRowPosition'] = rowPosition;
12508
13163
  }
12509
13164
  addRecords[parseInt(i.toString(), 10)].taskData = taskData;
12510
- addRowRecord = this.batchAddRowRecord[parseInt(i.toString(), 10)];
13165
+ if (this.batchAddRowRecord.length > 1) {
13166
+ addRowRecord = this.batchAddRowRecord[parseInt(i.toString(), 10)];
13167
+ }
13168
+ else {
13169
+ addRowRecord = this.batchAddRowRecord[0];
13170
+ }
12511
13171
  if (isNullOrUndefined(addRowRecord)) {
12512
13172
  addRowRecord = this.batchAddRowRecord[i - 1];
12513
13173
  }
@@ -12522,6 +13182,10 @@ class BatchEdit {
12522
13182
  if (isNullOrUndefined(addRecords[parseInt(i.toString(), 10)].index)) {
12523
13183
  addRowIndex = 0;
12524
13184
  }
13185
+ if (this.parent.editModule.isAddedMultipleRowsByMethod && this.isSelfReference && (this.parent.editSettings.newRowPosition === 'Above' || this.parent.editSettings.newRowPosition === 'Below')) {
13186
+ addRowIndex = args.index;
13187
+ addRowRecord = this.parent.flatData[args.index];
13188
+ }
12525
13189
  if (this.parent.editSettings.newRowPosition !== 'Top' && this.parent.editSettings.newRowPosition !== 'Bottom') {
12526
13190
  if (isNullOrUndefined(addRecords[parseInt(i.toString(), 10)].parentItem) && this.selectedIndex === -1) {
12527
13191
  selectedIndex = -1;
@@ -12648,6 +13312,7 @@ class Edit {
12648
13312
  this.isAddedRowByMethod = false;
12649
13313
  this.isAddedRowByContextMenu = false;
12650
13314
  this.isIndexUndefined = false;
13315
+ this.isAddedMultipleRowsByMethod = false;
12651
13316
  Grid.Inject(Edit$1);
12652
13317
  this.parent = parent;
12653
13318
  this.isSelfReference = !isNullOrUndefined(parent.parentIdMapping);
@@ -13304,7 +13969,8 @@ class Edit {
13304
13969
  }
13305
13970
  }
13306
13971
  }
13307
- if (this.parent.editSettings.mode === 'Batch' && !isNullOrUndefined(this.addRowIndex) && this.addRowIndex !== -1 && this['isAddedRowByMethod'] && !this.isAddedRowByContextMenu) {
13972
+ if (this.parent.editSettings.mode === 'Batch' && !isNullOrUndefined(this.addRowIndex) && this.addRowIndex !== -1 &&
13973
+ !this.isAddedMultipleRowsByMethod && this['isAddedRowByMethod'] && !this.isAddedRowByContextMenu) {
13308
13974
  index = this.batchEditModule.getAddRowIndex();
13309
13975
  this.selectedIndex = this.batchEditModule.getSelectedIndex();
13310
13976
  const batchAddedRecords = this.parent.getBatchChanges()['addedRecords'];
@@ -13326,6 +13992,28 @@ class Edit {
13326
13992
  this.batchEditModule['batchAddRowRecord'].push(this.batchEditModule['addRowRecord']);
13327
13993
  this.batchEditModule['batchAddedRecords'].push(args['data']);
13328
13994
  }
13995
+ else if (this.parent.editSettings.mode === 'Batch' && this.isAddedMultipleRowsByMethod && (this.parent.editSettings.newRowPosition === 'Above' || this.parent.editSettings.newRowPosition === 'Below')) {
13996
+ index = this.multipleRowIndex;
13997
+ this.selectedIndex = this.multipleRowIndex;
13998
+ const batchAddedRecords = this.updatedRecords.addedRecords;
13999
+ let newlyAddedRecord;
14000
+ if (batchAddedRecords.length) {
14001
+ for (let i = 0; i < batchAddedRecords.length; i++) {
14002
+ if (isNullOrUndefined(batchAddedRecords[parseInt(i.toString(), 10)].uniqueID)) {
14003
+ newlyAddedRecord = batchAddedRecords[parseInt(i.toString(), 10)];
14004
+ }
14005
+ const args = {
14006
+ action: 'add',
14007
+ data: newlyAddedRecord,
14008
+ index: index,
14009
+ seletedRow: 0
14010
+ };
14011
+ this.beginAddEdit(args);
14012
+ this.batchEditModule['batchAddRowRecord'].push(this.batchEditModule['addRowRecord']);
14013
+ this.batchEditModule['batchAddedRecords'].push(args['data']);
14014
+ }
14015
+ }
14016
+ }
13329
14017
  }
13330
14018
  // private beforeDataBound(args: BeforeDataBoundArgs): void {
13331
14019
  // if (this.parent.grid.isEdit && this.parent.dataSource instanceof DataManager &&
@@ -13346,6 +14034,9 @@ class Edit {
13346
14034
  // }
13347
14035
  // }
13348
14036
  beginEdit(args) {
14037
+ if (this.parent.flatData.length === 0 && !isNullOrUndefined(this.addRowRecord)) {
14038
+ this.addRowRecord = undefined;
14039
+ }
13349
14040
  if (args.requestType === 'refresh' && this.isOnBatch) {
13350
14041
  args.cancel = true;
13351
14042
  return;
@@ -13607,7 +14298,35 @@ class Edit {
13607
14298
  if (isNullOrUndefined(index)) {
13608
14299
  this.isIndexUndefined = true;
13609
14300
  }
13610
- if (!this.isSelfReference && !isNullOrUndefined(data) && Object.hasOwnProperty.call(data, this.parent.childMapping)) {
14301
+ if (!isNullOrUndefined(data) && Array.isArray(data)) {
14302
+ let addRecords = [];
14303
+ const previousEditMode = this.parent.editSettings.mode;
14304
+ const previousGridEditMode = this.parent.grid.editSettings.mode;
14305
+ if (!this.isSelfReference && !isNullOrUndefined(data) && Object.hasOwnProperty.call(data, this.parent.childMapping)) {
14306
+ addRecords.push(data);
14307
+ }
14308
+ else if (Array.isArray(data)) {
14309
+ addRecords = data;
14310
+ }
14311
+ this.parent.setProperties({ editSettings: { mode: 'Batch' } }, true);
14312
+ this.parent.grid.setProperties({ editSettings: { mode: 'Batch' } }, true);
14313
+ if (!isNullOrUndefined(position)) {
14314
+ this.parent.setProperties({ editSettings: { newRowPosition: position } }, true);
14315
+ }
14316
+ this.updatedRecords = { addedRecords: addRecords, changedRecords: [], deletedRecords: [] };
14317
+ if ((position === 'Above' || position === 'Below') && this.isSelfReference) {
14318
+ this.isAddedMultipleRowsByMethod = true;
14319
+ this.multipleRowIndex = index;
14320
+ this.addRowIndex = index;
14321
+ this.parent.notify(batchAdd, { updatedRecords: this.updatedRecords, index: index });
14322
+ }
14323
+ const updatedRecords = this.updatedRecords;
14324
+ this.parent.notify(batchSave, { updatedRecords, index });
14325
+ this.parent.setProperties({ editSettings: { mode: previousEditMode } }, true);
14326
+ this.parent.grid.setProperties({ editSettings: { mode: previousGridEditMode } }, true);
14327
+ this.parent.refresh();
14328
+ }
14329
+ else if (!this.isSelfReference && !isNullOrUndefined(data) && Object.hasOwnProperty.call(data, this.parent.childMapping)) {
13611
14330
  const addRecords = [];
13612
14331
  const previousEditMode = this.parent.editSettings.mode;
13613
14332
  const previousGridEditMode = this.parent.grid.editSettings.mode;
@@ -13617,7 +14336,8 @@ class Edit {
13617
14336
  if (!isNullOrUndefined(position)) {
13618
14337
  this.parent.setProperties({ editSettings: { newRowPosition: position } }, true);
13619
14338
  }
13620
- const updatedRecords = { addedRecords: addRecords, changedRecords: [], deletedRecords: [] };
14339
+ this.updatedRecords = { addedRecords: addRecords, changedRecords: [], deletedRecords: [] };
14340
+ const updatedRecords = this.updatedRecords;
13621
14341
  this.parent.notify(batchSave, { updatedRecords, index });
13622
14342
  this.parent.setProperties({ editSettings: { mode: previousEditMode } }, true);
13623
14343
  this.parent.grid.setProperties({ editSettings: { mode: previousGridEditMode } }, true);
@@ -14097,6 +14817,9 @@ class VirtualTreeContentRenderer extends VirtualContentRenderer {
14097
14817
  super.renderTable();
14098
14818
  if (!(this.parent.dataSource instanceof DataManager && this.parent.dataSource.dataSource.url !== undefined
14099
14819
  && this.parent.dataSource.dataSource.offline && this.parent.dataSource.dataSource.url !== '') || !isCountRequired(this.parent)) {
14820
+ if (this.observers) {
14821
+ this.observers.disconnect();
14822
+ }
14100
14823
  getValue('observer', this).options.debounceEvent = false;
14101
14824
  this.observers = new TreeInterSectionObserver(getValue('observer', this).element, getValue('observer', this).options);
14102
14825
  this.contents = this.getPanel().firstChild;
@@ -14811,6 +15534,9 @@ class VirtualTreeContentRenderer extends VirtualContentRenderer {
14811
15534
  this.parent.off('refresh-virtual-editform-cells', this.refreshCell);
14812
15535
  this.parent.off('virtaul-cell-focus', this.cellFocus);
14813
15536
  this.parent.off('virtual-scroll-edit', this.restoreEditState);
15537
+ if (this.observers) {
15538
+ this.observers.disconnect();
15539
+ }
14814
15540
  }
14815
15541
  }
14816
15542
  class TreeInterSectionObserver extends InterSectionObserver {
@@ -14833,12 +15559,28 @@ class TreeInterSectionObserver extends InterSectionObserver {
14833
15559
  observes(callback, onEnterCallback, instance) {
14834
15560
  const containerRect = 'containerRect';
14835
15561
  super[`${containerRect}`] = getValue('options', this).container.getBoundingClientRect();
14836
- EventHandler.add(getValue('options', this).container, 'scroll', this.virtualScrollHandlers(callback, onEnterCallback, instance), this);
15562
+ this.containerEl = getValue('options', this).container;
15563
+ this.containerScrollHandler = this.virtualScrollHandlers(callback, onEnterCallback, instance);
15564
+ EventHandler.add(this.containerEl, 'scroll', this.containerScrollHandler, this);
14837
15565
  if (getValue('options', this).movableContainer) {
14838
15566
  const movableContainerRect = 'movableContainerRect';
14839
15567
  super[`${movableContainerRect}`] = getValue('options', this).movableContainer.getBoundingClientRect();
14840
- EventHandler.add(getValue('options', this).movableContainer, 'scroll', this.virtualScrollHandlers(callback, onEnterCallback, instance), this);
15568
+ this.movableContainerEl = getValue('options', this).movableContainer;
15569
+ this.movableScrollHandler = this.virtualScrollHandlers(callback, onEnterCallback, instance);
15570
+ EventHandler.add(this.movableContainerEl, 'scroll', this.movableScrollHandler, this);
15571
+ }
15572
+ }
15573
+ disconnect() {
15574
+ if (this.containerEl && this.containerScrollHandler) {
15575
+ EventHandler.remove(this.containerEl, 'scroll', this.containerScrollHandler);
15576
+ this.containerScrollHandler = null;
15577
+ }
15578
+ if (this.movableContainerEl && this.movableScrollHandler) {
15579
+ EventHandler.remove(this.movableContainerEl, 'scroll', this.movableScrollHandler);
15580
+ this.movableScrollHandler = null;
14841
15581
  }
15582
+ this.containerEl = null;
15583
+ this.movableContainerEl = null;
14842
15584
  }
14843
15585
  /**
14844
15586
  * Clears the last known position.
@@ -15046,9 +15788,12 @@ class VirtualScroll {
15046
15788
  const dm = new DataManager(pageingDetails.result);
15047
15789
  const expanded = new Predicate$1('expanded', 'notequal', null).or('expanded', 'notequal', undefined);
15048
15790
  const parents = dm.executeLocal(new Query().where(expanded));
15049
- const visualData = parents.filter((e) => {
15050
- return getExpandStatus(this.parent, e);
15051
- });
15791
+ const isFiltering = pageingDetails.actionArgs.requestType === 'filtering';
15792
+ const isFlatHierarchy = this.parent.filterSettings.hierarchyMode === 'Child' ||
15793
+ this.parent.filterSettings.hierarchyMode === 'None';
15794
+ const visualData = isFiltering && isFlatHierarchy
15795
+ ? parents
15796
+ : parents.filter((e) => getExpandStatus(this.parent, e));
15052
15797
  this.visualData = visualData;
15053
15798
  pageingDetails.count = visualData.length;
15054
15799
  this.parent.grid.notify(dataListener, { data: visualData });
@@ -15279,7 +16024,7 @@ class Freeze {
15279
16024
  }
15280
16025
  const queryselector = args.action === 'e-childrow-hidden' ? '.e-treecolumn-container .e-treegridcollapse'
15281
16026
  : '.e-treecolumn-container .e-treegridexpand';
15282
- if (frozenrows[row.rowIndex].querySelector(queryselector)) {
16027
+ if (frozenrows[parseInt(row.getAttribute('aria-rowindex'), 10) - 1].querySelector(queryselector)) {
15283
16028
  const cRow = [];
15284
16029
  for (let i = 0; i < movableRows.length; i++) {
15285
16030
  if (movableRows[parseInt(i.toString(), 10)].querySelector('.e-gridrowindex' + rData.index + 'level' + (rData.level + 1))) {