apexgrid-skill 1.0.0 → 2.1.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/README.md +22 -7
- package/SKILL.md +312 -291
- package/index.js +2 -0
- package/package.json +1 -1
- package/references/columns-and-templates.md +72 -13
- package/references/data-pipeline.md +12 -7
- package/references/framework-integration.md +92 -47
- package/references/state-and-features.md +204 -0
- package/references/vanilla-js.md +154 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# State, Localization & Interaction Features (3.1+)
|
|
2
|
+
|
|
3
|
+
> Everything here landed in **apex-grid 3.1 – 3.3** on top of the 3.0 API. 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
|
+
|
|
5
|
+
## Row pinning — sticky top / bottom bands
|
|
6
|
+
|
|
7
|
+
Lift rows out of the scrolling body into sticky top / bottom bands. Identity is **by reference**, so pass the same object held in `grid.data`.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
grid.rowPinning = { enabled: true }; // GridRowPinningConfiguration
|
|
11
|
+
grid.pinRow(grid.data[0], 'top'); // 'top' | 'bottom' (default 'top')
|
|
12
|
+
grid.pinRow(grid.data[1], 'bottom');
|
|
13
|
+
grid.unpinRow(grid.data[0]);
|
|
14
|
+
grid.pinnedRows; // getter → { top: T[]; bottom: T[] }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Member | Signature | Notes |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `rowPinning` | `{ enabled: boolean }` | Disabled when omitted. |
|
|
20
|
+
| `pinRow(row, position?)` | `(T, 'top' \| 'bottom') => boolean` | Moves between bands if already pinned. `false` when disabled / cancelled. |
|
|
21
|
+
| `unpinRow(row)` | `(T) => boolean` | No-op (returns `true`) when not pinned. |
|
|
22
|
+
| `pinnedRows` *(getter)* | `{ top: T[]; bottom: T[] }` | In pin order. |
|
|
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; re-pin after a wholesale `data` swap.
|
|
25
|
+
|
|
26
|
+
## Row reordering — drag / keyboard / API
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
grid.rowReordering = { enabled: true, applyToData: false, handle: true };
|
|
30
|
+
grid.moveRow(2, 0, 'before'); // (from, to, position?: 'before' | 'after')
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Field | Type | Default | Notes |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| `enabled` | `boolean` | — | Disabled when omitted. |
|
|
36
|
+
| `applyToData` | `boolean` | `false` | `true` also splices `grid.data` in place; otherwise the order lives in the grid and you persist via `rowMoved`. |
|
|
37
|
+
| `handle` | `boolean` | `true` | `true` renders a leading grip cell (drag only from it); `false` drags from anywhere on the row. |
|
|
38
|
+
|
|
39
|
+
`moveRow(from, to, position?)` indices are into `grid.pageItems`; returns `boolean`. A manual order is **mutually exclusive with column sorting** — applying a sort clears it. Events: `rowMoving` (cancellable) → `rowMoved`, both `CustomEvent<{ from: number; to: number; data: T }>`.
|
|
40
|
+
|
|
41
|
+
## Inline-edit undo / redo
|
|
42
|
+
|
|
43
|
+
Opt in via `editing.history` (requires `editing.enabled`). Tracks every committed cell edit — single, row-mode, and bulk paste / fill.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
grid.editing = { enabled: true, history: { enabled: true, stackSize: 100 } };
|
|
47
|
+
grid.undo(); // Ctrl/Cmd+Z
|
|
48
|
+
grid.redo(); // Ctrl/Cmd+Shift+Z or Ctrl+Y
|
|
49
|
+
grid.clearHistory();
|
|
50
|
+
grid.canUndo; grid.canRedo; // getters (false unless history.enabled)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`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
|
+
|
|
55
|
+
## Declarative column validators
|
|
56
|
+
|
|
57
|
+
`column.validators: Validator[]` run **before** a candidate value is written. Each returns an error `string` (reject) or `null` (pass). All run and every message is collected, so one commit can surface multiple errors. A failing commit keeps the editor open, marks the cell `aria-invalid`, and emits `cellValidationFailed`. Covers bulk edits (paste / fill) too.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { required, min, max, pattern, custom } from 'apex-grid';
|
|
61
|
+
|
|
62
|
+
const columns: ColumnConfiguration<User>[] = [
|
|
63
|
+
{ key: 'name', editable: true, validators: [required('Name is required')] },
|
|
64
|
+
{ key: 'age', type: 'number', editable: true,
|
|
65
|
+
validators: [required(), min(0), max(120)] },
|
|
66
|
+
{ key: 'email', editable: true,
|
|
67
|
+
validators: [pattern(/^\S+@\S+$/, 'Invalid email')] },
|
|
68
|
+
{ key: 'code', editable: true,
|
|
69
|
+
validators: [custom((value, ctx) => value === ctx.data.id ? 'Cannot equal id' : null)] },
|
|
70
|
+
];
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Factory | Signature | Fails when |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `required(message?)` | `(msg?) => Validator` | value is `null` / `undefined` / empty-or-whitespace string |
|
|
76
|
+
| `min(limit, message?)` | `(number, msg?) => Validator` | numeric value `< limit` (non-numeric / empty pass — compose with `required`) |
|
|
77
|
+
| `max(limit, message?)` | `(number, msg?) => Validator` | numeric value `> limit` (non-numeric / empty pass) |
|
|
78
|
+
| `pattern(regex, message?)` | `(RegExp, msg?) => Validator` | `String(value)` doesn't match `regex` (empty passes) |
|
|
79
|
+
| `custom(fn)` | `(Validator) => Validator` | wraps a predicate; an inline arrow works identically |
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// Validator signature
|
|
83
|
+
type Validator<T, K> = (value: unknown, context: ValidatorContext<T, K>) => string | null;
|
|
84
|
+
interface ValidatorContext<T, K> { column: ColumnConfiguration<T, K>; data: T; rowIndex: number; }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Event: `cellValidationFailed` — `CustomEvent<{ key; rowIndex; data: T; value: unknown; errors: readonly string[] }>`.
|
|
88
|
+
|
|
89
|
+
## Column groups — spanning headers
|
|
90
|
+
|
|
91
|
+
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).
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
grid.columnGroups = [
|
|
95
|
+
{ id: 'name', headerText: 'Name' },
|
|
96
|
+
{ id: 'contact', headerText: 'Contact',
|
|
97
|
+
headerTemplate: ({ group, span }) => html`<em>${group.headerText} (${span})</em>` },
|
|
98
|
+
];
|
|
99
|
+
grid.columns = [
|
|
100
|
+
{ key: 'first', group: 'name' },
|
|
101
|
+
{ key: 'last', group: 'name' },
|
|
102
|
+
{ key: 'email', group: 'contact' },
|
|
103
|
+
{ key: 'phone', group: 'contact' },
|
|
104
|
+
];
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| `ColumnGroupConfiguration` | Type | Notes |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `id` | `string` | Referenced by a column's `group`. |
|
|
110
|
+
| `headerText` | `string` | Text in the spanning cell. |
|
|
111
|
+
| `headerTemplate?` | `(ctx: { group; span }) => TemplateResult` | Overrides `headerText`. |
|
|
112
|
+
| `collapsible?` | `boolean` | Reserved for a future collapse affordance; inert in v1. |
|
|
113
|
+
|
|
114
|
+
Getters: `hasColumnGroups` (a group has ≥1 visible member), `columnGroupDepth` (0 or 1 in v1), `displayColumns` (`readonly ColumnConfiguration<T>[]` in visual render order: start-pinned, unpinned, end-pinned).
|
|
115
|
+
|
|
116
|
+
## State & schema — persist / restore / drive AI
|
|
117
|
+
|
|
118
|
+
`getState()` returns a serializable, JSON-safe snapshot (functions / templates omitted); `setState()` applies a partial one. `getSchema()` returns a machine-readable descriptor of the grid (columns, per-column operations, capabilities, current state) — the contract an AI layer hands an LLM and validates a returned patch against.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const snapshot = grid.getState(); // GridState
|
|
122
|
+
localStorage.setItem('grid', JSON.stringify(snapshot));
|
|
123
|
+
// later …
|
|
124
|
+
grid.setState(JSON.parse(localStorage.getItem('grid')!)); // → SetStateResult
|
|
125
|
+
|
|
126
|
+
// Selection / expansion survive a wholesale data reload only with a stable id:
|
|
127
|
+
grid.rowId = (row) => row.id;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`GridState` (`version: 1`) captures: `columns` (order/width/pinning/visibility), `sort`, `filter` (condition by operand name), `quickFilter`, `pagination` (`{ page, pageSize }`), `selection`, `expansion`, `treeExpanded` / `treeExpandedKeys`, `rowPinning`, `rowOrder`, and per-`modules` state. Rows are captured as `RowRef` — a stable `id` when `rowId` is set, else a positional `index` into `data` (round-trips within a session, not across a data swap).
|
|
131
|
+
|
|
132
|
+
`setState(state, options?)` is **partial and defensive** — only present slices apply (omit a slice to leave it untouched; a present-but-empty `sort`/`filter` clears it). It never throws on malformed / stale / LLM input unless `options.strict` is set; unknown columns/operands, out-of-range pages, and unresolvable rows are dropped / clamped and reported. `rowOrder` is mutually exclusive with `sort` (a snapshot with both drops `rowOrder`).
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
interface SetStateResult { applied: string[]; skipped: string[]; warnings: string[]; }
|
|
136
|
+
interface GetStateOptions<T> { rowId?: (row: T) => string | number; }
|
|
137
|
+
interface SetStateOptions<T> { rowId?: (row: T) => string | number; strict?: boolean; }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
## Programmatic pagination, quick-filter, and column pinning
|
|
143
|
+
|
|
144
|
+
All emit the same cancellable-then-notify event pairs as their UI equivalents and return `Promise<boolean>`.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
await grid.gotoPage(2); // clamped into [0, pageCount - 1]
|
|
148
|
+
await grid.nextPage();
|
|
149
|
+
await grid.previousPage();
|
|
150
|
+
await grid.firstPage();
|
|
151
|
+
await grid.lastPage();
|
|
152
|
+
await grid.setPageSize(50); // returns to the first page
|
|
153
|
+
await grid.setQuickFilter('john'); // '' to clear
|
|
154
|
+
await grid.pinColumn('name', 'start'); // 'start' | 'end' | null
|
|
155
|
+
await grid.unpinColumn('actions'); // = pinColumn(key, null)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`gotoPage` / `setPageSize` / `nextPage` / `previousPage` / `firstPage` / `lastPage` emit `pageChanging` → `pageChanged`. `setQuickFilter` emits `quickFilterChanging` → `quickFilterChanged`. `pinColumn` / `unpinColumn` emit `columnPinning` → `columnPinned` and only change the visual render order (the `columns` array isn't reordered).
|
|
159
|
+
|
|
160
|
+
## Localization — `localeText`
|
|
161
|
+
|
|
162
|
+
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.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { esLocale } from 'apex-grid';
|
|
166
|
+
|
|
167
|
+
grid.localeText = esLocale; // whole-UI Spanish
|
|
168
|
+
grid.localeText = { 'toolbar.searchPlaceholder': 'Buscar…' }; // or override a few keys
|
|
169
|
+
|
|
170
|
+
grid.localize('pagination.summary', { start: 1, end: 25, total: 100 }); // resolve a key
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
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).
|
|
174
|
+
|
|
175
|
+
## Column menu & column separator
|
|
176
|
+
|
|
177
|
+
| Property | Type | Default | Notes |
|
|
178
|
+
|---|---|---|---|
|
|
179
|
+
| `columnMenu` | `boolean` (JS property, no attr) | `true` | Kebab-menu button in each header, always visible. Set `false` to hide it. |
|
|
180
|
+
| `columnSeparator` (attr `column-separator`) | `boolean` | `true` | Persistent trailing divider on every header. On resizable columns it doubles as the resize handle. Theme via `--ag-header-separator` / `--header-separator-color` and inset via `--apex-header-separator-inset`. |
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
grid.columnMenu = false;
|
|
184
|
+
grid.columnSeparator = false;
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Export source & formats
|
|
188
|
+
|
|
189
|
+
`exportToCSV` / `exportAs` accept a `source` selecting which rows to export:
|
|
190
|
+
|
|
191
|
+
| `source` | Rows exported |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `'view'` *(default)* | post-filter, post-sort rows across all pages |
|
|
194
|
+
| `'page'` | current page slice only |
|
|
195
|
+
| `'selected'` | current row selection (insertion order) |
|
|
196
|
+
| `'all'` | the raw `data` array |
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
grid.exportToCSV(); // download data.csv (view)
|
|
200
|
+
grid.exportToCSV({ filename: 'users', source: 'selected' });
|
|
201
|
+
const text = grid.exportToCSV({ filename: '' }); // string only, no download
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`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?` (keys to include), `includeHeader?` (default `true`), `formatter?`; CSV adds `delimiter?` (`','`), `bom?` (`true`), `newline?` (`'\r\n'`).
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Vanilla JavaScript — Without Lit's `render` in Your App Code
|
|
2
|
+
|
|
3
|
+
`apex-grid` is built with Lit internally, but you don't have to use Lit's `render` / `html` in your application code. You can write a pure-DOM page that imports the custom element, sets properties directly, and listens for events with `addEventListener`.
|
|
4
|
+
|
|
5
|
+
**One honest caveat:** `cellTemplate` and `headerTemplate` are the **only** places that still require Lit's `html` tag — they must return a `TemplateResult` and there is no string-template escape hatch. Returning a plain string renders as escaped text. If you only need the default rendering (no custom cells), you can avoid Lit entirely.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Pure-DOM, default rendering (no Lit at all)
|
|
10
|
+
|
|
11
|
+
Open this in a Vite dev server (`npm create vite@latest`, then `npm run dev`):
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8" />
|
|
18
|
+
<title>Apex Grid — vanilla JS</title>
|
|
19
|
+
<style>
|
|
20
|
+
body { margin: 0; padding: 20px; font-family: system-ui; }
|
|
21
|
+
apex-grid { height: 480px; } /* REQUIRED — bounded host. NEVER set `display` here — it overrides the component's `:host { display: grid }` and collapses the virtualizer. */
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<h1>Users</h1>
|
|
26
|
+
<apex-grid id="grid"></apex-grid>
|
|
27
|
+
|
|
28
|
+
<script type="module">
|
|
29
|
+
// 1. Register the element + adopt host sizing (no theme import needed)
|
|
30
|
+
import { setup } from 'apex-grid';
|
|
31
|
+
setup();
|
|
32
|
+
|
|
33
|
+
// 2. Set properties on the element
|
|
34
|
+
const grid = document.getElementById('grid');
|
|
35
|
+
|
|
36
|
+
grid.columns = [
|
|
37
|
+
{ key: 'id', type: 'number', headerText: 'ID', width: '80px', sort: true, filter: true },
|
|
38
|
+
{ key: 'name', type: 'string', headerText: 'Name', width: '240px', sort: true, filter: true },
|
|
39
|
+
{ key: 'age', type: 'number', headerText: 'Age', width: '100px', sort: true, filter: true },
|
|
40
|
+
{ key: 'subscribed', type: 'boolean', headerText: 'Subscribed', width: '140px', sort: true, filter: true },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
grid.data = [
|
|
44
|
+
{ id: 1, name: 'Ada Lovelace', age: 36, subscribed: true },
|
|
45
|
+
{ id: 2, name: 'Carl Sagan', age: 62, subscribed: false },
|
|
46
|
+
{ id: 3, name: 'Grace Hopper', age: 85, subscribed: true },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// 3. Listen for events
|
|
50
|
+
grid.addEventListener('sorted', (e) => console.log('sorted', e.detail));
|
|
51
|
+
grid.addEventListener('filtered', (e) => console.log('filtered', e.detail));
|
|
52
|
+
</script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This works without any `import { html, render } from 'lit'` in your code. The grid renders, sorts, and filters with the default cell display.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. When you need a custom cell — minimal Lit usage
|
|
62
|
+
|
|
63
|
+
The moment you want to style a cell (e.g. render the `subscribed` column as a checkbox), you must import `html` from Lit. There's no way around it.
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// Add this to the imports above:
|
|
67
|
+
import { html } from 'lit';
|
|
68
|
+
|
|
69
|
+
grid.columns = [
|
|
70
|
+
{ key: 'id', type: 'number', width: '80px', sort: true, filter: true },
|
|
71
|
+
{ key: 'name', type: 'string', width: '240px', sort: true, filter: true,
|
|
72
|
+
cellTemplate: ({ value }) => html`<strong>${value}</strong>`,
|
|
73
|
+
},
|
|
74
|
+
{ key: 'age', type: 'number', width: '100px', sort: true, filter: true },
|
|
75
|
+
{ key: 'subscribed', type: 'boolean', width: '140px', sort: true, filter: true,
|
|
76
|
+
cellTemplate: ({ value }) => html`<input type="checkbox" .checked=${value} disabled />`,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
You're still not calling Lit's `render(...)` anywhere — you only import `html` to author template values that the custom element internally renders for each cell.
|
|
82
|
+
|
|
83
|
+
### Why not a string template?
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
// ❌ This renders as literal text: <strong>Ada Lovelace</strong>
|
|
87
|
+
cellTemplate: ({ value }) => `<strong>${value}</strong>`,
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The cell renderer treats the return value as a Lit template, not as HTML markup. Strings are coerced to text nodes, so any `<tag>` characters are escaped. There is no `dangerouslySetInnerHTML` equivalent.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. Auto-generation — the genuinely 3-line demo
|
|
95
|
+
|
|
96
|
+
If you don't care about specific columns and just want to see your data in a grid:
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<apex-grid auto-generate id="grid"></apex-grid>
|
|
100
|
+
|
|
101
|
+
<script type="module">
|
|
102
|
+
import { setup } from 'apex-grid';
|
|
103
|
+
setup();
|
|
104
|
+
|
|
105
|
+
document.getElementById('grid').data = [
|
|
106
|
+
{ id: 1, name: 'Ada', age: 36 },
|
|
107
|
+
{ id: 2, name: 'Carl', age: 62 },
|
|
108
|
+
];
|
|
109
|
+
</script>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Auto-generation infers columns from `Object.keys(data[0])` and runtime types. `sort` and `filter` default to `false`, so the auto path is read-only out of the box. To enable them with auto-generated columns, run `updateColumns` after the auto-pass:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
grid.updateColumns([
|
|
116
|
+
{ key: 'id', sort: true, filter: true },
|
|
117
|
+
{ key: 'name', sort: true, filter: true },
|
|
118
|
+
{ key: 'age', sort: true, filter: true },
|
|
119
|
+
]);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 4. Programmatic sort & filter without templates
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
grid.sort({ key: 'age', direction: 'descending' });
|
|
128
|
+
|
|
129
|
+
grid.filter({ key: 'age', condition: 'greaterThan', searchTerm: 30 });
|
|
130
|
+
|
|
131
|
+
grid.filter({ key: 'subscribed', condition: 'true' }); // boolean operands are unary
|
|
132
|
+
|
|
133
|
+
grid.clearSort();
|
|
134
|
+
grid.clearFilter();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Programmatic calls don't fire `sorting` / `sorted` / `filtering` / `filtered` events. If you need a hook for every state change regardless of source, watch `grid.dataView` after each programmatic call:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
grid.sort({ key: 'age', direction: 'descending' });
|
|
141
|
+
queueMicrotask(() => console.log('post-sort:', grid.dataView));
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 5. Common vanilla-JS gotchas
|
|
147
|
+
|
|
148
|
+
| ❌ | ✅ |
|
|
149
|
+
|---|---|
|
|
150
|
+
| Setting `grid.setAttribute('columns', JSON.stringify(cols))` | `grid.columns = cols` — must be a property, not an attribute |
|
|
151
|
+
| Forgetting `<script type="module">` | All `apex-grid` imports are ESM; you need `type="module"` |
|
|
152
|
+
| Opening the HTML with `file://` | Use a dev server — ESM imports fail CORS over `file://` |
|
|
153
|
+
| `cellTemplate: ({ value }) => '<b>' + value + '</b>'` | `cellTemplate: ({ value }) => html\`<b>${value}</b>\`` — Lit `html` is required |
|
|
154
|
+
| Setting `grid.columns` *before* the script tag's module imports resolve | Set them inside the same `<script type="module">` as the imports |
|