apex-grid 3.5.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 (215) hide show
  1. package/CHANGELOG.md +573 -0
  2. package/LICENSE.md +125 -0
  3. package/README.md +143 -23
  4. package/components/cell.d.ts +43 -5
  5. package/components/cell.js +225 -64
  6. package/components/cell.js.map +1 -1
  7. package/components/filter-row.d.ts +2 -0
  8. package/components/filter-row.js +82 -28
  9. package/components/filter-row.js.map +1 -1
  10. package/components/grid.d.ts +281 -49
  11. package/components/grid.js +600 -159
  12. package/components/grid.js.map +1 -1
  13. package/components/group-header-row.js +28 -25
  14. package/components/group-header-row.js.map +1 -1
  15. package/components/header-row.d.ts +6 -10
  16. package/components/header-row.js +12 -28
  17. package/components/header-row.js.map +1 -1
  18. package/components/header.js +71 -22
  19. package/components/header.js.map +1 -1
  20. package/components/paginator.d.ts +20 -5
  21. package/components/paginator.js +54 -17
  22. package/components/paginator.js.map +1 -1
  23. package/components/row.d.ts +19 -3
  24. package/components/row.js +211 -56
  25. package/components/row.js.map +1 -1
  26. package/components/toolbar.d.ts +5 -1
  27. package/components/toolbar.js +6 -2
  28. package/components/toolbar.js.map +1 -1
  29. package/components/virtualize-rows.d.ts +38 -0
  30. package/components/virtualize-rows.js +286 -0
  31. package/components/virtualize-rows.js.map +1 -0
  32. package/components/virtualizer.d.ts +111 -1
  33. package/components/virtualizer.js +192 -5
  34. package/components/virtualizer.js.map +1 -1
  35. package/controllers/dom.d.ts +14 -7
  36. package/controllers/dom.js +45 -27
  37. package/controllers/dom.js.map +1 -1
  38. package/controllers/editing.d.ts +104 -37
  39. package/controllers/editing.js +230 -78
  40. package/controllers/editing.js.map +1 -1
  41. package/controllers/expansion.d.ts +14 -5
  42. package/controllers/expansion.js +24 -1
  43. package/controllers/expansion.js.map +1 -1
  44. package/controllers/filter.d.ts +10 -2
  45. package/controllers/filter.js +62 -17
  46. package/controllers/filter.js.map +1 -1
  47. package/controllers/history.d.ts +45 -8
  48. package/controllers/history.js +58 -20
  49. package/controllers/history.js.map +1 -1
  50. package/controllers/navigation.d.ts +130 -13
  51. package/controllers/navigation.js +474 -60
  52. package/controllers/navigation.js.map +1 -1
  53. package/controllers/pagination.d.ts +13 -6
  54. package/controllers/pagination.js +29 -22
  55. package/controllers/pagination.js.map +1 -1
  56. package/controllers/reorder.js +38 -22
  57. package/controllers/reorder.js.map +1 -1
  58. package/controllers/resize.d.ts +17 -2
  59. package/controllers/resize.js +16 -6
  60. package/controllers/resize.js.map +1 -1
  61. package/controllers/row-pin.d.ts +26 -2
  62. package/controllers/row-pin.js +24 -11
  63. package/controllers/row-pin.js.map +1 -1
  64. package/controllers/row-reorder.d.ts +40 -12
  65. package/controllers/row-reorder.js +137 -43
  66. package/controllers/row-reorder.js.map +1 -1
  67. package/controllers/selection.d.ts +33 -4
  68. package/controllers/selection.js +49 -11
  69. package/controllers/selection.js.map +1 -1
  70. package/controllers/sort.d.ts +22 -2
  71. package/controllers/sort.js +30 -25
  72. package/controllers/sort.js.map +1 -1
  73. package/controllers/state.d.ts +74 -7
  74. package/controllers/state.js +117 -36
  75. package/controllers/state.js.map +1 -1
  76. package/controllers/tree.d.ts +15 -10
  77. package/controllers/tree.js +0 -0
  78. package/controllers/tree.js.map +1 -1
  79. package/custom-elements.json +5091 -1023
  80. package/docs/assets/hierarchy.js +1 -1
  81. package/docs/assets/highlight.css +15 -8
  82. package/docs/assets/navigation.js +1 -1
  83. package/docs/assets/search.js +1 -1
  84. package/docs/classes/ApexGrid.html +233 -87
  85. package/docs/classes/ApexGridPaginator.html +39 -8
  86. package/docs/classes/ApexGridToolbar.html +11 -7
  87. package/docs/functions/localize.html +2 -2
  88. package/docs/hierarchy.html +1 -1
  89. package/docs/index.html +159 -26
  90. package/docs/interfaces/ApexCellValidationFailedEvent.html +3 -2
  91. package/docs/interfaces/ApexCellValueChangedEvent.html +4 -2
  92. package/docs/interfaces/ApexCellValueChangingEvent.html +3 -2
  93. package/docs/interfaces/ApexGridEventMap.html +5 -2
  94. package/docs/interfaces/ApexPageChangedEvent.html +3 -1
  95. package/docs/interfaces/ApexRowEditEndedEvent.html +2 -1
  96. package/docs/interfaces/ApexRowEditStartedEvent.html +2 -1
  97. package/docs/interfaces/ApexRowMovingEvent.html +9 -3
  98. package/docs/interfaces/ApexRowPinningEvent.html +1 -1
  99. package/docs/interfaces/ApexTreeRowExpandedEvent.html +4 -1
  100. package/docs/interfaces/ApexTreeRowExpandingEvent.html +4 -1
  101. package/docs/interfaces/BaseApexCellContext.html +18 -5
  102. package/docs/interfaces/BaseApexEditorContext.html +20 -5
  103. package/docs/interfaces/BaseFilterExpression.html +3 -3
  104. package/docs/interfaces/CSVExportOptions.html +12 -4
  105. package/docs/interfaces/DataPipelineConfiguration.html +4 -9
  106. package/docs/interfaces/ExportOptions.html +5 -3
  107. package/docs/interfaces/GridExpansionConfiguration.html +6 -2
  108. package/docs/interfaces/GridRowReorderingConfiguration.html +1 -1
  109. package/docs/interfaces/GridSelectionConfiguration.html +19 -4
  110. package/docs/interfaces/PaginationConfiguration.html +11 -4
  111. package/docs/interfaces/PaginationState.html +4 -2
  112. package/docs/interfaces/SetStateResult.html +3 -2
  113. package/docs/interfaces/ValidatorContext.html +7 -2
  114. package/docs/media/_token-values.scss +118 -0
  115. package/docs/media/package.json +9 -5
  116. package/docs/modules.html +1 -1
  117. package/docs/types/DataPipelineParams.html +2 -2
  118. package/docs/types/ExportSource.html +6 -4
  119. package/docs/types/OperandKeys.html +9 -0
  120. package/docs/types/PaginationMode.html +5 -4
  121. package/docs/variables/EN_LOCALE.html +2 -2
  122. package/i18n/en.d.ts +7 -0
  123. package/i18n/en.js +7 -0
  124. package/i18n/en.js.map +1 -1
  125. package/i18n/es.js +7 -0
  126. package/i18n/es.js.map +1 -1
  127. package/internal/column-types.d.ts +38 -2
  128. package/internal/column-types.js +83 -55
  129. package/internal/column-types.js.map +1 -1
  130. package/internal/constants.d.ts +1 -2
  131. package/internal/constants.js +0 -4
  132. package/internal/constants.js.map +1 -1
  133. package/internal/document-styles.d.ts +30 -0
  134. package/internal/document-styles.js +48 -0
  135. package/internal/document-styles.js.map +1 -0
  136. package/internal/export.d.ts +45 -7
  137. package/internal/export.js +39 -34
  138. package/internal/export.js.map +1 -1
  139. package/internal/feature-module.d.ts +67 -3
  140. package/internal/feature-module.js +6 -0
  141. package/internal/feature-module.js.map +1 -1
  142. package/internal/flip.d.ts +28 -16
  143. package/internal/flip.js +42 -10
  144. package/internal/flip.js.map +1 -1
  145. package/internal/index.d.ts +7 -5
  146. package/internal/index.js +6 -4
  147. package/internal/index.js.map +1 -1
  148. package/internal/normalize-case.d.ts +8 -1
  149. package/internal/normalize-case.js +3 -2
  150. package/internal/normalize-case.js.map +1 -1
  151. package/internal/state-snapshot.d.ts +60 -8
  152. package/internal/state-snapshot.js +87 -19
  153. package/internal/state-snapshot.js.map +1 -1
  154. package/internal/types.d.ts +92 -21
  155. package/internal/types.js.map +1 -1
  156. package/internal/utils.d.ts +34 -6
  157. package/internal/utils.js +30 -21
  158. package/internal/utils.js.map +1 -1
  159. package/internal/validators.js +1 -0
  160. package/internal/validators.js.map +1 -1
  161. package/operations/filter/operands/number.js +9 -6
  162. package/operations/filter/operands/number.js.map +1 -1
  163. package/operations/filter/operands/string.js +2 -3
  164. package/operations/filter/operands/string.js.map +1 -1
  165. package/operations/filter/types.d.ts +14 -2
  166. package/operations/filter/types.js.map +1 -1
  167. package/operations/filter.d.ts +6 -0
  168. package/operations/filter.js +23 -7
  169. package/operations/filter.js.map +1 -1
  170. package/operations/quick-filter.js +2 -5
  171. package/operations/quick-filter.js.map +1 -1
  172. package/operations/sort.d.ts +21 -2
  173. package/operations/sort.js +41 -14
  174. package/operations/sort.js.map +1 -1
  175. package/package.json +9 -4
  176. package/setup.d.ts +11 -0
  177. package/setup.js +9 -19
  178. package/setup.js.map +1 -1
  179. package/styles/_token-values.css.d.ts +1 -0
  180. package/styles/_token-values.css.js +3 -0
  181. package/styles/_token-values.css.js.map +1 -0
  182. package/styles/_tokens.css.js +1 -1
  183. package/styles/_tokens.css.js.map +1 -1
  184. package/styles/body-cell/body-cell.css.js +1 -1
  185. package/styles/body-cell/body-cell.css.js.map +1 -1
  186. package/styles/body-cell/cell-renderers.css.js +1 -1
  187. package/styles/body-cell/cell-renderers.css.js.map +1 -1
  188. package/styles/body-row/body-row.css.js +1 -1
  189. package/styles/body-row/body-row.css.js.map +1 -1
  190. package/styles/document-tokens.css.d.ts +1 -0
  191. package/styles/document-tokens.css.js +3 -0
  192. package/styles/document-tokens.css.js.map +1 -0
  193. package/styles/filter-row/filter-row.css.js +1 -1
  194. package/styles/filter-row/filter-row.css.js.map +1 -1
  195. package/styles/grid/_grid.base.css.js +1 -1
  196. package/styles/grid/_grid.base.css.js.map +1 -1
  197. package/styles/grid/grid.css.js +1 -1
  198. package/styles/grid/grid.css.js.map +1 -1
  199. package/styles/group-header-row/group-header-row.css.js +1 -1
  200. package/styles/group-header-row/group-header-row.css.js.map +1 -1
  201. package/styles/header-cell/header-cell.css.js +1 -1
  202. package/styles/header-cell/header-cell.css.js.map +1 -1
  203. package/styles/header-row/header-row.base.css.js +1 -1
  204. package/styles/header-row/header-row.base.css.js.map +1 -1
  205. package/styles/paginator/paginator.css.js +1 -1
  206. package/styles/paginator/paginator.css.js.map +1 -1
  207. package/styles/toolbar/toolbar.css.js +1 -1
  208. package/styles/toolbar/toolbar.css.js.map +1 -1
  209. package/tokens.css +78 -0
  210. package/LICENSE +0 -80
  211. package/docs/media/LICENSE +0 -80
  212. package/docs/media/_tokens.scss +0 -214
  213. package/operations/pagination.d.ts +0 -19
  214. package/operations/pagination.js +0 -12
  215. package/operations/pagination.js.map +0 -1
package/LICENSE.md ADDED
@@ -0,0 +1,125 @@
1
+ ## 📄 License Options for ApexGrid
2
+
3
+ ApexGrid is offered under a **dual-license model** to support individuals, startups, and commercial products of all sizes.
4
+
5
+ One license key works across every Apex product, so a customer of one does not buy a second for another.
6
+
7
+ ---
8
+
9
+ ### 🔓 Community License (Free)
10
+
11
+ For individuals, non-profits, educators, and small businesses whose **annual revenue, operating budget, funding, or equivalent financial resources are less than $2 million USD**.
12
+
13
+ ✅ What's allowed:
14
+
15
+ - Personal, educational, or non-profit use
16
+ - Commercial use by small orgs (under $2M in annual revenue, budget or funding)
17
+ - Modifications, and redistribution of the library itself (with attribution)
18
+
19
+ 🚫 Not allowed:
20
+
21
+ - Use by companies or entities at or above $2M in annual revenue, budget or funding
22
+ - Use in competing data grid or charting products
23
+ - Sublicensing under different terms
24
+ - Embedding in a product or platform used by other people, which needs the OEM License below whatever your size
25
+
26
+ ➡ By using ApexGrid under this license, you confirm that **you qualify as a Small Organization**.
27
+
28
+ ---
29
+
30
+ ### 💼 Commercial License (Paid)
31
+
32
+ The Commercial License applies to all paid SKUs (Pro, Premium and OEM).
33
+
34
+ The **Community License** is available at no cost **only** to organizations whose **annual revenue, operating budget, funding, or equivalent financial resources are under $2M USD**.
35
+ If you, your parent company, affiliates, or any entity under common control reach $2M USD or more on any of those measures, you must purchase one of our paid licenses.
36
+
37
+ Pro and Premium are priced per developer, and priced on the features you use rather than on your size.
38
+
39
+ ✅ What's included:
40
+
41
+ - Use in internal tools and commercial applications
42
+ - Modifications and app-level distribution
43
+ - 12-month subscription with updates & support
44
+
45
+ 🚫 Not allowed:
46
+
47
+ - Redistribution in toolkits, SDKs, or platforms
48
+ - Use by unlicensed developers
49
+ - Competing data grid or charting products
50
+
51
+ ---
52
+
53
+ ### 🎚 What each plan includes for ApexGrid
54
+
55
+ - **Free, at Community and above:** ApexGrid itself.
56
+ - **Premium and above:** the ApexGrid Enterprise add-on, a separate package (`apex-grid-enterprise`) covering Row Grouping, Aggregations, Pivoting, the Server-Side Row Model, Advanced Filter Builder, Range Selection, Integrated Charts, Formula Engine, AI Toolkit and Companion Elements.
57
+
58
+ For the full comparison see [https://apexcharts.com/pricing](https://apexcharts.com/pricing).
59
+
60
+ ---
61
+
62
+ ### 🏛 Non-Profit Tiers
63
+
64
+ Non-Profits are treated like commercial entities:
65
+
66
+ - Annual operating budget **less than $2 million USD**
67
+ - _Eligible for Community licenses_
68
+
69
+ or
70
+
71
+ - Annual operating budget **greater than $2 million USD**
72
+ - _Commercial license with possible discounts for Community, Pro, Premium and OEM products_
73
+
74
+ ---
75
+
76
+ ### 🔄 OEM / Redistribution License (Paid)
77
+
78
+ Required if you are **embedding ApexGrid into a product or platform used by other people**, such as:
79
+
80
+ - No-code dashboards
81
+ - Developer platforms
82
+ - Embedded BI tools
83
+ - SDKs
84
+
85
+ ✅ What's included:
86
+
87
+ - Redistribution rights for **one application or product**
88
+ - **Unlimited developer seats** on that application, with no separate per-developer license to buy
89
+ - **Every licensed feature**, at the highest tier
90
+ - 12-month subscription with updates & support
91
+
92
+ 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
+ ✅ OEM **not required** if your app simply renders static grids and users **cannot** configure or interact with them.
95
+
96
+ ---
97
+
98
+ ### ⏳ When a subscription ends
99
+
100
+ Your license key expires, and **licensed features render with a trial watermark**. You no longer receive updates, new features, or technical support. Work built only from free features is unaffected, and your existing deployment keeps running.
101
+
102
+ For OEM licenses, continued redistribution requires an active subscription.
103
+
104
+ ---
105
+
106
+ ### ⚠️ License Acceptance
107
+
108
+ By installing ApexGrid (e.g., via `npm install apex-grid`), you are agreeing to the applicable license based on your usage:
109
+
110
+ - Community License (if under $2M in annual revenue, budget or funding)
111
+ - Commercial License (if at or above $2M in annual revenue, budget or funding)
112
+ - OEM License (if redistributing to third-party users)
113
+
114
+ ---
115
+
116
+ ### 🛠 Need a License or Have Questions?
117
+
118
+ 📧 Contact us at [sales@apexcharts.com](mailto:sales@apexcharts.com)
119
+ 📚 Read full license agreements here: [https://apexcharts.com/license](https://apexcharts.com/license)
120
+
121
+ ---
122
+
123
+ Copyright © 2026 ApexCharts. All rights reserved.
124
+
125
+ Thank you for supporting ApexGrid! Your licensing keeps the Community License available to individuals and small teams.
package/README.md CHANGED
@@ -45,7 +45,7 @@ import { setup } from 'apex-grid';
45
45
  setup();
46
46
  ```
47
47
 
48
- That single call registers `<apex-grid>` and adopts a default host stylesheet (`height: 100%; min-height: 240px`). The grid is styled out-of-the-box; no theme CSS import is required.
48
+ That single call registers `<apex-grid>` and adopts a default host stylesheet (`height: 100%; min-height: 240px`), also inside any shadow root a grid connects in. The grid is styled out-of-the-box; no theme CSS import is required.
49
49
 
50
50
  ### Render the grid
51
51
 
@@ -111,7 +111,7 @@ Without this, `<apex-grid>` is an inert unknown element.
111
111
 
112
112
  ### 3. (Optional) Customize the look
113
113
 
114
- The grid is styled out-of-the-box; there is no theme to import. Customize it by overriding the `--ag-*` CSS custom properties on `apex-grid` (or any ancestor); a one-line brand override cascades to every tint:
114
+ The grid is styled out-of-the-box; there is no theme to import. Customize it by overriding the `--ag-*` CSS custom properties on `apex-grid` or on any ancestor: `:root`, a dashboard panel, or the shadow host of a web component the grid renders in (custom properties inherit through shadow boundaries, however deep). A one-line brand override cascades to every tint:
115
115
 
116
116
  ```css
117
117
  apex-grid {
@@ -122,16 +122,40 @@ apex-grid {
122
122
  }
123
123
  ```
124
124
 
125
- See [`src/styles/_tokens.scss`](src/styles/_tokens.scss) for the full token list (brand, surfaces, text, semantic state colors, typography, spacing, motion).
125
+ See [`src/styles/_token-values.scss`](src/styles/_token-values.scss) for the full token list and defaults (surfaces, text, semantic state colors, typography, spacing, motion).
126
+
127
+ **Where the defaults live.** The first grid to connect adopts the token defaults into its document, on `:where(:root)`, through `adoptedStyleSheets` (no `<style>` element, so no CSP nonce). Zero specificity means any value you set wins, and `var(--ag-*)` reads in your own CSS resolve to the defaults. If you manage your own CSS, opt out with `setup({ tokenStyles: false })` and import the same defaults as a file (also the way to get them into server-rendered HTML):
128
+
129
+ ```ts
130
+ import 'apex-grid/tokens.css';
131
+ ```
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:
134
+
135
+ - `--ag-brand` and `--ag-brand-strong`, so an Ignite UI palette set on any ancestor keeps tinting the grid (see below).
136
+ - 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
+
138
+ **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
+
140
+ ```css
141
+ .dashboard {
142
+ --ag-brand: #7c3aed;
143
+ --ag-surface: #fdfcff;
144
+ --ag-dark-surface: #140f24;
145
+ }
146
+ ```
147
+
148
+ `theme="tinted"` mixes a little brand into the surface, gridline and muted-text values in effect, including any you set. Its row separators are the brand alone, so a tinted grid ignores `--ag-border` and `--ag-dark-border`.
126
149
 
127
150
  **Grid edge / shadow.** By default the grid shows a flat 1px hairline edge (no drop shadow). Control it with the `--ag-grid-shadow` hook, an opt-in override that is not one of the `_tokens.scss` defaults:
128
151
 
129
152
  ```css
130
- apex-grid { --ag-grid-shadow: var(--ag-shadow-card); } /* elevated floating-card look */
131
- apex-grid { --ag-grid-shadow: none; } /* remove the edge entirely */
153
+ apex-grid { --ag-grid-shadow: var(--ag-shadow-card); } /* elevated floating-card look */
154
+ apex-grid[theme~='dark'] { --ag-grid-shadow: var(--ag-dark-shadow-card); } /* the same, for dark grids */
155
+ apex-grid { --ag-grid-shadow: none; } /* remove the edge entirely */
132
156
  ```
133
157
 
134
- If you embed the grid in an Ignite UI app, the brand tokens automatically re-tint from its palette: 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.
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.
135
159
 
136
160
  ### 4. Size the host
137
161
 
@@ -168,6 +192,9 @@ With the element registered and the host sized, you should see:
168
192
  | `<apex-grid>` blank tag in DOM | Step 2: element not registered |
169
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) |
170
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
+
171
198
  ---
172
199
 
173
200
  ## Features in depth
@@ -231,7 +258,20 @@ await grid.setPageSize(50);
231
258
  grid.nextPage(); grid.previousPage(); grid.firstPage(); grid.lastPage();
232
259
  ```
233
260
 
234
- 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:
235
275
 
236
276
  ```ts
237
277
  grid.pagination = { enabled: true, mode: 'remote', pageSize: 25, totalItems: 1280 };
@@ -240,7 +280,7 @@ grid.addEventListener('pageChanged', async (e) => {
240
280
  });
241
281
  ```
242
282
 
243
- 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`.
244
284
 
245
285
  ### Column pinning
246
286
 
@@ -281,6 +321,16 @@ grid.columnSeparator = false; // hide the dividers
281
321
 
282
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`.
283
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
+
284
334
  ### Column groups (spanning headers)
285
335
 
286
336
  ```ts
@@ -334,6 +384,14 @@ re-renders. The returned tally splits `applied` from `unchanged`, `invalid`,
334
384
  one. Edits naming a row out of range or a column the user could not edit either
335
385
  are skipped rather than throwing.
336
386
 
387
+ `rowIndex` is view-relative, so it reaches the current page only. To edit rows
388
+ anywhere (other pages, pinned rows, rows under a collapsed tree parent), name
389
+ the record itself with `row`:
390
+
391
+ ```ts
392
+ grid.applyEdits(grid.selectedRows.map((row) => ({ row, column: 'discount', value: 0 })));
393
+ ```
394
+
337
395
  ### Keyboard navigation and accessibility
338
396
 
339
397
  The grid body is a single tab stop: Tab enters the grid, and the active cell
@@ -356,9 +414,21 @@ A keyboard-driven commit or cancel returns focus to the cell, so navigation
356
414
  continues from where you were. Sorting, filtering, selection, paging, row
357
415
  pinning, reorder, expansion, and undo / redo are announced through a polite
358
416
  live region, and every announcement and control label is localizable (the
359
- `announce.*` and `header.*` locale keys). The host gets a localized
360
- `aria-label` ("Data grid") unless you provide `aria-label` /
361
- `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.
362
432
 
363
433
  > Changed in 3.4.0: Home / End used to jump to the first / last row; they now
364
434
  > stay within the row. Use Ctrl/Cmd+Home / Ctrl/Cmd+End for the grid corners.
@@ -403,6 +473,8 @@ grid.selectedRows; // snapshot
403
473
  grid.selectedRows = [data[2]]; // replace selection (goes through `rowSelecting`)
404
474
  ```
405
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
+
406
478
  Events: `rowSelecting` (cancellable), `rowSelected`.
407
479
 
408
480
  ### Row pinning (top / bottom)
@@ -416,7 +488,7 @@ grid.unpinRow(data[0]);
416
488
  grid.pinnedRows; // { top: T[], bottom: T[] }
417
489
  ```
418
490
 
419
- Pinned rows render in sticky top / bottom bands outside the virtualizer and are lifted out of the scrollable set (no duplication). Selection and styling work by row reference. Events: `rowPinning` (cancellable), `rowPinned`.
491
+ Pinned rows render in sticky top / bottom bands outside the virtualizer and are lifted out of the scrollable set (no duplication). Selection and styling work by row reference. Replacing `grid.data` carries the pins onto the new rows (by `rowId` when set, else a pin whose row left the data is dropped). While `rowPinning.enabled` is false the pinned rows return to the body and `pinnedRows` reports none, but the pins are kept (`getState` still saves them) and re-enabling brings them back. Events: `rowPinning` (cancellable), `rowPinned`.
420
492
 
421
493
  ### Row drag-reorder
422
494
 
@@ -426,7 +498,7 @@ grid.rowReordering = { enabled: true }; // add applyToData: true to splice gri
426
498
  grid.moveRow(0, 4, 'after');
427
499
  ```
428
500
 
429
- By default (`rowReordering.handle`, boolean, default `true`) a six-dot grip handle shows at the start of every reorderable row, and dragging lifts a full-row floating ghost that follows the cursor. Both interaction modes are supported: **handle mode** (only the grip starts a drag, so the rest of the row stays free for selection and editing) and **whole-row mode** (set `handle: false` to drag from anywhere on the row, excluding interactive sub-parts like inputs and buttons). Or use the keyboard: focus a row, **Space** to grab, **Arrow** keys to move, **Space / Enter** to drop, **Esc** to cancel. The grid holds a manual order that is mutually exclusive with sorting (applying a sort clears it). By default the app persists the order via the `rowMoved` event; set `applyToData: true` to splice `grid.data` directly. Events: `rowMoving` (cancellable), `rowMoved { from, to, data }`.
501
+ By default (`rowReordering.handle`, boolean, default `true`) a six-dot grip handle shows at the start of every reorderable row, and dragging lifts a full-row floating ghost that follows the cursor. Both interaction modes are supported: **handle mode** (only the grip starts a drag, so the rest of the row stays free for selection and editing) and **whole-row mode** (set `handle: false` to drag from anywhere on the row, excluding interactive sub-parts like inputs and buttons). Or use the keyboard: focus a row, **Space** to grab, **Arrow** keys to move, **Space / Enter** to drop, **Esc** to cancel. The grid holds a manual order that is mutually exclusive with sorting (applying a sort clears it, and a move clears the sort). Rows hidden by a filter keep their place when a visible row moves, and the order follows `grid.data` when it is replaced (by `rowId` when set). By default the app persists the order via the `rowMoved` event; set `applyToData: true` to splice `grid.data` directly. Events: `rowMoving` (cancellable), `rowMoved { from, to, position, toIndex, data }`, where `toIndex` is the view index the row lands at.
430
502
 
431
503
  ### Row expansion (master-detail)
432
504
 
@@ -471,7 +543,7 @@ await grid.toggleTreeRow(data[0]);
471
543
  await grid.expandAllTreeRows();
472
544
  ```
473
545
 
474
- 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"`.
475
547
 
476
548
  ### CSV export
477
549
 
@@ -497,16 +569,19 @@ Renders a download icon in the toolbar's trailing actions area; the menu has an
497
569
 
498
570
  ### Toolbar
499
571
 
500
- 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:
501
573
 
502
574
  | Part | What |
503
575
  |---|---|
576
+ | `toolbar-host` | The `<apex-grid-toolbar>` element |
504
577
  | `toolbar` | Root container |
505
578
  | `toolbar-search` | Quick-filter input wrapper |
506
579
  | `search-field` | The bordered input field |
507
580
  | `search-icon`, `search-input` | Leading icon, input element |
508
581
  | `toolbar-actions` | Trailing actions area |
509
- | `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 |
510
585
  | `export-menu` | Dropdown panel |
511
586
  | `export-menu-item` | Menu item |
512
587
 
@@ -514,13 +589,35 @@ Search input has a `debounce` attribute (default `200`ms).
514
589
 
515
590
  ### Theming
516
591
 
517
- The grid styles itself through `--ag-*` CSS custom properties; override them on `apex-grid` (or any ancestor) to rebrand; see [`src/styles/_tokens.scss`](src/styles/_tokens.scss) for the full list. Where an Ignite UI palette is present, the brand tokens auto-tint from `--ig-primary-500`.
592
+ 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`.
518
593
 
519
594
  Style with CSS parts on the grid, paginator, and toolbar:
520
595
 
521
596
  ```css
522
597
  apex-grid::part(paginator) { background: var(--surface-2); }
523
- apex-grid-toolbar::part(search-input) { font-family: var(--font-mono); }
598
+ apex-grid::part(search-input) { font-family: var(--font-mono); }
599
+ ```
600
+
601
+ Body rows re-export these parts, so `apex-grid::part(name)` reaches them:
602
+
603
+ | Part | What |
604
+ |---|---|
605
+ | `cell` | A data cell |
606
+ | `selection-cell`, `selection-checkbox` | Selection column cell and its checkbox |
607
+ | `expansion-cell`, `expansion-toggle`, `expansion-chevron` | Expansion column cell, its button and the chevron icon |
608
+ | `detail-panel` | Expanded detail panel container |
609
+ | `row-number-cell`, `row-number` | Row-number gutter cell and its label (`coordinateHints`) |
610
+ | `reorder-handle-cell`, `reorder-handle`, `reorder-grip` | Row-reorder grip cell, handle and icon (handle mode) |
611
+ | `group-row` | Default container of a row a feature module renders full width |
612
+
613
+ Parts used inside your own `detailTemplate` content are re-exported when you list them in `expansion.exportParts`:
614
+
615
+ ```js
616
+ grid.expansion = {
617
+ enabled: true,
618
+ exportParts: ['order-summary'],
619
+ detailTemplate: ({ data }) => html`<div part="order-summary">${data.total}</div>`,
620
+ };
524
621
  ```
525
622
 
526
623
  ### Right-to-left
@@ -557,6 +654,27 @@ grid.coordinateHints = true;
557
654
 
558
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`).
559
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
+
560
678
  ### State & persistence
561
679
 
562
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).
@@ -593,7 +711,7 @@ See [`demo/state-persistence.html`](../../demo/state-persistence.html) for a sav
593
711
  | `columns` | `ColumnConfiguration<T>[]` | `[]` | Column configuration (property only) |
594
712
  | `autoGenerate` | `boolean` | `false` | Infer columns from `data[0]` keys. Attr `auto-generate` |
595
713
  | `sortConfiguration` | `GridSortConfiguration` | `{ multiple, triState }` | |
596
- | `dataPipelineConfiguration` | `DataPipelineConfiguration<T>` | | Custom sort/filter/pagination hooks |
714
+ | `dataPipelineConfiguration` | `DataPipelineConfiguration<T>` | | Custom sort, filter and quick filter hooks |
597
715
  | `pagination` | `PaginationConfiguration` | | |
598
716
  | `quickFilter` | `string` | `''` | Attr `quick-filter` |
599
717
  | `showQuickFilter` | `boolean` | `false` | Attr `show-quick-filter` |
@@ -608,6 +726,8 @@ See [`demo/state-persistence.html`](../../demo/state-persistence.html) for a sav
608
726
  | `rowReordering` | `GridRowReorderingConfiguration` | | `{ enabled, applyToData?, handle? }` (`handle` default `true`: grip-only drag) |
609
727
  | `coordinateHints` | `boolean` | `false` | Show a row-number gutter + A / B / C header chips (spreadsheet look). Attr `coordinate-hints` |
610
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"` |
611
731
  | `rowId` | `(row: T) => string \| number` | | Durable row identity for `getState` / `setState` |
612
732
  | `localeText` | `GridLocaleText` | | Override map for built-in strings (e.g. `esLocale`) |
613
733
  | `canUndo`, `canRedo` | `boolean` | | Get (history) |
@@ -688,7 +808,7 @@ All events bubble and are composed across shadow boundaries. Names ending in `-i
688
808
  | `historyChanged` | no | `{ canUndo, canRedo }` |
689
809
  | `rowSelecting` / `rowSelected` | yes / no | `{ added, removed }` |
690
810
  | `rowPinning` / `rowPinned` | yes / no | `{ row, position }` |
691
- | `rowMoving` / `rowMoved` | yes / no | `{ from, to, data }` |
811
+ | `rowMoving` / `rowMoved` | yes / no | `{ from, to, position, toIndex, data }` |
692
812
  | `rowExpanding` / `rowExpanded` | yes / no | row context |
693
813
  | `treeRowExpanding` / `treeRowExpanded` | yes / no | row context |
694
814
  | `stateChanged` | no | `{ state }` (debounced; only while listened) |
@@ -697,14 +817,14 @@ Programmatic `sort()` / `filter()` calls are silent; only UI-initiated changes e
697
817
 
698
818
  ### Attributes
699
819
 
700
- `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`.
701
821
 
702
822
  ### CSS parts
703
823
 
704
824
  | Component | Parts |
705
825
  |---|---|
706
- | `<apex-grid-toolbar>` | `toolbar`, `toolbar-search`, `search-field`, `search-icon`, `search-input`, `toolbar-actions`, `export-trigger`, `export-menu`, `export-menu-item` |
707
- | `<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`) |
708
828
  | `<apex-grid-cell>` | `cell`, `editor` |
709
829
 
710
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
  */
@@ -46,13 +56,30 @@ export default class ApexGridCell<T extends object> extends LitElement {
46
56
  * auto-rendered selection + expansion columns ahead of the data columns.
47
57
  */
48
58
  colindex: number;
59
+ /**
60
+ * The parent row's view index, set by the row. Changing it re-decorates the
61
+ * cell (decoration such as a range selection is positional), so a row that
62
+ * moves with the same data (sort, filter, an expand above it) never keeps a
63
+ * stale highlight.
64
+ *
65
+ * @internal
66
+ */
67
+ rowIndex: number;
49
68
  /**
50
69
  * Reactive token forwarded from {@link StateController.decorationVersion}.
51
- * Changing it re-runs {@link willUpdate} so module-driven cell decoration is
52
- * re-evaluated when it changes (e.g. while dragging a selection range). Stays
53
- * `0` for the community grid, so cells are never re-decorated there.
70
+ * Changing it re-applies module-driven cell decoration (e.g. while dragging a
71
+ * selection range) without re-rendering the cell content. Stays `0` for the
72
+ * community grid, so cells are never re-decorated there.
54
73
  */
55
74
  decorationVersion: number;
75
+ /**
76
+ * Reactive token forwarded from {@link StateController.dataVersion}, bumped
77
+ * once per pipeline run. Re-renders only a cell that reads its row beyond
78
+ * its own value (see {@link shouldUpdate}).
79
+ *
80
+ * @internal
81
+ */
82
+ dataVersion: number;
56
83
  /**
57
84
  * Reactive token forwarded from {@link EditingController.validationVersion}.
58
85
  * Changing it re-runs {@link willUpdate} so the cell re-reads its validation
@@ -62,11 +89,22 @@ export default class ApexGridCell<T extends object> extends LitElement {
62
89
  validationVersion: number;
63
90
  connectedCallback(): void;
64
91
  disconnectedCallback(): void;
65
- protected willUpdate(): void;
92
+ /**
93
+ * Skips the render when only positional or decoration tokens changed: the
94
+ * decoration is host attributes, so it is reconciled here and the cell
95
+ * template (type renderers, user templates) is not re-run. A cell that reads
96
+ * its row beyond {@link value} (a `cellTemplate`, which may read other fields
97
+ * or the row index, or the tree group cell, whose chevron shows the row's
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.
100
+ */
101
+ protected shouldUpdate(changed: PropertyValues<this>): boolean;
102
+ protected willUpdate(changed: PropertyValues<this>): void;
66
103
  /**
67
104
  * The inline error node surfaced when the cell is invalid. `role="alert"`
68
105
  * announces the message when it appears, covering the cross-shadow gap where
69
- * 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).
70
108
  */
71
109
  protected renderError(): import("lit-html").TemplateResult<1> | null;
72
110
  protected updated(changed: PropertyValues<this>): void;