@svgrid/enterprise 2.6.2 → 2.6.4
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 +1 -1
- package/dist/cdn/svgrid-enterprise.svelte-external.js +1682 -1677
- package/dist/designer/assets/{GridMenus-Bjr19Tz_.js → GridMenus-BIU7Nfbr.js} +1 -1
- package/dist/designer/assets/SvGrid.helpers-C6wxF55r.js +1 -0
- package/dist/designer/assets/SvGridCellEditor-CMtmQINJ.js +2 -0
- package/dist/designer/assets/SvListBox-D-_I_Aff.js +1 -0
- package/dist/designer/assets/editor-registry-BB1Qb5mp.js +1 -0
- package/dist/designer/assets/{index-xjfKckuH.js → index-DxBZuosR.js} +42 -42
- package/dist/designer/assets/js-scroller.svelte-03aI6ovu.js +110 -0
- package/dist/designer/assets/row-drag-touch-IuJPEt0z.js +1 -0
- package/dist/designer/assets/src-D8qr49rE.css +1 -0
- package/dist/designer/assets/src-Dn1Axx6T.js +2896 -0
- package/dist/designer/index.html +7 -5
- package/dist/license-core.d.ts +1 -1
- package/dist/license-core.js +1 -1
- package/dist/node/studio.js +114 -78
- package/dist/studio/emit-project.js +16 -7
- package/dist/studio/emit-schema.d.ts +1 -4
- package/dist/studio/emit-schema.js +7 -29
- package/dist/studio/init-flow.js +2 -2
- package/dist/studio/scaffold-app.d.ts +8 -0
- package/dist/studio/scaffold-app.js +7 -2
- package/dist/studio/scaffold.d.ts +8 -0
- package/dist/studio/scaffold.js +5 -0
- package/dist/studio/themes.d.ts +17 -1
- package/dist/studio/themes.js +26 -1
- package/dist/studio/ui-components.generated.js +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/dist/designer/assets/SvListBox-BB8rt4aP.js +0 -1
- package/dist/designer/assets/js-scroller.svelte-CGkUUXwV.js +0 -110
- package/dist/designer/assets/src-BT3LjWJL.css +0 -1
- package/dist/designer/assets/src-DDhAK0WP.js +0 -2893
package/dist/designer/index.html
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>SvGrid Studio - Designer</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-DxBZuosR.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-Dd_uD5pT.js">
|
|
9
9
|
<link rel="modulepreload" crossorigin href="./assets/disclose-version-CIm4S9xS.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="./assets/export-format-DwjuTfrQ.js">
|
|
11
|
-
<link rel="modulepreload" crossorigin href="./assets/js-scroller.svelte-
|
|
11
|
+
<link rel="modulepreload" crossorigin href="./assets/js-scroller.svelte-03aI6ovu.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="./assets/SvGrid.helpers-C6wxF55r.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="./assets/editor-registry-BB1Qb5mp.js">
|
|
12
14
|
<link rel="modulepreload" crossorigin href="./assets/dismissable-2EZKoens.js">
|
|
13
15
|
<link rel="modulepreload" crossorigin href="./assets/popover-CUkLsvRX.js">
|
|
14
16
|
<link rel="modulepreload" crossorigin href="./assets/SvDateTimePicker-DOBYyLeN.js">
|
|
@@ -16,13 +18,13 @@
|
|
|
16
18
|
<link rel="modulepreload" crossorigin href="./assets/easing-zaypKrr0.js">
|
|
17
19
|
<link rel="modulepreload" crossorigin href="./assets/chart-DHEgt4lN.js">
|
|
18
20
|
<link rel="modulepreload" crossorigin href="./assets/SvGridChart-BEDNTdc5.js">
|
|
19
|
-
<link rel="modulepreload" crossorigin href="./assets/src-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="./assets/SvListBox-
|
|
21
|
+
<link rel="modulepreload" crossorigin href="./assets/src-Dn1Axx6T.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="./assets/SvListBox-D-_I_Aff.js">
|
|
21
23
|
<link rel="stylesheet" crossorigin href="./assets/dismissable-BF-9TJSd.css">
|
|
22
24
|
<link rel="stylesheet" crossorigin href="./assets/SvDateTimePicker-Cygij-dr.css">
|
|
23
25
|
<link rel="stylesheet" crossorigin href="./assets/SvGridDropdown-Crwb2P3Q.css">
|
|
24
26
|
<link rel="stylesheet" crossorigin href="./assets/SvGridChart-Vu4Nvjiv.css">
|
|
25
|
-
<link rel="stylesheet" crossorigin href="./assets/src-
|
|
27
|
+
<link rel="stylesheet" crossorigin href="./assets/src-D8qr49rE.css">
|
|
26
28
|
<link rel="stylesheet" crossorigin href="./assets/SvListBox-BMSfFJJT.css">
|
|
27
29
|
<link rel="stylesheet" crossorigin href="./assets/index-BfzoL904.css">
|
|
28
30
|
</head>
|
package/dist/license-core.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type LicenseInfo = {
|
|
|
29
29
|
* in that position) yields null, which reads as "no expiry encoded".
|
|
30
30
|
*
|
|
31
31
|
* Boundaries are UTC so a key does not lapse at a different instant per
|
|
32
|
-
* timezone. Kept in sync with
|
|
32
|
+
* timezone. Kept in sync with the (maintainer-side) key issuer.
|
|
33
33
|
*/
|
|
34
34
|
export declare function parseLicenseExpiry(key: string | null | undefined): Date | null;
|
|
35
35
|
/** Classify a license key string. Pure and side-effect-free.
|
package/dist/license-core.js
CHANGED
|
@@ -23,7 +23,7 @@ export const VALID_PREFIX = 'SVENTERPRISE-';
|
|
|
23
23
|
* in that position) yields null, which reads as "no expiry encoded".
|
|
24
24
|
*
|
|
25
25
|
* Boundaries are UTC so a key does not lapse at a different instant per
|
|
26
|
-
* timezone. Kept in sync with
|
|
26
|
+
* timezone. Kept in sync with the (maintainer-side) key issuer.
|
|
27
27
|
*/
|
|
28
28
|
export function parseLicenseExpiry(key) {
|
|
29
29
|
if (!key)
|
package/dist/node/studio.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isRangeOperator, isSetOperator, isValuelessOperator } from "@svgrid/grid/filtering";
|
|
2
1
|
import { defaultThemePreset, getThemePreset, resolveThemeTokens as resolveThemeTokens$1, themePresets } from "@svgrid/grid/themes";
|
|
2
|
+
import { isRangeOperator, isSetOperator, isValuelessOperator } from "@svgrid/grid/filtering";
|
|
3
3
|
//#region src/schema.ts
|
|
4
4
|
/** `first_name` / `firstName` / `first-name` -> `First Name`. */
|
|
5
5
|
function titleCase$2(field) {
|
|
@@ -1479,6 +1479,72 @@ function redactConnectionString(value) {
|
|
|
1479
1479
|
}
|
|
1480
1480
|
}
|
|
1481
1481
|
//#endregion
|
|
1482
|
+
//#region src/studio/themes.ts
|
|
1483
|
+
/**
|
|
1484
|
+
* The Studio look-and-feel themes: a `ProjectTheme`-aware wrapper around
|
|
1485
|
+
* `@svgrid/grid`'s built-in design-system presets (shadcn/ui, Tailwind,
|
|
1486
|
+
* Material 3, Excel, Fluent 2, ...) - the single source of truth for these
|
|
1487
|
+
* tokens, also used directly (as plain CSS) by app code that isn't going
|
|
1488
|
+
* through Studio's project model.
|
|
1489
|
+
*
|
|
1490
|
+
* Picking a preset (and a light/dark mode) restyles the WHOLE generated app -
|
|
1491
|
+
* shell, grids, charts, KPIs, forms - not just the accent.
|
|
1492
|
+
*
|
|
1493
|
+
* Pure + node-safe (studio subtree): plain data + helpers, so the same tokens
|
|
1494
|
+
* drive the browser preview and every emitted output - `app.css` for the full
|
|
1495
|
+
* app and the fragment, the `<svelte:head>` of the CLI `add` scaffolds - via
|
|
1496
|
+
* `themeTokenCss` below, the single place the token CSS is built.
|
|
1497
|
+
*/
|
|
1498
|
+
/** The built-in Studio themes, in gallery order (default first). */
|
|
1499
|
+
var studioThemes = themePresets;
|
|
1500
|
+
/** The default theme (used when a project has no `preset`). */
|
|
1501
|
+
var defaultStudioTheme = defaultThemePreset;
|
|
1502
|
+
/** Look up a theme by id. */
|
|
1503
|
+
var getStudioTheme = (id) => getThemePreset(id);
|
|
1504
|
+
/** True when the project's theme is rendering in dark mode. */
|
|
1505
|
+
var isDarkTheme = (theme) => theme?.mode === "dark";
|
|
1506
|
+
/** Resolve the effective `--sg-*` tokens for a project theme: the chosen preset
|
|
1507
|
+
* (default if none) in the chosen mode, with the accent override applied. */
|
|
1508
|
+
function resolveThemeTokens(theme) {
|
|
1509
|
+
const preset = getStudioTheme(theme?.preset) ?? defaultStudioTheme;
|
|
1510
|
+
const mode = theme?.mode === "dark" ? "dark" : "light";
|
|
1511
|
+
return resolveThemeTokens$1(preset, mode, theme?.accent);
|
|
1512
|
+
}
|
|
1513
|
+
/** Resolve the `--sg-*` tokens for a project theme in an EXPLICIT mode (for the
|
|
1514
|
+
* generated app's light/dark switcher, which ships both token sets). */
|
|
1515
|
+
function resolveThemeTokensFor(theme, mode) {
|
|
1516
|
+
const preset = getStudioTheme(theme?.preset) ?? defaultStudioTheme;
|
|
1517
|
+
return resolveThemeTokens$1(preset, mode, theme?.accent);
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* The `--sg-*` token rules for a project theme, as CSS text. `:root` carries
|
|
1521
|
+
* the mode picked in Studio (what the server-rendered page shows before
|
|
1522
|
+
* hydration), and both palettes are scoped by `[data-theme]` on `<html>` for
|
|
1523
|
+
* the generated app's light/dark switcher. An explicit accent override is
|
|
1524
|
+
* applied to both. No theme -> the default preset, so every output is themed.
|
|
1525
|
+
*
|
|
1526
|
+
* `layer` wraps the rules in a cascade layer. Use it for drop-in outputs (the
|
|
1527
|
+
* fragment, `svgrid-studio add`): layered declarations lose to unlayered ones,
|
|
1528
|
+
* so a host app that already defines its own `--sg-*` tokens keeps winning.
|
|
1529
|
+
*/
|
|
1530
|
+
function themeTokenCss(theme, opts = {}) {
|
|
1531
|
+
const decl = (m) => Object.entries(m).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
1532
|
+
const mode = isDarkTheme(theme) ? "dark" : "light";
|
|
1533
|
+
const light = resolveThemeTokensFor(theme, "light");
|
|
1534
|
+
const dark = resolveThemeTokensFor(theme, "dark");
|
|
1535
|
+
const rules = [
|
|
1536
|
+
`:root {\n${decl(mode === "dark" ? dark : light)}\n color-scheme: ${mode};\n}`,
|
|
1537
|
+
`:root[data-theme="light"] {\n${decl(light)}\n color-scheme: light;\n}`,
|
|
1538
|
+
`:root[data-theme="dark"] {\n${decl(dark)}\n color-scheme: dark;\n}`
|
|
1539
|
+
].join("\n");
|
|
1540
|
+
return opts.layer ? `@layer ${opts.layer} {\n${rules}\n}` : rules;
|
|
1541
|
+
}
|
|
1542
|
+
/** An inline `style` string (`--sg-x: y; ...; color-scheme; font-family`) for previews. */
|
|
1543
|
+
function themeStyleString(theme) {
|
|
1544
|
+
const tokens = resolveThemeTokens(theme);
|
|
1545
|
+
return `${Object.entries(tokens).map(([k, v]) => `${k}: ${v}`).join("; ")}; color-scheme: ${isDarkTheme(theme) ? "dark" : "light"}; font-family: ${tokens["--sg-font"]}`;
|
|
1546
|
+
}
|
|
1547
|
+
//#endregion
|
|
1482
1548
|
//#region src/studio/scaffold.ts
|
|
1483
1549
|
/**
|
|
1484
1550
|
* scaffold - turn an `EntitySchema` into runnable SvelteKit files. The shared
|
|
@@ -1704,6 +1770,13 @@ ${managed$1(serverBody)}
|
|
|
1704
1770
|
${managed$1(pageBody)}
|
|
1705
1771
|
<\/script>
|
|
1706
1772
|
|
|
1773
|
+
<svelte:head><style>
|
|
1774
|
+
${themeTokenCss({
|
|
1775
|
+
preset: options.theme,
|
|
1776
|
+
mode: options.dark ? "dark" : "light"
|
|
1777
|
+
}, { layer: "svgrid-studio" })}
|
|
1778
|
+
</style></svelte:head>
|
|
1779
|
+
|
|
1707
1780
|
<div class="page">
|
|
1708
1781
|
<header class="page__toolbar">
|
|
1709
1782
|
<h2 class="page__title">${label}</h2>
|
|
@@ -1803,7 +1876,7 @@ function mergeManaged(existing, generated) {
|
|
|
1803
1876
|
*/
|
|
1804
1877
|
var MANAGED_NOTE = "// Regenerated by SvGrid Studio. Edits inside these markers are overwritten.";
|
|
1805
1878
|
var managed = (body) => `${MANAGED_START}\n${MANAGED_NOTE}\n${body}\n${MANAGED_END}`;
|
|
1806
|
-
function layoutFile$1(nav, appTitle) {
|
|
1879
|
+
function layoutFile$1(nav, appTitle, theme) {
|
|
1807
1880
|
return {
|
|
1808
1881
|
path: "src/routes/+layout.svelte",
|
|
1809
1882
|
description: "App shell: a collapsible nav sidebar linking every entity screen (an off-canvas drawer on tablet/phone), with the routed page in the main area.",
|
|
@@ -1830,6 +1903,10 @@ ${managed(` let { children } = $props()
|
|
|
1830
1903
|
})`)}
|
|
1831
1904
|
<\/script>
|
|
1832
1905
|
|
|
1906
|
+
<svelte:head><style>
|
|
1907
|
+
${themeTokenCss(theme, { layer: "svgrid-studio" })}
|
|
1908
|
+
</style></svelte:head>
|
|
1909
|
+
|
|
1833
1910
|
<div class="app-shell" class:is-drawer={drawer} class:is-navopen={navOpen}>
|
|
1834
1911
|
<header class="app-shell__mobilebar">
|
|
1835
1912
|
<button type="button" class="app-shell__burger" aria-label="Toggle navigation" aria-expanded={navOpen} onclick={() => (navOpen = !navOpen)}>
|
|
@@ -1927,7 +2004,10 @@ function scaffoldApp(schemas, options = {}) {
|
|
|
1927
2004
|
href: `/${s.name}`,
|
|
1928
2005
|
label: s.label ?? titleCase$2(s.name)
|
|
1929
2006
|
}));
|
|
1930
|
-
files.push(layoutFile$1(nav, appTitle
|
|
2007
|
+
files.push(layoutFile$1(nav, appTitle, {
|
|
2008
|
+
preset: options.theme,
|
|
2009
|
+
mode: options.dark ? "dark" : "light"
|
|
2010
|
+
}), homeFile$1(nav, appTitle));
|
|
1931
2011
|
return { files };
|
|
1932
2012
|
}
|
|
1933
2013
|
//#endregion
|
|
@@ -8594,7 +8674,7 @@ var GENERATED_UI_SURFACE = {
|
|
|
8594
8674
|
"key": "sortable",
|
|
8595
8675
|
"label": "Sortable",
|
|
8596
8676
|
"type": "boolean",
|
|
8597
|
-
"description": "Convenience shortcuts to switch a whole capability on without wiring `features` or the finer-grained props by hand. Every capability is OFF by default - a bare grid is a plain read-only table - so set the shortcut `true` to opt in. (`false` / omitted both leave it off; the shortcut is mainly there to turn things ON.) `sortable` - column sorting (injects `rowSortingFeature`) `filterable` - column filtering (injects `columnFilteringFeature`) `editable` - inline cell editing (alias of `enableInlineEditing`) `groupable` - row grouping controls(alias of `showGroupingControls`, also injects `columnGroupingFeature`) `pageable` - pagination footer (alias of `showPagination`) Fine-grained props (`enableInlineEditing`, `showPagination`, ...) still work; the shortcut wins only when it is explicitly set.",
|
|
8677
|
+
"description": "Convenience shortcuts to switch a whole capability on without wiring `features` or the finer-grained props by hand. Every capability is OFF by default - a bare grid is a plain read-only table - so set the shortcut `true` to opt in. (`false` / omitted both leave it off; the shortcut is mainly there to turn things ON.) `sortable` - column sorting (injects `rowSortingFeature`) `filterable` - column filtering (injects `columnFilteringFeature`) `editable` - inline cell editing (alias of `enableInlineEditing`) `selectable` - cell selection (alias of `enableCellSelection`) `groupable` - row grouping controls(alias of `showGroupingControls`, also injects `columnGroupingFeature`) `pageable` - pagination footer (alias of `showPagination`) Fine-grained props (`enableInlineEditing`, `showPagination`, ...) still work; the shortcut wins only when it is explicitly set.",
|
|
8598
8678
|
"group": "common"
|
|
8599
8679
|
},
|
|
8600
8680
|
{
|
|
@@ -8609,6 +8689,13 @@ var GENERATED_UI_SURFACE = {
|
|
|
8609
8689
|
"type": "boolean",
|
|
8610
8690
|
"group": "common"
|
|
8611
8691
|
},
|
|
8692
|
+
{
|
|
8693
|
+
"key": "selectable",
|
|
8694
|
+
"label": "Selectable",
|
|
8695
|
+
"type": "boolean",
|
|
8696
|
+
"description": "Cell selection - click a cell to select it, drag or shift-click to extend the selection to a range. Alias of `enableCellSelection`, and the highest priority of the three inputs that decide it: `selectable` -> `enableCellSelection` -> `selectionMode`. NOTE the asymmetry with its siblings, which are all off until you opt in: this one is **ON by default**, because `selectionMode` defaults to `'both'`. It is named here so the capability is discoverable next to the other shortcuts, and so `selectable={false}` is a one-word way to turn cell selection off (the longhand being `selectionMode=\"none\"`, which also removes the row-selection surface). This is the CELL surface only. The selection **checkbox column** is the other half of `selectionMode`, controlled by `showRowSelection` - also on by default, and it needs no feature import (verified: the column renders and toggles rows with an empty `features` set). Use `selectionMode` when you want to move both surfaces at once.",
|
|
8697
|
+
"group": "common"
|
|
8698
|
+
},
|
|
8612
8699
|
{
|
|
8613
8700
|
"key": "treeData",
|
|
8614
8701
|
"label": "Tree Data",
|
|
@@ -15320,22 +15407,7 @@ function layoutFile(nav, opts = {}) {
|
|
|
15320
15407
|
const footer = shell.footer === void 0 ? "Built with SvGrid Studio" : shell.footer;
|
|
15321
15408
|
const right = style === "sidebar" && shell.navPosition === "right";
|
|
15322
15409
|
const defaultMode = opts.dark ? "dark" : "light";
|
|
15323
|
-
const
|
|
15324
|
-
const v = { ...m ?? {} };
|
|
15325
|
-
if (opts.accent) v["--sg-accent"] = opts.accent;
|
|
15326
|
-
return v;
|
|
15327
|
-
};
|
|
15328
|
-
const declLines = (m) => Object.entries(m).map(([k, v]) => `${k}: ${v};`).join(" ");
|
|
15329
|
-
const hasSwitch = !!(opts.lightVars && opts.darkVars);
|
|
15330
|
-
let themeHead = "";
|
|
15331
|
-
if (hasSwitch) {
|
|
15332
|
-
const lv = withAccent(opts.lightVars);
|
|
15333
|
-
const dv = withAccent(opts.darkVars);
|
|
15334
|
-
themeHead = `\n<svelte:head><style>:root { ${[declLines(defaultMode === "dark" ? dv : lv), `color-scheme: ${defaultMode};`].join(" ")} }\n:root[data-theme="light"] { ${[declLines(lv), "color-scheme: light;"].join(" ")} }\n:root[data-theme="dark"] { ${[declLines(dv), "color-scheme: dark;"].join(" ")} }</style></svelte:head>\n`;
|
|
15335
|
-
} else {
|
|
15336
|
-
const rootRule = [declLines(withAccent(opts.themeVars)), opts.dark ? "color-scheme: dark;" : ""].filter(Boolean).join(" ");
|
|
15337
|
-
themeHead = rootRule ? `\n<svelte:head><style>:root { ${rootRule} }</style></svelte:head>\n` : "";
|
|
15338
|
-
}
|
|
15410
|
+
const hasSwitch = !!opts.themeSwitch;
|
|
15339
15411
|
const anchor = `<a class="sv-app__link" class:is-active={page.url.pathname === item.href} href={item.href}>${opts.i18n ? `{item.id ? $t('nav.' + item.id, item.label) : item.label}` : "{item.label}"}</a>`;
|
|
15340
15412
|
const linkGate = opts.access ? `{#if !item.id || canScreen($currentRole, item.id)}${anchor}{/if}` : anchor;
|
|
15341
15413
|
const localeSwitcher = opts.i18n ? `\n <select class="sv-app__locale" aria-label="Language" onchange={(e) => currentLocale.set(e.currentTarget.value as typeof $currentLocale)}>
|
|
@@ -15524,7 +15596,7 @@ function layoutFile(nav, opts = {}) {
|
|
|
15524
15596
|
$effect(() => { void page.url.pathname; navOpen = false })${hasSwitch ? `
|
|
15525
15597
|
// Light/dark switcher: defaults to the mode picked in Studio, then honours the
|
|
15526
15598
|
// visitor's saved choice. Applies via [data-theme] on <html> (see the token
|
|
15527
|
-
// sets in
|
|
15599
|
+
// sets in app.css), and persists per browser.
|
|
15528
15600
|
let theme = $state<'light' | 'dark'>('${defaultMode}')
|
|
15529
15601
|
function applyTheme(t: 'light' | 'dark') {
|
|
15530
15602
|
theme = t
|
|
@@ -15562,7 +15634,6 @@ function layoutFile(nav, opts = {}) {
|
|
|
15562
15634
|
// Close both menus on route change.
|
|
15563
15635
|
$effect(() => { void page.url.pathname; bellOpen = false; menuOpen = false })` : ""}
|
|
15564
15636
|
<\/script>
|
|
15565
|
-
${themeHead}
|
|
15566
15637
|
|
|
15567
15638
|
${opts.auth ? `{#if ${JSON.stringify(opts.authRoutes ?? ["/login"])}.includes(page.url.pathname)}
|
|
15568
15639
|
{@render children()}
|
|
@@ -15728,47 +15799,6 @@ function emitStudioApp(schemas) {
|
|
|
15728
15799
|
];
|
|
15729
15800
|
}
|
|
15730
15801
|
//#endregion
|
|
15731
|
-
//#region src/studio/themes.ts
|
|
15732
|
-
/**
|
|
15733
|
-
* The Studio look-and-feel themes: a `ProjectTheme`-aware wrapper around
|
|
15734
|
-
* `@svgrid/grid`'s built-in design-system presets (shadcn/ui, Tailwind,
|
|
15735
|
-
* Material 3, Excel, Fluent 2, ...) - the single source of truth for these
|
|
15736
|
-
* tokens, also used directly (as plain CSS) by app code that isn't going
|
|
15737
|
-
* through Studio's project model.
|
|
15738
|
-
*
|
|
15739
|
-
* Picking a preset (and a light/dark mode) restyles the WHOLE generated app -
|
|
15740
|
-
* shell, grids, charts, KPIs, forms - not just the accent.
|
|
15741
|
-
*
|
|
15742
|
-
* Pure + node-safe (studio subtree): plain data + helpers, so the same tokens
|
|
15743
|
-
* drive the browser preview and the emitted `+layout.svelte` / `app.css`.
|
|
15744
|
-
*/
|
|
15745
|
-
/** The built-in Studio themes, in gallery order (default first). */
|
|
15746
|
-
var studioThemes = themePresets;
|
|
15747
|
-
/** The default theme (used when a project has no `preset`). */
|
|
15748
|
-
var defaultStudioTheme = defaultThemePreset;
|
|
15749
|
-
/** Look up a theme by id. */
|
|
15750
|
-
var getStudioTheme = (id) => getThemePreset(id);
|
|
15751
|
-
/** True when the project's theme is rendering in dark mode. */
|
|
15752
|
-
var isDarkTheme = (theme) => theme?.mode === "dark";
|
|
15753
|
-
/** Resolve the effective `--sg-*` tokens for a project theme: the chosen preset
|
|
15754
|
-
* (default if none) in the chosen mode, with the accent override applied. */
|
|
15755
|
-
function resolveThemeTokens(theme) {
|
|
15756
|
-
const preset = getStudioTheme(theme?.preset) ?? defaultStudioTheme;
|
|
15757
|
-
const mode = theme?.mode === "dark" ? "dark" : "light";
|
|
15758
|
-
return resolveThemeTokens$1(preset, mode, theme?.accent);
|
|
15759
|
-
}
|
|
15760
|
-
/** Resolve the `--sg-*` tokens for a project theme in an EXPLICIT mode (for the
|
|
15761
|
-
* generated app's light/dark switcher, which ships both token sets). */
|
|
15762
|
-
function resolveThemeTokensFor(theme, mode) {
|
|
15763
|
-
const preset = getStudioTheme(theme?.preset) ?? defaultStudioTheme;
|
|
15764
|
-
return resolveThemeTokens$1(preset, mode, theme?.accent);
|
|
15765
|
-
}
|
|
15766
|
-
/** An inline `style` string (`--sg-x: y; ...; color-scheme; font-family`) for previews. */
|
|
15767
|
-
function themeStyleString(theme) {
|
|
15768
|
-
const tokens = resolveThemeTokens(theme);
|
|
15769
|
-
return `${Object.entries(tokens).map(([k, v]) => `${k}: ${v}`).join("; ")}; color-scheme: ${isDarkTheme(theme) ? "dark" : "light"}; font-family: ${tokens["--sg-font"]}`;
|
|
15770
|
-
}
|
|
15771
|
-
//#endregion
|
|
15772
15802
|
//#region src/edit-panel.ts
|
|
15773
15803
|
/** True when any field's behaviour depends on the values (i.e. has a `when`). */
|
|
15774
15804
|
function hasFieldConditions(schema) {
|
|
@@ -15788,7 +15818,7 @@ function hasFieldConditions(schema) {
|
|
|
15788
15818
|
*
|
|
15789
15819
|
* `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
|
|
15790
15820
|
*/
|
|
15791
|
-
var SVGRID_VERSION = "2.6.
|
|
15821
|
+
var SVGRID_VERSION = "2.6.4";
|
|
15792
15822
|
//#endregion
|
|
15793
15823
|
//#region src/studio/emit-project.ts
|
|
15794
15824
|
var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
|
|
@@ -18310,12 +18340,9 @@ function emitStudioProject(project) {
|
|
|
18310
18340
|
...companions,
|
|
18311
18341
|
...handleFiles,
|
|
18312
18342
|
layoutFile(navExtras, {
|
|
18313
|
-
accent: project.theme?.accent,
|
|
18314
18343
|
shell: project.theme?.shell,
|
|
18315
18344
|
title: project.title,
|
|
18316
|
-
|
|
18317
|
-
lightVars: resolveThemeTokensFor(project.theme, "light"),
|
|
18318
|
-
darkVars: resolveThemeTokensFor(project.theme, "dark"),
|
|
18345
|
+
themeSwitch: true,
|
|
18319
18346
|
dark: isDarkTheme(project.theme),
|
|
18320
18347
|
access: accessEnabled,
|
|
18321
18348
|
auth: authEnabled && !supabaseAuth,
|
|
@@ -20268,8 +20295,15 @@ var SCAFFOLD_STATIC = [
|
|
|
20268
20295
|
contents: `node_modules\n.svelte-kit\n/build\n.env\n.env.*\n!.env.example\n.DS_Store\n`
|
|
20269
20296
|
}
|
|
20270
20297
|
];
|
|
20271
|
-
|
|
20272
|
-
*
|
|
20298
|
+
/** `src/app.css`: the project's theme tokens (see `themeTokenCss`) followed by the
|
|
20299
|
+
* `.st-*` page styles every generated screen uses. This is the ONE place the tokens
|
|
20300
|
+
* are emitted, so the fragment (which drops the layout) and the full app are
|
|
20301
|
+
* themed alike. `layer` is for drop-in outputs - see `themeTokenCss`. */
|
|
20302
|
+
function appCss(project, opts = {}) {
|
|
20303
|
+
return `${themeTokenCss(project.theme, opts)}
|
|
20304
|
+
${APP_CSS_BODY}`;
|
|
20305
|
+
}
|
|
20306
|
+
var APP_CSS_BODY = `* { box-sizing: border-box; }
|
|
20273
20307
|
html, body { margin: 0; height: 100%; }
|
|
20274
20308
|
body { font-family: var(--sg-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif); color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); }
|
|
20275
20309
|
.st__title { margin: 0; font-size: 22px; font-weight: 720; letter-spacing: -0.015em; }
|
|
@@ -20813,8 +20847,8 @@ function emitStudioAppBundle(project) {
|
|
|
20813
20847
|
}] : [],
|
|
20814
20848
|
{
|
|
20815
20849
|
path: "src/app.css",
|
|
20816
|
-
description: "App theme + page styles.",
|
|
20817
|
-
contents:
|
|
20850
|
+
description: "App theme tokens + page styles.",
|
|
20851
|
+
contents: appCss(project)
|
|
20818
20852
|
},
|
|
20819
20853
|
{
|
|
20820
20854
|
path: "src/custom.css",
|
|
@@ -20889,7 +20923,9 @@ ${depList}
|
|
|
20889
20923
|
|
|
20890
20924
|
## 3. Styles
|
|
20891
20925
|
Import \`src/app.css\` once (e.g. in your root \`+layout.svelte\`) - it carries the
|
|
20892
|
-
\`.st-*\` page styles the screens use and the \`--sg-*\` design tokens
|
|
20926
|
+
\`.st-*\` page styles the screens use and the \`--sg-*\` design tokens of the theme
|
|
20927
|
+
picked in Studio. The tokens sit in a cascade layer (\`@layer svgrid-studio\`), so if
|
|
20928
|
+
your app already defines its own \`--sg-*\` tokens, yours keep winning.
|
|
20893
20929
|
|
|
20894
20930
|
## 4. What your app must provide
|
|
20895
20931
|
The full-app nav shell, home redirect, auth guard, RBAC bootstrap, i18n provider,
|
|
@@ -20903,8 +20939,8 @@ ${libFiles.join("\n")}
|
|
|
20903
20939
|
...content,
|
|
20904
20940
|
{
|
|
20905
20941
|
path: "src/app.css",
|
|
20906
|
-
description: "
|
|
20907
|
-
contents:
|
|
20942
|
+
description: "Theme tokens (layered, so yours win) + page styles (.st-*) - import once in your layout.",
|
|
20943
|
+
contents: appCss(project, { layer: "svgrid-studio" })
|
|
20908
20944
|
},
|
|
20909
20945
|
{
|
|
20910
20946
|
path: "FRAGMENT.md",
|
|
@@ -40918,14 +40954,14 @@ async function askScreens(schemas, flags, prompts) {
|
|
|
40918
40954
|
async function askTheme(flags, prompts) {
|
|
40919
40955
|
let preset = flags.theme?.trim().toLowerCase();
|
|
40920
40956
|
if (preset && !getStudioTheme(preset)) {
|
|
40921
|
-
prompts.say(` ! Unknown theme "${preset}" - using
|
|
40957
|
+
prompts.say(` ! Unknown theme "${preset}" - using ember.`);
|
|
40922
40958
|
preset = void 0;
|
|
40923
40959
|
}
|
|
40924
40960
|
if (!preset && !flags.yes) preset = studioThemes[await pick(prompts, "Theme:", studioThemes.map((t) => ({
|
|
40925
40961
|
label: t.name,
|
|
40926
40962
|
hint: t.id
|
|
40927
40963
|
})))]?.id;
|
|
40928
|
-
preset ??= "
|
|
40964
|
+
preset ??= "ember";
|
|
40929
40965
|
const dark = flags.dark ?? (flags.yes ? false : isYes(await prompts.ask("Dark mode? (y/N)", "n"), false));
|
|
40930
40966
|
return {
|
|
40931
40967
|
preset,
|
|
@@ -41438,7 +41474,7 @@ var REVOKED_KEYS = /* @__PURE__ */ new Set([]);
|
|
|
41438
41474
|
* in that position) yields null, which reads as "no expiry encoded".
|
|
41439
41475
|
*
|
|
41440
41476
|
* Boundaries are UTC so a key does not lapse at a different instant per
|
|
41441
|
-
* timezone. Kept in sync with
|
|
41477
|
+
* timezone. Kept in sync with the (maintainer-side) key issuer.
|
|
41442
41478
|
*/
|
|
41443
41479
|
function parseLicenseExpiry(key) {
|
|
41444
41480
|
if (!key) return null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { tenantField, isTenantScoped } from './project.js';
|
|
2
2
|
import { blockColumns, blockStyleCss, blockClassName, sanitizeClassName, componentHandleName, componentHasBindings, entityDataSource, flattenBlocks, serializeProject, seedUsers, compileHandlerSteps, rowSelectSlot, eventSlot, FORM_SUBMIT, GRID_EVENTS, screenLayoutOf, isPaneLayout, canvasRectOf, CANVAS_ROW_PX, CANVAS_GAP_PX, gridOpts, stackOpts, splitOpts, dockOpts, canvasOpts, stateInitExpr, stateTsType, reconcileDock, ON_LOAD, ON_DESTROY, isSsrScreen, ssrScreenShape } from './project.js';
|
|
3
3
|
import { uiComponentSpec, gridApiSettableProps, STANDARD_UI_EVENTS } from './ui-components.js';
|
|
4
|
-
import {
|
|
4
|
+
import { isDarkTheme, themeTokenCss } from './themes.js';
|
|
5
5
|
import { hasFieldConditions } from '../edit-panel.js';
|
|
6
6
|
import { SVGRID_VERSION } from '../version.js';
|
|
7
7
|
import { emitEntityModules, homeFile, layoutFile, lookupVar, namesFor, prepareEntities, relationDisplayFields, sqlDdlFiles } from './emit-schema.js';
|
|
@@ -3048,7 +3048,7 @@ export function emitStudioProject(project) {
|
|
|
3048
3048
|
navExtras = [...navExtras, { href: '/users', label: 'Users', id: '__users__' }];
|
|
3049
3049
|
const i18nFiles = i18nEnabled ? [i18nModule(project)] : [];
|
|
3050
3050
|
const handleFiles = project.screens.some(screenHasCode) ? [handlesModuleFile()] : [];
|
|
3051
|
-
return [...files, ...accessFiles, ...authFileList, ...dataLayerList, ...ddlFiles, ...auditFiles, ...jobFileList, ...tenantFileList, ...i18nFiles, ...actionRouteFiles, ...ssrHelpers, ...pages, ...companions, ...handleFiles, layoutFile(navExtras, {
|
|
3051
|
+
return [...files, ...accessFiles, ...authFileList, ...dataLayerList, ...ddlFiles, ...auditFiles, ...jobFileList, ...tenantFileList, ...i18nFiles, ...actionRouteFiles, ...ssrHelpers, ...pages, ...companions, ...handleFiles, layoutFile(navExtras, { shell: project.theme?.shell, title: project.title, themeSwitch: true, dark: isDarkTheme(project.theme), access: accessEnabled, auth: authEnabled && !supabaseAuth, supabaseAuth, authRoutes: publicAuthRoutes, authAccount: dbBackedAuth, i18n: i18nEnabled, appClass: project.theme?.appClass }), homeFile(navExtras)];
|
|
3052
3052
|
}
|
|
3053
3053
|
/** The default-locale (`en`) message catalog, keyed for nav, screen titles, the
|
|
3054
3054
|
* New button, and grid column headers - the strings the app renders. */
|
|
@@ -4832,8 +4832,15 @@ const SCAFFOLD_STATIC = [
|
|
|
4832
4832
|
{ path: '.npmrc', description: 'npm config.', contents: `engine-strict=false\n` },
|
|
4833
4833
|
{ path: '.gitignore', description: 'git ignore.', contents: `node_modules\n.svelte-kit\n/build\n.env\n.env.*\n!.env.example\n.DS_Store\n` },
|
|
4834
4834
|
];
|
|
4835
|
-
|
|
4836
|
-
*
|
|
4835
|
+
/** `src/app.css`: the project's theme tokens (see `themeTokenCss`) followed by the
|
|
4836
|
+
* `.st-*` page styles every generated screen uses. This is the ONE place the tokens
|
|
4837
|
+
* are emitted, so the fragment (which drops the layout) and the full app are
|
|
4838
|
+
* themed alike. `layer` is for drop-in outputs - see `themeTokenCss`. */
|
|
4839
|
+
function appCss(project, opts = {}) {
|
|
4840
|
+
return `${themeTokenCss(project.theme, opts)}
|
|
4841
|
+
${APP_CSS_BODY}`;
|
|
4842
|
+
}
|
|
4843
|
+
const APP_CSS_BODY = `* { box-sizing: border-box; }
|
|
4837
4844
|
html, body { margin: 0; height: 100%; }
|
|
4838
4845
|
body { font-family: var(--sg-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif); color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); }
|
|
4839
4846
|
.st__title { margin: 0; font-size: 22px; font-weight: 720; letter-spacing: -0.015em; }
|
|
@@ -5390,7 +5397,7 @@ export function emitStudioAppBundle(project) {
|
|
|
5390
5397
|
...SCAFFOLD_STATIC,
|
|
5391
5398
|
...(envExample(allSource) ? [{ path: '.env.example', description: 'Environment variables the app reads (copy to .env and fill in).', contents: envExample(allSource) }] : []),
|
|
5392
5399
|
...(envDotFile(allSource) ? [{ path: '.env', description: 'Local env (git-ignored): a real random SESSION_SECRET is pre-filled so sessions are secure out of the box; fill in the rest.', contents: envDotFile(allSource) }] : []),
|
|
5393
|
-
{ path: 'src/app.css', description: 'App theme + page styles.', contents:
|
|
5400
|
+
{ path: 'src/app.css', description: 'App theme tokens + page styles.', contents: appCss(project) },
|
|
5394
5401
|
// Your styles: a dedicated CSS file the layout imports AFTER app.css, so its rules
|
|
5395
5402
|
// win. Edited in the designer's Styles panel; regenerated from the model.
|
|
5396
5403
|
{ path: 'src/custom.css', description: 'Your custom styles (edited in the designer; imported after app.css so it overrides).', contents: `/* Your custom styles. Edit in the designer's Styles panel, or here after ejecting.\n Imported after app.css, so these rules override the defaults. */\n${project.theme?.customCss ? `\n${project.theme.customCss}\n` : ''}` },
|
|
@@ -5445,7 +5452,9 @@ ${depList}
|
|
|
5445
5452
|
|
|
5446
5453
|
## 3. Styles
|
|
5447
5454
|
Import \`src/app.css\` once (e.g. in your root \`+layout.svelte\`) - it carries the
|
|
5448
|
-
\`.st-*\` page styles the screens use and the \`--sg-*\` design tokens
|
|
5455
|
+
\`.st-*\` page styles the screens use and the \`--sg-*\` design tokens of the theme
|
|
5456
|
+
picked in Studio. The tokens sit in a cascade layer (\`@layer svgrid-studio\`), so if
|
|
5457
|
+
your app already defines its own \`--sg-*\` tokens, yours keep winning.
|
|
5449
5458
|
|
|
5450
5459
|
## 4. What your app must provide
|
|
5451
5460
|
The full-app nav shell, home redirect, auth guard, RBAC bootstrap, i18n provider,
|
|
@@ -5457,7 +5466,7 @@ ${libFiles.join('\n')}
|
|
|
5457
5466
|
`;
|
|
5458
5467
|
return [
|
|
5459
5468
|
...content,
|
|
5460
|
-
{ path: 'src/app.css', description: '
|
|
5469
|
+
{ path: 'src/app.css', description: 'Theme tokens (layered, so yours win) + page styles (.st-*) - import once in your layout.', contents: appCss(project, { layer: 'svgrid-studio' }) },
|
|
5461
5470
|
{ path: 'FRAGMENT.md', description: 'How to drop this fragment into an existing SvelteKit app.', contents: fragmentMd },
|
|
5462
5471
|
];
|
|
5463
5472
|
}
|
|
@@ -58,12 +58,9 @@ export type NavItem = {
|
|
|
58
58
|
id?: string;
|
|
59
59
|
};
|
|
60
60
|
export declare function layoutFile(nav: NavItem[], opts?: {
|
|
61
|
-
accent?: string;
|
|
62
61
|
shell?: ShellConfig;
|
|
63
62
|
title?: string;
|
|
64
|
-
|
|
65
|
-
lightVars?: Record<string, string>;
|
|
66
|
-
darkVars?: Record<string, string>;
|
|
63
|
+
themeSwitch?: boolean;
|
|
67
64
|
dark?: boolean;
|
|
68
65
|
access?: boolean;
|
|
69
66
|
auth?: boolean;
|
|
@@ -666,34 +666,13 @@ export function layoutFile(nav, opts = {}) {
|
|
|
666
666
|
const brand = (shell.brand ?? '').trim() || opts.title || 'My Studio App';
|
|
667
667
|
const footer = shell.footer === undefined ? 'Built with SvGrid Studio' : shell.footer;
|
|
668
668
|
const right = style === 'sidebar' && shell.navPosition === 'right';
|
|
669
|
-
//
|
|
670
|
-
//
|
|
671
|
-
// built-in light/dark
|
|
672
|
-
// and the bare :root falls back to the mode
|
|
669
|
+
// The theme tokens themselves live in src/app.css (imported below) - see
|
|
670
|
+
// `themeTokenCss` - so the fragment, which drops this file, stays themed too.
|
|
671
|
+
// `themeSwitch` ships the built-in light/dark toggle: app.css scopes both
|
|
672
|
+
// palettes by [data-theme] on <html>, and the bare :root falls back to the mode
|
|
673
|
+
// picked in Studio (pre-hydration).
|
|
673
674
|
const defaultMode = opts.dark ? 'dark' : 'light';
|
|
674
|
-
const
|
|
675
|
-
const v = { ...(m ?? {}) };
|
|
676
|
-
if (opts.accent)
|
|
677
|
-
v['--sg-accent'] = opts.accent;
|
|
678
|
-
return v;
|
|
679
|
-
};
|
|
680
|
-
const declLines = (m) => Object.entries(m).map(([k, v]) => `${k}: ${v};`).join(' ');
|
|
681
|
-
const hasSwitch = !!(opts.lightVars && opts.darkVars);
|
|
682
|
-
let themeHead = '';
|
|
683
|
-
if (hasSwitch) {
|
|
684
|
-
const lv = withAccent(opts.lightVars);
|
|
685
|
-
const dv = withAccent(opts.darkVars);
|
|
686
|
-
const defRule = [declLines(defaultMode === 'dark' ? dv : lv), `color-scheme: ${defaultMode};`].join(' ');
|
|
687
|
-
const lightRule = [declLines(lv), 'color-scheme: light;'].join(' ');
|
|
688
|
-
const darkRule = [declLines(dv), 'color-scheme: dark;'].join(' ');
|
|
689
|
-
themeHead = `\n<svelte:head><style>:root { ${defRule} }\n:root[data-theme="light"] { ${lightRule} }\n:root[data-theme="dark"] { ${darkRule} }</style></svelte:head>\n`;
|
|
690
|
-
}
|
|
691
|
-
else {
|
|
692
|
-
const vars = withAccent(opts.themeVars);
|
|
693
|
-
const varLines = declLines(vars);
|
|
694
|
-
const rootRule = [varLines, opts.dark ? 'color-scheme: dark;' : ''].filter(Boolean).join(' ');
|
|
695
|
-
themeHead = rootRule ? `\n<svelte:head><style>:root { ${rootRule} }</style></svelte:head>\n` : '';
|
|
696
|
-
}
|
|
675
|
+
const hasSwitch = !!opts.themeSwitch;
|
|
697
676
|
// i18n: translate nav labels via `nav.<id>` keys (Home has no id -> literal).
|
|
698
677
|
const navLabel = opts.i18n ? `{item.id ? $t('nav.' + item.id, item.label) : item.label}` : '{item.label}';
|
|
699
678
|
const anchor = `<a class="sv-app__link" class:is-active={page.url.pathname === item.href} href={item.href}>${navLabel}</a>`;
|
|
@@ -932,7 +911,7 @@ export function layoutFile(nav, opts = {}) {
|
|
|
932
911
|
$effect(() => { void page.url.pathname; navOpen = false })${hasSwitch ? `
|
|
933
912
|
// Light/dark switcher: defaults to the mode picked in Studio, then honours the
|
|
934
913
|
// visitor's saved choice. Applies via [data-theme] on <html> (see the token
|
|
935
|
-
// sets in
|
|
914
|
+
// sets in app.css), and persists per browser.
|
|
936
915
|
let theme = $state<'light' | 'dark'>('${defaultMode}')
|
|
937
916
|
function applyTheme(t: 'light' | 'dark') {
|
|
938
917
|
theme = t
|
|
@@ -970,7 +949,6 @@ export function layoutFile(nav, opts = {}) {
|
|
|
970
949
|
// Close both menus on route change.
|
|
971
950
|
$effect(() => { void page.url.pathname; bellOpen = false; menuOpen = false })` : ''}
|
|
972
951
|
</script>
|
|
973
|
-
${themeHead}
|
|
974
952
|
|
|
975
953
|
${opts.auth ? `{#if ${JSON.stringify(opts.authRoutes ?? ['/login'])}.includes(page.url.pathname)}
|
|
976
954
|
{@render children()}
|
package/dist/studio/init-flow.js
CHANGED
|
@@ -335,14 +335,14 @@ async function askScreens(schemas, flags, prompts) {
|
|
|
335
335
|
async function askTheme(flags, prompts) {
|
|
336
336
|
let preset = flags.theme?.trim().toLowerCase();
|
|
337
337
|
if (preset && !getStudioTheme(preset)) {
|
|
338
|
-
prompts.say(` ! Unknown theme "${preset}" - using
|
|
338
|
+
prompts.say(` ! Unknown theme "${preset}" - using ember.`);
|
|
339
339
|
preset = undefined;
|
|
340
340
|
}
|
|
341
341
|
if (!preset && !flags.yes) {
|
|
342
342
|
const i = await pick(prompts, 'Theme:', studioThemes.map((t) => ({ label: t.name, hint: t.id })));
|
|
343
343
|
preset = studioThemes[i]?.id;
|
|
344
344
|
}
|
|
345
|
-
preset ?? (preset = '
|
|
345
|
+
preset ?? (preset = 'ember');
|
|
346
346
|
const dark = flags.dark ?? (flags.yes ? false : isYes(await prompts.ask('Dark mode? (y/N)', 'n'), false));
|
|
347
347
|
return { preset, mode: dark ? 'dark' : 'light' };
|
|
348
348
|
}
|
|
@@ -16,6 +16,14 @@ export type ScaffoldAppOptions = {
|
|
|
16
16
|
dataSource?: ScaffoldOptions['dataSource'];
|
|
17
17
|
/** App title shown in the nav + home. Default `'Data App'`. */
|
|
18
18
|
appTitle?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Theme preset id (one of `@svgrid/grid/themes`; default: the house Ember
|
|
21
|
+
* preset) and starting mode. The tokens are emitted in the page's
|
|
22
|
+
* `<svelte:head>` inside `@layer svgrid-studio`, so an app that already
|
|
23
|
+
* defines its own `--sg-*` tokens keeps its look.
|
|
24
|
+
*/
|
|
25
|
+
theme?: string;
|
|
26
|
+
dark?: boolean;
|
|
19
27
|
};
|
|
20
28
|
export declare function scaffoldApp(schemas: EntitySchema[], options?: ScaffoldAppOptions): {
|
|
21
29
|
files: GeneratedFile[];
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { linkRelationLabels, titleCase } from '../schema.js';
|
|
13
13
|
import { scaffold, MANAGED_START, MANAGED_END } from './scaffold.js';
|
|
14
|
+
import { themeTokenCss } from './themes.js';
|
|
14
15
|
const MANAGED_NOTE = '// Regenerated by SvGrid Studio. Edits inside these markers are overwritten.';
|
|
15
16
|
const managed = (body) => `${MANAGED_START}\n${MANAGED_NOTE}\n${body}\n${MANAGED_END}`;
|
|
16
|
-
function layoutFile(nav, appTitle) {
|
|
17
|
+
function layoutFile(nav, appTitle, theme) {
|
|
17
18
|
return {
|
|
18
19
|
path: 'src/routes/+layout.svelte',
|
|
19
20
|
description: 'App shell: a collapsible nav sidebar linking every entity screen (an off-canvas drawer on tablet/phone), with the routed page in the main area.',
|
|
@@ -40,6 +41,10 @@ ${managed(` let { children } = $props()
|
|
|
40
41
|
})`)}
|
|
41
42
|
</script>
|
|
42
43
|
|
|
44
|
+
<svelte:head><style>
|
|
45
|
+
${themeTokenCss(theme, { layer: 'svgrid-studio' })}
|
|
46
|
+
</style></svelte:head>
|
|
47
|
+
|
|
43
48
|
<div class="app-shell" class:is-drawer={drawer} class:is-navopen={navOpen}>
|
|
44
49
|
<header class="app-shell__mobilebar">
|
|
45
50
|
<button type="button" class="app-shell__burger" aria-label="Toggle navigation" aria-expanded={navOpen} onclick={() => (navOpen = !navOpen)}>
|
|
@@ -138,6 +143,6 @@ export function scaffoldApp(schemas, options = {}) {
|
|
|
138
143
|
files.push(...scaffold(schema, { dataSource: options.dataSource }).files);
|
|
139
144
|
}
|
|
140
145
|
const nav = linked.map((s) => ({ href: `/${s.name}`, label: s.label ?? titleCase(s.name) }));
|
|
141
|
-
files.push(layoutFile(nav, appTitle), homeFile(nav, appTitle));
|
|
146
|
+
files.push(layoutFile(nav, appTitle, { preset: options.theme, mode: options.dark ? 'dark' : 'light' }), homeFile(nav, appTitle));
|
|
142
147
|
return { files };
|
|
143
148
|
}
|
|
@@ -27,6 +27,14 @@ export type GeneratedFile = {
|
|
|
27
27
|
export type ScaffoldOptions = {
|
|
28
28
|
/** Route segment for the page, defaults to the schema name. e.g. `'customers'`. */
|
|
29
29
|
route?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Theme preset id (one of `@svgrid/grid/themes`; default: the house Ember
|
|
32
|
+
* preset) and starting mode. The tokens are emitted in the page's
|
|
33
|
+
* `<svelte:head>` inside `@layer svgrid-studio`, so an app that already
|
|
34
|
+
* defines its own `--sg-*` tokens keeps its look.
|
|
35
|
+
*/
|
|
36
|
+
theme?: string;
|
|
37
|
+
dark?: boolean;
|
|
30
38
|
/** API route the page talks to, defaults to `/api/{route}`. */
|
|
31
39
|
apiRoute?: string;
|
|
32
40
|
/** `$lib` path for the generated schema module, defaults to `$lib/{route}.schema`. */
|
package/dist/studio/scaffold.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* omitted from the emitted schema with a note to re-add them by hand.
|
|
15
15
|
*/
|
|
16
16
|
import { resolveIdField } from '../schema.js';
|
|
17
|
+
import { themeTokenCss } from './themes.js';
|
|
17
18
|
const DRIVERS = {
|
|
18
19
|
postgres: {
|
|
19
20
|
imports: `import pg from 'pg'`,
|
|
@@ -257,6 +258,10 @@ ${managed(serverBody)}
|
|
|
257
258
|
${managed(pageBody)}
|
|
258
259
|
</script>
|
|
259
260
|
|
|
261
|
+
<svelte:head><style>
|
|
262
|
+
${themeTokenCss({ preset: options.theme, mode: options.dark ? 'dark' : 'light' }, { layer: 'svgrid-studio' })}
|
|
263
|
+
</style></svelte:head>
|
|
264
|
+
|
|
260
265
|
<div class="page">
|
|
261
266
|
<header class="page__toolbar">
|
|
262
267
|
<h2 class="page__title">${label}</h2>
|