@svgrid/create 2.5.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/README.md +16 -7
- package/index.mjs +482 -403
- package/package.json +2 -2
- package/templates/admin-dashboard/src/app.css +184 -184
- package/templates/headless/README.md +65 -0
- package/templates/headless/_gitignore +4 -0
- package/templates/headless/_package.json +22 -0
- package/templates/headless/index.html +12 -0
- package/templates/headless/src/App.svelte +159 -0
- package/templates/headless/src/app.css +153 -0
- package/templates/headless/src/main.ts +7 -0
- package/templates/headless/src/vite-env.d.ts +2 -0
- package/templates/headless/svelte.config.js +5 -0
- package/templates/headless/tsconfig.json +14 -0
- package/templates/headless/vite.config.js +6 -0
- package/templates/minimal/src/app.css +1 -1
- package/templates/sveltekit/README.md +44 -0
- package/templates/sveltekit/_gitignore +23 -0
- package/templates/sveltekit/_package.json +26 -0
- package/templates/sveltekit/src/app.css +15 -0
- package/templates/sveltekit/src/app.d.ts +13 -0
- package/templates/sveltekit/src/app.html +22 -0
- package/templates/sveltekit/src/lib/people.ts +22 -0
- package/templates/sveltekit/src/lib/theme.svelte.ts +87 -0
- package/templates/sveltekit/src/routes/+layout.svelte +78 -0
- package/templates/sveltekit/src/routes/+page.svelte +7 -0
- package/templates/sveltekit/src/routes/people/+page.server.ts +16 -0
- package/templates/sveltekit/src/routes/people/+page.svelte +51 -0
- package/templates/sveltekit/tsconfig.json +20 -0
- package/templates/sveltekit/vite.config.ts +20 -0
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "commercial",
|
|
5
5
|
"url": "https://svgrid.com/pricing"
|
|
6
6
|
},
|
|
7
|
-
"version": "2.
|
|
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.
|
|
39
|
+
"@svgrid/grid": "^2.6.17"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"sync-templates": "node sync-templates.mjs"
|
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-
@import 'tailwindcss';
|
|
2
|
-
|
|
3
|
-
/* Tailwind's `dark:` modifier follows the `data-theme` attribute the layout
|
|
4
|
-
* writes onto <html>, so utility classes and the grid's own --sg-* tokens
|
|
5
|
-
* switch together. */
|
|
6
|
-
@custom-variant dark (&:where(html[data-theme='dark'], html[data-theme='dark'] *));
|
|
7
|
-
|
|
8
|
-
/* ---------------------------------------------------------------------------
|
|
9
|
-
* SvGrid theme tokens - one of @svgrid/grid's 20 built-in presets (pick one
|
|
10
|
-
* with `npm create @svgrid@latest -- --theme <id> [--dark]`, or edit the
|
|
11
|
-
* values below by hand). The dashboard shell reads them through the --app-*
|
|
12
|
-
* aliases further down, so the whole app themes as one, not just the grid.
|
|
13
|
-
* ------------------------------------------------------------------------- */
|
|
14
|
-
/* svgrid-theme:start */
|
|
15
|
-
:root {
|
|
16
|
-
--sg-bg: #ffffff;
|
|
17
|
-
--sg-fg: #0f172a;
|
|
18
|
-
--sg-muted: #64748b;
|
|
19
|
-
--sg-border: #e2e8f0;
|
|
20
|
-
--sg-header-bg: #f8fafc;
|
|
21
|
-
--sg-header-fg: #0f172a;
|
|
22
|
-
--sg-bg-subtle: #f8fafc;
|
|
23
|
-
--sg-row-alt-bg: #ffffff;
|
|
24
|
-
--sg-row-hover-bg: #f1f5f9;
|
|
25
|
-
--sg-selection-bg: #e0e7ff;
|
|
26
|
-
--sg-input-bg: #ffffff;
|
|
27
|
-
--sg-input-border: #e2e8f0;
|
|
28
|
-
--sg-accent: #4f46e5;
|
|
29
|
-
--sg-on-accent: #ffffff;
|
|
30
|
-
--sg-radius: 6px;
|
|
31
|
-
--sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
32
|
-
--sg-scrollbar-bg: #f8fafc;
|
|
33
|
-
--sg-scrollbar-border: #e2e8f0;
|
|
34
|
-
--sg-scrollbar-thumb: #64748b;
|
|
35
|
-
--sg-scrollbar-thumb-hover: #0f172a;
|
|
36
|
-
--sg-scrollbar-thumb-active: #0f172a;
|
|
37
|
-
--sg-scrollbar-arrow: #64748b;
|
|
38
|
-
--sg-scrollbar-arrow-hover: #0f172a;
|
|
39
|
-
--sg-scrollbar-arrow-hover-bg: #f1f5f9;
|
|
40
|
-
--sg-scrollbar-arrow-active: #0f172a;
|
|
41
|
-
--sg-scrollbar-arrow-active-bg: #e0e7ff;
|
|
42
|
-
--sg-scrollbar-arrow-disabled: #e2e8f0;
|
|
43
|
-
color-scheme: light;
|
|
44
|
-
}
|
|
45
|
-
:root[data-theme='dark'] {
|
|
46
|
-
--sg-bg: #0f172a;
|
|
47
|
-
--sg-fg: #f8fafc;
|
|
48
|
-
--sg-muted: #94a3b8;
|
|
49
|
-
--sg-border: #334155;
|
|
50
|
-
--sg-header-bg: #1e293b;
|
|
51
|
-
--sg-header-fg: #f8fafc;
|
|
52
|
-
--sg-bg-subtle: #1e293b;
|
|
53
|
-
--sg-row-alt-bg: #0f172a;
|
|
54
|
-
--sg-row-hover-bg: #1e293b;
|
|
55
|
-
--sg-selection-bg: #312e81;
|
|
56
|
-
--sg-input-bg: #0f172a;
|
|
57
|
-
--sg-input-border: #334155;
|
|
58
|
-
--sg-accent: #818cf8;
|
|
59
|
-
--sg-on-accent: #18181b;
|
|
60
|
-
--sg-radius: 6px;
|
|
61
|
-
--sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
62
|
-
--sg-scrollbar-bg: #1e293b;
|
|
63
|
-
--sg-scrollbar-border: #334155;
|
|
64
|
-
--sg-scrollbar-thumb: #94a3b8;
|
|
65
|
-
--sg-scrollbar-thumb-hover: #f8fafc;
|
|
66
|
-
--sg-scrollbar-thumb-active: #f8fafc;
|
|
67
|
-
--sg-scrollbar-arrow: #94a3b8;
|
|
68
|
-
--sg-scrollbar-arrow-hover: #f8fafc;
|
|
69
|
-
--sg-scrollbar-arrow-hover-bg: #1e293b;
|
|
70
|
-
--sg-scrollbar-arrow-active: #f8fafc;
|
|
71
|
-
--sg-scrollbar-arrow-active-bg: #312e81;
|
|
72
|
-
--sg-scrollbar-arrow-disabled: #334155;
|
|
73
|
-
color-scheme: dark;
|
|
74
|
-
}
|
|
75
|
-
/* svgrid-theme:end */
|
|
76
|
-
|
|
77
|
-
/* Dashboard shell aliases - map the chrome to the grid tokens above, so any
|
|
78
|
-
* theme (light or dark, whichever preset) reskins the whole page as one. */
|
|
79
|
-
:root {
|
|
80
|
-
--app-bg: var(--sg-row-alt-bg);
|
|
81
|
-
--app-panel: var(--sg-bg);
|
|
82
|
-
--app-border: var(--sg-border);
|
|
83
|
-
--app-fg: var(--sg-fg);
|
|
84
|
-
--app-muted: var(--sg-muted);
|
|
85
|
-
--app-accent: var(--sg-accent);
|
|
86
|
-
/* Text that sits ON the accent. Every preset declares this, and it is NOT
|
|
87
|
-
always white: shadcn dark uses a near-white accent (#fafafa) with near-black
|
|
88
|
-
text. Hardcoding #fff here made the active nav item invisible. */
|
|
89
|
-
--app-on-accent: var(--sg-on-accent);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
html,
|
|
93
|
-
body {
|
|
94
|
-
background: var(--app-bg);
|
|
95
|
-
color: var(--app-fg);
|
|
96
|
-
font-family: var(--sg-font, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
|
|
97
|
-
sans-serif);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/* ---------------------------------------------------------------------------
|
|
101
|
-
* Optional polish. The grid themes itself from the --sg-* tokens above, so it
|
|
102
|
-
* already follows light / dark with no extra CSS. These rules only add table
|
|
103
|
-
* niceties the grid leaves to the app: cell gridlines, zebra striping, row
|
|
104
|
-
* hover, and a styled pagination bar. Delete any you don't want.
|
|
105
|
-
* ------------------------------------------------------------------------- */
|
|
106
|
-
|
|
107
|
-
/* Cell gridlines (the grid draws none by default). */
|
|
108
|
-
.sv-grid-column,
|
|
109
|
-
.sv-grid-cell {
|
|
110
|
-
border-right: 1px solid var(--sg-border) !important;
|
|
111
|
-
border-bottom: 1px solid var(--sg-border) !important;
|
|
112
|
-
}
|
|
113
|
-
.sv-grid-table tr > :last-child.sv-grid-column,
|
|
114
|
-
.sv-grid-table tr > :last-child.sv-grid-cell {
|
|
115
|
-
border-right: 0 !important;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/* Zebra striping + row hover. */
|
|
119
|
-
.sv-grid-table tbody tr:nth-child(even) .sv-grid-cell {
|
|
120
|
-
background: var(--sg-row-alt-bg) !important;
|
|
121
|
-
}
|
|
122
|
-
.sv-grid-table tbody tr:hover .sv-grid-cell {
|
|
123
|
-
background: var(--sg-row-hover-bg) !important;
|
|
124
|
-
}
|
|
125
|
-
/* Selection wins over zebra (declared after, same specificity). */
|
|
126
|
-
.sv-grid-table tbody tr.sv-grid-row-selected .sv-grid-cell,
|
|
127
|
-
.sv-grid-table tbody tr .sv-grid-cell[data-selected-range='true'] {
|
|
128
|
-
background: var(--sg-selection-bg) !important;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* Pagination bar: layout + theme (the grid ships the markup, not the styling). */
|
|
132
|
-
.sv-grid-pagination {
|
|
133
|
-
display: flex !important;
|
|
134
|
-
align-items: center !important;
|
|
135
|
-
justify-content: flex-end !important;
|
|
136
|
-
gap: 24px !important;
|
|
137
|
-
padding: 12px 16px !important;
|
|
138
|
-
border: 1px solid var(--sg-border) !important;
|
|
139
|
-
border-top: 0 !important;
|
|
140
|
-
border-radius: 0 0 6px 6px !important;
|
|
141
|
-
background: var(--sg-header-bg) !important;
|
|
142
|
-
color: var(--sg-fg) !important;
|
|
143
|
-
font-size: 13px !important;
|
|
144
|
-
}
|
|
145
|
-
.sv-grid-pagination-pagesize {
|
|
146
|
-
display: inline-flex;
|
|
147
|
-
align-items: center;
|
|
148
|
-
gap: 8px;
|
|
149
|
-
color: var(--sg-muted);
|
|
150
|
-
}
|
|
151
|
-
.sv-grid-pagination-pagesize select,
|
|
152
|
-
.sv-grid-pagination-btn {
|
|
153
|
-
border: 1px solid var(--sg-input-border);
|
|
154
|
-
background: var(--sg-input-bg);
|
|
155
|
-
color: var(--sg-fg);
|
|
156
|
-
border-radius: 5px;
|
|
157
|
-
height: 28px;
|
|
158
|
-
font: inherit;
|
|
159
|
-
font-size: 13px;
|
|
160
|
-
cursor: pointer;
|
|
161
|
-
}
|
|
162
|
-
.sv-grid-pagination-btn {
|
|
163
|
-
display: inline-flex !important;
|
|
164
|
-
align-items: center !important;
|
|
165
|
-
justify-content: center !important;
|
|
166
|
-
width: 28px !important;
|
|
167
|
-
border: 0 !important;
|
|
168
|
-
background: transparent !important;
|
|
169
|
-
font-size: 16px !important;
|
|
170
|
-
}
|
|
171
|
-
.sv-grid-pagination-btn:hover:not(:disabled) {
|
|
172
|
-
background: var(--sg-input-bg) !important;
|
|
173
|
-
color: var(--sg-accent) !important;
|
|
174
|
-
}
|
|
175
|
-
.sv-grid-pagination-btn:disabled {
|
|
176
|
-
color: var(--sg-muted) !important;
|
|
177
|
-
opacity: 0.4 !important;
|
|
178
|
-
cursor: default !important;
|
|
179
|
-
}
|
|
180
|
-
.sv-grid-pagination-nav {
|
|
181
|
-
display: inline-flex;
|
|
182
|
-
align-items: center;
|
|
183
|
-
gap: 4px;
|
|
184
|
-
}
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
/* Tailwind's `dark:` modifier follows the `data-theme` attribute the layout
|
|
4
|
+
* writes onto <html>, so utility classes and the grid's own --sg-* tokens
|
|
5
|
+
* switch together. */
|
|
6
|
+
@custom-variant dark (&:where(html[data-theme='dark'], html[data-theme='dark'] *));
|
|
7
|
+
|
|
8
|
+
/* ---------------------------------------------------------------------------
|
|
9
|
+
* SvGrid theme tokens - one of @svgrid/grid's 20 built-in presets (pick one
|
|
10
|
+
* with `npm create @svgrid@latest -- --theme <id> [--dark]`, or edit the
|
|
11
|
+
* values below by hand). The dashboard shell reads them through the --app-*
|
|
12
|
+
* aliases further down, so the whole app themes as one, not just the grid.
|
|
13
|
+
* ------------------------------------------------------------------------- */
|
|
14
|
+
/* svgrid-theme:start */
|
|
15
|
+
:root {
|
|
16
|
+
--sg-bg: #ffffff;
|
|
17
|
+
--sg-fg: #0f172a;
|
|
18
|
+
--sg-muted: #64748b;
|
|
19
|
+
--sg-border: #e2e8f0;
|
|
20
|
+
--sg-header-bg: #f8fafc;
|
|
21
|
+
--sg-header-fg: #0f172a;
|
|
22
|
+
--sg-bg-subtle: #f8fafc;
|
|
23
|
+
--sg-row-alt-bg: #ffffff;
|
|
24
|
+
--sg-row-hover-bg: #f1f5f9;
|
|
25
|
+
--sg-selection-bg: #e0e7ff;
|
|
26
|
+
--sg-input-bg: #ffffff;
|
|
27
|
+
--sg-input-border: #e2e8f0;
|
|
28
|
+
--sg-accent: #4f46e5;
|
|
29
|
+
--sg-on-accent: #ffffff;
|
|
30
|
+
--sg-radius: 6px;
|
|
31
|
+
--sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
32
|
+
--sg-scrollbar-bg: #f8fafc;
|
|
33
|
+
--sg-scrollbar-border: #e2e8f0;
|
|
34
|
+
--sg-scrollbar-thumb: #64748b;
|
|
35
|
+
--sg-scrollbar-thumb-hover: #0f172a;
|
|
36
|
+
--sg-scrollbar-thumb-active: #0f172a;
|
|
37
|
+
--sg-scrollbar-arrow: #64748b;
|
|
38
|
+
--sg-scrollbar-arrow-hover: #0f172a;
|
|
39
|
+
--sg-scrollbar-arrow-hover-bg: #f1f5f9;
|
|
40
|
+
--sg-scrollbar-arrow-active: #0f172a;
|
|
41
|
+
--sg-scrollbar-arrow-active-bg: #e0e7ff;
|
|
42
|
+
--sg-scrollbar-arrow-disabled: #e2e8f0;
|
|
43
|
+
color-scheme: light;
|
|
44
|
+
}
|
|
45
|
+
:root[data-theme='dark'] {
|
|
46
|
+
--sg-bg: #0f172a;
|
|
47
|
+
--sg-fg: #f8fafc;
|
|
48
|
+
--sg-muted: #94a3b8;
|
|
49
|
+
--sg-border: #334155;
|
|
50
|
+
--sg-header-bg: #1e293b;
|
|
51
|
+
--sg-header-fg: #f8fafc;
|
|
52
|
+
--sg-bg-subtle: #1e293b;
|
|
53
|
+
--sg-row-alt-bg: #0f172a;
|
|
54
|
+
--sg-row-hover-bg: #1e293b;
|
|
55
|
+
--sg-selection-bg: #312e81;
|
|
56
|
+
--sg-input-bg: #0f172a;
|
|
57
|
+
--sg-input-border: #334155;
|
|
58
|
+
--sg-accent: #818cf8;
|
|
59
|
+
--sg-on-accent: #18181b;
|
|
60
|
+
--sg-radius: 6px;
|
|
61
|
+
--sg-font: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
62
|
+
--sg-scrollbar-bg: #1e293b;
|
|
63
|
+
--sg-scrollbar-border: #334155;
|
|
64
|
+
--sg-scrollbar-thumb: #94a3b8;
|
|
65
|
+
--sg-scrollbar-thumb-hover: #f8fafc;
|
|
66
|
+
--sg-scrollbar-thumb-active: #f8fafc;
|
|
67
|
+
--sg-scrollbar-arrow: #94a3b8;
|
|
68
|
+
--sg-scrollbar-arrow-hover: #f8fafc;
|
|
69
|
+
--sg-scrollbar-arrow-hover-bg: #1e293b;
|
|
70
|
+
--sg-scrollbar-arrow-active: #f8fafc;
|
|
71
|
+
--sg-scrollbar-arrow-active-bg: #312e81;
|
|
72
|
+
--sg-scrollbar-arrow-disabled: #334155;
|
|
73
|
+
color-scheme: dark;
|
|
74
|
+
}
|
|
75
|
+
/* svgrid-theme:end */
|
|
76
|
+
|
|
77
|
+
/* Dashboard shell aliases - map the chrome to the grid tokens above, so any
|
|
78
|
+
* theme (light or dark, whichever preset) reskins the whole page as one. */
|
|
79
|
+
:root {
|
|
80
|
+
--app-bg: var(--sg-row-alt-bg);
|
|
81
|
+
--app-panel: var(--sg-bg);
|
|
82
|
+
--app-border: var(--sg-border);
|
|
83
|
+
--app-fg: var(--sg-fg);
|
|
84
|
+
--app-muted: var(--sg-muted);
|
|
85
|
+
--app-accent: var(--sg-accent);
|
|
86
|
+
/* Text that sits ON the accent. Every preset declares this, and it is NOT
|
|
87
|
+
always white: shadcn dark uses a near-white accent (#fafafa) with near-black
|
|
88
|
+
text. Hardcoding #fff here made the active nav item invisible. */
|
|
89
|
+
--app-on-accent: var(--sg-on-accent);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
html,
|
|
93
|
+
body {
|
|
94
|
+
background: var(--app-bg);
|
|
95
|
+
color: var(--app-fg);
|
|
96
|
+
font-family: var(--sg-font, Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
|
|
97
|
+
sans-serif);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ---------------------------------------------------------------------------
|
|
101
|
+
* Optional polish. The grid themes itself from the --sg-* tokens above, so it
|
|
102
|
+
* already follows light / dark with no extra CSS. These rules only add table
|
|
103
|
+
* niceties the grid leaves to the app: cell gridlines, zebra striping, row
|
|
104
|
+
* hover, and a styled pagination bar. Delete any you don't want.
|
|
105
|
+
* ------------------------------------------------------------------------- */
|
|
106
|
+
|
|
107
|
+
/* Cell gridlines (the grid draws none by default). */
|
|
108
|
+
.sv-grid-column,
|
|
109
|
+
.sv-grid-cell {
|
|
110
|
+
border-right: 1px solid var(--sg-border) !important;
|
|
111
|
+
border-bottom: 1px solid var(--sg-border) !important;
|
|
112
|
+
}
|
|
113
|
+
.sv-grid-table tr > :last-child.sv-grid-column,
|
|
114
|
+
.sv-grid-table tr > :last-child.sv-grid-cell {
|
|
115
|
+
border-right: 0 !important;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Zebra striping + row hover. */
|
|
119
|
+
.sv-grid-table tbody tr:nth-child(even) .sv-grid-cell {
|
|
120
|
+
background: var(--sg-row-alt-bg) !important;
|
|
121
|
+
}
|
|
122
|
+
.sv-grid-table tbody tr:hover .sv-grid-cell {
|
|
123
|
+
background: var(--sg-row-hover-bg) !important;
|
|
124
|
+
}
|
|
125
|
+
/* Selection wins over zebra (declared after, same specificity). */
|
|
126
|
+
.sv-grid-table tbody tr.sv-grid-row-selected .sv-grid-cell,
|
|
127
|
+
.sv-grid-table tbody tr .sv-grid-cell[data-selected-range='true'] {
|
|
128
|
+
background: var(--sg-selection-bg) !important;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Pagination bar: layout + theme (the grid ships the markup, not the styling). */
|
|
132
|
+
.sv-grid-pagination {
|
|
133
|
+
display: flex !important;
|
|
134
|
+
align-items: center !important;
|
|
135
|
+
justify-content: flex-end !important;
|
|
136
|
+
gap: 24px !important;
|
|
137
|
+
padding: 12px 16px !important;
|
|
138
|
+
border: 1px solid var(--sg-border) !important;
|
|
139
|
+
border-top: 0 !important;
|
|
140
|
+
border-radius: 0 0 6px 6px !important;
|
|
141
|
+
background: var(--sg-header-bg) !important;
|
|
142
|
+
color: var(--sg-fg) !important;
|
|
143
|
+
font-size: 13px !important;
|
|
144
|
+
}
|
|
145
|
+
.sv-grid-pagination-pagesize {
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: 8px;
|
|
149
|
+
color: var(--sg-muted);
|
|
150
|
+
}
|
|
151
|
+
.sv-grid-pagination-pagesize select,
|
|
152
|
+
.sv-grid-pagination-btn {
|
|
153
|
+
border: 1px solid var(--sg-input-border);
|
|
154
|
+
background: var(--sg-input-bg);
|
|
155
|
+
color: var(--sg-fg);
|
|
156
|
+
border-radius: 5px;
|
|
157
|
+
height: 28px;
|
|
158
|
+
font: inherit;
|
|
159
|
+
font-size: 13px;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
.sv-grid-pagination-btn {
|
|
163
|
+
display: inline-flex !important;
|
|
164
|
+
align-items: center !important;
|
|
165
|
+
justify-content: center !important;
|
|
166
|
+
width: 28px !important;
|
|
167
|
+
border: 0 !important;
|
|
168
|
+
background: transparent !important;
|
|
169
|
+
font-size: 16px !important;
|
|
170
|
+
}
|
|
171
|
+
.sv-grid-pagination-btn:hover:not(:disabled) {
|
|
172
|
+
background: var(--sg-input-bg) !important;
|
|
173
|
+
color: var(--sg-accent) !important;
|
|
174
|
+
}
|
|
175
|
+
.sv-grid-pagination-btn:disabled {
|
|
176
|
+
color: var(--sg-muted) !important;
|
|
177
|
+
opacity: 0.4 !important;
|
|
178
|
+
cursor: default !important;
|
|
179
|
+
}
|
|
180
|
+
.sv-grid-pagination-nav {
|
|
181
|
+
display: inline-flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
gap: 4px;
|
|
184
|
+
}
|
|
@@ -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,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><table></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>
|