apex-grid 3.4.0 → 3.6.0

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 (217) hide show
  1. package/CHANGELOG.md +439 -0
  2. package/LICENSE.md +125 -0
  3. package/README.md +107 -14
  4. package/components/cell.d.ts +30 -4
  5. package/components/cell.js +199 -53
  6. package/components/cell.js.map +1 -1
  7. package/components/filter-row.d.ts +2 -0
  8. package/components/filter-row.js +82 -28
  9. package/components/filter-row.js.map +1 -1
  10. package/components/grid.d.ts +257 -20
  11. package/components/grid.js +463 -99
  12. package/components/grid.js.map +1 -1
  13. package/components/group-header-row.js +28 -25
  14. package/components/group-header-row.js.map +1 -1
  15. package/components/header-row.d.ts +7 -5
  16. package/components/header-row.js +19 -9
  17. package/components/header-row.js.map +1 -1
  18. package/components/header.js +36 -22
  19. package/components/header.js.map +1 -1
  20. package/components/row.d.ts +19 -3
  21. package/components/row.js +96 -22
  22. package/components/row.js.map +1 -1
  23. package/components/toolbar.js +2 -1
  24. package/components/toolbar.js.map +1 -1
  25. package/components/virtualizer.d.ts +45 -1
  26. package/components/virtualizer.js +184 -3
  27. package/components/virtualizer.js.map +1 -1
  28. package/controllers/dom.d.ts +4 -4
  29. package/controllers/dom.js +40 -22
  30. package/controllers/dom.js.map +1 -1
  31. package/controllers/editing.d.ts +104 -37
  32. package/controllers/editing.js +230 -78
  33. package/controllers/editing.js.map +1 -1
  34. package/controllers/expansion.d.ts +14 -5
  35. package/controllers/expansion.js +24 -1
  36. package/controllers/expansion.js.map +1 -1
  37. package/controllers/filter.d.ts +10 -2
  38. package/controllers/filter.js +62 -17
  39. package/controllers/filter.js.map +1 -1
  40. package/controllers/history.d.ts +45 -8
  41. package/controllers/history.js +58 -20
  42. package/controllers/history.js.map +1 -1
  43. package/controllers/navigation.d.ts +68 -13
  44. package/controllers/navigation.js +196 -51
  45. package/controllers/navigation.js.map +1 -1
  46. package/controllers/pagination.d.ts +6 -0
  47. package/controllers/pagination.js +10 -2
  48. package/controllers/pagination.js.map +1 -1
  49. package/controllers/reorder.js +42 -24
  50. package/controllers/reorder.js.map +1 -1
  51. package/controllers/resize.d.ts +17 -2
  52. package/controllers/resize.js +16 -6
  53. package/controllers/resize.js.map +1 -1
  54. package/controllers/row-pin.d.ts +20 -2
  55. package/controllers/row-pin.js +16 -9
  56. package/controllers/row-pin.js.map +1 -1
  57. package/controllers/row-reorder.d.ts +31 -11
  58. package/controllers/row-reorder.js +117 -37
  59. package/controllers/row-reorder.js.map +1 -1
  60. package/controllers/selection.d.ts +11 -4
  61. package/controllers/selection.js +26 -11
  62. package/controllers/selection.js.map +1 -1
  63. package/controllers/sort.d.ts +22 -2
  64. package/controllers/sort.js +29 -23
  65. package/controllers/sort.js.map +1 -1
  66. package/controllers/state.d.ts +65 -10
  67. package/controllers/state.js +86 -6
  68. package/controllers/state.js.map +1 -1
  69. package/controllers/tree.d.ts +15 -10
  70. package/controllers/tree.js +0 -0
  71. package/controllers/tree.js.map +1 -1
  72. package/custom-elements.json +3589 -452
  73. package/docs/assets/hierarchy.js +1 -1
  74. package/docs/assets/highlight.css +15 -8
  75. package/docs/assets/navigation.js +1 -1
  76. package/docs/assets/search.js +1 -1
  77. package/docs/classes/ApexGrid.html +660 -0
  78. package/docs/classes/ApexGridPaginator.html +36 -0
  79. package/docs/classes/ApexGridToolbar.html +67 -0
  80. package/docs/functions/localize.html +2 -2
  81. package/docs/functions/setup.html +1 -2
  82. package/docs/hierarchy.html +1 -1
  83. package/docs/index.html +105 -15
  84. package/docs/interfaces/ApexCellValidationFailedEvent.html +3 -2
  85. package/docs/interfaces/ApexCellValueChangedEvent.html +4 -2
  86. package/docs/interfaces/ApexCellValueChangingEvent.html +3 -2
  87. package/docs/interfaces/ApexColumnMovedEvent.html +2 -2
  88. package/docs/interfaces/ApexColumnMovingEvent.html +1 -1
  89. package/docs/interfaces/ApexDetailContext.html +2 -2
  90. package/docs/interfaces/ApexGridEventMap.html +9 -6
  91. package/docs/interfaces/ApexRowEditEndedEvent.html +2 -1
  92. package/docs/interfaces/ApexRowEditStartedEvent.html +2 -1
  93. package/docs/interfaces/ApexRowMovingEvent.html +9 -3
  94. package/docs/interfaces/ApexRowPinningEvent.html +1 -1
  95. package/docs/interfaces/ApexTreeRowExpandedEvent.html +4 -1
  96. package/docs/interfaces/ApexTreeRowExpandingEvent.html +4 -1
  97. package/docs/interfaces/BaseApexCellContext.html +1 -1
  98. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  99. package/docs/interfaces/BaseColumnConfiguration.html +3 -3
  100. package/docs/interfaces/BaseFilterExpression.html +2 -2
  101. package/docs/interfaces/CSVExportOptions.html +13 -5
  102. package/docs/interfaces/DataPipelineConfiguration.html +2 -2
  103. package/docs/interfaces/EditingHistoryConfiguration.html +1 -1
  104. package/docs/interfaces/ExportFormat.html +2 -2
  105. package/docs/interfaces/ExportOptions.html +5 -3
  106. package/docs/interfaces/GridExpansionConfiguration.html +6 -2
  107. package/docs/interfaces/GridRowPinningConfiguration.html +1 -1
  108. package/docs/interfaces/GridRowReorderingConfiguration.html +2 -2
  109. package/docs/interfaces/PaginationConfiguration.html +1 -1
  110. package/docs/interfaces/PaginationState.html +1 -1
  111. package/docs/interfaces/SetStateResult.html +3 -2
  112. package/docs/interfaces/ValidatorContext.html +7 -2
  113. package/docs/media/_token-values.scss +118 -0
  114. package/docs/media/package.json +5 -5
  115. package/docs/media/state-persistence.html +0 -10
  116. package/docs/modules.html +1 -1
  117. package/docs/types/DataPipelineParams.html +2 -2
  118. package/docs/types/EditMode.html +2 -2
  119. package/docs/types/ExportSource.html +6 -4
  120. package/docs/types/GridLocaleText.html +1 -1
  121. package/docs/types/OperandKeys.html +9 -0
  122. package/docs/types/PaginationMode.html +1 -1
  123. package/docs/variables/EN_LOCALE.html +3 -3
  124. package/docs/variables/esLocale.html +1 -1
  125. package/i18n/en.d.ts +19 -0
  126. package/i18n/en.js +19 -0
  127. package/i18n/en.js.map +1 -1
  128. package/i18n/es.js +19 -0
  129. package/i18n/es.js.map +1 -1
  130. package/index.d.ts +2 -0
  131. package/index.js +2 -0
  132. package/index.js.map +1 -1
  133. package/internal/column-types.d.ts +27 -1
  134. package/internal/column-types.js +75 -49
  135. package/internal/column-types.js.map +1 -1
  136. package/internal/constants.d.ts +1 -2
  137. package/internal/constants.js +0 -4
  138. package/internal/constants.js.map +1 -1
  139. package/internal/document-styles.d.ts +30 -0
  140. package/internal/document-styles.js +48 -0
  141. package/internal/document-styles.js.map +1 -0
  142. package/internal/export.d.ts +39 -7
  143. package/internal/export.js +35 -34
  144. package/internal/export.js.map +1 -1
  145. package/internal/feature-module.d.ts +82 -3
  146. package/internal/feature-module.js +11 -0
  147. package/internal/feature-module.js.map +1 -1
  148. package/internal/index.d.ts +6 -4
  149. package/internal/index.js +5 -3
  150. package/internal/index.js.map +1 -1
  151. package/internal/normalize-case.d.ts +8 -1
  152. package/internal/normalize-case.js +3 -2
  153. package/internal/normalize-case.js.map +1 -1
  154. package/internal/state-snapshot.d.ts +60 -8
  155. package/internal/state-snapshot.js +87 -19
  156. package/internal/state-snapshot.js.map +1 -1
  157. package/internal/types.d.ts +17 -2
  158. package/internal/types.js.map +1 -1
  159. package/internal/utils.d.ts +48 -6
  160. package/internal/utils.js +35 -21
  161. package/internal/utils.js.map +1 -1
  162. package/internal/validators.js +1 -0
  163. package/internal/validators.js.map +1 -1
  164. package/operations/filter/operands/number.js +9 -6
  165. package/operations/filter/operands/number.js.map +1 -1
  166. package/operations/filter/operands/string.js +2 -3
  167. package/operations/filter/operands/string.js.map +1 -1
  168. package/operations/filter/types.d.ts +13 -1
  169. package/operations/filter/types.js.map +1 -1
  170. package/operations/filter.d.ts +6 -0
  171. package/operations/filter.js +23 -7
  172. package/operations/filter.js.map +1 -1
  173. package/operations/quick-filter.js +2 -5
  174. package/operations/quick-filter.js.map +1 -1
  175. package/operations/sort.d.ts +21 -2
  176. package/operations/sort.js +41 -14
  177. package/operations/sort.js.map +1 -1
  178. package/package.json +8 -4
  179. package/setup.d.ts +23 -11
  180. package/setup.js +19 -21
  181. package/setup.js.map +1 -1
  182. package/styles/_common.css.js +1 -1
  183. package/styles/_common.css.js.map +1 -1
  184. package/styles/_token-values.css.d.ts +1 -0
  185. package/styles/_token-values.css.js +3 -0
  186. package/styles/_token-values.css.js.map +1 -0
  187. package/styles/_tokens.css.js +1 -1
  188. package/styles/_tokens.css.js.map +1 -1
  189. package/styles/body-cell/body-cell.css.js +1 -1
  190. package/styles/body-cell/body-cell.css.js.map +1 -1
  191. package/styles/body-cell/cell-renderers.css.js +1 -1
  192. package/styles/body-cell/cell-renderers.css.js.map +1 -1
  193. package/styles/body-row/body-row.css.js +1 -1
  194. package/styles/body-row/body-row.css.js.map +1 -1
  195. package/styles/document-tokens.css.d.ts +1 -0
  196. package/styles/document-tokens.css.js +3 -0
  197. package/styles/document-tokens.css.js.map +1 -0
  198. package/styles/filter-row/filter-row.css.js +1 -1
  199. package/styles/filter-row/filter-row.css.js.map +1 -1
  200. package/styles/grid/_grid.base.css.js +1 -1
  201. package/styles/grid/_grid.base.css.js.map +1 -1
  202. package/styles/grid/grid.css.js +1 -1
  203. package/styles/grid/grid.css.js.map +1 -1
  204. package/styles/group-header-row/group-header-row.css.js +1 -1
  205. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  206. package/styles/header-cell/header-cell.css.js +1 -1
  207. package/styles/header-cell/header-cell.css.js.map +1 -1
  208. package/styles/header-row/header-row.base.css.js +1 -1
  209. package/styles/header-row/header-row.base.css.js.map +1 -1
  210. package/styles/paginator/paginator.css.js +1 -1
  211. package/styles/paginator/paginator.css.js.map +1 -1
  212. package/styles/toolbar/toolbar.css.js +1 -1
  213. package/styles/toolbar/toolbar.css.js.map +1 -1
  214. package/tokens.css +78 -0
  215. package/LICENSE +0 -80
  216. package/docs/media/LICENSE +0 -80
  217. package/docs/media/_tokens.scss +0 -193
@@ -10,16 +10,18 @@ import { eventOptions, property, query, queryAll, state } from 'lit/decorators.j
10
10
  import { styleMap } from 'lit/directives/style-map.js';
11
11
  import { DataOperationsController } from '../controllers/data-operation.js';
12
12
  import { GridDOMController } from '../controllers/dom.js';
13
+ import { viewIndexer } from '../controllers/history.js';
13
14
  import { gridStateContext, StateController } from '../controllers/state.js';
14
15
  import { localize as resolveLocaleText, } from '../i18n/index.js';
15
16
  import { DEFAULT_COLUMN_CONFIG, PIPELINE, SENTINEL_NODE } from '../internal/constants.js';
17
+ import { adoptGridDocumentStyles } from '../internal/document-styles.js';
16
18
  import { buildCSV, downloadBlob, } from '../internal/export.js';
17
19
  import { EventEmitterBase } from '../internal/mixins/event-emitter.js';
18
20
  import { registerComponent } from '../internal/register.js';
19
21
  import { columnSchema, operandsByType } from '../internal/state-schema.js';
20
- import { applyColumnLayout, deserializeFilter, resolveRowRefs, serializeColumnLayout, serializeFilter, serializeRowRefs, serializeSort, } from '../internal/state-snapshot.js';
22
+ import { applyColumnLayout, deserializeFilter, resolveFilterOperand, resolveRowRefs, SET_STATE_SLICES, serializeColumnLayout, serializeFilter, serializeRowRefs, serializeSort, } from '../internal/state-snapshot.js';
21
23
  import { GRID_TAG } from '../internal/tags.js';
22
- import { asArray, autoGenerateColumns, getDisplayColumns, getFilterOperandsFor, } from '../internal/utils.js';
24
+ import { asArray, autoGenerateColumns, getDisplayColumns } from '../internal/utils.js';
23
25
  import { watch } from '../internal/watch.js';
24
26
  import { styles as gridStyles } from '../styles/grid/grid.css.js';
25
27
  import ApexGridCell from './cell.js';
@@ -30,19 +32,33 @@ import ApexGridPaginator from './paginator.js';
30
32
  import ApexGridRow from './row.js';
31
33
  import ApexGridToolbar from './toolbar.js';
32
34
  import ApexVirtualizer from './virtualizer.js';
33
- const SET_STATE_SLICES = [
35
+ const SORT_DIRECTIONS = ['ascending', 'descending', 'none'];
36
+ const VIEW_SHAPING_SLICES = new Set([
34
37
  'columns',
35
- 'sort',
36
38
  'filter',
37
39
  'quickFilter',
38
40
  'modules',
39
- 'rowOrder',
40
41
  'tree',
41
- 'expansion',
42
- 'rowPinning',
43
- 'selection',
44
- 'pagination',
45
- ];
42
+ ]);
43
+ const VIEW_SHAPING_PROPERTIES = new Map([
44
+ ['data', null],
45
+ ['quickFilter', null],
46
+ ['pagination', ['enabled', 'mode', 'pageSize', 'totalItems']],
47
+ ['tree', ['enabled', 'getDataPath', 'defaultExpanded']],
48
+ ['dataPipelineConfiguration', ['sort', 'filter', 'pagination', 'quickFilter']],
49
+ ]);
50
+ function viewShapeChanged(fields, before, after) {
51
+ if (Object.is(before, after))
52
+ return false;
53
+ if (!fields)
54
+ return true;
55
+ const a = (before ?? {});
56
+ const b = (after ?? {});
57
+ return fields.some((field) => !Object.is(a[field], b[field]));
58
+ }
59
+ function warnDropped(message) {
60
+ console.warn(`[apex-grid] ${message}`);
61
+ }
46
62
  export class ApexGrid extends EventEmitterBase {
47
63
  constructor() {
48
64
  super(...arguments);
@@ -55,21 +71,27 @@ export class ApexGrid extends EventEmitterBase {
55
71
  });
56
72
  this.dataState = [];
57
73
  this.columns = [];
58
- this.data = [];
74
+ this.#data = [];
59
75
  this.autoGenerate = false;
60
76
  this.sortConfiguration = {
61
77
  multiple: true,
62
78
  triState: true,
63
79
  };
64
- this.quickFilter = '';
80
+ this.#quickFilter = '';
81
+ this.#quickFilterWrites = 0;
65
82
  this.showQuickFilter = false;
66
83
  this.showExport = false;
67
84
  this.columnReordering = false;
68
85
  this.columnSeparator = true;
69
86
  this.coordinateHints = false;
70
87
  this.columnMenu = true;
88
+ this.#viewChangePending = false;
89
+ this.#shapingPending = false;
90
+ this.#pipelineRun = 0;
91
+ this.#pipelineSettled = Promise.resolve();
92
+ this.#settlePipeline = null;
71
93
  this.#autoAriaLabel = null;
72
- this.#stateChangedListeners = 0;
94
+ this.#stateChangedListeners = new Map();
73
95
  this.#lastStateJson = null;
74
96
  }
75
97
  static get tagName() {
@@ -82,6 +104,24 @@ export class ApexGrid extends EventEmitterBase {
82
104
  createStateController() {
83
105
  return new StateController(this);
84
106
  }
107
+ #data;
108
+ get data() {
109
+ return this.#data;
110
+ }
111
+ set data(value) {
112
+ if (value == null && !this.#data.length)
113
+ return;
114
+ this.#data = value ?? [];
115
+ }
116
+ get quickFilter() {
117
+ return this.#quickFilter;
118
+ }
119
+ set quickFilter(value) {
120
+ this.#quickFilter = value;
121
+ this.#quickFilterWrites++;
122
+ }
123
+ #quickFilter;
124
+ #quickFilterWrites;
85
125
  localize(key, params, fallback) {
86
126
  return resolveLocaleText(this.localeText, key, params, fallback);
87
127
  }
@@ -160,26 +200,50 @@ export class ApexGrid extends EventEmitterBase {
160
200
  isTreeRowExpanded(row) {
161
201
  return this.stateController.tree.isExpanded(row);
162
202
  }
203
+ #pendingSort;
204
+ #pendingFilter;
163
205
  set sortExpressions(expressions) {
164
- if (expressions.length) {
165
- this.sort(expressions);
166
- }
206
+ this.#pendingSort = [...(expressions ?? [])];
207
+ this.requestUpdate(PIPELINE);
167
208
  }
168
209
  get sortExpressions() {
210
+ if (this.#pendingSort)
211
+ return [...this.#pendingSort];
169
212
  return Array.from(this.stateController.sorting.state.values());
170
213
  }
171
214
  set filterExpressions(expressions) {
172
- if (expressions.length) {
173
- this.filter(expressions);
174
- }
215
+ this.#pendingFilter = [...(expressions ?? [])];
216
+ this.#markViewChange();
175
217
  }
176
218
  get filterExpressions() {
219
+ if (this.#pendingFilter) {
220
+ return this.#pendingFilter.map((each) => {
221
+ const resolved = this.#resolveFilter(each);
222
+ return typeof resolved === 'string' ? each : resolved;
223
+ });
224
+ }
177
225
  const expressions = [];
178
226
  for (const each of this.stateController.filtering.state.values) {
179
227
  expressions.push(...each.all);
180
228
  }
181
229
  return expressions;
182
230
  }
231
+ #resolveFilter(each) {
232
+ const column = each ? this.getColumn(each.key) : undefined;
233
+ if (!column)
234
+ return `filter: unknown column "${String(each?.key)}"; dropped`;
235
+ const defaults = this.stateController.filtering.getDefaultExpression(column);
236
+ const named = each.condition ?? defaults.condition;
237
+ const condition = typeof named === 'string' ? resolveFilterOperand(column, named) : named;
238
+ if (!condition)
239
+ return `filter: unknown operand "${named}" for "${String(each.key)}"; dropped`;
240
+ return {
241
+ ...defaults,
242
+ ...each,
243
+ condition,
244
+ criteria: each.criteria ?? 'and',
245
+ };
246
+ }
183
247
  get rows() {
184
248
  return Array.from(this._rows);
185
249
  }
@@ -197,7 +261,7 @@ export class ApexGrid extends EventEmitterBase {
197
261
  }
198
262
  get pageItems() {
199
263
  const rowPin = this.stateController.rowPin;
200
- const base = rowPin?.enabled && rowPin.hasPinnedRows
264
+ const base = rowPin?.hasPinnedRows
201
265
  ? this.dataState.filter((row) => !rowPin.isPinned(row))
202
266
  : this.dataState;
203
267
  if (!this.stateController.pagination.enabled)
@@ -214,7 +278,33 @@ export class ApexGrid extends EventEmitterBase {
214
278
  return this.stateController.pagination.page;
215
279
  }
216
280
  set page(value) {
217
- this.stateController.pagination.gotoPage(value);
281
+ if (this.#viewStale) {
282
+ this.stateController.pagination.page = Number.isFinite(value)
283
+ ? Math.max(0, Math.trunc(value))
284
+ : 0;
285
+ this.requestUpdate(PIPELINE);
286
+ }
287
+ else {
288
+ this.stateController.pagination.gotoPage(value);
289
+ }
290
+ }
291
+ #viewChangePending;
292
+ #shapingPending;
293
+ get #viewStale() {
294
+ return !this.hasUpdated || this.#viewChangePending || this.#shapingPending;
295
+ }
296
+ #markViewChange() {
297
+ this.#viewChangePending = true;
298
+ this.requestUpdate(PIPELINE);
299
+ }
300
+ requestUpdate(name, oldValue, options, useNewValue, newValue) {
301
+ const fields = name === undefined ? undefined : VIEW_SHAPING_PROPERTIES.get(name);
302
+ if (this.hasUpdated &&
303
+ fields !== undefined &&
304
+ viewShapeChanged(fields, oldValue, useNewValue ? newValue : this[name])) {
305
+ this.#viewChangePending = true;
306
+ }
307
+ super.requestUpdate(name, oldValue, options, useNewValue, newValue);
218
308
  }
219
309
  get pageSize() {
220
310
  return this.stateController.pagination.pageSize;
@@ -228,21 +318,31 @@ export class ApexGrid extends EventEmitterBase {
228
318
  watchColumns(_, newConfig = []) {
229
319
  this.columns = newConfig.map((config) => ({ ...DEFAULT_COLUMN_CONFIG, ...config }));
230
320
  }
321
+ historyRecords() {
322
+ return this.data;
323
+ }
231
324
  dataChanged() {
232
325
  let next = [...this.data];
233
326
  autoGenerateColumns(this);
327
+ this.stateController?.history?.reconcile(() => this.historyRecords());
234
328
  this.stateController?.tree?.resetForDataChange();
329
+ const carry = (rows) => this.#carryRows(rows);
330
+ this.stateController?.rowReorder.resetForDataChange(carry);
331
+ this.stateController?.rowPin.resetForDataChange(carry);
235
332
  if (this.stateController?.tree?.enabled) {
236
333
  next = this.stateController.tree.process(next);
237
334
  }
238
335
  this.dataState = this.stateController ? this.stateController.applyModuleTransforms(next) : next;
239
- if (this.hasUpdated) {
240
- this.pipeline();
241
- }
336
+ if (this.hasUpdated)
337
+ this.requestUpdate(PIPELINE);
242
338
  }
243
- async pipeline() {
244
- this.dataState = await this.dataController.apply([...this.data], this.stateController);
245
- this.stateController.pagination.reclamp();
339
+ #carryRows(rows) {
340
+ const rowId = this.rowId;
341
+ const records = [...this.historyRecords()];
342
+ if (rowId)
343
+ return resolveRowRefs(serializeRowRefs(rows, [], rowId), records, rowId);
344
+ const present = new Set(records);
345
+ return rows.filter((row) => present.has(row));
246
346
  }
247
347
  paginationChanged() {
248
348
  const ctrl = this.stateController?.pagination;
@@ -255,16 +355,103 @@ export class ApexGrid extends EventEmitterBase {
255
355
  if (typeof next.page === 'number') {
256
356
  ctrl.page = next.page;
257
357
  }
258
- if (this.hasUpdated) {
259
- this.pipeline();
260
- }
358
+ if (this.hasUpdated)
359
+ this.requestUpdate(PIPELINE);
261
360
  }
262
361
  quickFilterChanged() {
263
- if (this.hasUpdated) {
264
- this.pipeline();
362
+ if (this.hasUpdated || this.quickFilter)
363
+ this.requestUpdate(PIPELINE);
364
+ }
365
+ treeChanged(previous) {
366
+ if (!viewShapeChanged(VIEW_SHAPING_PROPERTIES.get('tree'), previous, this.tree))
367
+ return;
368
+ this.stateController.tree.resetForDataChange();
369
+ this.requestUpdate(PIPELINE);
370
+ }
371
+ #pipelineRun;
372
+ #pipelineSettled;
373
+ #settlePipeline;
374
+ pipeline() {
375
+ if (!this.#settlePipeline) {
376
+ this.#pipelineSettled = new Promise((settle) => {
377
+ this.#settlePipeline = settle;
378
+ });
379
+ }
380
+ return this.#runPipeline(++this.#pipelineRun);
381
+ }
382
+ async #runPipeline(run) {
383
+ try {
384
+ this.#flushPendingExpressions();
385
+ if (this.#viewChangePending || !this.hasUpdated)
386
+ this.#shapingPending = true;
387
+ this.#viewChangePending = false;
388
+ const next = await this.dataController.apply([...this.data], this.stateController);
389
+ if (run !== this.#pipelineRun)
390
+ return;
391
+ this.dataState = next;
392
+ this.stateController.dataVersion++;
393
+ this.stateController.pagination.reclamp();
394
+ }
395
+ finally {
396
+ if (run === this.#pipelineRun) {
397
+ this.#settlePipeline?.();
398
+ this.#settlePipeline = null;
399
+ if (this.#shapingPending) {
400
+ this.#shapingPending = false;
401
+ this.#baselineStateIfUnset();
402
+ }
403
+ }
265
404
  }
266
405
  }
406
+ async pipelineSettled() {
407
+ await this.updateComplete;
408
+ let settled;
409
+ do {
410
+ settled = this.#pipelineSettled;
411
+ await settled;
412
+ await this.updateComplete;
413
+ } while (settled !== this.#pipelineSettled);
414
+ }
415
+ #flushPendingExpressions() {
416
+ const sort = this.#pendingSort;
417
+ const filter = this.#pendingFilter;
418
+ this.#pendingSort = undefined;
419
+ this.#pendingFilter = undefined;
420
+ if (sort)
421
+ this.#replaceSort(sort, warnDropped);
422
+ if (filter)
423
+ this.#replaceFilter(filter);
424
+ }
425
+ #replaceSort(entries, warn) {
426
+ this.stateController.sorting.reset();
427
+ const valid = [];
428
+ for (const entry of entries) {
429
+ const { direction, ...rest } = (entry ?? {});
430
+ if (typeof entry !== 'object' || entry === null || !this.getColumn(rest.key)) {
431
+ warn(`sort: unknown column "${String(rest.key)}"; dropped`);
432
+ }
433
+ else if (direction === undefined) {
434
+ valid.push(rest);
435
+ }
436
+ else if (SORT_DIRECTIONS.includes(direction)) {
437
+ valid.push(entry);
438
+ }
439
+ else {
440
+ warn(`sort: invalid direction "${String(direction)}"; dropped`);
441
+ }
442
+ }
443
+ if (valid.length)
444
+ this.sort(valid);
445
+ this.requestUpdate(PIPELINE);
446
+ }
447
+ #replaceFilter(entries) {
448
+ this.#markViewChange();
449
+ this.stateController.filtering.reset();
450
+ if (entries.length)
451
+ this.filter([...entries]);
452
+ }
267
453
  updated() {
454
+ this.#viewChangePending = false;
268
455
  this.setAttribute('role', this.stateController.tree.enabled ? 'treegrid' : 'grid');
269
456
  const pinned = this.stateController.rowPin.pinnedRows;
270
457
  this.setAttribute('aria-rowcount', String(this.headerRowCount + pinned.top.length + this.pageItems.length + pinned.bottom.length));
@@ -279,9 +466,7 @@ export class ApexGrid extends EventEmitterBase {
279
466
  else {
280
467
  this.removeAttribute('aria-multiselectable');
281
468
  }
282
- if (this.scrollContainer) {
283
- this.scrollContainer.tabIndex = this.stateController.active === SENTINEL_NODE ? 0 : -1;
284
- }
469
+ this.#syncBodyTabStop();
285
470
  const currentLabel = this.getAttribute('aria-label');
286
471
  const authorLabeled = this.hasAttribute('aria-labelledby') ||
287
472
  (currentLabel !== null && currentLabel !== this.#autoAriaLabel);
@@ -297,21 +482,54 @@ export class ApexGrid extends EventEmitterBase {
297
482
  #stateChangedListeners;
298
483
  #lastStateJson;
299
484
  addEventListener(type, listener, options) {
300
- if (type === 'stateChanged') {
301
- this.#stateChangedListeners += 1;
302
- this.#lastStateJson = this.hasUpdated ? JSON.stringify(this.getState()) : null;
303
- }
485
+ if (type === 'stateChanged' && listener)
486
+ this.#trackStateChangedListener(listener, options);
304
487
  super.addEventListener(type, listener, options);
305
488
  }
489
+ #trackStateChangedListener(listener, options) {
490
+ const capture = typeof options === 'boolean' ? options : Boolean(options?.capture);
491
+ const captures = this.#stateChangedListeners.get(listener) ?? new Map();
492
+ if (!captures.has(capture) && !options?.signal?.aborted) {
493
+ if (!this.#stateChangedListeners.size) {
494
+ this.#lastStateJson = this.hasUpdated ? JSON.stringify(this.getState()) : null;
495
+ }
496
+ const token = {};
497
+ captures.set(capture, token);
498
+ this.#stateChangedListeners.set(listener, captures);
499
+ const forget = () => this.#forgetStateChangedListener(listener, capture, token);
500
+ if (typeof options === 'object') {
501
+ if (options.once) {
502
+ HTMLElement.prototype.addEventListener.call(this, 'stateChanged', forget, {
503
+ capture,
504
+ once: true,
505
+ });
506
+ }
507
+ options.signal?.addEventListener('abort', forget, { once: true });
508
+ }
509
+ }
510
+ }
511
+ #forgetStateChangedListener(listener, capture, token) {
512
+ const captures = this.#stateChangedListeners.get(listener);
513
+ if (!captures || (token && captures.get(capture) !== token))
514
+ return;
515
+ captures.delete(capture);
516
+ if (!captures.size)
517
+ this.#stateChangedListeners.delete(listener);
518
+ }
306
519
  removeEventListener(type, listener, options) {
307
- if (type === 'stateChanged') {
308
- this.#stateChangedListeners = Math.max(0, this.#stateChangedListeners - 1);
520
+ if (type === 'stateChanged' && listener) {
521
+ const capture = typeof options === 'boolean' ? options : Boolean(options?.capture);
522
+ this.#forgetStateChangedListener(listener, capture);
309
523
  }
310
524
  super.removeEventListener(type, listener, options);
311
525
  }
312
526
  #emitStateChangedIfNeeded() {
313
- if (this.#stateChangedListeners === 0)
527
+ if (!this.#stateChangedListeners.size)
314
528
  return;
529
+ if (this.#lastStateJson === null) {
530
+ this.#baselineStateIfUnset();
531
+ return;
532
+ }
315
533
  const snapshot = this.getState();
316
534
  const json = JSON.stringify(snapshot);
317
535
  if (json === this.#lastStateJson)
@@ -319,6 +537,15 @@ export class ApexGrid extends EventEmitterBase {
319
537
  this.#lastStateJson = json;
320
538
  this.emitEvent('stateChanged', { detail: { state: snapshot } });
321
539
  }
540
+ #baselineStateIfUnset() {
541
+ if (this.#lastStateJson === null && this.#stateChangedListeners.size && !this.#shapingPending) {
542
+ this.#lastStateJson = JSON.stringify(this.getState());
543
+ }
544
+ }
545
+ connectedCallback() {
546
+ super.connectedCallback();
547
+ adoptGridDocumentStyles(this);
548
+ }
322
549
  firstUpdated() {
323
550
  if (typeof getComputedStyle === 'function' && getComputedStyle(this).display !== 'grid') {
324
551
  console.warn('[apex-grid] Host `display` has been overridden. The grid requires ' +
@@ -328,26 +555,44 @@ export class ApexGrid extends EventEmitterBase {
328
555
  }
329
556
  }
330
557
  filter(config) {
331
- this.stateController.filtering.filter(asArray(config).map((each) => typeof each.condition === 'string'
332
- ?
333
- Object.assign(each, {
334
- condition: getFilterOperandsFor(this.getColumn(each.key))[each.condition],
335
- })
336
- : each));
558
+ if (this.#pendingFilter) {
559
+ const added = asArray(config);
560
+ this.#pendingFilter.push(...added);
561
+ if (added.length)
562
+ this.#markViewChange();
563
+ return;
564
+ }
565
+ const expressions = [];
566
+ for (const each of asArray(config)) {
567
+ const resolved = this.#resolveFilter(each);
568
+ if (typeof resolved === 'string')
569
+ warnDropped(resolved);
570
+ else
571
+ expressions.push(resolved);
572
+ }
573
+ if (!expressions.length)
574
+ return;
575
+ this.#markViewChange();
576
+ this.stateController.filtering.filter(expressions);
337
577
  }
338
578
  sort(expressions) {
579
+ if (this.#pendingSort) {
580
+ this.#pendingSort.push(...asArray(expressions));
581
+ this.requestUpdate(PIPELINE);
582
+ return;
583
+ }
339
584
  this.stateController.sorting.sort(expressions);
340
585
  }
341
586
  getState(options) {
342
587
  const rowId = options?.rowId ?? this.rowId;
343
- const pinned = this.stateController.rowPin.pinnedRows;
588
+ const pinned = this.stateController.rowPin.pins;
344
589
  const manualOrder = this.stateController.rowReorder.getManualOrder();
345
590
  return {
346
591
  version: 1,
347
592
  columns: serializeColumnLayout(this.columns),
348
593
  sort: serializeSort(this.sortExpressions),
349
594
  filter: serializeFilter(this.filterExpressions),
350
- quickFilter: this.quickFilter,
595
+ quickFilter: this.quickFilter ?? '',
351
596
  pagination: { page: this.page, pageSize: this.pageSize },
352
597
  selection: serializeRowRefs(this.selectedRows, this.data, rowId),
353
598
  expansion: serializeRowRefs(this.expandedRows, this.data, rowId),
@@ -367,7 +612,7 @@ export class ApexGrid extends EventEmitterBase {
367
612
  columns: this.columns.map((column) => columnSchema(column)),
368
613
  capabilities: {
369
614
  sort: {
370
- directions: ['ascending', 'descending'],
615
+ directions: SORT_DIRECTIONS.filter((direction) => direction !== 'none'),
371
616
  multi: Boolean(this.sortConfiguration?.multiple),
372
617
  },
373
618
  filter: { operandsByType: operandsByType(this.columns) },
@@ -384,6 +629,7 @@ export class ApexGrid extends EventEmitterBase {
384
629
  const strict = options?.strict ?? false;
385
630
  const applied = [];
386
631
  const warnings = [];
632
+ const viewSettled = !this.#viewStale;
387
633
  const warn = (message) => {
388
634
  if (strict)
389
635
  throw new Error(`[apex-grid] setState: ${message}`);
@@ -410,31 +656,23 @@ export class ApexGrid extends EventEmitterBase {
410
656
  }
411
657
  const columns = asArray(state.columns, 'columns');
412
658
  if (columns) {
659
+ const unkeyed = columns.filter((entry) => typeof entry?.key !== 'string').length;
660
+ if (unkeyed)
661
+ warn(`columns: ${unkeyed} entr${unkeyed === 1 ? 'y' : 'ies'} without a key; dropped`);
413
662
  this.columns = applyColumnLayout(this.columns, columns);
414
663
  applied.push('columns');
415
664
  }
416
665
  const sort = asArray(state.sort, 'sort');
417
666
  if (sort) {
418
- this.stateController.sorting.reset();
419
- const valid = [];
420
- for (const entry of sort) {
421
- if (this.getColumn(entry.key)) {
422
- valid.push(entry);
423
- }
424
- else {
425
- warn(`sort: unknown column "${entry.key}"; dropped`);
426
- }
427
- }
428
- if (valid.length)
429
- this.sort(valid);
667
+ this.#pendingSort = undefined;
668
+ this.#replaceSort(sort, warn);
430
669
  applied.push('sort');
431
670
  }
432
671
  const filter = asArray(state.filter, 'filter');
433
672
  if (filter) {
434
- this.stateController.filtering.reset();
673
+ this.#pendingFilter = undefined;
435
674
  const expressions = deserializeFilter(filter, (key) => this.getColumn(key), (snapshot, reason) => warn(`filter: "${snapshot.key}" dropped (${reason})`));
436
- if (expressions.length)
437
- this.filter(expressions);
675
+ this.#replaceFilter(expressions);
438
676
  applied.push('filter');
439
677
  }
440
678
  if (state.quickFilter !== undefined) {
@@ -449,6 +687,7 @@ export class ApexGrid extends EventEmitterBase {
449
687
  if (state.modules !== undefined) {
450
688
  if (state.modules && typeof state.modules === 'object') {
451
689
  this.stateController.restoreModuleState(state.modules);
690
+ this.#markViewChange();
452
691
  applied.push('modules');
453
692
  }
454
693
  else {
@@ -476,13 +715,22 @@ export class ApexGrid extends EventEmitterBase {
476
715
  if (state.treeExpanded !== undefined || state.treeExpandedKeys !== undefined) {
477
716
  const refs = asArray(state.treeExpanded, 'treeExpanded') ?? [];
478
717
  const keys = asArray(state.treeExpandedKeys, 'treeExpandedKeys') ?? [];
479
- this.stateController.tree.restoreExpansion(resolveCounted(refs, 'treeExpanded'), keys);
718
+ this.stateController.tree.restoreExpansion(resolveCounted(refs, 'treeExpanded'), keys.filter((key) => typeof key === 'string'));
719
+ this.#markViewChange();
480
720
  applied.push('tree');
481
721
  }
482
722
  const expansion = asArray(state.expansion, 'expansion');
483
723
  if (expansion) {
484
- this.expandedRows = resolveCounted(expansion, 'expansion');
485
- applied.push('expansion');
724
+ const ctrl = this.stateController.expansion;
725
+ const predicate = this.expansion?.isExpandable;
726
+ const rows = resolveCounted(expansion, 'expansion');
727
+ const expected = predicate ? rows.filter((row) => predicate(row)) : rows;
728
+ if (this.#replaceRows('expansion', ctrl.enabled, expected, warn, () => {
729
+ void ctrl.replaceExpansion(expected);
730
+ return ctrl.expandedRows();
731
+ })) {
732
+ applied.push('expansion');
733
+ }
486
734
  }
487
735
  if (state.rowPinning !== undefined) {
488
736
  const pinning = state.rowPinning;
@@ -496,8 +744,15 @@ export class ApexGrid extends EventEmitterBase {
496
744
  }
497
745
  const selection = asArray(state.selection, 'selection');
498
746
  if (selection) {
499
- this.selectedRows = resolveCounted(selection, 'selection');
500
- applied.push('selection');
747
+ const ctrl = this.stateController.selection;
748
+ const rows = resolveCounted(selection, 'selection');
749
+ const expected = ctrl.mode === 'single' ? rows.slice(0, 1) : rows;
750
+ if (this.#replaceRows('selection', ctrl.enabled, expected, warn, () => {
751
+ void ctrl.replaceSelection(expected);
752
+ return ctrl.selectedRows();
753
+ })) {
754
+ applied.push('selection');
755
+ }
501
756
  }
502
757
  if (state.pagination !== undefined) {
503
758
  const pagination = state.pagination;
@@ -514,10 +769,13 @@ export class ApexGrid extends EventEmitterBase {
514
769
  if (page !== undefined) {
515
770
  if (typeof page === 'number' && Number.isFinite(page)) {
516
771
  const requested = Math.trunc(page);
517
- this.page = requested;
518
- if (this.page !== requested) {
519
- warn(`pagination: page ${requested} out of range; clamped to ${this.page}`);
772
+ const reshaped = applied.some((slice) => VIEW_SHAPING_SLICES.has(slice));
773
+ const ctrl = this.stateController.pagination;
774
+ const next = viewSettled && !reshaped ? ctrl.clamp(requested) : Math.max(0, requested);
775
+ if (next !== requested) {
776
+ warn(`pagination: page ${requested} out of range; clamped to ${next}`);
520
777
  }
778
+ ctrl.page = next;
521
779
  }
522
780
  else {
523
781
  warn(`pagination: invalid page ${String(page)}; ignored`);
@@ -533,13 +791,37 @@ export class ApexGrid extends EventEmitterBase {
533
791
  const skipped = SET_STATE_SLICES.filter((slice) => !applied.includes(slice));
534
792
  return { applied, skipped, warnings };
535
793
  }
794
+ #replaceRows(label, enabled, expected, warn, replace) {
795
+ if (!enabled) {
796
+ if (expected.length)
797
+ warn(`${label}: the feature is disabled on this grid; skipped`);
798
+ return !expected.length;
799
+ }
800
+ const actual = new Set(replace());
801
+ const took = actual.size === new Set(expected).size && expected.every((row) => actual.has(row));
802
+ if (!took)
803
+ warn(`${label}: the change was cancelled by a listener; skipped`);
804
+ return took;
805
+ }
536
806
  clearSort(key) {
807
+ if (this.#pendingSort && key !== undefined) {
808
+ this.#pendingSort = this.#pendingSort.filter((each) => each.key !== key);
809
+ }
810
+ else {
811
+ this.#pendingSort = undefined;
812
+ }
537
813
  this.stateController.sorting.reset(key);
538
814
  this.requestUpdate(PIPELINE);
539
815
  }
540
816
  clearFilter(key) {
817
+ if (this.#pendingFilter && key !== undefined) {
818
+ this.#pendingFilter = this.#pendingFilter.filter((each) => each.key !== key);
819
+ }
820
+ else {
821
+ this.#pendingFilter = undefined;
822
+ }
823
+ this.#markViewChange();
541
824
  this.stateController.filtering.reset(key);
542
- this.requestUpdate(PIPELINE);
543
825
  }
544
826
  gotoPage(page) {
545
827
  return this.stateController.pagination.gotoPage(page);
@@ -563,15 +845,21 @@ export class ApexGrid extends EventEmitterBase {
563
845
  const next = value ?? '';
564
846
  if (next === this.quickFilter)
565
847
  return false;
848
+ const previous = this.quickFilter;
849
+ const writes = this.#quickFilterWrites;
566
850
  const proceed = this.emitEvent('quickFilterChanging', {
567
- detail: { value: this.quickFilter, nextValue: next },
851
+ detail: { value: previous, nextValue: next },
568
852
  cancelable: true,
569
853
  });
570
854
  if (!proceed)
571
855
  return false;
572
- this.quickFilter = next;
573
- this.stateController.pagination.page = 0;
574
- await this.updateComplete;
856
+ if (this.#quickFilterWrites === writes)
857
+ this.quickFilter = next;
858
+ if (this.quickFilter === previous)
859
+ return false;
860
+ const pageReset = this.stateController.pagination.resetPage();
861
+ await this.pipelineSettled();
862
+ await pageReset;
575
863
  this.emitEvent('quickFilterChanged', { detail: { value: this.quickFilter } });
576
864
  return true;
577
865
  }
@@ -637,6 +925,39 @@ export class ApexGrid extends EventEmitterBase {
637
925
  const editing = this.stateController.editing;
638
926
  return editing.mode === 'row' ? editing.commitRow() : editing.commitCell();
639
927
  }
928
+ applyEdits(edits) {
929
+ const tally = { applied: 0, unchanged: 0, invalid: 0, cancelled: 0, skipped: 0 };
930
+ if (!edits.length)
931
+ return tally;
932
+ const editing = this.stateController.editing;
933
+ const items = this.pageItems;
934
+ const indexOf = viewIndexer(items, edits.length);
935
+ const version = editing.validationVersion;
936
+ this.stateController.history.beginBatch();
937
+ try {
938
+ for (const { row, rowIndex, column, value } of edits) {
939
+ const record = row ?? items[rowIndex];
940
+ const config = this.getColumn(column);
941
+ if (!record ||
942
+ !config ||
943
+ !editing.isEditable(config) ||
944
+ !this.stateController.isDataRow(record)) {
945
+ tally.skipped += 1;
946
+ continue;
947
+ }
948
+ const index = row ? indexOf(row) : rowIndex;
949
+ tally[editing.applyCellEdit(index, column, record, value)] += 1;
950
+ }
951
+ }
952
+ finally {
953
+ this.stateController.history.endBatch();
954
+ }
955
+ if (tally.applied > 0)
956
+ this.requestUpdate(PIPELINE);
957
+ else if (editing.validationVersion !== version)
958
+ this.requestUpdate();
959
+ return tally;
960
+ }
640
961
  cancelEdit() {
641
962
  const editing = this.stateController.editing;
642
963
  if (editing.mode === 'row') {
@@ -706,22 +1027,20 @@ export class ApexGrid extends EventEmitterBase {
706
1027
  const target = this.columns[toIndex];
707
1028
  if ((source.pinned ?? null) !== (target.pinned ?? null))
708
1029
  return false;
709
- const proceed = this.emitEvent('columnMoving', {
710
- detail: { key: fromKey, fromIndex, toKey, position },
711
- cancelable: true,
712
- });
713
- if (!proceed)
714
- return false;
715
1030
  const next = this.columns.slice();
716
1031
  const [moved] = next.splice(fromIndex, 1);
717
1032
  let insertIndex = next.findIndex((column) => column.key === toKey);
718
- if (insertIndex < 0)
719
- return false;
720
1033
  if (position === 'after')
721
1034
  insertIndex += 1;
722
1035
  next.splice(insertIndex, 0, moved);
723
1036
  if (next.every((column, index) => column === this.columns[index]))
724
1037
  return false;
1038
+ const proceed = this.emitEvent('columnMoving', {
1039
+ detail: { key: fromKey, fromIndex, toKey, position },
1040
+ cancelable: true,
1041
+ });
1042
+ if (!proceed)
1043
+ return false;
725
1044
  this.columns = next;
726
1045
  await this.updateComplete;
727
1046
  const resolvedIndex = this.columns.findIndex((column) => column.key === fromKey);
@@ -730,8 +1049,42 @@ export class ApexGrid extends EventEmitterBase {
730
1049
  });
731
1050
  return true;
732
1051
  }
1052
+ ownsEvent(event) {
1053
+ for (const node of event.composedPath()) {
1054
+ if (node === this || node === this.scrollContainer)
1055
+ return true;
1056
+ if (node instanceof ApexGrid || node instanceof ApexVirtualizer)
1057
+ return false;
1058
+ }
1059
+ return false;
1060
+ }
1061
+ #syncBodyTabStop() {
1062
+ const body = this.scrollContainer;
1063
+ if (!body)
1064
+ return;
1065
+ const active = this.stateController.active;
1066
+ body.tabIndex = active !== SENTINEL_NODE && body.isRendered(active.row) ? -1 : 0;
1067
+ }
1068
+ bodyRangeChanged(event) {
1069
+ const { first, last } = event;
1070
+ this.#syncBodyTabStop();
1071
+ const focused = this.renderRoot instanceof ShadowRoot ? this.renderRoot.activeElement : null;
1072
+ if (focused instanceof ApexGridRow &&
1073
+ focused.parentElement === this.scrollContainer &&
1074
+ (focused.index < first || focused.index > last)) {
1075
+ this.scrollContainer.focus({ preventScroll: true });
1076
+ }
1077
+ }
1078
+ #ownCell(event) {
1079
+ if (!this.ownsEvent(event))
1080
+ return undefined;
1081
+ return event.composedPath().find((el) => el instanceof ApexGridCell);
1082
+ }
733
1083
  bodyClickHandler(event) {
734
- const target = event.composedPath().find((el) => el instanceof ApexGridCell);
1084
+ if (!this.ownsEvent(event))
1085
+ return;
1086
+ const path = event.composedPath();
1087
+ const target = path.find((el) => el instanceof ApexGridCell);
735
1088
  if (target) {
736
1089
  this.stateController.active = {
737
1090
  column: target.column.key,
@@ -740,11 +1093,22 @@ export class ApexGrid extends EventEmitterBase {
740
1093
  if (!target.editing)
741
1094
  target.focus({ preventScroll: true });
742
1095
  }
1096
+ else {
1097
+ const row = path.find((el) => el instanceof ApexGridRow);
1098
+ const cell = row?.presentedCell;
1099
+ if (row && cell && path.includes(cell)) {
1100
+ const { column } = this.stateController.active;
1101
+ this.stateController.active = { column, row: row.index };
1102
+ }
1103
+ }
743
1104
  }
744
1105
  bodyKeydownHandler(event) {
1106
+ if (!this.ownsEvent(event))
1107
+ return;
745
1108
  const origin = event.composedPath()[0];
746
1109
  const fromBody = this.scrollContainer.isSameNode(event.target);
747
- const fromCell = origin instanceof ApexGridCell && !origin.editing;
1110
+ const fromCell = (origin instanceof ApexGridCell && !origin.editing) ||
1111
+ (event.target instanceof ApexGridRow && event.target.presentedCell === origin);
748
1112
  if (fromBody || fromCell) {
749
1113
  this.stateController.navigation.navigate(event);
750
1114
  }
@@ -759,7 +1123,7 @@ export class ApexGrid extends EventEmitterBase {
759
1123
  return;
760
1124
  if (event.type === 'pointermove' && event.buttons === 0)
761
1125
  return;
762
- const target = event.composedPath().find((el) => el instanceof ApexGridCell);
1126
+ const target = this.#ownCell(event);
763
1127
  if (!target?.row)
764
1128
  return;
765
1129
  const kind = event.type === 'pointerdown' ? 'down' : event.type === 'pointerup' ? 'up' : 'over';
@@ -804,6 +1168,7 @@ export class ApexGrid extends EventEmitterBase {
804
1168
  @click=${this.bodyClickHandler}
805
1169
  @keydown=${this.bodyKeydownHandler}
806
1170
  @focusin=${this.bodyFocusHandler}
1171
+ @rangeChanged=${this.bodyRangeChanged}
807
1172
  @pointerdown=${this.bodyPointerHandler}
808
1173
  @pointermove=${this.bodyPointerHandler}
809
1174
  @pointerup=${this.bodyPointerHandler}
@@ -815,8 +1180,6 @@ export class ApexGrid extends EventEmitterBase {
815
1180
  }
816
1181
  renderPinnedTop() {
817
1182
  const rowPin = this.stateController.rowPin;
818
- if (!rowPin.enabled)
819
- return nothing;
820
1183
  const rows = rowPin.pinnedRows.top;
821
1184
  if (rows.length === 0)
822
1185
  return nothing;
@@ -826,8 +1189,6 @@ export class ApexGrid extends EventEmitterBase {
826
1189
  }
827
1190
  renderPinnedBottom() {
828
1191
  const rowPin = this.stateController.rowPin;
829
- if (!rowPin.enabled)
830
- return nothing;
831
1192
  const rows = rowPin.pinnedRows.bottom;
832
1193
  if (rows.length === 0)
833
1194
  return nothing;
@@ -948,7 +1309,7 @@ __decorate([
948
1309
  ], ApexGrid.prototype, "columns", void 0);
949
1310
  __decorate([
950
1311
  property({ attribute: false })
951
- ], ApexGrid.prototype, "data", void 0);
1312
+ ], ApexGrid.prototype, "data", null);
952
1313
  __decorate([
953
1314
  property({ attribute: false })
954
1315
  ], ApexGrid.prototype, "rowId", void 0);
@@ -966,7 +1327,7 @@ __decorate([
966
1327
  ], ApexGrid.prototype, "pagination", void 0);
967
1328
  __decorate([
968
1329
  property({ type: String, attribute: 'quick-filter' })
969
- ], ApexGrid.prototype, "quickFilter", void 0);
1330
+ ], ApexGrid.prototype, "quickFilter", null);
970
1331
  __decorate([
971
1332
  property({ type: Boolean, attribute: 'show-quick-filter' })
972
1333
  ], ApexGrid.prototype, "showQuickFilter", void 0);
@@ -1027,15 +1388,18 @@ __decorate([
1027
1388
  __decorate([
1028
1389
  watch('data')
1029
1390
  ], ApexGrid.prototype, "dataChanged", null);
1030
- __decorate([
1031
- watch(PIPELINE)
1032
- ], ApexGrid.prototype, "pipeline", null);
1033
1391
  __decorate([
1034
1392
  watch('pagination')
1035
1393
  ], ApexGrid.prototype, "paginationChanged", null);
1036
1394
  __decorate([
1037
1395
  watch('quickFilter')
1038
1396
  ], ApexGrid.prototype, "quickFilterChanged", null);
1397
+ __decorate([
1398
+ watch('tree', { waitUntilFirstUpdate: true })
1399
+ ], ApexGrid.prototype, "treeChanged", null);
1400
+ __decorate([
1401
+ watch(PIPELINE)
1402
+ ], ApexGrid.prototype, "pipeline", null);
1039
1403
  __decorate([
1040
1404
  eventOptions({ capture: true })
1041
1405
  ], ApexGrid.prototype, "bodyClickHandler", null);