apex-grid 3.6.0 → 4.0.0-beta.1

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 (122) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/README.md +79 -13
  3. package/components/cell.d.ts +13 -1
  4. package/components/cell.js +28 -13
  5. package/components/cell.js.map +1 -1
  6. package/components/grid.d.ts +135 -13
  7. package/components/grid.js +165 -63
  8. package/components/grid.js.map +1 -1
  9. package/components/header-row.d.ts +0 -6
  10. package/components/header-row.js +1 -19
  11. package/components/header-row.js.map +1 -1
  12. package/components/header.js +39 -2
  13. package/components/header.js.map +1 -1
  14. package/components/paginator.d.ts +20 -5
  15. package/components/paginator.js +54 -17
  16. package/components/paginator.js.map +1 -1
  17. package/components/row.js +149 -40
  18. package/components/row.js.map +1 -1
  19. package/components/toolbar.d.ts +5 -1
  20. package/components/toolbar.js +4 -1
  21. package/components/toolbar.js.map +1 -1
  22. package/components/virtualize-rows.d.ts +38 -0
  23. package/components/virtualize-rows.js +286 -0
  24. package/components/virtualize-rows.js.map +1 -0
  25. package/components/virtualizer.d.ts +71 -1
  26. package/components/virtualizer.js +105 -29
  27. package/components/virtualizer.js.map +1 -1
  28. package/controllers/dom.d.ts +10 -3
  29. package/controllers/dom.js +5 -5
  30. package/controllers/dom.js.map +1 -1
  31. package/controllers/navigation.d.ts +63 -0
  32. package/controllers/navigation.js +295 -21
  33. package/controllers/navigation.js.map +1 -1
  34. package/controllers/pagination.d.ts +7 -6
  35. package/controllers/pagination.js +27 -28
  36. package/controllers/pagination.js.map +1 -1
  37. package/controllers/row-pin.d.ts +6 -0
  38. package/controllers/row-pin.js +8 -2
  39. package/controllers/row-pin.js.map +1 -1
  40. package/controllers/row-reorder.d.ts +12 -4
  41. package/controllers/row-reorder.js +20 -6
  42. package/controllers/row-reorder.js.map +1 -1
  43. package/controllers/selection.d.ts +32 -10
  44. package/controllers/selection.js +33 -10
  45. package/controllers/selection.js.map +1 -1
  46. package/controllers/sort.js +2 -3
  47. package/controllers/sort.js.map +1 -1
  48. package/controllers/state.d.ts +18 -0
  49. package/controllers/state.js +53 -42
  50. package/controllers/state.js.map +1 -1
  51. package/custom-elements.json +1404 -215
  52. package/docs/assets/hierarchy.js +1 -1
  53. package/docs/assets/search.js +1 -1
  54. package/docs/classes/ApexGrid.html +119 -47
  55. package/docs/classes/ApexGridPaginator.html +39 -8
  56. package/docs/classes/ApexGridToolbar.html +9 -5
  57. package/docs/functions/localize.html +2 -2
  58. package/docs/hierarchy.html +1 -1
  59. package/docs/index.html +85 -15
  60. package/docs/interfaces/ApexPageChangedEvent.html +3 -1
  61. package/docs/interfaces/BaseApexCellContext.html +18 -5
  62. package/docs/interfaces/BaseApexEditorContext.html +20 -5
  63. package/docs/interfaces/BaseFilterExpression.html +1 -1
  64. package/docs/interfaces/CSVExportOptions.html +1 -1
  65. package/docs/interfaces/DataPipelineConfiguration.html +4 -9
  66. package/docs/interfaces/ExportOptions.html +1 -1
  67. package/docs/interfaces/GridSelectionConfiguration.html +19 -4
  68. package/docs/interfaces/PaginationConfiguration.html +11 -4
  69. package/docs/interfaces/PaginationState.html +4 -2
  70. package/docs/media/package.json +7 -4
  71. package/docs/types/DataPipelineParams.html +2 -2
  72. package/docs/types/PaginationMode.html +5 -4
  73. package/docs/variables/EN_LOCALE.html +2 -2
  74. package/i18n/en.d.ts +2 -0
  75. package/i18n/en.js +2 -0
  76. package/i18n/en.js.map +1 -1
  77. package/i18n/es.js +2 -0
  78. package/i18n/es.js.map +1 -1
  79. package/internal/column-types.d.ts +11 -1
  80. package/internal/column-types.js +8 -6
  81. package/internal/column-types.js.map +1 -1
  82. package/internal/export.d.ts +6 -0
  83. package/internal/export.js +4 -0
  84. package/internal/export.js.map +1 -1
  85. package/internal/feature-module.d.ts +7 -0
  86. package/internal/feature-module.js.map +1 -1
  87. package/internal/flip.d.ts +28 -16
  88. package/internal/flip.js +42 -10
  89. package/internal/flip.js.map +1 -1
  90. package/internal/index.d.ts +2 -2
  91. package/internal/index.js +2 -2
  92. package/internal/index.js.map +1 -1
  93. package/internal/types.d.ts +75 -19
  94. package/internal/types.js.map +1 -1
  95. package/operations/filter/types.d.ts +1 -1
  96. package/operations/filter/types.js.map +1 -1
  97. package/package.json +2 -2
  98. package/styles/_tokens.css.js +1 -1
  99. package/styles/_tokens.css.js.map +1 -1
  100. package/styles/body-cell/body-cell.css.js +1 -1
  101. package/styles/body-cell/body-cell.css.js.map +1 -1
  102. package/styles/body-cell/cell-renderers.css.js +1 -1
  103. package/styles/body-cell/cell-renderers.css.js.map +1 -1
  104. package/styles/body-row/body-row.css.js +1 -1
  105. package/styles/body-row/body-row.css.js.map +1 -1
  106. package/styles/grid/_grid.base.css.js +1 -1
  107. package/styles/grid/_grid.base.css.js.map +1 -1
  108. package/styles/grid/grid.css.js +1 -1
  109. package/styles/grid/grid.css.js.map +1 -1
  110. package/styles/group-header-row/group-header-row.css.js +1 -1
  111. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  112. package/styles/header-cell/header-cell.css.js +1 -1
  113. package/styles/header-cell/header-cell.css.js.map +1 -1
  114. package/styles/header-row/header-row.base.css.js +1 -1
  115. package/styles/header-row/header-row.base.css.js.map +1 -1
  116. package/styles/paginator/paginator.css.js +1 -1
  117. package/styles/paginator/paginator.css.js.map +1 -1
  118. package/styles/toolbar/toolbar.css.js +1 -1
  119. package/styles/toolbar/toolbar.css.js.map +1 -1
  120. package/operations/pagination.d.ts +0 -19
  121. package/operations/pagination.js +0 -12
  122. package/operations/pagination.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,241 @@ All notable changes to the `apex-grid` (community) package are documented here.
4
4
  This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
5
5
  and the format is based on [Keep a Changelog](https://keepachangelog.com/).
6
6
 
7
+ ## [Unreleased]
8
+
9
+ ## [4.0.0-beta.1] - 2026-10-29
10
+
11
+ A pre-release of 4.0.0, published under the `next` npm tag for trying out
12
+ before it becomes `latest`. It is a major version because these changes can
13
+ break existing code:
14
+
15
+ - **Body rows are recycled by default** (`rowRecycling`). A row element and
16
+ its cells can show another record after a scroll, sort or filter, so a
17
+ template that keeps state in the elements it creates, or code that holds
18
+ `grid.rows` elements or `ctx.row` across an `await`, can see another
19
+ record. Set `rowRecycling = false` (or `row-recycling="false"`) to keep
20
+ record-keyed rows.
21
+ - **The grid role moved off the host.** `role`, `aria-rowcount`,
22
+ `aria-colcount`, `aria-multiselectable` and the accessible name are on the
23
+ inner `[part~="table"]` element.
24
+ - **Types removed:** `DataPipelineConfiguration.pagination` and the
25
+ `'pagination'` value of `DataPipelineParams.type`.
26
+ - **Pagination:** pinned rows no longer count toward `totalItems` and the
27
+ pages, and a local page change no longer calls custom sort and filter hooks.
28
+ - **Smaller type and DOM changes:** `ctx.data` is a required field of the cell
29
+ context, `ApexGridPaginator.pageSizeOptions` is `readonly number[]`, the
30
+ paginator's previous button id is `prev`, and the protected
31
+ `ApexGrid.headerRowCount` and `ApexGridHeaderRow.renderDragGhost` are gone.
32
+
33
+ See Changed and Removed for the details and for other changes apps may notice.
34
+
35
+ ### Added
36
+ - The `ai.cancelled` locale key (English and Spanish), used by the enterprise
37
+ Ask AI panel.
38
+ - The `chart.rangeResized` locale key (English and Spanish), announced when an
39
+ enterprise charted range is resized from the keyboard.
40
+ - **`showHeader`** (attribute `show-header="false"`) leaves out the column
41
+ header row, the column group row and the filter panel, so a short grid such as
42
+ a dashboard tile gives that room to the data rows. Screen readers still
43
+ announce the column names, and sort, filter, column widths and the page come
44
+ back unchanged when the header returns. While it is hidden, resizing, the
45
+ column menu, drag reorder and the header filter button are unavailable.
46
+ ([#35](https://github.com/apexcharts/apex-grid/issues/35))
47
+ - **`selection.isRowSelectable(row)`** keeps chosen rows out of selection,
48
+ such as a pinned total row. Such a row shows no checkbox, and clicks, Shift
49
+ and Ctrl clicks, Space, Select all, `selectRow` and `setState` all skip it;
50
+ the header checkbox counts only selectable rows. Rows stay selectable by
51
+ default, pinned rows included.
52
+ ([#38](https://github.com/apexcharts/apex-grid/issues/38))
53
+ - `pagination.paginator: 'compact'` draws the small paginator: summary,
54
+ previous, page / count, next.
55
+ ([#36](https://github.com/apexcharts/apex-grid/issues/36))
56
+ - `apex-grid::part()` reaches every paginator and toolbar part
57
+ (`paginator-host`, `paginator`, `paginator-size`, `paginator-info`,
58
+ `paginator-controls`, `paginator-page`, `paginator-button`, `toolbar-host`,
59
+ `toolbar`, `toolbar-search`, `search-input`, `toolbar-actions`, `export`,
60
+ `export-menu` and the rest), and each paginator button also carries its own
61
+ part: `paginator-first`, `paginator-prev`, `paginator-next` or
62
+ `paginator-last`. ([#37](https://github.com/apexcharts/apex-grid/issues/37))
63
+ - **`ctx.data` in cell and editor templates.** The context handed to a
64
+ `cellTemplate` or an `editorTemplate` carries `data`, the record the cell
65
+ was rendered for. It stays that record after an `await`, while `ctx.row`
66
+ and `ctx.parent` are elements that a pinned row band can later re-bind to
67
+ another record. A `cellTemplate`'s `ctx.commit` writes to `ctx.data`. An
68
+ editor's `ctx.commit` still commits the cell in edit when it is called.
69
+
70
+ ### Changed
71
+ - **Body rows are now recycled by default** (new `rowRecycling` property,
72
+ default `true`). A scroll, sort or filter re-binds the rendered row elements
73
+ to the records now in view instead of building new rows, which makes long
74
+ jumps and sorts cheaper on large grids. A row element, and its cells, can
75
+ therefore show another record later. Cell and editor templates should
76
+ derive what they render from their context on each render, read
77
+ `ctx.data` for the record, and not hold `ctx.row` or `ctx.parent` across an
78
+ `await`; a template that keeps element state can wrap itself in
79
+ `keyed(ctx.data, ...)`. What you might notice: a template that keeps state
80
+ in the elements it creates (an input's typed value, a third-party widget)
81
+ can show it on another record after a scroll or a sort, and code that holds
82
+ `grid.rows` elements across a scroll or a sort finds them showing other
83
+ records. To keep record-keyed rows, set `grid.rowRecycling = false` (or the
84
+ `row-recycling="false"` attribute). Detail panel content follows its record
85
+ and keeps its DOM state across a scroll, a sort or a `moveRow`, so detail
86
+ templates need no change; a grid created inside a `detailTemplate` keeps its
87
+ own setting.
88
+ - **The grid role and its row counts moved from the `<apex-grid>` host to an
89
+ inner element** exposed as part `table`: `role` (`grid` or `treegrid`),
90
+ `aria-rowcount`, `aria-colcount`, `aria-multiselectable` and the accessible
91
+ name. It holds only the header rows and the body, so the toolbar, paginator,
92
+ filter panel and live region are no longer read as table content, and every
93
+ default grid passes the axe `aria-required-children` rule. Code or tests
94
+ that read these from the host should query
95
+ `grid.shadowRoot.querySelector('[part~="table"]')`. The layout is unchanged.
96
+ - An `aria-label` or `aria-labelledby` on the host still names the grid, and
97
+ `aria-describedby` becomes its description. References are resolved when the
98
+ grid renders: if the referenced element is added later, set the attribute
99
+ again.
100
+ - The grid uses the `treegrid` role whenever rows carry levels or expand: tree
101
+ data, master-detail with a detail template, and the enterprise row grouping,
102
+ expandable pivot and server-side grouping.
103
+ - `pageItems` keeps the same array between updates while nothing that shapes
104
+ it changes. Apps that compared it by identity now see a new array only when
105
+ the body rows change.
106
+ - The column header row is no longer a Tab stop.
107
+ - **The built-in column menu opens in the top layer** where the browser supports
108
+ the Popover API: pinned rows, the paginator and the grid's edge no longer
109
+ cover or clip it, it stays on its header cell through any scroll or layout
110
+ change, and it closes when its column scrolls out of the grid. It can now
111
+ extend outside the grid's box.
112
+ - The column drag ghost renders at the grid root, so it stays above the column
113
+ group header and pinned top rows, and `apex-grid::part(drag-ghost)` now
114
+ reaches it.
115
+ - `--filtering-header-background` applies to every filtered header, pinned and
116
+ dragged ones included, as a layer over the header's own background, and
117
+ filtered headers get the same hover highlight as other headers.
118
+ - The paginator follows `--header-background` and group header labels follow
119
+ `--header-text-color`, like the rest of the header area. The paginator text
120
+ size follows `--ag-fs-cell-secondary`, and the filter count badge uses
121
+ `--ag-text-on-brand` and `--ag-fw-semibold`. Default rendering is unchanged.
122
+ - Forced colors (Windows High Contrast): checkboxes are drawn by the browser in
123
+ system colours, so apps that styled them through parts see the native
124
+ checkbox in that mode only.
125
+ - With pagination, pinned rows no longer count toward the pages: `totalItems`,
126
+ `pageCount`, the paginator summary and the `pageChanged` detail count the
127
+ rows the pager pages through, and pinning a row on the last page moves to the
128
+ new last page. Without pagination `totalItems` still counts every row.
129
+ - A local page or page size change no longer reruns sorting, filtering and the
130
+ rest of the pipeline, so custom sort and filter hooks are no longer called on
131
+ a local page change. Remote mode still reruns it.
132
+ - `pageChanged` reports the change that was applied, so two quick
133
+ `nextPage()` calls report page 2 and then page 3.
134
+ - A page change is announced once ("Page X of N"); the paginator summary and
135
+ page label are no longer live regions.
136
+ - The first grid on a page adds one window `error` listener that cancels
137
+ "ResizeObserver loop" messages and hides them from listeners added after it.
138
+ Listeners registered before the first grid still receive them. Every other
139
+ error is untouched. ([#32](https://github.com/apexcharts/apex-grid/issues/32))
140
+ - `totalItems` is described one way everywhere: the post-filter, post-sort
141
+ row count, and in remote mode the `pagination.totalItems` you supply, the
142
+ number of rows the server holds for the current filter.
143
+ ([#34](https://github.com/apexcharts/apex-grid/issues/34))
144
+ - Type and DOM details for code that builds on the grid's internals:
145
+ `BaseApexCellContext.data` is required, so code that builds a cell context
146
+ by hand must supply it; `ApexGridPaginator.pageSizeOptions` is typed
147
+ `readonly number[]`; the paginator's previous button has the id `prev`
148
+ (it was `previous`) inside its shadow root; and the protected
149
+ `ApexGrid.headerRowCount` and `ApexGridHeaderRow.renderDragGhost` were
150
+ removed, which affects subclasses only.
151
+ - The `@lit-labs/virtualizer` dependency is pinned to exactly `2.1.1` (it was
152
+ `~2.1.0`). The grid body builds on parts of that package that are not public
153
+ API, so a newer version arrives only with a grid release that has been
154
+ checked against it.
155
+
156
+ ### Removed
157
+ - **Breaking (types):** `DataPipelineConfiguration.pagination` and the
158
+ `'pagination'` value of `DataPipelineParams.type`. The hook was documented
159
+ but never called, so no runtime behaviour changes; TypeScript code that sets
160
+ it stops compiling. For server paging use `pagination.mode: 'remote'`, fetch
161
+ the page on `pageChanged` and keep `pagination.totalItems` current.
162
+ ([#33](https://github.com/apexcharts/apex-grid/issues/33))
163
+
164
+ ### Fixed
165
+ - `aria-rowcount` no longer counts the floating filter panel as a row, and a
166
+ pinned top row no longer shares its `aria-rowindex` with the first body row.
167
+ - The master-detail panel sits in a full-width gridcell, and `aria-expanded` is
168
+ set only on rows and toggle buttons that can expand. Turning expansion off or
169
+ removing the detail template no longer leaves it on rows.
170
+ - Announcing the same message twice in a row (two identical `grid.announce()`
171
+ calls, for example) is read out again by screen readers.
172
+ - Invalid cells no longer set an `aria-errormessage` that pointed at an
173
+ unreachable id. The inline alert message is unchanged.
174
+ - Range and formula reference highlights on a pinned column no longer let
175
+ scrolled cells show through it.
176
+ - Alternating row stripes follow the row index, so they no longer flip or
177
+ flicker while you scroll.
178
+ - In the dark theme the default, muted and gold badges are readable and the
179
+ progress bar track is visible. The light theme is unchanged.
180
+ - The selected row accent bar sits on the leading edge in right to left
181
+ layouts, including a left to right grid nested in a right to left one.
182
+ - Forced colors: checked and indeterminate checkboxes no longer look unchecked;
183
+ in a selected row the cell text, row number, reorder grip and chevrons take
184
+ the selection text colour; an editor opened in a selected row uses the
185
+ system field colours; a keyboard-grabbed row shows an outline.
186
+ - The paginator no longer clips its controls in a narrow grid: it drops the
187
+ rows per page selector, then the first and last buttons, so previous and next
188
+ stay reachable. ([#36](https://github.com/apexcharts/apex-grid/issues/36))
189
+ - Grids whose cell text wraps no longer raise "ResizeObserver loop completed
190
+ with undelivered notifications" while they resize or scroll.
191
+ ([#32](https://github.com/apexcharts/apex-grid/issues/32))
192
+ - With row pinning and pagination, no trailing page is left empty.
193
+ - A `pageSize` missing from `pageSizeOptions` now appears in the rows per
194
+ page selector and is selected.
195
+ - A page size change or quick filter cancelled through `pageChanging` or
196
+ `quickFilterChanging` no longer leaves the selector or search box showing
197
+ the refused value.
198
+ - **Pinned rows no longer show the previous row's state.** When a pinned row
199
+ element switches to another record (a row above it is unpinned, say), it no
200
+ longer keeps the previous record's staged row edit value, validation error,
201
+ expanded state, detail panel content or selection checkbox state, even when
202
+ both records hold the same cell value.
203
+ - A `cellTemplate` that calls `ctx.commit` after an `await` writes to the
204
+ record it was rendered for, even if the rows changed in the meantime.
205
+ - Moving a keyboard grabbed row with the arrow keys keeps focus on that row
206
+ instead of dropping it to the page, and Escape puts the active cell and
207
+ focus back on the row at its original position.
208
+ - The floating ghost of a row drag shows the row's cell text. It showed empty
209
+ cells before.
210
+ - An editor rendered again for an edit already in progress (its row scrolled
211
+ back into view, for example) no longer scrolls the grid or selects the typed
212
+ draft when it takes focus.
213
+ - With an async `dataPipelineConfiguration` hook, moving a row with
214
+ `moveRow`, a keyboard grab or a drag animates the rows into their new places.
215
+ They jumped there without the animation before.
216
+ - A jump to a row far outside the view that is still waiting for the body to
217
+ finish scrolling no longer runs after a newer jump, so two quick jumps end on
218
+ the second row instead of snapping back to the first.
219
+ - A pinned row that switches to another record no longer animates from the
220
+ previous record's look: its selected wash, expand chevron, checkbox and
221
+ progress bars show the new record's state at once.
222
+ - An image cell whose URL changes (a pinned row switching records, an edit of
223
+ the URL) renders a new `<img>`, so the previous picture no longer stays on
224
+ screen while the new one loads.
225
+ - Arrow and Page keys in a grid whose rows grew after they were first shown
226
+ (detail panels expanded, wrapped text) keep the active cell in view and
227
+ focused. A quick run of key presses could leave it unrendered, with focus on
228
+ the body or on a cell of another record, until the next click.
229
+ - When a sort, a filter or a data change moves the record of an open cell
230
+ edit to another row that is still in view, the active cell moves with it, so
231
+ the keyboard carries on from the edited cell once the edit closes. It used to
232
+ stay on the old row, which then showed another record.
233
+
234
+ ### Performance
235
+ - Scrolling no longer re-renders the rows that stay in view; only the rows that
236
+ scroll in render.
237
+ - A grid with a pinned row or a page slice no longer rescans the whole view on
238
+ every key press or click.
239
+ - Grids without feature modules skip the per-cell decoration step, and grids
240
+ with modules no longer check every module on each row and cell render.
241
+
7
242
  ## [3.6.0] - 2026-10-21
8
243
 
9
244
  A quality release built from a full code-quality audit, plus `--ag-*` tokens
package/README.md CHANGED
@@ -192,6 +192,9 @@ With the element registered and the host sized, you should see:
192
192
  | `<apex-grid>` blank tag in DOM | Step 2: element not registered |
193
193
  | Columns shown as literal `[object Object]` | `columns=` used as an attribute; must be a **property** (`.columns=${...}` in Lit, `[columns]=` in Angular, `:columns.prop=` in Vue, `el.columns = ...` in vanilla JS) |
194
194
 
195
+ > [!NOTE]
196
+ > Rows whose text wraps change height while the grid resizes or scrolls, and the browser can defer the resulting row layout by one frame. It reports each deferral as a window `error` event with the message "ResizeObserver loop completed with undelivered notifications" (older browsers say "ResizeObserver loop limit exceeded"). The event is benign, so the first grid to connect adds one window `error` listener that cancels any message containing "ResizeObserver loop" (Safari then stops logging it) and stops the listeners added after it from seeing it. Every other error passes through untouched. Listeners added before the first grid connects, such as error monitoring set up at page start, still receive it, and the same message raised by your own code is quieted as well, since the browser does not say which observer raised it.
197
+
195
198
  ---
196
199
 
197
200
  ## Features in depth
@@ -255,7 +258,20 @@ await grid.setPageSize(50);
255
258
  grid.nextPage(); grid.previousPage(); grid.firstPage(); grid.lastPage();
256
259
  ```
257
260
 
258
- Remote mode:
261
+ The paginator never clips its controls: on a narrow grid it drops the page size select first, then the first and last buttons, so previous and next stay reachable. `paginator: 'compact'` always uses that narrow layout ("start-end of total", previous, "page / count", next), for example in a small dashboard tile:
262
+
263
+ ```ts
264
+ grid.pagination = { enabled: true, pageSize: 5, paginator: 'compact' };
265
+ ```
266
+
267
+ Style it through the grid with `apex-grid::part(...)`: `paginator-host` (the element), `paginator`, `paginator-size`, `paginator-info`, `paginator-controls`, `paginator-page`, and `paginator-button` on each button, which also carries `paginator-first`, `paginator-prev`, `paginator-next` or `paginator-last`:
268
+
269
+ ```css
270
+ apex-grid::part(paginator-first),
271
+ apex-grid::part(paginator-last) { display: none; }
272
+ ```
273
+
274
+ Remote mode: the grid does not slice `data`, it renders whatever `data` holds as the current page. Fetch the page on `pageChanged`, and keep `pagination.totalItems` at the number of rows the server holds for the current filter (update it when a server side filter changes that number), so the paginator can count the pages:
259
275
 
260
276
  ```ts
261
277
  grid.pagination = { enabled: true, mode: 'remote', pageSize: 25, totalItems: 1280 };
@@ -264,7 +280,7 @@ grid.addEventListener('pageChanged', async (e) => {
264
280
  });
265
281
  ```
266
282
 
267
- Properties: `page`, `pageSize`, `pageCount`, `totalItems`, `pageItems`. Events: `pageChanging` (cancellable), `pageChanged`.
283
+ Properties: `page`, `pageSize`, `pageCount`, `totalItems`, `pageItems`. `totalItems` is the post-filter, post-sort row count; with pagination enabled it counts the rows paged through, without pinned rows, which show on every page (in remote mode, the `pagination.totalItems` you supplied), and `pageChanged` carries the same number in `detail.totalItems`. Events: `pageChanging` (cancellable), `pageChanged`.
268
284
 
269
285
  ### Column pinning
270
286
 
@@ -305,6 +321,16 @@ grid.columnSeparator = false; // hide the dividers
305
321
 
306
322
  Theme the line with `--ag-header-separator` (or the `--header-separator-color` override hook) and set its vertical inset with `--apex-header-separator-inset`. Because the default is `true`, disable it through the property rather than markup (a default-on boolean attribute cannot be turned off by attribute alone). Attribute: `column-separator`.
307
323
 
324
+ ### Hiding the header
325
+
326
+ For a compact table, such as a short dashboard tile, leave out the header rows and give their room to the data rows:
327
+
328
+ ```ts
329
+ grid.showHeader = false; // or <apex-grid show-header="false">
330
+ ```
331
+
332
+ The column header row, the column group row and the filter panel take no space, paint nothing and take no pointer, and the built-in header controls leave the tab order, so resizing, the column menu and drag reorder are unavailable while the header is hidden (an open column menu closes). A focusable control you render in a `headerTemplate` stays in the tab order: give it `tabindex="-1"` while the header is hidden. The column header row stays in the accessibility tree, so screen readers still announce the column names, and `aria-rowcount` and `aria-rowindex` do not change. Sort, filter, column widths and the current page are kept and come back unchanged with `grid.showHeader = true`. Attribute: `show-header`.
333
+
308
334
  ### Column groups (spanning headers)
309
335
 
310
336
  ```ts
@@ -388,9 +414,21 @@ A keyboard-driven commit or cancel returns focus to the cell, so navigation
388
414
  continues from where you were. Sorting, filtering, selection, paging, row
389
415
  pinning, reorder, expansion, and undo / redo are announced through a polite
390
416
  live region, and every announcement and control label is localizable (the
391
- `announce.*` and `header.*` locale keys). The host gets a localized
392
- `aria-label` ("Data grid") unless you provide `aria-label` /
393
- `aria-labelledby` yourself.
417
+ `announce.*` and `header.*` locale keys).
418
+
419
+ The `grid` role (`treegrid` for tree data, master-detail and other expandable
420
+ rows), `aria-rowcount`, `aria-colcount` and `aria-multiselectable` sit on the
421
+ element exposed as `part="table"` inside the shadow root, not on the
422
+ `<apex-grid>` host. It holds only the header rows and the body, so the toolbar,
423
+ paginator and live region around it are not read as table content. It gets a
424
+ localized `aria-label` ("Data grid") unless you put `aria-label` or
425
+ `aria-labelledby` on the host, which then names it. An `aria-describedby` on
426
+ the host becomes its description. The references are resolved when the grid
427
+ renders, so if you add the referenced element later (a heading shown
428
+ conditionally), set the attribute again afterwards to pick it up.
429
+
430
+ > Changed in 4.0.0: the role and the row and column counts used to be on the
431
+ > host. Query `grid.shadowRoot.querySelector('[part~="table"]')` instead.
394
432
 
395
433
  > Changed in 3.4.0: Home / End used to jump to the first / last row; they now
396
434
  > stay within the row. Use Ctrl/Cmd+Home / Ctrl/Cmd+End for the grid corners.
@@ -435,6 +473,8 @@ grid.selectedRows; // snapshot
435
473
  grid.selectedRows = [data[2]]; // replace selection (goes through `rowSelecting`)
436
474
  ```
437
475
 
476
+ Every row is selectable by default, pinned rows included. `selection.isRowSelectable: (row) => boolean` keeps rows out of the selection (a pinned total row, for example): such a row renders no checkbox and no `aria-selected`, every selection path skips it (clicks, Space, Select all, `selectRow`, `selectedRows = ...`, `setState`), and the header checkbox counts only selectable rows. Reassigning `selection` or `data` drops selected rows the predicate now rejects; a selected row it comes to reject otherwise (pinned or edited in place) keeps its checkbox until deselected.
477
+
438
478
  Events: `rowSelecting` (cancellable), `rowSelected`.
439
479
 
440
480
  ### Row pinning (top / bottom)
@@ -503,7 +543,7 @@ await grid.toggleTreeRow(data[0]);
503
543
  await grid.expandAllTreeRows();
504
544
  ```
505
545
 
506
- Methods: `toggleTreeRow`, `expandTreeRow`, `collapseTreeRow`, `expandAllTreeRows`, `collapseAllTreeRows`, `isTreeRowExpanded`. Events: `treeRowExpanding` (cancellable), `treeRowExpanded`. When tree mode is active, the host element advertises `role="treegrid"`.
546
+ Methods: `toggleTreeRow`, `expandTreeRow`, `collapseTreeRow`, `expandAllTreeRows`, `collapseAllTreeRows`, `isTreeRowExpanded`. Events: `treeRowExpanding` (cancellable), `treeRowExpanded`. When tree mode is active, the grid's `part="table"` element advertises `role="treegrid"`.
507
547
 
508
548
  ### CSV export
509
549
 
@@ -529,16 +569,19 @@ Renders a download icon in the toolbar's trailing actions area; the menu has an
529
569
 
530
570
  ### Toolbar
531
571
 
532
- Rendered automatically above the header row when at least one of `show-quick-filter` or `show-export` is on. CSS parts:
572
+ Rendered automatically above the header row when at least one of `show-quick-filter` or `show-export` is on. The grid re-exports its CSS parts, so `apex-grid::part(name)` reaches them:
533
573
 
534
574
  | Part | What |
535
575
  |---|---|
576
+ | `toolbar-host` | The `<apex-grid-toolbar>` element |
536
577
  | `toolbar` | Root container |
537
578
  | `toolbar-search` | Quick-filter input wrapper |
538
579
  | `search-field` | The bordered input field |
539
580
  | `search-icon`, `search-input` | Leading icon, input element |
540
581
  | `toolbar-actions` | Trailing actions area |
541
- | `export-trigger` | Export menu button |
582
+ | `toolbar-action` | A host-contributed action button |
583
+ | `export` | Export menu wrapper |
584
+ | `export-trigger`, `export-icon`, `export-caret` | Export menu button and its icons |
542
585
  | `export-menu` | Dropdown panel |
543
586
  | `export-menu-item` | Menu item |
544
587
 
@@ -552,7 +595,7 @@ Style with CSS parts on the grid, paginator, and toolbar:
552
595
 
553
596
  ```css
554
597
  apex-grid::part(paginator) { background: var(--surface-2); }
555
- apex-grid-toolbar::part(search-input) { font-family: var(--font-mono); }
598
+ apex-grid::part(search-input) { font-family: var(--font-mono); }
556
599
  ```
557
600
 
558
601
  Body rows re-export these parts, so `apex-grid::part(name)` reaches them:
@@ -611,6 +654,27 @@ grid.coordinateHints = true;
611
654
 
612
655
  When `coordinateHints` is on, the grid shows a leading row-number gutter (1, 2, 3, and so on) and A / B / C column-letter chips in the headers, so cell references are discoverable (a spreadsheet-style look). Set it directly for a persistent spreadsheet look. `apex-grid-enterprise` turns it on by default for any grid that has an `allowFormula` column, so the gutter is reserved up front and entering a formula never shifts the layout. Reflected to the `coordinate-hints` attribute (default `false`).
613
656
 
657
+ ### Row recycling
658
+
659
+ ```ts
660
+ grid.rowRecycling = false; // opt out
661
+ ```
662
+
663
+ ```html
664
+ <apex-grid row-recycling="false"></apex-grid>
665
+ ```
666
+
667
+ On by default. A scroll, sort or filter re-binds the rendered body rows to the records now in view instead of building new rows, which makes long jumps and sorts cheaper on large grids. A row element, and its cells, then shows another record later, so cell and editor templates follow these rules:
668
+
669
+ - Derive everything a template renders from its context on each render, and keep no state in the elements it creates.
670
+ - Read `ctx.data` for the record, also after an `await`. `ctx.row` and `ctx.parent` are row and cell elements that may show another record by then, so do not hold them across an `await`.
671
+ - Wrap a template that has to keep element state in `keyed(ctx.data, ...)` from `lit/directives/keyed.js`. The state then never shows on the wrong record, but it is rebuilt every time the row shows another record, including after a sort or `moveRow`.
672
+ - Code that holds `grid.rows` elements across a scroll or a sort should read each element's `data` again rather than assume it still shows the same record.
673
+
674
+ Set `rowRecycling` to `false` (or the `row-recycling="false"` attribute) to restore record-keyed rows, where each row element stays with its record. Do that for templates that must keep element state across a sort or a move, such as some third-party widgets.
675
+
676
+ Detail templates are different: their content follows its record into whichever row shows it, and keeps its DOM state (typed text, a nested grid's sort) across a scroll within the rendered rows, a sort or a `moveRow`, so it needs neither `keyed()` nor the opt-out. It is rebuilt when the record leaves the rendered rows or is collapsed, as before. `ctx.parent` in a detail context is the grid itself. A grid created inside a `detailTemplate` has its own `rowRecycling` setting, so set it there too when you opt out.
677
+
614
678
  ### State & persistence
615
679
 
616
680
  `getState()` returns a JSON-safe snapshot of the grid's restorable view and `setState()` applies one back. The snapshot covers column layout (order, width, pinning, visibility), sort, filter, quick filter, pagination, selection, expansion, tree expansion, pinned rows, the manual drag-reorder order, and any feature-module state (enterprise grouping / pivot / aggregation / ranges). Functions and templates are never serialized: sort comparers, filter condition functions, and cell/header/editor templates re-bind from the live `columns` config on restore (filter conditions are captured by operand name).
@@ -647,7 +711,7 @@ See [`demo/state-persistence.html`](../../demo/state-persistence.html) for a sav
647
711
  | `columns` | `ColumnConfiguration<T>[]` | `[]` | Column configuration (property only) |
648
712
  | `autoGenerate` | `boolean` | `false` | Infer columns from `data[0]` keys. Attr `auto-generate` |
649
713
  | `sortConfiguration` | `GridSortConfiguration` | `{ multiple, triState }` | |
650
- | `dataPipelineConfiguration` | `DataPipelineConfiguration<T>` | | Custom sort/filter/pagination hooks |
714
+ | `dataPipelineConfiguration` | `DataPipelineConfiguration<T>` | | Custom sort, filter and quick filter hooks |
651
715
  | `pagination` | `PaginationConfiguration` | | |
652
716
  | `quickFilter` | `string` | `''` | Attr `quick-filter` |
653
717
  | `showQuickFilter` | `boolean` | `false` | Attr `show-quick-filter` |
@@ -662,6 +726,8 @@ See [`demo/state-persistence.html`](../../demo/state-persistence.html) for a sav
662
726
  | `rowReordering` | `GridRowReorderingConfiguration` | | `{ enabled, applyToData?, handle? }` (`handle` default `true`: grip-only drag) |
663
727
  | `coordinateHints` | `boolean` | `false` | Show a row-number gutter + A / B / C header chips (spreadsheet look). Attr `coordinate-hints` |
664
728
  | `columnMenu` | `boolean` | `true` | Show the always-visible column-menu (kebab) button on headers; `false` hides it |
729
+ | `showHeader` | `boolean` | `true` | `false` hides the header rows (kept for screen readers). Attr `show-header="false"` |
730
+ | `rowRecycling` | `boolean` | `true` | Re-binds body row elements across records on scroll, sort and filter; `false` restores record-keyed rows. See [Row recycling](#row-recycling). Attr `row-recycling="false"` |
665
731
  | `rowId` | `(row: T) => string \| number` | | Durable row identity for `getState` / `setState` |
666
732
  | `localeText` | `GridLocaleText` | | Override map for built-in strings (e.g. `esLocale`) |
667
733
  | `canUndo`, `canRedo` | `boolean` | | Get (history) |
@@ -751,14 +817,14 @@ Programmatic `sort()` / `filter()` calls are silent; only UI-initiated changes e
751
817
 
752
818
  ### Attributes
753
819
 
754
- `auto-generate`, `quick-filter`, `show-quick-filter`, `show-export`, `column-reordering`, `coordinate-hints`.
820
+ `auto-generate`, `quick-filter`, `show-quick-filter`, `show-export`, `column-reordering`, `coordinate-hints`, `show-header`, `row-recycling`.
755
821
 
756
822
  ### CSS parts
757
823
 
758
824
  | Component | Parts |
759
825
  |---|---|
760
- | `<apex-grid-toolbar>` | `toolbar`, `toolbar-search`, `search-field`, `search-icon`, `search-input`, `toolbar-actions`, `export-trigger`, `export-menu`, `export-menu-item` |
761
- | `<apex-grid-paginator>` | `paginator`, `paginator-size`, `paginator-info`, `paginator-controls`, `paginator-page` |
826
+ | `<apex-grid-toolbar>` | `toolbar`, `toolbar-search`, `search-field`, `search-icon`, `search-input`, `toolbar-actions`, `toolbar-action`, `export`, `export-trigger`, `export-icon`, `export-caret`, `export-menu`, `export-menu-item` (re-exported by the grid, which names the element `toolbar-host`) |
827
+ | `<apex-grid-paginator>` | `paginator`, `paginator-size`, `paginator-info`, `paginator-controls`, `paginator-page`, `paginator-button`, `paginator-first`, `paginator-prev`, `paginator-next`, `paginator-last` (re-exported by the grid, which names the element `paginator-host`) |
762
828
  | `<apex-grid-cell>` | `cell`, `editor` |
763
829
 
764
830
  ---
@@ -17,6 +17,16 @@ export default class ApexGridCell<T extends object> extends LitElement {
17
17
  * The value which will be rendered by the component.
18
18
  */
19
19
  value: PropertyType<T>;
20
+ /**
21
+ * The record (row data object) the cell shows, set by the row next to
22
+ * {@link value}. A row element can be re-bound to another record without
23
+ * being rebuilt (a pinned band does when a row above it is unpinned), and
24
+ * `value` alone cannot tell: two records may hold the same value. A change
25
+ * here is a full update that drops every per-record state of the cell.
26
+ *
27
+ * @internal
28
+ */
29
+ record: T;
20
30
  /**
21
31
  * A reference to the column configuration object.
22
32
  */
@@ -86,13 +96,15 @@ export default class ApexGridCell<T extends object> extends LitElement {
86
96
  * its row beyond {@link value} (a `cellTemplate`, which may read other fields
87
97
  * or the row index, or the tree group cell, whose chevron shows the row's
88
98
  * expansion) still renders when the row index or the data token moves.
99
+ * A {@link record} change is never a token: the cell renders in full.
89
100
  */
90
101
  protected shouldUpdate(changed: PropertyValues<this>): boolean;
91
102
  protected willUpdate(changed: PropertyValues<this>): void;
92
103
  /**
93
104
  * The inline error node surfaced when the cell is invalid. `role="alert"`
94
105
  * announces the message when it appears, covering the cross-shadow gap where
95
- * an `aria-errormessage` IDREF on the host can't reach this node.
106
+ * an `aria-errormessage` IDREF on the host can't reach this node, so the
107
+ * host sets none (it would dangle).
96
108
  */
97
109
  protected renderError(): import("lit-html").TemplateResult<1> | null;
98
110
  protected updated(changed: PropertyValues<this>): void;
@@ -4,7 +4,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- var _a;
8
7
  import { consume } from '@lit/context';
9
8
  import { html, LitElement } from 'lit';
10
9
  import { property, query } from 'lit/decorators.js';
@@ -62,7 +61,7 @@ function scrollRowIntoRange(scroller, body, index) {
62
61
  const rowCentre = top + (index - first.index + 0.5) * height;
63
62
  scroller.scrollTop += rowCentre - (view.top + scroller.clientHeight / 2);
64
63
  }
65
- class ApexGridCell extends LitElement {
64
+ export default class ApexGridCell extends LitElement {
66
65
  constructor() {
67
66
  super(...arguments);
68
67
  this.active = false;
@@ -77,6 +76,7 @@ class ApexGridCell extends LitElement {
77
76
  this.#staged = false;
78
77
  this.#resumed = null;
79
78
  this.#reconnected = false;
79
+ this.#rebound = false;
80
80
  this.#handleClick = (event) => {
81
81
  if (this.isEditing) {
82
82
  event.stopPropagation();
@@ -170,17 +170,20 @@ class ApexGridCell extends LitElement {
170
170
  }
171
171
  static { this.styles = styles; }
172
172
  static register() {
173
- registerComponent(_a);
173
+ registerComponent(ApexGridCell);
174
174
  }
175
175
  get context() {
176
+ const data = this.row.data;
177
+ const key = this.column.key;
176
178
  const ctx = {
177
179
  parent: this,
178
180
  row: this.row,
179
181
  column: this.column,
180
182
  value: this.value,
183
+ data,
181
184
  };
182
185
  if (this.isEditable) {
183
- ctx.commit = (next) => this.editingController.commitImmediate(this.row.data, this.column.key, next);
186
+ ctx.commit = (next) => this.editingController.commitImmediate(data, key, next);
184
187
  }
185
188
  return ctx;
186
189
  }
@@ -199,12 +202,15 @@ class ApexGridCell extends LitElement {
199
202
  const pin = this.state.rowPin;
200
203
  return pin.hasPinnedRows && pin.enabled && pin.isPinned(this.row.data);
201
204
  }
205
+ get #recycledBody() {
206
+ return Boolean(this.state.host.rowRecycling) && !this.#inPinnedBand;
207
+ }
202
208
  #decoratedKeys;
203
- static #ERROR_ID = 'apex-cell-error';
204
209
  #errors;
205
210
  #staged;
206
211
  #resumed;
207
212
  #reconnected;
213
+ #rebound;
208
214
  connectedCallback() {
209
215
  this.#reconnected = this.hasUpdated && !this.isUpdatePending;
210
216
  super.connectedCallback();
@@ -238,8 +244,9 @@ class ApexGridCell extends LitElement {
238
244
  return false;
239
245
  }
240
246
  willUpdate(changed) {
247
+ this.#rebound = this.hasUpdated && changed.has('record');
241
248
  this.#applyStaged();
242
- if (changed.has('editing')) {
249
+ if (changed.has('editing') || this.#rebound) {
243
250
  this.#resumed = this.editing
244
251
  ? this.editingController.heldDraft(this.row.data, this.column.key)
245
252
  : null;
@@ -289,13 +296,11 @@ class ApexGridCell extends LitElement {
289
296
  this.#errors = errors && errors.length > 0 ? errors : null;
290
297
  if (this.#errors) {
291
298
  this.setAttribute('aria-invalid', 'true');
292
- this.setAttribute('aria-errormessage', _a.#ERROR_ID);
293
299
  this.setAttribute('data-invalid', '');
294
300
  this.setAttribute('title', this.#errors.join('; '));
295
301
  }
296
302
  else {
297
303
  this.removeAttribute('aria-invalid');
298
- this.removeAttribute('aria-errormessage');
299
304
  this.removeAttribute('data-invalid');
300
305
  this.removeAttribute('title');
301
306
  }
@@ -305,12 +310,13 @@ class ApexGridCell extends LitElement {
305
310
  return null;
306
311
  return html `<span
307
312
  part="cell-error"
308
- id=${_a.#ERROR_ID}
309
313
  role="alert"
310
314
  >${this.#errors.join('; ')}</span
311
315
  >`;
312
316
  }
313
317
  #applyDecoration() {
318
+ if (!this.state?.hasCellDecorators && !this.#decoratedKeys.length)
319
+ return;
314
320
  const decoration = this.state && this.row
315
321
  ? this.state.decorateCell({
316
322
  row: this.row.data,
@@ -341,8 +347,13 @@ class ApexGridCell extends LitElement {
341
347
  if (!changed.has('editing'))
342
348
  return;
343
349
  const editor = this.editorElement;
344
- editor?.focus();
345
- if (editor instanceof HTMLInputElement) {
350
+ const resumed = this.#resumed !== null || this.#rebound;
351
+ if (resumed && this.#recycledBody && !this.state.navigation.ownsFocus)
352
+ return;
353
+ if (resumed)
354
+ editor?.getBoundingClientRect();
355
+ editor?.focus(resumed ? { preventScroll: true } : undefined);
356
+ if (!resumed && editor instanceof HTMLInputElement) {
346
357
  if (editor.type === 'text' || editor.type === 'number') {
347
358
  editor.select();
348
359
  }
@@ -351,6 +362,8 @@ class ApexGridCell extends LitElement {
351
362
  }
352
363
  if (changed.get('editing') === true && this.#refocusAfterEdit) {
353
364
  this.#refocusAfterEdit = false;
365
+ if (this.#rebound && this.editingController.activeCell)
366
+ return;
354
367
  this.focus();
355
368
  }
356
369
  }
@@ -482,6 +495,7 @@ class ApexGridCell extends LitElement {
482
495
  row: this.row,
483
496
  column: this.column,
484
497
  value: this.#editorValue,
498
+ data: this.row.data,
485
499
  commit: (next) => this.editingController.commitCell(next),
486
500
  cancel: () => this.editingController.cancelCell(),
487
501
  setDraft: (next) => this.editingController.setDraft(next),
@@ -586,11 +600,12 @@ class ApexGridCell extends LitElement {
586
600
  return html `${this.renderCellBody()}${error}`;
587
601
  }
588
602
  }
589
- _a = ApexGridCell;
590
- export default ApexGridCell;
591
603
  __decorate([
592
604
  property({ attribute: false })
593
605
  ], ApexGridCell.prototype, "value", void 0);
606
+ __decorate([
607
+ property({ attribute: false })
608
+ ], ApexGridCell.prototype, "record", void 0);
594
609
  __decorate([
595
610
  property({ attribute: false })
596
611
  ], ApexGridCell.prototype, "column", void 0);