@svgrid/create 2.6.0 → 2.8.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.
Files changed (44) hide show
  1. package/README.md +11 -5
  2. package/index.mjs +486 -466
  3. package/package.json +2 -2
  4. package/templates/headless/README.md +65 -0
  5. package/templates/headless/_gitignore +4 -0
  6. package/templates/headless/_package.json +22 -0
  7. package/templates/headless/index.html +12 -0
  8. package/templates/headless/src/App.svelte +159 -0
  9. package/templates/headless/src/app.css +153 -0
  10. package/templates/headless/src/main.ts +7 -0
  11. package/templates/headless/src/vite-env.d.ts +2 -0
  12. package/templates/headless/svelte.config.js +5 -0
  13. package/templates/headless/tsconfig.json +14 -0
  14. package/templates/headless/vite.config.js +6 -0
  15. package/templates/minimal/src/app.css +1 -1
  16. package/templates/pivot-dashboard/README.md +74 -0
  17. package/templates/pivot-dashboard/_gitignore +23 -0
  18. package/templates/pivot-dashboard/_package.json +27 -0
  19. package/templates/pivot-dashboard/src/app.css +15 -0
  20. package/templates/pivot-dashboard/src/app.d.ts +11 -0
  21. package/templates/pivot-dashboard/src/app.html +22 -0
  22. package/templates/pivot-dashboard/src/lib/drill.ts +141 -0
  23. package/templates/pivot-dashboard/src/lib/facts.ts +81 -0
  24. package/templates/pivot-dashboard/src/lib/theme.svelte.ts +87 -0
  25. package/templates/pivot-dashboard/src/routes/+layout.svelte +79 -0
  26. package/templates/pivot-dashboard/src/routes/+page.server.ts +11 -0
  27. package/templates/pivot-dashboard/src/routes/+page.svelte +138 -0
  28. package/templates/pivot-dashboard/src/routes/DrillRail.svelte +82 -0
  29. package/templates/pivot-dashboard/src/routes/TrendChart.svelte +62 -0
  30. package/templates/pivot-dashboard/tsconfig.json +20 -0
  31. package/templates/pivot-dashboard/vite.config.ts +20 -0
  32. package/templates/sveltekit/README.md +33 -3
  33. package/templates/sveltekit/src/app.css +1 -1
  34. package/templates/sveltekit/src/app.d.ts +10 -2
  35. package/templates/sveltekit/src/hooks.server.ts +49 -0
  36. package/templates/sveltekit/src/lib/server/auth.ts +177 -0
  37. package/templates/sveltekit/src/lib/theme.svelte.ts +1 -1
  38. package/templates/sveltekit/src/routes/+layout.server.ts +10 -0
  39. package/templates/sveltekit/src/routes/+layout.svelte +16 -1
  40. package/templates/sveltekit/src/routes/login/+page.server.ts +51 -0
  41. package/templates/sveltekit/src/routes/login/+page.svelte +55 -0
  42. package/templates/sveltekit/src/routes/logout/+server.ts +24 -0
  43. package/templates/sveltekit/src/routes/people/+page.server.ts +12 -3
  44. package/templates/sveltekit/src/routes/people/+page.svelte +10 -5
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.8.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.7.0"
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
@@ -0,0 +1,74 @@
1
+ # SvGrid pivot dashboard
2
+
3
+ A pivot cube, a linked chart, and a drill-through rail over one fact table -
4
+ the three pieces a reporting screen usually needs, wired so they cannot
5
+ disagree with each other.
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev # http://localhost:5173/
10
+ ```
11
+
12
+ ## What to try
13
+
14
+ 1. **Drag `Channel` into Rows** in the panel on the right. The cube, the chart
15
+ and any open drill all follow, because all three read the same layout.
16
+ 2. **Click any aggregated cell.** The rail opens with the exact rows behind that
17
+ number. The KPI at the top is recomputed from those rows, so it always equals
18
+ the cell you clicked.
19
+ 3. **Click a subtotal, then the grand total.** Same code path - a subtotal
20
+ simply contributes fewer filter terms, so the slice widens. The grand total
21
+ filters on nothing and returns all 1,800 facts.
22
+ 4. **Switch the measure** to `Units` in the Values well. The chart axis, the
23
+ rail KPI and the formatting all switch with it.
24
+ 5. **Click a bar** in the chart to drill that whole dimension value.
25
+
26
+ ## How the drill works
27
+
28
+ A pivot cell is the intersection of a row path and a column path, and the two
29
+ arrive in different shapes:
30
+
31
+ - the **row** path is the chain of ancestors up to the clicked `PivotRow`,
32
+ matched positionally against `layout.rows`;
33
+ - the **column** path is encoded in the column id - `pv__<dim>__<dim>__m<i>`,
34
+ where the trailing `m<i>` indexes into `layout.values`.
35
+
36
+ `src/lib/drill.ts` decodes both into one `{ field: value }` filter and returns
37
+ the facts that match every entry. Because the total is recomputed from those
38
+ same facts rather than read off the grid, the rail and the cube cannot drift
39
+ apart.
40
+
41
+ That module is deliberately free of Svelte and of the grid packages, so you can
42
+ unit-test your own reporting rules against it.
43
+
44
+ ## Where things are
45
+
46
+ | File | Does |
47
+ | --- | --- |
48
+ | `src/lib/facts.ts` | The fact table. Seeded and deterministic. Swap `loadFacts` for your query. |
49
+ | `src/lib/drill.ts` | Pure drill-through: cell -> filter -> facts. No Svelte, no grid imports. |
50
+ | `src/routes/+page.server.ts` | Builds the facts on the server and sends them with the page. |
51
+ | `src/routes/+page.svelte` | The dashboard: designer, chart and rail over one `facts` array. |
52
+ | `src/routes/DrillRail.svelte` | The rail: KPIs plus a grid of the underlying rows. |
53
+ | `src/routes/TrendChart.svelte` | The bar chart. Inline SVG-free DOM, styled with `--sg-*`. |
54
+
55
+ ## Licensing
56
+
57
+ `SvPivotDesigner` is part of `@svgrid/enterprise`, which is commercial. The app
58
+ runs unlicensed - it just nudges - so you can evaluate it before buying. Replace
59
+ the key in `src/routes/+page.svelte`:
60
+
61
+ ```ts
62
+ setLicenseKey('SVENTERPRISE-DEV-DEMO')
63
+ ```
64
+
65
+ Pricing: https://svgrid.com/pricing
66
+
67
+ ## Going to production
68
+
69
+ The facts are generated in-process. Point `loadFacts` at your warehouse and keep
70
+ the aggregation on the server if the fact table is large - the browser should
71
+ receive facts, not a database connection. Everything downstream takes `Fact[]`,
72
+ so nothing else has to change.
73
+
74
+ Full guide: https://svgrid.com/docs/enterprise/pivot/
@@ -0,0 +1,23 @@
1
+ node_modules
2
+
3
+ # Output
4
+ .output
5
+ .vercel
6
+ .netlify
7
+ .wrangler
8
+ /.svelte-kit
9
+ /build
10
+
11
+ # OS
12
+ .DS_Store
13
+ Thumbs.db
14
+
15
+ # Env
16
+ .env
17
+ .env.*
18
+ !.env.example
19
+ !.env.test
20
+
21
+ # Vite
22
+ vite.config.js.timestamp-*
23
+ vite.config.ts.timestamp-*
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "svgrid-pivot-dashboard",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite dev",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "prepare": "svelte-kit sync || echo ''",
11
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
13
+ },
14
+ "devDependencies": {
15
+ "@sveltejs/adapter-auto": "^7.0.1",
16
+ "@sveltejs/kit": "^2.63.0",
17
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
18
+ "svelte": "^5.56.1",
19
+ "svelte-check": "^4.6.0",
20
+ "typescript": "^6.0.3",
21
+ "vite": "^8.0.16"
22
+ },
23
+ "dependencies": {
24
+ "@svgrid/grid": "^2.6.8",
25
+ "@svgrid/enterprise": "^2.6.4"
26
+ }
27
+ }
@@ -0,0 +1,15 @@
1
+ /* SvGrid theme. One of the 20 presets @svgrid/grid ships.
2
+ *
3
+ * Importing it as a stylesheet means the FIRST paint - and the server-rendered
4
+ * HTML - is already themed, before any JavaScript runs. The theme picker in the
5
+ * layout overrides these values at runtime by setting the same --sg-* custom
6
+ * properties on <html>.
7
+ *
8
+ * `npm create @svgrid@latest -- --theme <id>` rewrites the line between the
9
+ * markers, so pick a starting theme at scaffold time if you prefer. */
10
+ /* svgrid-theme:start */
11
+ @import '@svgrid/grid/themes/ember.css';
12
+ /* svgrid-theme:end */
13
+
14
+ * { box-sizing: border-box; }
15
+ body { margin: 0; }
@@ -0,0 +1,11 @@
1
+ // See https://svelte.dev/docs/kit/types#app.d.ts
2
+ declare global {
3
+ namespace App {
4
+ // interface Error {}
5
+ // interface Locals {}
6
+ // interface PageData {}
7
+ // interface Platform {}
8
+ }
9
+ }
10
+
11
+ export {};
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="light">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="text-scale" content="scale" />
7
+ <script>
8
+ // Settle the theme before the first paint. Without this the server-rendered
9
+ // HTML shows one palette and the picker in +layout.svelte swaps it a frame
10
+ // later. With nothing saved yet we follow the OS.
11
+ try {
12
+ var saved = JSON.parse(localStorage.getItem('svgrid-theme') || 'null')
13
+ var fallback = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
14
+ document.documentElement.dataset.theme = (saved && saved.mode) || fallback
15
+ } catch (e) {}
16
+ </script>
17
+ %sveltekit.head%
18
+ </head>
19
+ <body data-sveltekit-preload-data="hover">
20
+ <div style="display: contents">%sveltekit.body%</div>
21
+ </body>
22
+ </html>