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/SKILL.md CHANGED
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: apex-grid
3
3
  description: >
4
- AI skill for building UIs with the `apex-grid` web component (the Lit-based
5
- ApexCharts data grid). Use whenever the user asks to create, configure, or
6
- troubleshoot a sortable / filterable / virtualized data table with
7
- `<apex-grid>`. Covers the generic `ColumnConfiguration<T>` shape, cell &
8
- header templates that return Lit `TemplateResult`s, the
9
- `sort` / `filter` / `sorted` / `filtered` events, programmatic
10
- `sort()` / `filter()` / `clearSort()` / `clearFilter()` / `updateColumns()`
11
- APIs, the `dataPipelineConfiguration` hooks for server-side data, and the
12
- one-time `ApexGrid.register()` custom-element registration.
4
+ AI skill for building UIs with the `apex-grid` web component (a Lit-based
5
+ data grid by ApexCharts). Use whenever the user asks to create, configure, or
6
+ troubleshoot a sortable / filterable / paginated / editable / virtualized data
7
+ table with `<apex-grid>`. Covers the register-and-size setup (no theme import —
8
+ the grid styles itself via `--ag-*` CSS variables), the generic
9
+ `ColumnConfiguration<T>` shape with its 13 column types, Lit `cellTemplate`
10
+ requirements, sorting / filtering / quick-filter, pagination, inline editing,
11
+ row selection, master-detail and tree rows, CSV export, the UI-only event
12
+ model, and the `dataPipelineConfiguration` hooks for server-side data.
13
13
  metadata:
14
14
  author: ApexCharts
15
- version: "1.0.0"
16
- library_version: ">=0.0.0"
15
+ version: "2.1.0"
16
+ library_version: "3.3.0"
17
17
  category: data-visualization
18
18
  tags: [grid, data-grid, table, web-component, lit, apex-grid]
19
19
  docs: https://github.com/apexcharts/apexgrid
@@ -23,64 +23,80 @@ metadata:
23
23
 
24
24
  # Apex Grid AI Skill
25
25
 
26
- > **Heads-up on naming.** The npm package is **`apex-grid`** (with a hyphen). The custom-element tag is **`<apex-grid>`**. The exported class is **`ApexGrid<T>`**. There is no `apexgrid` package — using `import ... from 'apexgrid'` will fail.
26
+ ## 1. Quickstart — produce a visible, styled grid
27
27
 
28
- ## 1. Critical Rules
28
+ The grid styles itself out of the box (borders, row separators, sort/filter UI) — there is **no theme CSS to import**. The one real gotcha is host sizing: the grid virtualizes rows, so it needs a bounded height or it collapses and renders nothing. Two setup steps: **register** the element and give it a **bounded height**, then bind `.data` / `.columns`.
29
29
 
30
- 1. **Apex Grid is a Lit web component, not a JS class with a constructor.** You don't `new ApexGrid(...)`. You register the custom element once and then use `<apex-grid>` in the DOM, setting `.data` and `.columns` as JS properties.
31
- 2. **Register the element exactly once at app startup**: either `import 'apex-grid/define'` (auto-registers as a side-effect) or `import { ApexGrid } from 'apex-grid'; ApexGrid.register();`. Without this, the tag renders as an empty inert element.
32
- 3. **`columns` and `data` are properties, not attributes.** Always bind with the property syntax (`.columns=${...}` in Lit, `[columns]="..."` in Angular, `:columns=` in Vue, ref-based in plain JS) — never as a stringified `columns="..."` attribute.
33
- 4. **`column.key` must be a real key of `T`** (the data row type). It is what the grid reads from each row, what sort/filter expressions reference, and what's emitted in events. There is no `field` / `dataIndex` / `accessor` alias.
34
- 5. **`type` defaults to `'string'`.** Set it to `'number'` or `'boolean'` to get the right filter operands and comparer. There is no `'date'` type — use `'number'` (timestamps) or `'string'` (ISO) and a custom `comparer` / `cellTemplate`.
35
- 6. **`sort` and `filter` per-column are `boolean | configObject`.** They are `false` by default — no opt-in, no UI. Set `sort: true` / `filter: true` to enable, or pass a config object for fine-grained control.
36
- 7. **`cellTemplate` and `headerTemplate` must return Lit `TemplateResult`s** (`html\`...\``) — not strings, DOM nodes, or framework-specific JSX. Plain HTML strings render as escaped text.
37
- 8. **Sort/filter events fire on UI-initiated operations only.** Programmatic calls (`grid.sort(...)`, `grid.filter(...)`) do not emit `sorting` / `sorted` / `filtering` / `filtered` events.
38
- 9. **`sorting` and `filtering` events are cancellable** (`event.preventDefault()`) and their `event.detail` is mutable so you can rewrite the expression before it runs.
39
- 10. **For server-side data, set `dataPipelineConfiguration.sort` / `.filter`** — async hooks that receive `{ data, grid, type }` and return the new array. The grid skips its built-in pipeline entirely when a hook is provided for that operation.
40
- 11. **`autoGenerate` is ignored when `columns` is non-empty.** To re-trigger auto-generation after a data swap, set `grid.columns = []` first, then assign the new `data`.
41
- 12. **`grid.rows` only returns currently rendered rows.** The grid uses `@lit-labs/virtualizer`, so off-screen rows are not in the DOM.
42
- 13. **`apex-grid` depends on `igniteui-webcomponents`** for filter/sort UI controls. The package lists it as a runtime dependency — don't try to remove it.
30
+ ### 1.1 Install
43
31
 
44
- ---
32
+ ```bash
33
+ npm install apex-grid lit
34
+ ```
35
+
36
+ `igniteui-webcomponents` is a regular (transitive) dependency — it installs automatically, and you do **not** import any theme CSS from it.
45
37
 
46
- ## 2. Data Format & Column Configuration
38
+ > Confirmed working versions: `apex-grid@3.0.1`, `lit@^3.0.0`.
47
39
 
48
- Apex Grid is generic over a row type `T`:
40
+ ### 1.2 Register the element + size the host — `setup()` does both
41
+
42
+ The one-call convenience export registers `<apex-grid>` and adopts a default host stylesheet (`height: 100%` with a `min-height: 240px` fallback) so the virtualizer has a bounded height:
49
43
 
50
44
  ```ts
51
- interface ColumnConfiguration<T extends object, K extends keyof T = keyof T> {
52
- key: K; // REQUIRED — must be a key of T
53
- type?: 'string' | 'number' | 'boolean'; // default 'string'
54
- headerText?: string; // default: the key itself
55
- width?: string; // any CSS width
56
- hidden?: boolean; // default false
57
- resizable?: boolean; // default false
58
- sort?: boolean | { caseSensitive?: boolean; comparer?: (a: T[K], b: T[K]) => number };
59
- filter?: boolean | { caseSensitive?: boolean };
60
- headerTemplate?: (ctx: ApexHeaderContext<T>) => TemplateResult | unknown;
61
- cellTemplate?: (ctx: ApexCellContext<T, K>) => TemplateResult | unknown;
45
+ import { setup } from 'apex-grid';
46
+ setup(); // registers <apex-grid> + adopts host sizing. Idempotent.
47
+ ```
48
+
49
+ Prefer manual control? Register with the side-effect import (or the static method) and size the host yourself:
50
+
51
+ ```ts
52
+ import 'apex-grid/define'; // side-effect import, idempotent
53
+ // or: import { ApexGrid } from 'apex-grid'; ApexGrid.register();
54
+ ```
55
+
56
+ ```css
57
+ apex-grid { height: 480px; } /* any explicit height; % works if the parent is sized too */
58
+ ```
59
+
60
+ `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.
61
+
62
+ > **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.
63
+
64
+ ### 1.3 Styling — `--ag-*` CSS custom properties (no theme import)
65
+
66
+ 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 an ancestor):
67
+
68
+ ```css
69
+ apex-grid {
70
+ height: 480px;
71
+ /* override any --ag-* variables to retheme; see the package README for the full list */
62
72
  }
63
73
  ```
64
74
 
65
- Minimal example:
75
+ > The deprecated `theme` option (and `igniteui-webcomponents`' `configureTheme()`) does **not** change the grid's appearance — it only forwards to igniteui for apps that embed igniteui components alongside the grid. Omit it; use `--ag-*` variables instead.
76
+
77
+ ### 1.4 Minimal example — a styled, sortable, filterable table
66
78
 
67
79
  ```ts
68
80
  import { html, render } from 'lit';
69
- import 'apex-grid/define'; // registers <apex-grid>
81
+ import { setup } from 'apex-grid';
70
82
  import type { ColumnConfiguration } from 'apex-grid';
71
83
 
84
+ setup(); // register + host sizing; no theme import needed
85
+
72
86
  type User = { id: number; name: string; age: number; subscribed: boolean };
73
87
 
74
88
  const data: User[] = [
75
- { id: 1, name: 'Ada', age: 36, subscribed: true },
76
- { id: 2, name: 'Carl', age: 41, subscribed: false },
89
+ { id: 1, name: 'Ada Lovelace', age: 36, subscribed: true },
90
+ { id: 2, name: 'Carl Sagan', age: 62, subscribed: false },
91
+ { id: 3, name: 'Grace Hopper', age: 85, subscribed: true },
77
92
  ];
78
93
 
94
+ // Explicit widths prevent the "last column stretches to fill" trap.
79
95
  const columns: ColumnConfiguration<User>[] = [
80
- { key: 'id', type: 'number', sort: true, filter: true, headerText: 'ID' },
81
- { key: 'name', type: 'string', sort: true, filter: true },
82
- { key: 'age', type: 'number', sort: true, filter: true },
83
- { key: 'subscribed', type: 'boolean', sort: true, filter: true },
96
+ { key: 'id', type: 'number', headerText: 'ID', width: '80px', sort: true, filter: true },
97
+ { key: 'name', type: 'string', headerText: 'Name', width: '240px', sort: true, filter: true },
98
+ { key: 'age', type: 'number', headerText: 'Age', width: '100px', sort: true, filter: true },
99
+ { key: 'subscribed', type: 'boolean', headerText: 'Subscribed', width: '140px', sort: true, filter: true },
84
100
  ];
85
101
 
86
102
  render(
@@ -89,322 +105,327 @@ render(
89
105
  );
90
106
  ```
91
107
 
92
- ### Column types and filter operands
108
+ ### 1.5 What success looks like
93
109
 
94
- The `type` field selects which filter operand set is exposed in the filter UI:
110
+ - **Visible borders** between rows and columns, and **hover state** on rows
111
+ - **Sort arrows** (↕) next to each header (because `sort: true`)
112
+ - A **filter row** below the headers with a "Filter" chip per column (because `filter: true`)
113
+ - **Smooth virtualized scrolling** — only ~20 `<apex-grid-row>` elements exist in the DOM at once, even with thousands of rows
95
114
 
96
- | `type` | Operands available |
97
- |---|---|
98
- | `'string'` *(default)* | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
99
- | `'number'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
100
- | `'boolean'` | `all`, `true`, `false`, `empty`, `notEmpty` |
115
+ If rows don't render at all, it's almost always the host height (§1.2). If the grid looks bare, you don't need a theme import — check your `--ag-*` overrides aren't fighting the defaults.
101
116
 
102
- Programmatic filter expressions can reference operands by name (`condition: 'contains'`) or by importing them as a function:
117
+ > **Dev-server reminder.** ES module imports need an HTTP server — opening via `file://` fails with CORS errors. Use Vite (`npm create vite@latest`), any static server, or your framework's dev server.
103
118
 
104
- ```ts
105
- import { StringOperands } from 'apex-grid';
106
-
107
- grid.filter({ key: 'name', condition: StringOperands.contains, searchTerm: 'ada' });
108
- // or shorthand
109
- grid.filter({ key: 'name', condition: 'contains', searchTerm: 'ada' });
110
- ```
111
-
112
- ### Cell & header templates (Lit)
113
-
114
- Templates are Lit render callbacks. They receive a context object and must return a `TemplateResult`.
115
-
116
- ```ts
117
- const columns: ColumnConfiguration<User>[] = [
118
- {
119
- key: 'name',
120
- cellTemplate: ({ value, row, parent, column }) => html`
121
- <strong style="color:#1f4ed8">${value}</strong>
122
- `,
123
- },
124
- {
125
- key: 'subscribed',
126
- type: 'boolean',
127
- cellTemplate: ({ value }) => html`
128
- <input type="checkbox" .checked=${value} disabled />
129
- `,
130
- },
131
- {
132
- key: 'age',
133
- headerTemplate: ({ column }) => html`
134
- <em>${column.headerText ?? String(column.key)}</em>
135
- `,
136
- },
137
- ];
138
- ```
139
-
140
- Context shapes:
119
+ ---
141
120
 
142
- ```ts
143
- interface ApexCellContext<T, K extends keyof T> {
144
- parent: ApexGridCell<T>; // the cell element
145
- row: ApexGridRow<T>; // the row element
146
- column: ColumnConfiguration<T, K>;
147
- value: T[K];
148
- }
121
+ ## 2. Naming — `apex-grid` vs `<apex-grid>` vs `ApexGrid`
149
122
 
150
- interface ApexHeaderContext<T> {
151
- parent: ApexGridHeader<T>;
152
- column: ColumnConfiguration<T>;
153
- }
154
- ```
123
+ | Form | What it is |
124
+ |---|---|
125
+ | `apex-grid` | The **npm package name**. `import { ApexGrid } from 'apex-grid'`. With a hyphen. **There is no `apexgrid` package.** |
126
+ | `<apex-grid>` | The **HTML custom-element tag** rendered into the DOM. |
127
+ | `ApexGrid<T>` | The **exported class**. Used for types, the static `register()` method, and `ApexGrid.tagName`. |
155
128
 
156
- ### Auto-generated columns
129
+ AI models trained before the package was published may suggest `apexgrid` (no hyphen) or treat `ApexGrid` as a class constructor — both are wrong.
157
130
 
158
- `auto-generate` (or `.autoGenerate=${true}`) infers columns from the first data row's keys. **It is ignored if `columns` is non-empty.** To re-trigger after a data swap:
131
+ ---
159
132
 
160
- ```ts
161
- grid.columns = []; // reset first
162
- grid.data = newData; // then replace data — columns are inferred
163
- ```
133
+ ## 3. Critical Rules
164
134
 
165
- ---
135
+ 1. **Apex Grid is a Lit web component, not a JS class with a constructor.** You don't `new ApexGrid(...)`. Render `<apex-grid>` in the DOM and set `.data` / `.columns` as properties.
166
136
 
167
- ## 3. Public API
137
+ 2. **Two setup steps for a visible grid:** register the element and give the host a bounded height — `setup()` does both (§1.2). **No theme CSS import is required** (§1.3); the grid styles itself via `--ag-*` variables. The classic failure is an unsized host → no rows.
168
138
 
169
- ### Properties
139
+ 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.
170
140
 
171
- | Property | Type | Default | Notes |
172
- |---|---|---|---|
173
- | `data` | `T[]` | `[]` | Row data. Setting it triggers a pipeline run. |
174
- | `columns` | `ColumnConfiguration<T>[]` | `[]` | Column definitions. Each entry is merged with `DEFAULT_COLUMN_CONFIG`. |
175
- | `autoGenerate` | `boolean` (attr `auto-generate`) | `false` | Infer columns from data. Ignored when `columns` is non-empty. |
176
- | `sortConfiguration` | `{ multiple: boolean; triState: boolean }` | `{ true, true }` | Grid-wide sort behaviour. |
177
- | `dataPipelineConfiguration` | `{ sort?, filter? }` | — | Server-side / async data hooks. |
178
- | `sortExpressions` | `SortExpression<T>[]` | `[]` | Get/set the active sort state. Setting an empty array is a no-op — use `clearSort()`. |
179
- | `filterExpressions` | `FilterExpression<T>[]` | `[]` | Get/set the active filter state. Empty-set semantics same as above. |
180
- | `rows` | `ApexGridRow<T>[]` *(getter)* | — | Currently rendered rows only (virtualized). |
181
- | `dataView` | `readonly T[]` *(getter)* | — | Data after sort + filter pipeline. |
182
- | `totalItems` | `number` *(getter)* | — | `dataView.length`. |
141
+ 4. **`column.key` must be a real `keyof T`.** No `field` / `dataIndex` / `accessor` aliases. To display a derived value, compute it in `cellTemplate` (which has `row` access).
183
142
 
184
- ### Methods
143
+ 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).
185
144
 
186
- | Method | Description |
187
- |---|---|
188
- | `sort(expr \| expr[])` | Apply a sort. Does **not** fire `sorting` / `sorted` events. |
189
- | `filter(expr \| expr[])` | Apply a filter. Does **not** fire `filtering` / `filtered` events. |
190
- | `clearSort(key?)` | Clear all sort state, or just the column with `key`. |
191
- | `clearFilter(key?)` | Clear all filter state, or just the column with `key`. |
192
- | `getColumn(keyOrIndex)` | Find a column by `key` or numeric index. |
193
- | `updateColumns(col \| col[])` | Merge new properties into existing columns by `key`. Triggers a pipeline run. |
145
+ 6. **`cellTemplate` / `headerTemplate` / `editorTemplate` must return Lit `TemplateResult`s** — values from an `` html`...` `` tagged template. Returning a plain string renders as escaped text.
194
146
 
195
- ### Static
147
+ 7. **Sort/filter (and the other `*-ing`/`*-ed`) events fire only for UI-initiated operations.** Programmatic `grid.sort(...)` / `grid.filter(...)` calls apply silently. To react to every change regardless of source, watch `grid.dataView`.
196
148
 
197
- | Member | Description |
198
- |---|---|
199
- | `ApexGrid.tagName` | The string `'apex-grid'`. |
200
- | `ApexGrid.register()` | Registers `<apex-grid>` and all internal sub-components. Idempotent. Either call this once, or import `'apex-grid/define'` for auto-registration. |
149
+ 8. **Features are opt-in via config objects.** Sorting/filtering are per-column (`sort`/`filter`); editing, selection, pagination, tree, and master-detail expansion are enabled via the grid-level `editing` / `selection` / `pagination` / `tree` / `expansion` properties (§5).
201
150
 
202
151
  ---
203
152
 
204
- ## 4. Events
153
+ ## 4. Data & Columns
205
154
 
206
- Events fire **only** when the user interacts with sort / filter UI. Programmatic `grid.sort(...)` / `grid.filter(...)` calls are silent.
155
+ Apex Grid is generic over a row type `T extends object`:
207
156
 
208
157
  ```ts
209
- import type { ApexGridEventMap } from 'apex-grid';
210
-
211
- grid.addEventListener('sorting', (e) => {
212
- // CustomEvent<SortExpression<T>>
213
- // e.detail is the expression about to be applied — mutate to rewrite, preventDefault to cancel
214
- if (e.detail.direction === 'descending') {
215
- e.preventDefault();
216
- }
217
- });
218
-
219
- grid.addEventListener('sorted', (e) => {
220
- // CustomEvent<SortExpression<T>>
221
- });
222
-
223
- grid.addEventListener('filtering', (e) => {
224
- // CustomEvent<{ key, expressions, type: 'add'|'modify'|'remove' }>
225
- });
226
-
227
- grid.addEventListener('filtered', (e) => {
228
- // CustomEvent<{ key, state: FilterExpression<T>[] }>
229
- });
158
+ interface ColumnConfiguration<T extends object, K extends keyof T = keyof T> {
159
+ key: K; // REQUIRED — must be a keyof T
160
+ type?: DataType; // default 'string' (see column types below)
161
+ headerText?: string; // default: String(key)
162
+ width?: string; // any CSS width — recommended in practice
163
+ hidden?: boolean; // default false
164
+ resizable?: boolean; // default false
165
+ pinned?: 'start' | 'end' | null; // freeze to a side during horizontal scroll
166
+ reorderable?: boolean; // per-column opt-out of drag reorder
167
+ exportable?: boolean; // default true; false omits from CSV export
168
+ group?: string; // id of a columnGroups spanning header (§5)
169
+ sort?: boolean | { caseSensitive?: boolean; comparer?: (a: T[K], b: T[K]) => number };
170
+ filter?: boolean | { caseSensitive?: boolean };
171
+ editable?: boolean; // requires grid `editing.enabled`
172
+ validators?: Validator<T, K>[]; // declarative edit-time validation (§5)
173
+ headerTemplate?: (ctx: ApexHeaderContext<T>) => TemplateResult | unknown;
174
+ cellTemplate?: (ctx: ApexCellContext<T, K>) => TemplateResult | unknown;
175
+ editorTemplate?: (ctx: ApexEditorContext<T, K>) => TemplateResult | unknown;
176
+ // Type-specific options (each is ignored unless `type` matches):
177
+ options?: (V | { value: V; label?: string })[]; // type: 'select'
178
+ max?: number; // 'rating' star count (5) / 'progress' full value (100)
179
+ format?: 'short' | 'medium' | 'long' | 'full'; // 'date' display (default 'medium')
180
+ shape?: 'square' | 'circle'; // 'image' crop (default 'square')
181
+ alt?: string; // 'image' alt text
182
+ currency?: string; // 'currency' ISO 4217 code (default 'USD')
183
+ locale?: string; // 'currency' BCP 47 locale
184
+ badgeVariant?: 'gold' | 'brand' | 'neutral' | 'muted' | ((v) => …); // 'badge'
185
+ statusVariant?: 'active' | 'trial' | 'churn' | ((v) => …); // 'status'
186
+ showDelta?: boolean; // 'sparkline' trailing delta label (default true)
187
+ }
230
188
  ```
231
189
 
232
- | Event | Cancellable | `event.detail` |
233
- |---|---|---|
234
- | `sorting` | yes | `SortExpression<T>` (mutable) |
235
- | `sorted` | no | `SortExpression<T>` |
236
- | `filtering` | yes | `{ key: keyof T; expressions: FilterExpression<T>[]; type: 'add' \| 'modify' \| 'remove' }` |
237
- | `filtered` | no | `{ key: keyof T; state: FilterExpression<T>[] }` |
190
+ Applied defaults (`DEFAULT_COLUMN_CONFIG`): `{ type: 'string', resizable: false, hidden: false, sort: false, filter: false }`. Sort and filter are **off** until you opt in. (`exportable` isn't in that object but behaves as `true` unless set to `false`.)
238
191
 
239
- ---
192
+ ### Column types (`DataType`)
240
193
 
241
- ## 5. Server-Side / Async Data — `dataPipelineConfiguration`
194
+ `type` is both the data type and, for the built-ins, the presentation renderer. The three primitive types drive sort/filter/editing; the rest are renderers over a primitive value (and sort/filter as that underlying value):
242
195
 
243
- When sort or filter should run on the server, supply async hooks. Each hook receives `{ data, grid, type }` and returns the new array. **The built-in pipeline is skipped for whichever operations have a hook.**
196
+ | `type` | Renders / behaves as |
197
+ |---|---|
198
+ | `'string'` | text (default); text editor |
199
+ | `'number'` | number; number editor |
200
+ | `'boolean'` | check-mark icon (true) / dimmed (false); checkbox editor |
201
+ | `'select'` | label from `options`; `<select>` editor. Sorts/filters as its value |
202
+ | `'rating'` | 0..`max` (default 5) as stars; star-picker editor. Numeric |
203
+ | `'date'` | locale date (`format` preset); `<input type="date">` editor. Accepts `Date`/ISO/timestamp |
204
+ | `'image'` | `<img>` (`shape` `'square'`/`'circle'`, `alt`) |
205
+ | `'currency'` | `Intl.NumberFormat` (`currency`, `locale`). Numeric |
206
+ | `'avatar'` | first letter in a tinted circle |
207
+ | `'badge'` | colored pill (`badgeVariant`) |
208
+ | `'progress'` | health bar, 0..`max` (default 100) |
209
+ | `'sparkline'` | inline trend chart of a `number[]` (`showDelta`) |
210
+ | `'status'` | dot + pill (`statusVariant`, or inferred) |
211
+
212
+ > 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.
213
+
214
+ ### Column type → filter operands (primitive types)
215
+
216
+ | `type` | Operand keys in the filter UI |
217
+ |---|---|
218
+ | `'string'` | `contains`, `doesNotContain`, `startsWith`, `endsWith`, `equals`, `doesNotEqual`, `empty`, `notEmpty` |
219
+ | `'number'` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
220
+ | `'boolean'` | `all`, `true`, `false`, `empty`, `notEmpty` (all **unary** — no `searchTerm`) |
244
221
 
245
222
  ```ts
246
- grid.dataPipelineConfiguration = {
247
- sort: async ({ data, grid }) => {
248
- const expressions = grid.sortExpressions;
249
- return fetch('/api/users/sort', {
250
- method: 'POST',
251
- body: JSON.stringify(expressions),
252
- }).then((r) => r.json());
253
- },
254
- filter: async ({ data, grid }) => {
255
- const expressions = grid.filterExpressions;
256
- return fetch('/api/users/filter', {
257
- method: 'POST',
258
- body: JSON.stringify(expressions),
259
- }).then((r) => r.json());
260
- },
261
- };
223
+ grid.filter({ key: 'subscribed', condition: 'true' }); // unary — no searchTerm
262
224
  ```
263
225
 
264
- You can supply just `sort`, just `filter`, or both. Anything you don't override falls back to the in-memory pipeline.
226
+ ### Cell template
265
227
 
266
- ---
228
+ Receives `ApexCellContext<T, K>` (`{ parent, row, column, value, commit? }`). Must return a Lit `TemplateResult`:
229
+
230
+ ```ts
231
+ import { html } from 'lit';
232
+
233
+ { key: 'name', cellTemplate: ({ value, row }) => html`<strong>${value}</strong>` }
234
+ ```
267
235
 
268
- ## 6. Programmatic Sort & Filter
236
+ ### Auto-generated columns — the 3-line demo
269
237
 
270
238
  ```ts
271
- import { StringOperands } from 'apex-grid';
272
-
273
- // Single sort
274
- grid.sort({ key: 'name', direction: 'ascending' });
275
-
276
- // Multiple (multi-column) sort — works only when sortConfiguration.multiple is true
277
- grid.sort([
278
- { key: 'subscribed', direction: 'descending' },
279
- { key: 'age', direction: 'ascending' },
280
- ]);
281
-
282
- // Filter — by operand name
283
- grid.filter({ key: 'name', condition: 'contains', searchTerm: 'ad' });
284
-
285
- // Filter — by operand object (typed)
286
- grid.filter({ key: 'name', condition: StringOperands.contains, searchTerm: 'ad' });
287
-
288
- // Multiple filters with AND/OR
289
- grid.filter([
290
- { key: 'age', condition: 'greaterThan', searchTerm: 30 },
291
- { key: 'age', condition: 'lessThan', searchTerm: 50, criteria: 'and' },
292
- ]);
293
-
294
- // Clear
295
- grid.clearSort(); // all columns
296
- grid.clearSort('name'); // just `name`
297
- grid.clearFilter();
298
- grid.clearFilter('name');
239
+ render(html`<apex-grid auto-generate .data=${data}></apex-grid>`, document.getElementById('app')!);
299
240
  ```
300
241
 
301
- `SortExpression<T>` and `FilterExpression<T>` are exported from `apex-grid` for typed arrays.
242
+ Auto-generates columns from `Object.keys(data[0])` and runtime types. `sort` / `filter` default to `false`, so the auto path is read-only. To re-trigger after a data swap, reset `grid.columns = []` first.
243
+
244
+ For full column patterns — derived columns, header/editor templates, per-column styling — see `references/columns-and-templates.md`.
302
245
 
303
246
  ---
304
247
 
305
- ## 7. Lifecycle Pattern
248
+ ## 5. Lifecycle & API
249
+
250
+ ### Lifecycle pattern
306
251
 
307
252
  ```ts
308
- // 1. Register the custom element ONCE at app startup
309
- import 'apex-grid/define';
310
- // — or —
311
- import { ApexGrid } from 'apex-grid';
312
- ApexGrid.register();
253
+ import { setup } from 'apex-grid';
254
+ setup(); // register + host sizing; no theme import
313
255
 
314
- // 2. Stamp the element into the DOM
315
256
  import { html, render } from 'lit';
316
257
  render(html`<apex-grid></apex-grid>`, document.getElementById('app')!);
317
258
 
318
- // 3. Set properties (after the element is in the DOM)
319
259
  const grid = document.querySelector('apex-grid')!;
320
- grid.columns = [...];
321
- grid.data = [...];
260
+ grid.columns = columns; // properties, after element is in DOM
261
+ grid.data = data;
322
262
 
323
- // 4. Listen for UI events
324
- grid.addEventListener('sorted', (e) => console.log(e.detail));
263
+ grid.addEventListener('sorted', (e) => {}); // UI events
264
+ grid.sort({ key: 'name', direction: 'ascending' }); // programmatic (silent)
325
265
 
326
- // 5. Optional: programmatic operations
327
- grid.sort({ key: 'name', direction: 'ascending' });
266
+ // No destroy() — removing the element triggers Lit's disconnectedCallback automatically.
267
+ ```
328
268
 
329
- // 6. Removing the element from the DOM tears down its observers automatically.
330
- // There is no destroy() — Lit's disconnectedCallback handles cleanup.
269
+ ### Core properties
270
+
271
+ | Property | Type | Notes |
272
+ |---|---|---|
273
+ | `data` | `T[]` | Setting triggers a pipeline run. |
274
+ | `columns` | `ColumnConfiguration<T>[]` | Each entry merged with column defaults. |
275
+ | `autoGenerate` (attr `auto-generate`) | `boolean` | Ignored if `columns` is non-empty. |
276
+ | `sortConfiguration` | `{ multiple, triState }` | Multi-column + tri-state sorting toggles. |
277
+ | `dataPipelineConfiguration` | `{ sort?, filter?, pagination?, quickFilter? }` | Server-side / async hooks (§ data-pipeline). |
278
+ | `quickFilter` (attr `quick-filter`) | `string` | Global substring search across visible columns. |
279
+ | `showQuickFilter` / `showExport` | `boolean` | Toolbar UI toggles for quick-filter input / export menu. |
280
+ | `columnReordering` (attr `column-reordering`) | `boolean` | Drag-reorder headers; per-column opt-out via `reorderable`. |
281
+ | `columnMenu` | `boolean` | Header kebab-menu button. Default `true`; set `false` to hide. |
282
+ | `columnSeparator` (attr `column-separator`) | `boolean` | Persistent header dividers (also the resize handle). Default `true`; theme via `--ag-header-separator`. |
283
+ | `columnGroups` | `ColumnGroupConfiguration[]` | Spanning headers over contiguous columns (join via `column.group`). See `references/state-and-features.md`. |
284
+ | `localeText` | `GridLocaleText` | Partial locale-key → string overrides; ready-made `esLocale` export. Omitted keys fall back to English. |
285
+ | `rowId` | `(row: T) => string \| number` | Stable row-identity resolver so `getState` selection/expansion survives a data reload. |
286
+ | `displayColumns` *(getter)* | `readonly ColumnConfiguration<T>[]` | Columns in visual render order (start-pinned → unpinned → end-pinned). |
287
+ | `hasColumnGroups` / `columnGroupDepth` *(getters)* | `boolean` / `number` | Whether a group header row renders / its depth (0–1). |
288
+ | `pinnedRows` *(getter)* | `{ top: T[]; bottom: T[] }` | Currently pinned rows per band. |
289
+ | `canUndo` / `canRedo` *(getters)* | `boolean` | Undo/redo availability (needs `editing.history.enabled`). |
290
+ | `exportFormats` *(getter)* | `readonly { id, label }[]` | Export menu formats (CSV in the community grid). |
291
+ | `sortExpressions` / `filterExpressions` | `…[]` (get/set) | Current state. To clear, use `clearSort()` / `clearFilter()`. |
292
+ | `selectedRows` / `expandedRows` | `T[]` (get/set) | Snapshots; set to replace (goes through cancellable events). |
293
+ | `rows` *(getter)* | `ApexGridRow<T>[]` | **Currently rendered only** (virtualized). |
294
+ | `dataView` *(getter)* | `readonly T[]` | Full post-filter/sort data. |
295
+ | `pageItems` *(getter)* | `readonly T[]` | Current page slice (= `dataView` if no pagination). |
296
+ | `totalItems` / `pageCount` *(getters)* | `number` | Row / page totals. |
297
+ | `page` / `pageSize` | `number` (get/set) | Current page (0-based) / page size. |
298
+
299
+ ### Feature config properties (opt-in)
300
+
301
+ | Property | Shape |
302
+ |---|---|
303
+ | `editing` | `{ enabled, mode?: 'cell' \| 'row', trigger?: 'click' \| 'doubleClick', history?: { enabled, stackSize? } }` — plus per-column `editable: true` |
304
+ | `selection` | `{ enabled, mode?: 'single' \| 'multiple', showCheckboxColumn? }` |
305
+ | `pagination` | `{ enabled, mode?: 'local' \| 'remote', page?, pageSize?, pageSizeOptions?, totalItems? }` |
306
+ | `tree` | `{ enabled, getDataPath: (row) => string[], groupColumnKey?, defaultExpanded?, childIndent? }` — flat data, hierarchy derived |
307
+ | `expansion` | `{ enabled, detailTemplate: (ctx) => TemplateResult, isExpandable?, showToggleColumn? }` — master-detail |
308
+ | `rowPinning` | `{ enabled }` — sticky top/bottom bands via `pinRow()` / `unpinRow()` |
309
+ | `rowReordering` | `{ enabled, applyToData?, handle? }` — drag/keyboard reorder + `moveRow()`; manual order is mutually exclusive with sort |
310
+
311
+ ```ts
312
+ grid.editing = { enabled: true, mode: 'cell', trigger: 'doubleClick', history: { enabled: true } };
313
+ grid.selection = { enabled: true, mode: 'multiple', showCheckboxColumn: true };
314
+ grid.pagination = { enabled: true, pageSize: 25 };
315
+ grid.rowPinning = { enabled: true };
316
+ grid.rowReordering = { enabled: true };
331
317
  ```
332
318
 
319
+ Declarative validators (`column.validators`), column groups (`columnGroups`), undo/redo, and state persistence (`getState` / `setState` / `getSchema`) are covered in `references/state-and-features.md`.
320
+
321
+ ### Methods
322
+
323
+ | Method | Description |
324
+ |---|---|
325
+ | `sort(expr \| expr[])` / `filter(expr \| expr[])` | Programmatic. **No events.** |
326
+ | `clearSort(key?)` / `clearFilter(key?)` | Clear all, or one column's, sort/filter state. |
327
+ | `getColumn(keyOrIndex)` | Find a column. |
328
+ | `updateColumns(col \| col[])` | Merge new fields into existing columns by `key`. Triggers a pipeline run. |
329
+ | `moveColumn(fromKey, toKey, position?)` | Reorder a column (within its pinning group). |
330
+ | `pinColumn(key, 'start' \| 'end' \| null)` / `unpinColumn(key)` | Programmatic column pinning → `Promise<boolean>`; emits `columnPinning`/`columnPinned`. |
331
+ | `exportToCSV(options?)` / `exportAs(formatId, options?)` | Export the data. CSV is built in. `options.source`: `'view'`(def)/`'page'`/`'selected'`/`'all'`. |
332
+ | pagination | `gotoPage(n)`, `setPageSize(n)`, `nextPage()`, `previousPage()`, `firstPage()`, `lastPage()` → `Promise<boolean>`; emit `pageChanging`/`pageChanged`. |
333
+ | `setQuickFilter(value)` | Apply global search (`''` clears) → `Promise<boolean>`; emits `quickFilterChanging`/`quickFilterChanged`. |
334
+ | selection | `selectRow`, `deselectRow`, `toggleRowSelection`, `selectAllRows`, `clearSelection`, `isRowSelected` |
335
+ | expansion | `expandRow`, `collapseRow`, `toggleRowExpansion`, `expandAllRows`, `collapseAllRows`, `isRowExpanded` |
336
+ | tree | `expandTreeRow`, `collapseTreeRow`, `toggleTreeRow`, `expandAllTreeRows`, `collapseAllTreeRows`, `isTreeRowExpanded` |
337
+ | row pinning | `pinRow(row, 'top' \| 'bottom')`, `unpinRow(row)` (needs `rowPinning.enabled`) |
338
+ | row reorder | `moveRow(from, to, position?)` (needs `rowReordering.enabled`) |
339
+ | editing | `commitEdit()`, `cancelEdit()` (used with `mode: 'row'`) |
340
+ | undo/redo | `undo()`, `redo()`, `clearHistory()` (needs `editing.history.enabled`) |
341
+ | state | `getState(options?)` → `GridState`, `setState(partial, options?)` → `SetStateResult`, `getSchema()` → `GridSchema` |
342
+ | `ApexGrid.register()` / `ApexGrid.tagName` | Static element registration / `'apex-grid'`. |
343
+
344
+ ### Events — UI-initiated only
345
+
346
+ Every event fires only for operations driven through the grid's own UI; programmatic API calls are silent. The `*-ing` events are **cancellable** (call `event.preventDefault()`); the `*-ed` events are notifications. `event.detail` carries the relevant payload.
347
+
348
+ | Pairs (`-ing` cancellable / `-ed` after) | Triggered by |
349
+ |---|---|
350
+ | `sorting` / `sorted` | header sort |
351
+ | `filtering` / `filtered` | filter-row change |
352
+ | `quickFilterChanging` / `quickFilterChanged` | quick-filter input |
353
+ | `pageChanging` / `pageChanged` | paginator |
354
+ | `columnPinning` / `columnPinned` | pin/unpin |
355
+ | `columnMoving` / `columnMoved` | header drag reorder |
356
+ | `cellValueChanging` / `cellValueChanged` | inline edit commit |
357
+ | `rowEditStarted` / `rowEditEnded` | row-edit session (`mode: 'row'`) |
358
+ | `rowSelecting` / `rowSelected` | row selection |
359
+ | `rowExpanding` / `rowExpanded` | master-detail expand |
360
+ | `treeRowExpanding` / `treeRowExpanded` | tree-row expand |
361
+ | `rowPinning` / `rowPinned` | row pin / unpin (also fires for `pinRow()`/`unpinRow()`) |
362
+ | `rowMoving` / `rowMoved` | row reorder (also fires for `moveRow()`) |
363
+
364
+ **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).
365
+
366
+ Single-shot events (no `-ing` pair):
367
+
368
+ | Event | `event.detail` | Fires when |
369
+ |---|---|---|
370
+ | `cellValidationFailed` | `{ key, rowIndex, data, value, errors: string[] }` | a candidate value is rejected by `column.validators` (keeps editor open) |
371
+ | `historyChanged` | `{ canUndo, canRedo }` | undo/redo stacks change (record / undo / redo / clear) |
372
+ | `stateChanged` | `{ state: GridState }` | restorable state changes (debounced; UI or programmatic incl. `setState`) |
373
+
374
+ 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`.
375
+
333
376
  ---
334
377
 
335
- ## 8. Pitfalls — ❌ Wrong vs ✅ Correct
378
+ ## 6. Pitfalls — ❌ Wrong vs ✅ Correct
336
379
 
337
- ### 1. Treating it like a class-based JS API
338
- ❌ `const grid = new ApexGrid({ container: '#app', columns, data })` — this is a custom element, not a constructor.
339
- ✅ Render `<apex-grid .data=${data} .columns=${columns}></apex-grid>` and let the DOM instantiate it.
380
+ ### 1. Treating it like a class API
381
+ ❌ `const grid = new ApexGrid({ container: '#app', columns, data })`
382
+ ✅ Render `<apex-grid .data=${data} .columns=${columns}></apex-grid>`.
340
383
 
341
384
  ### 2. Forgetting to register the element
342
- ❌ Importing `ApexGrid` but never calling `register()` — `<apex-grid>` renders as an inert tag.
343
- ✅ `import 'apex-grid/define'` once, or `ApexGrid.register()` at app startup.
385
+ ❌ Importing `ApexGrid` but never registering.
386
+ ✅ `setup()` (or `import 'apex-grid/define'`) once at app startup.
344
387
 
345
- ### 3. Wrong package name
346
- ❌ `import { ApexGrid } from 'apexgrid'` — the package is `apex-grid` (with a hyphen).
347
- ✅ `import { ApexGrid } from 'apex-grid'`.
388
+ ### 3. Importing a theme CSS that no longer exists
389
+ ❌ `import 'igniteui-webcomponents/themes/light/bootstrap.css'` + `configureTheme('bootstrap')` — obsolete; does not affect the grid.
390
+ ✅ Nothing to import — the grid styles itself; retheme via `--ag-*` CSS variables. The only required CSS is a host **height**.
348
391
 
349
392
  ### 4. Stringified `columns` attribute
350
- ❌ `<apex-grid columns="[...]">` — attributes are strings; `columns` is an object array.
351
- ✅ Property binding: `.columns=${columns}` (Lit), `:columns="columns"` (Vue), `[columns]="columns"` (Angular), or `el.columns = columns` (vanilla).
393
+ ❌ `<apex-grid columns="[...]">` — becomes the literal string `"[object Object],..."`.
394
+ ✅ Property binding: `.columns=${columns}` / `:columns.prop="columns"` / `[columns]="columns"` / `el.columns = columns`.
352
395
 
353
396
  ### 5. `column.key` not a real key of `T`
354
- ❌ `{ key: 'fullName' }` when `User` has only `firstName` / `lastName`.
355
- ✅ Either rename the field on `User` or compute it via `cellTemplate`: `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\``.
397
+ ❌ `{ key: 'fullName' }` when `User` has only `firstName` + `lastName`.
398
+ ✅ Compute via template: `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\``.
356
399
 
357
- ### 6. Returning a string from `cellTemplate`
400
+ ### 6. String from `cellTemplate`
358
401
  ❌ `cellTemplate: ({ value }) => \`<strong>\${value}</strong>\`` — renders as escaped text.
359
402
  ✅ `cellTemplate: ({ value }) => html\`<strong>\${value}</strong>\``.
360
403
 
361
- ### 7. Expecting events from programmatic calls
362
- ❌ Listening for `sorting` after calling `grid.sort(...)` — never fires.
363
- ✅ The UI-initiated path fires events; the programmatic path is silent. If you need a side-effect on every change, watch `grid.dataView` or wrap the call yourself.
364
-
365
- ### 8. `sort: true` without setting `type`
366
- ❌ Sorting numbers as strings: `{ key: 'age', sort: true }` with no `type`.
367
- ✅ `{ key: 'age', type: 'number', sort: true }` — gets the correct comparer and filter operands.
368
-
369
- ### 9. Looking for a `'date'` column type
370
- ❌ `{ key: 'created', type: 'date' }` — there is no `'date'` type.
371
- ✅ Store dates as numbers (timestamps) with `type: 'number'`, or as ISO strings with a custom `sort.comparer` and a `cellTemplate` that formats:
372
- ```ts
373
- {
374
- key: 'created',
375
- type: 'string',
376
- sort: { comparer: (a, b) => a.localeCompare(b) },
377
- cellTemplate: ({ value }) => html`${new Date(value).toLocaleDateString()}`,
378
- }
379
- ```
380
-
381
- ### 10. Calling `clearSort()` to flush an empty array
382
- ❌ `grid.sortExpressions = []` — the setter is a no-op for empty arrays.
383
- ✅ `grid.clearSort()`.
404
+ ### 7. Numbers sorted as strings
405
+ ❌ `{ key: 'age', sort: true }` (default `type: 'string'`) — "10" sorts before "9".
406
+ ✅ `{ key: 'age', type: 'number', sort: true }`.
384
407
 
385
- ### 11. Querying all rows
386
- ❌ `grid.rows.length === grid.data.length` — false; rows are virtualized.
387
- ✅ Use `grid.totalItems` for the post-pipeline count, or `grid.dataView` for the post-pipeline rows.
408
+ ### 8. Reaching for a custom comparer to display dates
409
+ ❌ `{ key: 'created', type: 'string', sort: { comparer: ... } }` to fake date handling.
410
+ ✅ `{ key: 'created', type: 'date', format: 'medium' }` — a real type; accepts `Date`, ISO strings, or millisecond timestamps and sorts chronologically.
388
411
 
389
- ### 12. Auto-generating after replacing data
390
- ❌ `grid.data = newData` with `autoGenerate: true` and existing columns — the old columns persist.
391
- ✅ Reset first: `grid.columns = []; grid.data = newData;`.
412
+ ### 9. Clearing sort/filter
413
+ ❌ Assuming you must rebuild state to clear it.
414
+ ✅ `grid.clearSort()` / `grid.clearFilter()` (optionally per `key`).
392
415
 
393
- ### 13. Mixing operand keys across types
394
- ❌ `grid.filter({ key: 'age' /* number */, condition: 'contains' /* string operand */ })` — runtime error.
395
- ✅ Match the operand to the column type (or import the typed operand: `NumberOperands.greaterThan`).
396
-
397
- ### 14. Rendering before `register()` in the same task
398
- ❌ Stamping `<apex-grid>` before the registration import has resolved — the element is upgraded later but properties set before upgrade may be lost.
399
- ✅ Import `'apex-grid/define'` at the top of your entry file, then render.
416
+ ### 10. Expecting events from programmatic operations
417
+ ❌ Listening for `sorting` after calling `grid.sort(...)` — never fires.
418
+ ✅ Wrap the programmatic call yourself, or watch `grid.dataView` for any state change.
400
419
 
401
420
  ---
402
421
 
403
- ## 9. Reference Routing Table
422
+ ## 7. Reference Routing Table
404
423
 
405
424
  | Topic | Reference File |
406
425
  |---|---|
407
- | Column configuration, types, templates, auto-generation | `references/columns-and-templates.md` |
408
- | Sort & filter — UI events, programmatic API, expressions, operands | `references/sort-and-filter.md` |
409
- | Server-side data pipeline, async hooks, virtualization notes | `references/data-pipeline.md` |
410
- | Framework integration — Lit, React, Vue, Angular | `references/framework-integration.md` |
426
+ | Column types, templates, auto-generation, per-column styling, header/editor templates | `references/columns-and-templates.md` |
427
+ | Sort & filter — operands, expressions, multi-column, quick-filter, events | `references/sort-and-filter.md` |
428
+ | Server-side data hooks (sort/filter/pagination/quickFilter), virtualization, `dataView` | `references/data-pipeline.md` |
429
+ | Row pinning / reordering, undo-redo, validators, column groups, state & schema (`getState`/`setState`/`getSchema`), localization, export source | `references/state-and-features.md` |
430
+ | End-to-end vanilla JS (no Lit `render`) | `references/vanilla-js.md` |
431
+ | Lit, React, Vue, Angular integration | `references/framework-integration.md` |