apexgrid-skill 1.0.0 → 2.0.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 +274 -291
- package/index.js +1 -0
- package/package.json +1 -1
- package/references/columns-and-templates.md +55 -13
- package/references/data-pipeline.md +12 -7
- package/references/framework-integration.md +92 -47
- package/references/vanilla-js.md +154 -0
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apexgrid-skill",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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,38 @@ 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?:
|
|
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
|
+
editable?: boolean; // requires grid editing.enabled
|
|
15
25
|
sort?: boolean | { caseSensitive?: boolean; comparer?: (a: T[K], b: T[K]) => number };
|
|
16
26
|
filter?: boolean | { caseSensitive?: boolean };
|
|
17
27
|
headerTemplate?: (ctx: ApexHeaderContext<T>) => TemplateResult | unknown;
|
|
18
28
|
cellTemplate?: (ctx: ApexCellContext<T, K>) => TemplateResult | unknown;
|
|
29
|
+
editorTemplate?: (ctx: ApexEditorContext<T, K>) => TemplateResult | unknown;
|
|
30
|
+
// type-specific (ignored unless `type` matches):
|
|
31
|
+
options?: (V | { value: V; label?: string })[]; // 'select'
|
|
32
|
+
max?: number; // 'rating' (stars, def 5) / 'progress' (full, def 100)
|
|
33
|
+
format?: 'short' | 'medium' | 'long' | 'full'; // 'date' (def 'medium')
|
|
34
|
+
shape?: 'square' | 'circle'; // 'image' (def 'square')
|
|
35
|
+
alt?: string; // 'image' alt text
|
|
36
|
+
currency?: string; // 'currency' ISO 4217 (def 'USD')
|
|
37
|
+
locale?: string; // 'currency' BCP 47 locale
|
|
38
|
+
badgeVariant?: 'gold' | 'brand' | 'neutral' | 'muted' | ((v) => …); // 'badge'
|
|
39
|
+
statusVariant?: 'active' | 'trial' | 'churn' | ((v) => …); // 'status'
|
|
40
|
+
showDelta?: boolean; // 'sparkline' (def true)
|
|
19
41
|
}
|
|
20
42
|
```
|
|
21
43
|
|
|
44
|
+
### Column types (`DataType`)
|
|
45
|
+
|
|
46
|
+
`'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`).
|
|
47
|
+
|
|
22
48
|
Defaults applied on assignment (`DEFAULT_COLUMN_CONFIG`):
|
|
23
49
|
|
|
24
50
|
```ts
|
|
@@ -33,17 +59,10 @@ Defaults applied on assignment (`DEFAULT_COLUMN_CONFIG`):
|
|
|
33
59
|
| `'number'` | numeric `<` / `>` | `equals`, `doesNotEqual`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `empty`, `notEmpty` |
|
|
34
60
|
| `'boolean'` | `false < true` | `all`, `true`, `false`, `empty`, `notEmpty` |
|
|
35
61
|
|
|
36
|
-
|
|
62
|
+
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
63
|
|
|
38
64
|
```ts
|
|
39
|
-
|
|
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()}` }
|
|
65
|
+
{ key: 'createdAt', type: 'date', format: 'medium', sort: true, filter: true }
|
|
47
66
|
```
|
|
48
67
|
|
|
49
68
|
## Sort configuration per column
|
|
@@ -137,16 +156,39 @@ interface ApexHeaderContext<T> {
|
|
|
137
156
|
}
|
|
138
157
|
```
|
|
139
158
|
|
|
140
|
-
## Auto-generation
|
|
159
|
+
## Auto-generation — the fastest demo
|
|
160
|
+
|
|
161
|
+
The shortest possible "just show me a table" example:
|
|
141
162
|
|
|
142
163
|
```ts
|
|
143
|
-
|
|
164
|
+
import { html, render } from 'lit';
|
|
165
|
+
import { setup } from 'apex-grid';
|
|
166
|
+
setup();
|
|
167
|
+
|
|
168
|
+
const data = [
|
|
169
|
+
{ id: 1, name: 'Ada', age: 36 },
|
|
170
|
+
{ id: 2, name: 'Carl', age: 62 },
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
render(
|
|
174
|
+
html`<apex-grid auto-generate .data=${data}></apex-grid>`,
|
|
175
|
+
document.getElementById('app')!,
|
|
176
|
+
);
|
|
144
177
|
```
|
|
145
178
|
|
|
179
|
+
> Remember the host CSS — `apex-grid { height: 480px; }` — or you'll see no rows even with `auto-generate`.
|
|
180
|
+
|
|
181
|
+
**How it works:**
|
|
182
|
+
|
|
146
183
|
- Reads `Object.keys(data[0])` and creates a column for each, using the value's runtime type to pick `type`.
|
|
147
184
|
- **Skipped entirely if `columns` is non-empty.** Set `grid.columns = []` before swapping data to re-trigger.
|
|
148
185
|
- 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
186
|
|
|
187
|
+
**Caveats:**
|
|
188
|
+
|
|
189
|
+
- `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 }, ...])`.
|
|
190
|
+
- 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.
|
|
191
|
+
|
|
150
192
|
## Updating columns at runtime
|
|
151
193
|
|
|
152
194
|
```ts
|
|
@@ -186,6 +228,6 @@ The grid renders a drag handle on the right edge of the column header. Minimum r
|
|
|
186
228
|
| `{ key: 'fullName' }` when `fullName` isn't in `T` | Add to `T`, or compute via `cellTemplate: ({ row }) => html\`${row.firstName} ${row.lastName}\`` |
|
|
187
229
|
| `cellTemplate: ({ value }) => \`<b>\${value}</b>\`` (string) | `cellTemplate: ({ value }) => html\`<b>\${value}</b>\`` |
|
|
188
230
|
| `{ key: 'age', sort: true }` (default `type: 'string'`) | Set `type: 'number'` |
|
|
189
|
-
| `{ key: 'created', type: '
|
|
231
|
+
| `{ key: 'created', type: 'string', sort: { comparer } }` to fake dates | `{ key: 'created', type: 'date', format: 'medium' }` — a real type (accepts `Date`/ISO/timestamp) |
|
|
190
232
|
| `auto-generate` with existing columns | Reset `grid.columns = []` first |
|
|
191
233
|
| `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,
|
|
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) ─►
|
|
8
|
+
data ─► (quickFilter) ─► (filter) ─► (sort) ─► (pagination) ─► virtualizer ─► rendered rows
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
By default
|
|
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?:
|
|
18
|
-
filter?:
|
|
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
|
|
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
|
|
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:
|
|
3
|
+
`apex-grid` is a standard custom element with no first-party framework wrappers. Every framework consumes it the same way:
|
|
4
4
|
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
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',
|
|
19
|
-
{ key: 'name',
|
|
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',
|
|
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
|
|
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 '
|
|
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}
|
|
108
|
+
return <apex-grid ref={ref} />;
|
|
75
109
|
}
|
|
76
110
|
```
|
|
77
111
|
|
|
78
|
-
|
|
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
|
-
|
|
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({
|
|
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 '
|
|
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"`
|
|
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
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
|
199
|
-
|
|
|
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
|
-
|
|
|
202
|
-
|
|
|
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 |
|
|
@@ -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 |
|