apexgrid-skill 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +64 -17
- package/README.md +3 -2
- package/SKILL.md +73 -40
- package/package.json +1 -1
- package/references/columns-and-templates.md +34 -12
- package/references/data-pipeline.md +33 -9
- package/references/framework-integration.md +3 -1
- package/references/sort-and-filter.md +8 -0
- package/references/state-and-features.md +56 -5
package/.cursorrules
CHANGED
|
@@ -4,7 +4,8 @@ You are working with `apex-grid`, a Lit-based web-component data grid by ApexCha
|
|
|
4
4
|
Documentation: https://github.com/apexcharts/apexgrid
|
|
5
5
|
NPM: apex-grid (with a hyphen — no `apexgrid` package exists)
|
|
6
6
|
GitHub: https://github.com/apexcharts/apexgrid
|
|
7
|
-
Confirmed versions: apex-grid@
|
|
7
|
+
Confirmed versions: apex-grid@4.0.0, lit@^3.3.0 (apex-grid-enterprise@0.9.0 needs apex-grid 4.0).
|
|
8
|
+
Upgrading from 3.x: see "Changed in 4.0" at the end.
|
|
8
9
|
|
|
9
10
|
## Quickstart — register + size the host
|
|
10
11
|
|
|
@@ -19,8 +20,7 @@ npm install apex-grid lit
|
|
|
19
20
|
That is the whole install: `apex-grid` brings `lit`, `@lit/context` and `@lit-labs/virtualizer`.
|
|
20
21
|
There is no theme CSS to import for a styled grid (3.5.0 dropped the old `igniteui-webcomponents`
|
|
21
22
|
dependency). An Ignite UI theme never fully themed the grid: 3.x only took its brand and font. From
|
|
22
|
-
4.0 the only way to follow an Ignite UI theme is the free `apex-grid/ignite-ui` add-on
|
|
23
|
-
(`npm install apex-grid@next`, 4.0.0-beta.2; plain `npm install apex-grid` gets 3.6.0 without it;
|
|
23
|
+
4.0 the only way to follow an Ignite UI theme is the free `apex-grid/ignite-ui` add-on (not in 3.x;
|
|
24
24
|
see "Ignite UI theming" below).
|
|
25
25
|
|
|
26
26
|
### 2. Register + size — `setup()` does both
|
|
@@ -44,10 +44,7 @@ and IGNORES the light ones (`--ag-surface`, ...), even when set on the dark grid
|
|
|
44
44
|
Brand, sizing and typography tokens are shared. In your own CSS, `var(--ag-brand)` has no
|
|
45
45
|
root default: write `var(--ag-brand, #0d6fd3)`.
|
|
46
46
|
|
|
47
|
-
### Ignite UI theming (
|
|
48
|
-
Not in 3.6.0 (what `npm install apex-grid` installs): `npm install apex-grid@next` first
|
|
49
|
-
(`apex-grid-enterprise@next`, 0.9.0-beta.2, if you use it). 4.0 has other breaking changes (body rows
|
|
50
|
-
recycled by default via `rowRecycling`, grid role moved off the host); check the CHANGELOG.
|
|
47
|
+
### Ignite UI theming (new in 4.0)
|
|
51
48
|
`import 'apex-grid/ignite-ui';` once (free, no licence key; ES modules only, no script-tag file).
|
|
52
49
|
Every grid on the page, shadow roots included, then follows the Ignite UI theme: brand, text,
|
|
53
50
|
gridlines, pinned edge, header (`--ig-surface-raised`, 7.5+), state colours, font, enterprise popups.
|
|
@@ -60,7 +57,7 @@ gridlines, pinned edge, header (`--ig-surface-raised`, 7.5+), state colours, fon
|
|
|
60
57
|
- Live: no `refreshTokens()` needed. With the add-on, the Ignite-following tokens leave the root
|
|
61
58
|
defaults: page CSS reading them needs a fallback, `var(--ag-text, #111827)`.
|
|
62
59
|
- New tokens `--ag-overlay`, `--ag-overlay-text` (dark: `--ag-dark-overlay`, `--ag-dark-overlay-text`)
|
|
63
|
-
colour the enterprise popups (apex-grid-enterprise 0.9.0
|
|
60
|
+
colour the enterprise popups (apex-grid-enterprise 0.9.0); no default.
|
|
64
61
|
- `--ag-font` you set now wins over family and Ignite UI fonts and has no root default: `var(--ag-font, Inter, sans-serif)`.
|
|
65
62
|
- BREAKING (4.0): the grid no longer reads `--ig-*` by itself (3.x took the brand and font with no
|
|
66
63
|
import). Without the add-on an Ignite UI stylesheet changes nothing. The 4.0 grid also follows
|
|
@@ -121,7 +118,7 @@ render(
|
|
|
121
118
|
1. **Apex Grid is a Lit web component, not a JS class.** Don't `new ApexGrid(...)`. Render `<apex-grid>` and set `.data` / `.columns` as properties.
|
|
122
119
|
2. **Two setup steps:** register the element and give the host a bounded height — `setup()` does both. **No theme CSS import is needed** (the grid self-styles via `--ag-*`).
|
|
123
120
|
3. **`columns` and `data` are properties, not attributes.** Use property binding.
|
|
124
|
-
4. **`column.key` must be a real `keyof T`.** No aliases. Compute derived values in `cellTemplate` (
|
|
121
|
+
4. **`column.key` must be a real `keyof T`.** No aliases. Compute derived values in `cellTemplate` from `ctx.data`, the record (4.0); `ctx.row` is the row element, which recycling re-binds to other records.
|
|
125
122
|
5. **Set `type` per column.** Primitive `'string'`/`'number'`/`'boolean'` drive sort/filter/editing (default `'string'`). Presentation types also exist: `'select'`, `'rating'`, `'date'`, `'image'`, `'currency'`, `'avatar'`, `'badge'`, `'progress'`, `'sparkline'`, `'status'`.
|
|
126
123
|
6. **`cellTemplate` and `headerTemplate` must return Lit `TemplateResult`s** (`html\`...\``). Strings render as escaped text.
|
|
127
124
|
7. **Sort/filter events fire only for UI-initiated operations.** Programmatic `grid.sort(...)` / `grid.filter(...)` are silent.
|
|
@@ -161,9 +158,13 @@ Applied defaults: `{ type: 'string', resizable: false, hidden: false, sort: fals
|
|
|
161
158
|
```ts
|
|
162
159
|
import { html } from 'lit';
|
|
163
160
|
|
|
164
|
-
cellTemplate: ({ value, row, parent
|
|
165
|
-
html`<strong>${value}</strong>`,
|
|
161
|
+
cellTemplate: ({ value, data, column, row, parent }) => // data = the record (4.0)
|
|
162
|
+
html`<strong>${value}</strong> <small>#${data.id}</small>`,
|
|
166
163
|
```
|
|
164
|
+
Body rows are recycled by default (4.0, `rowRecycling`): a row element can show another record after a
|
|
165
|
+
scroll, sort or filter. Read `ctx.data`, never hold `ctx.row` / `ctx.parent` / `grid.rows` across an
|
|
166
|
+
`await`, keep no state in template elements (or wrap in `keyed(ctx.data, ...)` from
|
|
167
|
+
`lit/directives/keyed.js`), or opt out with `grid.rowRecycling = false` / `row-recycling="false"`.
|
|
167
168
|
|
|
168
169
|
### Auto-generation — 3-line demo
|
|
169
170
|
|
|
@@ -202,14 +203,20 @@ grid.filter([
|
|
|
202
203
|
| `columns` | `ColumnConfiguration<T>[]` | |
|
|
203
204
|
| `autoGenerate` | `boolean` | Ignored if `columns` non-empty |
|
|
204
205
|
| `sortConfiguration` | `{ multiple, triState }` | Multi-column + tri-state toggles |
|
|
205
|
-
| `dataPipelineConfiguration` | `{ sort?, filter?,
|
|
206
|
+
| `dataPipelineConfiguration` | `{ sort?, filter?, quickFilter? }` | Server-side hooks (4.0 removed `pagination`: use `pagination.mode: 'remote'`) |
|
|
206
207
|
| `selection` / `editing` / `pagination` / `tree` / `expansion` | config objects | Opt-in: `{ enabled, ... }` (see SKILL.md §5) |
|
|
208
|
+
| `rowRecycling` (attr `row-recycling`) | `boolean` | Default `true` (4.0); `false` keeps record-keyed rows |
|
|
209
|
+
| `showHeader` (attr `show-header`) | `boolean` | Default `true`; `false` (4.0) hides the header rows and filter panel |
|
|
207
210
|
| `quickFilter` / `columnReordering` | `string` / `boolean` | Global search / drag-reorder headers |
|
|
208
211
|
| `sortExpressions` / `filterExpressions` | array (get/set) | Assigning REPLACES (3.6); `[]` clears. `sort()` / `filter()` add |
|
|
209
212
|
| `selectedRows` / `expandedRows` | `T[]` (get/set) | Snapshots; set to replace |
|
|
210
213
|
| `rows` (getter) | `ApexGridRow<T>[]` | Currently rendered only (virtualized) |
|
|
211
214
|
| `dataView` / `pageItems` (getters) | `readonly T[]` | Full post-filter/sort / current page |
|
|
212
|
-
| `totalItems` / `pageCount` (getters) | `number` | |
|
|
215
|
+
| `totalItems` / `pageCount` (getters) | `number` | With pagination, pinned rows not counted (4.0) |
|
|
216
|
+
|
|
217
|
+
Feature config additions (4.0): `selection.isRowSelectable: (row) => boolean` (rejected rows get no
|
|
218
|
+
checkbox and every selection path skips them, e.g. a pinned total row);
|
|
219
|
+
`pagination.paginator: 'full' | 'compact'` (`'compact'`: summary, previous, page / count, next).
|
|
213
220
|
|
|
214
221
|
| Method | Description |
|
|
215
222
|
|---|---|
|
|
@@ -261,7 +268,7 @@ Built-in pipeline is skipped for whichever operations have a hook. Omit one to k
|
|
|
261
268
|
### 3. Importing a theme CSS that no longer exists
|
|
262
269
|
❌ `import 'igniteui-webcomponents/themes/.../bootstrap.css'` + `configureTheme(...)` — obsolete; no effect.
|
|
263
270
|
✅ No import needed for a styled grid; retheme via `--ag-*`. The only required CSS is host height.
|
|
264
|
-
To follow an Ignite UI theme, `import 'apex-grid/ignite-ui'` (4.0
|
|
271
|
+
To follow an Ignite UI theme, `import 'apex-grid/ignite-ui'` (4.0), not a theme import.
|
|
265
272
|
|
|
266
273
|
### 4. Stringified attribute
|
|
267
274
|
❌ `<apex-grid columns="[...]">`
|
|
@@ -269,7 +276,7 @@ To follow an Ignite UI theme, `import 'apex-grid/ignite-ui'` (4.0 only: `apex-gr
|
|
|
269
276
|
|
|
270
277
|
### 5. `column.key` not a `keyof T`
|
|
271
278
|
❌ `{ key: 'fullName' }` when not in `T`
|
|
272
|
-
✅ `cellTemplate: ({
|
|
279
|
+
✅ `cellTemplate: ({ data }) => html\`${data.firstName} ${data.lastName}\`` (`data` is the record; `row` is the element)
|
|
273
280
|
|
|
274
281
|
### 6. String from `cellTemplate`
|
|
275
282
|
❌ `cellTemplate: ({ value }) => \`<b>\${value}</b>\``
|
|
@@ -298,8 +305,17 @@ To follow an Ignite UI theme, `import 'apex-grid/ignite-ui'` (4.0 only: `apex-gr
|
|
|
298
305
|
### 12. Ignite UI theme without the add-on (4.0)
|
|
299
306
|
❌ Ignite UI stylesheet on the page, no `apex-grid/ignite-ui`: `--ig-*` is silently ignored in 4.0.
|
|
300
307
|
❌ `theme="light"` on the grid while the page switches to a dark Ignite UI theme: blocks the dark variant.
|
|
301
|
-
❌ `import 'apex-grid/ignite-ui'`
|
|
302
|
-
✅
|
|
308
|
+
❌ `import 'apex-grid/ignite-ui'` on apex-grid 3.x: no such module.
|
|
309
|
+
✅ apex-grid 4.0, `import 'apex-grid/ignite-ui'` once and leave `theme` unset.
|
|
310
|
+
|
|
311
|
+
### 13. Treating a row element as a record (4.0)
|
|
312
|
+
❌ Reading `ctx.row.data` after an `await`, keeping `grid.rows[i]` as a handle, or an `<input>` / widget in a
|
|
313
|
+
`cellTemplate` holding its own state: with recycling the element shows another record after a scroll or sort.
|
|
314
|
+
✅ Read `ctx.data`; `keyed(ctx.data, html\`...\`)` for stateful templates; or `grid.rowRecycling = false`.
|
|
315
|
+
|
|
316
|
+
### 14. Reading the grid role from the host (4.0)
|
|
317
|
+
❌ `grid.getAttribute('role')` / `aria-rowcount` on `<apex-grid>`: they moved.
|
|
318
|
+
✅ `grid.shadowRoot.querySelector('[part~="table"]')`. An `aria-label` on the host still names the grid.
|
|
303
319
|
|
|
304
320
|
---
|
|
305
321
|
|
|
@@ -337,6 +353,37 @@ To follow an Ignite UI theme, `import 'apex-grid/ignite-ui'` (4.0 only: `apex-gr
|
|
|
337
353
|
|
|
338
354
|
---
|
|
339
355
|
|
|
356
|
+
## Changed in 4.0 (upgrading from 3.x)
|
|
357
|
+
|
|
358
|
+
Breaking:
|
|
359
|
+
- Body rows are recycled by default (`rowRecycling`): read `ctx.data` in templates, or set
|
|
360
|
+
`rowRecycling = false` / `row-recycling="false"` for record-keyed rows.
|
|
361
|
+
- `role` (`grid` / `treegrid`), `aria-rowcount`, `aria-colcount`, `aria-multiselectable` and the
|
|
362
|
+
accessible name moved from the host to the inner `[part~="table"]` element.
|
|
363
|
+
- An Ignite UI theme does nothing until `import 'apex-grid/ignite-ui'`; `--ag-font` has no root default.
|
|
364
|
+
- Pagination: pinned rows no longer count toward `totalItems` / pages; a local page change no longer runs
|
|
365
|
+
custom sort / filter hooks; `DataPipelineConfiguration.pagination` and `DataPipelineParams.type`
|
|
366
|
+
`'pagination'` are removed (types only; the hook was never called).
|
|
367
|
+
- `min()` / `max()` fail a value that is not a number (3.x passed `'abc'`).
|
|
368
|
+
- Types / DOM: `ctx.data` is required in the cell context; `ApexGridPaginator.pageSizeOptions` is
|
|
369
|
+
`readonly number[]`; the paginator's previous button id is `prev`; protected
|
|
370
|
+
`ApexGrid.headerRowCount` and `ApexGridHeaderRow.renderDragGhost` removed.
|
|
371
|
+
|
|
372
|
+
New:
|
|
373
|
+
- `showHeader` (`show-header="false"`), `selection.isRowSelectable`, `pagination.paginator: 'compact'`.
|
|
374
|
+
- `apex-grid::part()` for every paginator part (`paginator`, `paginator-size`, `paginator-info`,
|
|
375
|
+
`paginator-controls`, `paginator-page`, `paginator-button`, `paginator-first` / `-prev` / `-next` /
|
|
376
|
+
`-last`) and toolbar part (`toolbar`, `toolbar-search`, `search-field`, `search-icon`, `search-input`,
|
|
377
|
+
`toolbar-actions`, `toolbar-action`, `export`, `export-trigger`, `export-menu`, `export-menu-item`, ...).
|
|
378
|
+
- `ctx.data` in cell and editor templates; a cell template's `ctx.commit` writes to it.
|
|
379
|
+
- IME input in the cell editor, filter panel and quick filter; the filter panel and `min()` / `max()` read
|
|
380
|
+
full-width digits and the column `locale`'s grouping (`1.234,5` under `de-DE`) and currency marks.
|
|
381
|
+
- Locale keys: `toolbar.exportCsv`, `filter.and` / `filter.or`, `cell.rating`, `cell.true` / `cell.false`,
|
|
382
|
+
`validation.required` / `.min` / `.max` / `.pattern`; `ValidatorContext.localize(key, params?)`.
|
|
383
|
+
- Ignite UI add-on (`apex-grid/ignite-ui`) and the family `--apx-*` tokens.
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
340
387
|
## Dev-server reminder
|
|
341
388
|
|
|
342
389
|
ES module imports require an HTTP server — opening with `file://` fails CORS. Use Vite (`npm create vite@latest`) or any static server.
|
package/README.md
CHANGED
|
@@ -20,13 +20,14 @@ AI models routinely get web-component grid code wrong: they treat `<apex-grid>`
|
|
|
20
20
|
|
|
21
21
|
### Coverage
|
|
22
22
|
|
|
23
|
-
- **Quickstart**: register + size the host (`setup()` does both); no theme import, since the grid self-styles via `--ag-*` CSS variables; the free `apex-grid/ignite-ui` add-on (apex-grid 4.0
|
|
23
|
+
- **Quickstart**: register + size the host (`setup()` does both); no theme import, since the grid self-styles via `--ag-*` CSS variables; the free `apex-grid/ignite-ui` add-on (apex-grid 4.0) follows an Ignite UI theme
|
|
24
|
+
- **Upgrading from 3.x to 4.0**: row recycling and `ctx.data`, the grid role on `[part~="table"]`, pagination and validator changes
|
|
24
25
|
- **Custom-element registration** — `setup()`, `import 'apex-grid/define'`, or `ApexGrid.register()`
|
|
25
26
|
- **Generic `ColumnConfiguration<T>`** — `key`, the 13 column `type`s (incl. `date`, `select`, `currency`, …), templates, `sort`, `filter`, `pinned`, explicit widths
|
|
26
27
|
- **Lit cell, header & editor templates** with the right context shapes
|
|
27
28
|
- **Sort, filter & quick-filter** — programmatic API, UI events, operands, multi-column
|
|
28
29
|
- **Editing, selection, pagination, tree & master-detail** — the opt-in feature config objects
|
|
29
|
-
- **Server-side data
|
|
30
|
+
- **Server-side data**: `dataPipelineConfiguration` async hooks (sort/filter/quickFilter) and remote pagination
|
|
30
31
|
- **CSV export & column reordering/pinning**
|
|
31
32
|
- **Virtualization** — `grid.rows` vs `grid.dataView` vs `grid.totalItems`
|
|
32
33
|
- **Vanilla JS** — full setup without using Lit's `render()` in app code
|
package/SKILL.md
CHANGED
|
@@ -6,18 +6,19 @@ description: >
|
|
|
6
6
|
troubleshoot a sortable / filterable / paginated / editable / virtualized data
|
|
7
7
|
table with `<apex-grid>`. Covers the register-and-size setup (no theme import —
|
|
8
8
|
the grid styles itself via `--ag-*` CSS variables), the optional free
|
|
9
|
-
`apex-grid/ignite-ui` add-on that follows an Ignite UI theme
|
|
10
|
-
pre-release only, `apex-grid@next`), the generic
|
|
9
|
+
`apex-grid/ignite-ui` add-on that follows an Ignite UI theme, the generic
|
|
11
10
|
`ColumnConfiguration<T>` shape with its 13 column types, Lit `cellTemplate`
|
|
12
|
-
requirements
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`
|
|
11
|
+
requirements (read the record from `ctx.data`; body rows are recycled),
|
|
12
|
+
theming and dark mode (`--ag-*` / `--ag-dark-*` tokens, `theme="dark"`),
|
|
13
|
+
sorting / filtering / quick-filter, pagination (full or compact paginator),
|
|
14
|
+
inline editing, row selection, master-detail and tree rows, CSV export, batch
|
|
15
|
+
writes via `applyEdits`, right-to-left layout, the UI-only event model, the
|
|
16
|
+
`dataPipelineConfiguration` hooks for server-side data, and upgrading from
|
|
17
|
+
apex-grid 3.x to 4.0.
|
|
17
18
|
metadata:
|
|
18
19
|
author: ApexCharts
|
|
19
|
-
version: "2.
|
|
20
|
-
library_version: "
|
|
20
|
+
version: "2.6.0"
|
|
21
|
+
library_version: "4.0.0"
|
|
21
22
|
category: data-visualization
|
|
22
23
|
tags: [grid, data-grid, table, web-component, lit, apex-grid]
|
|
23
24
|
docs: https://github.com/apexcharts/apexgrid
|
|
@@ -39,9 +40,9 @@ npm install apex-grid lit
|
|
|
39
40
|
|
|
40
41
|
That is the whole install. `apex-grid` brings its own runtime dependencies (`lit`, `@lit/context`, `@lit-labs/virtualizer`) and there is **no theme CSS to import** from anywhere.
|
|
41
42
|
|
|
42
|
-
> **Changed in 3.5.0:** earlier versions also pulled in `igniteui-webcomponents` and `igniteui-theming` transitively. Both are gone. The grid never registered an Ignite UI element, so nothing you wrote against the grid is affected, but code that relied on `igc-*` elements being installed as a side effect must now depend on that package directly. An Ignite UI theme on the page never fully themed the grid: through 3.x it only tinted the brand and replaced the font. From 4.0 the only way to make the grid follow an Ignite UI theme is the `apex-grid/ignite-ui` add-on
|
|
43
|
+
> **Changed in 3.5.0:** earlier versions also pulled in `igniteui-webcomponents` and `igniteui-theming` transitively. Both are gone. The grid never registered an Ignite UI element, so nothing you wrote against the grid is affected, but code that relied on `igc-*` elements being installed as a side effect must now depend on that package directly. An Ignite UI theme on the page never fully themed the grid: through 3.x it only tinted the brand and replaced the font. From 4.0 the only way to make the grid follow an Ignite UI theme is the `apex-grid/ignite-ui` add-on (§1.3).
|
|
43
44
|
|
|
44
|
-
> Confirmed working versions: `apex-grid@
|
|
45
|
+
> Confirmed working versions: `apex-grid@4.0.0`, `lit@^3.3.0` (and `apex-grid-enterprise@0.9.0`, which needs apex-grid 4.0). 4.0 is a major version: moving an app from 3.x, read §1.6 first.
|
|
45
46
|
|
|
46
47
|
### 1.2 Register the element + size the host — `setup()` does both
|
|
47
48
|
|
|
@@ -102,18 +103,17 @@ apex-grid[theme~='dark'] { --ag-grid-shadow: var(--ag-dark-shadow-card); } /* th
|
|
|
102
103
|
|
|
103
104
|
**CSS parts.** Since 3.6.0 every part a body row renders is reachable with `apex-grid::part(...)`: `cell`, `selection-cell`, `selection-checkbox`, `expansion-cell`, `expansion-toggle`, `expansion-chevron`, `detail-panel`, `row-number-cell`, `row-number`, `reorder-handle-cell`, `reorder-handle`, `reorder-grip`. Before, only `cell` was. Parts inside your own `detailTemplate` content are reached by listing them in `expansion.exportParts` (`references/state-and-features.md`).
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
**Ignite UI theming (apex-grid 4.0 pre-release only: `4.0.0-beta.2` on the npm `next` tag).** The add-on is not in 3.6.0, the version `npm install apex-grid` installs, and `import 'apex-grid/ignite-ui'` does not resolve there. Install the pre-release first:
|
|
106
|
+
Since 4.0.0 the paginator and toolbar parts are reachable too. Paginator: `paginator-host` (the element), `paginator`, `paginator-size`, `paginator-info`, `paginator-controls`, `paginator-page`, `paginator-button` (every button) plus one part per button, `paginator-first`, `paginator-prev`, `paginator-next`, `paginator-last`. Toolbar: `toolbar-host`, `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`. Also new: `table` (the element with the grid role) and `drag-ghost` (the column drag ghost).
|
|
108
107
|
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
```css
|
|
109
|
+
apex-grid::part(paginator-button) { border-radius: 999px; }
|
|
110
|
+
apex-grid::part(paginator-next) { font-weight: 600; }
|
|
111
|
+
apex-grid::part(search-input) { font-size: 14px; }
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
> The deprecated `setup({ theme })` option does **not** change the grid's appearance. It only ever forwarded to Ignite UI's `configureTheme()`, and since 3.5.0 it is inert and warns once. Omit it; use `--ag-*` variables (and the `theme="dark"` attribute) instead, and to follow an Ignite UI theme, the `apex-grid/ignite-ui` add-on below.
|
|
115
115
|
|
|
116
|
-
To make grids follow an Ignite UI theme loaded on the page, import the add-on once. It is free and needs no licence key.
|
|
116
|
+
**Ignite UI theming (new in 4.0.0).** The add-on ships with apex-grid 4.0; on 3.x `import 'apex-grid/ignite-ui'` does not resolve. To make grids follow an Ignite UI theme loaded on the page, import the add-on once. It is free and needs no licence key.
|
|
117
117
|
|
|
118
118
|
```ts
|
|
119
119
|
import 'apex-grid/ignite-ui';
|
|
@@ -126,10 +126,10 @@ Every grid on the page, in the document or any shadow root, then follows the the
|
|
|
126
126
|
- **Per grid:** `design-system="off"` (property `designSystem = false`) turns the Ignite UI colours and font off (the variant still picks the mode); `tokens="off"` (`tokens = false`) turns off the `--apx-*` tokens and Ignite UI together, the variant included, for the grid and the content it renders.
|
|
127
127
|
- **No `refreshTokens()` needed:** the grid follows the cascade live.
|
|
128
128
|
- **Your own CSS:** while the add-on is loaded, the tokens that follow Ignite UI (text, lines, surfaces, header, state colours, pinned edge) leave the root defaults, so page CSS that reads one needs its own fallback: `var(--ag-text, #111827)`.
|
|
129
|
-
- **Enterprise popups:** new tokens `--ag-overlay` and `--ag-overlay-text` (dark grids: `--ag-dark-overlay`, `--ag-dark-overlay-text`) colour the `apex-grid-enterprise` popups (apex-grid-enterprise
|
|
129
|
+
- **Enterprise popups:** new tokens `--ag-overlay` and `--ag-overlay-text` (dark grids: `--ag-dark-overlay`, `--ag-dark-overlay-text`) colour the `apex-grid-enterprise` popups (apex-grid-enterprise 0.9.0); unset, each keeps its built-in colours. For the enterprise chart panel to read the theme itself, also import `apexcharts/features/ignite-ui` (ApexCharts 8.8.0 or later; the grid never loads it).
|
|
130
130
|
- **`--ag-font`:** in 4.0 an `--ag-font` you set wins over the family and Ignite UI fonts, and it no longer has a root default (the built-in Inter stack is unchanged inside the grid), so page CSS that reads it needs a fallback, as with `--ag-brand`.
|
|
131
131
|
|
|
132
|
-
> **Breaking in 4.0
|
|
132
|
+
> **Breaking in 4.0.0:** the grid no longer reads Ignite UI tokens by itself. Through 3.x an Ignite UI theme on the page re-tinted the brand (`--ig-primary-500`, `--ig-primary-700`) and replaced the font (`--ig-font-family`) with no import. From 4.0 an Ignite UI stylesheet changes nothing until you `import 'apex-grid/ignite-ui'`, which brings the brand and font back along with the rest of the theme. The 4.0 grid also follows the family `--apx-*` tokens: a page that sets them on `:root` for its charts sees the grid follow them too (set the matching `--ag-*` tokens to keep the grid as it was).
|
|
133
133
|
|
|
134
134
|
### 1.4 Minimal example — a styled, sortable, filterable table
|
|
135
135
|
|
|
@@ -175,6 +175,20 @@ If rows don't render at all, it's almost always the host height (§1.2). If the
|
|
|
175
175
|
|
|
176
176
|
> **Dev-server reminder.** ES module imports need an HTTP server — opening via `file://` fails with CORS errors. Use Vite (`npm create vite@latest`), any static server, or your framework's dev server.
|
|
177
177
|
|
|
178
|
+
### 1.6 Upgrading from 3.x (4.0.0)
|
|
179
|
+
|
|
180
|
+
4.0 is a major version. Check these when moving an app from 3.6:
|
|
181
|
+
|
|
182
|
+
- **Body rows are recycled by default** (`rowRecycling`, default `true`). A scroll, sort or filter re-binds the rendered row elements to the records now in view, so a row element and its cells can show another record later. In `cellTemplate` / `editorTemplate`, read the record from `ctx.data` (not `ctx.row.data`), derive everything from the context on each render, and do not hold `ctx.row`, `ctx.parent` or `grid.rows` elements across an `await` or a scroll. A template that keeps element state (an input's typed value, a third-party widget) can wrap itself in `keyed(ctx.data, ...)` (`lit/directives/keyed.js`), or opt the grid out with `grid.rowRecycling = false` (attribute `row-recycling="false"`) for record-keyed rows. Detail panels follow their record and need no change.
|
|
183
|
+
- **The grid role moved off the host.** `role` (`grid`, or `treegrid` for tree, master-detail and grouped grids), `aria-rowcount`, `aria-colcount`, `aria-multiselectable` and the accessible name are on the inner `[part~="table"]` element. An `aria-label` / `aria-labelledby` on the host still names the grid and `aria-describedby` describes it. Tests that read these from the host query `grid.shadowRoot.querySelector('[part~="table"]')`.
|
|
184
|
+
- **An Ignite UI theme does nothing until you `import 'apex-grid/ignite-ui'`** (3.x took its brand and font on its own), and **`--ag-font` has no root default**: page CSS that reads it writes `var(--ag-font, Inter, sans-serif)` (§1.3).
|
|
185
|
+
- **Pagination.** Pinned rows no longer count toward `totalItems`, `pageCount` or the summary (they show on every page). A local page change no longer reruns the pipeline, so custom `sort` / `filter` hooks are not called on it (remote mode still reruns). `DataPipelineConfiguration.pagination` and the `'pagination'` value of `DataPipelineParams.type` are removed: the hook was never called, but TypeScript that sets it stops compiling. For server paging use `pagination.mode: 'remote'` (`references/data-pipeline.md`).
|
|
186
|
+
- **`min()` and `max()` fail a value that is not a number.** On 3.x text such as `'abc'` passed both. Empty values still pass.
|
|
187
|
+
- **Smaller type and DOM changes:** `ctx.data` is a required field of the cell context (code that builds one by hand supplies it), `ApexGridPaginator.pageSizeOptions` is `readonly number[]`, the paginator's previous button id is `prev` (was `previous`), and the protected `ApexGrid.headerRowCount` and `ApexGridHeaderRow.renderDragGhost` are gone (subclasses only).
|
|
188
|
+
- **Noticeable, not breaking:** `pageItems` keeps the same array while nothing that shapes it changes; the column header row is no longer a Tab stop; the column menu opens in the top layer (it can extend outside the grid's box); a page change is announced once.
|
|
189
|
+
|
|
190
|
+
New in 4.0: `showHeader`, `selection.isRowSelectable`, `pagination.paginator: 'compact'` (§5), `::part()` for every paginator and toolbar part (§1.3), `ctx.data` (§4), the Ignite UI add-on and the family `--apx-*` tokens (§1.3), IME input and numbers typed in full width or with the locale's grouping (§5), and locale keys for text that stayed English (`references/state-and-features.md`).
|
|
191
|
+
|
|
178
192
|
---
|
|
179
193
|
|
|
180
194
|
## 2. Naming — `apex-grid` vs `<apex-grid>` vs `ApexGrid`
|
|
@@ -197,7 +211,7 @@ AI models trained before the package was published may suggest `apexgrid` (no hy
|
|
|
197
211
|
|
|
198
212
|
3. **`columns` and `data` are properties, not attributes.** Use property binding (`.columns=${...}` in Lit, `[columns]=` in Angular, `:columns.prop=` in Vue, `el.columns = ...` in vanilla JS). A stringified `columns="[...]"` HTML attribute becomes the literal string and the grid renders nothing.
|
|
199
213
|
|
|
200
|
-
4. **`column.key` must be a real `keyof T`.** No `field` / `dataIndex` / `accessor` aliases. To display a derived value, compute it in `cellTemplate`,
|
|
214
|
+
4. **`column.key` must be a real `keyof T`.** No `field` / `dataIndex` / `accessor` aliases. To display a derived value, compute it in `cellTemplate` from `ctx.data`, the record the cell renders (4.0). `ctx.row` is the `<apex-grid-row>` element, which recycling can re-bind to another record.
|
|
201
215
|
|
|
202
216
|
5. **Set `type` per column.** The primitive types `'string'`, `'number'`, `'boolean'` drive the default sort comparer, filter operands, and editor. The default is `'string'` — leaving a numeric column unset sorts as strings ("10" before "9"). There are also presentation types — `'select'`, `'rating'`, `'date'`, `'image'`, `'currency'`, `'avatar'`, `'badge'`, `'progress'`, `'sparkline'`, `'status'` — that change rendering while sorting/filtering as their underlying value (§4).
|
|
203
217
|
|
|
@@ -239,7 +253,7 @@ interface ColumnConfiguration<T extends object, K extends keyof T = keyof T> {
|
|
|
239
253
|
shape?: 'square' | 'circle'; // 'image' crop (default 'square')
|
|
240
254
|
alt?: string; // 'image' alt text
|
|
241
255
|
currency?: string; // 'currency' ISO 4217 code (default 'USD')
|
|
242
|
-
locale?: string; // 'currency' BCP 47 locale
|
|
256
|
+
locale?: string; // 'currency' BCP 47 locale; 4.0: also how numbers typed into the filter panel, or checked by min() and max(), are read (enterprise: paste, advanced filter)
|
|
243
257
|
badgeVariant?: 'gold' | 'brand' | 'neutral' | 'muted' | ((v) => …); // 'badge'
|
|
244
258
|
statusVariant?: 'active' | 'trial' | 'churn' | ((v) => …); // 'status'
|
|
245
259
|
showDelta?: boolean; // 'sparkline' trailing delta label (default true)
|
|
@@ -287,14 +301,16 @@ grid.filter({ key: 'price', condition: 'greaterThan', searchTerm: 100 }); // a
|
|
|
287
301
|
|
|
288
302
|
### Cell template
|
|
289
303
|
|
|
290
|
-
Receives `ApexCellContext<T, K>` (`{
|
|
304
|
+
Receives `ApexCellContext<T, K>` (`{ data, value, column, parent, row, commit? }`). `data` (4.0) is the record the cell was rendered for; `parent` and `row` are the cell and `<apex-grid-row>` elements. Must return a Lit `TemplateResult`:
|
|
291
305
|
|
|
292
306
|
```ts
|
|
293
307
|
import { html } from 'lit';
|
|
294
308
|
|
|
295
|
-
{ key: 'name', cellTemplate: ({ value,
|
|
309
|
+
{ key: 'name', cellTemplate: ({ value, data }) => html`<strong>${value}</strong> <small>#${data.id}</small>` }
|
|
296
310
|
```
|
|
297
311
|
|
|
312
|
+
> **Changed in 4.0.0:** body rows are recycled by default (`rowRecycling`), so a row element and its cells can show another record after a scroll, sort or filter. Read the record from `ctx.data`, which stays that record even after an `await`; `ctx.row.data` is whatever the element shows now. Do not keep state in the elements a template creates, or wrap it in `keyed(ctx.data, ...)`, or set `grid.rowRecycling = false` (§1.6).
|
|
313
|
+
|
|
298
314
|
Since 3.6.0 a `cellTemplate` runs again for every visible row after each pipeline run (sort, filter, edit, quick filter, tree toggle), so a template that reads other fields of its row stays current. Keep templates cheap.
|
|
299
315
|
|
|
300
316
|
### Auto-generated columns — the 3-line demo
|
|
@@ -338,7 +354,9 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
338
354
|
| `columns` | `ColumnConfiguration<T>[]` | Each entry merged with column defaults. |
|
|
339
355
|
| `autoGenerate` (attr `auto-generate`) | `boolean` | Ignored if `columns` is non-empty. |
|
|
340
356
|
| `sortConfiguration` | `{ multiple, triState }` | Multi-column + tri-state sorting toggles. |
|
|
341
|
-
| `dataPipelineConfiguration` | `{ sort?, filter?,
|
|
357
|
+
| `dataPipelineConfiguration` | `{ sort?, filter?, quickFilter? }` | Server-side / async hooks (§ data-pipeline). 4.0 removed the never-called `pagination` hook: page on the server with `pagination.mode: 'remote'`. |
|
|
358
|
+
| `rowRecycling` (attr `row-recycling`) | `boolean` | Default `true` (4.0): body row elements are re-bound to other records on scroll, sort and filter. `false` (`row-recycling="false"`) keeps record-keyed rows for templates that hold element state (§1.6). |
|
|
359
|
+
| `showHeader` (attr `show-header`) | `boolean` | Default `true`. `false` (4.0) leaves out the column header row, the column group row and the filter panel, so a short grid (a dashboard tile) gives that room to the rows. Screen readers still announce the column names; sort, filter, widths and the page are kept. While hidden, resizing, the column menu, drag reorder and the header filter button are unavailable. |
|
|
342
360
|
| `quickFilter` (attr `quick-filter`) | `string` | Global substring search across visible columns. |
|
|
343
361
|
| `showQuickFilter` / `showExport` | `boolean` | Toolbar UI toggles for quick-filter input / export menu. |
|
|
344
362
|
| `columnReordering` (attr `column-reordering`) | `boolean` | Drag-reorder headers; per-column opt-out via `reorderable`. |
|
|
@@ -354,10 +372,10 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
354
372
|
| `exportFormats` *(getter)* | `readonly { id, label }[]` | Export menu formats (CSV in the community grid). |
|
|
355
373
|
| `sortExpressions` / `filterExpressions` | `…[]` (get/set) | Current state. Assigning **replaces** the whole sort / filter, and `[]` or `null` clears it (3.6; on 3.5 assignment merged and `[]` did nothing). `sort()` / `filter()` add instead. `clearSort()` / `clearFilter()` also clear. |
|
|
356
374
|
| `selectedRows` / `expandedRows` | `T[]` (get/set) | Snapshots; set to replace (goes through cancellable events). |
|
|
357
|
-
| `rows` *(getter)* | `ApexGridRow<T>[]` | **Currently rendered only** (virtualized). |
|
|
375
|
+
| `rows` *(getter)* | `ApexGridRow<T>[]` | **Currently rendered only** (virtualized). With recycling (4.0 default) an element can show another record later: read its `data` each time, never keep it as a handle to a record. |
|
|
358
376
|
| `dataView` *(getter)* | `readonly T[]` | Full post-filter/sort data. |
|
|
359
|
-
| `pageItems` *(getter)* | `readonly T[]` | Current page slice (= `dataView` if no pagination). |
|
|
360
|
-
| `totalItems` / `pageCount` *(getters)* | `number` | Row / page totals. |
|
|
377
|
+
| `pageItems` *(getter)* | `readonly T[]` | Current page slice (= `dataView` if no pagination), without pinned rows. Since 4.0 the same array while nothing that shapes it changes. |
|
|
378
|
+
| `totalItems` / `pageCount` *(getters)* | `number` | Row / page totals. With pagination, pinned rows are not counted (4.0); in remote mode `totalItems` is the `pagination.totalItems` you supply. |
|
|
361
379
|
| `page` / `pageSize` | `number` (get/set) | Current page (0-based) / page size. |
|
|
362
380
|
|
|
363
381
|
### Feature config properties (opt-in)
|
|
@@ -365,8 +383,8 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
365
383
|
| Property | Shape |
|
|
366
384
|
|---|---|
|
|
367
385
|
| `editing` | `{ enabled, mode?: 'cell' \| 'row', trigger?: 'click' \| 'doubleClick', history?: { enabled, stackSize? } }` — plus per-column `editable: true` |
|
|
368
|
-
| `selection` | `{ enabled, mode?: 'single' \| 'multiple', showCheckboxColumn? }` |
|
|
369
|
-
| `pagination` | `{ enabled, mode?: 'local' \| 'remote', page?, pageSize?, pageSizeOptions?, totalItems
|
|
386
|
+
| `selection` | `{ enabled, mode?: 'single' \| 'multiple', showCheckboxColumn?, isRowSelectable? }`. `isRowSelectable(row)` (4.0) keeps chosen rows (a pinned total row) out of selection: no checkbox, and clicks, Shift/Ctrl clicks, Space, Select all, `selectRow`, `selectedRows = ...` and `setState` skip them; the header checkbox counts only selectable rows. Unset, every row is selectable, pinned rows included |
|
|
387
|
+
| `pagination` | `{ enabled, mode?: 'local' \| 'remote', page?, pageSize?, pageSizeOptions?, totalItems?, paginator?: 'full' \| 'compact' }`. `'full'` (default) drops the page size select, then first / last, when the grid is narrow; `'compact'` (4.0) always shows summary, previous, page / count, next |
|
|
370
388
|
| `tree` | `{ enabled, getDataPath: (row) => string[], groupColumnKey?, defaultExpanded?, childIndent? }`: flat data, hierarchy derived. Under a filter, the ancestors of a match show as their own rows (3.6) |
|
|
371
389
|
| `expansion` | `{ enabled, detailTemplate: (ctx) => TemplateResult, isExpandable?, showToggleColumn?, exportParts? }`: master-detail. `exportParts` (3.6) re-exports parts used in the detail content |
|
|
372
390
|
| `rowPinning` | `{ enabled }` — sticky top/bottom bands via `pinRow()` / `unpinRow()` |
|
|
@@ -374,8 +392,10 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
374
392
|
|
|
375
393
|
```ts
|
|
376
394
|
grid.editing = { enabled: true, mode: 'cell', trigger: 'doubleClick', history: { enabled: true } };
|
|
377
|
-
grid.selection = { enabled: true, mode: 'multiple', showCheckboxColumn: true
|
|
378
|
-
|
|
395
|
+
grid.selection = { enabled: true, mode: 'multiple', showCheckboxColumn: true,
|
|
396
|
+
isRowSelectable: (row) => row !== totalRow }; // 4.0
|
|
397
|
+
grid.pagination = { enabled: true, pageSize: 25, paginator: 'compact' }; // 'compact': 4.0
|
|
398
|
+
grid.showHeader = false; // 4.0: no header rows (dashboard tile)
|
|
379
399
|
grid.rowPinning = { enabled: true };
|
|
380
400
|
grid.rowReordering = { enabled: true };
|
|
381
401
|
```
|
|
@@ -466,7 +486,11 @@ Keyboard-driven commit (Enter) or cancel (Escape) returns focus to the cell, so
|
|
|
466
486
|
|
|
467
487
|
**Editing behavior:** clearing a `number` or `currency` editor now commits `null` (previously it wrote `NaN`). An empty or unparseable numeric input leaves the cell empty rather than poisoning the row. Since 3.6.0 an open editor stays on the record it was opened for when the data, sort, filter or page change under it, and the edit is cancelled if that record leaves the view; row mode edits a staged copy (`references/state-and-features.md`).
|
|
468
488
|
|
|
469
|
-
**Announcements:** sorting, filtering, selection, paging, row pinning, row reorder, expansion, and undo / redo each announce through a localized live region
|
|
489
|
+
**Announcements:** sorting, filtering, selection, paging, row pinning, row reorder, expansion, and undo / redo each announce through a localized live region (a page change once, "Page X of N"). The strings are locale keys (`announce.*`, `header.*`, `grid.label`, `editor.rating`, and since 4.0 `cell.rating`, `cell.true` / `cell.false`); override them via `localeText` (see `references/state-and-features.md`).
|
|
490
|
+
|
|
491
|
+
> **Changed in 4.0.0:** the `grid` / `treegrid` role, `aria-rowcount`, `aria-colcount`, `aria-multiselectable` (multi-row selection) and the localized accessible name ("Data grid", `grid.label`) are on the inner `[part~="table"]` element, not the `<apex-grid>` host. It holds only the header rows, the pinned row bands and the body, so the toolbar, paginator, filter panel and live region are no longer read as table content. An `aria-label` / `aria-labelledby` you put on the host still names the grid. The column header row is no longer a Tab stop.
|
|
492
|
+
|
|
493
|
+
**Input methods and typed numbers (4.0):** Japanese, Chinese or Korean input through an IME works in the cell editor, the filter panel and the quick filter: the Enter, Escape and Tab that confirm or cancel a conversion stay with the IME (they do not commit or cancel an edit, close the filter panel or clear the quick filter), and the filter panel and quick filter apply the text once the conversion ends. A number typed in the filter panel, or checked by `min()` / `max()`, is read as a person writes it: full-width digits and signs (`123`, `-5`), the grouping and decimal separator of the column's `locale` (else the runtime locale, so `1.234,5` is 1234.5 under `'de-DE'`), and a leading or trailing `¥`, `$`, `€`, `円` or the column currency's symbol. A plain ASCII decimal reads as before in every locale (`1.000` stays 1 even under `'de-DE'`), and other text splits: the filter panel falls back to its leading number as in 3.x (`12.5%` filters by 12.5, `10²` by 10), while `min()` / `max()` read `12.5%` or `10²` as not a number, so the value fails. Programmatic `filter()` search terms are not parsed this way: pass numbers.
|
|
470
494
|
|
|
471
495
|
**New in 3.5, all automatic:** the grid honours `prefers-reduced-motion` (every transition routes through the `--ag-dur-*` tokens, which zero under the media query), it stays usable under forced colors / Windows High Contrast (the active-cell ring, invalid-cell ring, selected-row bar and focus rings are painted as outlines, which forced colors recolours rather than drops), and interactive targets meet the WCAG 2.2 AA 24x24 minimum. The only visible change is that the paginator bar is 2px taller.
|
|
472
496
|
|
|
@@ -496,7 +520,7 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
496
520
|
|
|
497
521
|
### 3. Importing a theme CSS that no longer exists
|
|
498
522
|
❌ `import 'igniteui-webcomponents/themes/light/bootstrap.css'` + `setup({ theme: 'bootstrap' })` is obsolete; it never affected the grid, and as of 3.5.0 the option is inert and that package is not even installed.
|
|
499
|
-
✅ Nothing to import for a styled grid: it styles itself; retheme via `--ag-*` CSS variables. The only required CSS is a host **height**. To follow an Ignite UI theme that the page loads, the path is the `apex-grid/ignite-ui` add-on (apex-grid 4.0
|
|
523
|
+
✅ Nothing to import for a styled grid: it styles itself; retheme via `--ag-*` CSS variables. The only required CSS is a host **height**. To follow an Ignite UI theme that the page loads, the path is the `apex-grid/ignite-ui` add-on (apex-grid 4.0, §1.3), not a theme import into the grid.
|
|
500
524
|
|
|
501
525
|
### 4. Stringified `columns` attribute
|
|
502
526
|
❌ `<apex-grid columns="[...]">` — becomes the literal string `"[object Object],..."`.
|
|
@@ -504,7 +528,7 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
504
528
|
|
|
505
529
|
### 5. `column.key` not a real key of `T`
|
|
506
530
|
❌ `{ key: 'fullName' }` when `User` has only `firstName` + `lastName`.
|
|
507
|
-
✅ Compute via template: `cellTemplate: ({
|
|
531
|
+
✅ Compute via template: `cellTemplate: ({ data }) => html\`${data.firstName} ${data.lastName}\``. The context's `data` is the record (4.0); `row` is the row element, not the record.
|
|
508
532
|
|
|
509
533
|
### 6. String from `cellTemplate`
|
|
510
534
|
❌ `cellTemplate: ({ value }) => \`<strong>\${value}</strong>\`` — renders as escaped text.
|
|
@@ -535,8 +559,16 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
535
559
|
✅ `apex-grid { --ag-dark-surface: #111827; --ag-dark-text: #f9fafb; }` (or on any ancestor). Brand, sizing and typography tokens are shared, so `--ag-brand` still applies to both.
|
|
536
560
|
|
|
537
561
|
### 13. Expecting an Ignite UI theme to restyle the grid without the add-on (4.0)
|
|
538
|
-
❌ Loading an Ignite UI theme stylesheet and expecting the 4.0 grid to follow it: without `apex-grid/ignite-ui` every `--ig-*` token is silently ignored (3.x picked up the brand and font on its own; 4.0 does not). Also ❌ `theme="light"` on a page that switches to a dark Ignite UI theme: the pinned mode blocks the dark variant. Also ❌ `import 'apex-grid/ignite-ui'`
|
|
539
|
-
✅
|
|
562
|
+
❌ Loading an Ignite UI theme stylesheet and expecting the 4.0 grid to follow it: without `apex-grid/ignite-ui` every `--ig-*` token is silently ignored (3.x picked up the brand and font on its own; 4.0 does not). Also ❌ `theme="light"` on a page that switches to a dark Ignite UI theme: the pinned mode blocks the dark variant. Also ❌ `import 'apex-grid/ignite-ui'` on apex-grid 3.x: the module does not exist there.
|
|
563
|
+
✅ apex-grid 4.0, then `import 'apex-grid/ignite-ui'` once (free, no licence key) and leave the `theme` attribute off so the grid follows `--ig-theme-variant`. Use `design-system="off"` or `tokens="off"` to opt one grid out.
|
|
564
|
+
|
|
565
|
+
### 14. Treating a row element as a record (4.0)
|
|
566
|
+
❌ A click handler in a `cellTemplate` that reads `ctx.row.data` after an `await`, or code that keeps `grid.rows[0]` as a handle: with recycling (the 4.0 default) that element can show another record after a scroll, sort or filter, so the code acts on the wrong row. Also ❌ an `<input>` or third-party widget in a `cellTemplate` that keeps its own typed state: the state shows on another record after a scroll.
|
|
567
|
+
✅ Read `ctx.data` (it stays the record the cell rendered, across an `await`), keep state in your data rather than in the elements, wrap a stateful template in `keyed(ctx.data, html\`...\`)` (`lit/directives/keyed.js`), or set `grid.rowRecycling = false` for record-keyed rows.
|
|
568
|
+
|
|
569
|
+
### 15. Reading the grid's ARIA from the host (4.0)
|
|
570
|
+
❌ `expect(grid.getAttribute('role')).toBe('grid')` or `grid.getAttribute('aria-rowcount')` in a test: the host no longer carries them.
|
|
571
|
+
✅ `grid.shadowRoot!.querySelector('[part~="table"]')!.getAttribute('role')`. Name the grid with `aria-label` / `aria-labelledby` on the host as before.
|
|
540
572
|
|
|
541
573
|
---
|
|
542
574
|
|
|
@@ -544,9 +576,10 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
544
576
|
|
|
545
577
|
| Topic | Reference File |
|
|
546
578
|
|---|---|
|
|
547
|
-
|
|
|
579
|
+
| Upgrading an app from 3.x to 4.0 | §1.6 |
|
|
580
|
+
| Column types, templates (`ctx.data`, row recycling), auto-generation, per-column styling, header/editor templates | `references/columns-and-templates.md` |
|
|
548
581
|
| Sort & filter — operands, expressions, multi-column, quick-filter, events | `references/sort-and-filter.md` |
|
|
549
|
-
| Server-side data hooks (sort/filter/
|
|
582
|
+
| Server-side data hooks (sort/filter/quickFilter), remote pagination, virtualization, `dataView` | `references/data-pipeline.md` |
|
|
550
583
|
| Row pinning / reordering, undo-redo, batch edits (`applyEdits`), validators, row-mode editing, column groups, state & schema (`getState`/`setState`/`getSchema`), localization, export source and CSV escaping, master-detail parts and nested grids (`exportParts`, `ownsEvent`) | `references/state-and-features.md` |
|
|
551
584
|
| End-to-end vanilla JS (no Lit `render`) | `references/vanilla-js.md` |
|
|
552
585
|
| Lit, React, Vue, Angular integration | `references/framework-integration.md` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apexgrid-skill",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "AI coding skill for the apex-grid web component — structured reference docs that help AI assistants (Claude, Cursor, Copilot, etc.) generate correct apex-grid code.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apex-grid",
|
|
@@ -36,7 +36,7 @@ interface BaseColumnConfiguration<T extends object, K extends keyof T = keyof T>
|
|
|
36
36
|
shape?: 'square' | 'circle'; // 'image' (def 'square')
|
|
37
37
|
alt?: string; // 'image' alt text
|
|
38
38
|
currency?: string; // 'currency' ISO 4217 (def 'USD')
|
|
39
|
-
locale?: string; // 'currency' BCP 47 locale
|
|
39
|
+
locale?: string; // 'currency' BCP 47 locale; 4.0: also the separators for numbers typed into the filter panel, or checked by min() and max() (enterprise: paste, advanced filter)
|
|
40
40
|
badgeVariant?: 'gold' | 'brand' | 'neutral' | 'muted' | ((v) => …); // 'badge'
|
|
41
41
|
statusVariant?: 'active' | 'trial' | 'churn' | ((v) => …); // 'status'
|
|
42
42
|
showDelta?: boolean; // 'sparkline' (def true)
|
|
@@ -100,15 +100,36 @@ Receives `ApexCellContext<T, K>`:
|
|
|
100
100
|
|
|
101
101
|
```ts
|
|
102
102
|
interface BaseApexCellContext<T, K> {
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
readonly data: T; // 4.0: the record this cell was rendered for; stays it after an await
|
|
104
|
+
parent: ApexGridCell<T>; // <apex-grid-cell> element (may later show another record)
|
|
105
|
+
row: ApexGridRow<T>; // <apex-grid-row> element (may later show another record)
|
|
105
106
|
column: ColumnConfiguration<T, K>;
|
|
106
107
|
value: T[K]; // T[K] — fully typed
|
|
107
|
-
commit?: (value: T[K]) => Promise<boolean>; // editable columns only: write without opening the editor
|
|
108
|
+
commit?: (value: T[K]) => Promise<boolean>; // editable columns only: write without opening the editor; writes to ctx.data
|
|
108
109
|
}
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
|
|
112
|
+
`ApexEditorContext` extends it with `commit(value)` and `cancel()`, so an `editorTemplate` gets `ctx.data` too. Unlike the cell context's `commit`, the editor's `commit` commits whichever cell is in edit when it is called, so do not call it after an `await` that can outlive the edit.
|
|
113
|
+
|
|
114
|
+
### Row recycling (Changed in 4.0.0)
|
|
115
|
+
|
|
116
|
+
Body rows are recycled by default (`grid.rowRecycling`, default `true`): a scroll, sort or filter re-binds the rendered row elements 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 can therefore show another record later. Templates must:
|
|
117
|
+
|
|
118
|
+
- **derive what they render from the context on each render**, and read the record from `ctx.data` (on 3.x the usual spelling was `ctx.row.data`, which now reads whatever record the element shows at that moment);
|
|
119
|
+
- **not hold `ctx.row` or `ctx.parent` across an `await`** (a `ctx.commit` after an `await` is safe: it writes to `ctx.data`);
|
|
120
|
+
- **not keep state in the elements they create** (an input's typed value, a third-party widget), or wrap the template in `keyed(ctx.data, ...)` so the state never shows on the wrong record.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { html } from 'lit';
|
|
124
|
+
import { keyed } from 'lit/directives/keyed.js';
|
|
125
|
+
|
|
126
|
+
{ key: 'notes',
|
|
127
|
+
cellTemplate: (ctx) => keyed(ctx.data, html`<my-rich-note .value=${ctx.value}></my-rich-note>`) }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
To keep record-keyed rows (each element stays with its record), opt out: `grid.rowRecycling = false` or `<apex-grid row-recycling="false">`. Detail panel content follows its record and keeps its DOM state without any change, and a grid created inside a `detailTemplate` keeps its own setting. Pinned rows are not recycled, but a pinned band can re-bind its elements (when a row above is unpinned), which is why `ctx.data` is the safe read everywhere.
|
|
131
|
+
|
|
132
|
+
Since 3.6 a `cellTemplate` runs again for every visible row, body and pinned, after each pipeline run (sort, filter, quick filter, edit, tree toggle), so a template that reads other fields of `ctx.data` stays current after an edit to another cell. Keep templates cheap; plain and typed cells are not re-rendered this way. In row edit mode, `commit(value)` stages the value with the row edit instead of writing straight to the record (3.6), so `cancelEdit()` rolls it back.
|
|
112
133
|
|
|
113
134
|
```ts
|
|
114
135
|
import { html } from 'lit';
|
|
@@ -121,11 +142,11 @@ const columns: ColumnConfiguration<User>[] = [
|
|
|
121
142
|
{ key: 'name',
|
|
122
143
|
cellTemplate: ({ value }) => html`<strong>${value}</strong>` },
|
|
123
144
|
|
|
124
|
-
// Conditional render with
|
|
145
|
+
// Conditional render with the record (ctx.data, 4.0)
|
|
125
146
|
{ key: 'subscribed', type: 'boolean',
|
|
126
|
-
cellTemplate: ({ value,
|
|
147
|
+
cellTemplate: ({ value, data }) => html`
|
|
127
148
|
<input type="checkbox" .checked=${value} disabled />
|
|
128
|
-
<small>${
|
|
149
|
+
<small>${data.name}</small>` },
|
|
129
150
|
|
|
130
151
|
// Prefer the built-in type over a template where one exists:
|
|
131
152
|
// `avatar` and `rating` are column types, no template needed.
|
|
@@ -176,7 +197,7 @@ import { required, min, max, pattern, custom } from 'apex-grid';
|
|
|
176
197
|
validators: [custom((value, ctx) => (value === ctx.data.id ? 'Cannot equal id' : null))] }
|
|
177
198
|
```
|
|
178
199
|
|
|
179
|
-
Built-in factories: `required(message?)`, `min(limit, message?)`, `max(limit, message?)`, `pattern(regex, message?)`, `custom(fn)`. `min`/`max`/`pattern` pass on empty
|
|
200
|
+
Built-in factories: `required(message?)`, `min(limit, message?)`, `max(limit, message?)`, `pattern(regex, message?)`, `custom(fn)`. `min`/`max`/`pattern` pass on empty values, so compose with `required`. **Changed in 4.0.0:** `min` / `max` fail a value that is not a number (on 3.x `'abc'` passed both), and read text as typed: full-width digits (`123`), the column `locale`'s grouping and decimal separator, and a currency mark. Called without a `message`, the built-ins report the `validation.*` locale keys (4.0), so `localeText` translates them. In row edit mode `ctx.data` is the staged row (the record with the row's pending values applied) since 3.6, so a cross-field rule such as `ctx.data.end >= ctx.data.start` sees the row being saved. See `references/state-and-features.md` for the `Validator` / `ValidatorContext` shapes and the `cellValidationFailed` payload.
|
|
180
201
|
|
|
181
202
|
**Numeric editing (3.4):** clearing a `number` or `currency` editor now commits `null`, not `NaN`. An empty or unparseable numeric input leaves the cell empty instead of writing `NaN` into the row. Editing also opens from the keyboard now (Enter or F2 on the active cell), not only by pointer; see SKILL.md §5 for the full keyboard model. Separately, a spreadsheet-style error value (an object carrying a `#...` code, such as an enterprise formula engine's `#VALUE!`) renders as its code in typed `number` / `currency` columns rather than coercing to `NaN` and showing an empty cell.
|
|
182
203
|
|
|
@@ -235,7 +256,7 @@ grid.updateColumns([
|
|
|
235
256
|
{ key: 'internalId', hidden: true }
|
|
236
257
|
```
|
|
237
258
|
|
|
238
|
-
Column data is still accessible via `grid.dataView` and the cell template's `
|
|
259
|
+
Column data is still accessible via `grid.dataView` and the cell template's `data` context, but no cell or header is rendered for it. Toggle at runtime via `updateColumns`.
|
|
239
260
|
|
|
240
261
|
## Resizable columns
|
|
241
262
|
|
|
@@ -249,8 +270,9 @@ The grid renders a drag handle on the right edge of the column header. Minimum r
|
|
|
249
270
|
|
|
250
271
|
| ❌ | ✅ |
|
|
251
272
|
|---|---|
|
|
252
|
-
| `{ key: 'fullName' }` when `fullName` isn't in `T` | Add to `T`, or compute via `cellTemplate: ({
|
|
253
|
-
| `cellTemplate: ({ row }) => row.name` | `row` is the `<apex-grid-row>` element
|
|
273
|
+
| `{ key: 'fullName' }` when `fullName` isn't in `T` | Add to `T`, or compute via `cellTemplate: ({ data }) => html\`${data.firstName} ${data.lastName}\`` |
|
|
274
|
+
| `cellTemplate: ({ row }) => row.name` | `row` is the `<apex-grid-row>` element, not the record: read `data.name` (4.0) |
|
|
275
|
+
| Reading `ctx.row.data` after an `await`, or keeping state in a template's elements | With row recycling (4.0 default) the element can show another record by then: read `ctx.data`, use `keyed(ctx.data, ...)`, or set `grid.rowRecycling = false` |
|
|
254
276
|
| `cellTemplate: ({ value }) => \`<b>\${value}</b>\`` (string) | `cellTemplate: ({ value }) => html\`<b>\${value}</b>\`` |
|
|
255
277
|
| `{ key: 'age', sort: true }` (default `type: 'string'`) | Set `type: 'number'` |
|
|
256
278
|
| `{ key: 'created', type: 'string', sort: { comparer } }` to fake dates | `{ key: 'created', type: 'date', format: 'medium' }` — a real type (accepts `Date`/ISO/timestamp) |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## How the pipeline runs
|
|
4
4
|
|
|
5
|
-
When `data`, `columns`, sort state, filter state, the quick-filter value, or the page changes, Apex Grid runs an async pipeline:
|
|
5
|
+
When `data`, `columns`, sort state, filter state, the quick-filter value, or (in remote pagination mode) the page changes, Apex Grid runs an async pipeline. Since 4.0 a local page change only re-slices the view and does not rerun it. The full pipeline:
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
data ─► (quickFilter) ─► (filter) ─► (sort) ─► (pagination) ─► virtualizer ─► rendered rows
|
|
@@ -13,18 +13,19 @@ By default every stage runs in-memory. Override any stage with `dataPipelineConf
|
|
|
13
13
|
## `dataPipelineConfiguration`
|
|
14
14
|
|
|
15
15
|
```ts
|
|
16
|
-
type DataPipelineParams<T> = { data: T[]; grid: ApexGrid<T>; type: 'sort' | 'filter' | 'quickFilter'
|
|
16
|
+
type DataPipelineParams<T> = { data: T[]; grid: ApexGrid<T>; type: 'sort' | 'filter' | 'quickFilter' };
|
|
17
17
|
type DataPipelineHook<T> = (state: DataPipelineParams<T>) => T[] | Promise<T[]>;
|
|
18
18
|
|
|
19
19
|
interface DataPipelineConfiguration<T extends object> {
|
|
20
20
|
sort?: DataPipelineHook<T>;
|
|
21
21
|
filter?: DataPipelineHook<T>; // column filters
|
|
22
22
|
quickFilter?: DataPipelineHook<T>; // global search (runs before column filter)
|
|
23
|
-
pagination?: DataPipelineHook<T>; // return the page slice; remote: set pagination.totalItems
|
|
24
23
|
}
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
The grid invokes the matching hook in place of its built-in pipeline stage. Whatever the hook returns becomes the input for the next stage (or the final view). All
|
|
26
|
+
The grid invokes the matching hook in place of its built-in pipeline stage. Whatever the hook returns becomes the input for the next stage (or the final view). All three hooks are optional: omit a stage to keep it in-memory.
|
|
27
|
+
|
|
28
|
+
> **Changed in 4.0.0 (types only):** `DataPipelineConfiguration.pagination` and the `'pagination'` value of `DataPipelineParams.type` are removed. The hook was documented but never called, so nothing changes at runtime, but TypeScript that sets it stops compiling. Page on the server with remote pagination (below).
|
|
28
29
|
|
|
29
30
|
```ts
|
|
30
31
|
grid.dataPipelineConfiguration = {
|
|
@@ -62,7 +63,27 @@ grid.dataPipelineConfiguration = {
|
|
|
62
63
|
|
|
63
64
|
### Order of operations
|
|
64
65
|
|
|
65
|
-
The grid runs the stages in a fixed order: **quick-filter → column filter → sort → pagination**. If your server endpoint does several of these together, do the work in the earliest relevant hook (returning the fully processed array) and omit the later hooks.
|
|
66
|
+
The grid runs the stages in a fixed order: **quick-filter → column filter → sort → pagination**. If your server endpoint does several of these together, do the work in the earliest relevant hook (returning the fully processed array) and omit the later hooks.
|
|
67
|
+
|
|
68
|
+
### Server-driven paging: `pagination.mode: 'remote'`
|
|
69
|
+
|
|
70
|
+
In remote mode the grid does not slice `data`: it renders whatever `data` holds as the current page. Fetch that page on `pageChanged`, assign it to `data`, and keep `pagination.totalItems` at the number of rows the server holds for the current filter, so the paginator can count the pages.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
grid.pagination = { enabled: true, mode: 'remote', pageSize: 25, totalItems: 0 };
|
|
74
|
+
|
|
75
|
+
async function load(page: number, pageSize: number) {
|
|
76
|
+
const res = await fetch(`/api/users?page=${page}&size=${pageSize}`);
|
|
77
|
+
const { rows, total } = await res.json();
|
|
78
|
+
grid.pagination = { ...grid.pagination!, page, pageSize, totalItems: total }; // a `page` in the config is applied on every assignment
|
|
79
|
+
grid.data = rows;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
grid.addEventListener('pageChanged', (e) => load(e.detail.page, e.detail.pageSize));
|
|
83
|
+
load(0, 25);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In remote mode a page or page size change reruns the pipeline (so your `sort` / `filter` hooks run again). **Changed in 4.0.0:** in local mode it no longer does, so custom `sort` and `filter` hooks are not called on a local page change, and with pagination on, pinned rows no longer count toward `totalItems`, `pageCount` or the summary (they show on every page).
|
|
66
87
|
|
|
67
88
|
### Hooks fire on every state change
|
|
68
89
|
|
|
@@ -100,18 +121,20 @@ grid.dataPipelineConfiguration = {
|
|
|
100
121
|
|
|
101
122
|
## Virtualization
|
|
102
123
|
|
|
103
|
-
Apex Grid uses [`@lit-labs/virtualizer`](https://www.npmjs.com/package/@lit-labs/virtualizer) (`<apex-virtualizer
|
|
124
|
+
Apex Grid uses [`@lit-labs/virtualizer`](https://www.npmjs.com/package/@lit-labs/virtualizer) (`<apex-virtualizer>`, pinned to exactly 2.1.1 in 4.0) to render rows. **Only currently visible rows + a small overscan are present in the DOM.**
|
|
104
125
|
|
|
105
126
|
| API | What it returns |
|
|
106
127
|
|---|---|
|
|
107
128
|
| `grid.rows` | `ApexGridRow<T>[]` — only the rendered chunk |
|
|
108
129
|
| `grid.dataView` | `readonly T[]` — full post-pipeline data (all rows) |
|
|
109
|
-
| `grid.totalItems` | `number
|
|
130
|
+
| `grid.totalItems` | `number`: `dataView.length`; with pagination, without pinned rows (4.0); in remote mode, the `pagination.totalItems` you supply |
|
|
131
|
+
|
|
132
|
+
**Row recycling (Changed in 4.0.0).** `grid.rowRecycling` defaults to `true`: a scroll, sort or filter re-binds the rendered row elements to the records now in view (and only the rows that scroll in render). So a row element can show a different record after any of those, not only after a scroll. `grid.rowRecycling = false` (attribute `row-recycling="false"`) keeps record-keyed rows. Templates read the record from `ctx.data` (`references/columns-and-templates.md`).
|
|
110
133
|
|
|
111
134
|
Implications:
|
|
112
135
|
|
|
113
136
|
- **Don't size the grid based on `grid.rows.length`.** Use `grid.totalItems`.
|
|
114
|
-
- **Don't hold references to `<apex-grid-row>` elements** across
|
|
137
|
+
- **Don't hold references to `<apex-grid-row>` elements** across a scroll, sort or filter: read `row.data` each time, never keep the element as a handle to a record.
|
|
115
138
|
- **DOM-driven row queries** (`document.querySelectorAll('apex-grid-row')`) only see visible rows.
|
|
116
139
|
|
|
117
140
|
## When the pipeline runs
|
|
@@ -152,7 +175,8 @@ Assigning `grid.data = null` or `undefined` (a framework prop still loading) sho
|
|
|
152
175
|
| ❌ | ✅ |
|
|
153
176
|
|---|---|
|
|
154
177
|
| Reading `grid.rows.length` for total count | `grid.totalItems` (or `grid.dataView.length`) |
|
|
155
|
-
| Caching `<apex-grid-row>` references across scrolls | Look them up fresh from `grid.rows` (or operate on `grid.dataView` data) |
|
|
178
|
+
| Caching `<apex-grid-row>` references across scrolls, sorts or filters | Look them up fresh from `grid.rows` (or operate on `grid.dataView` data) |
|
|
179
|
+
| `dataPipelineConfiguration.pagination` to page on the server | Removed in 4.0 (it was never called): `pagination.mode: 'remote'` + `pageChanged` + `pagination.totalItems` |
|
|
156
180
|
| Re-fetching from the server only on UI events | Hooks fire for both UI and programmatic — debounce / abort inside the hook |
|
|
157
181
|
| Sorting in the `filter` hook expecting it to override the built-in sort | The grid still runs the built-in sort after your filter hook unless you also supply a `sort` hook |
|
|
158
182
|
| Returning a Promise that rejects | Errors propagate; the grid keeps the previous `dataView`. Catch + log inside the hook to surface failures |
|
|
@@ -239,7 +239,9 @@ export class UsersGridComponent implements OnInit {
|
|
|
239
239
|
| ❌ | ✅ |
|
|
240
240
|
|---|---|
|
|
241
241
|
| Forgetting to register the element | Top-level `import 'apex-grid/define'` once in the app entry |
|
|
242
|
-
| Importing an Ignite UI theme + `configureTheme(name)` | Obsolete: the grid self-styles; retheme via `--ag-*` CSS variables. To follow an Ignite UI theme on the page, `import 'apex-grid/ignite-ui'` once in the app entry (apex-grid 4.0
|
|
242
|
+
| Importing an Ignite UI theme + `configureTheme(name)` | Obsolete: the grid self-styles; retheme via `--ag-*` CSS variables. To follow an Ignite UI theme on the page, `import 'apex-grid/ignite-ui'` once in the app entry (apex-grid 4.0; free). Without it the 4.0 grid ignores `--ig-*` |
|
|
243
|
+
| A stateful widget (a framework component mounted into a cell, a third-party picker) inside `cellTemplate` showing the wrong row's state after a scroll or sort | Body rows are recycled by default in 4.0: read the record from `ctx.data`, keep the state in your data, wrap the template in `keyed(ctx.data, ...)`, or set `grid.rowRecycling = false` (`row-recycling="false"`) |
|
|
244
|
+
| `show-header={false}` / `:show-header="false"` expecting a boolean | Boolean attributes such as `show-header` and `row-recycling` turn off only on the string `"false"`; bind the property instead (`.showHeader=${false}`, `:showHeader.prop="false"`, `[showHeader]="false"`, or `el.showHeader = false`) |
|
|
243
245
|
| Pinning `theme="light"` on a page with a dark Ignite UI theme | Leave the `theme` attribute off so the grid follows `--ig-theme-variant`; `design-system="off"` / `tokens="off"` opt one grid out |
|
|
244
246
|
| Forgetting `setup()` / no host-height rule for `apex-grid` | `setup()` registers + sizes the host. Or `import 'apex-grid/define'` + `apex-grid { height: ... }` (never set `display` — it overrides the component's `:host { display: grid }`) |
|
|
245
247
|
| Vue: `:data="data"` (attribute, becomes `"[object Object]"`) | `:data.prop="data"`, or set via `ref` in `onMounted` |
|
|
@@ -109,6 +109,14 @@ Unary operands (no `searchTerm` needed): `empty`, `notEmpty`, `all`, `true`, `fa
|
|
|
109
109
|
|
|
110
110
|
Since 3.6 the numeric presentation types `currency`, `rating` and `progress` take the number operands (on 3.5 they took the text ones). A condition name that does not belong to the column's set, such as `contains` on a `currency` column, is dropped with a console warning, from `filter()` and from a saved state alike. Text conditions read `null` / `undefined` as empty text and match other values (numbers, dates) on their text instead of throwing, and the range conditions (`greaterThan`, `lessThan`, ...) no longer match an empty cell as if it were 0.
|
|
111
111
|
|
|
112
|
+
**Typed numbers in the filter panel (Changed in 4.0.0).** On a numeric column (`number`, `currency`, `rating`, `progress`) the filter panel reads a number as a person writes it: full-width digits and signs (`123`, `-5`), the grouping and decimal separator of the column's `locale` (or the runtime locale), and a leading or trailing `¥`, `$`, `€`, `円` or the column currency's symbol. On 3.6 `123` did not filter and `1,000` filtered by 1. A plain ASCII decimal reads as before in every locale (`1.000` filters as 1 even under `'de-DE'`), the input keeps the text as typed, and text the parser does not read falls back to its leading number as in 3.x (`12.5%` filters by 12.5, `10²` by 10). The `min()` / `max()` validators have no such fallback: `12.5%` or `10²` there is not a number, so the value fails. Programmatic `filter()` / `filterExpressions` terms are not parsed: pass a number as `searchTerm`.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
{ key: 'price', type: 'currency', currency: 'EUR', locale: 'de-DE', filter: true } // typing 1.234,5 filters by 1234.5
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**IME input (4.0).** Japanese, Chinese or Korean typed through an input method works in the filter panel and the quick filter: the Enter, Escape and Tab that confirm or cancel a conversion stay with the IME (they do not close the panel or clear the quick filter), the text applies once the conversion ends rather than on every unconverted keystroke, and a quick filter search still waiting out its debounce is dropped when a conversion starts. Typing without an IME filters as before.
|
|
119
|
+
|
|
112
120
|
### Programmatic filter
|
|
113
121
|
|
|
114
122
|
```ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# State, Localization & Interaction Features (3.1+)
|
|
2
2
|
|
|
3
|
-
> Everything here landed in **apex-grid 3.1 and later** on top of the 3.0 API; version tags mark what is newer. The setup rules from `SKILL.md` (register + bounded host height, no theme import) still apply. Row pinning / reordering, undo-redo, and validators are all **opt-in** via a config object, off by default.
|
|
3
|
+
> Everything here landed in **apex-grid 3.1 and later** on top of the 3.0 API; version tags mark what is newer (up to 4.0). The setup rules from `SKILL.md` (register + bounded host height, no theme import) still apply. Row pinning / reordering, undo-redo, and validators are all **opt-in** via a config object, off by default.
|
|
4
4
|
|
|
5
5
|
## Row pinning — sticky top / bottom bands
|
|
6
6
|
|
|
@@ -23,6 +23,19 @@ grid.pinnedRows; // getter → { top: T[]; bottom
|
|
|
23
23
|
|
|
24
24
|
Events: `rowPinning` (cancellable) → `rowPinned`, both `CustomEvent<{ row: T; position: 'top' | 'bottom' | null }>` (`null` when unpinning). Pins survive sort / filter / pagination as long as row identity is preserved. Since 3.6, replacing `grid.data` carries the pins onto the new rows: matched by `rowId` when it is set, otherwise a pin whose object left the data is dropped (re-pin those). While `rowPinning.enabled` is `false`, `pinnedRows` reports none and the rows return to the body, but the pins are kept (`getState` still saves them) and come back when pinning is re-enabled.
|
|
25
25
|
|
|
26
|
+
**With pagination (Changed in 4.0.0):** pinned rows show on every page and no longer count toward the pages: `totalItems`, `pageCount`, the paginator summary and the `pageChanged` detail count only the rows the pager pages through, and pinning a row on the last page can move the grid to the new last page. Without pagination `totalItems` still counts every row.
|
|
27
|
+
|
|
28
|
+
**A pinned summary row that cannot be selected (4.0):** pinned rows are selectable like any other unless `selection.isRowSelectable` says otherwise.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const totalRow = { id: -1, name: 'Total', amount: sum };
|
|
32
|
+
grid.data = [...rows, totalRow];
|
|
33
|
+
grid.rowPinning = { enabled: true };
|
|
34
|
+
grid.pinRow(totalRow, 'bottom');
|
|
35
|
+
grid.selection = { enabled: true, mode: 'multiple', showCheckboxColumn: true,
|
|
36
|
+
isRowSelectable: (row) => row !== totalRow }; // no checkbox, skipped by Select all
|
|
37
|
+
```
|
|
38
|
+
|
|
26
39
|
## Row reordering — drag / keyboard / API
|
|
27
40
|
|
|
28
41
|
```ts
|
|
@@ -128,17 +141,32 @@ const columns: ColumnConfiguration<User>[] = [
|
|
|
128
141
|
| Factory | Signature | Fails when |
|
|
129
142
|
|---|---|---|
|
|
130
143
|
| `required(message?)` | `(msg?) => Validator` | value is `null` / `undefined` / empty-or-whitespace string |
|
|
131
|
-
| `min(limit, message?)` | `(number, msg?) => Validator` | numeric value `< limit
|
|
132
|
-
| `max(limit, message?)` | `(number, msg?) => Validator` | numeric value `> limit
|
|
144
|
+
| `min(limit, message?)` | `(number, msg?) => Validator` | numeric value `< limit`, or (4.0) a value that is not a number (empty passes; compose with `required`) |
|
|
145
|
+
| `max(limit, message?)` | `(number, msg?) => Validator` | numeric value `> limit`, or (4.0) a value that is not a number (empty passes) |
|
|
133
146
|
| `pattern(regex, message?)` | `(RegExp, msg?) => Validator` | `String(value)` doesn't match `regex` (empty passes) |
|
|
134
147
|
| `custom(fn)` | `(Validator) => Validator` | wraps a predicate; an inline arrow works identically |
|
|
135
148
|
|
|
149
|
+
> **Changed in 4.0.0:** `min()` and `max()` fail a value that is not a number; on 3.x `'abc'` (any text `Number()` could not read) passed both. Text is read as a person types it: full-width digits and signs (`123`, `-5`), the grouping and decimal separator of the column's `locale` (or the runtime locale), and a leading or trailing `¥`, `$`, `€`, `円` or the column currency's symbol.
|
|
150
|
+
|
|
136
151
|
```ts
|
|
137
152
|
// Validator signature
|
|
138
153
|
type Validator<T, K> = (value: unknown, context: ValidatorContext<T, K>) => string | null;
|
|
139
|
-
interface ValidatorContext<T, K> {
|
|
154
|
+
interface ValidatorContext<T, K> {
|
|
155
|
+
column: ColumnConfiguration<T, K>;
|
|
156
|
+
data: T;
|
|
157
|
+
rowIndex: number;
|
|
158
|
+
localize?: (key: GridLocaleKey, params?: LocaleParams) => string; // 4.0; absent outside a grid
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 4.0: a custom validator can reuse a translated message through the grid's localeText
|
|
162
|
+
const endRequiredWithStart = custom<Booking>((value, ctx) =>
|
|
163
|
+
ctx.data.start && value == null
|
|
164
|
+
? (ctx.localize?.('validation.required') ?? 'This field is required')
|
|
165
|
+
: null);
|
|
140
166
|
```
|
|
141
167
|
|
|
168
|
+
Called without a `message`, the built-ins report the `validation.required`, `validation.min`, `validation.max` and `validation.pattern` locale keys (4.0) through `ctx.localize`, so `localeText` (or `esLocale`) translates them. On 3.x those messages were English whatever `localeText` said.
|
|
169
|
+
|
|
142
170
|
`rowIndex` is view-relative, or -1 for a pinned row or a record on another page (3.6). In row edit mode `ctx.data` is the **staged row** (3.6): a copy of the record (same prototype) with the row's staged values applied, so a cross-field rule judges the row being saved rather than the stored record. A row commit also re-validates the row's other editable cells against it.
|
|
143
171
|
|
|
144
172
|
Event: `cellValidationFailed` — `CustomEvent<{ key; rowIndex; data: T; value: unknown; errors: readonly string[] }>`.
|
|
@@ -219,6 +247,19 @@ await grid.unpinColumn('actions'); // = pinColumn(key, null)
|
|
|
219
247
|
|
|
220
248
|
`gotoPage` / `setPageSize` / `nextPage` / `previousPage` / `firstPage` / `lastPage` emit `pageChanging` → `pageChanged`. `setQuickFilter` emits `quickFilterChanging` → `quickFilterChanged`, plus (3.6) `pageChanged` when it moves the grid back to the first page. Since 3.6 the `*-ed` events fire after the pipeline run they caused, so awaiting the returned promise, or reading `grid.pageItems` in the listener, sees the new rows even with an async pipeline hook. `pinColumn` / `unpinColumn` emit `columnPinning` → `columnPinned` and only change the visual render order (the `columns` array isn't reordered).
|
|
221
249
|
|
|
250
|
+
Since 4.0 `pageChanged` reports the change that was applied, so two quick `nextPage()` calls report page 2 and then page 3, and a local page change no longer reruns sort / filter (custom hooks are not called; remote mode still reruns). A page size or quick filter cancelled through `pageChanging` / `quickFilterChanging` no longer leaves the selector or search box showing the refused value.
|
|
251
|
+
|
|
252
|
+
**Paginator layout (4.0).** `pagination.paginator: 'full'` (default) shows the page size select, the "1-25 of 100" summary, first, previous, "page / count", next and last, and drops the select, then first / last, when the grid is too narrow, so previous and next stay reachable. `'compact'` always uses that narrow layout. Every paginator and toolbar part is styleable from outside (`paginator-button`, `paginator-prev`, `search-input`, ...; full list in SKILL.md §1.3). A `pageSize` missing from `pageSizeOptions` now appears in the selector.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
grid.pagination = { enabled: true, pageSize: 10, paginator: 'compact' };
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```css
|
|
259
|
+
apex-grid::part(paginator-info) { font-variant-numeric: tabular-nums; }
|
|
260
|
+
apex-grid::part(paginator-button) { border-radius: 999px; }
|
|
261
|
+
```
|
|
262
|
+
|
|
222
263
|
## Localization — `localeText`
|
|
223
264
|
|
|
224
265
|
Every built-in UI string (pagination, filter operators, toolbar, row/selection labels) is localizable through a `GridLocaleText` map keyed by dot-namespaced `GridLocaleKey`s. Partial maps are valid — any omitted key falls back to the English default.
|
|
@@ -234,10 +275,20 @@ grid.localize('pagination.summary', { start: 1, end: 25, total: 100 }); // res
|
|
|
234
275
|
|
|
235
276
|
Exports: `EN_LOCALE` (the default dictionary / source of truth for keys), `esLocale` (ready-made Spanish), `interpolate` (`{token}` substitution), `localize`, and the types `GridLocaleKey` / `GridLocaleText` / `LocaleParams`. `grid.localize(key, params?, fallback?)` applies any `localeText` override over the English default and interpolates `{placeholder}` tokens; `fallback` covers keys outside the built-in set (e.g. a custom operand label).
|
|
236
277
|
|
|
237
|
-
**3.4 key changes** (both English and Spanish): the accessibility work added live-region announcement keys (`announce.*`, e.g. `announce.sortedAscending`, `announce.page`, `announce.rowSelected`, `announce.undoOne`), header control / sort-state labels (`header.*`, e.g. `header.sortedAscending`, `header.filterColumn`, `header.columnMenu`), the
|
|
278
|
+
**3.4 key changes** (both English and Spanish): the accessibility work added live-region announcement keys (`announce.*`, e.g. `announce.sortedAscending`, `announce.page`, `announce.rowSelected`, `announce.undoOne`), header control / sort-state labels (`header.*`, e.g. `header.sortedAscending`, `header.filterColumn`, `header.columnMenu`), the grid label `grid.label` ("Data grid"; since 4.0 on the inner `[part~="table"]` element, not the host), and `editor.rating`. The `ai.noAdapter` key was **removed** (drop it from any `localeText` override you kept). `EN_LOCALE` remains the source of truth for the full, current key list.
|
|
238
279
|
|
|
239
280
|
**3.6 key additions** (English and Spanish) are used only by enterprise panels: `toolPanel.valueColumn`, `toolPanel.columnLabelColumn`, `chart.mapCategoryNone`, `setFilter.more`, `pivot.columnsTruncated`. A partial `localeText` map needs none of them; a full translation typed as `Record<GridLocaleKey, string>` must add them to compile.
|
|
240
281
|
|
|
282
|
+
**4.0 key additions** (English and Spanish; each English default is the text shown before, and `esLocale` translates them all). Text that stayed English whatever `localeText` said now has keys: `toolbar.exportCsv` (the CSV export menu item), `filter.and` / `filter.or` (the filter panel's join toggles), `cell.rating` (a rating's accessible name, `{value} of {max}`), `cell.true` / `cell.false` (a boolean cell's accessible name), and `validation.required`, `validation.min`, `validation.max` (`{limit}`), `validation.pattern` (the built-in validators' default messages). Keys read only by `apex-grid-enterprise`: `filterBuilder.true` / `filterBuilder.false`, `contextMenu.groupBy`, `contextMenu.ungroupAll`, `contextMenu.expandGroups`, `contextMenu.collapseGroups`, `ai.cancelled`, `chart.rangeResized`. As with 3.6, a full `Record<GridLocaleKey, string>` translation must add them to compile. A filter chip for a condition with no value now shows its `filter.operator.*` label ("Not empty") instead of the operator id.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
grid.localeText = {
|
|
286
|
+
'toolbar.exportCsv': 'CSV herunterladen',
|
|
287
|
+
'validation.min': 'Mindestens {limit}',
|
|
288
|
+
'cell.true': 'ja', 'cell.false': 'nein',
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
241
292
|
## Column menu & column separator
|
|
242
293
|
|
|
243
294
|
| Property | Type | Default | Notes |
|