apexgrid-skill 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +35 -9
- package/README.md +0 -1
- package/SKILL.md +86 -34
- package/package.json +1 -1
- package/references/columns-and-templates.md +20 -14
- package/references/data-pipeline.md +5 -1
- package/references/framework-integration.md +2 -2
- package/references/sort-and-filter.md +46 -15
- package/references/state-and-features.md +79 -9
- package/references/vanilla-js.md +5 -2
package/.cursorrules
CHANGED
|
@@ -4,7 +4,7 @@ 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@3.0
|
|
7
|
+
Confirmed versions: apex-grid@3.6.0, lit@^3.0.0
|
|
8
8
|
|
|
9
9
|
## Quickstart — register + size the host
|
|
10
10
|
|
|
@@ -16,7 +16,8 @@ height or it collapses and renders nothing. `setup()` handles both.
|
|
|
16
16
|
```bash
|
|
17
17
|
npm install apex-grid lit
|
|
18
18
|
```
|
|
19
|
-
|
|
19
|
+
That is the whole install: `apex-grid` brings `lit`, `@lit/context` and `@lit-labs/virtualizer`.
|
|
20
|
+
There is no theme CSS to import from anywhere (3.5.0 dropped the old Ignite UI dependencies).
|
|
20
21
|
|
|
21
22
|
### 2. Register + size — `setup()` does both
|
|
22
23
|
```ts
|
|
@@ -28,10 +29,16 @@ Manual alternative: `import 'apex-grid/define'` (or `ApexGrid.register()`) plus
|
|
|
28
29
|
|
|
29
30
|
### 3. Style via `--ag-*` CSS variables (no theme import)
|
|
30
31
|
The grid is styled out of the box; retheme by overriding `--ag-*` custom properties on
|
|
31
|
-
`apex-grid
|
|
32
|
+
`apex-grid` or on any ancestor, shadow hosts included (3.6). The deprecated `setup({ theme })`
|
|
33
|
+
option / `configureTheme()` does NOT affect appearance.
|
|
32
34
|
```css
|
|
33
|
-
apex-grid { height: 480px; /* + any --ag-* overrides */ }
|
|
35
|
+
apex-grid { height: 480px; --ag-brand: #7c3aed; /* + any --ag-* overrides */ }
|
|
34
36
|
```
|
|
37
|
+
Dark mode: the `theme="dark"` attribute (`tinted` adds brand chrome; `theme="dark tinted"`).
|
|
38
|
+
Since 3.6 a dark grid reads `--ag-dark-*` colour tokens (`--ag-dark-surface`, `--ag-dark-text`, ...)
|
|
39
|
+
and IGNORES the light ones (`--ag-surface`, ...), even when set on the dark grid itself.
|
|
40
|
+
Brand, sizing and typography tokens are shared. In your own CSS, `var(--ag-brand)` has no
|
|
41
|
+
root default: write `var(--ag-brand, #0d6fd3)`.
|
|
35
42
|
|
|
36
43
|
### Minimal example
|
|
37
44
|
|
|
@@ -88,7 +95,7 @@ render(
|
|
|
88
95
|
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.
|
|
89
96
|
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-*`).
|
|
90
97
|
3. **`columns` and `data` are properties, not attributes.** Use property binding.
|
|
91
|
-
4. **`column.key` must be a real `keyof T`.** No aliases. Compute derived values in `cellTemplate
|
|
98
|
+
4. **`column.key` must be a real `keyof T`.** No aliases. Compute derived values in `cellTemplate` (its `row` is the row element; the record is `row.data`).
|
|
92
99
|
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'`.
|
|
93
100
|
6. **`cellTemplate` and `headerTemplate` must return Lit `TemplateResult`s** (`html\`...\``). Strings render as escaped text.
|
|
94
101
|
7. **Sort/filter events fire only for UI-initiated operations.** Programmatic `grid.sort(...)` / `grid.filter(...)` are silent.
|
|
@@ -145,8 +152,8 @@ render(html`<apex-grid auto-generate .data=${data}></apex-grid>`, root);
|
|
|
145
152
|
|
|
146
153
|
| Type | Operands |
|
|
147
154
|
|---|---|
|
|
148
|
-
| `'string'` | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
|
|
149
|
-
| `'number'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
155
|
+
| `'string'` and other non-numeric types | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
|
|
156
|
+
| `'number'`, `'currency'`, `'rating'`, `'progress'` (3.6) | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
150
157
|
| `'boolean'` | `all`, `true`, `false`, `empty`, `notEmpty` (all unary — no `searchTerm`) |
|
|
151
158
|
|
|
152
159
|
```ts
|
|
@@ -172,7 +179,7 @@ grid.filter([
|
|
|
172
179
|
| `dataPipelineConfiguration` | `{ sort?, filter?, pagination?, quickFilter? }` | Server-side hooks |
|
|
173
180
|
| `selection` / `editing` / `pagination` / `tree` / `expansion` | config objects | Opt-in: `{ enabled, ... }` (see SKILL.md §5) |
|
|
174
181
|
| `quickFilter` / `columnReordering` | `string` / `boolean` | Global search / drag-reorder headers |
|
|
175
|
-
| `sortExpressions` / `filterExpressions` | array (get/set) |
|
|
182
|
+
| `sortExpressions` / `filterExpressions` | array (get/set) | Assigning REPLACES (3.6); `[]` clears. `sort()` / `filter()` add |
|
|
176
183
|
| `selectedRows` / `expandedRows` | `T[]` (get/set) | Snapshots; set to replace |
|
|
177
184
|
| `rows` (getter) | `ApexGridRow<T>[]` | Currently rendered only (virtualized) |
|
|
178
185
|
| `dataView` / `pageItems` (getters) | `readonly T[]` | Full post-filter/sort / current page |
|
|
@@ -235,7 +242,7 @@ Built-in pipeline is skipped for whichever operations have a hook. Omit one to k
|
|
|
235
242
|
|
|
236
243
|
### 5. `column.key` not a `keyof T`
|
|
237
244
|
❌ `{ key: 'fullName' }` when not in `T`
|
|
238
|
-
✅ `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\``
|
|
245
|
+
✅ `cellTemplate: ({ row }) => html\`${row.data.firstName} ${row.data.lastName}\`` (`row` is the element)
|
|
239
246
|
|
|
240
247
|
### 6. String from `cellTemplate`
|
|
241
248
|
❌ `cellTemplate: ({ value }) => \`<b>\${value}</b>\``
|
|
@@ -278,6 +285,25 @@ Built-in pipeline is skipped for whichever operations have a hook. Omit one to k
|
|
|
278
285
|
|
|
279
286
|
---
|
|
280
287
|
|
|
288
|
+
## Changed in 3.6
|
|
289
|
+
|
|
290
|
+
- `sortExpressions` / `filterExpressions` assignment REPLACES the sort / filter (`[]` or `null` clears).
|
|
291
|
+
Assigning one expression at a time keeps only the last: assign the full list, or `grid.sort(expr)` to add.
|
|
292
|
+
- `sorted`, `filtered`, `pageChanged`, `quickFilterChanged` fire after the pipeline run, so `dataView` is fresh
|
|
293
|
+
in the listener, async hooks included.
|
|
294
|
+
- `currency`, `rating`, `progress` columns filter with the number operands; a `contains` on them is dropped.
|
|
295
|
+
- A sort `comparer` receives raw values (original casing, `null` included); empty cells sort last otherwise.
|
|
296
|
+
- `applyEdits([{ row, column, value }])` names a row by record (any page, pinned); `rowIndex` stays page-relative.
|
|
297
|
+
- `grid.ownsEvent(e)`: events bubble, so filter out a nested master-detail grid's events with it.
|
|
298
|
+
- `expansion.exportParts` re-exports parts used in `detailTemplate`; every row part now works with `apex-grid::part()`.
|
|
299
|
+
- `rowMoving` / `rowMoved` add `position` and `toIndex`; a row move on a sorted grid clears the sort.
|
|
300
|
+
- Row edit mode: validators get the staged row as `ctx.data`; checkbox and `ctx.commit` join the row edit.
|
|
301
|
+
- Replacing `grid.data` with new records clears undo history; `grid.data = null` shows an empty grid.
|
|
302
|
+
- CSV export prefixes formula-like text with `'` (`exportToCSV({ escapeFormulas: false })` to opt out);
|
|
303
|
+
booleans export `true` only for `true`; currency / progress export as numbers.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
281
307
|
## Dev-server reminder
|
|
282
308
|
|
|
283
309
|
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
|
@@ -88,7 +88,6 @@ const cols = await readFile(referencePath('columns-and-templates.md'), 'utf8');
|
|
|
88
88
|
- [apex-grid GitHub](https://github.com/apexcharts/apexgrid)
|
|
89
89
|
- [npm: apex-grid](https://www.npmjs.com/package/apex-grid)
|
|
90
90
|
- [Lit framework](https://lit.dev)
|
|
91
|
-
- [igniteui-webcomponents](https://www.npmjs.com/package/igniteui-webcomponents) (transitive dep — installs automatically)
|
|
92
91
|
|
|
93
92
|
## License
|
|
94
93
|
|
package/SKILL.md
CHANGED
|
@@ -7,14 +7,15 @@ description: >
|
|
|
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 generic
|
|
9
9
|
`ColumnConfiguration<T>` shape with its 13 column types, Lit `cellTemplate`
|
|
10
|
-
requirements,
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
requirements, theming and dark mode (`--ag-*` / `--ag-dark-*` tokens,
|
|
11
|
+
`theme="dark"`), sorting / filtering / quick-filter, pagination, inline
|
|
12
|
+
editing, row selection, master-detail and tree rows, CSV export, batch writes
|
|
13
|
+
via `applyEdits`, right-to-left layout, the UI-only event model, and the
|
|
13
14
|
`dataPipelineConfiguration` hooks for server-side data.
|
|
14
15
|
metadata:
|
|
15
16
|
author: ApexCharts
|
|
16
|
-
version: "2.
|
|
17
|
-
library_version: "3.
|
|
17
|
+
version: "2.4.0"
|
|
18
|
+
library_version: "3.6.0"
|
|
18
19
|
category: data-visualization
|
|
19
20
|
tags: [grid, data-grid, table, web-component, lit, apex-grid]
|
|
20
21
|
docs: https://github.com/apexcharts/apexgrid
|
|
@@ -34,9 +35,11 @@ The grid styles itself out of the box (borders, row separators, sort/filter UI)
|
|
|
34
35
|
npm install apex-grid lit
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
`
|
|
38
|
+
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.
|
|
38
39
|
|
|
39
|
-
>
|
|
40
|
+
> **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.
|
|
41
|
+
|
|
42
|
+
> Confirmed working versions: `apex-grid@3.6.0`, `lit@^3.0.0`.
|
|
40
43
|
|
|
41
44
|
### 1.2 Register the element + size the host — `setup()` does both
|
|
42
45
|
|
|
@@ -60,20 +63,44 @@ apex-grid { height: 480px; } /* any explicit height; % works if the parent is
|
|
|
60
63
|
|
|
61
64
|
`setup({ hostStyles: false })` registers the element but skips the injected host CSS, leaving sizing entirely to you. Without a bounded height (from `setup()` or your own CSS) the grid collapses and shows no rows.
|
|
62
65
|
|
|
66
|
+
Since 3.6.0 the `setup()` sizing is adopted into whichever root each grid connects in, so a grid rendered inside another web component's shadow root gets it too. On 3.5 and earlier it reached the page only, and a grid inside a shadow root needed its own height rule there.
|
|
67
|
+
|
|
63
68
|
> **Do not set `display` on `apex-grid`** — the component declares `:host { display: grid }` for its internal track layout; an outer `display` rule overrides it and collapses the virtualizer.
|
|
64
69
|
|
|
65
70
|
### 1.3 Styling — `--ag-*` CSS custom properties (no theme import)
|
|
66
71
|
|
|
67
|
-
The grid is styled out of the box and customized entirely through `--ag-*` CSS custom properties
|
|
72
|
+
The grid is styled out of the box and customized entirely through `--ag-*` CSS custom properties. There is **no theme file to import and no `configureTheme()` call**. Set the variables on `apex-grid` or on any ancestor: `:root`, a dashboard panel, or the shadow host of a web component the grid renders in.
|
|
68
73
|
|
|
69
74
|
```css
|
|
70
75
|
apex-grid {
|
|
71
76
|
height: 480px;
|
|
72
|
-
|
|
77
|
+
--ag-brand: #7c3aed; /* selection, focus, accents; the brand washes follow it */
|
|
78
|
+
--ag-radius: 12px; /* outer card radius */
|
|
79
|
+
--ag-row-h: 40px; /* row height; see the package README for the full token list */
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
> **Changed in 3.6.0:** the token defaults moved off the grid host onto `:where(:root)`, adopted into the document (through `adoptedStyleSheets`, so no CSP nonce) when the first grid connects. On 3.5 and earlier a value set on an ancestor never reached the grid, because the host's own default beat it; only values set on the `apex-grid` element took effect. Now both do, through shadow roots however deep.
|
|
84
|
+
|
|
85
|
+
**Dark mode.** Add the `theme="dark"` attribute (an attribute, not a property). `theme="tinted"` mixes the brand into the chrome, and the two combine as `theme="dark tinted"`. A dark grid reads its own colour inputs, the `--ag-dark-*` tokens (`--ag-dark-surface`, `--ag-dark-surface-elevated`, `--ag-dark-grid-bg`, `--ag-dark-text`, `--ag-dark-text-muted`, `--ag-dark-hairline`, `--ag-dark-border`, `--ag-dark-row-hover`, `--ag-dark-shadow-card`, ...). Sizing, typography, motion, the brand and the base state colours are shared by both modes, so one place can theme light and dark grids:
|
|
86
|
+
|
|
87
|
+
```css
|
|
88
|
+
.dashboard {
|
|
89
|
+
--ag-brand: #7c3aed;
|
|
90
|
+
--ag-surface: #fdfcff; /* light grids */
|
|
91
|
+
--ag-dark-surface: #140f24; /* grids with theme="dark" */
|
|
73
92
|
}
|
|
93
|
+
apex-grid { --ag-grid-shadow: var(--ag-shadow-card); } /* elevated card edge */
|
|
94
|
+
apex-grid[theme~='dark'] { --ag-grid-shadow: var(--ag-dark-shadow-card); } /* the same, on dark grids */
|
|
74
95
|
```
|
|
75
96
|
|
|
76
|
-
>
|
|
97
|
+
> **Changed in 3.6.0:** a light colour token (`--ag-surface`, `--ag-text`, `--ag-border`, ...) no longer applies to a dark grid, whether it is set on an ancestor or **on the dark grid element itself**. A rule such as `apex-grid[theme='dark'] { --ag-surface: #111; }` is now ignored: write `--ag-dark-surface`. A `theme="tinted"` grid ignores `--ag-border` and `--ag-dark-border` (its row separators are the brand alone).
|
|
98
|
+
|
|
99
|
+
**Reading tokens in your own CSS** (page styles, markup in a `cellTemplate`): `var(--ag-surface)`, `var(--ag-text-muted)` and the other inputs resolve to their defaults anywhere on the page. `--ag-brand`, `--ag-brand-strong` and the derived washes (`--ag-brand-soft`, `--ag-brand-softer`, `--ag-brand-line`, `--ag-row-hover`, `--ag-surface-alt`) have no root default, so give them a fallback: `var(--ag-brand, #0d6fd3)`. To manage the defaults yourself, `setup({ tokenStyles: false })` skips the adopted sheet and `import 'apex-grid/tokens.css'` ships the same defaults as a file (also the way to get them into server-rendered HTML). The grid renders the same either way.
|
|
100
|
+
|
|
101
|
+
**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`).
|
|
102
|
+
|
|
103
|
+
> 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.
|
|
77
104
|
|
|
78
105
|
### 1.4 Minimal example — a styled, sortable, filterable table
|
|
79
106
|
|
|
@@ -141,7 +168,7 @@ AI models trained before the package was published may suggest `apexgrid` (no hy
|
|
|
141
168
|
|
|
142
169
|
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.
|
|
143
170
|
|
|
144
|
-
4. **`column.key` must be a real `keyof T`.** No `field` / `dataIndex` / `accessor` aliases. To display a derived value, compute it in `cellTemplate`
|
|
171
|
+
4. **`column.key` must be a real `keyof T`.** No `field` / `dataIndex` / `accessor` aliases. To display a derived value, compute it in `cellTemplate`, whose `row` is the `<apex-grid-row>` element: the record is `row.data`.
|
|
145
172
|
|
|
146
173
|
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).
|
|
147
174
|
|
|
@@ -208,27 +235,30 @@ Applied defaults (`DEFAULT_COLUMN_CONFIG`): `{ type: 'string', resizable: false,
|
|
|
208
235
|
| `'currency'` | `Intl.NumberFormat` (`currency`, `locale`). Numeric |
|
|
209
236
|
| `'avatar'` | first letter in a tinted circle |
|
|
210
237
|
| `'badge'` | colored pill (`badgeVariant`) |
|
|
211
|
-
| `'progress'` | health bar, 0..`max` (default 100) |
|
|
238
|
+
| `'progress'` | health bar, 0..`max` (default 100). Numeric |
|
|
212
239
|
| `'sparkline'` | inline trend chart of a `number[]` (`showDelta`) |
|
|
213
240
|
| `'status'` | dot + pill (`statusVariant`, or inferred) |
|
|
214
241
|
|
|
215
242
|
> A `'date'` column **does** exist in 3.x — use `type: 'date'` (it accepts `Date`, ISO strings, or millisecond timestamps), not a string column with a custom comparer.
|
|
216
243
|
|
|
217
|
-
### Column type → filter operands
|
|
244
|
+
### Column type → filter operands
|
|
218
245
|
|
|
219
246
|
| `type` | Operand keys in the filter UI |
|
|
220
247
|
|---|---|
|
|
221
|
-
| `'string'` | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
|
|
222
|
-
| `'number'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
223
|
-
| `'boolean'` | `all`, `true`, `false`, `empty`, `notEmpty` (all **unary
|
|
248
|
+
| `'string'`, and every type not listed below (`select`, `date`, `image`, `avatar`, `badge`, `sparkline`, `status`) | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
|
|
249
|
+
| `'number'`, and the numeric types `'currency'`, `'rating'`, `'progress'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
250
|
+
| `'boolean'` | `all`, `true`, `false`, `empty`, `notEmpty` (all **unary**: no `searchTerm`) |
|
|
224
251
|
|
|
225
252
|
```ts
|
|
226
|
-
grid.filter({ key: 'subscribed', condition: 'true' }); // unary
|
|
253
|
+
grid.filter({ key: 'subscribed', condition: 'true' }); // unary: no searchTerm
|
|
254
|
+
grid.filter({ key: 'price', condition: 'greaterThan', searchTerm: 100 }); // a 'currency' column
|
|
227
255
|
```
|
|
228
256
|
|
|
257
|
+
> **Changed in 3.6.0:** `currency`, `rating` and `progress` columns filter as numbers. On 3.5 they took the text operands. A text condition such as `contains` on one of them, from `filter()` or a saved state, is now dropped with a console warning.
|
|
258
|
+
|
|
229
259
|
### Cell template
|
|
230
260
|
|
|
231
|
-
Receives `ApexCellContext<T, K>` (`{ parent, row, column, value, commit? }`). Must return a Lit `TemplateResult`:
|
|
261
|
+
Receives `ApexCellContext<T, K>` (`{ parent, row, column, value, commit? }`, where `row` is the `<apex-grid-row>` element and `row.data` the record). Must return a Lit `TemplateResult`:
|
|
232
262
|
|
|
233
263
|
```ts
|
|
234
264
|
import { html } from 'lit';
|
|
@@ -236,6 +266,8 @@ import { html } from 'lit';
|
|
|
236
266
|
{ key: 'name', cellTemplate: ({ value, row }) => html`<strong>${value}</strong>` }
|
|
237
267
|
```
|
|
238
268
|
|
|
269
|
+
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.
|
|
270
|
+
|
|
239
271
|
### Auto-generated columns — the 3-line demo
|
|
240
272
|
|
|
241
273
|
```ts
|
|
@@ -273,7 +305,7 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
273
305
|
|
|
274
306
|
| Property | Type | Notes |
|
|
275
307
|
|---|---|---|
|
|
276
|
-
| `data` | `T[]` | Setting triggers a pipeline run. |
|
|
308
|
+
| `data` | `T[]` | Setting triggers a pipeline run. `null` / `undefined` (a framework prop still loading) shows an empty grid and reads back as `[]` (3.6). Replacing it with new records clears undo history and drops expansion of rows that left (`references/state-and-features.md`). |
|
|
277
309
|
| `columns` | `ColumnConfiguration<T>[]` | Each entry merged with column defaults. |
|
|
278
310
|
| `autoGenerate` (attr `auto-generate`) | `boolean` | Ignored if `columns` is non-empty. |
|
|
279
311
|
| `sortConfiguration` | `{ multiple, triState }` | Multi-column + tri-state sorting toggles. |
|
|
@@ -291,7 +323,7 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
291
323
|
| `pinnedRows` *(getter)* | `{ top: T[]; bottom: T[] }` | Currently pinned rows per band. |
|
|
292
324
|
| `canUndo` / `canRedo` *(getters)* | `boolean` | Undo/redo availability (needs `editing.history.enabled`). |
|
|
293
325
|
| `exportFormats` *(getter)* | `readonly { id, label }[]` | Export menu formats (CSV in the community grid). |
|
|
294
|
-
| `sortExpressions` / `filterExpressions` | `…[]` (get/set) | Current state.
|
|
326
|
+
| `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. |
|
|
295
327
|
| `selectedRows` / `expandedRows` | `T[]` (get/set) | Snapshots; set to replace (goes through cancellable events). |
|
|
296
328
|
| `rows` *(getter)* | `ApexGridRow<T>[]` | **Currently rendered only** (virtualized). |
|
|
297
329
|
| `dataView` *(getter)* | `readonly T[]` | Full post-filter/sort data. |
|
|
@@ -306,10 +338,10 @@ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silen
|
|
|
306
338
|
| `editing` | `{ enabled, mode?: 'cell' \| 'row', trigger?: 'click' \| 'doubleClick', history?: { enabled, stackSize? } }` — plus per-column `editable: true` |
|
|
307
339
|
| `selection` | `{ enabled, mode?: 'single' \| 'multiple', showCheckboxColumn? }` |
|
|
308
340
|
| `pagination` | `{ enabled, mode?: 'local' \| 'remote', page?, pageSize?, pageSizeOptions?, totalItems? }` |
|
|
309
|
-
| `tree` | `{ enabled, getDataPath: (row) => string[], groupColumnKey?, defaultExpanded?, childIndent? }
|
|
310
|
-
| `expansion` | `{ enabled, detailTemplate: (ctx) => TemplateResult, isExpandable?, showToggleColumn? }`
|
|
341
|
+
| `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) |
|
|
342
|
+
| `expansion` | `{ enabled, detailTemplate: (ctx) => TemplateResult, isExpandable?, showToggleColumn?, exportParts? }`: master-detail. `exportParts` (3.6) re-exports parts used in the detail content |
|
|
311
343
|
| `rowPinning` | `{ enabled }` — sticky top/bottom bands via `pinRow()` / `unpinRow()` |
|
|
312
|
-
| `rowReordering` | `{ enabled, applyToData?, handle? }
|
|
344
|
+
| `rowReordering` | `{ enabled, applyToData?, handle? }`: drag/keyboard reorder + `moveRow()`. A sort clears the manual order, and a move clears the sort (3.6) |
|
|
313
345
|
|
|
314
346
|
```ts
|
|
315
347
|
grid.editing = { enabled: true, mode: 'cell', trigger: 'doubleClick', history: { enabled: true } };
|
|
@@ -331,7 +363,7 @@ Declarative validators (`column.validators`), column groups (`columnGroups`), un
|
|
|
331
363
|
| `updateColumns(col \| col[])` | Merge new fields into existing columns by `key`. Triggers a pipeline run. |
|
|
332
364
|
| `moveColumn(fromKey, toKey, position?)` | Reorder a column (within its pinning group). |
|
|
333
365
|
| `pinColumn(key, 'start' \| 'end' \| null)` / `unpinColumn(key)` | Programmatic column pinning → `Promise<boolean>`; emits `columnPinning`/`columnPinned`. |
|
|
334
|
-
| `exportToCSV(options?)` / `exportAs(formatId, options?)` | Export the data. CSV is built in. `options.source`: `'view'`(def)/`'page'`/`'selected'`/`'all'`. |
|
|
366
|
+
| `exportToCSV(options?)` / `exportAs(formatId, options?)` | Export the data. CSV is built in. `options.source`: `'view'`(def)/`'page'`/`'selected'`/`'all'`. Formula-like CSV text (leading `=`, `+`, `-`, `@`, tab or CR; a plain signed number such as `-5` excepted) gets a leading apostrophe unless `escapeFormulas: false` (3.6). |
|
|
335
367
|
| pagination | `gotoPage(n)`, `setPageSize(n)`, `nextPage()`, `previousPage()`, `firstPage()`, `lastPage()` → `Promise<boolean>`; emit `pageChanging`/`pageChanged`. |
|
|
336
368
|
| `setQuickFilter(value)` | Apply global search (`''` clears) → `Promise<boolean>`; emits `quickFilterChanging`/`quickFilterChanged`. |
|
|
337
369
|
| selection | `selectRow`, `deselectRow`, `toggleRowSelection`, `selectAllRows`, `clearSelection`, `isRowSelected` |
|
|
@@ -340,7 +372,8 @@ Declarative validators (`column.validators`), column groups (`columnGroups`), un
|
|
|
340
372
|
| row pinning | `pinRow(row, 'top' \| 'bottom')`, `unpinRow(row)` (needs `rowPinning.enabled`) |
|
|
341
373
|
| row reorder | `moveRow(from, to, position?)` (needs `rowReordering.enabled`) |
|
|
342
374
|
| editing | `commitEdit()`, `cancelEdit()` (used with `mode: 'row'`) |
|
|
343
|
-
| `applyEdits(edits)` | Write many cells as **one** operation: one undo step, one pipeline run. Returns a tally, not a count. (3.5) |
|
|
375
|
+
| `applyEdits(edits)` | Write many cells as **one** operation: one undo step, one pipeline run. Returns a tally, not a count. (3.5) Each entry names its row by `rowIndex` (current page) or, since 3.6, by record: `{ row, column, value }` reaches any page, pinned rows and collapsed tree children. |
|
|
376
|
+
| `ownsEvent(event)` | `true` when an event came from this grid, `false` when from a grid nested in one of its master-detail panels. (3.6) |
|
|
344
377
|
| undo/redo | `undo()`, `redo()`, `clearHistory()` (needs `editing.history.enabled`) |
|
|
345
378
|
| state | `getState(options?)` → `GridState`, `setState(partial, options?)` → `SetStateResult`, `getSchema()` → `GridSchema` |
|
|
346
379
|
| `ApexGrid.register()` / `ApexGrid.tagName` | Static element registration / `'apex-grid'`. |
|
|
@@ -365,7 +398,18 @@ Every event fires only for operations driven through the grid's own UI; programm
|
|
|
365
398
|
| `rowPinning` / `rowPinned` | row pin / unpin (also fires for `pinRow()`/`unpinRow()`) |
|
|
366
399
|
| `rowMoving` / `rowMoved` | row reorder (also fires for `moveRow()`) |
|
|
367
400
|
|
|
368
|
-
**Exceptions to "UI-only".** The dedicated programmatic methods added for pagination (`gotoPage`, `nextPage`, …), quick-filter (`setQuickFilter`), column pinning (`pinColumn` / `unpinColumn`), row pinning (`pinRow` / `unpinRow`), and row reorder (`moveRow`) **do** emit their `*-ing` / `*-ed` pairs (unlike `sort()` / `filter()`, which stay silent).
|
|
401
|
+
**Exceptions to "UI-only".** The dedicated programmatic methods added for pagination (`gotoPage`, `nextPage`, …), quick-filter (`setQuickFilter`), column pinning (`pinColumn` / `unpinColumn`), row pinning (`pinRow` / `unpinRow`), and row reorder (`moveRow`) **do** emit their `*-ing` / `*-ed` pairs (unlike `sort()` / `filter()`, which stay silent). Since 3.6, `setQuickFilter` also fires `pageChanged` when it moves the grid back to the first page; assigning the `quickFilter` property stays silent.
|
|
402
|
+
|
|
403
|
+
**Timing (3.6).** `sorted`, `filtered`, `pageChanged` and `quickFilterChanged` fire after the pipeline run they caused, so a listener that reads `grid.dataView`, `grid.pageItems` or `grid.totalItems` sees the new rows, even with an async `dataPipelineConfiguration` hook. On 3.5 they could fire before an async hook resolved.
|
|
404
|
+
|
|
405
|
+
**Nested grids (3.6).** Grid events bubble and are composed, so a listener on a grid also hears the events of a grid nested in one of its master-detail panels. Filter them inside the listener:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
grid.addEventListener('cellValueChanged', (e) => {
|
|
409
|
+
if (!grid.ownsEvent(e)) return; // came from a grid inside a detail panel
|
|
410
|
+
save(e.detail.data);
|
|
411
|
+
});
|
|
412
|
+
```
|
|
369
413
|
|
|
370
414
|
Single-shot events (no `-ing` pair):
|
|
371
415
|
|
|
@@ -377,7 +421,7 @@ Single-shot events (no `-ing` pair):
|
|
|
377
421
|
|
|
378
422
|
Operand reference, multi-column sort, and server-side hooks: `references/sort-and-filter.md` and `references/data-pipeline.md`. Row pinning/reorder, undo/redo, validators, column groups, state/schema, and localization: `references/state-and-features.md`.
|
|
379
423
|
|
|
380
|
-
### Keyboard & accessibility (3.4
|
|
424
|
+
### Keyboard & accessibility (3.4 and later)
|
|
381
425
|
|
|
382
426
|
The grid ships full keyboard operation and screen-reader support out of the box; there's nothing to enable. The body uses a **roving-tabindex** focus model: the active cell is the body's single tab stop, so `Tab` moves into and out of the grid in one step, and real focus follows arrow-key navigation (screen readers track cell-to-cell movement).
|
|
383
427
|
|
|
@@ -386,12 +430,12 @@ The grid ships full keyboard operation and screen-reader support out of the box;
|
|
|
386
430
|
| Arrow keys | Move the active cell one cell up / down / left / right |
|
|
387
431
|
| Home / End | Move to the **first / last cell of the current row** |
|
|
388
432
|
| Ctrl/Cmd+Home / Ctrl/Cmd+End | Jump to the grid's first / last cell |
|
|
389
|
-
| PageUp / PageDown |
|
|
433
|
+
| PageUp / PageDown | Move the active cell by the rows that fit in the visible body (below the sticky header and pinned rows) |
|
|
390
434
|
| Enter or F2 | Open the active cell's editor (editing is no longer pointer-only) |
|
|
391
435
|
|
|
392
|
-
Keyboard-driven commit (Enter) or cancel (Escape) returns focus to the cell, so navigation continues from where you left off.
|
|
436
|
+
Keyboard-driven commit (Enter) or cancel (Escape) returns focus to the cell, so navigation continues from where you left off. Since 3.6.0, after tabbing into the body with no cell active yet, the first arrow, Home or PageUp key activates the first cell instead of moving past it (ArrowDown lands on row 0).
|
|
393
437
|
|
|
394
|
-
**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.
|
|
438
|
+
**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`).
|
|
395
439
|
|
|
396
440
|
**Announcements:** sorting, filtering, selection, paging, row pinning, row reorder, expansion, and undo / redo each announce through a localized live region, and the host applies a localized `aria-label` ("Data grid") plus `aria-multiselectable` when multi-row selection is on. The strings are locale keys (`announce.*`, `header.*`, `grid.label`, `editor.rating`); override them via `localeText` (see `references/state-and-features.md`).
|
|
397
441
|
|
|
@@ -422,7 +466,7 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
422
466
|
✅ `setup()` (or `import 'apex-grid/define'`) once at app startup.
|
|
423
467
|
|
|
424
468
|
### 3. Importing a theme CSS that no longer exists
|
|
425
|
-
❌ `import 'igniteui-webcomponents/themes/light/bootstrap.css'` + `
|
|
469
|
+
❌ `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.
|
|
426
470
|
✅ Nothing to import — the grid styles itself; retheme via `--ag-*` CSS variables. The only required CSS is a host **height**.
|
|
427
471
|
|
|
428
472
|
### 4. Stringified `columns` attribute
|
|
@@ -431,7 +475,7 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
431
475
|
|
|
432
476
|
### 5. `column.key` not a real key of `T`
|
|
433
477
|
❌ `{ key: 'fullName' }` when `User` has only `firstName` + `lastName`.
|
|
434
|
-
✅ Compute via template: `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\``.
|
|
478
|
+
✅ Compute via template: `cellTemplate: ({ row }) => html\`${row.data.firstName} ${row.data.lastName}\``. The context's `row` is the row element; the record is `row.data`.
|
|
435
479
|
|
|
436
480
|
### 6. String from `cellTemplate`
|
|
437
481
|
❌ `cellTemplate: ({ value }) => \`<strong>\${value}</strong>\`` — renders as escaped text.
|
|
@@ -447,12 +491,20 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
447
491
|
|
|
448
492
|
### 9. Clearing sort/filter
|
|
449
493
|
❌ Assuming you must rebuild state to clear it.
|
|
450
|
-
✅ `grid.clearSort()` / `grid.clearFilter()` (optionally per `key`).
|
|
494
|
+
✅ `grid.clearSort()` / `grid.clearFilter()` (optionally per `key`), or `grid.sortExpressions = []` / `grid.filterExpressions = []` (3.6).
|
|
451
495
|
|
|
452
496
|
### 10. Expecting events from programmatic operations
|
|
453
497
|
❌ Listening for `sorting` after calling `grid.sort(...)` — never fires.
|
|
454
498
|
✅ Wrap the programmatic call yourself, or watch `grid.dataView` for any state change.
|
|
455
499
|
|
|
500
|
+
### 11. Building a multi-column sort one assignment at a time
|
|
501
|
+
❌ `grid.sortExpressions = [byCity]; grid.sortExpressions = [byAge];` expecting both: since 3.6 each assignment **replaces** the sort (the same for `filterExpressions`), so only `byAge` applies.
|
|
502
|
+
✅ Assign the full list, `grid.sortExpressions = [byCity, byAge]`, or add with `grid.sort(byAge)` / `grid.filter(expr)`.
|
|
503
|
+
|
|
504
|
+
### 12. Theming a dark grid with the light tokens
|
|
505
|
+
❌ `apex-grid[theme='dark'] { --ag-surface: #111827; --ag-text: #f9fafb; }`: since 3.6 a dark grid ignores the light colour tokens, even on its own element.
|
|
506
|
+
✅ `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.
|
|
507
|
+
|
|
456
508
|
---
|
|
457
509
|
|
|
458
510
|
## 7. Reference Routing Table
|
|
@@ -462,6 +514,6 @@ Arrow-key navigation, column resize and column reorder all follow inline order r
|
|
|
462
514
|
| Column types, templates, auto-generation, per-column styling, header/editor templates | `references/columns-and-templates.md` |
|
|
463
515
|
| Sort & filter — operands, expressions, multi-column, quick-filter, events | `references/sort-and-filter.md` |
|
|
464
516
|
| Server-side data hooks (sort/filter/pagination/quickFilter), virtualization, `dataView` | `references/data-pipeline.md` |
|
|
465
|
-
| Row pinning / reordering, undo-redo, batch edits (`applyEdits`), validators, column groups, state & schema (`getState`/`setState`/`getSchema`), localization, export source | `references/state-and-features.md` |
|
|
517
|
+
| 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` |
|
|
466
518
|
| End-to-end vanilla JS (no Lit `render`) | `references/vanilla-js.md` |
|
|
467
519
|
| 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.4.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",
|
|
@@ -45,7 +45,7 @@ interface BaseColumnConfiguration<T extends object, K extends keyof T = keyof T>
|
|
|
45
45
|
|
|
46
46
|
### Column types (`DataType`)
|
|
47
47
|
|
|
48
|
-
`'string'` / `'number'` / `'boolean'` are the primitive types driving sort, filter, and the default editor. The rest are presentation renderers over a primitive value (and sort/filter as that value): `'select'` (label from `options`, `<select>` editor), `'rating'` (stars, 0..`max`), `'date'` (locale date via `format`; accepts `Date`/ISO/timestamp; `<input type="date">` editor), `'image'` (`<img>`, `shape`/`alt`), `'currency'` (`Intl.NumberFormat`, `currency`/`locale`), `'avatar'`, `'badge'` (`badgeVariant`), `'progress'` (0..`max`), `'sparkline'` (`number[]`, `showDelta`), `'status'` (`statusVariant`).
|
|
48
|
+
`'string'` / `'number'` / `'boolean'` are the primitive types driving sort, filter, and the default editor. The rest are presentation renderers over a primitive value (and sort/filter as that value): `'select'` (label from `options`, `<select>` editor), `'rating'` (stars, 0..`max`), `'date'` (locale date via `format`; accepts `Date`/ISO/timestamp; `<input type="date">` editor), `'image'` (`<img>`, `shape`/`alt`), `'currency'` (`Intl.NumberFormat`, `currency`/`locale`), `'avatar'`, `'badge'` (`badgeVariant`), `'progress'` (0..`max`), `'sparkline'` (`number[]`, `showDelta`), `'status'` (`statusVariant`). Of these, `'currency'`, `'rating'` and `'progress'` are numeric: since 3.6 they filter with the number operands and export as numbers, like `'number'`.
|
|
49
49
|
|
|
50
50
|
Defaults applied on assignment (`DEFAULT_COLUMN_CONFIG`):
|
|
51
51
|
|
|
@@ -61,7 +61,7 @@ Defaults applied on assignment (`DEFAULT_COLUMN_CONFIG`):
|
|
|
61
61
|
| `'number'` | numeric `<` / `>` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
62
62
|
| `'boolean'` | `false < true` | `all`, `true`, `false`, `empty`, `notEmpty` |
|
|
63
63
|
|
|
64
|
-
The table above lists the three **primitive** types. The presentation types (`'select'`, `'rating'`, `'date'`, `'image'`, `'currency'`, `'avatar'`, `'badge'`, `'progress'`, `'sparkline'`, `'status'`) sort
|
|
64
|
+
The table above lists the three **primitive** types. The presentation types (`'select'`, `'rating'`, `'date'`, `'image'`, `'currency'`, `'avatar'`, `'badge'`, `'progress'`, `'sparkline'`, `'status'`) sort as their underlying value. For filtering, `'currency'`, `'rating'` and `'progress'` take the `'number'` operands (since 3.6; on 3.5 they took the text ones), and the others take the `'string'` operands. With the built-in comparison, empty cells (`null`, `undefined`, `NaN`, invalid `Date`) sort last in both directions (3.6). For dates, use the real `'date'` type: it accepts `Date`, ISO strings, or millisecond timestamps, renders a locale date (`format`), and sorts chronologically. No timestamp/comparer workaround needed:
|
|
65
65
|
|
|
66
66
|
```ts
|
|
67
67
|
{ key: 'createdAt', type: 'date', format: 'medium', sort: true, filter: true }
|
|
@@ -83,7 +83,7 @@ The table above lists the three **primitive** types. The presentation types (`'s
|
|
|
83
83
|
}
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
The `comparer` signature is fully typed against `T[K]`.
|
|
86
|
+
The `comparer` signature is fully typed against `T[K]`. Since 3.6 it receives the **raw** cell values, original casing and `null` / `undefined` included, and owns their order (a descending sort reverses its result). On 3.5 it received strings already lower-cased (unless `caseSensitive`), so a lookup written in the data's casing (`'High'`, `'Low'`) never matched and the sort did nothing. Now the lookup above matches only values stored in lower case: write it in the data's own casing, and decide where empty values go.
|
|
87
87
|
|
|
88
88
|
## Filter configuration per column
|
|
89
89
|
|
|
@@ -101,12 +101,15 @@ Receives `ApexCellContext<T, K>`:
|
|
|
101
101
|
```ts
|
|
102
102
|
interface BaseApexCellContext<T, K> {
|
|
103
103
|
parent: ApexGridCell<T>; // <apex-grid-cell> element
|
|
104
|
-
row: ApexGridRow<T>; // <apex-grid-row> element
|
|
104
|
+
row: ApexGridRow<T>; // <apex-grid-row> element; the record is row.data
|
|
105
105
|
column: ColumnConfiguration<T, K>;
|
|
106
106
|
value: T[K]; // T[K] — fully typed
|
|
107
|
+
commit?: (value: T[K]) => Promise<boolean>; // editable columns only: write without opening the editor
|
|
107
108
|
}
|
|
108
109
|
```
|
|
109
110
|
|
|
111
|
+
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 `row.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
|
+
|
|
110
113
|
```ts
|
|
111
114
|
import { html } from 'lit';
|
|
112
115
|
|
|
@@ -118,19 +121,21 @@ const columns: ColumnConfiguration<User>[] = [
|
|
|
118
121
|
{ key: 'name',
|
|
119
122
|
cellTemplate: ({ value }) => html`<strong>${value}</strong>` },
|
|
120
123
|
|
|
121
|
-
// Conditional render with row context
|
|
124
|
+
// Conditional render with row context (row is the element, row.data the record)
|
|
122
125
|
{ key: 'subscribed', type: 'boolean',
|
|
123
126
|
cellTemplate: ({ value, row }) => html`
|
|
124
127
|
<input type="checkbox" .checked=${value} disabled />
|
|
125
|
-
<small>${row.name}</small>` },
|
|
128
|
+
<small>${row.data.name}</small>` },
|
|
126
129
|
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
// Prefer the built-in type over a template where one exists:
|
|
131
|
+
// `avatar` and `rating` are column types, no template needed.
|
|
132
|
+
{ key: 'avatar', type: 'avatar' },
|
|
133
|
+
{ key: 'satisfaction', type: 'rating' },
|
|
130
134
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
135
|
+
// Any custom element works, but YOU must install and register it:
|
|
136
|
+
// the grid ships no third-party components (see SKILL.md §1.1).
|
|
137
|
+
{ key: 'status',
|
|
138
|
+
cellTemplate: ({ value }) => html`<my-status-chip .value=${value}></my-status-chip>` },
|
|
134
139
|
];
|
|
135
140
|
```
|
|
136
141
|
|
|
@@ -171,7 +176,7 @@ import { required, min, max, pattern, custom } from 'apex-grid';
|
|
|
171
176
|
validators: [custom((value, ctx) => (value === ctx.data.id ? 'Cannot equal id' : null))] }
|
|
172
177
|
```
|
|
173
178
|
|
|
174
|
-
Built-in factories: `required(message?)`, `min(limit, message?)`, `max(limit, message?)`, `pattern(regex, message?)`, `custom(fn)`. `min`/`max`/`pattern` pass on empty / non-numeric values
|
|
179
|
+
Built-in factories: `required(message?)`, `min(limit, message?)`, `max(limit, message?)`, `pattern(regex, message?)`, `custom(fn)`. `min`/`max`/`pattern` pass on empty / non-numeric values, so compose with `required`. 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.
|
|
175
180
|
|
|
176
181
|
**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.
|
|
177
182
|
|
|
@@ -244,7 +249,8 @@ The grid renders a drag handle on the right edge of the column header. Minimum r
|
|
|
244
249
|
|
|
245
250
|
| ❌ | ✅ |
|
|
246
251
|
|---|---|
|
|
247
|
-
| `{ key: 'fullName' }` when `fullName` isn't in `T` | Add to `T`, or compute via `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\`` |
|
|
252
|
+
| `{ key: 'fullName' }` when `fullName` isn't in `T` | Add to `T`, or compute via `cellTemplate: ({ row }) => html\`${row.data.firstName} ${row.data.lastName}\`` |
|
|
253
|
+
| `cellTemplate: ({ row }) => row.name` | `row` is the `<apex-grid-row>` element: read the record as `row.data.name` |
|
|
248
254
|
| `cellTemplate: ({ value }) => \`<b>\${value}</b>\`` (string) | `cellTemplate: ({ value }) => html\`<b>\${value}</b>\`` |
|
|
249
255
|
| `{ key: 'age', sort: true }` (default `type: 'string'`) | Set `type: 'number'` |
|
|
250
256
|
| `{ key: 'created', type: 'string', sort: { comparer } }` to fake dates | `{ key: 'created', type: 'date', format: 'medium' }` — a real type (accepts `Date`/ISO/timestamp) |
|
|
@@ -66,7 +66,7 @@ The grid runs the stages in a fixed order: **quick-filter → column filter →
|
|
|
66
66
|
|
|
67
67
|
### Hooks fire on every state change
|
|
68
68
|
|
|
69
|
-
The pipeline runs whenever `data`, `columns`, sort state, or filter state changes
|
|
69
|
+
The pipeline runs whenever `data`, `columns`, sort state, or filter state changes, including programmatic `sort()` / `filter()` calls. Since 3.6 only the latest run's result is applied: a slower earlier run that resolves after a newer one is discarded, and no longer holds back the newer run's events. If your server endpoint is expensive, still abort or debounce inside the hook, so the superseded requests are not sent at all:
|
|
70
70
|
|
|
71
71
|
```ts
|
|
72
72
|
let pending: AbortController | null = null;
|
|
@@ -143,6 +143,10 @@ grid.addEventListener('sorted', updateSummary);
|
|
|
143
143
|
grid.addEventListener('filtered', updateSummary);
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
Since 3.6 this is reliable with async hooks too: `sorted`, `filtered`, `pageChanged` and `quickFilterChanged` fire after the pipeline run they caused, so `dataView` already holds the new rows. On 3.5 they could fire before an async hook resolved and read the previous view. (They remain UI-only, except the page and quick-filter methods listed in SKILL.md §5.)
|
|
147
|
+
|
|
148
|
+
Assigning `grid.data = null` or `undefined` (a framework prop still loading) shows an empty grid and reads back as `[]` since 3.6; on 3.5 it threw and kept the previous rows.
|
|
149
|
+
|
|
146
150
|
## Common pitfalls
|
|
147
151
|
|
|
148
152
|
| ❌ | ✅ |
|
|
@@ -19,7 +19,7 @@ import { setup } from 'apex-grid';
|
|
|
19
19
|
setup(); // registers <apex-grid> + adopts a bounded host height
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
No theme import needed
|
|
22
|
+
No theme import needed: the grid self-styles. Retheme by overriding `--ag-*` CSS variables on `apex-grid` or on any ancestor (an app shell, a component's shadow host), and use the `--ag-dark-*` variables for grids with `theme="dark"` (SKILL.md §1.3). If you prefer manual control over sizing, call `setup({ hostStyles: false })` (or use `import 'apex-grid/define'`) and add your own rule:
|
|
23
23
|
|
|
24
24
|
```css
|
|
25
25
|
apex-grid {
|
|
@@ -109,7 +109,7 @@ export function UsersGrid({ data }: { data: User[] }) {
|
|
|
109
109
|
}
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
If you'd rather size per instance instead of globally, replace the global CSS rule with a `style` prop: `style={{ height: '500px'
|
|
112
|
+
If you'd rather size per instance instead of globally, replace the global CSS rule with a `style` prop: `style={{ height: '500px' }}`. Never add `display` there: the grid needs its own `display: grid`, collapses without it, and logs a console warning on first render when something overrides it.
|
|
113
113
|
|
|
114
114
|
For full TypeScript / JSX intrinsic-element support:
|
|
115
115
|
|
|
@@ -35,25 +35,39 @@ grid.sort({
|
|
|
35
35
|
});
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
`grid.sort(...)` does **not** fire `sorting` / `sorted` events.
|
|
38
|
+
`grid.sort(...)` does **not** fire `sorting` / `sorted` events. It **adds** to the current sort (a key already sorted is updated in place); to replace the sort, assign `sortExpressions`.
|
|
39
39
|
|
|
40
|
-
###
|
|
40
|
+
### How rows compare
|
|
41
|
+
|
|
42
|
+
With the built-in comparison, strings compare with `localeCompare` (case folded unless `caseSensitive: true`) and other values with `<` / `>`. Since 3.6:
|
|
43
|
+
|
|
44
|
+
- **Empty cells** (`null`, `undefined`, `NaN`, an invalid `Date`) sort after every real value in **both** directions. Rows that tie keep their incoming order (a stable sort).
|
|
45
|
+
- **A custom `comparer` receives the raw cell values**, original casing and empty values included, and owns their order; a descending sort reverses its result. On 3.5 it received strings already lower-cased, so a lookup comparer over mixed-case values (`'High'`, `'Low'`) ranked every row the same. Write the lookup in the data's own casing, and decide where `null` goes.
|
|
46
|
+
|
|
47
|
+
### Reading and assigning the sort state
|
|
41
48
|
|
|
42
49
|
```ts
|
|
43
50
|
grid.sortExpressions; // SortExpression<T>[]
|
|
51
|
+
|
|
52
|
+
grid.sortExpressions = [ // 3.6: REPLACES the current sort
|
|
53
|
+
{ key: 'subscribed', direction: 'descending' },
|
|
54
|
+
{ key: 'age', direction: 'ascending' },
|
|
55
|
+
];
|
|
56
|
+
grid.sortExpressions = []; // clears it (so does null)
|
|
44
57
|
```
|
|
45
58
|
|
|
46
59
|
The array is in the order operations were applied (insertion order on the underlying `Map`).
|
|
47
60
|
|
|
61
|
+
> **Changed in 3.6.0:** assigning `sortExpressions` replaces the sort, as `setState` does; on 3.5 it merged into it and `[]` was a no-op. Code that assigned one expression at a time to build a multi-column sort now keeps only the last one: assign the full list, or call `grid.sort()`, which still adds. The list applies at the next update against the columns as they are then, so a framework template may bind `sortExpressions` before `columns`. Entries naming an unknown column, or with an unknown direction, are dropped with a console warning (an omitted direction sorts ascending).
|
|
62
|
+
|
|
48
63
|
### Clearing sort
|
|
49
64
|
|
|
50
65
|
```ts
|
|
51
66
|
grid.clearSort(); // clear everything
|
|
52
67
|
grid.clearSort('name'); // clear only the `name` column
|
|
68
|
+
grid.sortExpressions = []; // 3.6: same as clearSort()
|
|
53
69
|
```
|
|
54
70
|
|
|
55
|
-
> **Heads-up:** assigning an empty array (`grid.sortExpressions = []`) is a no-op. Always call `clearSort()` to reset.
|
|
56
|
-
|
|
57
71
|
### `sortConfiguration`
|
|
58
72
|
|
|
59
73
|
```ts
|
|
@@ -72,25 +86,29 @@ interface BaseFilterExpression<T, K extends keyof T> {
|
|
|
72
86
|
key: K;
|
|
73
87
|
condition: FilterOperation<T[K]> | OperandKeys<T[K]>; // operand object or string key
|
|
74
88
|
searchTerm?: T[K]; // optional for unary operands (empty / notEmpty / true / false / all)
|
|
75
|
-
criteria?: 'and' | 'or'; //
|
|
89
|
+
criteria?: 'and' | 'or'; // default 'and'; combines expressions of the SAME column
|
|
76
90
|
caseSensitive?: boolean; // overrides column config
|
|
77
91
|
}
|
|
78
92
|
```
|
|
79
93
|
|
|
94
|
+
Within one column, a row passes when **all** of its `and` expressions pass **or any one** of its `or` expressions passes. Columns always combine with AND: criteria never reach across columns (3.6; on 3.5 an `or` on one column could cancel the `or` conditions of another).
|
|
95
|
+
|
|
80
96
|
### Operand sets
|
|
81
97
|
|
|
82
98
|
```ts
|
|
83
99
|
import { StringOperands, NumberOperands, BooleanOperands } from 'apex-grid';
|
|
84
100
|
```
|
|
85
101
|
|
|
86
|
-
|
|
|
102
|
+
| Column `type` | Operand keys |
|
|
87
103
|
|---|---|
|
|
88
|
-
| `'string'` | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
|
|
89
|
-
| `'number'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
104
|
+
| `'string'` and every other type not listed below | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
|
|
105
|
+
| `'number'`, `'currency'`, `'rating'`, `'progress'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
90
106
|
| `'boolean'` | `all`, `true`, `false`, `empty`, `notEmpty` |
|
|
91
107
|
|
|
92
108
|
Unary operands (no `searchTerm` needed): `empty`, `notEmpty`, `all`, `true`, `false`.
|
|
93
109
|
|
|
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
|
+
|
|
94
112
|
### Programmatic filter
|
|
95
113
|
|
|
96
114
|
```ts
|
|
@@ -120,14 +138,21 @@ grid.filter([
|
|
|
120
138
|
]);
|
|
121
139
|
```
|
|
122
140
|
|
|
123
|
-
`grid.filter(...)` does **not** fire `filtering` / `filtered` events.
|
|
141
|
+
`grid.filter(...)` does **not** fire `filtering` / `filtered` events. Like `grid.sort()`, it **adds** to the current filter; assign `filterExpressions` to replace it.
|
|
124
142
|
|
|
125
|
-
### Reading the
|
|
143
|
+
### Reading and assigning the filter state
|
|
126
144
|
|
|
127
145
|
```ts
|
|
128
146
|
grid.filterExpressions; // FilterExpression<T>[]
|
|
147
|
+
|
|
148
|
+
grid.filterExpressions = [ // 3.6: REPLACES the current filter
|
|
149
|
+
{ key: 'age', condition: 'greaterThan', searchTerm: 30 },
|
|
150
|
+
];
|
|
151
|
+
grid.filterExpressions = []; // clears it (so does null)
|
|
129
152
|
```
|
|
130
153
|
|
|
154
|
+
Assignment follows the same 3.6 rules as `sortExpressions`: it replaces (3.5 merged, and `[]` was a no-op), applies at the next update, and assigning a list equal to the current one (a wrapper that re-binds it on every render) keeps the scroll position. Any other filter change scrolls the grid back to its first row.
|
|
155
|
+
|
|
131
156
|
Combined across columns. To get state for a single column, find by `key`:
|
|
132
157
|
|
|
133
158
|
```ts
|
|
@@ -175,6 +200,8 @@ grid.addEventListener('filtered', (e) => {
|
|
|
175
200
|
| `filtering` | yes | `{ key, expressions, type }` | no |
|
|
176
201
|
| `filtered` | no | `{ key, state }` | no |
|
|
177
202
|
|
|
203
|
+
Since 3.6, `sorted` and `filtered` fire after the pipeline run they caused, so a listener reading `grid.dataView` or `grid.totalItems` sees the new rows, even when a `dataPipelineConfiguration` hook is async. On 3.5 they fired after the next render, which could come before an async hook resolved.
|
|
204
|
+
|
|
178
205
|
If you need a side-effect on every change regardless of source, watch `grid.dataView` (after `requestUpdate` / via `updated()` in a Lit parent) or wrap your programmatic calls.
|
|
179
206
|
|
|
180
207
|
## Combining UI + programmatic operations
|
|
@@ -186,18 +213,22 @@ UI operations and programmatic operations write to the **same underlying state**
|
|
|
186
213
|
const savedSort = grid.sortExpressions;
|
|
187
214
|
const savedFilter = grid.filterExpressions;
|
|
188
215
|
|
|
189
|
-
// ... later, after re-mounting
|
|
190
|
-
grid.
|
|
191
|
-
grid.
|
|
216
|
+
// ... later, after re-mounting: assignment replaces whatever is there (3.6)
|
|
217
|
+
grid.sortExpressions = savedSort;
|
|
218
|
+
grid.filterExpressions = savedFilter;
|
|
192
219
|
```
|
|
193
220
|
|
|
221
|
+
`grid.sort(savedSort)` / `grid.filter(savedFilter)` would add onto any sort or filter already applied instead. For a full snapshot (columns, paging, selection too), use `getState()` / `setState()` (`references/state-and-features.md`).
|
|
222
|
+
|
|
194
223
|
## Common pitfalls
|
|
195
224
|
|
|
196
225
|
| ❌ | ✅ |
|
|
197
226
|
|---|---|
|
|
198
227
|
| Listening for events on programmatic calls | Wrap the call yourself, or watch `grid.dataView` |
|
|
199
|
-
| `grid.filter({ key: 'age', condition: 'contains' })` (string operand on number column) | Match operand to type
|
|
200
|
-
| `grid.sortExpressions = []
|
|
228
|
+
| `grid.filter({ key: 'age', condition: 'contains' })` (string operand on number column) | Match operand to type: `condition: 'greaterThan'` for `number`, `currency`, `rating` and `progress` columns (a mismatched name is dropped with a console warning) |
|
|
229
|
+
| `grid.sortExpressions = [a]; grid.sortExpressions = [b];` to build a two-column sort | Each assignment replaces (3.6): assign `[a, b]`, or `grid.sort(b)` to add |
|
|
230
|
+
| Restoring with `grid.sort(saved)` onto a grid that already has a sort | `grid.sortExpressions = saved` replaces it (3.6) |
|
|
231
|
+
| A `comparer` that expects lower-cased strings | Since 3.6 it receives raw values (original casing, `null` included) |
|
|
201
232
|
| Multi-column sort with `sortConfiguration.multiple = false` | Either set `multiple: true` or call `grid.sort` with a single expression |
|
|
202
233
|
| Forgetting `searchTerm` on a non-unary operand | Add `searchTerm`, or use a unary operand (`empty` / `notEmpty` / `true` / `false` / `all`) |
|
|
203
234
|
| Mutating `e.detail` in a `sorted` / `filtered` listener | Only `sorting` / `filtering` are cancellable / mutable; the post-events are read-only by design |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# State, Localization & Interaction Features (3.1+)
|
|
2
2
|
|
|
3
|
-
> Everything here landed in **apex-grid 3.1
|
|
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.
|
|
4
4
|
|
|
5
5
|
## Row pinning — sticky top / bottom bands
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ grid.pinnedRows; // getter → { top: T[]; bottom
|
|
|
21
21
|
| `unpinRow(row)` | `(T) => boolean` | No-op (returns `true`) when not pinned. |
|
|
22
22
|
| `pinnedRows` *(getter)* | `{ top: T[]; bottom: T[] }` | In pin order. |
|
|
23
23
|
|
|
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
|
|
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
26
|
## Row reordering — drag / keyboard / API
|
|
27
27
|
|
|
@@ -36,7 +36,26 @@ grid.moveRow(2, 0, 'before'); // (from, to, position?: 'before
|
|
|
36
36
|
| `applyToData` | `boolean` | `false` | `true` also splices `grid.data` in place; otherwise the order lives in the grid and you persist via `rowMoved`. |
|
|
37
37
|
| `handle` | `boolean` | `true` | `true` renders a leading grip cell (drag only from it); `false` drags from anywhere on the row. |
|
|
38
38
|
|
|
39
|
-
`moveRow(from, to, position?)` indices are into `grid.pageItems`; returns `boolean
|
|
39
|
+
`moveRow(from, to, position?)` indices are into `grid.pageItems`; returns `boolean`, `false` when disabled, out of range, cancelled, or (3.6) a no-op move that would leave the row where it is, which fires no events. A manual order is **mutually exclusive with column sorting**: applying a sort clears the manual order, and since 3.6 a move on a sorted grid clears the sort and keeps the moved order (on 3.5 an `applyToData` move was undone by the re-sort). Rows hidden by a filter keep their place when a visible row moves, and replacing `grid.data` carries the order onto the new rows (by `rowId` when set).
|
|
40
|
+
|
|
41
|
+
Events: `rowMoving` (cancellable) → `rowMoved`, both `CustomEvent<ApexRowMovingEvent<T>>`:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
interface ApexRowMovingEvent<T> {
|
|
45
|
+
from: number; // view index before the move
|
|
46
|
+
to: number; // the target row's view index
|
|
47
|
+
position: 'before' | 'after'; // 3.6: which side of the target it lands
|
|
48
|
+
toIndex: number; // 3.6: the view index it lands at (moveRow(0, 3) lands at 2)
|
|
49
|
+
data: T; // the moved record
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Mirror a move in your own array ordered like the view (the indices are view indices):
|
|
53
|
+
grid.addEventListener('rowMoved', ({ detail: { from, toIndex } }) => {
|
|
54
|
+
order.splice(toIndex, 0, ...order.splice(from, 1));
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Pressing Escape after keyboard grab-and-move steps restores the original order (and `grid.data` with `applyToData`, and any sort) and fires `rowMoved` for the way back, so a persisted order stays in sync.
|
|
40
59
|
|
|
41
60
|
## Inline-edit undo / redo
|
|
42
61
|
|
|
@@ -52,6 +71,8 @@ grid.canUndo; grid.canRedo; // getters (false unless history
|
|
|
52
71
|
|
|
53
72
|
`stackSize` defaults to `100` (older commands evicted past the cap). Event: `historyChanged` — `CustomEvent<{ canUndo: boolean; canRedo: boolean }>`, fires after any record / undo / redo / clear (drive toolbar button enablement from it).
|
|
54
73
|
|
|
74
|
+
**Data reloads (3.6).** Replacing `grid.data` with new records clears the history and fires `historyChanged`; before, undo replayed onto the old, detached objects and nothing changed on screen. Data that keeps the edited records, such as `grid.data = [...grid.data, newRow]`, keeps its history, and so do in-place edits. A `cellValueChanged` fired by undo / redo reports `rowIndex: -1` when the record is pinned or on another page, so identify the row by `event.detail.data`, not by index.
|
|
75
|
+
|
|
55
76
|
## Batch edits: `applyEdits()` (3.5)
|
|
56
77
|
|
|
57
78
|
Writes many cells as a single operation. Each write still goes through the same choke point interactive editing uses, so it emits the cancellable `cellValueChanging` and then `cellValueChanged`, and runs the column's validators. What differs from looping `editCell` + `commitEdit` is that the whole set lands as **one undo step and one pipeline run**: a thousand round-trips would otherwise mean a thousand undo entries and a thousand re-renders.
|
|
@@ -62,9 +83,17 @@ const tally = grid.applyEdits([
|
|
|
62
83
|
{ rowIndex: 3, column: 'discount', value: 0 },
|
|
63
84
|
]);
|
|
64
85
|
// { applied: 2, unchanged: 0, invalid: 0, cancelled: 0, skipped: 0 }
|
|
86
|
+
|
|
87
|
+
// 3.6: name the row by record to reach every selected row, on any page
|
|
88
|
+
grid.applyEdits(grid.selectedRows.map((row) => ({ row, column: 'discount', value: 0 })));
|
|
65
89
|
```
|
|
66
90
|
|
|
67
|
-
|
|
91
|
+
Each entry names its row one of two ways:
|
|
92
|
+
|
|
93
|
+
- `rowIndex` is **view-relative** and matches `grid.pageItems`, the same as `editCell`, so it reaches the current page only. It must be a whole number from `0` to `pageItems.length - 1`.
|
|
94
|
+
- `row` (3.6) is the record itself, the same object held in `grid.data`. It also reaches rows on other pages, pinned rows and rows under a collapsed tree parent; the events and validators for those see `rowIndex` -1.
|
|
95
|
+
|
|
96
|
+
Order is preserved, so a later edit to the same cell wins.
|
|
68
97
|
|
|
69
98
|
It returns a per-outcome tally rather than a count, because "nothing changed" and "everything was rejected" are different answers:
|
|
70
99
|
|
|
@@ -74,7 +103,7 @@ It returns a per-outcome tally rather than a count, because "nothing changed" an
|
|
|
74
103
|
| `unchanged` | The cell already held that value. |
|
|
75
104
|
| `invalid` | A `column.validators` rule rejected it. |
|
|
76
105
|
| `cancelled` | A `cellValueChanging` listener called `preventDefault()`. |
|
|
77
|
-
| `skipped` | Unknown column, `rowIndex`
|
|
106
|
+
| `skipped` | Unknown column, a `rowIndex` that is negative, fractional or past the end of the page (3.5 counted a negative index from the end and rounded a fraction down), or a column the user could not edit either (editing off, not `editable`, or hidden). |
|
|
78
107
|
|
|
79
108
|
Skipping rather than throwing means a partially stale batch still applies what it can. Needs `editing.enabled` and per-column `editable`, the same gate as interactive editing; without `editing.history.enabled` the batch simply is not recorded.
|
|
80
109
|
|
|
@@ -110,8 +139,15 @@ type Validator<T, K> = (value: unknown, context: ValidatorContext<T, K>) => stri
|
|
|
110
139
|
interface ValidatorContext<T, K> { column: ColumnConfiguration<T, K>; data: T; rowIndex: number; }
|
|
111
140
|
```
|
|
112
141
|
|
|
142
|
+
`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
|
+
|
|
113
144
|
Event: `cellValidationFailed` — `CustomEvent<{ key; rowIndex; data: T; value: unknown; errors: readonly string[] }>`.
|
|
114
145
|
|
|
146
|
+
## Editing behaviour (3.6)
|
|
147
|
+
|
|
148
|
+
- **Row mode edits a staged row.** The cells of the row in edit show their staged values. A boolean checkbox click, or a `cellTemplate` calling `ctx.commit(value)`, joins the row edit instead of writing straight to the record, so `cancelEdit()` rolls it back and `commitEdit()` writes it with the rest. Every cell passes validation and its `cellValueChanging` gate before any is written: one invalid or cancelled cell aborts the whole row.
|
|
149
|
+
- **An open editor stays on its record.** When the data, sort, filter or page changes while a cell is in edit, the editor follows the record it was opened for (on 3.5 it could jump to another row, and Enter wrote into the wrong record). The edit is cancelled when that record leaves the view (removed, filtered out, or replaced by a copy). `editingCell`, `editingRow` and the `rowIndex` of the edit events are -1 while the record is pinned or on another page; identify it by the event's `data`.
|
|
150
|
+
|
|
115
151
|
## Column groups — spanning headers
|
|
116
152
|
|
|
117
153
|
A presentational header layer above the column header row. `columns` stays flat; a column joins a group via its `group` id. Members must be **contiguous within one pin region** (the grid warns and skips the spanning cell otherwise). v1 ships static spanning headers only (`collapsible` is reserved / inert).
|
|
@@ -163,7 +199,7 @@ interface GetStateOptions<T> { rowId?: (row: T) => string | number; }
|
|
|
163
199
|
interface SetStateOptions<T> { rowId?: (row: T) => string | number; strict?: boolean; }
|
|
164
200
|
```
|
|
165
201
|
|
|
166
|
-
Event: `stateChanged
|
|
202
|
+
Event: `stateChanged`: `CustomEvent<{ state: GridState }>`, debounced after the render pipeline settles. Fires for programmatic (including `setState`) and UI changes. The payload is only computed while a listener is attached (zero-cost otherwise); identical snapshots don't re-fire, so compare before persisting to avoid a save loop. Attach the listener to the grid element itself: the grid counts only listeners registered on it, so a delegated listener on an ancestor receives nothing while none is attached to the grid.
|
|
167
203
|
|
|
168
204
|
## Programmatic pagination, quick-filter, and column pinning
|
|
169
205
|
|
|
@@ -181,7 +217,7 @@ await grid.pinColumn('name', 'start'); // 'start' | 'end' | null
|
|
|
181
217
|
await grid.unpinColumn('actions'); // = pinColumn(key, null)
|
|
182
218
|
```
|
|
183
219
|
|
|
184
|
-
`gotoPage` / `setPageSize` / `nextPage` / `previousPage` / `firstPage` / `lastPage` emit `pageChanging` → `pageChanged`. `setQuickFilter` emits `quickFilterChanging` → `quickFilterChanged
|
|
220
|
+
`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).
|
|
185
221
|
|
|
186
222
|
## Localization — `localeText`
|
|
187
223
|
|
|
@@ -200,6 +236,8 @@ Exports: `EN_LOCALE` (the default dictionary / source of truth for keys), `esLoc
|
|
|
200
236
|
|
|
201
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 host label `grid.label` ("Data grid"), 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.
|
|
202
238
|
|
|
239
|
+
**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
|
+
|
|
203
241
|
## Column menu & column separator
|
|
204
242
|
|
|
205
243
|
| Property | Type | Default | Notes |
|
|
@@ -220,13 +258,45 @@ grid.columnSeparator = false;
|
|
|
220
258
|
|---|---|
|
|
221
259
|
| `'view'` *(default)* | post-filter, post-sort rows across all pages |
|
|
222
260
|
| `'page'` | current page slice only |
|
|
223
|
-
| `'selected'` |
|
|
261
|
+
| `'selected'` | the selected rows in the order the grid shows them, then selected rows the view hides (3.6; on 3.5, selection order) |
|
|
224
262
|
| `'all'` | the raw `data` array |
|
|
225
263
|
|
|
226
264
|
```ts
|
|
227
265
|
grid.exportToCSV(); // download data.csv (view)
|
|
228
266
|
grid.exportToCSV({ filename: 'users', source: 'selected' });
|
|
229
267
|
const text = grid.exportToCSV({ filename: '' }); // string only, no download
|
|
268
|
+
grid.exportToCSV({ escapeFormulas: false }); // 3.6: opt out of formula escaping
|
|
230
269
|
```
|
|
231
270
|
|
|
232
|
-
`exportFormats` *(getter)* → `readonly ExportFormat[]` (`{ id, label }`)
|
|
271
|
+
`exportFormats` *(getter)* → `readonly ExportFormat[]` (`{ id, label }`). The community grid offers CSV only; it drives the toolbar export menu, one item per entry, dispatching to `exportAs(formatId, options?)`. Other `ExportOptions` fields: `columns?` (narrows to these keys among the visible, exportable columns, kept in display order), `includeHeader?` (default `true`), `formatter?`; CSV (`CSVExportOptions`) adds `delimiter?` (`','`), `bom?` (`true`), `newline?` (`'\r\n'`), `escapeFormulas?` (`true`, 3.6).
|
|
272
|
+
|
|
273
|
+
**Export output changes in 3.6:**
|
|
274
|
+
|
|
275
|
+
- **Formula escaping.** A CSV text cell or header that starts with `=`, `+`, `-`, `@`, a tab or a carriage return is written with a leading apostrophe, so a spreadsheet shows it as text instead of running it. Numbers are untouched, and so is text that is a plain signed decimal (`-5`, `+1.5`). Other text with a leading sign gains the apostrophe too: a phone number such as `+44 20 7946 0958`, or formatter output such as `-$5.00`. Pass `escapeFormulas: false` for the 3.5 output.
|
|
276
|
+
- **Typed values.** `boolean` columns export `true` only for the value `true` (as the checkbox shows), so strings such as `'false'` or `'yes'` and the number `1` export as `false`. `currency` and `progress` columns export as numbers, like `number` and `rating`; a value that is not a number exports as an empty cell.
|
|
277
|
+
|
|
278
|
+
## Master-detail: styling detail content and nested grids (3.6)
|
|
279
|
+
|
|
280
|
+
Parts used inside your own `detailTemplate` content are not reachable with `apex-grid::part()` until you list them in `expansion.exportParts`. The panel itself is always reachable as `detail-panel`.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
grid.expansion = {
|
|
284
|
+
enabled: true,
|
|
285
|
+
exportParts: ['order-summary'],
|
|
286
|
+
detailTemplate: ({ data }) => html`<div part="order-summary">${data.total}</div>`,
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
```css
|
|
291
|
+
apex-grid::part(detail-panel) { padding: 12px; }
|
|
292
|
+
apex-grid::part(order-summary) { font-weight: 600; }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
A grid rendered inside a detail panel is nested in the outer grid's rows, and grid events bubble and are composed, so a listener on the outer grid also receives the inner grid's events (`cellValueChanged`, `rowSelected`, `sorted`, ...). `grid.ownsEvent(event)` returns `true` only for the grid's own events; call it inside the listener, while the event is dispatching. The grid applies the same test to its own interactions, so since 3.6 clicks, Space selection, Ctrl+Z and pointer drags in a nested grid no longer drive the outer one.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
outer.addEventListener('rowSelected', (e) => {
|
|
299
|
+
if (!outer.ownsEvent(e)) return; // a nested grid's selection
|
|
300
|
+
updateToolbar(outer.selectedRows);
|
|
301
|
+
});
|
|
302
|
+
```
|
package/references/vanilla-js.md
CHANGED
|
@@ -134,13 +134,16 @@ grid.clearSort();
|
|
|
134
134
|
grid.clearFilter();
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
-
Programmatic calls don't fire `sorting` / `sorted` / `filtering` / `filtered` events. If you need a hook for every state change regardless of source,
|
|
137
|
+
Programmatic calls don't fire `sorting` / `sorted` / `filtering` / `filtered` events. If you need a hook for every state change regardless of source, read `grid.dataView` once the grid has updated after each programmatic call:
|
|
138
138
|
|
|
139
139
|
```js
|
|
140
140
|
grid.sort({ key: 'age', direction: 'descending' });
|
|
141
|
-
|
|
141
|
+
await grid.updateComplete; // the in-memory pipeline has run by now
|
|
142
|
+
console.log('post-sort:', grid.dataView);
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
A `queueMicrotask` callback is too early: it runs before the pipeline assigns the new view. With an async `dataPipelineConfiguration` hook the new rows exist only once your hook resolves, so act on them there.
|
|
146
|
+
|
|
144
147
|
---
|
|
145
148
|
|
|
146
149
|
## 5. Common vanilla-JS gotchas
|