apex-grid 3.2.0 → 3.4.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 (205) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +229 -52
  3. package/components/cell.d.ts +16 -3
  4. package/components/cell.js +78 -8
  5. package/components/cell.js.map +1 -1
  6. package/components/filter-row.d.ts +3 -6
  7. package/components/filter-row.js +144 -115
  8. package/components/filter-row.js.map +1 -1
  9. package/components/grid.d.ts +421 -77
  10. package/components/grid.js +432 -13
  11. package/components/grid.js.map +1 -1
  12. package/components/group-header-row.d.ts +34 -0
  13. package/components/group-header-row.js +125 -0
  14. package/components/group-header-row.js.map +1 -0
  15. package/components/header-row.d.ts +13 -2
  16. package/components/header-row.js +33 -18
  17. package/components/header-row.js.map +1 -1
  18. package/components/header.d.ts +11 -0
  19. package/components/header.js +209 -8
  20. package/components/header.js.map +1 -1
  21. package/components/paginator.d.ts +6 -1
  22. package/components/paginator.js +17 -11
  23. package/components/paginator.js.map +1 -1
  24. package/components/row.d.ts +28 -0
  25. package/components/row.js +100 -3
  26. package/components/row.js.map +1 -1
  27. package/components/toolbar.d.ts +8 -1
  28. package/components/toolbar.js +25 -7
  29. package/components/toolbar.js.map +1 -1
  30. package/controllers/data-operation.js +1 -0
  31. package/controllers/data-operation.js.map +1 -1
  32. package/controllers/dom.d.ts +24 -6
  33. package/controllers/dom.js +62 -5
  34. package/controllers/dom.js.map +1 -1
  35. package/controllers/editing.d.ts +68 -1
  36. package/controllers/editing.js +129 -44
  37. package/controllers/editing.js.map +1 -1
  38. package/controllers/expansion.js +4 -4
  39. package/controllers/expansion.js.map +1 -1
  40. package/controllers/filter.d.ts +1 -1
  41. package/controllers/filter.js +27 -5
  42. package/controllers/filter.js.map +1 -1
  43. package/controllers/history.d.ts +86 -0
  44. package/controllers/history.js +125 -0
  45. package/controllers/history.js.map +1 -0
  46. package/controllers/navigation.d.ts +49 -2
  47. package/controllers/navigation.js +145 -3
  48. package/controllers/navigation.js.map +1 -1
  49. package/controllers/pagination.js +1 -1
  50. package/controllers/pagination.js.map +1 -1
  51. package/controllers/reorder.js +3 -1
  52. package/controllers/reorder.js.map +1 -1
  53. package/controllers/row-pin.d.ts +59 -0
  54. package/controllers/row-pin.js +75 -0
  55. package/controllers/row-pin.js.map +1 -0
  56. package/controllers/row-reorder.d.ts +161 -0
  57. package/controllers/row-reorder.js +332 -0
  58. package/controllers/row-reorder.js.map +1 -0
  59. package/controllers/selection.js +3 -3
  60. package/controllers/selection.js.map +1 -1
  61. package/controllers/sort.js +13 -3
  62. package/controllers/sort.js.map +1 -1
  63. package/controllers/state.d.ts +33 -0
  64. package/controllers/state.js +37 -2
  65. package/controllers/state.js.map +1 -1
  66. package/controllers/tree.d.ts +8 -0
  67. package/controllers/tree.js +0 -0
  68. package/controllers/tree.js.map +1 -1
  69. package/custom-elements.json +4667 -926
  70. package/docs/assets/hierarchy.js +1 -1
  71. package/docs/assets/highlight.css +28 -0
  72. package/docs/assets/navigation.js +1 -1
  73. package/docs/assets/search.js +1 -1
  74. package/docs/functions/custom.html +3 -0
  75. package/docs/functions/interpolate.html +4 -0
  76. package/docs/functions/localize.html +8 -0
  77. package/docs/functions/max.html +3 -0
  78. package/docs/functions/min.html +3 -0
  79. package/docs/functions/pattern.html +3 -0
  80. package/docs/functions/required.html +3 -0
  81. package/docs/hierarchy.html +1 -1
  82. package/docs/index.html +245 -54
  83. package/docs/interfaces/ApexCellValidationFailedEvent.html +13 -0
  84. package/docs/interfaces/ApexCellValueChangedEvent.html +1 -1
  85. package/docs/interfaces/ApexCellValueChangingEvent.html +2 -2
  86. package/docs/interfaces/ApexColumnGroupContext.html +6 -0
  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 +37 -9
  91. package/docs/interfaces/ApexHistoryChangedEvent.html +6 -0
  92. package/docs/interfaces/ApexPageChangedEvent.html +1 -1
  93. package/docs/interfaces/ApexRowMovingEvent.html +9 -0
  94. package/docs/interfaces/ApexRowPinningEvent.html +8 -0
  95. package/docs/interfaces/ApexStateChangedEvent.html +4 -0
  96. package/docs/interfaces/BaseApexCellContext.html +1 -1
  97. package/docs/interfaces/BaseApexEditorContext.html +1 -1
  98. package/docs/interfaces/BaseColumnConfiguration.html +42 -17
  99. package/docs/interfaces/CSVExportOptions.html +2 -2
  100. package/docs/interfaces/ColumnGroupConfiguration.html +12 -0
  101. package/docs/interfaces/ColumnLayoutState.html +2 -0
  102. package/docs/interfaces/ColumnSchema.html +22 -0
  103. package/docs/interfaces/DataPipelineConfiguration.html +2 -2
  104. package/docs/interfaces/EditingHistoryConfiguration.html +10 -0
  105. package/docs/interfaces/ExportFormat.html +2 -2
  106. package/docs/interfaces/ExportOptions.html +1 -1
  107. package/docs/interfaces/FilterStateSnapshot.html +4 -0
  108. package/docs/interfaces/GetStateOptions.html +4 -0
  109. package/docs/interfaces/GridCapabilities.html +8 -0
  110. package/docs/interfaces/GridEditingConfiguration.html +4 -2
  111. package/docs/interfaces/GridRowPinningConfiguration.html +5 -0
  112. package/docs/interfaces/GridRowReorderingConfiguration.html +22 -0
  113. package/docs/interfaces/GridSchema.html +19 -0
  114. package/docs/interfaces/GridState.html +41 -0
  115. package/docs/interfaces/GridTreeConfiguration.html +2 -2
  116. package/docs/interfaces/PaginationConfiguration.html +1 -1
  117. package/docs/interfaces/PaginationState.html +1 -1
  118. package/docs/interfaces/SetStateOptions.html +9 -0
  119. package/docs/interfaces/SetStateResult.html +11 -0
  120. package/docs/interfaces/SortStateSnapshot.html +2 -0
  121. package/docs/interfaces/ValidatorContext.html +8 -0
  122. package/docs/media/_tokens.scss +70 -81
  123. package/docs/media/package.json +2 -2
  124. package/docs/media/state-persistence.html +189 -0
  125. package/docs/modules.html +1 -1
  126. package/docs/types/DataPipelineParams.html +2 -2
  127. package/docs/types/EditMode.html +2 -2
  128. package/docs/types/GridLocaleKey.html +3 -0
  129. package/docs/types/GridLocaleText.html +5 -0
  130. package/docs/types/LocaleParams.html +2 -0
  131. package/docs/types/PaginationMode.html +1 -1
  132. package/docs/types/RowDropPosition.html +2 -0
  133. package/docs/types/RowPinPosition.html +2 -0
  134. package/docs/types/RowRef.html +3 -0
  135. package/docs/types/Validator.html +4 -0
  136. package/docs/variables/EN_LOCALE.html +9 -0
  137. package/docs/variables/esLocale.html +8 -0
  138. package/i18n/en.d.ts +245 -0
  139. package/i18n/en.js +219 -0
  140. package/i18n/en.js.map +1 -0
  141. package/i18n/es.d.ts +18 -0
  142. package/i18n/es.js +219 -0
  143. package/i18n/es.js.map +1 -0
  144. package/i18n/index.d.ts +3 -0
  145. package/i18n/index.js +4 -0
  146. package/i18n/index.js.map +1 -0
  147. package/i18n/localize.d.ts +23 -0
  148. package/i18n/localize.js +15 -0
  149. package/i18n/localize.js.map +1 -0
  150. package/index.d.ts +8 -2
  151. package/index.js +2 -0
  152. package/index.js.map +1 -1
  153. package/internal/column-types.js +1 -1
  154. package/internal/column-types.js.map +1 -1
  155. package/internal/feature-module.d.ts +46 -0
  156. package/internal/feature-module.js +11 -0
  157. package/internal/feature-module.js.map +1 -1
  158. package/internal/flip.js +1 -1
  159. package/internal/flip.js.map +1 -1
  160. package/internal/icons.d.ts +9 -7
  161. package/internal/icons.js +75 -35
  162. package/internal/icons.js.map +1 -1
  163. package/internal/index.d.ts +5 -3
  164. package/internal/index.js +2 -1
  165. package/internal/index.js.map +1 -1
  166. package/internal/state-schema.d.ts +85 -0
  167. package/internal/state-schema.js +27 -0
  168. package/internal/state-schema.js.map +1 -0
  169. package/internal/state-snapshot.d.ts +152 -0
  170. package/internal/state-snapshot.js +115 -0
  171. package/internal/state-snapshot.js.map +1 -0
  172. package/internal/tags.d.ts +1 -0
  173. package/internal/tags.js +1 -0
  174. package/internal/tags.js.map +1 -1
  175. package/internal/types.d.ts +162 -2
  176. package/internal/types.js.map +1 -1
  177. package/internal/utils.d.ts +12 -0
  178. package/internal/utils.js +17 -0
  179. package/internal/utils.js.map +1 -1
  180. package/internal/validators.d.ts +31 -0
  181. package/internal/validators.js +44 -0
  182. package/internal/validators.js.map +1 -0
  183. package/package.json +1 -1
  184. package/styles/_tokens.css.js +1 -1
  185. package/styles/_tokens.css.js.map +1 -1
  186. package/styles/body-cell/body-cell.css.js +1 -1
  187. package/styles/body-cell/body-cell.css.js.map +1 -1
  188. package/styles/body-row/body-row.css.js +1 -1
  189. package/styles/body-row/body-row.css.js.map +1 -1
  190. package/styles/filter-row/filter-row.css.js +1 -1
  191. package/styles/filter-row/filter-row.css.js.map +1 -1
  192. package/styles/grid/_grid.base.css.js +1 -1
  193. package/styles/grid/_grid.base.css.js.map +1 -1
  194. package/styles/grid/grid.css.js +1 -1
  195. package/styles/grid/grid.css.js.map +1 -1
  196. package/styles/group-header-row/group-header-row.css.d.ts +1 -0
  197. package/styles/group-header-row/group-header-row.css.js +3 -0
  198. package/styles/group-header-row/group-header-row.css.js.map +1 -0
  199. package/styles/header-cell/header-cell.css.js +1 -1
  200. package/styles/header-cell/header-cell.css.js.map +1 -1
  201. package/styles/header-row/header-row.base.css.js +1 -1
  202. package/styles/header-row/header-row.base.css.js.map +1 -1
  203. package/styles/toolbar/toolbar.css.js +1 -1
  204. package/styles/toolbar/toolbar.css.js.map +1 -1
  205. package/docs/classes/ApexGrid.html +0 -399
@@ -1,5 +1,6 @@
1
1
  import { html } from 'lit';
2
2
  import { styleMap } from 'lit/directives/style-map.js';
3
+ import { SENTINEL_NODE } from '../internal/constants.js';
3
4
  import { applyColumnWidths, getDisplayColumns } from '../internal/utils.js';
4
5
  export class GridDOMController {
5
6
  constructor(host, state) {
@@ -21,8 +22,7 @@ export class GridDOMController {
21
22
  this.rowRenderer = (data, index) => {
22
23
  const editingCell = this.state.editing.activeCell;
23
24
  const editingKey = editingCell?.rowIndex === index ? editingCell.columnKey : null;
24
- const hasFilter = this.host.columns.some((column) => column.filter);
25
- const ariaRowOffset = hasFilter ? 2 : 1;
25
+ const ariaRowOffset = this.headerRowCount + this.state.rowPin.pinnedRows.top.length;
26
26
  return html `
27
27
  <apex-grid-row
28
28
  part="row"
@@ -35,12 +35,19 @@ export class GridDOMController {
35
35
  .editingKey=${editingKey}
36
36
  .ariaRowOffset=${ariaRowOffset}
37
37
  .decorationVersion=${this.state.decorationVersion}
38
+ .validationVersion=${this.state.editing.validationVersion}
39
+ .dragging=${this.state.rowReorder.dragging === data}
40
+ .grabbed=${this.state.rowReorder.grabbed === data}
41
+ .coordinateHints=${Boolean(this.host.coordinateHints)}
38
42
  >
39
43
  </apex-grid-row>
40
44
  `;
41
45
  };
46
+ this.#lastGroupTop = -1;
42
47
  this.#lastHeaderTop = -1;
43
48
  this.#lastFilterTop = -1;
49
+ this.#lastPinnedTop = -1;
50
+ this.#lastPinnedBottom = -1;
44
51
  this.#lastHostWidth = -1;
45
52
  this.#hostWidthObserver = null;
46
53
  this.host.addController(this);
@@ -56,6 +63,28 @@ export class GridDOMController {
56
63
  get displayColumns() {
57
64
  return getDisplayColumns(this.host.columns);
58
65
  }
66
+ get headerRowCount() {
67
+ return this.host.columnGroupDepth + 1;
68
+ }
69
+ pinnedRowRenderer(data, localIndex, ariaRowOffset) {
70
+ return html `
71
+ <apex-grid-row
72
+ part="row pinned-row"
73
+ style=${styleMap(this.columnSizes)}
74
+ .index=${localIndex}
75
+ .activeNode=${SENTINEL_NODE}
76
+ .data=${data}
77
+ .columns=${this.displayColumns}
78
+ .pinOffsets=${this.pinOffsets}
79
+ .editingKey=${null}
80
+ .ariaRowOffset=${ariaRowOffset}
81
+ .decorationVersion=${this.state.decorationVersion}
82
+ .validationVersion=${this.state.editing.validationVersion}
83
+ .coordinateHints=${Boolean(this.host.coordinateHints)}
84
+ >
85
+ </apex-grid-row>
86
+ `;
87
+ }
59
88
  async hostConnected() {
60
89
  this.setGridColumnSizes();
61
90
  await this.host.updateComplete;
@@ -103,6 +132,8 @@ export class GridDOMController {
103
132
  this.columnSizes = applyColumnWidths(this.displayColumns, {
104
133
  showSelectionColumn: this.state.selection.showCheckboxColumn,
105
134
  showExpansionColumn: this.state.expansion.showToggleColumn,
135
+ showReorderHandle: this.state.rowReorder.showHandleColumn,
136
+ showRowNumbers: Boolean(this.host.coordinateHints),
106
137
  });
107
138
  }
108
139
  getActiveRowStyles(index) {
@@ -164,8 +195,11 @@ export class GridDOMController {
164
195
  }
165
196
  this.#observedHeaders = next;
166
197
  }
198
+ #lastGroupTop;
167
199
  #lastHeaderTop;
168
200
  #lastFilterTop;
201
+ #lastPinnedTop;
202
+ #lastPinnedBottom;
169
203
  #lastHostWidth;
170
204
  #hostWidthObserver;
171
205
  recomputeStickyRowOffsets() {
@@ -173,11 +207,23 @@ export class GridDOMController {
173
207
  if (!root)
174
208
  return;
175
209
  const toolbar = root.querySelector('apex-grid-toolbar');
210
+ const groupHeader = root.querySelector('apex-grid-group-header-row');
176
211
  const header = root.querySelector('apex-grid-header-row');
212
+ const filter = root.querySelector('apex-filter-row');
213
+ const paginator = root.querySelector('apex-grid-paginator');
177
214
  const toolbarH = toolbar?.offsetHeight ?? 0;
215
+ const groupH = groupHeader?.offsetHeight ?? 0;
178
216
  const headerH = header?.offsetHeight ?? 0;
179
- const headerTop = toolbarH;
180
- const filterTop = toolbarH + headerH;
217
+ const filterH = filter?.offsetHeight ?? 0;
218
+ const paginatorH = paginator?.offsetHeight ?? 0;
219
+ const groupTop = toolbarH;
220
+ const headerTop = toolbarH + groupH;
221
+ const filterTop = toolbarH + groupH + headerH;
222
+ const pinnedTop = toolbarH + groupH + headerH + filterH;
223
+ if (groupTop !== this.#lastGroupTop) {
224
+ this.host.style.setProperty('--apex-row-top-group', `${groupTop}px`);
225
+ this.#lastGroupTop = groupTop;
226
+ }
181
227
  if (headerTop !== this.#lastHeaderTop) {
182
228
  this.host.style.setProperty('--apex-row-top-header', `${headerTop}px`);
183
229
  this.#lastHeaderTop = headerTop;
@@ -186,19 +232,30 @@ export class GridDOMController {
186
232
  this.host.style.setProperty('--apex-row-top-filter', `${filterTop}px`);
187
233
  this.#lastFilterTop = filterTop;
188
234
  }
235
+ if (pinnedTop !== this.#lastPinnedTop) {
236
+ this.host.style.setProperty('--apex-row-top-pinned', `${pinnedTop}px`);
237
+ this.#lastPinnedTop = pinnedTop;
238
+ }
239
+ if (paginatorH !== this.#lastPinnedBottom) {
240
+ this.host.style.setProperty('--apex-row-bottom-pinned', `${paginatorH}px`);
241
+ this.#lastPinnedBottom = paginatorH;
242
+ }
189
243
  }
190
244
  observeStickyRows() {
191
245
  const root = this.host.shadowRoot;
192
246
  if (!root)
193
247
  return;
194
248
  const toolbar = root.querySelector('apex-grid-toolbar');
249
+ const groupHeader = root.querySelector('apex-grid-group-header-row');
195
250
  const header = root.querySelector('apex-grid-header-row');
251
+ const filter = root.querySelector('apex-filter-row');
252
+ const paginator = root.querySelector('apex-grid-paginator');
196
253
  if (!this.#stickyRowObserver) {
197
254
  this.#stickyRowObserver = new ResizeObserver(() => {
198
255
  requestAnimationFrame(() => this.recomputeStickyRowOffsets());
199
256
  });
200
257
  }
201
- const next = new Set([toolbar, header].filter((el) => !!el));
258
+ const next = new Set([toolbar, groupHeader, header, filter, paginator].filter((el) => !!el));
202
259
  for (const element of this.#observedStickyRows) {
203
260
  if (!next.has(element))
204
261
  this.#stickyRowObserver.unobserve(element);
@@ -1 +1 @@
1
- {"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/controllers/dom.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RenderItemFunction } from '@lit-labs/virtualizer/virtualize.js';\nimport { html, type ReactiveController } from 'lit';\nimport { type StyleInfo, styleMap } from 'lit/directives/style-map.js';\nimport type { ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\nimport { applyColumnWidths, getDisplayColumns } from '../internal/utils.js';\nimport type { StateController } from './state.js';\n\nexport class GridDOMController<T extends object> implements ReactiveController {\n constructor(\n protected host: GridHost<T>,\n protected state: StateController<T>\n ) {\n this.host.addController(this);\n }\n\n #initialSize = () => {\n setTimeout(() => {\n this.setScrollOffset();\n this.recomputePinOffsets();\n this.recomputeStickyRowOffsets();\n });\n };\n\n #resizeObserver: ResizeObserver | null = null;\n #observedHeaders = new Set<Element>();\n #stickyRowObserver: ResizeObserver | null = null;\n #observedStickyRows = new Set<Element>();\n\n public get container() {\n // @ts-expect-error: protected member access\n return this.host.scrollContainer;\n }\n\n /**\n * Visual render order: `'start'`-pinned columns first, then unpinned, then\n * `'end'`-pinned. The user-supplied `columns` array is never mutated.\n */\n public get displayColumns(): Array<ColumnConfiguration<T>> {\n return getDisplayColumns(this.host.columns);\n }\n\n public columnSizes: StyleInfo = {};\n\n /**\n * The resolved inline-start (for `'start'` pin) or inline-end (for `'end'` pin)\n * pixel offset per pinned column key. Populated after layout by\n * {@link recomputePinOffsets}.\n */\n public pinOffsets = new Map<Keys<T>, number>();\n\n public rowRenderer: RenderItemFunction<T> = (data: T, index: number) => {\n const editingCell = this.state.editing.activeCell;\n const editingKey = editingCell?.rowIndex === index ? editingCell.columnKey : null;\n // aria-rowindex offset: header row is always row 1; filter row (when\n // any column has a filter configured) sits at row 2; body rows start\n // immediately after.\n const hasFilter = this.host.columns.some((column) => column.filter);\n const ariaRowOffset = hasFilter ? 2 : 1;\n return html`\n <apex-grid-row\n part=\"row\"\n style=${styleMap({ ...this.columnSizes, ...this.getActiveRowStyles(index) })}\n .index=${index}\n .activeNode=${this.state.active}\n .data=${data}\n .columns=${this.displayColumns}\n .pinOffsets=${this.pinOffsets}\n .editingKey=${editingKey}\n .ariaRowOffset=${ariaRowOffset}\n .decorationVersion=${this.state.decorationVersion}\n >\n </apex-grid-row>\n `;\n };\n\n public async hostConnected() {\n this.setGridColumnSizes();\n // Wait for the initial paint of the virtualizer and recalculate the scrollbar offset\n // for the next one\n await this.host.updateComplete;\n this.#initialSize();\n if (typeof ResizeObserver !== 'undefined' && !this.#hostWidthObserver) {\n this.#hostWidthObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => this.recomputeHostWidth());\n });\n this.#hostWidthObserver.observe(this.host as HTMLElement);\n }\n this.recomputeHostWidth();\n }\n\n public hostUpdate(): void {\n this.setScrollOffset();\n this.setGridColumnSizes();\n }\n\n public hostUpdated(): void {\n this.recomputePinOffsets();\n this.observeHeaderCells();\n this.recomputeStickyRowOffsets();\n this.observeStickyRows();\n }\n\n public hostDisconnected(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.#observedHeaders.clear();\n this.#stickyRowObserver?.disconnect();\n this.#stickyRowObserver = null;\n this.#observedStickyRows.clear();\n this.#hostWidthObserver?.disconnect();\n this.#hostWidthObserver = null;\n }\n\n /**\n * Writes the grid host's `clientWidth` into `--apex-host-width`. Used by the\n * toolbar and paginator to clamp their width to the visible viewport so\n * horizontal scrolling doesn't drag them off-screen.\n */\n public recomputeHostWidth(): void {\n const host = this.host as HTMLElement;\n const width = host.clientWidth;\n if (width === this.#lastHostWidth) return;\n host.style.setProperty('--apex-host-width', `${width}px`);\n this.#lastHostWidth = width;\n }\n\n public setScrollOffset() {\n // The grid host now owns vertical scrolling, so the historic per-row\n // padding-inline-end compensation is no longer needed. Keep the CSS var\n // set to `0` for forward compatibility with consumer themes that read it.\n this.host.style.setProperty('--scrollbar-offset', '0px');\n }\n\n protected setGridColumnSizes() {\n this.columnSizes = applyColumnWidths(this.displayColumns, {\n showSelectionColumn: this.state.selection.showCheckboxColumn,\n showExpansionColumn: this.state.expansion.showToggleColumn,\n });\n }\n\n public getActiveRowStyles(index: number): StyleInfo {\n const { row } = this.state.active;\n return row === index ? { 'z-index': '3' } : {};\n }\n\n /**\n * Measures the rendered widths of pinned header cells and writes cumulative\n * inline-start / inline-end pixel offsets into {@link pinOffsets}. Called after\n * each grid update and whenever a header cell is resized.\n */\n public recomputePinOffsets(): void {\n // @ts-expect-error - protected member access\n const headerRow = this.host.headerRow as\n | {\n headers?: Array<{\n column: ColumnConfiguration<T>;\n getBoundingClientRect: () => DOMRect;\n }>;\n }\n | undefined;\n if (!headerRow?.headers) return;\n\n const next = new Map<Keys<T>, number>();\n const headersByKey = new Map<Keys<T>, DOMRect>();\n for (const header of headerRow.headers) {\n headersByKey.set(header.column.key as Keys<T>, header.getBoundingClientRect());\n }\n\n let leftAccum = 0;\n for (const column of this.displayColumns) {\n if (column.hidden) continue;\n if (column.pinned !== 'start') break;\n next.set(column.key as Keys<T>, leftAccum);\n const rect = headersByKey.get(column.key as Keys<T>);\n leftAccum += rect?.width ?? 0;\n }\n\n let rightAccum = 0;\n for (let i = this.displayColumns.length - 1; i >= 0; i--) {\n const column = this.displayColumns[i];\n if (column.hidden) continue;\n if (column.pinned !== 'end') break;\n next.set(column.key as Keys<T>, rightAccum);\n const rect = headersByKey.get(column.key as Keys<T>);\n rightAccum += rect?.width ?? 0;\n }\n\n if (!offsetsEqual(this.pinOffsets, next)) {\n this.pinOffsets = next;\n this.host.requestUpdate();\n }\n }\n\n protected observeHeaderCells(): void {\n // @ts-expect-error - protected member access\n const headerRow = this.host.headerRow as { headers?: Array<Element> } | undefined;\n if (!headerRow?.headers) return;\n\n if (!this.#resizeObserver) {\n this.#resizeObserver = new ResizeObserver(() => {\n // Defer to a new frame so any layout writes can't synchronously feed back\n // into this observer (which makes the browser raise the benign\n // \"ResizeObserver loop completed\" warning).\n requestAnimationFrame(() => this.recomputePinOffsets());\n });\n }\n\n const next = new Set<Element>(headerRow.headers);\n for (const element of this.#observedHeaders) {\n if (!next.has(element)) this.#resizeObserver.unobserve(element);\n }\n for (const element of next) {\n if (!this.#observedHeaders.has(element)) this.#resizeObserver.observe(element);\n }\n this.#observedHeaders = next;\n }\n\n #lastHeaderTop = -1;\n #lastFilterTop = -1;\n #lastHostWidth = -1;\n #hostWidthObserver: ResizeObserver | null = null;\n\n /**\n * Writes the cumulative height of the sticky top rows into CSS custom\n * properties used by the header/filter rows for their `inset-block-start`\n * offsets. The toolbar is always at `top: 0`; the header sits below it\n * (`--apex-row-top-header`) and the filter sits below the header\n * (`--apex-row-top-filter`). Writes are skipped when the values haven't\n * changed so the ResizeObserver feedback loop terminates.\n */\n public recomputeStickyRowOffsets(): void {\n const root = (this.host as HTMLElement).shadowRoot;\n if (!root) return;\n const toolbar = root.querySelector('apex-grid-toolbar') as HTMLElement | null;\n const header = root.querySelector('apex-grid-header-row') as HTMLElement | null;\n\n const toolbarH = toolbar?.offsetHeight ?? 0;\n const headerH = header?.offsetHeight ?? 0;\n const headerTop = toolbarH;\n const filterTop = toolbarH + headerH;\n\n if (headerTop !== this.#lastHeaderTop) {\n this.host.style.setProperty('--apex-row-top-header', `${headerTop}px`);\n this.#lastHeaderTop = headerTop;\n }\n if (filterTop !== this.#lastFilterTop) {\n this.host.style.setProperty('--apex-row-top-filter', `${filterTop}px`);\n this.#lastFilterTop = filterTop;\n }\n }\n\n protected observeStickyRows(): void {\n const root = (this.host as HTMLElement).shadowRoot;\n if (!root) return;\n const toolbar = root.querySelector('apex-grid-toolbar') as HTMLElement | null;\n const header = root.querySelector('apex-grid-header-row') as HTMLElement | null;\n\n if (!this.#stickyRowObserver) {\n this.#stickyRowObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => this.recomputeStickyRowOffsets());\n });\n }\n\n const next = new Set<Element>([toolbar, header].filter((el): el is HTMLElement => !!el));\n for (const element of this.#observedStickyRows) {\n if (!next.has(element)) this.#stickyRowObserver.unobserve(element);\n }\n for (const element of next) {\n if (!this.#observedStickyRows.has(element)) this.#stickyRowObserver.observe(element);\n }\n this.#observedStickyRows = next;\n }\n}\n\nfunction offsetsEqual<K>(a: Map<K, number>, b: Map<K, number>): boolean {\n if (a.size !== b.size) return false;\n for (const [key, value] of a) {\n if (b.get(key) !== value) return false;\n }\n return true;\n}\n"]}
1
+ {"version":3,"file":"dom.js","sourceRoot":"","sources":["../../src/controllers/dom.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RenderItemFunction } from '@lit-labs/virtualizer/virtualize.js';\nimport { html, type ReactiveController } from 'lit';\nimport { type StyleInfo, styleMap } from 'lit/directives/style-map.js';\nimport { SENTINEL_NODE } from '../internal/constants.js';\nimport type { ActiveNode, ColumnConfiguration, GridHost, Keys } from '../internal/types.js';\nimport { applyColumnWidths, getDisplayColumns } from '../internal/utils.js';\nimport type { StateController } from './state.js';\n\nexport class GridDOMController<T extends object> implements ReactiveController {\n constructor(\n protected host: GridHost<T>,\n protected state: StateController<T>\n ) {\n this.host.addController(this);\n }\n\n #initialSize = () => {\n setTimeout(() => {\n this.setScrollOffset();\n this.recomputePinOffsets();\n this.recomputeStickyRowOffsets();\n });\n };\n\n #resizeObserver: ResizeObserver | null = null;\n #observedHeaders = new Set<Element>();\n #stickyRowObserver: ResizeObserver | null = null;\n #observedStickyRows = new Set<Element>();\n\n public get container() {\n // @ts-expect-error: protected member access\n return this.host.scrollContainer;\n }\n\n /**\n * Visual render order: `'start'`-pinned columns first, then unpinned, then\n * `'end'`-pinned. The user-supplied `columns` array is never mutated.\n */\n public get displayColumns(): Array<ColumnConfiguration<T>> {\n return getDisplayColumns(this.host.columns);\n }\n\n public columnSizes: StyleInfo = {};\n\n /**\n * The resolved inline-start (for `'start'` pin) or inline-end (for `'end'` pin)\n * pixel offset per pinned column key. Populated after layout by\n * {@link recomputePinOffsets}.\n */\n public pinOffsets = new Map<Keys<T>, number>();\n\n /**\n * Number of chrome rows above the body for `aria-rowindex`: an optional group\n * header row and the column header (always present). The filter panel is a\n * floating overlay rather than a table row, so it is not counted here.\n */\n protected get headerRowCount(): number {\n return this.host.columnGroupDepth + 1;\n }\n\n public rowRenderer: RenderItemFunction<T> = (data: T, index: number) => {\n const editingCell = this.state.editing.activeCell;\n const editingKey = editingCell?.rowIndex === index ? editingCell.columnKey : null;\n // Body rows follow the chrome rows and any pinned-top rows in the\n // aria-rowindex sequence.\n const ariaRowOffset = this.headerRowCount + this.state.rowPin.pinnedRows.top.length;\n return html`\n <apex-grid-row\n part=\"row\"\n style=${styleMap({ ...this.columnSizes, ...this.getActiveRowStyles(index) })}\n .index=${index}\n .activeNode=${this.state.active}\n .data=${data}\n .columns=${this.displayColumns}\n .pinOffsets=${this.pinOffsets}\n .editingKey=${editingKey}\n .ariaRowOffset=${ariaRowOffset}\n .decorationVersion=${this.state.decorationVersion}\n .validationVersion=${this.state.editing.validationVersion}\n .dragging=${this.state.rowReorder.dragging === data}\n .grabbed=${this.state.rowReorder.grabbed === data}\n .coordinateHints=${Boolean(this.host.coordinateHints)}\n >\n </apex-grid-row>\n `;\n };\n\n /**\n * Renders a single pinned row for a sticky band. Reuses {@link ApexGridRow}\n * so selection / expansion / decoration all work by data reference, but pins a\n * sentinel {@link ActiveNode} (pinned rows are presentation + selection\n * surfaces in v1, never the active/edited cell) and renders no editor.\n *\n * @param localIndex - position within the band (drives the active sentinel id).\n * @param ariaRowOffset - so `aria-rowindex` continues the unified sequence.\n */\n public pinnedRowRenderer(data: T, localIndex: number, ariaRowOffset: number) {\n return html`\n <apex-grid-row\n part=\"row pinned-row\"\n style=${styleMap(this.columnSizes)}\n .index=${localIndex}\n .activeNode=${SENTINEL_NODE as ActiveNode<T>}\n .data=${data}\n .columns=${this.displayColumns}\n .pinOffsets=${this.pinOffsets}\n .editingKey=${null}\n .ariaRowOffset=${ariaRowOffset}\n .decorationVersion=${this.state.decorationVersion}\n .validationVersion=${this.state.editing.validationVersion}\n .coordinateHints=${Boolean(this.host.coordinateHints)}\n >\n </apex-grid-row>\n `;\n }\n\n public async hostConnected() {\n this.setGridColumnSizes();\n // Wait for the initial paint of the virtualizer and recalculate the scrollbar offset\n // for the next one\n await this.host.updateComplete;\n this.#initialSize();\n if (typeof ResizeObserver !== 'undefined' && !this.#hostWidthObserver) {\n this.#hostWidthObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => this.recomputeHostWidth());\n });\n this.#hostWidthObserver.observe(this.host as HTMLElement);\n }\n this.recomputeHostWidth();\n }\n\n public hostUpdate(): void {\n this.setScrollOffset();\n this.setGridColumnSizes();\n }\n\n public hostUpdated(): void {\n this.recomputePinOffsets();\n this.observeHeaderCells();\n this.recomputeStickyRowOffsets();\n this.observeStickyRows();\n }\n\n public hostDisconnected(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = null;\n this.#observedHeaders.clear();\n this.#stickyRowObserver?.disconnect();\n this.#stickyRowObserver = null;\n this.#observedStickyRows.clear();\n this.#hostWidthObserver?.disconnect();\n this.#hostWidthObserver = null;\n }\n\n /**\n * Writes the grid host's `clientWidth` into `--apex-host-width`. Used by the\n * toolbar and paginator to clamp their width to the visible viewport so\n * horizontal scrolling doesn't drag them off-screen.\n */\n public recomputeHostWidth(): void {\n const host = this.host as HTMLElement;\n const width = host.clientWidth;\n if (width === this.#lastHostWidth) return;\n host.style.setProperty('--apex-host-width', `${width}px`);\n this.#lastHostWidth = width;\n }\n\n public setScrollOffset() {\n // The grid host now owns vertical scrolling, so the historic per-row\n // padding-inline-end compensation is no longer needed. Keep the CSS var\n // set to `0` for forward compatibility with consumer themes that read it.\n this.host.style.setProperty('--scrollbar-offset', '0px');\n }\n\n protected setGridColumnSizes() {\n this.columnSizes = applyColumnWidths(this.displayColumns, {\n showSelectionColumn: this.state.selection.showCheckboxColumn,\n showExpansionColumn: this.state.expansion.showToggleColumn,\n showReorderHandle: this.state.rowReorder.showHandleColumn,\n showRowNumbers: Boolean(this.host.coordinateHints),\n });\n }\n\n public getActiveRowStyles(index: number): StyleInfo {\n const { row } = this.state.active;\n return row === index ? { 'z-index': '3' } : {};\n }\n\n /**\n * Measures the rendered widths of pinned header cells and writes cumulative\n * inline-start / inline-end pixel offsets into {@link pinOffsets}. Called after\n * each grid update and whenever a header cell is resized.\n */\n public recomputePinOffsets(): void {\n // @ts-expect-error - protected member access\n const headerRow = this.host.headerRow as\n | {\n headers?: Array<{\n column: ColumnConfiguration<T>;\n getBoundingClientRect: () => DOMRect;\n }>;\n }\n | undefined;\n if (!headerRow?.headers) return;\n\n const next = new Map<Keys<T>, number>();\n const headersByKey = new Map<Keys<T>, DOMRect>();\n for (const header of headerRow.headers) {\n headersByKey.set(header.column.key as Keys<T>, header.getBoundingClientRect());\n }\n\n let leftAccum = 0;\n for (const column of this.displayColumns) {\n if (column.hidden) continue;\n if (column.pinned !== 'start') break;\n next.set(column.key as Keys<T>, leftAccum);\n const rect = headersByKey.get(column.key as Keys<T>);\n leftAccum += rect?.width ?? 0;\n }\n\n let rightAccum = 0;\n for (let i = this.displayColumns.length - 1; i >= 0; i--) {\n const column = this.displayColumns[i];\n if (column.hidden) continue;\n if (column.pinned !== 'end') break;\n next.set(column.key as Keys<T>, rightAccum);\n const rect = headersByKey.get(column.key as Keys<T>);\n rightAccum += rect?.width ?? 0;\n }\n\n if (!offsetsEqual(this.pinOffsets, next)) {\n this.pinOffsets = next;\n this.host.requestUpdate();\n }\n }\n\n protected observeHeaderCells(): void {\n // @ts-expect-error - protected member access\n const headerRow = this.host.headerRow as { headers?: Array<Element> } | undefined;\n if (!headerRow?.headers) return;\n\n if (!this.#resizeObserver) {\n this.#resizeObserver = new ResizeObserver(() => {\n // Defer to a new frame so any layout writes can't synchronously feed back\n // into this observer (which makes the browser raise the benign\n // \"ResizeObserver loop completed\" warning).\n requestAnimationFrame(() => this.recomputePinOffsets());\n });\n }\n\n const next = new Set<Element>(headerRow.headers);\n for (const element of this.#observedHeaders) {\n if (!next.has(element)) this.#resizeObserver.unobserve(element);\n }\n for (const element of next) {\n if (!this.#observedHeaders.has(element)) this.#resizeObserver.observe(element);\n }\n this.#observedHeaders = next;\n }\n\n #lastGroupTop = -1;\n #lastHeaderTop = -1;\n #lastFilterTop = -1;\n #lastPinnedTop = -1;\n #lastPinnedBottom = -1;\n #lastHostWidth = -1;\n #hostWidthObserver: ResizeObserver | null = null;\n\n /**\n * Writes the cumulative heights of the sticky chrome rows into CSS custom\n * properties used for their `inset-block-start` / `inset-block-end` offsets.\n * The toolbar is at `top: 0`; the header sits below it\n * (`--apex-row-top-header`), the filter below the header\n * (`--apex-row-top-filter`), and the pinned-top band below the filter\n * (`--apex-row-top-pinned`). The pinned-bottom band sticks above the\n * paginator (`--apex-row-bottom-pinned`). Writes are skipped when values are\n * unchanged so the ResizeObserver feedback loop terminates.\n */\n public recomputeStickyRowOffsets(): void {\n const root = (this.host as HTMLElement).shadowRoot;\n if (!root) return;\n const toolbar = root.querySelector('apex-grid-toolbar') as HTMLElement | null;\n const groupHeader = root.querySelector('apex-grid-group-header-row') as HTMLElement | null;\n const header = root.querySelector('apex-grid-header-row') as HTMLElement | null;\n const filter = root.querySelector('apex-filter-row') as HTMLElement | null;\n const paginator = root.querySelector('apex-grid-paginator') as HTMLElement | null;\n\n const toolbarH = toolbar?.offsetHeight ?? 0;\n const groupH = groupHeader?.offsetHeight ?? 0;\n const headerH = header?.offsetHeight ?? 0;\n const filterH = filter?.offsetHeight ?? 0;\n const paginatorH = paginator?.offsetHeight ?? 0;\n // The group header (when present) sits below the toolbar; the column header\n // sits below the group header; filter below header; pinned-top below filter.\n const groupTop = toolbarH;\n const headerTop = toolbarH + groupH;\n const filterTop = toolbarH + groupH + headerH;\n const pinnedTop = toolbarH + groupH + headerH + filterH;\n\n if (groupTop !== this.#lastGroupTop) {\n this.host.style.setProperty('--apex-row-top-group', `${groupTop}px`);\n this.#lastGroupTop = groupTop;\n }\n if (headerTop !== this.#lastHeaderTop) {\n this.host.style.setProperty('--apex-row-top-header', `${headerTop}px`);\n this.#lastHeaderTop = headerTop;\n }\n if (filterTop !== this.#lastFilterTop) {\n this.host.style.setProperty('--apex-row-top-filter', `${filterTop}px`);\n this.#lastFilterTop = filterTop;\n }\n if (pinnedTop !== this.#lastPinnedTop) {\n this.host.style.setProperty('--apex-row-top-pinned', `${pinnedTop}px`);\n this.#lastPinnedTop = pinnedTop;\n }\n if (paginatorH !== this.#lastPinnedBottom) {\n this.host.style.setProperty('--apex-row-bottom-pinned', `${paginatorH}px`);\n this.#lastPinnedBottom = paginatorH;\n }\n }\n\n protected observeStickyRows(): void {\n const root = (this.host as HTMLElement).shadowRoot;\n if (!root) return;\n const toolbar = root.querySelector('apex-grid-toolbar') as HTMLElement | null;\n const groupHeader = root.querySelector('apex-grid-group-header-row') as HTMLElement | null;\n const header = root.querySelector('apex-grid-header-row') as HTMLElement | null;\n const filter = root.querySelector('apex-filter-row') as HTMLElement | null;\n const paginator = root.querySelector('apex-grid-paginator') as HTMLElement | null;\n\n if (!this.#stickyRowObserver) {\n this.#stickyRowObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => this.recomputeStickyRowOffsets());\n });\n }\n\n const next = new Set<Element>(\n [toolbar, groupHeader, header, filter, paginator].filter((el): el is HTMLElement => !!el)\n );\n for (const element of this.#observedStickyRows) {\n if (!next.has(element)) this.#stickyRowObserver.unobserve(element);\n }\n for (const element of next) {\n if (!this.#observedStickyRows.has(element)) this.#stickyRowObserver.observe(element);\n }\n this.#observedStickyRows = next;\n }\n}\n\nfunction offsetsEqual<K>(a: Map<K, number>, b: Map<K, number>): boolean {\n if (a.size !== b.size) return false;\n for (const [key, value] of a) {\n if (b.get(key) !== value) return false;\n }\n return true;\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import type { ReactiveController } from 'lit';
2
2
  import type { ColumnConfiguration, EditMode, EditTrigger, GridHost, Keys } from '../internal/types.js';
3
+ import type { HistoryController } from './history.js';
3
4
  /**
4
5
  * The default editing configuration used when the grid has none set.
5
6
  */
@@ -17,6 +18,28 @@ interface ActiveCell<T> {
17
18
  */
18
19
  data: T;
19
20
  }
21
+ /**
22
+ * Outcome of a single committed cell edit routed through
23
+ * {@link EditingController.applyCellEdit}.
24
+ *
25
+ * - `'unchanged'` - the candidate equalled the current value; nothing happened.
26
+ * - `'invalid'` - a column validator rejected the candidate; the value was not
27
+ * written and `cellValidationFailed` fired.
28
+ * - `'cancelled'` - a `cellValueChanging` listener called `preventDefault()`.
29
+ * - `'applied'` - the value was written and `cellValueChanged` fired.
30
+ */
31
+ export type CellEditResult = 'unchanged' | 'invalid' | 'cancelled' | 'applied';
32
+ /**
33
+ * Options for {@link EditingController.applyCellEdit}.
34
+ */
35
+ export interface ApplyCellEditOptions {
36
+ /**
37
+ * Run the column's declarative validators before writing. Defaults to `true`.
38
+ * Set to `false` for writes that must not be re-validated (e.g. reverting a
39
+ * previously valid value during undo/redo).
40
+ */
41
+ validate?: boolean;
42
+ }
20
43
  /**
21
44
  * Reactive controller backing inline cell + row editing.
22
45
  *
@@ -28,7 +51,9 @@ interface ActiveCell<T> {
28
51
  * propagate to consumers that own the source array.
29
52
  */
30
53
  export declare class EditingController<T extends object> implements ReactiveController {
54
+ #private;
31
55
  protected host: GridHost<T>;
56
+ protected history?: HistoryController<T> | undefined;
32
57
  /** The cell currently in edit mode, or `null`. */
33
58
  activeCell: ActiveCell<T> | null;
34
59
  /** The row currently in edit mode (row mode only), or `null`. */
@@ -42,7 +67,14 @@ export declare class EditingController<T extends object> implements ReactiveCont
42
67
  * {@link commitRow}.
43
68
  */
44
69
  pending: Map<Keys<T>, unknown>;
45
- constructor(host: GridHost<T>);
70
+ /**
71
+ * Monotonic token bumped whenever the validation-error set changes. Forwarded
72
+ * down to each cell as a reactive property so a validation-only change
73
+ * re-renders the affected cells (toggling `aria-invalid` and the error node)
74
+ * without re-running the data pipeline. Stays `0` until the first failure.
75
+ */
76
+ get validationVersion(): number;
77
+ constructor(host: GridHost<T>, history?: HistoryController<T> | undefined);
46
78
  hostConnected(): void;
47
79
  /**
48
80
  * Whether editing is enabled at the grid level.
@@ -82,6 +114,41 @@ export declare class EditingController<T extends object> implements ReactiveCont
82
114
  * success. No-op in cell mode.
83
115
  */
84
116
  editRow(rowIndex: number): Promise<boolean>;
117
+ /**
118
+ * The single choke point for committing a cell value to the backing record.
119
+ * Short-circuits no-op writes, runs the cancellable `cellValueChanging` gate,
120
+ * writes the value through to {@link ApexGrid.data}, and emits
121
+ * `cellValueChanged`. Returns the {@link CellEditResult} so callers can manage
122
+ * their own UI state (active cell) and batch the render.
123
+ *
124
+ * @remarks
125
+ * Does **not** request a render — callers refresh (once per edit, or once per
126
+ * batch) so row-mode and bulk edits coalesce into a single pipeline pass.
127
+ * Every committed edit (single-cell, row-mode, and enterprise paste/fill)
128
+ * funnels through here so cross-cutting concerns (validation, undo/redo) have
129
+ * one place to hook the old/new values and the write.
130
+ */
131
+ applyCellEdit(rowIndex: number, columnKey: Keys<T>, data: T, value: unknown, options?: ApplyCellEditOptions): CellEditResult;
132
+ /**
133
+ * Runs the column's declarative validators against a candidate value and
134
+ * returns the collected error messages (empty when the value passes or the
135
+ * column has no validators).
136
+ */
137
+ validateCell(rowIndex: number, columnKey: Keys<T>, data: T, value: unknown): string[];
138
+ /**
139
+ * The current validation errors for `(data, columnKey)`, or `null` when the
140
+ * cell is valid. Read by {@link ApexGridCell} to drive `aria-invalid` and the
141
+ * inline error node.
142
+ */
143
+ getCellErrors(data: T, columnKey: Keys<T>): readonly string[] | null;
144
+ /**
145
+ * Clears any validation error recorded for `(data, columnKey)`.
146
+ */
147
+ clearCellErrors(data: T, columnKey: Keys<T>): void;
148
+ /**
149
+ * Clears every validation error recorded for a record (all columns).
150
+ */
151
+ clearRecordErrors(data: T): void;
85
152
  /**
86
153
  * Commits a candidate value for the currently editing cell. In cell mode the
87
154
  * value is written through to {@link ApexGrid.data}; in row mode it is staged
@@ -1,15 +1,24 @@
1
1
  import { PIPELINE } from '../internal/constants.js';
2
+ import { runValidators } from '../internal/validators.js';
2
3
  export const DEFAULT_EDITING_CONFIG = Object.freeze({
3
4
  enabled: false,
4
5
  mode: 'cell',
5
6
  trigger: 'doubleClick',
6
7
  });
7
8
  export class EditingController {
8
- constructor(host) {
9
+ #invalid;
10
+ #validationVersion;
11
+ get validationVersion() {
12
+ return this.#validationVersion;
13
+ }
14
+ constructor(host, history) {
9
15
  this.host = host;
16
+ this.history = history;
10
17
  this.activeCell = null;
11
18
  this.activeRow = null;
12
19
  this.pending = new Map();
20
+ this.#invalid = new Map();
21
+ this.#validationVersion = 0;
13
22
  this.host.addController(this);
14
23
  }
15
24
  hostConnected() { }
@@ -77,6 +86,74 @@ export class EditingController {
77
86
  this.host.emitEvent('rowEditStarted', { detail: { rowIndex } });
78
87
  return true;
79
88
  }
89
+ applyCellEdit(rowIndex, columnKey, data, value, options) {
90
+ const record = data;
91
+ const oldValue = record[columnKey];
92
+ if (Object.is(value, oldValue)) {
93
+ this.clearCellErrors(data, columnKey);
94
+ return 'unchanged';
95
+ }
96
+ if (options?.validate !== false) {
97
+ const errors = this.validateCell(rowIndex, columnKey, data, value);
98
+ if (errors.length > 0) {
99
+ this.#setCellErrors(data, columnKey, errors);
100
+ this.host.emitEvent('cellValidationFailed', {
101
+ detail: { key: columnKey, rowIndex, data, value, errors },
102
+ });
103
+ return 'invalid';
104
+ }
105
+ }
106
+ const proceed = this.host.emitEvent('cellValueChanging', {
107
+ detail: { key: columnKey, rowIndex, data, oldValue, newValue: value },
108
+ cancelable: true,
109
+ });
110
+ if (!proceed)
111
+ return 'cancelled';
112
+ record[columnKey] = value;
113
+ this.clearCellErrors(data, columnKey);
114
+ this.history?.record({ record: data, key: columnKey, rowIndex, oldValue, newValue: value });
115
+ this.host.emitEvent('cellValueChanged', {
116
+ detail: { key: columnKey, rowIndex, data, value },
117
+ });
118
+ return 'applied';
119
+ }
120
+ validateCell(rowIndex, columnKey, data, value) {
121
+ const column = this.host.getColumn(columnKey);
122
+ if (!column?.validators?.length)
123
+ return [];
124
+ return runValidators(column.validators, value, {
125
+ column,
126
+ data,
127
+ rowIndex,
128
+ });
129
+ }
130
+ getCellErrors(data, columnKey) {
131
+ return this.#invalid.get(data)?.get(columnKey) ?? null;
132
+ }
133
+ clearCellErrors(data, columnKey) {
134
+ const perColumn = this.#invalid.get(data);
135
+ if (!perColumn)
136
+ return;
137
+ if (perColumn.delete(columnKey)) {
138
+ if (perColumn.size === 0)
139
+ this.#invalid.delete(data);
140
+ this.#validationVersion += 1;
141
+ }
142
+ }
143
+ clearRecordErrors(data) {
144
+ if (this.#invalid.delete(data)) {
145
+ this.#validationVersion += 1;
146
+ }
147
+ }
148
+ #setCellErrors(data, columnKey, errors) {
149
+ let perColumn = this.#invalid.get(data);
150
+ if (!perColumn) {
151
+ perColumn = new Map();
152
+ this.#invalid.set(data, perColumn);
153
+ }
154
+ perColumn.set(columnKey, errors);
155
+ this.#validationVersion += 1;
156
+ }
80
157
  async commitCell(value) {
81
158
  const active = this.activeCell;
82
159
  if (!active)
@@ -85,24 +162,21 @@ export class EditingController {
85
162
  const oldValue = data[columnKey];
86
163
  const candidate = value === undefined ? oldValue : value;
87
164
  if (this.mode === 'cell') {
88
- if (Object.is(candidate, oldValue)) {
89
- this.activeCell = null;
165
+ const result = this.applyCellEdit(rowIndex, columnKey, data, candidate);
166
+ if (result === 'cancelled')
167
+ return false;
168
+ if (result === 'invalid') {
90
169
  this.host.requestUpdate();
91
- return true;
92
- }
93
- const proceed = this.host.emitEvent('cellValueChanging', {
94
- detail: { key: columnKey, rowIndex, data, oldValue, newValue: candidate },
95
- cancelable: true,
96
- });
97
- if (!proceed)
98
170
  return false;
99
- data[columnKey] = candidate;
171
+ }
100
172
  this.activeCell = null;
101
- this.host.requestUpdate(PIPELINE);
102
- await this.host.updateComplete;
103
- this.host.emitEvent('cellValueChanged', {
104
- detail: { key: columnKey, rowIndex, data, value: candidate },
105
- });
173
+ if (result === 'applied') {
174
+ this.host.requestUpdate(PIPELINE);
175
+ await this.host.updateComplete;
176
+ }
177
+ else {
178
+ this.host.requestUpdate();
179
+ }
106
180
  return true;
107
181
  }
108
182
  if (!Object.is(candidate, oldValue)) {
@@ -121,27 +195,25 @@ export class EditingController {
121
195
  const data = this.host.pageItems[rowIndex];
122
196
  if (!data)
123
197
  return false;
124
- const oldValue = data[columnKey];
125
- if (Object.is(value, oldValue))
126
- return true;
127
- const proceed = this.host.emitEvent('cellValueChanging', {
128
- detail: { key: columnKey, rowIndex, data, oldValue, newValue: value },
129
- cancelable: true,
130
- });
131
- if (!proceed)
198
+ const result = this.applyCellEdit(rowIndex, columnKey, data, value);
199
+ if (result === 'cancelled')
132
200
  return false;
133
- data[columnKey] = value;
134
- this.host.requestUpdate(PIPELINE);
135
- await this.host.updateComplete;
136
- this.host.emitEvent('cellValueChanged', {
137
- detail: { key: columnKey, rowIndex, data, value },
138
- });
201
+ if (result === 'invalid') {
202
+ this.host.requestUpdate();
203
+ return false;
204
+ }
205
+ if (result === 'applied') {
206
+ this.host.requestUpdate(PIPELINE);
207
+ await this.host.updateComplete;
208
+ }
139
209
  return true;
140
210
  }
141
211
  cancelCell() {
142
212
  if (!this.activeCell)
143
213
  return;
214
+ const { data, columnKey } = this.activeCell;
144
215
  this.activeCell = null;
216
+ this.clearCellErrors(data, columnKey);
145
217
  this.host.requestUpdate();
146
218
  }
147
219
  async commitRow() {
@@ -152,20 +224,32 @@ export class EditingController {
152
224
  if (this.activeCell?.rowIndex === rowIndex) {
153
225
  await this.commitCell();
154
226
  }
227
+ let hasInvalid = false;
155
228
  for (const [columnKey, candidate] of this.pending) {
156
- const oldValue = data[columnKey];
157
- if (Object.is(candidate, oldValue))
158
- continue;
159
- const proceed = this.host.emitEvent('cellValueChanging', {
160
- detail: { key: columnKey, rowIndex, data, oldValue, newValue: candidate },
161
- cancelable: true,
162
- });
163
- if (!proceed)
164
- return false;
165
- data[columnKey] = candidate;
166
- this.host.emitEvent('cellValueChanged', {
167
- detail: { key: columnKey, rowIndex, data, value: candidate },
168
- });
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;
236
+ }
237
+ }
238
+ if (hasInvalid) {
239
+ this.host.requestUpdate();
240
+ return false;
241
+ }
242
+ this.history?.beginBatch();
243
+ 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
+ }
249
+ }
250
+ }
251
+ finally {
252
+ this.history?.endBatch();
169
253
  }
170
254
  this.pending.clear();
171
255
  this.activeRow = null;
@@ -177,10 +261,11 @@ export class EditingController {
177
261
  cancelRow() {
178
262
  if (!this.activeRow)
179
263
  return;
180
- const { rowIndex } = this.activeRow;
264
+ const { rowIndex, data } = this.activeRow;
181
265
  this.activeCell = null;
182
266
  this.activeRow = null;
183
267
  this.pending.clear();
268
+ this.clearRecordErrors(data);
184
269
  this.host.requestUpdate();
185
270
  this.host.emitEvent('rowEditEnded', { detail: { rowIndex, committed: false } });
186
271
  }