@svgrid/create 2.6.0 → 2.7.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/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "commercial",
5
5
  "url": "https://svgrid.com/pricing"
6
6
  },
7
- "version": "2.6.0",
7
+ "version": "2.7.0",
8
8
  "description": "Scaffold a Svelte app powered by SvGrid in one command: npm create @svgrid@latest",
9
9
  "type": "module",
10
10
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "@svgrid/grid": "^2.6.9"
39
+ "@svgrid/grid": "^2.6.17"
40
40
  },
41
41
  "scripts": {
42
42
  "sync-templates": "node sync-templates.mjs"
@@ -0,0 +1,65 @@
1
+ # Headless SvGrid app
2
+
3
+ A [Vite](https://vite.dev) + [Svelte 5](https://svelte.dev) app built on the
4
+ [SvGrid](https://svgrid.com) **engine** rather than its renderer. `createSvGrid`
5
+ runs the row pipeline; the `<table>` in `src/App.svelte` and the stylesheet in
6
+ `src/app.css` are entirely yours.
7
+
8
+ ```bash
9
+ npm install
10
+ npm run dev # http://localhost:5173
11
+ npm run build
12
+ npm run check # svelte-check
13
+ ```
14
+
15
+ ## What's wired up
16
+
17
+ Everything comes from `@svgrid/grid/core` - the headless entry point. It has no
18
+ DOM code, no ARIA, and no CSS, so nothing in this project styles itself.
19
+
20
+ - **Row pipeline** - `coreRowModel` -> `filteredRowModel` -> `sortedRowModel`.
21
+ You opt into the steps you use; the rest never enters the bundle. Add
22
+ `createGroupedRowModel`, `createPaginatedRowModel`, or `createTreeRowModel`
23
+ the same way.
24
+ - **Features** - `tableFeatures({ rowSortingFeature, columnFilteringFeature })`.
25
+ Registering a feature is what makes its state and its row model legal.
26
+ - **Controlled state** - `sorting` and `columnFilters` are plain Svelte 5
27
+ `$state`. The engine reads them and reports changes back through
28
+ `onSortingChange` / `onColumnFiltersChange`; it never writes to them.
29
+ - **Your markup** - `table.getHeaderGroups()` and `table.getRowModel().rows` are
30
+ the whole contract. Render a `<table>`, a list of cards, an SVG, a string for
31
+ an email. The engine has no opinion.
32
+
33
+ ## Where to go next
34
+
35
+ - [Headless overview](https://svgrid.com/docs/help/headless/overview) - what the
36
+ engine gives you and when to reach for it.
37
+ - [Row models](https://svgrid.com/docs/help/headless/row-models) - grouping,
38
+ pagination, tree data.
39
+ - [Virtualization](https://svgrid.com/docs/help/headless/virtualization) - the
40
+ virtualizer exports, for when the row count outgrows a plain `<table>`.
41
+ - [Server-side](https://svgrid.com/docs/help/headless/server-side) - drive the
42
+ pipeline from a `load` function or a Node service.
43
+ - [Controlled state](https://svgrid.com/docs/help/headless/controlled-state) -
44
+ sharing one state object across two views.
45
+
46
+ ## Want the batteries-included grid instead?
47
+
48
+ `<SvGrid>` is the renderer built on this same engine - virtual scrolling,
49
+ Excel-style filters, inline editing, 20 themes:
50
+
51
+ ```bash
52
+ npm create @svgrid@latest my-app -- --template minimal
53
+ ```
54
+
55
+ ## Working with an AI assistant
56
+
57
+ Point it at the SvGrid MCP server so it writes against the real API instead of
58
+ guessing:
59
+
60
+ ```bash
61
+ claude mcp add svgrid -- npx -y @svgrid/mcp
62
+ ```
63
+
64
+ Built with [SvGrid](https://svgrid.com). SvGrid(TM) is a trademark of
65
+ jQWidgets Ltd. This template is MIT-licensed.
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.local
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "sv-grid-app",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "check": "svelte-check --tsconfig ./tsconfig.json"
11
+ },
12
+ "devDependencies": {
13
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
14
+ "svelte": "^5.55.5",
15
+ "svelte-check": "^4.4.6",
16
+ "typescript": "^5.9.2",
17
+ "vite": "^8.0.10"
18
+ },
19
+ "dependencies": {
20
+ "@svgrid/grid": "latest"
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Headless SvGrid app</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,159 @@
1
+ <script lang="ts">
2
+ /**
3
+ * The engine, not the renderer.
4
+ *
5
+ * `createSvGrid` from `@svgrid/grid/core` computes the row model - filtered,
6
+ * then sorted - and hands you rows and header groups. It touches no DOM, ships
7
+ * no CSS, and has no opinion about your markup. Everything below <table> is
8
+ * yours to rewrite.
9
+ *
10
+ * If you'd rather have the batteries-included component, that's
11
+ * `npm create @svgrid@latest my-app -- --template minimal`.
12
+ */
13
+ import {
14
+ createSvGrid,
15
+ createCoreRowModel,
16
+ createFilteredRowModel,
17
+ createSortedRowModel,
18
+ tableFeatures,
19
+ rowSortingFeature,
20
+ columnFilteringFeature,
21
+ type ColumnDef,
22
+ type ColumnFiltersState,
23
+ type SortingState,
24
+ } from '@svgrid/grid/core'
25
+
26
+ type Person = {
27
+ id: number
28
+ name: string
29
+ team: string
30
+ salary: number
31
+ active: boolean
32
+ }
33
+
34
+ // Opt into the features you use. Registering a feature is what makes its
35
+ // state and its row model legal; what you leave out never enters the bundle.
36
+ const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
37
+
38
+ const columns: ColumnDef<typeof features, Person>[] = [
39
+ { field: 'name', header: 'Name' },
40
+ { field: 'team', header: 'Team' },
41
+ { field: 'salary', header: 'Salary' },
42
+ { field: 'active', header: 'Active' },
43
+ ]
44
+
45
+ // Swap for a fetch() in $effect, a load function, a store - the engine only
46
+ // wants an array.
47
+ const data: Person[] = [
48
+ { id: 1, name: 'Ada Lovelace', team: 'Engineering', salary: 145000, active: true },
49
+ { id: 2, name: 'Alan Turing', team: 'Research', salary: 160000, active: true },
50
+ { id: 3, name: 'Grace Hopper', team: 'Engineering', salary: 152000, active: false },
51
+ { id: 4, name: 'Katherine Johnson', team: 'Data', salary: 138000, active: true },
52
+ { id: 5, name: 'Edsger Dijkstra', team: 'Research', salary: 149000, active: false },
53
+ { id: 6, name: 'Barbara Liskov', team: 'Research', salary: 158000, active: true },
54
+ { id: 7, name: 'Margaret Hamilton', team: 'Engineering', salary: 151000, active: true },
55
+ { id: 8, name: 'Donald Knuth', team: 'Data', salary: 141000, active: false },
56
+ ]
57
+
58
+ // Controlled state. The engine never mutates these - it reports what changed
59
+ // through onXxxChange and you decide what to store. $state is what makes the
60
+ // round trip reactive.
61
+ let sorting = $state<SortingState>([{ id: 'salary', desc: true }])
62
+ let columnFilters = $state<ColumnFiltersState>([])
63
+ let query = $state('')
64
+
65
+ $effect(() => {
66
+ columnFilters = query ? [{ id: 'name', value: query }] : []
67
+ })
68
+
69
+ // Rebuilt whenever data or state changes. Recreating the engine is cheap -
70
+ // it's a state machine over your array, not a component tree.
71
+ const table = $derived.by(() =>
72
+ createSvGrid({
73
+ _features: features,
74
+ _rowModels: {
75
+ // The pipeline, in order. Add createGroupedRowModel /
76
+ // createPaginatedRowModel / createTreeRowModel as you need them.
77
+ coreRowModel: createCoreRowModel<Person>(),
78
+ filteredRowModel: createFilteredRowModel<Person>(),
79
+ sortedRowModel: createSortedRowModel<Person>(),
80
+ },
81
+ data,
82
+ columns,
83
+ state: { sorting, columnFilters },
84
+ onSortingChange: (u) => (sorting = typeof u === 'function' ? u(sorting) : u),
85
+ onColumnFiltersChange: (u) => (columnFilters = typeof u === 'function' ? u(columnFilters) : u),
86
+ }),
87
+ )
88
+
89
+ const headerGroups = $derived(table.getHeaderGroups())
90
+ const rows = $derived(table.getRowModel().rows)
91
+
92
+ // Sorting is yours to drive: click cycles asc -> desc -> unsorted.
93
+ function toggleSort(id: string) {
94
+ const current = sorting[0]
95
+ sorting = current?.id !== id ? [{ id, desc: false }] : current.desc ? [] : [{ id, desc: true }]
96
+ }
97
+
98
+ const indicator = (id: string) => (sorting[0]?.id === id ? (sorting[0].desc ? '▼' : '▲') : '')
99
+
100
+ const money = new Intl.NumberFormat('en-US', {
101
+ style: 'currency',
102
+ currency: 'USD',
103
+ maximumFractionDigits: 0,
104
+ })
105
+ </script>
106
+
107
+ <main>
108
+ <header>
109
+ <h1>Headless SvGrid</h1>
110
+ <p>
111
+ <code>createSvGrid</code> sorts and filters. The <code>&lt;table&gt;</code> below is plain
112
+ markup in <code>src/App.svelte</code>, styled by <code>src/app.css</code>. No grid CSS is
113
+ loaded.
114
+ </p>
115
+ </header>
116
+
117
+ <input class="search" placeholder="Filter by name…" bind:value={query} />
118
+
119
+ <table>
120
+ <thead>
121
+ {#each headerGroups as group (group.id)}
122
+ <tr>
123
+ {#each group.headers as header (header.id)}
124
+ <th
125
+ class:numeric={header.column.id === 'salary'}
126
+ aria-sort={sorting[0]?.id === header.column.id
127
+ ? sorting[0].desc
128
+ ? 'descending'
129
+ : 'ascending'
130
+ : 'none'}
131
+ >
132
+ <button type="button" onclick={() => toggleSort(header.column.id)}>
133
+ {header.column.columnDef.header}
134
+ <span class="indicator">{indicator(header.column.id)}</span>
135
+ </button>
136
+ </th>
137
+ {/each}
138
+ </tr>
139
+ {/each}
140
+ </thead>
141
+ <tbody>
142
+ {#each rows as row (row.id)}
143
+ {@const person = row.original as Person}
144
+ <tr>
145
+ <td>{person.name}</td>
146
+ <td>{person.team}</td>
147
+ <td class="numeric">{money.format(person.salary)}</td>
148
+ <td>{person.active ? 'Yes' : 'No'}</td>
149
+ </tr>
150
+ {:else}
151
+ <tr>
152
+ <td colspan="4" class="empty">Nobody matches “{query}”.</td>
153
+ </tr>
154
+ {/each}
155
+ </tbody>
156
+ </table>
157
+
158
+ <p class="count">{rows.length} of {data.length} rows</p>
159
+ </main>
@@ -0,0 +1,153 @@
1
+ /* ---------------------------------------------------------------------------
2
+ * Your stylesheet. All of it.
3
+ *
4
+ * Nothing here comes from @svgrid/grid - there is no preset import, no --sg-*
5
+ * token, no theme to pick. The headless entry point ships an engine and no CSS,
6
+ * so the table below is styled the same way any other table in your app would
7
+ * be. Rename the variables, drop them for Tailwind classes, delete this file
8
+ * and start over: none of it affects sorting or filtering.
9
+ *
10
+ * If you'd rather inherit a themed grid than write one, the other starters use
11
+ * the <SvGrid> renderer and its 20 presets:
12
+ * npm create @svgrid@latest my-app -- --template minimal
13
+ * ------------------------------------------------------------------------- */
14
+
15
+ :root {
16
+ --app-bg: #ffffff;
17
+ --app-surface: #fafaf9;
18
+ --app-fg: #1c1917;
19
+ --app-muted: #78716c;
20
+ --app-border: #e7e5e4;
21
+ --app-accent: #c2410c;
22
+ --app-hover: #f5f5f4;
23
+ color-scheme: light;
24
+ }
25
+
26
+ /* Dark mode follows the OS. Swap this for a data-theme attribute and a toggle
27
+ if you want the choice to be the user's. */
28
+ @media (prefers-color-scheme: dark) {
29
+ :root {
30
+ --app-bg: #1c1917;
31
+ --app-surface: #292524;
32
+ --app-fg: #fafaf9;
33
+ --app-muted: #a8a29e;
34
+ --app-border: #44403c;
35
+ --app-accent: #fb923c;
36
+ --app-hover: #292524;
37
+ color-scheme: dark;
38
+ }
39
+ }
40
+
41
+ body {
42
+ margin: 0;
43
+ min-height: 100vh;
44
+ background: var(--app-bg);
45
+ color: var(--app-fg);
46
+ font-family: system-ui, -apple-system, Segoe UI, sans-serif;
47
+ }
48
+
49
+ main {
50
+ max-width: 720px;
51
+ margin: 3rem auto;
52
+ padding: 0 1rem;
53
+ }
54
+
55
+ h1 {
56
+ margin: 0 0 0.5rem;
57
+ font-size: 1.4rem;
58
+ }
59
+
60
+ header p {
61
+ margin: 0 0 1.5rem;
62
+ color: var(--app-muted);
63
+ line-height: 1.6;
64
+ }
65
+
66
+ code {
67
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
68
+ font-size: 0.9em;
69
+ }
70
+
71
+ .search {
72
+ width: 260px;
73
+ margin-bottom: 1rem;
74
+ padding: 0.45rem 0.7rem;
75
+ border: 1px solid var(--app-border);
76
+ border-radius: 6px;
77
+ background: var(--app-bg);
78
+ color: var(--app-fg);
79
+ font: inherit;
80
+ font-size: 0.9rem;
81
+ }
82
+
83
+ .search:focus-visible {
84
+ outline: 2px solid var(--app-accent);
85
+ outline-offset: 1px;
86
+ }
87
+
88
+ table {
89
+ width: 100%;
90
+ border-collapse: collapse;
91
+ font-size: 0.9rem;
92
+ }
93
+
94
+ th {
95
+ padding: 0;
96
+ background: var(--app-surface);
97
+ border-bottom: 2px solid var(--app-border);
98
+ text-align: left;
99
+ }
100
+
101
+ /* The header is a button so keyboard users can sort too - the engine does not
102
+ care what element calls toggleSort. */
103
+ th button {
104
+ width: 100%;
105
+ padding: 0.6rem 0.75rem;
106
+ border: 0;
107
+ background: none;
108
+ color: inherit;
109
+ font: inherit;
110
+ font-weight: 600;
111
+ text-align: inherit;
112
+ cursor: pointer;
113
+ }
114
+
115
+ th button:hover {
116
+ color: var(--app-accent);
117
+ }
118
+
119
+ th button:focus-visible {
120
+ outline: 2px solid var(--app-accent);
121
+ outline-offset: -2px;
122
+ }
123
+
124
+ .indicator {
125
+ color: var(--app-accent);
126
+ font-size: 0.7em;
127
+ }
128
+
129
+ td {
130
+ padding: 0.6rem 0.75rem;
131
+ border-bottom: 1px solid var(--app-border);
132
+ }
133
+
134
+ tbody tr:hover td {
135
+ background: var(--app-hover);
136
+ }
137
+
138
+ .numeric {
139
+ text-align: right;
140
+ font-variant-numeric: tabular-nums;
141
+ }
142
+
143
+ .empty {
144
+ padding: 1.5rem;
145
+ color: var(--app-muted);
146
+ text-align: center;
147
+ }
148
+
149
+ .count {
150
+ margin-top: 0.75rem;
151
+ color: var(--app-muted);
152
+ font-size: 0.8rem;
153
+ }
@@ -0,0 +1,7 @@
1
+ import { mount } from 'svelte'
2
+ import './app.css'
3
+ import App from './App.svelte'
4
+
5
+ const app = mount(App, { target: document.getElementById('app')! })
6
+
7
+ export default app
@@ -0,0 +1,2 @@
1
+ /// <reference types="svelte" />
2
+ /// <reference types="vite/client" />
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
7
+ "resolveJsonModule": true,
8
+ "isolatedModules": true,
9
+ "skipLibCheck": true,
10
+ "strict": true,
11
+ "noEmit": true
12
+ },
13
+ "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.svelte"]
14
+ }
@@ -0,0 +1,6 @@
1
+ import { svelte } from '@sveltejs/vite-plugin-svelte'
2
+ import { defineConfig } from 'vite'
3
+
4
+ export default defineConfig({
5
+ plugins: [svelte()],
6
+ })
@@ -13,7 +13,7 @@
13
13
  * Token reference: https://svgrid.com/docs/getting-started/5-theme-and-density
14
14
  * ------------------------------------------------------------------------- */
15
15
  /* svgrid-theme:start */
16
- @import '@svgrid/grid/themes/shadcn.css';
16
+ @import '@svgrid/grid/themes/ember.css';
17
17
  /* svgrid-theme:end */
18
18
 
19
19
  /* Which way the page leans, so scrollbars, form controls and the canvas behind
@@ -8,7 +8,7 @@
8
8
  * `npm create @svgrid@latest -- --theme <id>` rewrites the line between the
9
9
  * markers, so pick a starting theme at scaffold time if you prefer. */
10
10
  /* svgrid-theme:start */
11
- @import '@svgrid/grid/themes/tailwind.css';
11
+ @import '@svgrid/grid/themes/ember.css';
12
12
  /* svgrid-theme:end */
13
13
 
14
14
  * { box-sizing: border-box; }
@@ -28,7 +28,7 @@ const STORAGE_KEY = 'svgrid-theme'
28
28
  // so does the OS preference when nobody has pinned a mode. The inline script in
29
29
  // `app.html` settles the same question before the first paint.
30
30
  /* svgrid-initial-theme:start */
31
- export const INITIAL_THEME = 'tailwind'
31
+ export const INITIAL_THEME = 'ember'
32
32
  export const INITIAL_MODE: ThemeMode = 'light'
33
33
  /* svgrid-initial-theme:end */
34
34