apex-grid 3.5.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 (197) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/LICENSE.md +125 -0
  3. package/README.md +64 -10
  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 +146 -36
  11. package/components/grid.js +442 -103
  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 +11 -9
  17. package/components/header-row.js.map +1 -1
  18. package/components/header.js +32 -20
  19. package/components/header.js.map +1 -1
  20. package/components/row.d.ts +19 -3
  21. package/components/row.js +69 -23
  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 +40 -0
  26. package/components/virtualizer.js +116 -5
  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 +67 -13
  44. package/controllers/navigation.js +191 -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 +38 -22
  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 +56 -7
  67. package/controllers/state.js +76 -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 +3156 -277
  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 +155 -81
  78. package/docs/classes/ApexGridPaginator.html +1 -1
  79. package/docs/classes/ApexGridToolbar.html +3 -3
  80. package/docs/functions/localize.html +2 -2
  81. package/docs/hierarchy.html +1 -1
  82. package/docs/index.html +74 -11
  83. package/docs/interfaces/ApexCellValidationFailedEvent.html +3 -2
  84. package/docs/interfaces/ApexCellValueChangedEvent.html +4 -2
  85. package/docs/interfaces/ApexCellValueChangingEvent.html +3 -2
  86. package/docs/interfaces/ApexGridEventMap.html +5 -2
  87. package/docs/interfaces/ApexRowEditEndedEvent.html +2 -1
  88. package/docs/interfaces/ApexRowEditStartedEvent.html +2 -1
  89. package/docs/interfaces/ApexRowMovingEvent.html +9 -3
  90. package/docs/interfaces/ApexRowPinningEvent.html +1 -1
  91. package/docs/interfaces/ApexTreeRowExpandedEvent.html +4 -1
  92. package/docs/interfaces/ApexTreeRowExpandingEvent.html +4 -1
  93. package/docs/interfaces/BaseApexCellContext.html +1 -1
  94. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  95. package/docs/interfaces/BaseFilterExpression.html +2 -2
  96. package/docs/interfaces/CSVExportOptions.html +12 -4
  97. package/docs/interfaces/DataPipelineConfiguration.html +1 -1
  98. package/docs/interfaces/ExportOptions.html +5 -3
  99. package/docs/interfaces/GridExpansionConfiguration.html +6 -2
  100. package/docs/interfaces/GridRowReorderingConfiguration.html +1 -1
  101. package/docs/interfaces/PaginationState.html +1 -1
  102. package/docs/interfaces/SetStateResult.html +3 -2
  103. package/docs/interfaces/ValidatorContext.html +7 -2
  104. package/docs/media/_token-values.scss +118 -0
  105. package/docs/media/package.json +3 -2
  106. package/docs/modules.html +1 -1
  107. package/docs/types/ExportSource.html +6 -4
  108. package/docs/types/OperandKeys.html +9 -0
  109. package/docs/variables/EN_LOCALE.html +2 -2
  110. package/i18n/en.d.ts +5 -0
  111. package/i18n/en.js +5 -0
  112. package/i18n/en.js.map +1 -1
  113. package/i18n/es.js +5 -0
  114. package/i18n/es.js.map +1 -1
  115. package/internal/column-types.d.ts +27 -1
  116. package/internal/column-types.js +75 -49
  117. package/internal/column-types.js.map +1 -1
  118. package/internal/constants.d.ts +1 -2
  119. package/internal/constants.js +0 -4
  120. package/internal/constants.js.map +1 -1
  121. package/internal/document-styles.d.ts +30 -0
  122. package/internal/document-styles.js +48 -0
  123. package/internal/document-styles.js.map +1 -0
  124. package/internal/export.d.ts +39 -7
  125. package/internal/export.js +35 -34
  126. package/internal/export.js.map +1 -1
  127. package/internal/feature-module.d.ts +60 -3
  128. package/internal/feature-module.js +6 -0
  129. package/internal/feature-module.js.map +1 -1
  130. package/internal/index.d.ts +6 -4
  131. package/internal/index.js +5 -3
  132. package/internal/index.js.map +1 -1
  133. package/internal/normalize-case.d.ts +8 -1
  134. package/internal/normalize-case.js +3 -2
  135. package/internal/normalize-case.js.map +1 -1
  136. package/internal/state-snapshot.d.ts +60 -8
  137. package/internal/state-snapshot.js +87 -19
  138. package/internal/state-snapshot.js.map +1 -1
  139. package/internal/types.d.ts +17 -2
  140. package/internal/types.js.map +1 -1
  141. package/internal/utils.d.ts +34 -6
  142. package/internal/utils.js +30 -21
  143. package/internal/utils.js.map +1 -1
  144. package/internal/validators.js +1 -0
  145. package/internal/validators.js.map +1 -1
  146. package/operations/filter/operands/number.js +9 -6
  147. package/operations/filter/operands/number.js.map +1 -1
  148. package/operations/filter/operands/string.js +2 -3
  149. package/operations/filter/operands/string.js.map +1 -1
  150. package/operations/filter/types.d.ts +13 -1
  151. package/operations/filter/types.js.map +1 -1
  152. package/operations/filter.d.ts +6 -0
  153. package/operations/filter.js +23 -7
  154. package/operations/filter.js.map +1 -1
  155. package/operations/quick-filter.js +2 -5
  156. package/operations/quick-filter.js.map +1 -1
  157. package/operations/sort.d.ts +21 -2
  158. package/operations/sort.js +41 -14
  159. package/operations/sort.js.map +1 -1
  160. package/package.json +8 -3
  161. package/setup.d.ts +11 -0
  162. package/setup.js +9 -19
  163. package/setup.js.map +1 -1
  164. package/styles/_token-values.css.d.ts +1 -0
  165. package/styles/_token-values.css.js +3 -0
  166. package/styles/_token-values.css.js.map +1 -0
  167. package/styles/_tokens.css.js +1 -1
  168. package/styles/_tokens.css.js.map +1 -1
  169. package/styles/body-cell/body-cell.css.js +1 -1
  170. package/styles/body-cell/body-cell.css.js.map +1 -1
  171. package/styles/body-cell/cell-renderers.css.js +1 -1
  172. package/styles/body-cell/cell-renderers.css.js.map +1 -1
  173. package/styles/body-row/body-row.css.js +1 -1
  174. package/styles/body-row/body-row.css.js.map +1 -1
  175. package/styles/document-tokens.css.d.ts +1 -0
  176. package/styles/document-tokens.css.js +3 -0
  177. package/styles/document-tokens.css.js.map +1 -0
  178. package/styles/filter-row/filter-row.css.js +1 -1
  179. package/styles/filter-row/filter-row.css.js.map +1 -1
  180. package/styles/grid/_grid.base.css.js +1 -1
  181. package/styles/grid/_grid.base.css.js.map +1 -1
  182. package/styles/grid/grid.css.js +1 -1
  183. package/styles/grid/grid.css.js.map +1 -1
  184. package/styles/group-header-row/group-header-row.css.js +1 -1
  185. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  186. package/styles/header-cell/header-cell.css.js +1 -1
  187. package/styles/header-cell/header-cell.css.js.map +1 -1
  188. package/styles/header-row/header-row.base.css.js +1 -1
  189. package/styles/header-row/header-row.base.css.js.map +1 -1
  190. package/styles/paginator/paginator.css.js +1 -1
  191. package/styles/paginator/paginator.css.js.map +1 -1
  192. package/styles/toolbar/toolbar.css.js +1 -1
  193. package/styles/toolbar/toolbar.css.js.map +1 -1
  194. package/tokens.css +78 -0
  195. package/LICENSE +0 -80
  196. package/docs/media/LICENSE +0 -80
  197. package/docs/media/_tokens.scss +0 -214
@@ -46,13 +46,30 @@ export default class ApexGridCell<T extends object> extends LitElement {
46
46
  * auto-rendered selection + expansion columns ahead of the data columns.
47
47
  */
48
48
  colindex: number;
49
+ /**
50
+ * The parent row's view index, set by the row. Changing it re-decorates the
51
+ * cell (decoration such as a range selection is positional), so a row that
52
+ * moves with the same data (sort, filter, an expand above it) never keeps a
53
+ * stale highlight.
54
+ *
55
+ * @internal
56
+ */
57
+ rowIndex: number;
49
58
  /**
50
59
  * Reactive token forwarded from {@link StateController.decorationVersion}.
51
- * Changing it re-runs {@link willUpdate} so module-driven cell decoration is
52
- * re-evaluated when it changes (e.g. while dragging a selection range). Stays
53
- * `0` for the community grid, so cells are never re-decorated there.
60
+ * Changing it re-applies module-driven cell decoration (e.g. while dragging a
61
+ * selection range) without re-rendering the cell content. Stays `0` for the
62
+ * community grid, so cells are never re-decorated there.
54
63
  */
55
64
  decorationVersion: number;
65
+ /**
66
+ * Reactive token forwarded from {@link StateController.dataVersion}, bumped
67
+ * once per pipeline run. Re-renders only a cell that reads its row beyond
68
+ * its own value (see {@link shouldUpdate}).
69
+ *
70
+ * @internal
71
+ */
72
+ dataVersion: number;
56
73
  /**
57
74
  * Reactive token forwarded from {@link EditingController.validationVersion}.
58
75
  * Changing it re-runs {@link willUpdate} so the cell re-reads its validation
@@ -62,7 +79,16 @@ export default class ApexGridCell<T extends object> extends LitElement {
62
79
  validationVersion: number;
63
80
  connectedCallback(): void;
64
81
  disconnectedCallback(): void;
65
- protected willUpdate(): void;
82
+ /**
83
+ * Skips the render when only positional or decoration tokens changed: the
84
+ * decoration is host attributes, so it is reconciled here and the cell
85
+ * template (type renderers, user templates) is not re-run. A cell that reads
86
+ * its row beyond {@link value} (a `cellTemplate`, which may read other fields
87
+ * or the row index, or the tree group cell, whose chevron shows the row's
88
+ * expansion) still renders when the row index or the data token moves.
89
+ */
90
+ protected shouldUpdate(changed: PropertyValues<this>): boolean;
91
+ protected willUpdate(changed: PropertyValues<this>): void;
66
92
  /**
67
93
  * The inline error node surfaced when the cell is invalid. `role="alert"`
68
94
  * announces the message when it appears, covering the cross-shadow gap where
@@ -11,7 +11,7 @@ import { property, query } from 'lit/decorators.js';
11
11
  import { gridStateContext } from '../controllers/state.js';
12
12
  import { getColumnTypeRenderer } from '../internal/column-types.js';
13
13
  import { registerComponent } from '../internal/register.js';
14
- import { GRID_CELL_TAG } from '../internal/tags.js';
14
+ import { GRID_CELL_TAG, GRID_ROW_TAG } from '../internal/tags.js';
15
15
  import { styles } from '../styles/body-cell/body-cell.css.js';
16
16
  function isErrorLike(value) {
17
17
  return (typeof value === 'object' &&
@@ -19,16 +19,64 @@ function isErrorLike(value) {
19
19
  typeof value.code === 'string' &&
20
20
  value.code.startsWith('#'));
21
21
  }
22
+ function renderedRows(body) {
23
+ if (!body)
24
+ return [];
25
+ return Array.from(body.children).filter((element) => element.localName === GRID_ROW_TAG);
26
+ }
27
+ function tabTarget(count, from, keys, key, step, isFullWidth) {
28
+ const column = keys[keys.indexOf(key) + step];
29
+ if (column !== undefined)
30
+ return { row: from, column };
31
+ const wrapColumn = keys[step > 0 ? 0 : keys.length - 1];
32
+ for (let index = from + step; index >= 0 && index < count; index += step) {
33
+ if (!isFullWidth(index))
34
+ return { row: index, column: wrapColumn };
35
+ }
36
+ return null;
37
+ }
38
+ function nearestCellRow(count, start, step, isFullWidth) {
39
+ const origin = Math.min(Math.max(start, 0), count - 1);
40
+ for (let distance = 0; distance < count; distance++) {
41
+ for (const index of [origin + distance * step, origin - distance * step]) {
42
+ if (index >= 0 && index < count && !isFullWidth(index))
43
+ return index;
44
+ }
45
+ }
46
+ return -1;
47
+ }
48
+ function findRenderedCell(body, node) {
49
+ return renderedRows(body)
50
+ .find((row) => row.index === node.row)
51
+ ?.cells.find((cell) => cell.column.key === node.column);
52
+ }
53
+ function scrollRowIntoRange(scroller, body, index) {
54
+ const rows = renderedRows(body).sort((a, b) => a.index - b.index);
55
+ const first = rows.at(0);
56
+ const last = rows.at(-1);
57
+ if (!first || !last)
58
+ return;
59
+ const top = first.getBoundingClientRect().top;
60
+ const height = (last.getBoundingClientRect().bottom - top) / (last.index - first.index + 1);
61
+ const view = scroller.getBoundingClientRect();
62
+ const rowCentre = top + (index - first.index + 0.5) * height;
63
+ scroller.scrollTop += rowCentre - (view.top + scroller.clientHeight / 2);
64
+ }
22
65
  class ApexGridCell extends LitElement {
23
66
  constructor() {
24
67
  super(...arguments);
25
68
  this.active = false;
26
69
  this.editing = false;
27
70
  this.colindex = 0;
71
+ this.rowIndex = -1;
28
72
  this.decorationVersion = 0;
73
+ this.dataVersion = 0;
29
74
  this.validationVersion = 0;
30
75
  this.#decoratedKeys = [];
31
76
  this.#errors = null;
77
+ this.#staged = false;
78
+ this.#resumed = null;
79
+ this.#reconnected = false;
32
80
  this.#handleClick = (event) => {
33
81
  if (this.isEditing) {
34
82
  event.stopPropagation();
@@ -56,9 +104,7 @@ class ApexGridCell extends LitElement {
56
104
  this.#startEdit();
57
105
  };
58
106
  this.#refocusAfterEdit = false;
59
- this.#commitWith = async (value) => {
60
- return this.editingController.commitCell(value ?? this.#pendingValue);
61
- };
107
+ this.#commit = () => this.editingController.commitCell();
62
108
  this.#cancel = () => {
63
109
  this.editingController.cancelCell();
64
110
  };
@@ -68,7 +114,7 @@ class ApexGridCell extends LitElement {
68
114
  case 'Enter':
69
115
  event.preventDefault();
70
116
  this.#refocusAfterEdit = true;
71
- this.#commitWith();
117
+ this.#commit();
72
118
  return;
73
119
  case 'Escape':
74
120
  event.preventDefault();
@@ -76,24 +122,12 @@ class ApexGridCell extends LitElement {
76
122
  this.#cancel();
77
123
  return;
78
124
  case 'Tab':
79
- event.preventDefault();
80
- this.#refocusAfterEdit = true;
81
- this.#commitWith();
125
+ this.#commitAndTab(event);
82
126
  return;
83
127
  default:
84
128
  return;
85
129
  }
86
130
  };
87
- this.#handleBlur = () => {
88
- if (!this.isEditing)
89
- return;
90
- if (this.editingController.mode === 'cell') {
91
- this.#commitWith();
92
- }
93
- else {
94
- this.editingController.commitCell(this.#pendingValue);
95
- }
96
- };
97
131
  this.#handleCellKeydown = (event) => {
98
132
  if (!this.isEditing)
99
133
  return;
@@ -103,11 +137,8 @@ class ApexGridCell extends LitElement {
103
137
  this.#cancel();
104
138
  return;
105
139
  }
106
- if (event.key === 'Tab') {
107
- event.preventDefault();
108
- this.#refocusAfterEdit = true;
109
- this.#commitWith();
110
- }
140
+ if (event.key === 'Tab')
141
+ this.#commitAndTab(event);
111
142
  };
112
143
  this.#handleCellFocusOut = (event) => {
113
144
  if (!this.isEditing)
@@ -115,26 +146,23 @@ class ApexGridCell extends LitElement {
115
146
  const next = event.relatedTarget;
116
147
  if (next && (this.contains(next) || this.shadowRoot?.contains(next)))
117
148
  return;
118
- if (this.editingController.mode === 'cell') {
119
- this.#commitWith();
120
- }
121
- else {
122
- this.editingController.commitCell(this.#pendingValue);
123
- }
149
+ this.#refocusAfterEdit = false;
150
+ this.#commit();
124
151
  };
125
152
  this.#handleTextInput = (event) => {
126
153
  const target = event.target;
127
- if (this.column.type === 'number') {
154
+ if (this.#isNumeric) {
128
155
  const numeric = target.valueAsNumber;
129
- this.#pendingValue = target.value === '' || Number.isNaN(numeric) ? null : numeric;
156
+ this.editingController.setDraft(target.value === '' || Number.isNaN(numeric) ? null : numeric);
130
157
  return;
131
158
  }
132
- this.#pendingValue = target.value;
159
+ this.editingController.setDraft(target.value);
133
160
  };
134
- this.#handleCheckboxChange = (event) => {
161
+ this.#handleCheckboxChange = async (event) => {
135
162
  const target = event.target;
136
- this.#pendingValue = target.checked;
137
- this.#commitWith(target.checked);
163
+ if (!(await this.editingController.commitCell(target.checked))) {
164
+ target.checked = !target.checked;
165
+ }
138
166
  };
139
167
  }
140
168
  static get tagName() {
@@ -152,7 +180,7 @@ class ApexGridCell extends LitElement {
152
180
  value: this.value,
153
181
  };
154
182
  if (this.isEditable) {
155
- ctx.commit = (next) => this.editingController.commitImmediate(this.row.index, this.column.key, next);
183
+ ctx.commit = (next) => this.editingController.commitImmediate(this.row.data, this.column.key, next);
156
184
  }
157
185
  return ctx;
158
186
  }
@@ -167,10 +195,18 @@ class ApexGridCell extends LitElement {
167
195
  return false;
168
196
  return this.editingController.isEditable(this.column);
169
197
  }
198
+ get #inPinnedBand() {
199
+ const pin = this.state.rowPin;
200
+ return pin.hasPinnedRows && pin.enabled && pin.isPinned(this.row.data);
201
+ }
170
202
  #decoratedKeys;
171
203
  static #ERROR_ID = 'apex-cell-error';
172
204
  #errors;
205
+ #staged;
206
+ #resumed;
207
+ #reconnected;
173
208
  connectedCallback() {
209
+ this.#reconnected = this.hasUpdated && !this.isUpdatePending;
174
210
  super.connectedCallback();
175
211
  this.setAttribute('role', 'gridcell');
176
212
  this.addEventListener('click', this.#handleClick);
@@ -185,7 +221,29 @@ class ApexGridCell extends LitElement {
185
221
  this.removeEventListener('focusout', this.#handleCellFocusOut);
186
222
  super.disconnectedCallback();
187
223
  }
188
- willUpdate() {
224
+ shouldUpdate(changed) {
225
+ const reconnected = this.#reconnected;
226
+ this.#reconnected = false;
227
+ if (!this.hasUpdated || (!changed.size && !reconnected))
228
+ return true;
229
+ const tree = this.state?.tree;
230
+ const readsRow = this.column.cellTemplate || (tree?.enabled && tree.isGroupColumn(this.column.key));
231
+ for (const key of changed.keys()) {
232
+ const token = key === 'rowIndex' || key === 'dataVersion';
233
+ if (key !== 'decorationVersion' && (readsRow || !token))
234
+ return true;
235
+ }
236
+ if (changed.has('decorationVersion') || changed.has('rowIndex'))
237
+ this.#applyDecoration();
238
+ return false;
239
+ }
240
+ willUpdate(changed) {
241
+ this.#applyStaged();
242
+ if (changed.has('editing')) {
243
+ this.#resumed = this.editing
244
+ ? this.editingController.heldDraft(this.row.data, this.column.key)
245
+ : null;
246
+ }
189
247
  if (this.colindex > 0) {
190
248
  this.setAttribute('aria-colindex', String(this.colindex));
191
249
  }
@@ -210,6 +268,20 @@ class ApexGridCell extends LitElement {
210
268
  this.#applyValidation();
211
269
  this.#applyDecoration();
212
270
  }
271
+ #applyStaged() {
272
+ const staged = this.state?.editing?.activeRow
273
+ ? this.editingController.stagedValues(this.row.data)
274
+ : null;
275
+ const key = this.column.key;
276
+ if (staged?.has(key)) {
277
+ this.value = staged.get(key);
278
+ this.#staged = true;
279
+ }
280
+ else if (this.#staged) {
281
+ this.#staged = false;
282
+ this.value = this.row.data[key];
283
+ }
284
+ }
213
285
  #applyValidation() {
214
286
  const errors = this.state && this.row?.data
215
287
  ? (this.editingController?.getCellErrors(this.row.data, this.column.key) ?? null)
@@ -242,7 +314,7 @@ class ApexGridCell extends LitElement {
242
314
  const decoration = this.state && this.row
243
315
  ? this.state.decorateCell({
244
316
  row: this.row.data,
245
- rowIndex: this.row.index,
317
+ rowIndex: this.#inPinnedBand ? -1 : this.rowIndex,
246
318
  column: this.column,
247
319
  value: this.value,
248
320
  })
@@ -266,6 +338,8 @@ class ApexGridCell extends LitElement {
266
338
  }
267
339
  updated(changed) {
268
340
  if (this.isEditing) {
341
+ if (!changed.has('editing'))
342
+ return;
269
343
  const editor = this.editorElement;
270
344
  editor?.focus();
271
345
  if (editor instanceof HTMLInputElement) {
@@ -285,22 +359,94 @@ class ApexGridCell extends LitElement {
285
359
  #startEdit() {
286
360
  if (!this.row)
287
361
  return;
288
- this.editingController.editCell(this.row.index, this.column.key);
362
+ this.editingController.editRecord(this.row.data, this.column.key);
289
363
  }
290
- #pendingValue;
291
364
  #refocusAfterEdit;
292
- #commitWith;
365
+ #commit;
293
366
  #cancel;
367
+ #commitAndTab(event) {
368
+ event.preventDefault();
369
+ if (this.#inPinnedBand) {
370
+ this.#refocusAfterEdit = true;
371
+ void this.#commit();
372
+ return;
373
+ }
374
+ const step = event.shiftKey ? -1 : 1;
375
+ const host = this.state.host;
376
+ const body = this.row.parentElement;
377
+ const record = this.row.data;
378
+ const origin = this.row.index;
379
+ const key = this.column.key;
380
+ const columns = this.row.columns;
381
+ const keys = this.row.cells.map((cell) => cell.column.key);
382
+ const settle = async () => {
383
+ await host.updateComplete;
384
+ await body?.updateComplete;
385
+ await Promise.all(renderedRows(body).map((row) => row.updateComplete));
386
+ };
387
+ void this.#commit().then(async (committed) => {
388
+ if (!committed)
389
+ return;
390
+ await settle();
391
+ const items = host.pageItems;
392
+ const isFullWidth = (index) => items[index] == null ||
393
+ this.state.presentRow(items[index], { columns, rowIndex: index }) !== null;
394
+ const from = items.indexOf(record);
395
+ let target;
396
+ if (from >= 0) {
397
+ target = tabTarget(items.length, from, keys, key, step, isFullWidth) ?? {
398
+ row: from,
399
+ column: key,
400
+ };
401
+ }
402
+ else {
403
+ const row = nearestCellRow(items.length, origin, step, isFullWidth);
404
+ target = row < 0 ? null : { row, column: key };
405
+ }
406
+ if (!target) {
407
+ body?.focus({ preventScroll: true });
408
+ return;
409
+ }
410
+ this.state.active = target;
411
+ await settle();
412
+ let cell = findRenderedCell(body, target);
413
+ for (let attempt = 0; !cell && attempt < 3; attempt++) {
414
+ scrollRowIntoRange(host, body, target.row);
415
+ await new Promise(requestAnimationFrame);
416
+ await new Promise(requestAnimationFrame);
417
+ await settle();
418
+ cell = findRenderedCell(body, target);
419
+ }
420
+ if (!cell) {
421
+ body?.focus({ preventScroll: true });
422
+ return;
423
+ }
424
+ cell.focus({ preventScroll: true });
425
+ cell.scrollIntoView({ block: 'nearest', inline: 'nearest' });
426
+ await new Promise(requestAnimationFrame);
427
+ await new Promise(requestAnimationFrame);
428
+ const active = this.state.active;
429
+ const dropped = !document.activeElement || document.activeElement === document.body;
430
+ if (dropped && active.row === target.row && active.column === target.column) {
431
+ findRenderedCell(body, target)?.focus({ preventScroll: true });
432
+ }
433
+ });
434
+ }
294
435
  #handleKeydown;
295
- #handleBlur;
296
436
  #handleCellKeydown;
297
437
  #handleCellFocusOut;
438
+ get #isNumeric() {
439
+ const type = this.column.type;
440
+ return type === 'number' || type === 'currency';
441
+ }
298
442
  #handleTextInput;
299
443
  #handleCheckboxChange;
444
+ get #editorValue() {
445
+ return this.#resumed ? this.#resumed.value : this.value;
446
+ }
300
447
  renderDefaultEditor() {
301
- const value = this.value;
448
+ const value = this.#editorValue;
302
449
  if (this.column.type === 'boolean') {
303
- this.#pendingValue = Boolean(value);
304
450
  return html `<input
305
451
  type="checkbox"
306
452
  part="editor"
@@ -308,11 +454,9 @@ class ApexGridCell extends LitElement {
308
454
  ?checked=${Boolean(value)}
309
455
  @change=${this.#handleCheckboxChange}
310
456
  @keydown=${this.#handleKeydown}
311
- @blur=${this.#handleBlur}
312
457
  />`;
313
458
  }
314
- if (this.column.type === 'number') {
315
- this.#pendingValue = value;
459
+ if (this.#isNumeric) {
316
460
  return html `<input
317
461
  type="number"
318
462
  part="editor"
@@ -320,10 +464,8 @@ class ApexGridCell extends LitElement {
320
464
  .value=${value == null ? '' : String(value)}
321
465
  @input=${this.#handleTextInput}
322
466
  @keydown=${this.#handleKeydown}
323
- @blur=${this.#handleBlur}
324
467
  />`;
325
468
  }
326
- this.#pendingValue = value;
327
469
  return html `<input
328
470
  type="text"
329
471
  part="editor"
@@ -331,7 +473,6 @@ class ApexGridCell extends LitElement {
331
473
  .value=${value == null ? '' : String(value)}
332
474
  @input=${this.#handleTextInput}
333
475
  @keydown=${this.#handleKeydown}
334
- @blur=${this.#handleBlur}
335
476
  />`;
336
477
  }
337
478
  renderEditor() {
@@ -340,17 +481,16 @@ class ApexGridCell extends LitElement {
340
481
  parent: this,
341
482
  row: this.row,
342
483
  column: this.column,
343
- value: this.value,
484
+ value: this.#editorValue,
344
485
  commit: (next) => this.editingController.commitCell(next),
345
486
  cancel: () => this.editingController.cancelCell(),
487
+ setDraft: (next) => this.editingController.setDraft(next),
346
488
  };
347
489
  if (template) {
348
- this.#pendingValue = this.value;
349
490
  return template(ctx);
350
491
  }
351
492
  const typeRenderer = getColumnTypeRenderer(this.column.type);
352
493
  if (typeRenderer?.editor) {
353
- this.#pendingValue = this.value;
354
494
  return typeRenderer.editor(ctx);
355
495
  }
356
496
  return this.renderDefaultEditor();
@@ -470,9 +610,15 @@ __decorate([
470
610
  __decorate([
471
611
  property({ attribute: false, type: Number })
472
612
  ], ApexGridCell.prototype, "colindex", void 0);
613
+ __decorate([
614
+ property({ attribute: false })
615
+ ], ApexGridCell.prototype, "rowIndex", void 0);
473
616
  __decorate([
474
617
  property({ attribute: false, type: Number })
475
618
  ], ApexGridCell.prototype, "decorationVersion", void 0);
619
+ __decorate([
620
+ property({ attribute: false })
621
+ ], ApexGridCell.prototype, "dataVersion", void 0);
476
622
  __decorate([
477
623
  property({ attribute: false, type: Number })
478
624
  ], ApexGridCell.prototype, "validationVersion", void 0);