apex-grid 3.6.0 → 4.0.0-beta.2

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 (151) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/LICENSE.md +2 -1
  3. package/README.md +117 -18
  4. package/components/cell.d.ts +13 -1
  5. package/components/cell.js +28 -13
  6. package/components/cell.js.map +1 -1
  7. package/components/grid.d.ts +175 -28
  8. package/components/grid.js +183 -63
  9. package/components/grid.js.map +1 -1
  10. package/components/header-row.d.ts +0 -6
  11. package/components/header-row.js +1 -19
  12. package/components/header-row.js.map +1 -1
  13. package/components/header.js +39 -2
  14. package/components/header.js.map +1 -1
  15. package/components/paginator.d.ts +20 -5
  16. package/components/paginator.js +54 -17
  17. package/components/paginator.js.map +1 -1
  18. package/components/row.js +149 -40
  19. package/components/row.js.map +1 -1
  20. package/components/toolbar.d.ts +5 -1
  21. package/components/toolbar.js +4 -1
  22. package/components/toolbar.js.map +1 -1
  23. package/components/virtualize-rows.d.ts +38 -0
  24. package/components/virtualize-rows.js +286 -0
  25. package/components/virtualize-rows.js.map +1 -0
  26. package/components/virtualizer.d.ts +71 -1
  27. package/components/virtualizer.js +105 -29
  28. package/components/virtualizer.js.map +1 -1
  29. package/controllers/dom.d.ts +10 -3
  30. package/controllers/dom.js +5 -5
  31. package/controllers/dom.js.map +1 -1
  32. package/controllers/navigation.d.ts +63 -0
  33. package/controllers/navigation.js +295 -21
  34. package/controllers/navigation.js.map +1 -1
  35. package/controllers/pagination.d.ts +7 -6
  36. package/controllers/pagination.js +27 -28
  37. package/controllers/pagination.js.map +1 -1
  38. package/controllers/row-pin.d.ts +6 -0
  39. package/controllers/row-pin.js +8 -2
  40. package/controllers/row-pin.js.map +1 -1
  41. package/controllers/row-reorder.d.ts +12 -4
  42. package/controllers/row-reorder.js +20 -6
  43. package/controllers/row-reorder.js.map +1 -1
  44. package/controllers/selection.d.ts +32 -10
  45. package/controllers/selection.js +33 -10
  46. package/controllers/selection.js.map +1 -1
  47. package/controllers/sort.js +2 -3
  48. package/controllers/sort.js.map +1 -1
  49. package/controllers/state.d.ts +18 -5
  50. package/controllers/state.js +53 -46
  51. package/controllers/state.js.map +1 -1
  52. package/custom-elements.json +1762 -339
  53. package/docs/assets/hierarchy.js +1 -1
  54. package/docs/assets/search.js +1 -1
  55. package/docs/classes/ApexGrid.html +166 -66
  56. package/docs/classes/ApexGridPaginator.html +39 -8
  57. package/docs/classes/ApexGridToolbar.html +9 -5
  58. package/docs/functions/localize.html +2 -2
  59. package/docs/hierarchy.html +1 -1
  60. package/docs/index.html +123 -20
  61. package/docs/interfaces/ApexPageChangedEvent.html +3 -1
  62. package/docs/interfaces/BaseApexCellContext.html +18 -5
  63. package/docs/interfaces/BaseApexEditorContext.html +20 -5
  64. package/docs/interfaces/BaseFilterExpression.html +1 -1
  65. package/docs/interfaces/CSVExportOptions.html +1 -1
  66. package/docs/interfaces/DataPipelineConfiguration.html +4 -9
  67. package/docs/interfaces/ExportOptions.html +1 -1
  68. package/docs/interfaces/GridSelectionConfiguration.html +19 -4
  69. package/docs/interfaces/PaginationConfiguration.html +11 -4
  70. package/docs/interfaces/PaginationState.html +4 -2
  71. package/docs/media/_token-values.scss +42 -16
  72. package/docs/media/package.json +12 -4
  73. package/docs/types/DataPipelineParams.html +2 -2
  74. package/docs/types/PaginationMode.html +5 -4
  75. package/docs/variables/EN_LOCALE.html +2 -2
  76. package/i18n/en.d.ts +2 -0
  77. package/i18n/en.js +2 -0
  78. package/i18n/en.js.map +1 -1
  79. package/i18n/es.js +2 -0
  80. package/i18n/es.js.map +1 -1
  81. package/ignite-ui.d.ts +1 -0
  82. package/ignite-ui.js +4 -0
  83. package/ignite-ui.js.map +1 -0
  84. package/internal/column-types.d.ts +11 -1
  85. package/internal/column-types.js +8 -6
  86. package/internal/column-types.js.map +1 -1
  87. package/internal/design-system.d.ts +32 -0
  88. package/internal/design-system.js +66 -0
  89. package/internal/design-system.js.map +1 -0
  90. package/internal/export.d.ts +6 -0
  91. package/internal/export.js +4 -0
  92. package/internal/export.js.map +1 -1
  93. package/internal/feature-module.d.ts +7 -0
  94. package/internal/feature-module.js.map +1 -1
  95. package/internal/flip.d.ts +28 -16
  96. package/internal/flip.js +42 -10
  97. package/internal/flip.js.map +1 -1
  98. package/internal/icons.d.ts +2 -16
  99. package/internal/icons.js +0 -8
  100. package/internal/icons.js.map +1 -1
  101. package/internal/ignite-ui.d.ts +10 -0
  102. package/internal/ignite-ui.js +118 -0
  103. package/internal/ignite-ui.js.map +1 -0
  104. package/internal/index.d.ts +4 -2
  105. package/internal/index.js +3 -2
  106. package/internal/index.js.map +1 -1
  107. package/internal/types.d.ts +75 -19
  108. package/internal/types.js.map +1 -1
  109. package/operations/filter/types.d.ts +1 -1
  110. package/operations/filter/types.js.map +1 -1
  111. package/package.json +7 -2
  112. package/styles/_token-blocks.css.d.ts +1 -0
  113. package/styles/_token-blocks.css.js +3 -0
  114. package/styles/_token-blocks.css.js.map +1 -0
  115. package/styles/_tokens.css.js +1 -1
  116. package/styles/_tokens.css.js.map +1 -1
  117. package/styles/body-cell/body-cell.css.js +1 -1
  118. package/styles/body-cell/body-cell.css.js.map +1 -1
  119. package/styles/body-cell/cell-renderers.css.js +1 -1
  120. package/styles/body-cell/cell-renderers.css.js.map +1 -1
  121. package/styles/body-row/body-row.css.js +1 -1
  122. package/styles/body-row/body-row.css.js.map +1 -1
  123. package/styles/document-tokens.css.js +1 -1
  124. package/styles/document-tokens.css.js.map +1 -1
  125. package/styles/grid/_grid.base.css.js +1 -1
  126. package/styles/grid/_grid.base.css.js.map +1 -1
  127. package/styles/grid/grid.css.js +1 -1
  128. package/styles/grid/grid.css.js.map +1 -1
  129. package/styles/group-header-row/group-header-row.css.js +1 -1
  130. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  131. package/styles/header-cell/header-cell.css.js +1 -1
  132. package/styles/header-cell/header-cell.css.js.map +1 -1
  133. package/styles/header-row/header-row.base.css.js +1 -1
  134. package/styles/header-row/header-row.base.css.js.map +1 -1
  135. package/styles/ignite-ui/ignite-ui-dark.css.d.ts +1 -0
  136. package/styles/ignite-ui/ignite-ui-dark.css.js +3 -0
  137. package/styles/ignite-ui/ignite-ui-dark.css.js.map +1 -0
  138. package/styles/ignite-ui/ignite-ui-document.css.d.ts +1 -0
  139. package/styles/ignite-ui/ignite-ui-document.css.js +3 -0
  140. package/styles/ignite-ui/ignite-ui-document.css.js.map +1 -0
  141. package/styles/ignite-ui/ignite-ui.css.d.ts +1 -0
  142. package/styles/ignite-ui/ignite-ui.css.js +3 -0
  143. package/styles/ignite-ui/ignite-ui.css.js.map +1 -0
  144. package/styles/paginator/paginator.css.js +1 -1
  145. package/styles/paginator/paginator.css.js.map +1 -1
  146. package/styles/toolbar/toolbar.css.js +1 -1
  147. package/styles/toolbar/toolbar.css.js.map +1 -1
  148. package/tokens.css +14 -15
  149. package/operations/pagination.d.ts +0 -19
  150. package/operations/pagination.js +0 -12
  151. package/operations/pagination.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,307 @@ 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.2] - 2026-11-08
10
+
11
+ A second pre-release of 4.0.0, under the `next` npm tag. It adds opt-in
12
+ Ignite UI theming (`apex-grid/ignite-ui`) and the family `--apx-*` tokens.
13
+ Breaking against 4.0.0-beta.1 and 3.x: an Ignite UI stylesheet no longer
14
+ re-tints the brand or replaces the font until you import the add-on, and
15
+ `--ag-font` has no root default any more (see Changed and Fixed).
16
+
17
+ ### Added
18
+ - **`apex-grid/ignite-ui`, the Ignite UI add-on.** `import 'apex-grid/ignite-ui'`
19
+ once and every grid on the page follows the Ignite UI theme the page loads:
20
+ brand, text and muted text, gridlines and the pinned-column edge, the header
21
+ (`--ig-surface-raised`, Ignite UI 7.5 and later), state colours, font and the
22
+ enterprise popups (`--ig-surface-overlay` on 7.5 and later). A grid without
23
+ `theme="dark"` or `theme="light"` follows the Ignite UI variant, live, with
24
+ a JS fallback where the browser has no container style queries;
25
+ `theme="light"` keeps a grid light. Ranking: an `--ag-*` token you set, then
26
+ the family `--apx-*` tokens, then Ignite UI, then the built-in values. Per
27
+ grid, `design-system="off"` turns the Ignite UI colours and font off (the
28
+ variant still picks the mode) and `tokens="off"` turns off the family tokens
29
+ and Ignite UI, variant included (both also as the reflected `designSystem`
30
+ and `tokens` properties). It registers on a shared `globalThis` slot, so it
31
+ reaches the grids of a separately built bundle too. While the add-on is
32
+ loaded, the tokens that follow Ignite UI (text, lines, surfaces, header,
33
+ state colours, pinned edge) leave the root defaults, so page CSS that reads
34
+ one needs its own fallback. It adds about 2.5 KB gzip to an app, and only
35
+ where it is imported.
36
+ - **The family `--apx-*` tokens** shared by every Apex product: `--apx-accent`
37
+ (brand), `--apx-fore` (text), `--apx-grid` (gridlines, row borders, header
38
+ separator), `--apx-surface` (card and row background), `--apx-font-family`
39
+ and `--apx-radius-factor`, each ranked below the matching `--ag-*` token.
40
+ The text, line and surface root defaults are now written as their family
41
+ chain (`--ag-text: var(--apx-fore, #111827)` in `:where(:root)` and
42
+ `apex-grid/tokens.css`), so they follow a family token set on `:root`.
43
+ - `--apx-radius-factor` scales `--ag-radius`, `--ag-radius-sm` and
44
+ `--ag-radius-xs` while it is set. Unset or 1, every radius is unchanged
45
+ (a multi-value radius too, without a factor); the pill stays round.
46
+ - `tokens="off"` (or `grid.tokens = false`) keeps one grid, and the content it
47
+ renders, off the family tokens, with or without the add-on. Without the
48
+ add-on, an `--apx-fore`, `--apx-grid` or `--apx-surface` set on `:root` still
49
+ reaches that grid through the root defaults, which resolve there.
50
+ - `--ag-overlay` and `--ag-overlay-text` (dark grids: `--ag-dark-overlay`,
51
+ `--ag-dark-overlay-text`) colour the `apex-grid-enterprise` popups. They have
52
+ no default: unset, each popup keeps its built-in colours.
53
+
54
+ ### Changed
55
+ - **Breaking: the grid no longer reads Ignite UI tokens by itself.** Before,
56
+ an Ignite UI theme on the page re-tinted the brand (`--ig-primary-500`,
57
+ `--ig-primary-700`) and replaced the font (`--ig-font-family`) with no
58
+ import. Now an Ignite UI stylesheet changes nothing until you import
59
+ `apex-grid/ignite-ui`, which brings the brand and font back along with the
60
+ rest of the theme.
61
+ - The grid now follows the family `--apx-*` tokens (above). A page that sets
62
+ them for its charts, on `:root` for example, sees the grid follow them too.
63
+ Set the matching `--ag-*` tokens to keep the grid as it was, or set the
64
+ family tokens on the elements they are meant for.
65
+
66
+ ### Fixed
67
+ - An explicit `--ag-font` now wins over the family and Ignite UI fonts, the
68
+ same order the brand tokens use. Before, an Ignite UI theme on the page
69
+ replaced the grid font even when `--ag-font` was set on the grid itself. The
70
+ default Inter stack is unchanged. To keep the family and Ignite UI fonts
71
+ reachable, `--ag-font` no longer has a root default (it is gone from
72
+ `:where(:root)` and `apex-grid/tokens.css`), so page CSS that reads
73
+ `var(--ag-font)` needs its own fallback, as with `--ag-brand`.
74
+
75
+ ## [4.0.0-beta.1] - 2026-10-29
76
+
77
+ A pre-release of 4.0.0, published under the `next` npm tag for trying out
78
+ before it becomes `latest`. It is a major version because these changes can
79
+ break existing code:
80
+
81
+ - **Body rows are recycled by default** (`rowRecycling`). A row element and
82
+ its cells can show another record after a scroll, sort or filter, so a
83
+ template that keeps state in the elements it creates, or code that holds
84
+ `grid.rows` elements or `ctx.row` across an `await`, can see another
85
+ record. Set `rowRecycling = false` (or `row-recycling="false"`) to keep
86
+ record-keyed rows.
87
+ - **The grid role moved off the host.** `role`, `aria-rowcount`,
88
+ `aria-colcount`, `aria-multiselectable` and the accessible name are on the
89
+ inner `[part~="table"]` element.
90
+ - **Types removed:** `DataPipelineConfiguration.pagination` and the
91
+ `'pagination'` value of `DataPipelineParams.type`.
92
+ - **Pagination:** pinned rows no longer count toward `totalItems` and the
93
+ pages, and a local page change no longer calls custom sort and filter hooks.
94
+ - **Smaller type and DOM changes:** `ctx.data` is a required field of the cell
95
+ context, `ApexGridPaginator.pageSizeOptions` is `readonly number[]`, the
96
+ paginator's previous button id is `prev`, and the protected
97
+ `ApexGrid.headerRowCount` and `ApexGridHeaderRow.renderDragGhost` are gone.
98
+
99
+ See Changed and Removed for the details and for other changes apps may notice.
100
+
101
+ ### Added
102
+ - The `ai.cancelled` locale key (English and Spanish), used by the enterprise
103
+ Ask AI panel.
104
+ - The `chart.rangeResized` locale key (English and Spanish), announced when an
105
+ enterprise charted range is resized from the keyboard.
106
+ - **`showHeader`** (attribute `show-header="false"`) leaves out the column
107
+ header row, the column group row and the filter panel, so a short grid such as
108
+ a dashboard tile gives that room to the data rows. Screen readers still
109
+ announce the column names, and sort, filter, column widths and the page come
110
+ back unchanged when the header returns. While it is hidden, resizing, the
111
+ column menu, drag reorder and the header filter button are unavailable.
112
+ ([#35](https://github.com/apexcharts/apex-grid/issues/35))
113
+ - **`selection.isRowSelectable(row)`** keeps chosen rows out of selection,
114
+ such as a pinned total row. Such a row shows no checkbox, and clicks, Shift
115
+ and Ctrl clicks, Space, Select all, `selectRow` and `setState` all skip it;
116
+ the header checkbox counts only selectable rows. Rows stay selectable by
117
+ default, pinned rows included.
118
+ ([#38](https://github.com/apexcharts/apex-grid/issues/38))
119
+ - `pagination.paginator: 'compact'` draws the small paginator: summary,
120
+ previous, page / count, next.
121
+ ([#36](https://github.com/apexcharts/apex-grid/issues/36))
122
+ - `apex-grid::part()` reaches every paginator and toolbar part
123
+ (`paginator-host`, `paginator`, `paginator-size`, `paginator-info`,
124
+ `paginator-controls`, `paginator-page`, `paginator-button`, `toolbar-host`,
125
+ `toolbar`, `toolbar-search`, `search-input`, `toolbar-actions`, `export`,
126
+ `export-menu` and the rest), and each paginator button also carries its own
127
+ part: `paginator-first`, `paginator-prev`, `paginator-next` or
128
+ `paginator-last`. ([#37](https://github.com/apexcharts/apex-grid/issues/37))
129
+ - **`ctx.data` in cell and editor templates.** The context handed to a
130
+ `cellTemplate` or an `editorTemplate` carries `data`, the record the cell
131
+ was rendered for. It stays that record after an `await`, while `ctx.row`
132
+ and `ctx.parent` are elements that a pinned row band can later re-bind to
133
+ another record. A `cellTemplate`'s `ctx.commit` writes to `ctx.data`. An
134
+ editor's `ctx.commit` still commits the cell in edit when it is called.
135
+
136
+ ### Changed
137
+ - **Body rows are now recycled by default** (new `rowRecycling` property,
138
+ default `true`). A scroll, sort or filter re-binds the rendered row elements
139
+ to the records now in view instead of building new rows, which makes long
140
+ jumps and sorts cheaper on large grids. A row element, and its cells, can
141
+ therefore show another record later. Cell and editor templates should
142
+ derive what they render from their context on each render, read
143
+ `ctx.data` for the record, and not hold `ctx.row` or `ctx.parent` across an
144
+ `await`; a template that keeps element state can wrap itself in
145
+ `keyed(ctx.data, ...)`. What you might notice: a template that keeps state
146
+ in the elements it creates (an input's typed value, a third-party widget)
147
+ can show it on another record after a scroll or a sort, and code that holds
148
+ `grid.rows` elements across a scroll or a sort finds them showing other
149
+ records. To keep record-keyed rows, set `grid.rowRecycling = false` (or the
150
+ `row-recycling="false"` attribute). Detail panel content follows its record
151
+ and keeps its DOM state across a scroll, a sort or a `moveRow`, so detail
152
+ templates need no change; a grid created inside a `detailTemplate` keeps its
153
+ own setting.
154
+ - **The grid role and its row counts moved from the `<apex-grid>` host to an
155
+ inner element** exposed as part `table`: `role` (`grid` or `treegrid`),
156
+ `aria-rowcount`, `aria-colcount`, `aria-multiselectable` and the accessible
157
+ name. It holds only the header rows and the body, so the toolbar, paginator,
158
+ filter panel and live region are no longer read as table content, and every
159
+ default grid passes the axe `aria-required-children` rule. Code or tests
160
+ that read these from the host should query
161
+ `grid.shadowRoot.querySelector('[part~="table"]')`. The layout is unchanged.
162
+ - An `aria-label` or `aria-labelledby` on the host still names the grid, and
163
+ `aria-describedby` becomes its description. References are resolved when the
164
+ grid renders: if the referenced element is added later, set the attribute
165
+ again.
166
+ - The grid uses the `treegrid` role whenever rows carry levels or expand: tree
167
+ data, master-detail with a detail template, and the enterprise row grouping,
168
+ expandable pivot and server-side grouping.
169
+ - `pageItems` keeps the same array between updates while nothing that shapes
170
+ it changes. Apps that compared it by identity now see a new array only when
171
+ the body rows change.
172
+ - The column header row is no longer a Tab stop.
173
+ - **The built-in column menu opens in the top layer** where the browser supports
174
+ the Popover API: pinned rows, the paginator and the grid's edge no longer
175
+ cover or clip it, it stays on its header cell through any scroll or layout
176
+ change, and it closes when its column scrolls out of the grid. It can now
177
+ extend outside the grid's box.
178
+ - The column drag ghost renders at the grid root, so it stays above the column
179
+ group header and pinned top rows, and `apex-grid::part(drag-ghost)` now
180
+ reaches it.
181
+ - `--filtering-header-background` applies to every filtered header, pinned and
182
+ dragged ones included, as a layer over the header's own background, and
183
+ filtered headers get the same hover highlight as other headers.
184
+ - The paginator follows `--header-background` and group header labels follow
185
+ `--header-text-color`, like the rest of the header area. The paginator text
186
+ size follows `--ag-fs-cell-secondary`, and the filter count badge uses
187
+ `--ag-text-on-brand` and `--ag-fw-semibold`. Default rendering is unchanged.
188
+ - Forced colors (Windows High Contrast): checkboxes are drawn by the browser in
189
+ system colours, so apps that styled them through parts see the native
190
+ checkbox in that mode only.
191
+ - With pagination, pinned rows no longer count toward the pages: `totalItems`,
192
+ `pageCount`, the paginator summary and the `pageChanged` detail count the
193
+ rows the pager pages through, and pinning a row on the last page moves to the
194
+ new last page. Without pagination `totalItems` still counts every row.
195
+ - A local page or page size change no longer reruns sorting, filtering and the
196
+ rest of the pipeline, so custom sort and filter hooks are no longer called on
197
+ a local page change. Remote mode still reruns it.
198
+ - `pageChanged` reports the change that was applied, so two quick
199
+ `nextPage()` calls report page 2 and then page 3.
200
+ - A page change is announced once ("Page X of N"); the paginator summary and
201
+ page label are no longer live regions.
202
+ - The first grid on a page adds one window `error` listener that cancels
203
+ "ResizeObserver loop" messages and hides them from listeners added after it.
204
+ Listeners registered before the first grid still receive them. Every other
205
+ error is untouched. ([#32](https://github.com/apexcharts/apex-grid/issues/32))
206
+ - `totalItems` is described one way everywhere: the post-filter, post-sort
207
+ row count, and in remote mode the `pagination.totalItems` you supply, the
208
+ number of rows the server holds for the current filter.
209
+ ([#34](https://github.com/apexcharts/apex-grid/issues/34))
210
+ - Type and DOM details for code that builds on the grid's internals:
211
+ `BaseApexCellContext.data` is required, so code that builds a cell context
212
+ by hand must supply it; `ApexGridPaginator.pageSizeOptions` is typed
213
+ `readonly number[]`; the paginator's previous button has the id `prev`
214
+ (it was `previous`) inside its shadow root; and the protected
215
+ `ApexGrid.headerRowCount` and `ApexGridHeaderRow.renderDragGhost` were
216
+ removed, which affects subclasses only.
217
+ - The `@lit-labs/virtualizer` dependency is pinned to exactly `2.1.1` (it was
218
+ `~2.1.0`). The grid body builds on parts of that package that are not public
219
+ API, so a newer version arrives only with a grid release that has been
220
+ checked against it.
221
+
222
+ ### Removed
223
+ - **Breaking (types):** `DataPipelineConfiguration.pagination` and the
224
+ `'pagination'` value of `DataPipelineParams.type`. The hook was documented
225
+ but never called, so no runtime behaviour changes; TypeScript code that sets
226
+ it stops compiling. For server paging use `pagination.mode: 'remote'`, fetch
227
+ the page on `pageChanged` and keep `pagination.totalItems` current.
228
+ ([#33](https://github.com/apexcharts/apex-grid/issues/33))
229
+
230
+ ### Fixed
231
+ - `aria-rowcount` no longer counts the floating filter panel as a row, and a
232
+ pinned top row no longer shares its `aria-rowindex` with the first body row.
233
+ - The master-detail panel sits in a full-width gridcell, and `aria-expanded` is
234
+ set only on rows and toggle buttons that can expand. Turning expansion off or
235
+ removing the detail template no longer leaves it on rows.
236
+ - Announcing the same message twice in a row (two identical `grid.announce()`
237
+ calls, for example) is read out again by screen readers.
238
+ - Invalid cells no longer set an `aria-errormessage` that pointed at an
239
+ unreachable id. The inline alert message is unchanged.
240
+ - Range and formula reference highlights on a pinned column no longer let
241
+ scrolled cells show through it.
242
+ - Alternating row stripes follow the row index, so they no longer flip or
243
+ flicker while you scroll.
244
+ - In the dark theme the default, muted and gold badges are readable and the
245
+ progress bar track is visible. The light theme is unchanged.
246
+ - The selected row accent bar sits on the leading edge in right to left
247
+ layouts, including a left to right grid nested in a right to left one.
248
+ - Forced colors: checked and indeterminate checkboxes no longer look unchecked;
249
+ in a selected row the cell text, row number, reorder grip and chevrons take
250
+ the selection text colour; an editor opened in a selected row uses the
251
+ system field colours; a keyboard-grabbed row shows an outline.
252
+ - The paginator no longer clips its controls in a narrow grid: it drops the
253
+ rows per page selector, then the first and last buttons, so previous and next
254
+ stay reachable. ([#36](https://github.com/apexcharts/apex-grid/issues/36))
255
+ - Grids whose cell text wraps no longer raise "ResizeObserver loop completed
256
+ with undelivered notifications" while they resize or scroll.
257
+ ([#32](https://github.com/apexcharts/apex-grid/issues/32))
258
+ - With row pinning and pagination, no trailing page is left empty.
259
+ - A `pageSize` missing from `pageSizeOptions` now appears in the rows per
260
+ page selector and is selected.
261
+ - A page size change or quick filter cancelled through `pageChanging` or
262
+ `quickFilterChanging` no longer leaves the selector or search box showing
263
+ the refused value.
264
+ - **Pinned rows no longer show the previous row's state.** When a pinned row
265
+ element switches to another record (a row above it is unpinned, say), it no
266
+ longer keeps the previous record's staged row edit value, validation error,
267
+ expanded state, detail panel content or selection checkbox state, even when
268
+ both records hold the same cell value.
269
+ - A `cellTemplate` that calls `ctx.commit` after an `await` writes to the
270
+ record it was rendered for, even if the rows changed in the meantime.
271
+ - Moving a keyboard grabbed row with the arrow keys keeps focus on that row
272
+ instead of dropping it to the page, and Escape puts the active cell and
273
+ focus back on the row at its original position.
274
+ - The floating ghost of a row drag shows the row's cell text. It showed empty
275
+ cells before.
276
+ - An editor rendered again for an edit already in progress (its row scrolled
277
+ back into view, for example) no longer scrolls the grid or selects the typed
278
+ draft when it takes focus.
279
+ - With an async `dataPipelineConfiguration` hook, moving a row with
280
+ `moveRow`, a keyboard grab or a drag animates the rows into their new places.
281
+ They jumped there without the animation before.
282
+ - A jump to a row far outside the view that is still waiting for the body to
283
+ finish scrolling no longer runs after a newer jump, so two quick jumps end on
284
+ the second row instead of snapping back to the first.
285
+ - A pinned row that switches to another record no longer animates from the
286
+ previous record's look: its selected wash, expand chevron, checkbox and
287
+ progress bars show the new record's state at once.
288
+ - An image cell whose URL changes (a pinned row switching records, an edit of
289
+ the URL) renders a new `<img>`, so the previous picture no longer stays on
290
+ screen while the new one loads.
291
+ - Arrow and Page keys in a grid whose rows grew after they were first shown
292
+ (detail panels expanded, wrapped text) keep the active cell in view and
293
+ focused. A quick run of key presses could leave it unrendered, with focus on
294
+ the body or on a cell of another record, until the next click.
295
+ - When a sort, a filter or a data change moves the record of an open cell
296
+ edit to another row that is still in view, the active cell moves with it, so
297
+ the keyboard carries on from the edited cell once the edit closes. It used to
298
+ stay on the old row, which then showed another record.
299
+
300
+ ### Performance
301
+ - Scrolling no longer re-renders the rows that stay in view; only the rows that
302
+ scroll in render.
303
+ - A grid with a pinned row or a page slice no longer rescans the whole view on
304
+ every key press or click.
305
+ - Grids without feature modules skip the per-cell decoration step, and grids
306
+ with modules no longer check every module on each row and cell render.
307
+
7
308
  ## [3.6.0] - 2026-10-21
8
309
 
9
310
  A quality release built from a full code-quality audit, plus `--ag-*` tokens
package/LICENSE.md CHANGED
@@ -85,10 +85,11 @@ Required if you are **embedding ApexGrid into a product or platform used by othe
85
85
  ✅ What's included:
86
86
 
87
87
  - Redistribution rights for **one application or product**
88
- - **Unlimited developer seats** on that application, with no separate per-developer license to buy
89
88
  - **Every licensed feature**, at the highest tier
90
89
  - 12-month subscription with updates & support
91
90
 
91
+ ⚠️ **Developer licenses are purchased separately.** The OEM License grants redistribution rights and carries no developer seats. Each developer who works with ApexGrid in that application also needs a **Community + Commercial** license of their own. That is the tier whatever features your product uses, because the license key comes from the OEM License and already renders every licensed feature.
92
+
92
93
  Your end users need no license of their own. A second application needs a second OEM license. White-label, resale, and affiliate arrangements are quoted separately.
93
94
 
94
95
  ✅ OEM **not required** if your app simply renders static grids and users **cannot** configure or interact with them.
package/README.md CHANGED
@@ -29,7 +29,7 @@ A Lit-based, framework-agnostic web component data grid. Ships as a single custo
29
29
  - **CSV export**: programmatic method plus an optional toolbar dropdown. (Excel/XLSX export is in `apex-grid-enterprise`.)
30
30
  - **Toolbar**: opt-in `<apex-grid-toolbar>` with debounced quick filter and export menu.
31
31
  - **Templating**: slot-based templates for cells, headers, editors, and detail panels.
32
- - **Theming**: styled out-of-the-box; fully customizable through `--ag-*` CSS custom properties (no theme import or build step). Auto-tints to an Ignite UI host app's palette when one is present, with no dependency on it.
32
+ - **Theming**: styled out-of-the-box; fully customizable through `--ag-*` CSS custom properties (no theme import or build step), and follows the `--apx-*` tokens shared by every Apex product. One import (`apex-grid/ignite-ui`) makes it follow an Ignite UI theme, light or dark.
33
33
  - **Accessibility**: WCAG 2.2 AA semantics (`role="grid"` / `role="treegrid"`, `aria-rowcount`, `aria-colcount`, focus + keyboard navigation).
34
34
  - **Provenance-signed npm releases** with OIDC trusted publishing.
35
35
 
@@ -130,11 +130,15 @@ See [`src/styles/_token-values.scss`](src/styles/_token-values.scss) for the ful
130
130
  import 'apex-grid/tokens.css';
131
131
  ```
132
132
 
133
- The grid renders the same without the sheet; it carries the defaults as fallbacks. Three groups have no root default, so page CSS reading them needs its own fallback:
133
+ The grid renders the same without the sheet; it carries the defaults as fallbacks. Four groups have no root default, so page CSS reading them needs its own fallback:
134
134
 
135
- - `--ag-brand` and `--ag-brand-strong`, so an Ignite UI palette set on any ancestor keeps tinting the grid (see below).
135
+ - `--ag-brand` and `--ag-brand-strong`, so a family `--apx-accent` (or, with the Ignite UI add-on, an Ignite UI palette) set on any ancestor still reaches the grid (see below).
136
+ - `--ag-font`, for the same reason (`--apx-font-family`, an Ignite UI font).
137
+ - `--ag-overlay` and `--ag-overlay-text` (and `--ag-dark-overlay`, `--ag-dark-overlay-text`): unset, the enterprise popups keep their own colours (see below).
136
138
  - The derived washes `--ag-brand-soft`, `--ag-brand-softer`, `--ag-brand-line`, `--ag-row-hover` and `--ag-surface-alt`. The grid mixes them from the brand and surface in effect, so they follow a brand set on an ancestor. Set them only to override the mix.
137
139
 
140
+ The root defaults of the text, line and surface tokens are written as their family chain (`--ag-text: var(--apx-fore, #111827)`), so they follow a family token set on `:root` (see below).
141
+
138
142
  **Dark mode.** `theme="dark"` reads its own colour inputs, `--ag-dark-*` (`--ag-dark-surface`, `--ag-dark-text`, `--ag-dark-hairline`, `--ag-dark-shadow-card`, and so on), so a light palette set on a shared ancestor never puts a white surface under the dark theme's light text. Sizing, typography, motion, the brand and the base state colours are shared by both modes. To theme both from one place, set both palettes:
139
143
 
140
144
  ```css
@@ -155,7 +159,34 @@ apex-grid[theme~='dark'] { --ag-grid-shadow: var(--ag-dark-shadow-card); } /* th
155
159
  apex-grid { --ag-grid-shadow: none; } /* remove the edge entirely */
156
160
  ```
157
161
 
158
- If you embed the grid in an Ignite UI app, the brand tokens automatically re-tint from its palette, wherever it is declared: they read `--ig-primary-500` with the grid's own default as the fallback. That is a plain CSS variable lookup, so the grid neither depends on nor bundles Ignite UI.
162
+ **Enterprise popups.** `--ag-overlay` and `--ag-overlay-text` colour the popups of `apex-grid-enterprise` (filter builder, set filter, AI panel, tool panel, status bar); dark grids read `--ag-dark-overlay` and `--ag-dark-overlay-text`. Set both: the popup lines, muted text and hover wash are mixed from the two. Unset, each popup keeps its built-in colours.
163
+
164
+ **Family tokens (`--apx-*`).** The grid also reads the tokens every Apex product shares, ranked below its own: `--apx-accent` (brand), `--apx-fore` (text), `--apx-grid` (gridlines, row borders, header separator), `--apx-surface` (card and row background), `--apx-font-family` and `--apx-radius-factor`. An `--ag-*` or `--ag-dark-*` token you set wins over them, and they apply to light and dark grids alike. The brand, font and radius factor reach the grid from any ancestor. Text, lines and surface have root defaults, which resolve on `:root`: they follow a family token set on `:root` (or, with the Ignite UI add-on, which lifts those defaults, on any ancestor). `--apx-radius-factor` scales `--ag-radius`, `--ag-radius-sm` and `--ag-radius-xs` while it is set; at 1 nothing changes, the pill shape stays round, and a multi-value radius (`--ag-radius: 12px 12px 0 0`) works only without a factor.
165
+
166
+ `tokens="off"` on a grid (or `grid.tokens = false`) keeps it off the family tokens and the content it renders too. One limit without the Ignite UI add-on: an `--apx-fore`, `--apx-grid` or `--apx-surface` set on `:root` has already resolved into the text, line and surface root defaults there, which every grid reads, so that grid still shows it; set the matching `--ag-*` tokens on it, or set the family tokens on the elements they are meant for.
167
+
168
+ ```css
169
+ :root {
170
+ --apx-accent: #7c3aed;
171
+ --apx-font-family: 'Source Sans 3', sans-serif;
172
+ --apx-radius-factor: 0.5;
173
+ }
174
+ ```
175
+
176
+ **Ignite UI.** To follow an Ignite UI theme, import the add-on once:
177
+
178
+ ```ts
179
+ import 'apex-grid/ignite-ui';
180
+ ```
181
+
182
+ Every grid on the page, in the document or in any shadow root, then follows the Ignite UI theme the page loads, including grids that connected before the import: the brand (`--ig-primary-*`), text and muted text, gridlines and the pinned-column edge (the gray ramp), the header (`--ig-surface-raised`, Ignite UI 7.5 and later), the state colours, the font, and the enterprise popups (`--ig-surface-overlay` on 7.5 and later, else the grid's own popup colours). The card and row surfaces keep the grid's own light or dark background. Without the add-on the grid reads no `--ig-*` token, and an Ignite UI stylesheet changes nothing.
183
+
184
+ - **Ranking:** an `--ag-*` token you set, then the family `--apx-*` tokens, then Ignite UI, then the built-in values.
185
+ - **Mode:** a grid without `theme="dark"` or `theme="light"` follows the Ignite UI variant (`--ig-theme-variant`) declared on `:root` or any ancestor (as Ignite UI themes do), and a theme swap live. `theme="light"` keeps a grid light (text and lines then keep the built-in values, since a dark theme's grays are light) and `theme="dark"` keeps it dark. Where the browser has no container style queries, grids follow the document's variant.
186
+ - **Per grid:** `design-system="off"` (the `designSystem` property set to `false`) turns the Ignite UI colours and font off (the variant still picks the mode); `tokens="off"` (`tokens = false`) turns off the family tokens and Ignite UI, the variant included, for the grid and the content it renders.
187
+ - **Root defaults:** while the add-on is loaded, the tokens that follow Ignite UI (text, lines, surfaces, header, state colours, pinned edge) leave the root defaults, as `--ag-brand` and `--ag-font` always have, so page CSS that reads one gives its own fallback: `var(--ag-text, #111827)`.
188
+ - **Other products:** each takes its own add-on. For the `apex-grid-enterprise` chart panel to read the theme itself, also import `apexcharts/features/ignite-ui` (ApexCharts releases that ship it); the grid never loads it, since it themes every chart on the page. Without it the panel chart takes the grid's colours, which follow the theme.
189
+ - **Bundles:** the add-on registers on a shared `globalThis` slot, so it reaches every grid even when the grid and the add-on come from separately built bundles.
159
190
 
160
191
  ### 4. Size the host
161
192
 
@@ -192,6 +223,9 @@ With the element registered and the host sized, you should see:
192
223
  | `<apex-grid>` blank tag in DOM | Step 2: element not registered |
193
224
  | 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
225
 
226
+ > [!NOTE]
227
+ > 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.
228
+
195
229
  ---
196
230
 
197
231
  ## Features in depth
@@ -255,7 +289,20 @@ await grid.setPageSize(50);
255
289
  grid.nextPage(); grid.previousPage(); grid.firstPage(); grid.lastPage();
256
290
  ```
257
291
 
258
- Remote mode:
292
+ 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:
293
+
294
+ ```ts
295
+ grid.pagination = { enabled: true, pageSize: 5, paginator: 'compact' };
296
+ ```
297
+
298
+ 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`:
299
+
300
+ ```css
301
+ apex-grid::part(paginator-first),
302
+ apex-grid::part(paginator-last) { display: none; }
303
+ ```
304
+
305
+ 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
306
 
260
307
  ```ts
261
308
  grid.pagination = { enabled: true, mode: 'remote', pageSize: 25, totalItems: 1280 };
@@ -264,7 +311,7 @@ grid.addEventListener('pageChanged', async (e) => {
264
311
  });
265
312
  ```
266
313
 
267
- Properties: `page`, `pageSize`, `pageCount`, `totalItems`, `pageItems`. Events: `pageChanging` (cancellable), `pageChanged`.
314
+ 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
315
 
269
316
  ### Column pinning
270
317
 
@@ -305,6 +352,16 @@ grid.columnSeparator = false; // hide the dividers
305
352
 
306
353
  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
354
 
355
+ ### Hiding the header
356
+
357
+ For a compact table, such as a short dashboard tile, leave out the header rows and give their room to the data rows:
358
+
359
+ ```ts
360
+ grid.showHeader = false; // or <apex-grid show-header="false">
361
+ ```
362
+
363
+ 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`.
364
+
308
365
  ### Column groups (spanning headers)
309
366
 
310
367
  ```ts
@@ -388,9 +445,21 @@ A keyboard-driven commit or cancel returns focus to the cell, so navigation
388
445
  continues from where you were. Sorting, filtering, selection, paging, row
389
446
  pinning, reorder, expansion, and undo / redo are announced through a polite
390
447
  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.
448
+ `announce.*` and `header.*` locale keys).
449
+
450
+ The `grid` role (`treegrid` for tree data, master-detail and other expandable
451
+ rows), `aria-rowcount`, `aria-colcount` and `aria-multiselectable` sit on the
452
+ element exposed as `part="table"` inside the shadow root, not on the
453
+ `<apex-grid>` host. It holds only the header rows and the body, so the toolbar,
454
+ paginator and live region around it are not read as table content. It gets a
455
+ localized `aria-label` ("Data grid") unless you put `aria-label` or
456
+ `aria-labelledby` on the host, which then names it. An `aria-describedby` on
457
+ the host becomes its description. The references are resolved when the grid
458
+ renders, so if you add the referenced element later (a heading shown
459
+ conditionally), set the attribute again afterwards to pick it up.
460
+
461
+ > Changed in 4.0.0: the role and the row and column counts used to be on the
462
+ > host. Query `grid.shadowRoot.querySelector('[part~="table"]')` instead.
394
463
 
395
464
  > Changed in 3.4.0: Home / End used to jump to the first / last row; they now
396
465
  > stay within the row. Use Ctrl/Cmd+Home / Ctrl/Cmd+End for the grid corners.
@@ -435,6 +504,8 @@ grid.selectedRows; // snapshot
435
504
  grid.selectedRows = [data[2]]; // replace selection (goes through `rowSelecting`)
436
505
  ```
437
506
 
507
+ 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.
508
+
438
509
  Events: `rowSelecting` (cancellable), `rowSelected`.
439
510
 
440
511
  ### Row pinning (top / bottom)
@@ -503,7 +574,7 @@ await grid.toggleTreeRow(data[0]);
503
574
  await grid.expandAllTreeRows();
504
575
  ```
505
576
 
506
- Methods: `toggleTreeRow`, `expandTreeRow`, `collapseTreeRow`, `expandAllTreeRows`, `collapseAllTreeRows`, `isTreeRowExpanded`. Events: `treeRowExpanding` (cancellable), `treeRowExpanded`. When tree mode is active, the host element advertises `role="treegrid"`.
577
+ 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
578
 
508
579
  ### CSV export
509
580
 
@@ -529,16 +600,19 @@ Renders a download icon in the toolbar's trailing actions area; the menu has an
529
600
 
530
601
  ### Toolbar
531
602
 
532
- Rendered automatically above the header row when at least one of `show-quick-filter` or `show-export` is on. CSS parts:
603
+ 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
604
 
534
605
  | Part | What |
535
606
  |---|---|
607
+ | `toolbar-host` | The `<apex-grid-toolbar>` element |
536
608
  | `toolbar` | Root container |
537
609
  | `toolbar-search` | Quick-filter input wrapper |
538
610
  | `search-field` | The bordered input field |
539
611
  | `search-icon`, `search-input` | Leading icon, input element |
540
612
  | `toolbar-actions` | Trailing actions area |
541
- | `export-trigger` | Export menu button |
613
+ | `toolbar-action` | A host-contributed action button |
614
+ | `export` | Export menu wrapper |
615
+ | `export-trigger`, `export-icon`, `export-caret` | Export menu button and its icons |
542
616
  | `export-menu` | Dropdown panel |
543
617
  | `export-menu-item` | Menu item |
544
618
 
@@ -546,13 +620,13 @@ Search input has a `debounce` attribute (default `200`ms).
546
620
 
547
621
  ### Theming
548
622
 
549
- The grid styles itself through `--ag-*` CSS custom properties; override them on `apex-grid` or any ancestor (shadow hosts included) to rebrand, and use `--ag-dark-*` for dark grids. See [Customize the look](#3-optional-customize-the-look) and [`src/styles/_token-values.scss`](src/styles/_token-values.scss) for the full list. Where an Ignite UI palette is present, the brand tokens auto-tint from `--ig-primary-500`.
623
+ The grid styles itself through `--ag-*` CSS custom properties; override them on `apex-grid` or any ancestor (shadow hosts included) to rebrand, and use `--ag-dark-*` for dark grids. See [Customize the look](#3-optional-customize-the-look) and [`src/styles/_token-values.scss`](src/styles/_token-values.scss) for the full list. The grid also follows the family `--apx-*` tokens, and an Ignite UI theme with the opt-in `apex-grid/ignite-ui` add-on (both under Customize the look).
550
624
 
551
625
  Style with CSS parts on the grid, paginator, and toolbar:
552
626
 
553
627
  ```css
554
628
  apex-grid::part(paginator) { background: var(--surface-2); }
555
- apex-grid-toolbar::part(search-input) { font-family: var(--font-mono); }
629
+ apex-grid::part(search-input) { font-family: var(--font-mono); }
556
630
  ```
557
631
 
558
632
  Body rows re-export these parts, so `apex-grid::part(name)` reaches them:
@@ -611,6 +685,27 @@ grid.coordinateHints = true;
611
685
 
612
686
  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
687
 
688
+ ### Row recycling
689
+
690
+ ```ts
691
+ grid.rowRecycling = false; // opt out
692
+ ```
693
+
694
+ ```html
695
+ <apex-grid row-recycling="false"></apex-grid>
696
+ ```
697
+
698
+ 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:
699
+
700
+ - Derive everything a template renders from its context on each render, and keep no state in the elements it creates.
701
+ - 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`.
702
+ - 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`.
703
+ - 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.
704
+
705
+ 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.
706
+
707
+ 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.
708
+
614
709
  ### State & persistence
615
710
 
616
711
  `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 +742,7 @@ See [`demo/state-persistence.html`](../../demo/state-persistence.html) for a sav
647
742
  | `columns` | `ColumnConfiguration<T>[]` | `[]` | Column configuration (property only) |
648
743
  | `autoGenerate` | `boolean` | `false` | Infer columns from `data[0]` keys. Attr `auto-generate` |
649
744
  | `sortConfiguration` | `GridSortConfiguration` | `{ multiple, triState }` | |
650
- | `dataPipelineConfiguration` | `DataPipelineConfiguration<T>` | | Custom sort/filter/pagination hooks |
745
+ | `dataPipelineConfiguration` | `DataPipelineConfiguration<T>` | | Custom sort, filter and quick filter hooks |
651
746
  | `pagination` | `PaginationConfiguration` | | |
652
747
  | `quickFilter` | `string` | `''` | Attr `quick-filter` |
653
748
  | `showQuickFilter` | `boolean` | `false` | Attr `show-quick-filter` |
@@ -662,6 +757,10 @@ See [`demo/state-persistence.html`](../../demo/state-persistence.html) for a sav
662
757
  | `rowReordering` | `GridRowReorderingConfiguration` | | `{ enabled, applyToData?, handle? }` (`handle` default `true`: grip-only drag) |
663
758
  | `coordinateHints` | `boolean` | `false` | Show a row-number gutter + A / B / C header chips (spreadsheet look). Attr `coordinate-hints` |
664
759
  | `columnMenu` | `boolean` | `true` | Show the always-visible column-menu (kebab) button on headers; `false` hides it |
760
+ | `showHeader` | `boolean` | `true` | `false` hides the header rows (kept for screen readers). Attr `show-header="false"` |
761
+ | `designSystem` | `boolean` | `true` | With the `apex-grid/ignite-ui` add-on, `false` turns the Ignite UI colours and font off for this grid (the variant still picks the mode). Reflected as `design-system="off"` |
762
+ | `tokens` | `boolean` | `true` | `false` turns the family `--apx-*` tokens (and, with the add-on, Ignite UI) off for this grid and its content. Reflected as `tokens="off"` |
763
+ | `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
764
  | `rowId` | `(row: T) => string \| number` | | Durable row identity for `getState` / `setState` |
666
765
  | `localeText` | `GridLocaleText` | | Override map for built-in strings (e.g. `esLocale`) |
667
766
  | `canUndo`, `canRedo` | `boolean` | | Get (history) |
@@ -751,14 +850,14 @@ Programmatic `sort()` / `filter()` calls are silent; only UI-initiated changes e
751
850
 
752
851
  ### Attributes
753
852
 
754
- `auto-generate`, `quick-filter`, `show-quick-filter`, `show-export`, `column-reordering`, `coordinate-hints`.
853
+ `auto-generate`, `quick-filter`, `show-quick-filter`, `show-export`, `column-reordering`, `coordinate-hints`, `show-header`, `row-recycling`. Theming: `theme` (`dark`, `light`, `tinted`), `tokens="off"` and, with the `apex-grid/ignite-ui` add-on, `design-system="off"` (see [Customize the look](#3-optional-customize-the-look)).
755
854
 
756
855
  ### CSS parts
757
856
 
758
857
  | Component | Parts |
759
858
  |---|---|
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` |
859
+ | `<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`) |
860
+ | `<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
861
  | `<apex-grid-cell>` | `cell`, `editor` |
763
862
 
764
863
  ---
@@ -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;