@svgrid/create 1.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.
Files changed (31) hide show
  1. package/README.md +49 -0
  2. package/index.mjs +225 -0
  3. package/package.json +40 -0
  4. package/templates/admin-dashboard/README.md +77 -0
  5. package/templates/admin-dashboard/_env.example +3 -0
  6. package/templates/admin-dashboard/_gitignore +9 -0
  7. package/templates/admin-dashboard/_npmrc +1 -0
  8. package/templates/admin-dashboard/_package.json +27 -0
  9. package/templates/admin-dashboard/src/app.css +18 -0
  10. package/templates/admin-dashboard/src/app.d.ts +12 -0
  11. package/templates/admin-dashboard/src/app.html +16 -0
  12. package/templates/admin-dashboard/src/lib/StatCard.svelte +20 -0
  13. package/templates/admin-dashboard/src/lib/data.ts +84 -0
  14. package/templates/admin-dashboard/src/lib/types.ts +25 -0
  15. package/templates/admin-dashboard/src/routes/+layout.svelte +79 -0
  16. package/templates/admin-dashboard/src/routes/+layout.ts +4 -0
  17. package/templates/admin-dashboard/src/routes/+page.svelte +87 -0
  18. package/templates/admin-dashboard/src/routes/customers/+page.svelte +75 -0
  19. package/templates/admin-dashboard/src/routes/orders/+page.svelte +89 -0
  20. package/templates/admin-dashboard/static/favicon.svg +10 -0
  21. package/templates/admin-dashboard/svelte.config.js +14 -0
  22. package/templates/admin-dashboard/tsconfig.json +14 -0
  23. package/templates/admin-dashboard/vite.config.ts +7 -0
  24. package/templates/minimal/README.md +21 -0
  25. package/templates/minimal/_gitignore +4 -0
  26. package/templates/minimal/_package.json +19 -0
  27. package/templates/minimal/index.html +12 -0
  28. package/templates/minimal/src/App.svelte +61 -0
  29. package/templates/minimal/src/main.js +6 -0
  30. package/templates/minimal/svelte.config.js +5 -0
  31. package/templates/minimal/vite.config.js +6 -0
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # @svgrid/create
2
+
3
+ Scaffold a [Svelte](https://svelte.dev) app powered by
4
+ [SvGrid](https://www.svgrid.com) - the modern Svelte 5 data grid - in one
5
+ command.
6
+
7
+ ```bash
8
+ npm create sv-grid@latest
9
+ # or
10
+ pnpm create sv-grid
11
+ # or
12
+ yarn create sv-grid
13
+ ```
14
+
15
+ Interactive by default. Or pass a directory and template directly:
16
+
17
+ ```bash
18
+ npm create sv-grid@latest my-app -- --template admin-dashboard
19
+ pnpm create sv-grid my-app -t minimal
20
+ ```
21
+
22
+ ## Templates
23
+
24
+ | Template | Stack | Best for |
25
+ | --- | --- | --- |
26
+ | `minimal` | Vite + Svelte 5 + SvGrid | Dropping a grid into something quickly |
27
+ | `admin-dashboard` | SvelteKit + Tailwind + SvGrid, deploy to Vercel | A real dashboard / internal tool |
28
+
29
+ ## Options
30
+
31
+ | Flag | Description |
32
+ | --- | --- |
33
+ | `--template`, `-t` | `minimal` or `admin-dashboard` |
34
+ | `--force`, `-f` | Scaffold into a non-empty directory |
35
+ | `--help`, `-h` | Show usage |
36
+
37
+ Then:
38
+
39
+ ```bash
40
+ cd my-app
41
+ npm install
42
+ npm run dev
43
+ ```
44
+
45
+ All templates use the free MIT `@svgrid/grid` core. Add
46
+ [`@svgrid/enterprise`](https://www.svgrid.com/pricing) for Excel/PDF export, import,
47
+ print, pivot, and AI helpers.
48
+
49
+ SvGrid(TM) is a trademark of jQWidgets Ltd. This package is MIT-licensed.
package/index.mjs ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ // @svgrid/create - scaffold a Svelte app powered by SvGrid.
3
+ //
4
+ // npm create sv-grid@latest # interactive
5
+ // pnpm create sv-grid # interactive
6
+ // npm create sv-grid@latest my-app -- --template admin-dashboard
7
+ // npm create sv-grid@latest my-app -- -t minimal
8
+ //
9
+ // Zero runtime dependencies - Node built-ins only. Copies a bundled template,
10
+ // renames `_`-prefixed dotfiles, and rewrites the project name.
11
+
12
+ import { cp, mkdir, readdir, rename, stat, readFile, writeFile } from 'node:fs/promises'
13
+ import { existsSync } from 'node:fs'
14
+ import { dirname, join, basename, resolve, isAbsolute } from 'node:path'
15
+ import { fileURLToPath } from 'node:url'
16
+ import { createInterface } from 'node:readline/promises'
17
+ import { stdin, stdout } from 'node:process'
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url))
20
+
21
+ const TEMPLATES = {
22
+ minimal: {
23
+ label: 'Minimal - Vite + Svelte 5 + SvGrid, one page',
24
+ bundled: join(__dirname, 'templates', 'minimal'),
25
+ },
26
+ 'admin-dashboard': {
27
+ label: 'Admin dashboard - SvelteKit shell, multiple grids, deploy to Vercel',
28
+ bundled: join(__dirname, 'templates', 'admin-dashboard'),
29
+ // When running from the monorepo before `prepack` has synced the bundled
30
+ // copy, fall back to the canonical source.
31
+ fallback: join(__dirname, '..', '..', 'templates', 'sveltekit-admin-dashboard'),
32
+ },
33
+ }
34
+
35
+ const RENAME_BACK = new Map([
36
+ ['_gitignore', '.gitignore'],
37
+ ['_npmrc', '.npmrc'],
38
+ ['_env.example', '.env.example'],
39
+ ['_package.json', 'package.json'],
40
+ ])
41
+
42
+ const c = {
43
+ reset: '\x1b[0m',
44
+ bold: '\x1b[1m',
45
+ dim: '\x1b[2m',
46
+ green: '\x1b[32m',
47
+ cyan: '\x1b[36m',
48
+ red: '\x1b[31m',
49
+ yellow: '\x1b[33m',
50
+ }
51
+ const color = stdout.isTTY ? (k, s) => `${c[k]}${s}${c.reset}` : (_k, s) => s
52
+
53
+ function parseArgs(argv) {
54
+ const args = { _: [], template: null, force: false, help: false }
55
+ for (let i = 0; i < argv.length; i++) {
56
+ const a = argv[i]
57
+ if (a === '--help' || a === '-h') args.help = true
58
+ else if (a === '--force' || a === '-f') args.force = true
59
+ else if (a === '--template' || a === '-t') args.template = argv[++i]
60
+ else if (a.startsWith('--template=')) args.template = a.slice('--template='.length)
61
+ else if (!a.startsWith('-')) args._.push(a)
62
+ }
63
+ return args
64
+ }
65
+
66
+ function printHelp() {
67
+ stdout.write(`
68
+ ${color('bold', '@svgrid/create')} - scaffold a Svelte app powered by SvGrid
69
+
70
+ ${color('bold', 'Usage')}
71
+ npm create sv-grid@latest [dir] -- [--template <name>] [--force]
72
+
73
+ ${color('bold', 'Templates')}
74
+ ${Object.entries(TEMPLATES)
75
+ .map(([k, t]) => ` ${color('cyan', k.padEnd(16))} ${t.label.replace(/^\S+\s+-\s+/, '')}`)
76
+ .join('\n')}
77
+
78
+ ${color('bold', 'Examples')}
79
+ npm create sv-grid@latest
80
+ npm create sv-grid@latest my-app -- --template admin-dashboard
81
+ pnpm create sv-grid my-app -t minimal
82
+ `)
83
+ }
84
+
85
+ function sanitizeName(name) {
86
+ return (
87
+ name
88
+ .trim()
89
+ .toLowerCase()
90
+ .replace(/[^a-z0-9._-]+/g, '-')
91
+ .replace(/^[-_.]+|[-_.]+$/g, '') || 'sv-grid-app'
92
+ )
93
+ }
94
+
95
+ function resolveTemplateDir(key) {
96
+ const t = TEMPLATES[key]
97
+ if (existsSync(t.bundled)) return t.bundled
98
+ if (t.fallback && existsSync(t.fallback)) return t.fallback
99
+ return null
100
+ }
101
+
102
+ async function copyTemplate(srcDir, destDir) {
103
+ await cp(srcDir, destDir, {
104
+ recursive: true,
105
+ filter: (src) =>
106
+ !/[\\/](node_modules|\.svelte-kit|\.vercel|build|dist)([\\/]|$)/.test(src),
107
+ })
108
+ // Rename `_`-prefixed files back to their real dotfile names.
109
+ await renameBack(destDir)
110
+ }
111
+
112
+ async function renameBack(dir) {
113
+ for (const entry of await readdir(dir)) {
114
+ const full = join(dir, entry)
115
+ if ((await stat(full)).isDirectory()) {
116
+ await renameBack(full)
117
+ } else if (RENAME_BACK.has(entry)) {
118
+ await rename(full, join(dir, RENAME_BACK.get(entry)))
119
+ }
120
+ }
121
+ }
122
+
123
+ async function setProjectName(destDir, name) {
124
+ const pkgPath = join(destDir, 'package.json')
125
+ if (!existsSync(pkgPath)) return
126
+ try {
127
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
128
+ pkg.name = name
129
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
130
+ } catch {
131
+ // leave the template's name if it isn't valid JSON for some reason
132
+ }
133
+ }
134
+
135
+ async function isEmptyDir(dir) {
136
+ if (!existsSync(dir)) return true
137
+ const entries = await readdir(dir)
138
+ return entries.filter((e) => e !== '.git').length === 0
139
+ }
140
+
141
+ async function main() {
142
+ const args = parseArgs(process.argv.slice(2))
143
+ if (args.help) return printHelp()
144
+
145
+ const interactive = stdin.isTTY && stdout.isTTY
146
+ let rl = null
147
+ const ask = async (q, def) => {
148
+ if (!interactive) return def
149
+ rl ??= createInterface({ input: stdin, output: stdout })
150
+ const a = (await rl.question(`${q} ${color('dim', `(${def})`)} `)).trim()
151
+ return a || def
152
+ }
153
+
154
+ stdout.write(`\n${color('bold', '◆ @svgrid/create')} ${color('dim', 'Svelte + SvGrid')}\n\n`)
155
+
156
+ // 1. Target directory.
157
+ let target = args._[0]
158
+ if (!target) target = await ask('Project directory:', 'my-sv-grid-app')
159
+ const destDir = resolve(process.cwd(), target)
160
+ const projectName = sanitizeName(basename(destDir))
161
+
162
+ // 2. Template.
163
+ let template = args.template
164
+ if (!template) {
165
+ if (interactive) {
166
+ stdout.write(`\n Templates:\n`)
167
+ Object.entries(TEMPLATES).forEach(([k, t], i) => {
168
+ stdout.write(` ${color('cyan', String(i + 1))}. ${t.label}\n`)
169
+ })
170
+ const pick = await ask('\nChoose a template (1-2):', '1')
171
+ template = Object.keys(TEMPLATES)[Number(pick) - 1] ?? 'minimal'
172
+ } else {
173
+ template = 'minimal'
174
+ }
175
+ }
176
+ if (!TEMPLATES[template]) {
177
+ if (rl) rl.close()
178
+ stdout.write(
179
+ `\n${color('red', '✖')} Unknown template "${template}". Choose: ${Object.keys(TEMPLATES).join(', ')}\n`,
180
+ )
181
+ process.exit(1)
182
+ }
183
+
184
+ // 3. Safety: don't clobber a non-empty directory.
185
+ if (!(await isEmptyDir(destDir)) && !args.force) {
186
+ const ok = await ask(
187
+ `\n${color('yellow', '!')} ${target} is not empty. Continue and overwrite files? (y/N)`,
188
+ 'N',
189
+ )
190
+ if (!/^y(es)?$/i.test(ok)) {
191
+ if (rl) rl.close()
192
+ stdout.write(`${color('red', '✖')} Aborted.\n`)
193
+ process.exit(1)
194
+ }
195
+ }
196
+
197
+ const srcDir = resolveTemplateDir(template)
198
+ if (!srcDir) {
199
+ if (rl) rl.close()
200
+ stdout.write(
201
+ `\n${color('red', '✖')} Template "${template}" is not available in this build.\n`,
202
+ )
203
+ process.exit(1)
204
+ }
205
+
206
+ // 4. Scaffold.
207
+ await mkdir(destDir, { recursive: true })
208
+ await copyTemplate(srcDir, destDir)
209
+ await setProjectName(destDir, projectName)
210
+ if (rl) rl.close()
211
+
212
+ // 5. Next steps.
213
+ const rel = isAbsolute(target) || target.startsWith('.') ? target : `./${target}`
214
+ stdout.write(`\n${color('green', '✔')} Scaffolded ${color('bold', projectName)} (${template}) into ${rel}\n\n`)
215
+ stdout.write(`${color('bold', 'Next steps')}\n`)
216
+ stdout.write(` cd ${target}\n`)
217
+ stdout.write(` npm install\n`)
218
+ stdout.write(` npm run dev\n\n`)
219
+ stdout.write(`${color('dim', 'Docs:')} https://www.svgrid.com/docs ${color('dim', 'Pro:')} https://www.svgrid.com/pricing\n\n`)
220
+ }
221
+
222
+ main().catch((err) => {
223
+ console.error(err)
224
+ process.exit(1)
225
+ })
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@svgrid/create",
3
+ "version": "1.0.0",
4
+ "description": "Scaffold a Svelte app powered by SvGrid in one command: npm create sv-grid@latest",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "jQWidgets Ltd",
8
+ "homepage": "https://www.svgrid.com",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/sv-grid/sv-grid.git",
12
+ "directory": "packages/create-sv-grid"
13
+ },
14
+ "keywords": [
15
+ "svelte",
16
+ "sveltekit",
17
+ "data-grid",
18
+ "datagrid",
19
+ "table",
20
+ "sv-grid",
21
+ "create",
22
+ "scaffold",
23
+ "starter",
24
+ "template"
25
+ ],
26
+ "bin": {
27
+ "@svgrid/create": "index.mjs"
28
+ },
29
+ "files": [
30
+ "index.mjs",
31
+ "templates"
32
+ ],
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "scripts": {
37
+ "sync-templates": "node sync-templates.mjs",
38
+ "prepack": "node sync-templates.mjs"
39
+ }
40
+ }
@@ -0,0 +1,77 @@
1
+ # SvGrid Admin Dashboard (SvelteKit starter)
2
+
3
+ A production-ready admin dashboard built with **[SvelteKit](https://svelte.dev/docs/kit)**
4
+ and **[SvGrid](https://www.svgrid.com)** - the modern Svelte 5 data grid.
5
+ Sortable, filterable, editable grids; KPI cards; a sidebar shell; prerendered
6
+ to static HTML for SEO; one-click deploy to Vercel.
7
+
8
+ ![SvGrid + SvelteKit](https://img.shields.io/badge/SvelteKit-SvGrid-4f46e5)
9
+
10
+ ## Deploy
11
+
12
+ [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fsv-grid%2Fsv-grid%2Ftree%2Fmain%2Ftemplates%2Fsveltekit-admin-dashboard&project-name=sv-grid-admin&repository-name=sv-grid-admin)
13
+
14
+ > Deploying from this monorepo subfolder? In the Vercel import step set the
15
+ > **Root Directory** to `templates/sveltekit-admin-dashboard`. Or scaffold a
16
+ > standalone copy first (recommended):
17
+ >
18
+ > ```bash
19
+ > npm create sv-grid@latest my-admin -- --template admin-dashboard
20
+ > ```
21
+
22
+ ## Quick start
23
+
24
+ ```bash
25
+ npm install
26
+ npm run dev # http://localhost:5173
27
+ ```
28
+
29
+ ```bash
30
+ npm run build # static + serverless output via adapter-vercel
31
+ npm run preview # preview the production build
32
+ npm run check # svelte-check (types)
33
+ ```
34
+
35
+ ## What's inside
36
+
37
+ | Path | What it shows |
38
+ | --- | --- |
39
+ | `src/routes/+layout.svelte` | App shell: sidebar nav + top bar |
40
+ | `src/routes/+page.svelte` | Overview: KPI cards + a recent-orders grid |
41
+ | `src/routes/orders/+page.svelte` | Full grid: sort, filter, **row select**, inline edit, pagination |
42
+ | `src/routes/customers/+page.svelte` | Grid with **column grouping** (drag a column to the group bar) |
43
+ | `src/lib/data.ts` | Deterministic sample data - swap for your API / `load()` |
44
+ | `src/lib/types.ts` | `Order` / `Customer` row types |
45
+
46
+ The grids mount client-side (`{#if browser}`) while the shell and headings
47
+ prerender to crawlable HTML - good for SEO and instant first paint. Every
48
+ route is `prerender = true` (see `src/routes/+layout.ts`); delete that line
49
+ on any route that needs SSR or runtime data.
50
+
51
+ ## Wiring your own data
52
+
53
+ Replace the calls to `makeOrders()` / `makeCustomers()` in each route with a
54
+ SvelteKit [`load`](https://svelte.dev/docs/kit/load) function:
55
+
56
+ ```ts
57
+ // src/routes/orders/+page.ts
58
+ export async function load({ fetch }) {
59
+ const orders = await fetch('/api/orders').then((r) => r.json())
60
+ return { orders }
61
+ }
62
+ ```
63
+
64
+ Then read `data.orders` in the page via `let { data } = $props()`.
65
+
66
+ ## Upgrade to Pro
67
+
68
+ This starter uses the free MIT **`@svgrid/grid`** core. For Excel/PDF
69
+ export, data import, printing, pivot, and the AI helpers, add
70
+ [`@svgrid/enterprise`](https://www.svgrid.com/pricing) and call `installPro(api)` on
71
+ the grid's API. Pro runs in evaluation with a watermark - no key needed to try
72
+ it.
73
+
74
+ ## License
75
+
76
+ This template is MIT-licensed - use it as the basis for your own app.
77
+ SvGrid(TM) is a trademark of jQWidgets Ltd.
@@ -0,0 +1,3 @@
1
+ # Copy to .env and fill in for your own data source. The starter ships with
2
+ # in-memory sample data, so nothing here is required to run.
3
+ # PUBLIC_API_BASE_URL=https://api.example.com
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ /build
3
+ /.svelte-kit
4
+ /.vercel
5
+ /package
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ vite.config.ts.timestamp-*
@@ -0,0 +1 @@
1
+ engine-strict=true
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "sv-grid-admin-dashboard",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "description": "Production-ready SvelteKit admin dashboard built on SvGrid (Svelte 5 data grid).",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "vite dev",
9
+ "build": "vite build",
10
+ "preview": "vite preview",
11
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
12
+ },
13
+ "devDependencies": {
14
+ "@sveltejs/adapter-vercel": "^5.5.0",
15
+ "@sveltejs/kit": "^2.15.0",
16
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
17
+ "@tailwindcss/vite": "^4.2.4",
18
+ "svelte": "^5.55.5",
19
+ "svelte-check": "^4.4.6",
20
+ "tailwindcss": "^4.2.4",
21
+ "typescript": "^5.7.0",
22
+ "vite": "^8.0.10"
23
+ },
24
+ "dependencies": {
25
+ "@svgrid/grid": "^1.0.0"
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ @import 'tailwindcss';
2
+
3
+ /* SvGrid ships its own scoped styles. These custom properties let you theme
4
+ the grid to match the dashboard shell. Tweak freely. */
5
+ :root {
6
+ --app-bg: #f8fafc;
7
+ --app-panel: #ffffff;
8
+ --app-border: #e2e8f0;
9
+ --app-fg: #0f172a;
10
+ --app-muted: #64748b;
11
+ --app-accent: #4f46e5;
12
+ }
13
+
14
+ html,
15
+ body {
16
+ background: var(--app-bg);
17
+ color: var(--app-fg);
18
+ }
@@ -0,0 +1,12 @@
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 PageState {}
8
+ // interface Platform {}
9
+ }
10
+ }
11
+
12
+ export {}
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en" class="h-full">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%sveltekit.assets%/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <meta
8
+ name="description"
9
+ content="Admin dashboard starter built with SvelteKit and SvGrid - the Svelte 5 data grid."
10
+ />
11
+ %sveltekit.head%
12
+ </head>
13
+ <body data-sveltekit-preload-data="hover" class="h-full">
14
+ <div style="display: contents">%sveltekit.body%</div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,20 @@
1
+ <script lang="ts">
2
+ let {
3
+ label,
4
+ value,
5
+ hint = '',
6
+ }: { label: string; value: string; hint?: string } = $props()
7
+ </script>
8
+
9
+ <div
10
+ class="rounded-xl border bg-white p-4 shadow-sm"
11
+ style="border-color: var(--app-border);"
12
+ >
13
+ <p class="text-xs font-medium uppercase tracking-wide" style="color: var(--app-muted);">
14
+ {label}
15
+ </p>
16
+ <p class="mt-1 text-2xl font-bold" style="color: var(--app-fg);">{value}</p>
17
+ {#if hint}
18
+ <p class="mt-1 text-xs" style="color: var(--app-muted);">{hint}</p>
19
+ {/if}
20
+ </div>
@@ -0,0 +1,84 @@
1
+ import type { Customer, Order, OrderStatus } from './types'
2
+
3
+ // Deterministic pseudo-random generator so the sample data is stable across
4
+ // reloads and SSR/CSR (no hydration mismatch). Swap any of this out for your
5
+ // own API / load function.
6
+ function mulberry32(seed: number): () => number {
7
+ let a = seed
8
+ return () => {
9
+ a |= 0
10
+ a = (a + 0x6d2b79f5) | 0
11
+ let t = Math.imul(a ^ (a >>> 15), 1 | a)
12
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
13
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296
14
+ }
15
+ }
16
+
17
+ const FIRST = ['Ava', 'Liam', 'Noah', 'Emma', 'Mia', 'Lucas', 'Sofia', 'Ethan', 'Aria', 'Leo', 'Zoe', 'Kai']
18
+ const LAST = ['Carter', 'Nguyen', 'Patel', 'Kim', 'Garcia', 'Müller', 'Rossi', 'Silva', 'Okafor', 'Haddad', 'Novak', 'Tanaka']
19
+ const COMPANIES = ['Northwind', 'Acme', 'Globex', 'Initech', 'Umbrella', 'Hooli', 'Vandelay', 'Stark Co', 'Wayne LLC', 'Soylent']
20
+ const PRODUCTS = ['Starter Plan', 'Team Plan', 'Enterprise Plan', 'Add-on: Storage', 'Add-on: Seats', 'Onboarding']
21
+ const COUNTRIES = ['US', 'UK', 'DE', 'FR', 'JP', 'BR', 'CA', 'AU', 'IN', 'NL']
22
+ const STATUSES: OrderStatus[] = ['paid', 'paid', 'paid', 'pending', 'refunded', 'failed']
23
+ const PLANS: Customer['plan'][] = ['Free', 'Pro', 'Pro', 'Enterprise']
24
+
25
+ function isoDaysAgo(rng: () => number, maxDays: number): string {
26
+ const d = new Date()
27
+ d.setDate(d.getDate() - Math.floor(rng() * maxDays))
28
+ return d.toISOString().slice(0, 10)
29
+ }
30
+
31
+ export function makeOrders(count = 240): Order[] {
32
+ const rng = mulberry32(42)
33
+ return Array.from({ length: count }, (_, i) => {
34
+ const customer = `${FIRST[Math.floor(rng() * FIRST.length)]} ${LAST[Math.floor(rng() * LAST.length)]}`
35
+ const quantity = 1 + Math.floor(rng() * 6)
36
+ const unit = [19, 49, 99, 149, 299][Math.floor(rng() * 5)]
37
+ return {
38
+ id: `ORD-${(10000 + i).toString()}`,
39
+ customer,
40
+ email: `${customer.toLowerCase().replace(/[^a-z]/g, '.')}@example.com`,
41
+ product: PRODUCTS[Math.floor(rng() * PRODUCTS.length)],
42
+ status: STATUSES[Math.floor(rng() * STATUSES.length)],
43
+ quantity,
44
+ total: quantity * unit,
45
+ country: COUNTRIES[Math.floor(rng() * COUNTRIES.length)],
46
+ date: isoDaysAgo(rng, 120),
47
+ }
48
+ })
49
+ }
50
+
51
+ export function makeCustomers(count = 120): Customer[] {
52
+ const rng = mulberry32(7)
53
+ return Array.from({ length: count }, (_, i) => {
54
+ const name = `${FIRST[Math.floor(rng() * FIRST.length)]} ${LAST[Math.floor(rng() * LAST.length)]}`
55
+ const plan = PLANS[Math.floor(rng() * PLANS.length)]
56
+ const seats = plan === 'Enterprise' ? 10 + Math.floor(rng() * 90) : plan === 'Pro' ? 1 + Math.floor(rng() * 10) : 1
57
+ const perSeat = plan === 'Enterprise' ? 40 : plan === 'Pro' ? 15 : 0
58
+ return {
59
+ id: `CUS-${(2000 + i).toString()}`,
60
+ name,
61
+ email: `${name.toLowerCase().replace(/[^a-z]/g, '.')}@example.com`,
62
+ company: COMPANIES[Math.floor(rng() * COMPANIES.length)],
63
+ plan,
64
+ mrr: seats * perSeat,
65
+ seats,
66
+ active: rng() > 0.18,
67
+ joined: isoDaysAgo(rng, 900),
68
+ }
69
+ })
70
+ }
71
+
72
+ /** Headline KPIs derived from the sample data - feeds the dashboard cards. */
73
+ export function summarize(orders: Order[], customers: Customer[]) {
74
+ const revenue = orders.filter((o) => o.status === 'paid').reduce((s, o) => s + o.total, 0)
75
+ const mrr = customers.filter((c) => c.active).reduce((s, c) => s + c.mrr, 0)
76
+ const pending = orders.filter((o) => o.status === 'pending').length
77
+ return {
78
+ revenue,
79
+ mrr,
80
+ pending,
81
+ customers: customers.filter((c) => c.active).length,
82
+ orders: orders.length,
83
+ }
84
+ }
@@ -0,0 +1,25 @@
1
+ export type OrderStatus = 'paid' | 'pending' | 'refunded' | 'failed'
2
+
3
+ export interface Order {
4
+ id: string
5
+ customer: string
6
+ email: string
7
+ product: string
8
+ status: OrderStatus
9
+ quantity: number
10
+ total: number
11
+ country: string
12
+ date: string // ISO yyyy-mm-dd
13
+ }
14
+
15
+ export interface Customer {
16
+ id: string
17
+ name: string
18
+ email: string
19
+ company: string
20
+ plan: 'Free' | 'Pro' | 'Enterprise'
21
+ mrr: number
22
+ seats: number
23
+ active: boolean
24
+ joined: string // ISO yyyy-mm-dd
25
+ }
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ import '../app.css'
3
+ import { page } from '$app/state'
4
+
5
+ let { children } = $props()
6
+
7
+ const nav = [
8
+ { href: '/', label: 'Overview', icon: '▦' },
9
+ { href: '/orders', label: 'Orders', icon: '🧾' },
10
+ { href: '/customers', label: 'Customers', icon: '👥' },
11
+ ]
12
+
13
+ function isActive(href: string): boolean {
14
+ return href === '/' ? page.url.pathname === '/' : page.url.pathname.startsWith(href)
15
+ }
16
+ </script>
17
+
18
+ <div class="flex h-full min-h-screen">
19
+ <!-- Sidebar -->
20
+ <aside
21
+ class="hidden w-60 shrink-0 flex-col border-r bg-white md:flex"
22
+ style="border-color: var(--app-border);"
23
+ >
24
+ <div class="flex items-center gap-2 px-5 py-4">
25
+ <span
26
+ class="grid h-8 w-8 place-items-center rounded-lg font-bold text-white"
27
+ style="background: var(--app-accent);">S</span
28
+ >
29
+ <div class="leading-tight">
30
+ <p class="text-sm font-bold" style="color: var(--app-fg);">SvGrid Admin</p>
31
+ <p class="text-[11px]" style="color: var(--app-muted);">Dashboard starter</p>
32
+ </div>
33
+ </div>
34
+
35
+ <nav class="mt-2 flex-1 px-3">
36
+ {#each nav as item}
37
+ <a
38
+ href={item.href}
39
+ class="mb-1 flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors"
40
+ style={isActive(item.href)
41
+ ? 'background: var(--app-accent); color: #fff;'
42
+ : 'color: var(--app-fg);'}
43
+ >
44
+ <span class="w-5 text-center">{item.icon}</span>
45
+ {item.label}
46
+ </a>
47
+ {/each}
48
+ </nav>
49
+
50
+ <div class="px-5 py-4 text-[11px]" style="color: var(--app-muted);">
51
+ Built with
52
+ <a class="underline" href="https://www.svgrid.com" target="_blank" rel="noopener">SvGrid</a>
53
+ + SvelteKit
54
+ </div>
55
+ </aside>
56
+
57
+ <!-- Main -->
58
+ <div class="flex min-w-0 flex-1 flex-col">
59
+ <header
60
+ class="flex items-center justify-between border-b bg-white px-6 py-3"
61
+ style="border-color: var(--app-border);"
62
+ >
63
+ <h1 class="text-base font-semibold" style="color: var(--app-fg);">
64
+ {nav.find((n) => isActive(n.href))?.label ?? 'Dashboard'}
65
+ </h1>
66
+ <a
67
+ href="https://www.svgrid.com/docs"
68
+ target="_blank"
69
+ rel="noopener"
70
+ class="rounded-lg border px-3 py-1.5 text-sm font-medium"
71
+ style="border-color: var(--app-border); color: var(--app-fg);">Docs</a
72
+ >
73
+ </header>
74
+
75
+ <main class="min-w-0 flex-1 p-6">
76
+ {@render children()}
77
+ </main>
78
+ </div>
79
+ </div>
@@ -0,0 +1,4 @@
1
+ // Prerender the whole dashboard to static HTML at build time. The grids
2
+ // hydrate on the client; the shell + headings ship as crawlable HTML, which
3
+ // is great for SEO and makes the Vercel deploy a static, edge-cached site.
4
+ export const prerender = true
@@ -0,0 +1,87 @@
1
+ <script lang="ts">
2
+ import { browser } from '$app/environment'
3
+ import {
4
+ SvGrid,
5
+ tableFeatures,
6
+ rowSortingFeature,
7
+ columnFilteringFeature,
8
+ type ColumnDef,
9
+ } from '@svgrid/grid'
10
+ import StatCard from '$lib/StatCard.svelte'
11
+ import { makeOrders, makeCustomers, summarize } from '$lib/data'
12
+ import type { Order } from '$lib/types'
13
+
14
+ const orders = makeOrders()
15
+ const customers = makeCustomers()
16
+ const kpis = summarize(orders, customers)
17
+
18
+ const usd = (n: number) =>
19
+ n.toLocaleString('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 })
20
+
21
+ // Most recent 8 orders for the overview table.
22
+ const recent = $state(
23
+ [...orders].sort((a, b) => b.date.localeCompare(a.date)).slice(0, 8),
24
+ )
25
+
26
+ const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
27
+
28
+ const columns: ColumnDef<typeof features, Order>[] = [
29
+ { field: 'id', header: 'Order', width: 110 },
30
+ { field: 'customer', header: 'Customer', width: 160 },
31
+ { field: 'product', header: 'Product', width: 150 },
32
+ { field: 'status', header: 'Status', width: 110 },
33
+ {
34
+ field: 'total',
35
+ header: 'Total',
36
+ width: 110,
37
+ align: 'right',
38
+ format: { type: 'currency', currency: 'USD' },
39
+ },
40
+ { field: 'date', header: 'Date', width: 120, format: { type: 'date', pattern: 'y-m-d' } },
41
+ ]
42
+ </script>
43
+
44
+ <svelte:head>
45
+ <title>Overview · SvGrid Admin</title>
46
+ </svelte:head>
47
+
48
+ <section class="space-y-6">
49
+ <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
50
+ <StatCard label="Revenue (paid)" value={usd(kpis.revenue)} hint="all-time, sample data" />
51
+ <StatCard label="MRR" value={usd(kpis.mrr)} hint={`${kpis.customers} active customers`} />
52
+ <StatCard label="Orders" value={kpis.orders.toString()} hint={`${kpis.pending} pending`} />
53
+ <StatCard label="Active customers" value={kpis.customers.toString()} />
54
+ </div>
55
+
56
+ <div class="rounded-xl border bg-white p-4 shadow-sm" style="border-color: var(--app-border);">
57
+ <div class="mb-3 flex items-center justify-between">
58
+ <h2 class="text-sm font-semibold" style="color: var(--app-fg);">Recent orders</h2>
59
+ <a class="text-sm font-medium" style="color: var(--app-accent);" href="/orders">View all →</a>
60
+ </div>
61
+
62
+ <div style="height: 360px;">
63
+ {#if browser}
64
+ <SvGrid
65
+ data={recent}
66
+ {columns}
67
+ {features}
68
+ filterMode="menu"
69
+ showRowNumbers={false}
70
+ showPagination={false}
71
+ rowHeight={40}
72
+ containerHeight="100%"
73
+ fitColumns={true}
74
+ />
75
+ {:else}
76
+ <!-- Prerendered placeholder: real, crawlable rows for SEO -->
77
+ <ul class="text-sm" style="color: var(--app-muted);">
78
+ {#each recent as o}
79
+ <li class="border-b py-1" style="border-color: var(--app-border);">
80
+ {o.id} · {o.customer} · {o.product} · {usd(o.total)}
81
+ </li>
82
+ {/each}
83
+ </ul>
84
+ {/if}
85
+ </div>
86
+ </div>
87
+ </section>
@@ -0,0 +1,75 @@
1
+ <script lang="ts">
2
+ import { browser } from '$app/environment'
3
+ import {
4
+ SvGrid,
5
+ tableFeatures,
6
+ rowSortingFeature,
7
+ columnFilteringFeature,
8
+ columnGroupingFeature,
9
+ type ColumnDef,
10
+ } from '@svgrid/grid'
11
+ import { makeCustomers } from '$lib/data'
12
+ import type { Customer } from '$lib/types'
13
+
14
+ const features = tableFeatures({
15
+ rowSortingFeature,
16
+ columnFilteringFeature,
17
+ columnGroupingFeature,
18
+ })
19
+
20
+ let rows = $state<Customer[]>(makeCustomers())
21
+
22
+ const columns: ColumnDef<typeof features, Customer>[] = [
23
+ { field: 'id', header: 'ID', width: 100 },
24
+ { field: 'name', header: 'Name', editorType: 'text', width: 150 },
25
+ { field: 'company', header: 'Company', width: 130 },
26
+ { field: 'plan', header: 'Plan', width: 120 },
27
+ {
28
+ field: 'seats',
29
+ header: 'Seats',
30
+ width: 90,
31
+ align: 'right',
32
+ format: { type: 'number', options: { maximumFractionDigits: 0 } },
33
+ },
34
+ {
35
+ field: 'mrr',
36
+ header: 'MRR',
37
+ width: 110,
38
+ align: 'right',
39
+ format: { type: 'currency', currency: 'USD' },
40
+ },
41
+ { field: 'active', header: 'Active', width: 90, editorType: 'checkbox' },
42
+ { field: 'joined', header: 'Joined', width: 120, format: { type: 'date', pattern: 'y-m-d' } },
43
+ ]
44
+ </script>
45
+
46
+ <svelte:head>
47
+ <title>Customers · SvGrid Admin</title>
48
+ </svelte:head>
49
+
50
+ <section class="flex h-full flex-col gap-3">
51
+ <p class="text-sm" style="color: var(--app-muted);">
52
+ {rows.length} customers · drag the <strong>Plan</strong> column into the group bar to group by plan.
53
+ </p>
54
+
55
+ <div class="min-h-0 flex-1 rounded-xl border bg-white p-2 shadow-sm" style="border-color: var(--app-border);">
56
+ {#if browser}
57
+ <SvGrid
58
+ data={rows}
59
+ {columns}
60
+ {features}
61
+ filterMode="menu"
62
+ showRowNumbers={true}
63
+ showPagination={true}
64
+ pageSize={50}
65
+ showGroupingControls={true}
66
+ enableInlineEditing={true}
67
+ rowHeight={38}
68
+ containerHeight="100%"
69
+ fitColumns={true}
70
+ />
71
+ {:else}
72
+ <p class="p-4 text-sm" style="color: var(--app-muted);">Loading {rows.length} customers…</p>
73
+ {/if}
74
+ </div>
75
+ </section>
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import { browser } from '$app/environment'
3
+ import {
4
+ SvGrid,
5
+ tableFeatures,
6
+ rowSortingFeature,
7
+ columnFilteringFeature,
8
+ rowSelectionFeature,
9
+ type ColumnDef,
10
+ type SvGridApi,
11
+ } from '@svgrid/grid'
12
+ import { makeOrders } from '$lib/data'
13
+ import type { Order } from '$lib/types'
14
+
15
+ const features = tableFeatures({
16
+ rowSortingFeature,
17
+ columnFilteringFeature,
18
+ rowSelectionFeature,
19
+ })
20
+
21
+ let rows = $state<Order[]>(makeOrders())
22
+ let api = $state<SvGridApi<typeof features, Order> | null>(null)
23
+ let selectedCount = $state(0)
24
+
25
+ const columns: ColumnDef<typeof features, Order>[] = [
26
+ { field: 'id', header: 'Order', width: 110 },
27
+ { field: 'customer', header: 'Customer', editorType: 'text', width: 150 },
28
+ { field: 'email', header: 'Email', editorType: 'text', width: 190 },
29
+ { field: 'product', header: 'Product', editorType: 'text', width: 150 },
30
+ { field: 'status', header: 'Status', width: 110 },
31
+ {
32
+ field: 'quantity',
33
+ header: 'Qty',
34
+ editorType: 'number',
35
+ width: 80,
36
+ align: 'right',
37
+ format: { type: 'number', options: { maximumFractionDigits: 0 } },
38
+ },
39
+ {
40
+ field: 'total',
41
+ header: 'Total',
42
+ width: 120,
43
+ align: 'right',
44
+ format: { type: 'currency', currency: 'USD' },
45
+ },
46
+ { field: 'country', header: 'Country', width: 90 },
47
+ { field: 'date', header: 'Date', width: 120, format: { type: 'date', pattern: 'y-m-d' } },
48
+ ]
49
+ </script>
50
+
51
+ <svelte:head>
52
+ <title>Orders · SvGrid Admin</title>
53
+ </svelte:head>
54
+
55
+ <section class="flex h-full flex-col gap-3">
56
+ <div class="flex items-center justify-between">
57
+ <p class="text-sm" style="color: var(--app-muted);">
58
+ {rows.length} orders · sort, filter, select, edit inline, paginate.
59
+ {#if selectedCount > 0}
60
+ <span class="font-medium" style="color: var(--app-accent);">{selectedCount} selected</span>
61
+ {/if}
62
+ </p>
63
+ </div>
64
+
65
+ <div class="min-h-0 flex-1 rounded-xl border bg-white p-2 shadow-sm" style="border-color: var(--app-border);">
66
+ {#if browser}
67
+ <SvGrid
68
+ data={rows}
69
+ {columns}
70
+ {features}
71
+ filterMode="menu"
72
+ selectionMode="row"
73
+ showRowSelection={true}
74
+ showRowNumbers={true}
75
+ showPagination={true}
76
+ pageSize={25}
77
+ enableInlineEditing={true}
78
+ rowHeight={38}
79
+ containerHeight="100%"
80
+ fitColumns={true}
81
+ getRowId={(o: Order) => o.id}
82
+ onApiReady={(next) => (api = next)}
83
+ onRowSelectionChange={(_e, sel) => (selectedCount = sel.length)}
84
+ />
85
+ {:else}
86
+ <p class="p-4 text-sm" style="color: var(--app-muted);">Loading {rows.length} orders…</p>
87
+ {/if}
88
+ </div>
89
+ </section>
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="7" fill="#4f46e5" />
3
+ <g fill="#fff">
4
+ <rect x="6" y="7" width="20" height="4" rx="1.2" />
5
+ <rect x="6" y="14" width="9" height="4" rx="1.2" />
6
+ <rect x="17" y="14" width="9" height="4" rx="1.2" opacity="0.7" />
7
+ <rect x="6" y="21" width="9" height="4" rx="1.2" opacity="0.7" />
8
+ <rect x="17" y="21" width="9" height="4" rx="1.2" />
9
+ </g>
10
+ </svg>
@@ -0,0 +1,14 @@
1
+ import adapter from '@sveltejs/adapter-vercel'
2
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
3
+
4
+ /** @type {import('@sveltejs/kit').Config} */
5
+ const config = {
6
+ preprocess: vitePreprocess(),
7
+ kit: {
8
+ // adapter-vercel ships zero-config to Vercel. Swap for adapter-auto,
9
+ // adapter-static, adapter-node, etc. if you deploy elsewhere.
10
+ adapter: adapter(),
11
+ },
12
+ }
13
+
14
+ export default config
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "./.svelte-kit/tsconfig.json",
3
+ "compilerOptions": {
4
+ "allowJs": true,
5
+ "checkJs": true,
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "resolveJsonModule": true,
9
+ "skipLibCheck": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "moduleResolution": "bundler"
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ import { sveltekit } from '@sveltejs/kit/vite'
2
+ import tailwindcss from '@tailwindcss/vite'
3
+ import { defineConfig } from 'vite'
4
+
5
+ export default defineConfig({
6
+ plugins: [tailwindcss(), sveltekit()],
7
+ })
@@ -0,0 +1,21 @@
1
+ # SvGrid app
2
+
3
+ A minimal [Vite](https://vite.dev) + [Svelte 5](https://svelte.dev) app wired
4
+ to [SvGrid](https://www.svgrid.com), the Svelte data grid.
5
+
6
+ ```bash
7
+ npm install
8
+ npm run dev # http://localhost:5173
9
+ npm run build
10
+ ```
11
+
12
+ Open `src/App.svelte` and edit the `rows` / `columns` to use your own data.
13
+
14
+ ## Next steps
15
+
16
+ - Add more features: `rowExpandingFeature`, `columnGroupingFeature`, pagination, pinned rows. See the [docs](https://www.svgrid.com/docs).
17
+ - Need Excel/PDF export, import, print, pivot, or AI helpers? Add [`@svgrid/enterprise`](https://www.svgrid.com/pricing).
18
+ - Want a full app shell with routing? Scaffold the admin starter instead:
19
+ `npm create sv-grid@latest my-admin -- --template admin-dashboard`
20
+
21
+ SvGrid(TM) is a trademark of jQWidgets Ltd. This template is MIT-licensed.
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ .DS_Store
4
+ *.local
@@ -0,0 +1,19 @@
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
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
13
+ "svelte": "^5.55.5",
14
+ "vite": "^8.0.10"
15
+ },
16
+ "dependencies": {
17
+ "@svgrid/grid": "^1.0.0"
18
+ }
19
+ }
@@ -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>SvGrid app</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,61 @@
1
+ <script>
2
+ import {
3
+ SvGrid,
4
+ tableFeatures,
5
+ rowSortingFeature,
6
+ columnFilteringFeature,
7
+ rowSelectionFeature,
8
+ } from '@svgrid/grid'
9
+
10
+ const features = tableFeatures({
11
+ rowSortingFeature,
12
+ columnFilteringFeature,
13
+ rowSelectionFeature,
14
+ })
15
+
16
+ // Your data. Swap for a fetch() in onMount, a store, or props.
17
+ let rows = $state([
18
+ { id: 1, name: 'Ada Lovelace', team: 'Engineering', salary: 145000, active: true },
19
+ { id: 2, name: 'Alan Turing', team: 'Research', salary: 160000, active: true },
20
+ { id: 3, name: 'Grace Hopper', team: 'Engineering', salary: 152000, active: false },
21
+ { id: 4, name: 'Katherine Johnson', team: 'Data', salary: 138000, active: true },
22
+ { id: 5, name: 'Edsger Dijkstra', team: 'Research', salary: 149000, active: false },
23
+ ])
24
+
25
+ const columns = [
26
+ { field: 'name', header: 'Name', editorType: 'text', width: 200 },
27
+ { field: 'team', header: 'Team', editorType: 'text', width: 150 },
28
+ {
29
+ field: 'salary',
30
+ header: 'Salary',
31
+ width: 130,
32
+ align: 'right',
33
+ format: { type: 'currency', currency: 'USD' },
34
+ },
35
+ { field: 'active', header: 'Active', editorType: 'checkbox', width: 90 },
36
+ ]
37
+ </script>
38
+
39
+ <main style="max-width: 720px; margin: 3rem auto; font-family: system-ui, sans-serif;">
40
+ <h1 style="font-size: 1.4rem;">SvGrid</h1>
41
+ <p style="color:#64748b;">
42
+ Sort, filter, select, and double-click a cell to edit. Edit
43
+ <code>src/App.svelte</code> to make it yours.
44
+ </p>
45
+
46
+ <div style="height: 320px;">
47
+ <SvGrid
48
+ data={rows}
49
+ {columns}
50
+ {features}
51
+ filterMode="menu"
52
+ selectionMode="row"
53
+ showRowSelection={true}
54
+ showRowNumbers={true}
55
+ enableInlineEditing={true}
56
+ rowHeight={38}
57
+ containerHeight="100%"
58
+ fitColumns={true}
59
+ />
60
+ </div>
61
+ </main>
@@ -0,0 +1,6 @@
1
+ import { mount } from 'svelte'
2
+ import App from './App.svelte'
3
+
4
+ const app = mount(App, { target: document.getElementById('app') })
5
+
6
+ export default app
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ }
@@ -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
+ })