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
@@ -5,9 +5,13 @@ export const DEFAULT_EDITING_CONFIG = Object.freeze({
5
5
  mode: 'cell',
6
6
  trigger: 'doubleClick',
7
7
  });
8
+ const NO_DRAFT = Symbol('no-draft');
8
9
  export class EditingController {
10
+ #effects;
9
11
  #invalid;
10
12
  #validationVersion;
13
+ #draft;
14
+ #draftRejected;
11
15
  get validationVersion() {
12
16
  return this.#validationVersion;
13
17
  }
@@ -17,11 +21,46 @@ export class EditingController {
17
21
  this.activeCell = null;
18
22
  this.activeRow = null;
19
23
  this.pending = new Map();
20
- this.#invalid = new Map();
24
+ this.#effects = new Map();
25
+ this.#invalid = new WeakMap();
21
26
  this.#validationVersion = 0;
27
+ this.#draft = NO_DRAFT;
28
+ this.#draftRejected = false;
22
29
  this.host.addController(this);
23
30
  }
24
31
  hostConnected() { }
32
+ hostUpdated() {
33
+ const data = this.activeRow?.data ?? this.activeCell?.data;
34
+ if (!data)
35
+ return;
36
+ const rowIndex = this.#indexOf(data);
37
+ if (rowIndex < 0 && !this.#inView(data)) {
38
+ if (this.activeRow)
39
+ this.cancelRow();
40
+ else
41
+ this.cancelCell();
42
+ return;
43
+ }
44
+ if (this.activeRow)
45
+ this.activeRow.rowIndex = rowIndex;
46
+ if (this.activeCell)
47
+ this.activeCell.rowIndex = rowIndex;
48
+ }
49
+ #indexOf(data) {
50
+ return this.host.pageItems.indexOf(data);
51
+ }
52
+ #inView(data) {
53
+ return this.host.dataView.includes(data) || this.#isPinned(data);
54
+ }
55
+ #isPinned(data) {
56
+ if (!this.host.rowPinning?.enabled)
57
+ return false;
58
+ const { top, bottom } = this.host.pinnedRows;
59
+ return top.includes(data) || bottom.includes(data);
60
+ }
61
+ #isHidden(data) {
62
+ return this.#indexOf(data) < 0 && !this.#isPinned(data);
63
+ }
25
64
  get enabled() {
26
65
  return Boolean(this.host.editing?.enabled);
27
66
  }
@@ -34,36 +73,32 @@ export class EditingController {
34
73
  isEditable(column) {
35
74
  return Boolean(this.enabled && column.editable && !column.hidden);
36
75
  }
37
- isCellEditing(rowIndex, columnKey) {
38
- return this.activeCell?.rowIndex === rowIndex && this.activeCell?.columnKey === columnKey;
39
- }
40
- isRowEditing(rowIndex) {
41
- return this.activeRow?.rowIndex === rowIndex;
76
+ isCellEditing(data, columnKey) {
77
+ const active = this.activeCell;
78
+ return active !== null && active.data === data && active.columnKey === columnKey;
42
79
  }
43
80
  async editCell(rowIndex, columnKey) {
44
- if (!this.enabled)
81
+ const data = this.host.pageItems[rowIndex];
82
+ return data ? this.editRecord(data, columnKey) : false;
83
+ }
84
+ async editRecord(data, columnKey) {
85
+ if (!this.enabled || !this.#isRecord(data))
45
86
  return false;
46
87
  const column = this.host.getColumn(columnKey);
47
88
  if (!column || !this.isEditable(column))
48
89
  return false;
49
- const data = this.host.pageItems[rowIndex];
50
- if (!data)
90
+ if (this.mode === 'row' && !(await this.#openRow(data)))
51
91
  return false;
52
- if (this.mode === 'row') {
53
- if (!this.activeRow) {
54
- await this.editRow(rowIndex);
55
- }
56
- else if (this.activeRow.rowIndex !== rowIndex) {
57
- const committed = await this.commitRow();
58
- if (!committed)
92
+ if (this.activeCell && !this.isCellEditing(data, columnKey)) {
93
+ if (this.#draftRejected || !(await this.commitCell())) {
94
+ if (!this.#isHidden(this.activeCell.data))
59
95
  return false;
60
- await this.editRow(rowIndex);
96
+ this.cancelCell();
61
97
  }
62
98
  }
63
- else if (this.activeCell && !this.isCellEditing(rowIndex, columnKey)) {
64
- await this.commitCell();
65
- }
66
- this.activeCell = { rowIndex, columnKey, data };
99
+ if (!this.isCellEditing(data, columnKey))
100
+ this.#resetDraft();
101
+ this.activeCell = { rowIndex: this.#indexOf(data), columnKey, data };
67
102
  this.host.requestUpdate();
68
103
  return true;
69
104
  }
@@ -71,17 +106,25 @@ export class EditingController {
71
106
  if (!this.enabled || this.mode !== 'row')
72
107
  return false;
73
108
  const data = this.host.pageItems[rowIndex];
74
- if (!data)
75
- return false;
76
- if (this.activeRow && this.activeRow.rowIndex !== rowIndex) {
77
- const committed = await this.commitRow();
78
- if (!committed)
109
+ return data && this.#isRecord(data) ? this.#openRow(data) : false;
110
+ }
111
+ #isRecord(data) {
112
+ const state = this.host
113
+ .stateController;
114
+ return state?.isDataRow(data) ?? true;
115
+ }
116
+ async #openRow(data) {
117
+ if (this.activeRow?.data === data)
118
+ return true;
119
+ if (this.activeRow && !(await this.commitRow())) {
120
+ if (!this.#isHidden(this.activeRow.data))
79
121
  return false;
122
+ this.cancelRow();
80
123
  }
81
- if (this.activeRow?.rowIndex === rowIndex)
82
- return true;
124
+ const rowIndex = this.#indexOf(data);
83
125
  this.activeRow = { rowIndex, data };
84
126
  this.pending.clear();
127
+ this.#effects.clear();
85
128
  this.host.requestUpdate();
86
129
  this.host.emitEvent('rowEditStarted', { detail: { rowIndex } });
87
130
  return true;
@@ -103,12 +146,9 @@ export class EditingController {
103
146
  return 'invalid';
104
147
  }
105
148
  }
106
- const proceed = this.host.emitEvent('cellValueChanging', {
107
- detail: { key: columnKey, rowIndex, data, oldValue, newValue: value },
108
- cancelable: true,
109
- });
110
- if (!proceed)
149
+ if (options?.gate !== false && !this.#gate(rowIndex, columnKey, data, value)) {
111
150
  return 'cancelled';
151
+ }
112
152
  record[columnKey] = value;
113
153
  this.clearCellErrors(data, columnKey);
114
154
  this.history?.record({ record: data, key: columnKey, rowIndex, oldValue, newValue: value });
@@ -117,16 +157,53 @@ export class EditingController {
117
157
  });
118
158
  return 'applied';
119
159
  }
120
- validateCell(rowIndex, columnKey, data, value) {
160
+ #gate(rowIndex, columnKey, data, value) {
161
+ return this.host.emitEvent('cellValueChanging', {
162
+ detail: {
163
+ key: columnKey,
164
+ rowIndex,
165
+ data,
166
+ oldValue: data[columnKey],
167
+ newValue: value,
168
+ },
169
+ cancelable: true,
170
+ });
171
+ }
172
+ validateCell(rowIndex, columnKey, data, value, row = data) {
121
173
  const column = this.host.getColumn(columnKey);
122
174
  if (!column?.validators?.length)
123
175
  return [];
124
176
  return runValidators(column.validators, value, {
125
177
  column,
126
- data,
178
+ data: row,
127
179
  rowIndex,
128
180
  });
129
181
  }
182
+ stagedValues(data) {
183
+ return this.activeRow?.data === data ? this.pending : null;
184
+ }
185
+ stagedEffect(data, columnKey) {
186
+ return this.activeRow?.data === data ? this.#effects.get(columnKey) : undefined;
187
+ }
188
+ #run(effect) {
189
+ effect.apply();
190
+ this.history?.push(effect);
191
+ }
192
+ #stage(data, columnKey, value, effect) {
193
+ if (effect)
194
+ this.#effects.set(columnKey, effect);
195
+ else if (effect === undefined)
196
+ this.#effects.delete(columnKey);
197
+ if (Object.is(value, data[columnKey])) {
198
+ if (this.pending.delete(columnKey))
199
+ this.#validationVersion += 1;
200
+ this.clearCellErrors(data, columnKey);
201
+ }
202
+ else if (!this.pending.has(columnKey) || !Object.is(this.pending.get(columnKey), value)) {
203
+ this.pending.set(columnKey, value);
204
+ this.#validationVersion += 1;
205
+ }
206
+ }
130
207
  getCellErrors(data, columnKey) {
131
208
  return this.#invalid.get(data)?.get(columnKey) ?? null;
132
209
  }
@@ -154,22 +231,59 @@ export class EditingController {
154
231
  perColumn.set(columnKey, errors);
155
232
  this.#validationVersion += 1;
156
233
  }
157
- async commitCell(value) {
234
+ setDraft(value) {
235
+ if (!this.activeCell)
236
+ return;
237
+ this.#draft = value;
238
+ this.#draftRejected = false;
239
+ }
240
+ heldDraft(data, columnKey) {
241
+ return this.#draft === NO_DRAFT || !this.isCellEditing(data, columnKey)
242
+ ? null
243
+ : { value: this.#draft };
244
+ }
245
+ #resetDraft() {
246
+ this.#draft = NO_DRAFT;
247
+ this.#draftRejected = false;
248
+ }
249
+ #endCellEdit() {
250
+ this.activeCell = null;
251
+ this.#resetDraft();
252
+ }
253
+ async commitCell(...args) {
158
254
  const active = this.activeCell;
159
255
  if (!active)
160
256
  return false;
161
257
  const { rowIndex, columnKey, data } = active;
162
- const oldValue = data[columnKey];
163
- const candidate = value === undefined ? oldValue : value;
258
+ const staged = this.stagedValues(data);
259
+ const current = staged?.has(columnKey)
260
+ ? staged.get(columnKey)
261
+ : data[columnKey];
262
+ const draft = this.#draft;
263
+ const candidate = args.length > 0 ? args[0] : draft === NO_DRAFT ? current : draft;
264
+ const effect = args[1];
164
265
  if (this.mode === 'cell') {
165
- const result = this.applyCellEdit(rowIndex, columnKey, data, candidate);
266
+ let result;
267
+ if (effect)
268
+ this.history?.beginBatch();
269
+ try {
270
+ result = this.applyCellEdit(rowIndex, columnKey, data, candidate);
271
+ if (effect && (result === 'applied' || result === 'unchanged'))
272
+ this.#run(effect);
273
+ }
274
+ finally {
275
+ if (effect)
276
+ this.history?.endBatch();
277
+ }
166
278
  if (result === 'cancelled')
167
279
  return false;
168
280
  if (result === 'invalid') {
281
+ if (args.length === 0)
282
+ this.#draftRejected = true;
169
283
  this.host.requestUpdate();
170
284
  return false;
171
285
  }
172
- this.activeCell = null;
286
+ this.#endCellEdit();
173
287
  if (result === 'applied') {
174
288
  this.host.requestUpdate(PIPELINE);
175
289
  await this.host.updateComplete;
@@ -179,23 +293,25 @@ export class EditingController {
179
293
  }
180
294
  return true;
181
295
  }
182
- if (!Object.is(candidate, oldValue)) {
183
- this.pending.set(columnKey, candidate);
184
- }
185
- this.activeCell = null;
296
+ this.#stage(data, columnKey, candidate, args.length > 0 ? effect : null);
297
+ this.#endCellEdit();
186
298
  this.host.requestUpdate();
187
299
  return true;
188
300
  }
189
- async commitImmediate(rowIndex, columnKey, value) {
190
- if (!this.enabled)
301
+ async commitImmediate(data, columnKey, value) {
302
+ if (!this.enabled || !this.#isRecord(data))
191
303
  return false;
192
304
  const column = this.host.getColumn(columnKey);
193
305
  if (!column || !this.isEditable(column))
194
306
  return false;
195
- const data = this.host.pageItems[rowIndex];
196
- if (!data)
197
- return false;
198
- const result = this.applyCellEdit(rowIndex, columnKey, data, value);
307
+ if (this.mode === 'row') {
308
+ if (!(await this.#openRow(data)))
309
+ return false;
310
+ this.#stage(data, columnKey, value);
311
+ this.host.requestUpdate();
312
+ return true;
313
+ }
314
+ const result = this.applyCellEdit(this.#indexOf(data), columnKey, data, value);
199
315
  if (result === 'cancelled')
200
316
  return false;
201
317
  if (result === 'invalid') {
@@ -212,7 +328,7 @@ export class EditingController {
212
328
  if (!this.activeCell)
213
329
  return;
214
330
  const { data, columnKey } = this.activeCell;
215
- this.activeCell = null;
331
+ this.#endCellEdit();
216
332
  this.clearCellErrors(data, columnKey);
217
333
  this.host.requestUpdate();
218
334
  }
@@ -220,63 +336,99 @@ export class EditingController {
220
336
  const active = this.activeRow;
221
337
  if (!active)
222
338
  return false;
223
- const { rowIndex, data } = active;
224
- if (this.activeCell?.rowIndex === rowIndex) {
339
+ const { data } = active;
340
+ if (this.activeCell?.data === data) {
225
341
  await this.commitCell();
226
342
  }
227
- let hasInvalid = false;
228
- for (const [columnKey, candidate] of this.pending) {
229
- const errors = this.validateCell(rowIndex, columnKey, data, candidate);
230
- if (errors.length > 0) {
231
- this.#setCellErrors(data, columnKey, errors);
232
- this.host.emitEvent('cellValidationFailed', {
233
- detail: { key: columnKey, rowIndex, data, value: candidate, errors },
234
- });
235
- hasInvalid = true;
343
+ const rowIndex = active.rowIndex;
344
+ const record = data;
345
+ for (const [columnKey, effect] of this.#effects) {
346
+ if (!effect.stagedValue)
347
+ continue;
348
+ const value = effect.stagedValue(this.pending);
349
+ if (this.pending.has(columnKey) || !Object.is(value, record[columnKey])) {
350
+ this.pending.set(columnKey, value);
236
351
  }
237
352
  }
238
- if (hasInvalid) {
353
+ const changes = [...this.pending].filter(([columnKey, candidate]) => !Object.is(candidate, record[columnKey]));
354
+ if (this.pending.size > 0 && !this.#validateRow(rowIndex, data)) {
239
355
  this.host.requestUpdate();
240
356
  return false;
241
357
  }
358
+ for (const [columnKey, candidate] of changes) {
359
+ if (!this.#gate(rowIndex, columnKey, data, candidate))
360
+ return false;
361
+ }
242
362
  this.history?.beginBatch();
243
363
  try {
244
- for (const [columnKey, candidate] of this.pending) {
245
- if (this.applyCellEdit(rowIndex, columnKey, data, candidate, { validate: false }) ===
246
- 'cancelled') {
247
- return false;
248
- }
364
+ for (const [columnKey, candidate] of changes) {
365
+ this.applyCellEdit(rowIndex, columnKey, data, candidate, { validate: false, gate: false });
249
366
  }
367
+ for (const effect of this.#effects.values())
368
+ this.#run(effect);
250
369
  }
251
370
  finally {
252
371
  this.history?.endBatch();
253
372
  }
373
+ if (this.pending.size > 0)
374
+ this.#validationVersion += 1;
254
375
  this.pending.clear();
376
+ this.#effects.clear();
255
377
  this.activeRow = null;
256
378
  this.host.requestUpdate(PIPELINE);
257
379
  await this.host.updateComplete;
258
380
  this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: true } });
259
381
  return true;
260
382
  }
383
+ #validateRow(rowIndex, data) {
384
+ const row = Object.assign(Object.create(Object.getPrototypeOf(data)), data);
385
+ for (const [columnKey, value] of this.pending) {
386
+ row[columnKey] = value;
387
+ }
388
+ let valid = true;
389
+ for (const column of this.host.columns) {
390
+ if (!this.isEditable(column) || !column.validators?.length)
391
+ continue;
392
+ const columnKey = column.key;
393
+ const value = row[columnKey];
394
+ const errors = this.#validateStaged(rowIndex, columnKey, data, value, row);
395
+ if (errors.length === 0) {
396
+ this.clearCellErrors(data, columnKey);
397
+ continue;
398
+ }
399
+ if (!this.pending.has(columnKey) &&
400
+ this.validateCell(rowIndex, columnKey, data, value).length > 0) {
401
+ continue;
402
+ }
403
+ valid = false;
404
+ this.#setCellErrors(data, columnKey, errors);
405
+ this.host.emitEvent('cellValidationFailed', {
406
+ detail: { key: columnKey, rowIndex, data, value, errors },
407
+ });
408
+ }
409
+ return valid;
410
+ }
411
+ #validateStaged(rowIndex, columnKey, data, value, row) {
412
+ try {
413
+ return this.validateCell(rowIndex, columnKey, data, value, row);
414
+ }
415
+ catch {
416
+ return this.validateCell(rowIndex, columnKey, data, value);
417
+ }
418
+ }
261
419
  cancelRow() {
262
420
  if (!this.activeRow)
263
421
  return;
264
422
  const { rowIndex, data } = this.activeRow;
265
- this.activeCell = null;
423
+ this.#endCellEdit();
266
424
  this.activeRow = null;
425
+ if (this.pending.size > 0)
426
+ this.#validationVersion += 1;
267
427
  this.pending.clear();
428
+ this.#effects.clear();
268
429
  this.clearRecordErrors(data);
269
430
  this.host.requestUpdate();
270
431
  this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: false } });
271
432
  }
272
- getValue(rowIndex, columnKey) {
273
- if (this.mode === 'row' &&
274
- this.activeRow?.rowIndex === rowIndex &&
275
- this.pending.has(columnKey)) {
276
- return this.pending.get(columnKey);
277
- }
278
- const data = this.host.pageItems[rowIndex];
279
- return data ? data[columnKey] : undefined;
280
- }
281
433
  }
282
434
  //# sourceMappingURL=editing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"editing.js","sourceRoot":"","sources":["../../src/controllers/editing.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { PIPELINE } from '../internal/constants.js';\nimport type {\n ColumnConfiguration,\n EditMode,\n EditTrigger,\n GridHost,\n Keys,\n Validator,\n ValidatorContext,\n} from '../internal/types.js';\nimport { runValidators } from '../internal/validators.js';\nimport type { HistoryController } from './history.js';\n\n/**\n * The default editing configuration used when the grid has none set.\n */\nexport const DEFAULT_EDITING_CONFIG = Object.freeze({\n enabled: false,\n mode: 'cell' as EditMode,\n trigger: 'doubleClick' as EditTrigger,\n});\n\ninterface ActiveCell<T> {\n rowIndex: number;\n columnKey: Keys<T>;\n /**\n * Live reference to the underlying record (preserved through the pipeline so\n * the source array can be mutated on commit).\n */\n data: T;\n}\n\n/**\n * Outcome of a single committed cell edit routed through\n * {@link EditingController.applyCellEdit}.\n *\n * - `'unchanged'` - the candidate equalled the current value; nothing happened.\n * - `'invalid'` - a column validator rejected the candidate; the value was not\n * written and `cellValidationFailed` fired.\n * - `'cancelled'` - a `cellValueChanging` listener called `preventDefault()`.\n * - `'applied'` - the value was written and `cellValueChanged` fired.\n */\nexport type CellEditResult = 'unchanged' | 'invalid' | 'cancelled' | 'applied';\n\n/**\n * Options for {@link EditingController.applyCellEdit}.\n */\nexport interface ApplyCellEditOptions {\n /**\n * Run the column's declarative validators before writing. Defaults to `true`.\n * Set to `false` for writes that must not be re-validated (e.g. reverting a\n * previously valid value during undo/redo).\n */\n validate?: boolean;\n}\n\n/**\n * Reactive controller backing inline cell + row editing.\n *\n * @remarks\n * Holds the currently active cell / row, pending values for row-mode edits,\n * and is responsible for emitting the cancellable `cellValueChanging` event\n * and follow-up `cellValueChanged` event. The actual data mutation writes\n * through to {@link ApexGrid.data} so changes survive pipeline runs and\n * propagate to consumers that own the source array.\n */\nexport class EditingController<T extends object> implements ReactiveController {\n /** The cell currently in edit mode, or `null`. */\n public activeCell: ActiveCell<T> | null = null;\n\n /** The row currently in edit mode (row mode only), or `null`. */\n public activeRow: { rowIndex: number; data: T } | null = null;\n\n /**\n * Pending values keyed by column for the currently editing row. Populated in\n * row mode as the user edits individual cells; flushed on\n * {@link commitRow}.\n */\n public pending: Map<Keys<T>, unknown> = new Map();\n\n /**\n * Per-record validation errors, keyed by the live data reference and then by\n * column. Keyed by reference (not row index) so an invalid marker tracks its\n * row across pipeline runs (sort / filter / paginate).\n */\n #invalid: Map<T, Map<Keys<T>, string[]>> = new Map();\n\n #validationVersion = 0;\n\n /**\n * Monotonic token bumped whenever the validation-error set changes. Forwarded\n * down to each cell as a reactive property so a validation-only change\n * re-renders the affected cells (toggling `aria-invalid` and the error node)\n * without re-running the data pipeline. Stays `0` until the first failure.\n */\n public get validationVersion(): number {\n return this.#validationVersion;\n }\n\n constructor(\n protected host: GridHost<T>,\n protected history?: HistoryController<T>\n ) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /**\n * Whether editing is enabled at the grid level.\n */\n public get enabled(): boolean {\n return Boolean(this.host.editing?.enabled);\n }\n\n /**\n * The resolved edit mode (`'cell'` if unset).\n */\n public get mode(): EditMode {\n return this.host.editing?.mode ?? DEFAULT_EDITING_CONFIG.mode;\n }\n\n /**\n * The resolved edit trigger (`'doubleClick'` if unset).\n */\n public get trigger(): EditTrigger {\n return this.host.editing?.trigger ?? DEFAULT_EDITING_CONFIG.trigger;\n }\n\n /**\n * Whether the given column can be edited.\n */\n public isEditable(column: ColumnConfiguration<T>): boolean {\n return Boolean(this.enabled && column.editable && !column.hidden);\n }\n\n /**\n * Whether the cell `(rowIndex, columnKey)` is currently in edit mode.\n */\n public isCellEditing(rowIndex: number, columnKey: Keys<T>): boolean {\n return this.activeCell?.rowIndex === rowIndex && this.activeCell?.columnKey === columnKey;\n }\n\n /**\n * Whether the given row is currently in edit mode (row mode).\n */\n public isRowEditing(rowIndex: number): boolean {\n return this.activeRow?.rowIndex === rowIndex;\n }\n\n /**\n * Begins editing the cell at `(rowIndex, columnKey)`. Auto-commits any cell\n * already in edit mode (in cell mode); in row mode it switches the active\n * cell within the same row and rejects cross-row moves.\n *\n * @returns `true` if edit started, `false` if rejected (column not editable\n * or row-mode constraint violated).\n */\n public async editCell(rowIndex: number, columnKey: Keys<T>): Promise<boolean> {\n if (!this.enabled) return false;\n const column = this.host.getColumn(columnKey);\n if (!column || !this.isEditable(column)) return false;\n const data = this.host.pageItems[rowIndex] as T | undefined;\n if (!data) return false;\n\n if (this.mode === 'row') {\n if (!this.activeRow) {\n await this.editRow(rowIndex);\n } else if (this.activeRow.rowIndex !== rowIndex) {\n // Switching rows in row mode — commit pending edits in the previous row\n // before starting elsewhere.\n const committed = await this.commitRow();\n if (!committed) return false;\n await this.editRow(rowIndex);\n }\n } else if (this.activeCell && !this.isCellEditing(rowIndex, columnKey)) {\n // Cell mode — commit any in-flight cell before switching.\n await this.commitCell();\n }\n\n this.activeCell = { rowIndex, columnKey, data };\n this.host.requestUpdate();\n return true;\n }\n\n /**\n * Begins editing the given row in row mode. Emits `rowEditStarted` on\n * success. No-op in cell mode.\n */\n public async editRow(rowIndex: number): Promise<boolean> {\n if (!this.enabled || this.mode !== 'row') return false;\n const data = this.host.pageItems[rowIndex] as T | undefined;\n if (!data) return false;\n if (this.activeRow && this.activeRow.rowIndex !== rowIndex) {\n const committed = await this.commitRow();\n if (!committed) return false;\n }\n if (this.activeRow?.rowIndex === rowIndex) return true;\n this.activeRow = { rowIndex, data };\n this.pending.clear();\n this.host.requestUpdate();\n this.host.emitEvent('rowEditStarted', { detail: { rowIndex } });\n return true;\n }\n\n /**\n * The single choke point for committing a cell value to the backing record.\n * Short-circuits no-op writes, runs the cancellable `cellValueChanging` gate,\n * writes the value through to {@link ApexGrid.data}, and emits\n * `cellValueChanged`. Returns the {@link CellEditResult} so callers can manage\n * their own UI state (active cell) and batch the render.\n *\n * @remarks\n * Does **not** request a render — callers refresh (once per edit, or once per\n * batch) so row-mode and bulk edits coalesce into a single pipeline pass.\n * Every committed edit (single-cell, row-mode, and enterprise paste/fill)\n * funnels through here so cross-cutting concerns (validation, undo/redo) have\n * one place to hook the old/new values and the write.\n */\n public applyCellEdit(\n rowIndex: number,\n columnKey: Keys<T>,\n data: T,\n value: unknown,\n options?: ApplyCellEditOptions\n ): CellEditResult {\n const record = data as Record<string, unknown>;\n const oldValue = record[columnKey as string];\n if (Object.is(value, oldValue)) {\n // Re-entering the original value clears any stale invalid marker.\n this.clearCellErrors(data, columnKey);\n return 'unchanged';\n }\n\n if (options?.validate !== false) {\n const errors = this.validateCell(rowIndex, columnKey, data, value);\n if (errors.length > 0) {\n this.#setCellErrors(data, columnKey, errors);\n this.host.emitEvent('cellValidationFailed', {\n detail: { key: columnKey, rowIndex, data, value, errors },\n });\n return 'invalid';\n }\n }\n\n const proceed = this.host.emitEvent('cellValueChanging', {\n detail: { key: columnKey, rowIndex, data, oldValue, newValue: value },\n cancelable: true,\n });\n if (!proceed) return 'cancelled';\n\n record[columnKey as string] = value;\n this.clearCellErrors(data, columnKey);\n this.history?.record({ record: data, key: columnKey, rowIndex, oldValue, newValue: value });\n this.host.emitEvent('cellValueChanged', {\n detail: { key: columnKey, rowIndex, data, value },\n });\n return 'applied';\n }\n\n /**\n * Runs the column's declarative validators against a candidate value and\n * returns the collected error messages (empty when the value passes or the\n * column has no validators).\n */\n public validateCell(rowIndex: number, columnKey: Keys<T>, data: T, value: unknown): string[] {\n const column = this.host.getColumn(columnKey);\n if (!column?.validators?.length) return [];\n // `ColumnConfiguration<T>` is a distributive conditional, so `validators`\n // and `column` widen to a per-key union here; collapse to the base form for\n // the runner (the runtime shapes are identical).\n return runValidators(column.validators as Validator<T>[], value, {\n column,\n data,\n rowIndex,\n } as ValidatorContext<T>);\n }\n\n /**\n * The current validation errors for `(data, columnKey)`, or `null` when the\n * cell is valid. Read by {@link ApexGridCell} to drive `aria-invalid` and the\n * inline error node.\n */\n public getCellErrors(data: T, columnKey: Keys<T>): readonly string[] | null {\n return this.#invalid.get(data)?.get(columnKey) ?? null;\n }\n\n /**\n * Clears any validation error recorded for `(data, columnKey)`.\n */\n public clearCellErrors(data: T, columnKey: Keys<T>): void {\n const perColumn = this.#invalid.get(data);\n if (!perColumn) return;\n if (perColumn.delete(columnKey)) {\n if (perColumn.size === 0) this.#invalid.delete(data);\n this.#validationVersion += 1;\n }\n }\n\n /**\n * Clears every validation error recorded for a record (all columns).\n */\n public clearRecordErrors(data: T): void {\n if (this.#invalid.delete(data)) {\n this.#validationVersion += 1;\n }\n }\n\n #setCellErrors(data: T, columnKey: Keys<T>, errors: string[]): void {\n let perColumn = this.#invalid.get(data);\n if (!perColumn) {\n perColumn = new Map();\n this.#invalid.set(data, perColumn);\n }\n perColumn.set(columnKey, errors);\n this.#validationVersion += 1;\n }\n\n /**\n * Commits a candidate value for the currently editing cell. In cell mode the\n * value is written through to {@link ApexGrid.data}; in row mode it is staged\n * in {@link pending} until {@link commitRow} is called.\n *\n * @param value - The candidate value.\n * @returns `true` if accepted (or staged), `false` if rejected by\n * `cellValueChanging.preventDefault()`.\n */\n public async commitCell(value?: unknown): Promise<boolean> {\n const active = this.activeCell;\n if (!active) return false;\n const { rowIndex, columnKey, data } = active;\n const oldValue = (data as Record<string, unknown>)[columnKey as string];\n const candidate = value === undefined ? oldValue : value;\n\n if (this.mode === 'cell') {\n const result = this.applyCellEdit(rowIndex, columnKey, data, candidate);\n if (result === 'cancelled') return false;\n // A failed validation keeps the editor open so the user can correct the\n // value; re-render to surface `aria-invalid` + the error node.\n if (result === 'invalid') {\n this.host.requestUpdate();\n return false;\n }\n // Both 'applied' and 'unchanged' close the editor; only an applied write\n // needs the data pipeline to re-run.\n this.activeCell = null;\n if (result === 'applied') {\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n } else {\n this.host.requestUpdate();\n }\n return true;\n }\n\n // Row mode — stage and exit cell edit (row stays open).\n if (!Object.is(candidate, oldValue)) {\n this.pending.set(columnKey, candidate);\n }\n this.activeCell = null;\n this.host.requestUpdate();\n return true;\n }\n\n /**\n * Writes `value` directly to the cell at `(rowIndex, columnKey)` without\n * entering edit mode. Used by interactive display widgets (e.g. the boolean\n * checkbox) that handle their own input. Goes through the same\n * `cellValueChanging` / `cellValueChanged` event path as a normal commit.\n *\n * @returns `true` if the value was applied, `false` if rejected by\n * `cellValueChanging.preventDefault()` or the column is not editable.\n */\n public async commitImmediate(\n rowIndex: number,\n columnKey: Keys<T>,\n value: unknown\n ): Promise<boolean> {\n if (!this.enabled) return false;\n const column = this.host.getColumn(columnKey);\n if (!column || !this.isEditable(column)) return false;\n const data = this.host.pageItems[rowIndex] as T | undefined;\n if (!data) return false;\n\n const result = this.applyCellEdit(rowIndex, columnKey, data, value);\n if (result === 'cancelled') return false;\n if (result === 'invalid') {\n this.host.requestUpdate();\n return false;\n }\n if (result === 'applied') {\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n }\n return true;\n }\n\n /**\n * Discards the current cell edit without writing. In row mode the row edit\n * stays open.\n */\n public cancelCell(): void {\n if (!this.activeCell) return;\n const { data, columnKey } = this.activeCell;\n this.activeCell = null;\n // Abandoning the edit discards any validation error raised for the cell.\n this.clearCellErrors(data, columnKey);\n this.host.requestUpdate();\n }\n\n /**\n * Commits all pending edits for the currently editing row. Emits\n * `cellValueChanging` for every changed cell and `rowEditEnded` at the end.\n *\n * @returns `true` if the row was applied, `false` if any cell change was\n * cancelled (the row remains in edit mode in that case).\n */\n public async commitRow(): Promise<boolean> {\n const active = this.activeRow;\n if (!active) return false;\n const { rowIndex, data } = active;\n\n // Make sure any open cell editor flushes its pending value first.\n if (this.activeCell?.rowIndex === rowIndex) {\n await this.commitCell();\n }\n\n // Pre-validate every pending cell so a single failure aborts the whole row\n // commit without a partial write. Each failing cell is marked invalid and\n // the row stays in edit mode.\n let hasInvalid = false;\n for (const [columnKey, candidate] of this.pending) {\n const errors = this.validateCell(rowIndex, columnKey, data, candidate);\n if (errors.length > 0) {\n this.#setCellErrors(data, columnKey, errors);\n this.host.emitEvent('cellValidationFailed', {\n detail: { key: columnKey, rowIndex, data, value: candidate, errors },\n });\n hasInvalid = true;\n }\n }\n if (hasInvalid) {\n this.host.requestUpdate();\n return false;\n }\n\n // Coalesce the row's writes into one undo step.\n this.history?.beginBatch();\n try {\n for (const [columnKey, candidate] of this.pending) {\n // Validation already ran above; a cancelled cell aborts the whole row\n // commit, leaving it in edit mode.\n if (\n this.applyCellEdit(rowIndex, columnKey, data, candidate, { validate: false }) ===\n 'cancelled'\n ) {\n return false;\n }\n }\n } finally {\n this.history?.endBatch();\n }\n\n this.pending.clear();\n this.activeRow = null;\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: true } });\n return true;\n }\n\n /**\n * Discards every pending edit for the active row and exits row edit mode.\n * Emits `rowEditEnded` with `committed: false`.\n */\n public cancelRow(): void {\n if (!this.activeRow) return;\n const { rowIndex, data } = this.activeRow;\n this.activeCell = null;\n this.activeRow = null;\n this.pending.clear();\n // Abandoning the row discards every validation error raised for it.\n this.clearRecordErrors(data);\n this.host.requestUpdate();\n this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: false } });\n }\n\n /**\n * Returns the current pending or committed value for `(rowIndex, columnKey)`.\n * Cells in row mode read this so unstaged-but-pending values are reflected\n * during the edit.\n */\n public getValue(rowIndex: number, columnKey: Keys<T>): unknown {\n if (\n this.mode === 'row' &&\n this.activeRow?.rowIndex === rowIndex &&\n this.pending.has(columnKey)\n ) {\n return this.pending.get(columnKey);\n }\n const data = this.host.pageItems[rowIndex] as T | undefined;\n return data ? (data as Record<string, unknown>)[columnKey as string] : undefined;\n }\n}\n"]}
1
+ {"version":3,"file":"editing.js","sourceRoot":"","sources":["../../src/controllers/editing.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ReactiveController } from 'lit';\nimport { PIPELINE } from '../internal/constants.js';\nimport type {\n ColumnConfiguration,\n EditMode,\n EditTrigger,\n GridHost,\n Keys,\n Validator,\n ValidatorContext,\n} from '../internal/types.js';\nimport { runValidators } from '../internal/validators.js';\nimport type { EditCommand, HistoryController } from './history.js';\n\n/**\n * The default editing configuration used when the grid has none set.\n */\nexport const DEFAULT_EDITING_CONFIG = Object.freeze({\n enabled: false,\n mode: 'cell' as EditMode,\n trigger: 'doubleClick' as EditTrigger,\n});\n\n/** {@link EditingController} draft sentinel: the open editor has no typed value yet. */\nconst NO_DRAFT = Symbol('no-draft');\n\ninterface ActiveCell<T> {\n /**\n * The record's view index (matches {@link ApexGrid.pageItems}), refreshed\n * after every grid update; `-1` for a pinned record or one on another page.\n */\n rowIndex: number;\n columnKey: Keys<T>;\n /**\n * Live reference to the underlying record (preserved through the pipeline so\n * the source array can be mutated on commit). The edit is bound to this\n * record, never to the view index, so a sort, filter, page or data change\n * cannot move the editor onto another record.\n */\n data: T;\n}\n\n/**\n * Outcome of a single committed cell edit routed through\n * {@link EditingController.applyCellEdit}.\n *\n * - `'unchanged'` - the candidate equalled the current value; nothing happened.\n * - `'invalid'` - a column validator rejected the candidate; the value was not\n * written and `cellValidationFailed` fired.\n * - `'cancelled'` - a `cellValueChanging` listener called `preventDefault()`.\n * - `'applied'` - the value was written and `cellValueChanged` fired.\n */\nexport type CellEditResult = 'unchanged' | 'invalid' | 'cancelled' | 'applied';\n\n/**\n * Options for {@link EditingController.applyCellEdit}.\n */\nexport interface ApplyCellEditOptions {\n /**\n * Run the column's declarative validators before writing. Defaults to `true`.\n * Set to `false` for writes that must not be re-validated (e.g. reverting a\n * previously valid value during undo/redo).\n */\n validate?: boolean;\n /**\n * Emit the cancellable `cellValueChanging` before writing. Defaults to\n * `true`. Set to `false` when the caller already ran the gate for this\n * write (a row commit gates every cell before writing any).\n */\n gate?: boolean;\n}\n\n/**\n * Reactive controller backing inline cell + row editing.\n *\n * @remarks\n * Holds the currently active cell / row, pending values for row-mode edits,\n * and is responsible for emitting the cancellable `cellValueChanging` event\n * and follow-up `cellValueChanged` event. The actual data mutation writes\n * through to {@link ApexGrid.data} so changes survive pipeline runs and\n * propagate to consumers that own the source array.\n */\nexport class EditingController<T extends object> implements ReactiveController {\n /** The cell currently in edit mode, or `null`. */\n public activeCell: ActiveCell<T> | null = null;\n\n /**\n * The row currently in edit mode (row mode only), or `null`. Bound to its\n * record like {@link activeCell}.\n */\n public activeRow: { rowIndex: number; data: T } | null = null;\n\n /**\n * Pending values keyed by column for the currently editing row. Populated in\n * row mode as the user edits individual cells; flushed on\n * {@link commitRow}. Holds only values that differ from the record.\n */\n public pending: Map<Keys<T>, unknown> = new Map();\n\n /**\n * Commands staged with the row edit, keyed by column (see\n * {@link commitCell}): run when the row commits, dropped when it cancels.\n */\n #effects: Map<Keys<T>, EditCommand> = new Map();\n\n /**\n * Per-record validation errors, keyed by the live data reference and then by\n * column. Keyed by reference (not row index) so an invalid marker tracks its\n * row across pipeline runs (sort / filter / paginate). Weak, so the markers\n * of records the app replaced are released with them.\n */\n #invalid: WeakMap<T, Map<Keys<T>, string[]>> = new WeakMap();\n\n #validationVersion = 0;\n\n /**\n * The open editor's uncommitted value, or {@link NO_DRAFT}. Held here rather\n * than in the cell so a re-render of the editing cell cannot reset it and a\n * bare {@link commitCell} (from `grid.commitEdit()`, a cell switch or a row\n * commit) writes what the user typed.\n */\n #draft: unknown = NO_DRAFT;\n\n /** Whether {@link #draft} failed validation and has not changed since. */\n #draftRejected = false;\n\n /**\n * Monotonic token bumped whenever the validation-error set or the staged\n * row-mode values change. Forwarded down to each cell as a reactive property\n * so such a change re-renders the affected cells (toggling `aria-invalid`\n * and the error node, showing a staged value) without re-running the data\n * pipeline. Stays `0` until the first failure or staged value.\n */\n public get validationVersion(): number {\n return this.#validationVersion;\n }\n\n constructor(\n protected host: GridHost<T>,\n protected history?: HistoryController<T>\n ) {\n this.host.addController(this);\n }\n\n public hostConnected() {}\n\n /**\n * Keeps an open edit bound to its record after the view changed: refreshes\n * the reported view index, and cancels the edit when the record left the\n * view (removed, filtered out, replaced by a copy), so no hidden editor can\n * commit later. A record on another page or in a pinned band stays open.\n * Runs only while an edit is open.\n */\n public hostUpdated(): void {\n const data = this.activeRow?.data ?? this.activeCell?.data;\n if (!data) return;\n const rowIndex = this.#indexOf(data);\n if (rowIndex < 0 && !this.#inView(data)) {\n if (this.activeRow) this.cancelRow();\n else this.cancelCell();\n return;\n }\n if (this.activeRow) this.activeRow.rowIndex = rowIndex;\n if (this.activeCell) this.activeCell.rowIndex = rowIndex;\n }\n\n /** The record's view index (matches {@link ApexGrid.pageItems}), or `-1`. */\n #indexOf(data: T): number {\n return this.host.pageItems.indexOf(data);\n }\n\n /** Whether the record is still in the processed view or a pinned band. */\n #inView(data: T): boolean {\n return this.host.dataView.includes(data) || this.#isPinned(data);\n }\n\n #isPinned(data: T): boolean {\n if (!this.host.rowPinning?.enabled) return false;\n const { top, bottom } = this.host.pinnedRows;\n return top.includes(data) || bottom.includes(data);\n }\n\n /**\n * Whether the record of an open edit is out of sight: on another page, so\n * neither a body row nor a pinned row renders its editor.\n */\n #isHidden(data: T): boolean {\n return this.#indexOf(data) < 0 && !this.#isPinned(data);\n }\n\n /**\n * Whether editing is enabled at the grid level.\n */\n public get enabled(): boolean {\n return Boolean(this.host.editing?.enabled);\n }\n\n /**\n * The resolved edit mode (`'cell'` if unset).\n */\n public get mode(): EditMode {\n return this.host.editing?.mode ?? DEFAULT_EDITING_CONFIG.mode;\n }\n\n /**\n * The resolved edit trigger (`'doubleClick'` if unset).\n */\n public get trigger(): EditTrigger {\n return this.host.editing?.trigger ?? DEFAULT_EDITING_CONFIG.trigger;\n }\n\n /**\n * Whether the given column can be edited.\n */\n public isEditable(column: ColumnConfiguration<T>): boolean {\n return Boolean(this.enabled && column.editable && !column.hidden);\n }\n\n /**\n * Whether the cell `(data, columnKey)` is currently in edit mode.\n */\n public isCellEditing(data: T, columnKey: Keys<T>): boolean {\n const active = this.activeCell;\n return active !== null && active.data === data && active.columnKey === columnKey;\n }\n\n /**\n * Begins editing the cell at `(rowIndex, columnKey)`, where `rowIndex` is\n * the view index (matches {@link ApexGrid.pageItems}). See\n * {@link editRecord}.\n */\n public async editCell(rowIndex: number, columnKey: Keys<T>): Promise<boolean> {\n const data = this.host.pageItems[rowIndex] as T | undefined;\n return data ? this.editRecord(data, columnKey) : false;\n }\n\n /**\n * Begins editing the `columnKey` cell of the record `data`, a body or a\n * pinned row. Auto-commits any cell already in edit mode (in cell mode); in\n * row mode it stages that cell and commits the row first on a cross-row\n * move.\n *\n * @returns `true` if edit started, `false` if rejected (column not editable,\n * `data` a row a feature synthesized, the in-flight cell's commit was\n * rejected, or the previous row could not be committed). A rejected commit\n * keeps the in-flight editor open, unless its record is on another page:\n * an edit the user cannot see or correct is cancelled instead, so it cannot\n * block every other edit.\n */\n public async editRecord(data: T, columnKey: Keys<T>): Promise<boolean> {\n if (!this.enabled || !this.#isRecord(data)) return false;\n const column = this.host.getColumn(columnKey);\n if (!column || !this.isEditable(column)) return false;\n // Row mode: switching rows commits the pending edits of the previous row\n // before starting elsewhere.\n if (this.mode === 'row' && !(await this.#openRow(data))) return false;\n if (this.activeCell && !this.isCellEditing(data, columnKey)) {\n // Commit (cell mode) or stage (row mode) the in-flight cell, draft\n // included, before switching. A rejected commit keeps that editor open\n // with its error instead of abandoning it over still-valid data. A\n // draft that already failed validation (the focus loss of the same\n // click) is not validated a second time.\n if (this.#draftRejected || !(await this.commitCell())) {\n if (!this.#isHidden(this.activeCell.data)) return false;\n this.cancelCell();\n }\n }\n\n if (!this.isCellEditing(data, columnKey)) this.#resetDraft();\n this.activeCell = { rowIndex: this.#indexOf(data), columnKey, data };\n this.host.requestUpdate();\n return true;\n }\n\n /**\n * Begins editing the given row in row mode. Emits `rowEditStarted` on\n * success. No-op in cell mode.\n */\n public async editRow(rowIndex: number): Promise<boolean> {\n if (!this.enabled || this.mode !== 'row') return false;\n const data = this.host.pageItems[rowIndex] as T | undefined;\n return data && this.#isRecord(data) ? this.#openRow(data) : false;\n }\n\n /**\n * Whether `data` is a record, not a row a feature synthesized (a group\n * header, a total, a row a server has not sent yet; see\n * `StateController.isDataRow`). Always `true` for the community grid.\n */\n #isRecord(data: T): boolean {\n const state = (this.host as unknown as { stateController?: { isDataRow(row: T): boolean } })\n .stateController;\n return state?.isDataRow(data) ?? true;\n }\n\n /** Opens `data` for a row edit, committing a different open row first. */\n async #openRow(data: T): Promise<boolean> {\n if (this.activeRow?.data === data) return true;\n if (this.activeRow && !(await this.commitRow())) {\n // A rejected row on another page cannot be corrected there: cancel it\n // rather than block every other edit.\n if (!this.#isHidden(this.activeRow.data)) return false;\n this.cancelRow();\n }\n const rowIndex = this.#indexOf(data);\n this.activeRow = { rowIndex, data };\n this.pending.clear();\n this.#effects.clear();\n this.host.requestUpdate();\n this.host.emitEvent('rowEditStarted', { detail: { rowIndex } });\n return true;\n }\n\n /**\n * The single choke point for committing a cell value to the backing record.\n * Short-circuits no-op writes, runs the cancellable `cellValueChanging` gate,\n * writes the value through to {@link ApexGrid.data}, and emits\n * `cellValueChanged`. Returns the {@link CellEditResult} so callers can manage\n * their own UI state (active cell) and batch the render.\n *\n * @remarks\n * Does **not** request a render — callers refresh (once per edit, or once per\n * batch) so row-mode and bulk edits coalesce into a single pipeline pass.\n * Every committed edit (single-cell, row-mode, and enterprise paste/fill)\n * funnels through here so cross-cutting concerns (validation, undo/redo) have\n * one place to hook the old/new values and the write.\n */\n public applyCellEdit(\n rowIndex: number,\n columnKey: Keys<T>,\n data: T,\n value: unknown,\n options?: ApplyCellEditOptions\n ): CellEditResult {\n const record = data as Record<string, unknown>;\n const oldValue = record[columnKey as string];\n if (Object.is(value, oldValue)) {\n // Re-entering the original value clears any stale invalid marker.\n this.clearCellErrors(data, columnKey);\n return 'unchanged';\n }\n\n if (options?.validate !== false) {\n const errors = this.validateCell(rowIndex, columnKey, data, value);\n if (errors.length > 0) {\n this.#setCellErrors(data, columnKey, errors);\n this.host.emitEvent('cellValidationFailed', {\n detail: { key: columnKey, rowIndex, data, value, errors },\n });\n return 'invalid';\n }\n }\n\n if (options?.gate !== false && !this.#gate(rowIndex, columnKey, data, value)) {\n return 'cancelled';\n }\n\n record[columnKey as string] = value;\n this.clearCellErrors(data, columnKey);\n this.history?.record({ record: data, key: columnKey, rowIndex, oldValue, newValue: value });\n this.host.emitEvent('cellValueChanged', {\n detail: { key: columnKey, rowIndex, data, value },\n });\n return 'applied';\n }\n\n /** Emits the cancellable `cellValueChanging`; `false` when a listener vetoed it. */\n #gate(rowIndex: number, columnKey: Keys<T>, data: T, value: unknown): boolean {\n return this.host.emitEvent('cellValueChanging', {\n detail: {\n key: columnKey,\n rowIndex,\n data,\n oldValue: (data as Record<string, unknown>)[columnKey as string],\n newValue: value,\n },\n cancelable: true,\n });\n }\n\n /**\n * Runs the column's declarative validators against a candidate value and\n * returns the collected error messages (empty when the value passes or the\n * column has no validators).\n *\n * @param row - What validators see as `ctx.data`: the record itself, or in\n * a row commit the staged row (the record with the staged values applied).\n */\n public validateCell(\n rowIndex: number,\n columnKey: Keys<T>,\n data: T,\n value: unknown,\n row: T = data\n ): string[] {\n const column = this.host.getColumn(columnKey);\n if (!column?.validators?.length) return [];\n // `ColumnConfiguration<T>` is a distributive conditional, so `validators`\n // and `column` widen to a per-key union here; collapse to the base form for\n // the runner (the runtime shapes are identical).\n return runValidators(column.validators as Validator<T>[], value, {\n column,\n data: row,\n rowIndex,\n } as ValidatorContext<T>);\n }\n\n /**\n * The staged row-mode values of `data` keyed by column, or `null` when\n * `data` is not the row in edit. Cells read it to show staged values.\n */\n public stagedValues(data: T): ReadonlyMap<Keys<T>, unknown> | null {\n return this.activeRow?.data === data ? this.pending : null;\n }\n\n /** The command staged with the row edit of `data` for `columnKey`, if any. */\n public stagedEffect(data: T, columnKey: Keys<T>): EditCommand | undefined {\n return this.activeRow?.data === data ? this.#effects.get(columnKey) : undefined;\n }\n\n /** Runs a command riding with an accepted edit and records it for undo. */\n #run(effect: EditCommand): void {\n effect.apply();\n this.history?.push(effect);\n }\n\n /**\n * Stages `value` for the active row's `columnKey`. A value equal to the\n * record's is unstaged (and its stale error cleared), so re-entering the\n * original undoes a staged edit. `effect` replaces the command staged for\n * the column; `undefined` drops it and `null` keeps it.\n */\n #stage(data: T, columnKey: Keys<T>, value: unknown, effect?: EditCommand | null): void {\n if (effect) this.#effects.set(columnKey, effect);\n else if (effect === undefined) this.#effects.delete(columnKey);\n if (Object.is(value, (data as Record<string, unknown>)[columnKey as string])) {\n if (this.pending.delete(columnKey)) this.#validationVersion += 1;\n this.clearCellErrors(data, columnKey);\n } else if (!this.pending.has(columnKey) || !Object.is(this.pending.get(columnKey), value)) {\n this.pending.set(columnKey, value);\n this.#validationVersion += 1;\n }\n }\n\n /**\n * The current validation errors for `(data, columnKey)`, or `null` when the\n * cell is valid. Read by {@link ApexGridCell} to drive `aria-invalid` and the\n * inline error node.\n */\n public getCellErrors(data: T, columnKey: Keys<T>): readonly string[] | null {\n return this.#invalid.get(data)?.get(columnKey) ?? null;\n }\n\n /**\n * Clears any validation error recorded for `(data, columnKey)`.\n */\n public clearCellErrors(data: T, columnKey: Keys<T>): void {\n const perColumn = this.#invalid.get(data);\n if (!perColumn) return;\n if (perColumn.delete(columnKey)) {\n if (perColumn.size === 0) this.#invalid.delete(data);\n this.#validationVersion += 1;\n }\n }\n\n /**\n * Clears every validation error recorded for a record (all columns).\n */\n public clearRecordErrors(data: T): void {\n if (this.#invalid.delete(data)) {\n this.#validationVersion += 1;\n }\n }\n\n #setCellErrors(data: T, columnKey: Keys<T>, errors: string[]): void {\n let perColumn = this.#invalid.get(data);\n if (!perColumn) {\n perColumn = new Map();\n this.#invalid.set(data, perColumn);\n }\n perColumn.set(columnKey, errors);\n this.#validationVersion += 1;\n }\n\n /**\n * Records the open editor's current, uncommitted value. A later\n * {@link commitCell} without an argument commits it. No-op while no cell is\n * in edit mode.\n */\n public setDraft(value: unknown): void {\n if (!this.activeCell) return;\n this.#draft = value;\n this.#draftRejected = false;\n }\n\n /**\n * The draft held for the open editor of `(data, columnKey)`, or `null` when\n * that cell is not in edit or nothing was typed. An editor rendered again\n * for an edit already open (its record paged or scrolled back into view)\n * resumes from it, so it shows what a bare {@link commitCell} commits.\n */\n public heldDraft(data: T, columnKey: Keys<T>): { value: unknown } | null {\n return this.#draft === NO_DRAFT || !this.isCellEditing(data, columnKey)\n ? null\n : { value: this.#draft };\n }\n\n #resetDraft(): void {\n this.#draft = NO_DRAFT;\n this.#draftRejected = false;\n }\n\n #endCellEdit(): void {\n this.activeCell = null;\n this.#resetDraft();\n }\n\n /**\n * Commits a candidate value for the currently editing cell. In cell mode the\n * value is written through to {@link ApexGrid.data}; in row mode it is staged\n * in {@link pending} until {@link commitRow} is called.\n *\n * @param args - The candidate value, committed as given (`undefined`\n * included). Called with no argument, it commits the open editor's draft\n * (see {@link setDraft}), or keeps the current value when nothing was typed.\n * An optional command rides with the value: it runs once the value is\n * accepted (or, in row mode, when the row commits), joins the same undo\n * step, and is dropped when the edit is rejected or the row cancelled.\n * @returns `true` if accepted (or staged), `false` if rejected by\n * `cellValueChanging.preventDefault()` or a validator.\n */\n public async commitCell(...args: [value?: unknown, effect?: EditCommand]): Promise<boolean> {\n const active = this.activeCell;\n if (!active) return false;\n const { rowIndex, columnKey, data } = active;\n // The value the editor opened on: in row mode a staged value, so an\n // untouched editor keeps it staged.\n const staged = this.stagedValues(data);\n const current = staged?.has(columnKey)\n ? staged.get(columnKey)\n : (data as Record<string, unknown>)[columnKey as string];\n const draft = this.#draft;\n const candidate = args.length > 0 ? args[0] : draft === NO_DRAFT ? current : draft;\n\n const effect = args[1];\n if (this.mode === 'cell') {\n let result: CellEditResult;\n // The value and its command undo as one step.\n if (effect) this.history?.beginBatch();\n try {\n result = this.applyCellEdit(rowIndex, columnKey, data, candidate);\n if (effect && (result === 'applied' || result === 'unchanged')) this.#run(effect);\n } finally {\n if (effect) this.history?.endBatch();\n }\n if (result === 'cancelled') return false;\n // A failed validation keeps the editor open so the user can correct the\n // value; re-render to surface `aria-invalid` + the error node.\n if (result === 'invalid') {\n if (args.length === 0) this.#draftRejected = true;\n this.host.requestUpdate();\n return false;\n }\n // Both 'applied' and 'unchanged' close the editor; only an applied write\n // needs the data pipeline to re-run.\n this.#endCellEdit();\n if (result === 'applied') {\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n } else {\n this.host.requestUpdate();\n }\n return true;\n }\n\n // Row mode: stage and exit cell edit (row stays open). A bare commit\n // keeps the command staged for the cell.\n this.#stage(data, columnKey, candidate, args.length > 0 ? effect : null);\n this.#endCellEdit();\n this.host.requestUpdate();\n return true;\n }\n\n /**\n * Writes `value` directly to the `columnKey` cell of the record `data` (a\n * body or a pinned row) without entering edit mode. Used by interactive\n * display widgets (e.g. the boolean checkbox) that handle their own input.\n * Goes through the same `cellValueChanging` / `cellValueChanged` event path\n * as a normal commit. In row mode the value joins the row's staged values\n * instead (opening the row edit first), so it commits or cancels with the\n * rest of the row.\n *\n * @returns `true` if the value was applied (or staged), `false` if rejected\n * by `cellValueChanging.preventDefault()`, a validator, a previous row that\n * could not be committed, the column is not editable, or `data` is a row a\n * feature synthesized (as in {@link editRecord}).\n */\n public async commitImmediate(data: T, columnKey: Keys<T>, value: unknown): Promise<boolean> {\n if (!this.enabled || !this.#isRecord(data)) return false;\n const column = this.host.getColumn(columnKey);\n if (!column || !this.isEditable(column)) return false;\n\n if (this.mode === 'row') {\n if (!(await this.#openRow(data))) return false;\n this.#stage(data, columnKey, value);\n this.host.requestUpdate();\n return true;\n }\n\n const result = this.applyCellEdit(this.#indexOf(data), columnKey, data, value);\n if (result === 'cancelled') return false;\n if (result === 'invalid') {\n this.host.requestUpdate();\n return false;\n }\n if (result === 'applied') {\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n }\n return true;\n }\n\n /**\n * Discards the current cell edit without writing. In row mode the row edit\n * stays open.\n */\n public cancelCell(): void {\n if (!this.activeCell) return;\n const { data, columnKey } = this.activeCell;\n this.#endCellEdit();\n // Abandoning the edit discards any validation error raised for the cell.\n this.clearCellErrors(data, columnKey);\n this.host.requestUpdate();\n }\n\n /**\n * Commits all pending edits for the currently editing row. Emits\n * `cellValueChanging` for every changed cell and `rowEditEnded` at the end.\n *\n * @remarks\n * Validators see the staged row as `ctx.data`, and once any value is staged\n * the other editable cells of the row are validated too, so a cross-field\n * rule judges the row being saved. Every cell passes its `cellValueChanging`\n * gate before any is written: an invalid or cancelled cell aborts the whole\n * row.\n *\n * @returns `true` if the row was applied, `false` if any cell failed\n * validation or its change was cancelled (the row remains in edit mode).\n */\n public async commitRow(): Promise<boolean> {\n const active = this.activeRow;\n if (!active) return false;\n const { data } = active;\n\n // Make sure any open cell editor flushes its pending value first.\n if (this.activeCell?.data === data) {\n await this.commitCell();\n }\n const rowIndex = active.rowIndex;\n const record = data as Record<string, unknown>;\n // A staged command may compute its value from cells staged after it.\n for (const [columnKey, effect] of this.#effects) {\n if (!effect.stagedValue) continue;\n const value = effect.stagedValue(this.pending);\n if (this.pending.has(columnKey) || !Object.is(value, record[columnKey as string])) {\n this.pending.set(columnKey, value);\n }\n }\n const changes = [...this.pending].filter(\n ([columnKey, candidate]) => !Object.is(candidate, record[columnKey as string])\n );\n\n if (this.pending.size > 0 && !this.#validateRow(rowIndex, data)) {\n this.host.requestUpdate();\n return false;\n }\n for (const [columnKey, candidate] of changes) {\n if (!this.#gate(rowIndex, columnKey, data, candidate)) return false;\n }\n\n // Coalesce the row's writes into one undo step. Validation and the gate\n // already ran above for every cell.\n this.history?.beginBatch();\n try {\n for (const [columnKey, candidate] of changes) {\n this.applyCellEdit(rowIndex, columnKey, data, candidate, { validate: false, gate: false });\n }\n for (const effect of this.#effects.values()) this.#run(effect);\n } finally {\n this.history?.endBatch();\n }\n\n // Staged values were showing in the row's cells, and a cell whose record\n // value ended where it started gets no new value binding: re-render them.\n if (this.pending.size > 0) this.#validationVersion += 1;\n this.pending.clear();\n this.#effects.clear();\n this.activeRow = null;\n this.host.requestUpdate(PIPELINE);\n await this.host.updateComplete;\n this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: true } });\n return true;\n }\n\n /**\n * Validates the staged row, marking each failing cell (and clearing the\n * marker of each passing one). `false` when any cell failed. A staged cell\n * is judged on its staged value; another editable cell fails only when the\n * staged values made it invalid (a cross-field rule), so an untouched value\n * that was already invalid does not block the row.\n */\n #validateRow(rowIndex: number, data: T): boolean {\n // The record with the staged values applied, keeping its prototype so\n // validators reading accessors or methods still work.\n const row = Object.assign(Object.create(Object.getPrototypeOf(data)), data) as T;\n for (const [columnKey, value] of this.pending) {\n (row as Record<string, unknown>)[columnKey as string] = value;\n }\n let valid = true;\n for (const column of this.host.columns) {\n if (!this.isEditable(column) || !column.validators?.length) continue;\n const columnKey = column.key;\n const value = (row as Record<string, unknown>)[columnKey as string];\n const errors = this.#validateStaged(rowIndex, columnKey, data, value, row);\n if (errors.length === 0) {\n this.clearCellErrors(data, columnKey);\n continue;\n }\n if (\n !this.pending.has(columnKey) &&\n this.validateCell(rowIndex, columnKey, data, value).length > 0\n ) {\n continue;\n }\n valid = false;\n this.#setCellErrors(data, columnKey, errors);\n this.host.emitEvent('cellValidationFailed', {\n detail: { key: columnKey, rowIndex, data, value, errors },\n });\n }\n return valid;\n }\n\n /**\n * Validates a cell against the staged row. A validator that throws on the\n * copy (one reading private class fields of the record, say) is run again\n * against the live record, as before staged rows existed; a throw there\n * propagates.\n */\n #validateStaged(rowIndex: number, columnKey: Keys<T>, data: T, value: unknown, row: T): string[] {\n try {\n return this.validateCell(rowIndex, columnKey, data, value, row);\n } catch {\n return this.validateCell(rowIndex, columnKey, data, value);\n }\n }\n\n /**\n * Discards every pending edit for the active row and exits row edit mode.\n * Emits `rowEditEnded` with `committed: false`.\n */\n public cancelRow(): void {\n if (!this.activeRow) return;\n const { rowIndex, data } = this.activeRow;\n this.#endCellEdit();\n this.activeRow = null;\n // Staged values were showing in the row's cells: re-render them.\n if (this.pending.size > 0) this.#validationVersion += 1;\n this.pending.clear();\n this.#effects.clear();\n // Abandoning the row discards every validation error raised for it.\n this.clearRecordErrors(data);\n this.host.requestUpdate();\n this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: false } });\n }\n}\n"]}