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/index.js CHANGED
@@ -17,6 +17,8 @@ export const referenceFiles = [
17
17
  'data-pipeline.md',
18
18
  'framework-integration.md',
19
19
  'sort-and-filter.md',
20
+ 'state-and-features.md',
21
+ 'vanilla-js.md',
20
22
  ];
21
23
 
22
24
  /** Resolve a reference file by its filename. Throws if not in the known list. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apexgrid-skill",
3
- "version": "1.0.0",
3
+ "version": "2.1.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",
@@ -1,5 +1,11 @@
1
1
  # Column Configuration & Lit Templates
2
2
 
3
+ > **Before you start**: this reference assumes the Quickstart setup from `SKILL.md` is done — element registered and the host element sized (e.g. `setup()`). **No theme CSS import is needed**; the grid self-styles via `--ag-*` CSS variables. Without a sized host, no column configuration will produce a visible grid.
4
+
5
+ ## A note on column widths
6
+
7
+ If you omit `width`, the column flex-fills the remaining space. With one un-widthed column among several explicit ones, that column **stretches to fill the grid** — including pushing its sort arrow to the far edge of the viewport, which looks like a phantom column. For predictable layouts, set explicit widths on every column.
8
+
3
9
  ## `ColumnConfiguration<T, K>`
4
10
 
5
11
  Apex Grid is generic over a row type `T extends object`. Every column is typed against a key `K extends keyof T` so the cell template's `value` is correctly inferred.
@@ -7,18 +13,40 @@ Apex Grid is generic over a row type `T extends object`. Every column is typed a
7
13
  ```ts
8
14
  interface BaseColumnConfiguration<T extends object, K extends keyof T = keyof T> {
9
15
  key: K; // REQUIRED — keyof T
10
- type?: 'string' | 'number' | 'boolean'; // default 'string'
16
+ type?: DataType; // default 'string' (see "Column types" below)
11
17
  headerText?: string; // default: String(key)
12
18
  width?: string; // any CSS width unit
13
19
  hidden?: boolean; // default false
14
20
  resizable?: boolean; // default false
21
+ pinned?: 'start' | 'end' | null; // freeze to a side during horizontal scroll
22
+ reorderable?: boolean; // per-column opt-out of drag reorder
23
+ exportable?: boolean; // default true; false omits from CSV export
24
+ group?: string; // id of a columnGroups spanning header (state-and-features.md)
25
+ editable?: boolean; // requires grid editing.enabled
26
+ validators?: Validator<T, K>[]; // edit-time validation (see below)
15
27
  sort?: boolean | { caseSensitive?: boolean; comparer?: (a: T[K], b: T[K]) => number };
16
28
  filter?: boolean | { caseSensitive?: boolean };
17
29
  headerTemplate?: (ctx: ApexHeaderContext<T>) => TemplateResult | unknown;
18
30
  cellTemplate?: (ctx: ApexCellContext<T, K>) => TemplateResult | unknown;
31
+ editorTemplate?: (ctx: ApexEditorContext<T, K>) => TemplateResult | unknown;
32
+ // type-specific (ignored unless `type` matches):
33
+ options?: (V | { value: V; label?: string })[]; // 'select'
34
+ max?: number; // 'rating' (stars, def 5) / 'progress' (full, def 100)
35
+ format?: 'short' | 'medium' | 'long' | 'full'; // 'date' (def 'medium')
36
+ shape?: 'square' | 'circle'; // 'image' (def 'square')
37
+ alt?: string; // 'image' alt text
38
+ currency?: string; // 'currency' ISO 4217 (def 'USD')
39
+ locale?: string; // 'currency' BCP 47 locale
40
+ badgeVariant?: 'gold' | 'brand' | 'neutral' | 'muted' | ((v) => …); // 'badge'
41
+ statusVariant?: 'active' | 'trial' | 'churn' | ((v) => …); // 'status'
42
+ showDelta?: boolean; // 'sparkline' (def true)
19
43
  }
20
44
  ```
21
45
 
46
+ ### Column types (`DataType`)
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`).
49
+
22
50
  Defaults applied on assignment (`DEFAULT_COLUMN_CONFIG`):
23
51
 
24
52
  ```ts
@@ -33,17 +61,10 @@ Defaults applied on assignment (`DEFAULT_COLUMN_CONFIG`):
33
61
  | `'number'` | numeric `<` / `>` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
34
62
  | `'boolean'` | `false < true` | `all`, `true`, `false`, `empty`, `notEmpty` |
35
63
 
36
- There is **no `'date'` type**. For dates:
64
+ The table above lists the three **primitive** types. The presentation types (`'select'`, `'rating'`, `'date'`, `'image'`, `'currency'`, `'avatar'`, `'badge'`, `'progress'`, `'sparkline'`, `'status'`) sort and filter as their underlying value. 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:
37
65
 
38
66
  ```ts
39
- // Option A — store as timestamps
40
- { key: 'createdAt', type: 'number', sort: true, filter: true,
41
- cellTemplate: ({ value }) => html`${new Date(value).toLocaleDateString()}` }
42
-
43
- // Option B — store as ISO strings + custom comparer
44
- { key: 'createdAt', type: 'string',
45
- sort: { comparer: (a, b) => a.localeCompare(b) },
46
- cellTemplate: ({ value }) => html`${new Date(value).toLocaleDateString()}` }
67
+ { key: 'createdAt', type: 'date', format: 'medium', sort: true, filter: true }
47
68
  ```
48
69
 
49
70
  ## Sort configuration per column
@@ -137,16 +158,54 @@ interface ApexHeaderContext<T> {
137
158
  }
138
159
  ```
139
160
 
140
- ## Auto-generation
161
+ ## Column validators (`validators`) — 3.2+
162
+
163
+ `column.validators` run before a candidate value is written (inline edit, row-mode, and bulk paste / fill). Each returns an error `string` (reject) or `null` (pass); all run and every message is collected. A failing commit keeps the editor open, marks the cell `aria-invalid`, and emits `cellValidationFailed`. Requires the column to be `editable` and grid `editing.enabled`.
164
+
165
+ ```ts
166
+ import { required, min, max, pattern, custom } from 'apex-grid';
167
+
168
+ { key: 'age', type: 'number', editable: true, validators: [required(), min(0), max(120)] }
169
+ { key: 'email', editable: true, validators: [pattern(/^\S+@\S+$/, 'Invalid email')] }
170
+ { key: 'code', editable: true,
171
+ validators: [custom((value, ctx) => (value === ctx.data.id ? 'Cannot equal id' : null))] }
172
+ ```
173
+
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 — compose with `required`. See `references/state-and-features.md` for the `Validator` / `ValidatorContext` shapes and the `cellValidationFailed` payload.
175
+
176
+ ## Auto-generation — the fastest demo
177
+
178
+ The shortest possible "just show me a table" example:
141
179
 
142
180
  ```ts
143
- <apex-grid auto-generate .data=${data}></apex-grid>
181
+ import { html, render } from 'lit';
182
+ import { setup } from 'apex-grid';
183
+ setup();
184
+
185
+ const data = [
186
+ { id: 1, name: 'Ada', age: 36 },
187
+ { id: 2, name: 'Carl', age: 62 },
188
+ ];
189
+
190
+ render(
191
+ html`<apex-grid auto-generate .data=${data}></apex-grid>`,
192
+ document.getElementById('app')!,
193
+ );
144
194
  ```
145
195
 
196
+ > Remember the host CSS — `apex-grid { height: 480px; }` — or you'll see no rows even with `auto-generate`.
197
+
198
+ **How it works:**
199
+
146
200
  - Reads `Object.keys(data[0])` and creates a column for each, using the value's runtime type to pick `type`.
147
201
  - **Skipped entirely if `columns` is non-empty.** Set `grid.columns = []` before swapping data to re-trigger.
148
202
  - Late-bound data (e.g. fetched after first render) infers correctly because the watcher re-runs on `data` changes — but only when `columns` is still empty.
149
203
 
204
+ **Caveats:**
205
+
206
+ - `sort` and `filter` default to `false`, so the auto path is read-only. To get sortable / filterable columns from inferred data, run auto-generate first then patch via `grid.updateColumns([{ key: 'name', sort: true, filter: true }, ...])`.
207
+ - Auto-generated columns get no explicit `width` — they flex-fill across the grid. For mixed-type columns that's usually not what you want; assign explicit `columns` instead.
208
+
150
209
  ## Updating columns at runtime
151
210
 
152
211
  ```ts
@@ -186,6 +245,6 @@ The grid renders a drag handle on the right edge of the column header. Minimum r
186
245
  | `{ key: 'fullName' }` when `fullName` isn't in `T` | Add to `T`, or compute via `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\`` |
187
246
  | `cellTemplate: ({ value }) => \`<b>\${value}</b>\`` (string) | `cellTemplate: ({ value }) => html\`<b>\${value}</b>\`` |
188
247
  | `{ key: 'age', sort: true }` (default `type: 'string'`) | Set `type: 'number'` |
189
- | `{ key: 'created', type: 'date' }` | No `'date'` type — use timestamp + `'number'` or ISO + custom `comparer` |
248
+ | `{ key: 'created', type: 'string', sort: { comparer } }` to fake dates | `{ key: 'created', type: 'date', format: 'medium' }` — a real type (accepts `Date`/ISO/timestamp) |
190
249
  | `auto-generate` with existing columns | Reset `grid.columns = []` first |
191
250
  | `updateColumns({ key: 'newField', ... })` to add | `updateColumns` only patches existing — assign a new `columns` array to add |
@@ -2,24 +2,29 @@
2
2
 
3
3
  ## How the pipeline runs
4
4
 
5
- When `data`, `columns`, sort state, or filter state changes, Apex Grid runs an async pipeline:
5
+ When `data`, `columns`, sort state, filter state, the quick-filter value, or the page changes, Apex Grid runs an async pipeline:
6
6
 
7
7
  ```
8
- data ─► (filter) ─► (sort) ─► dataState ─► virtualizer ─► rendered rows
8
+ data ─► (quickFilter) ─► (filter) ─► (sort) ─► (pagination) ─► virtualizer ─► rendered rows
9
9
  ```
10
10
 
11
- By default both stages run in-memory. Override either stage (or both) with `dataPipelineConfiguration`.
11
+ By default every stage runs in-memory. Override any stage with `dataPipelineConfiguration`.
12
12
 
13
13
  ## `dataPipelineConfiguration`
14
14
 
15
15
  ```ts
16
+ type DataPipelineParams<T> = { data: T[]; grid: ApexGrid<T>; type: 'sort' | 'filter' | 'quickFilter' | 'pagination' };
17
+ type DataPipelineHook<T> = (state: DataPipelineParams<T>) => T[] | Promise<T[]>;
18
+
16
19
  interface DataPipelineConfiguration<T extends object> {
17
- sort?: (state: { data: T[]; grid: ApexGrid<T>; type: 'sort' }) => T[] | Promise<T[]>;
18
- filter?: (state: { data: T[]; grid: ApexGrid<T>; type: 'filter' }) => T[] | Promise<T[]>;
20
+ sort?: DataPipelineHook<T>;
21
+ filter?: DataPipelineHook<T>; // column filters
22
+ quickFilter?: DataPipelineHook<T>; // global search (runs before column filter)
23
+ pagination?: DataPipelineHook<T>; // return the page slice; remote: set pagination.totalItems
19
24
  }
20
25
  ```
21
26
 
22
- The grid invokes the matching hook in place of its built-in pipeline stage. Whatever the hook returns becomes the new `dataState` for the next stage (or the final view).
27
+ The grid invokes the matching hook in place of its built-in pipeline stage. Whatever the hook returns becomes the input for the next stage (or the final view). All four hooks are optional — omit a stage to keep it in-memory.
23
28
 
24
29
  ```ts
25
30
  grid.dataPipelineConfiguration = {
@@ -57,7 +62,7 @@ grid.dataPipelineConfiguration = {
57
62
 
58
63
  ### Order of operations
59
64
 
60
- The grid runs **filter first, then sort** — and that ordering is fixed. If your server endpoint sorts and filters together, do both in the `filter` hook (returning the fully sorted+filtered array) and skip the `sort` hook.
65
+ The grid runs the stages in a fixed order: **quick-filter → column filter → sort → pagination**. If your server endpoint does several of these together, do the work in the earliest relevant hook (returning the fully processed array) and omit the later hooks. For server-driven paging, return the current page from the `pagination` hook and set `grid.pagination = { ..., mode: 'remote', totalItems }` so the paginator can compute the page count.
61
66
 
62
67
  ### Hooks fire on every state change
63
68
 
@@ -1,13 +1,46 @@
1
1
  # Framework Integration — Lit, React, Vue, Angular
2
2
 
3
- `apex-grid` is a standard custom element with no first-party framework wrappers. Every framework consumes it the same way: register the tag once, then bind `data` and `columns` as **properties** (not attributes) and listen for `sorting` / `sorted` / `filtering` / `filtered` events.
3
+ `apex-grid` is a standard custom element with no first-party framework wrappers. Every framework consumes it the same way:
4
4
 
5
- ## Lit (recommended)
5
+ 1. **Register the element + size the host once at app startup** — `setup()` does both (registers `<apex-grid>` and adopts a bounded host height). No theme CSS import is needed; the grid self-styles via `--ag-*` variables.
6
+ 2. **Bind `data` and `columns` as properties** (not attributes).
7
+ 3. **Listen for `sorting` / `sorted` / `filtering` / `filtered` (and the other UI) events** as standard DOM events.
6
8
 
7
- The most natural host — Lit `html` templates already understand the `.property` and `@event` binding syntax.
9
+ The `setup()` call ideally lives in your application entry file (`main.ts` / `main.js` / `app.module.ts`) so it runs once, not per component.
10
+
11
+ ---
12
+
13
+ ## App-entry setup (all frameworks)
14
+
15
+ Put this at the top of your application entry point — `src/main.ts` for Vite/Lit/Vue, `src/index.tsx` for React, `src/main.ts` for Angular:
16
+
17
+ ```ts
18
+ import { setup } from 'apex-grid';
19
+ setup(); // registers <apex-grid> + adopts a bounded host height
20
+ ```
21
+
22
+ No theme import needed — the grid self-styles. Retheme by overriding `--ag-*` CSS variables on `apex-grid`. If you prefer manual control over sizing, call `setup({ hostStyles: false })` (or use `import 'apex-grid/define'`) and add your own rule:
23
+
24
+ ```css
25
+ apex-grid {
26
+ height: 480px; /* or whatever fits your layout; + any --ag-* overrides */
27
+ }
28
+ ```
29
+
30
+ The framework examples below assume this app-entry setup is already done.
31
+
32
+ ---
33
+
34
+ ## Lit
35
+
36
+ The most natural host — Lit `html` templates already understand `.property` and `@event` binding syntax.
8
37
 
9
38
  ```ts
10
- import 'apex-grid/define';
39
+ // src/main.ts — done once
40
+ import { setup } from 'apex-grid';
41
+ setup();
42
+
43
+ // component
11
44
  import { html, render } from 'lit';
12
45
  import type { ColumnConfiguration } from 'apex-grid';
13
46
 
@@ -15,11 +48,11 @@ type User = { id: number; name: string; age: number; subscribed: boolean };
15
48
 
16
49
  const data: User[] = [/* … */];
17
50
  const columns: ColumnConfiguration<User>[] = [
18
- { key: 'id', type: 'number', sort: true, filter: true },
19
- { key: 'name', type: 'string', sort: true, filter: true,
51
+ { key: 'id', type: 'number', width: '80px', sort: true, filter: true },
52
+ { key: 'name', type: 'string', width: '240px', sort: true, filter: true,
20
53
  cellTemplate: ({ value }) => html`<strong>${value}</strong>` },
21
- { key: 'age', type: 'number', sort: true, filter: true },
22
- { key: 'subscribed', type: 'boolean', sort: true, filter: true },
54
+ { key: 'age', type: 'number', width: '100px', sort: true, filter: true },
55
+ { key: 'subscribed', type: 'boolean', width: '140px', sort: true, filter: true },
23
56
  ];
24
57
 
25
58
  render(
@@ -33,15 +66,16 @@ render(
33
66
  );
34
67
  ```
35
68
 
69
+ ---
70
+
36
71
  ## React
37
72
 
38
- React 19+ supports custom elements natively, including property binding via `ref` for non-string values. For broad compatibility, use a `useEffect` + `ref` pattern:
73
+ React 19+ supports custom elements natively, including property binding for non-string values. For broad compatibility, use `ref` + `useEffect`:
39
74
 
40
75
  ```tsx
41
76
  import { useEffect, useRef } from 'react';
42
- import 'apex-grid/define';
77
+ import { html } from 'lit'; // required for cellTemplate
43
78
  import type { ApexGrid, ColumnConfiguration } from 'apex-grid';
44
- import { html } from 'lit';
45
79
 
46
80
  type User = { id: number; name: string; age: number; subscribed: boolean };
47
81
 
@@ -53,11 +87,11 @@ export function UsersGrid({ data }: { data: User[] }) {
53
87
  if (!grid) return;
54
88
 
55
89
  grid.columns = [
56
- { key: 'id', type: 'number', sort: true, filter: true },
57
- { key: 'name', type: 'string', sort: true, filter: true,
90
+ { key: 'id', type: 'number', width: '80px', sort: true, filter: true },
91
+ { key: 'name', type: 'string', width: '240px', sort: true, filter: true,
58
92
  cellTemplate: ({ value }) => html`<strong>${value}</strong>` },
59
- { key: 'age', type: 'number', sort: true, filter: true },
60
- { key: 'subscribed', type: 'boolean', sort: true, filter: true },
93
+ { key: 'age', type: 'number', width: '100px', sort: true, filter: true },
94
+ { key: 'subscribed', type: 'boolean', width: '140px', sort: true, filter: true },
61
95
  ];
62
96
 
63
97
  const onSorted = (e: Event) => console.log('sorted', (e as CustomEvent).detail);
@@ -65,20 +99,22 @@ export function UsersGrid({ data }: { data: User[] }) {
65
99
  return () => grid.removeEventListener('sorted', onSorted);
66
100
  }, []);
67
101
 
68
- // Set data whenever it changes
69
102
  useEffect(() => {
70
- if (ref.current) ref.current.data = data;
103
+ if (ref.current) ref.current.data = data; // set property whenever data changes
71
104
  }, [data]);
72
105
 
106
+ // The global `apex-grid { height: ... }` CSS handles sizing.
73
107
  // @ts-expect-error — apex-grid is a custom element
74
- return <apex-grid ref={ref} style={{ height: '500px', display: 'block' }} />;
108
+ return <apex-grid ref={ref} />;
75
109
  }
76
110
  ```
77
111
 
78
- For full TypeScript / JSX intrinsic-element support, declare:
112
+ If you'd rather size per instance instead of globally, replace the global CSS rule with a `style` prop: `style={{ height: '500px', display: 'block' }}`.
113
+
114
+ For full TypeScript / JSX intrinsic-element support:
79
115
 
80
116
  ```ts
81
- // apex-grid.d.ts
117
+ // types/apex-grid.d.ts
82
118
  import type { ApexGrid } from 'apex-grid';
83
119
 
84
120
  declare module 'react' {
@@ -90,24 +126,31 @@ declare module 'react' {
90
126
  }
91
127
  ```
92
128
 
129
+ ---
130
+
93
131
  ## Vue 3
94
132
 
95
- Vue understands property binding for custom elements out of the box. Mark `apex-grid` as a custom element in the compiler config so Vue doesn't try to resolve it as a Vue component:
133
+ ### Vite plugin config
134
+
135
+ Tell the Vue compiler that `apex-grid` is a custom element so it doesn't try to resolve it as a Vue component:
96
136
 
97
137
  ```ts
98
138
  // vite.config.ts
99
139
  export default defineConfig({
100
140
  plugins: [
101
- vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('apex-') } } }),
141
+ vue({
142
+ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith('apex-') } },
143
+ }),
102
144
  ],
103
145
  });
104
146
  ```
105
147
 
148
+ ### Component
149
+
106
150
  ```vue
107
151
  <script setup lang="ts">
108
152
  import { ref, onMounted } from 'vue';
109
- import 'apex-grid/define';
110
- import { html } from 'lit';
153
+ import { html } from 'lit'; // required for cellTemplate
111
154
  import type { ApexGrid, ColumnConfiguration } from 'apex-grid';
112
155
 
113
156
  type User = { id: number; name: string; age: number; subscribed: boolean };
@@ -116,15 +159,15 @@ const grid = ref<ApexGrid<User> | null>(null);
116
159
 
117
160
  const data = ref<User[]>([/* … */]);
118
161
  const columns: ColumnConfiguration<User>[] = [
119
- { key: 'id', type: 'number', sort: true, filter: true },
120
- { key: 'name', type: 'string', sort: true, filter: true,
162
+ { key: 'id', type: 'number', width: '80px', sort: true, filter: true },
163
+ { key: 'name', type: 'string', width: '240px', sort: true, filter: true,
121
164
  cellTemplate: ({ value }) => html`<strong>${value}</strong>` },
122
- { key: 'age', type: 'number', sort: true, filter: true },
123
- { key: 'subscribed', type: 'boolean', sort: true, filter: true },
165
+ { key: 'age', type: 'number', width: '100px', sort: true, filter: true },
166
+ { key: 'subscribed', type: 'boolean', width: '140px', sort: true, filter: true },
124
167
  ];
125
168
 
126
169
  onMounted(() => {
127
- grid.value!.columns = columns;
170
+ grid.value!.columns = columns; // set as property after mount
128
171
  });
129
172
 
130
173
  function onSorted(e: CustomEvent) {
@@ -137,22 +180,21 @@ function onSorted(e: CustomEvent) {
137
180
  ref="grid"
138
181
  :data.prop="data"
139
182
  @sorted="onSorted"
140
- style="height: 500px; display: block;"
141
183
  />
142
184
  </template>
143
185
  ```
144
186
 
145
- > Use `:data.prop="data"` (or set via `ref` in `onMounted`) to bind as a property. Vue's default `:data="data"` sets an attribute (a stringified object) which `apex-grid` will not read.
187
+ > Use `:data.prop="data"` to bind as a property. Vue's default `:data="data"` sets an attribute, which becomes the literal string `"[object Object]"` and the grid renders nothing.
188
+
189
+ ---
146
190
 
147
191
  ## Angular
148
192
 
149
- Add `CUSTOM_ELEMENTS_SCHEMA` to the module / standalone component, then use property binding `[data]="..."` and event binding `(sorted)="..."`:
193
+ Add `CUSTOM_ELEMENTS_SCHEMA` to the component (or module). Use `[data]="..."` for property binding and `(sorted)="..."` for events:
150
194
 
151
195
  ```ts
152
- // app.component.ts
153
196
  import { Component, CUSTOM_ELEMENTS_SCHEMA, OnInit, ViewChild, ElementRef } from '@angular/core';
154
- import 'apex-grid/define';
155
- import { html } from 'lit';
197
+ import { html } from 'lit'; // required for cellTemplate
156
198
  import type { ApexGrid, ColumnConfiguration } from 'apex-grid';
157
199
 
158
200
  type User = { id: number; name: string; age: number; subscribed: boolean };
@@ -165,9 +207,8 @@ type User = { id: number; name: string; age: number; subscribed: boolean };
165
207
  <apex-grid
166
208
  #grid
167
209
  [data]="data"
168
- (sorted)="onSorted($event)"
169
- style="height: 500px; display: block;"
170
- ></apex-grid>
210
+ (sorted)="onSorted($event)">
211
+ </apex-grid>
171
212
  `,
172
213
  })
173
214
  export class UsersGridComponent implements OnInit {
@@ -177,11 +218,11 @@ export class UsersGridComponent implements OnInit {
177
218
 
178
219
  ngOnInit() {
179
220
  this.grid.nativeElement.columns = [
180
- { key: 'id', type: 'number', sort: true, filter: true },
181
- { key: 'name', type: 'string', sort: true, filter: true,
221
+ { key: 'id', type: 'number', width: '80px', sort: true, filter: true },
222
+ { key: 'name', type: 'string', width: '240px', sort: true, filter: true,
182
223
  cellTemplate: ({ value }) => html`<strong>${value}</strong>` },
183
- { key: 'age', type: 'number', sort: true, filter: true },
184
- { key: 'subscribed', type: 'boolean', sort: true, filter: true },
224
+ { key: 'age', type: 'number', width: '100px', sort: true, filter: true },
225
+ { key: 'subscribed', type: 'boolean', width: '140px', sort: true, filter: true },
185
226
  ];
186
227
  }
187
228
 
@@ -191,13 +232,17 @@ export class UsersGridComponent implements OnInit {
191
232
  }
192
233
  ```
193
234
 
235
+ ---
236
+
194
237
  ## Common pitfalls across frameworks
195
238
 
196
239
  | ❌ | ✅ |
197
240
  |---|---|
198
- | Vue: `:data="data"` (attribute, becomes `"[object Object]"`) | `:data.prop="data"` or set via `ref` in `onMounted` |
199
- | React: `<apex-grid columns={[...]}>` before React 19 (passes as attribute) | Use `ref` + `useEffect` to set as property, or React 19+ |
241
+ | Forgetting to register the element | Top-level `import 'apex-grid/define'` once in the app entry |
242
+ | Importing an Ignite UI theme + `configureTheme(name)` | Obsolete — the grid self-styles; retheme via `--ag-*` CSS variables. No import does anything for appearance |
243
+ | Forgetting `setup()` / no host-height rule for `apex-grid` | `setup()` registers + sizes the host. Or `import 'apex-grid/define'` + `apex-grid { height: ... }` (never set `display` — it overrides the component's `:host { display: grid }`) |
244
+ | Vue: `:data="data"` (attribute, becomes `"[object Object]"`) | `:data.prop="data"`, or set via `ref` in `onMounted` |
245
+ | React: `<apex-grid columns={[...]}>` before React 19 | Use `ref` + `useEffect` to set as property |
200
246
  | Angular: missing `CUSTOM_ELEMENTS_SCHEMA` | Add to the component / module schemas |
201
- | All: forgetting to register the element | Top-level `import 'apex-grid/define'` once |
202
- | Lit: writing string templates instead of `html` literals | Always import `html` from `lit` and use tagged literals |
203
- | Treating `cellTemplate` as framework-specific JSX | Always use Lit `html\`...\`` — the template runs inside the web component, not in the host framework |
247
+ | Writing string templates for cells | Always `import { html } from 'lit'` and use `html\`...\`` tagged literals |
248
+ | Treating `cellTemplate` as framework-specific JSX | The template runs inside the web component — only Lit `html` works |