@svgrid/enterprise 3.1.3 → 3.1.5

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 (56) hide show
  1. package/LICENSE +22 -17
  2. package/README.md +7 -7
  3. package/dist/advanced-filter-enable.js +2 -2
  4. package/dist/alerts.js +2 -2
  5. package/dist/board.js +2 -2
  6. package/dist/cdn/svgrid-enterprise.svelte-external.js +13969 -13515
  7. package/dist/designer/assets/GridMenus-CvcASlKv.js +8 -0
  8. package/dist/designer/assets/SvListBox-BlmmzYcU.js +110 -0
  9. package/dist/designer/assets/{index-CScGnzuX.js → index-B3WBezq_.js} +432 -370
  10. package/dist/designer/assets/index-KmSEY7Lb.css +1 -0
  11. package/dist/designer/assets/src-D4PRZJYA.js +58 -0
  12. package/dist/designer/assets/{validate-YxSrBF1H.js → validate-B71dE1Cs.js} +1 -1
  13. package/dist/designer/index.html +4 -4
  14. package/dist/first-party.d.ts +2 -0
  15. package/dist/first-party.js +11 -0
  16. package/dist/gantt/gantt.js +2 -2
  17. package/dist/install.js +2 -2
  18. package/dist/license-core.d.ts +1 -0
  19. package/dist/license-core.js +25 -23
  20. package/dist/license-hash.d.ts +1 -0
  21. package/dist/license-hash.js +6 -0
  22. package/dist/license.js +55 -30
  23. package/dist/node/studio.js +531 -136
  24. package/dist/pivot-enable.js +2 -2
  25. package/dist/scheduler.js +2 -2
  26. package/dist/selection-bar.js +2 -2
  27. package/dist/server/enable.js +2 -2
  28. package/dist/server/server-row-model.js +199 -26
  29. package/dist/sha256.d.ts +1 -0
  30. package/dist/sha256.js +69 -0
  31. package/dist/studio/emit-project.js +86 -56
  32. package/dist/studio/emit-schema.d.ts +3 -0
  33. package/dist/studio/emit-schema.js +128 -29
  34. package/dist/studio/index.d.ts +2 -1
  35. package/dist/studio/index.js +2 -1
  36. package/dist/studio/init-flow.d.ts +3 -0
  37. package/dist/studio/init-flow.js +5 -2
  38. package/dist/studio/project.d.ts +10 -1
  39. package/dist/studio/project.js +28 -15
  40. package/dist/studio/samples/restaurant.js +1 -1
  41. package/dist/studio/sveltekit3.d.ts +38 -0
  42. package/dist/studio/sveltekit3.js +194 -0
  43. package/dist/studio/ui-components.generated.js +15 -4
  44. package/dist/upgrade-prompt.js +3 -0
  45. package/dist/version.d.ts +2 -2
  46. package/dist/version.js +2 -2
  47. package/dist/watermark.js +37 -4
  48. package/dist/wc/{GridMenus-6_fF5jIm.js → GridMenus-fWbBUiRH.js} +121 -121
  49. package/dist/wc/sv-sheet-element.js +11090 -10547
  50. package/dist/wc/{svelte-virtualizer.svelte-Du4tjrIP.js → svelte-virtualizer.svelte-Dntdh8Ms.js} +103 -56
  51. package/dist/wc/{validate-JAuMsk6Z.js → validate-43C03DLP.js} +1 -1
  52. package/package.json +3 -3
  53. package/dist/designer/assets/GridMenus-DGZJKFvX.js +0 -8
  54. package/dist/designer/assets/SvListBox-v2jm0-vf.js +0 -110
  55. package/dist/designer/assets/index-l21iGkpF.css +0 -1
  56. package/dist/designer/assets/src-DL33yzkY.js +0 -58
@@ -13,6 +13,7 @@ export { introspectDatabase, listDatabaseTables, countTableRows, probeConnection
13
13
  export { buildConnectionString, parseConnectionString, redactConnectionString, isFileDialect, DRIVER_PACKAGE, DEFAULT_PORT, } from './db-connect-string.js';
14
14
  export { scaffold, mergeManaged, skipUserOwned, MANAGED_START, MANAGED_END, } from './scaffold.js';
15
15
  export { scaffoldApp } from './scaffold-app.js';
16
+ export { toSvelteKit3, SVELTEKIT_3_DEV_DEPENDENCIES, SVELTEKIT_3_ADAPTERS, } from './sveltekit3.js';
16
17
  export { emitStudioApp, emitEntityModules, entityScreenPage, prepareEntities } from './emit-schema.js';
17
18
  export { emitStudioProject, emitStudioAppBundle, emitStudioFragment, runtimeDeps, studioDeployInfo, screenHandles, screenDataset, ctxCompletions, componentHandleMembers, ctxAmbientDts, GRID_API_MEMBERS } from './emit-project.js';
18
19
  export { sampleApps, getSampleApp } from './samples/index.js';
@@ -28,7 +29,7 @@ export { studioThemes, defaultStudioTheme, getStudioTheme, resolveThemeTokens, r
28
29
  export { createProject, defaultScreenFor, defaultBlockConfig, gridColumns, entityOf, blockPalette, addBlock, addBlockAt, addComponentBlock, removeBlock, duplicateBlock, moveBlock, reorderBlock, updateBlock, addEntity, removeEntity, updateEntity,
29
30
  // Form layout: the operations a form builder drives, on the entity's own
30
31
  // `form` so a built form travels with the entity rather than with one block.
31
- formPlan, setEntityForm, setFormColumns, addFormSection, updateFormSection, removeFormSection, moveFormSection, moveFormField, moveFormFields, setFieldConditions, updateEntityField, setFieldInput, setFieldHidden, formControlsFor, formControlSettings, suggestFormSections, addScreen, addFreestandingScreen, addScreenAction, removeScreenAction, enableScreenCode, disableScreenCode, setScreenRenderGrid, screenLayoutOf, buildDockLayout, reconcileDock, syncDockPanes, setScreenLayout, isPaneLayout, PANE_LAYOUTS, buildCanvasLayout, canvasRectOf, setCanvasRect, CANVAS_COLS, CANVAS_ROW_PX, CANVAS_GAP_PX, applyGridPreset, gridPresetLabel, GRID_PRESETS, setScreenDock, reseedScreenDock, setDockPaneTitle, dockPaneTitleOf, dockPaneIds, setLayoutOpts, gridOpts, stackOpts, splitOpts, dockOpts, canvasOpts, setHandlerBody, setScreenHandlersSource, setHandlerSteps, addHandlerStep, updateHandlerStep, moveHandlerStep, stepsToCode, compileStep, compileHandlerSteps, compileValue, compileCondition, compileTriggerStep, compileTriggerSteps, triggersOf, setTrigger, TRIGGER_EVENTS, setJob, setTenancy, tenantField, isTenantScoped, clickSlot, ssrScreenShape, ssrEligible, withSsrDefaults, setScreenRenderMode, eventSlot, rowSelectSlot, changeSlot, FORM_SUBMIT, GRID_EVENTS, addStateVar, updateStateVar, removeStateVar, stateInitExpr, stateTsType, setComponentName, setComponentBinding, componentHasBindings, componentHandleName, ON_LOAD, removeScreen, updateScreen, duplicateScreen, reorderScreen, insertBlock, setDataSource, setDeployTarget, setAuth, setDataLayer, seedUsers, defaultEntitySource, flattenBlocks, TAB_CHILD_KINDS, CONTAINER_CHILD_KINDS, CONTAINER_DISPLAY_KINDS, addTab, removeTab, renameTab, addTabBlock, addTabComponent, removeTabBlock, addAccordionSection, removeAccordionSection, renameAccordionSection, setAccordionMultiple, addAccordionBlock, addAccordionComponent, removeAccordionBlock, setEntityDataSource, entityDataSource, setTheme, setThemePreset, setShell, sanitizeProject, screenFromTemplate, addScreenFromTemplate, serializeProject, parseProject, validateProject, isProjectValid, blockColumns, blockStyleCss, blockClassName, sanitizeClassName, mergeBlockStyle, roleCanScreen, roleCanAction, CRUD_ACTIONS, } from './project.js';
32
+ formPlan, setEntityForm, setFormColumns, addFormSection, updateFormSection, removeFormSection, moveFormSection, moveFormField, moveFormFields, setFieldConditions, updateEntityField, setFieldInput, setFieldHidden, formControlsFor, formControlSettings, suggestFormSections, addScreen, addFreestandingScreen, addScreenAction, removeScreenAction, enableScreenCode, disableScreenCode, setScreenRenderGrid, screenLayoutOf, buildDockLayout, reconcileDock, syncDockPanes, setScreenLayout, isPaneLayout, PANE_LAYOUTS, buildCanvasLayout, canvasRectOf, setCanvasRect, CANVAS_COLS, CANVAS_ROW_PX, CANVAS_GAP_PX, applyGridPreset, gridPresetLabel, GRID_PRESETS, setScreenDock, reseedScreenDock, setDockPaneTitle, dockPaneTitleOf, dockPaneIds, setLayoutOpts, gridOpts, stackOpts, splitOpts, dockOpts, canvasOpts, setHandlerBody, setScreenHandlersSource, setHandlerSteps, addHandlerStep, updateHandlerStep, moveHandlerStep, stepsToCode, compileStep, compileHandlerSteps, compileValue, compileCondition, compileTriggerStep, compileTriggerSteps, triggersOf, setTrigger, TRIGGER_EVENTS, setJob, setTenancy, tenantField, isTenantScoped, clickSlot, ssrScreenShape, ssrEligible, withSsrDefaults, setScreenRenderMode, eventSlot, rowSelectSlot, changeSlot, FORM_SUBMIT, GRID_EVENTS, addStateVar, updateStateVar, removeStateVar, stateInitExpr, stateTsType, setComponentName, setComponentBinding, componentHasBindings, componentHandleName, ON_LOAD, removeScreen, updateScreen, duplicateScreen, reorderScreen, insertBlock, setDataSource, setDeployTarget, setKitVersion, setAuth, setDataLayer, seedUsers, defaultEntitySource, flattenBlocks, TAB_CHILD_KINDS, CONTAINER_CHILD_KINDS, CONTAINER_DISPLAY_KINDS, addTab, removeTab, renameTab, addTabBlock, addTabComponent, removeTabBlock, addAccordionSection, removeAccordionSection, renameAccordionSection, setAccordionMultiple, addAccordionBlock, addAccordionComponent, removeAccordionBlock, setEntityDataSource, entityDataSource, setTheme, setThemePreset, setShell, sanitizeProject, screenFromTemplate, addScreenFromTemplate, serializeProject, parseProject, validateProject, isProjectValid, blockColumns, blockStyleCss, blockClassName, sanitizeClassName, mergeBlockStyle, roleCanScreen, roleCanAction, CRUD_ACTIONS, } from './project.js';
32
33
  export { verifyScaffold, summarizeVerify, } from './verify.js';
33
34
  export { UI_COMPONENT_REGISTRY, uiComponentSpec, gridPropSurface, gridApiSettableProps, gridIconGroups, GRID_CURATED_PROPS, STANDARD_UI_EVENTS, } from './ui-components.js';
34
35
  export { buildCopilotMessages, projectFromModelText } from './copilot-core.js';
@@ -40,6 +40,9 @@ export type InitFlags = {
40
40
  /** Theme preset id. */
41
41
  theme?: string;
42
42
  dark?: boolean;
43
+ /** SvelteKit major to target. 2 (the default) boots in StackBlitz; 3 needs
44
+ * Vite 8, which does not run there yet. Saved into studio.config.json. */
45
+ kit?: 2 | 3;
43
46
  /** App title (skips the question). */
44
47
  title?: string;
45
48
  /** Take every default; ask nothing that has one. */
@@ -10,7 +10,7 @@ import { emitStudioAppBundle } from './emit-project.js';
10
10
  import { mergeManaged, skipUserOwned } from './scaffold.js';
11
11
  import { getStudioTheme, studioThemes } from './themes.js';
12
12
  import { UserError } from './user-error.js';
13
- import { serializeProject } from './project.js';
13
+ import { serializeProject, setKitVersion } from './project.js';
14
14
  const SOURCES = [
15
15
  { key: 'sample', label: 'Sample data', hint: 'a seeded pair of related tables - see a working app immediately' },
16
16
  { key: 'db', label: 'An existing database', hint: 'Postgres, MySQL, SQL Server or SQLite - reads your tables' },
@@ -465,7 +465,7 @@ export async function runStudioInit(flags, prompts, db, io, fetchText = null) {
465
465
  const title = flags.yes || flags.title ? defaultTitle : (await prompts.ask('\nApp name:', defaultTitle)).trim() || defaultTitle;
466
466
  const outDir = flags.out ?? (flags.yes ? '.' : (await prompts.ask('Write it where?', '.')).trim() || '.');
467
467
  await confirmTargetFolder(outDir, flags, prompts, io, outDir === candidateDir ? markersBefore : null);
468
- const project = crudAppFromSchemas(data.schemas, {
468
+ const built = crudAppFromSchemas(data.schemas, {
469
469
  title,
470
470
  dataSource: data.kind,
471
471
  sources: data.sources,
@@ -474,6 +474,9 @@ export async function runStudioInit(flags, prompts, db, io, fetchText = null) {
474
474
  overviewDashboard: true,
475
475
  ...(theme ? { theme } : {}),
476
476
  });
477
+ // On the model, not only on this emit: studio.config.json is written from the
478
+ // same object below, so the designer reopens it as the same SvelteKit major.
479
+ const project = flags.kit === 3 ? setKitVersion(built, 3) : built;
477
480
  const files = emitStudioAppBundle(project);
478
481
  const written = await writeAll(files, outDir, io);
479
482
  // The design itself, so `svgrid-studio designer` reopens exactly this app.
@@ -1003,6 +1003,11 @@ export type StudioProject = {
1003
1003
  /** Deploy target: picks the SvelteKit adapter + provider config the bundle emits.
1004
1004
  * Defaults to `auto` (@sveltejs/adapter-auto, which detects Vercel/Netlify/Cloudflare). */
1005
1005
  deploy?: DeployTarget;
1006
+ /** SvelteKit major the app targets. Absent means 2, the default: SvelteKit 3
1007
+ * needs Vite 8, whose Rolldown bundler does not run in StackBlitz's
1008
+ * WebContainer, so opting in trades that for v3. Saved in studio.config.json
1009
+ * so a v3 project is still v3 when it is reopened and regenerated. */
1010
+ kit?: 3;
1006
1011
  /** Server-side business-rule triggers, keyed by entity name. Enforced on the
1007
1012
  * SQL route (compiled into createKitHandlers `hooks`). */
1008
1013
  triggers?: Record<string, EntityTriggers>;
@@ -1076,7 +1081,8 @@ export declare function screenRenderMode(_project: StudioProject, screen: Screen
1076
1081
  export declare function ssrScreenShape(project: StudioProject, screen: Screen): 'grid' | 'read' | null;
1077
1082
  /** Whether a screen can be emitted as SSR-native (any supported shape). */
1078
1083
  export declare function ssrEligible(project: StudioProject, screen: Screen): boolean;
1079
- /** Set a screen's render mode ('spa' clears back to the default). */
1084
+ /** Set a screen's render mode. Both modes are stored: a screen switched to
1085
+ * 'spa' stays client-rendered even where `withSsrDefaults` would turn it on. */
1080
1086
  export declare function setScreenRenderMode(project: StudioProject, screenId: string, mode: 'ssr' | 'spa'): StudioProject;
1081
1087
  /** True when the screen should actually emit as SSR (mode is 'ssr' AND eligible). */
1082
1088
  export declare function isSsrScreen(project: StudioProject, screen: Screen): boolean;
@@ -1402,6 +1408,9 @@ export declare function duplicateScreen(project: StudioProject, screenId: string
1402
1408
  export declare function reorderScreen(project: StudioProject, screenId: string, toIndex: number): StudioProject;
1403
1409
  export declare function setDataSource(project: StudioProject, dataSource: DataSourceKind): StudioProject;
1404
1410
  /** Set the deploy target (SvelteKit adapter + provider config the bundle emits). */
1411
+ /** Target a SvelteKit major. 2 is the default and is stored as the field's
1412
+ * absence, the same way `deploy: 'auto'` is. */
1413
+ export declare function setKitVersion(project: StudioProject, kit: 2 | 3): StudioProject;
1405
1414
  export declare function setDeployTarget(project: StudioProject, deploy: DeployTarget): StudioProject;
1406
1415
  /** Enable / disable the authentication starter (login + session + hooks). */
1407
1416
  export declare function setAuth(project: StudioProject, patch: Partial<AuthConfig> & {
@@ -302,8 +302,8 @@ export function ssrScreenShape(project, screen) {
302
302
  return null;
303
303
  const source = project.dataSources?.[screen.entity];
304
304
  const kind = source?.kind ?? project.dataSource;
305
- // memory runs the source in-process; sql reuses the connected /api route via
306
- // event.fetch; rest calls the remote API straight from the server, which needs
305
+ // memory and sql both reuse the entity's /api route via event.fetch (memory's
306
+ // rows are held on the server behind it); rest calls the remote API straight from the server, which needs
307
307
  // an absolute URL to resolve there. (supabase and pglite stay SPA: pglite only
308
308
  // exists in the browser, and a Supabase read carries the signed-in user's token,
309
309
  // which a server-side call with the anon key would silently drop.)
@@ -334,11 +334,12 @@ export function ssrScreenShape(project, screen) {
334
334
  export function ssrEligible(project, screen) {
335
335
  return ssrScreenShape(project, screen) !== null;
336
336
  }
337
- /** Set a screen's render mode ('spa' clears back to the default). */
337
+ /** Set a screen's render mode. Both modes are stored: a screen switched to
338
+ * 'spa' stays client-rendered even where `withSsrDefaults` would turn it on. */
338
339
  export function setScreenRenderMode(project, screenId, mode) {
339
340
  return {
340
341
  ...project,
341
- screens: project.screens.map((s) => (s.id === screenId ? { ...s, renderMode: mode === 'ssr' ? 'ssr' : undefined } : s)),
342
+ screens: project.screens.map((s) => (s.id === screenId ? { ...s, renderMode: mode } : s)),
342
343
  };
343
344
  }
344
345
  /** True when the screen should actually emit as SSR (mode is 'ssr' AND eligible). */
@@ -346,19 +347,19 @@ export function isSsrScreen(project, screen) {
346
347
  return screenRenderMode(project, screen) === 'ssr' && ssrEligible(project, screen);
347
348
  }
348
349
  /**
349
- * Turn server rendering on for every screen that can support it, for entities
350
- * backed by a real database. Applied when a project is first generated, so a new
351
- * app reads as idiomatic SvelteKit (a `load` and form `actions`) rather than a
352
- * client SPA. Screens that already state a mode are left alone, so this never
353
- * overrides a choice somebody made.
350
+ * Turn server rendering on for every screen that can support it. Applied when a
351
+ * project is first generated, so a new app reads as idiomatic SvelteKit (a
352
+ * `load` and form `actions`) rather than a client SPA. Screens that already
353
+ * state a mode are left alone, so this never overrides a choice somebody made.
354
354
  *
355
- * Deliberately limited to sources that hold one copy of the data. In-memory and
356
- * PGlite sources are module singletons, so an SSR screen would read and write the
357
- * *server's* copy of the rows while the app's remaining SPA screens read the
358
- * browser's - create a row on one and the other never sees it. SQL and REST have
359
- * no such split: every path goes to the same database or the same remote API.
355
+ * Limited to sources that hold one copy of the data. SQL and REST always did:
356
+ * every path goes to the same database or the same remote API. In-memory rows
357
+ * are held once on the server ($lib/server/store) and every screen reaches them
358
+ * through the entity's /api route, so an SSR screen and an SPA screen see the
359
+ * same rows. PGlite lives in the browser's IndexedDB, which the server cannot
360
+ * read, so it stays client-rendered.
360
361
  */
361
- const SSR_DEFAULT_KINDS = new Set(['sql', 'rest']);
362
+ const SSR_DEFAULT_KINDS = new Set(['sql', 'rest', 'memory']);
362
363
  export function withSsrDefaults(project) {
363
364
  return {
364
365
  ...project,
@@ -1805,6 +1806,15 @@ export function setDataSource(project, dataSource) {
1805
1806
  return { ...project, dataSource };
1806
1807
  }
1807
1808
  /** Set the deploy target (SvelteKit adapter + provider config the bundle emits). */
1809
+ /** Target a SvelteKit major. 2 is the default and is stored as the field's
1810
+ * absence, the same way `deploy: 'auto'` is. */
1811
+ export function setKitVersion(project, kit) {
1812
+ if (kit === 2) {
1813
+ const { kit: _drop, ...rest } = project;
1814
+ return rest;
1815
+ }
1816
+ return { ...project, kit: 3 };
1817
+ }
1808
1818
  export function setDeployTarget(project, deploy) {
1809
1819
  if (deploy === 'auto') {
1810
1820
  const { deploy: _drop, ...rest } = project;
@@ -2034,6 +2044,9 @@ export function parseProject(json) {
2034
2044
  ...(typeof p.audit === 'boolean' ? { audit: p.audit } : {}),
2035
2045
  ...(p.i18n && typeof p.i18n === 'object' ? { i18n: p.i18n } : {}),
2036
2046
  ...(typeof p.deploy === 'string' && p.deploy !== 'auto' ? { deploy: p.deploy } : {}),
2047
+ // Without this a SvelteKit 3 project reopened in the designer would quietly
2048
+ // regenerate as SvelteKit 2: parseProject keeps only the fields it lists.
2049
+ ...(p.kit === 3 ? { kit: 3 } : {}),
2037
2050
  ...(p.triggers && typeof p.triggers === 'object' ? { triggers: p.triggers } : {}),
2038
2051
  });
2039
2052
  }
@@ -166,7 +166,7 @@ export const restaurant = {
166
166
  ]),
167
167
  // Resource scheduler: a column per table, reservations placed by time + tinted by
168
168
  // status, drag-to-reassign a table + a detail drawer (day view). Was a month calendar.
169
- schedulerScreen(orders, { id: 'reservations', title: 'Reservations', order: 2 }, { startField: 'reservationTime', titleField: 'name', colorField: 'status', resourceField: 'tableId', initialView: 'day' }),
169
+ schedulerScreen(orders, { id: 'reservations', title: 'Reservations', order: 2 }, { startField: 'reservationTime', titleField: 'customerPhone', colorField: 'status', resourceField: 'tableId', initialView: 'day' }),
170
170
  detailScreen(tables, { id: 'table-detail', title: 'Table detail', order: 3 }, {
171
171
  titleField: 'name', statusField: 'section', metricFields: ['seats'],
172
172
  related: [{ entity: 'orders', foreignKey: 'tableId', label: 'Orders', titleField: 'customerPhone', subtitleField: 'total', dateField: 'reservationTime', statusField: 'status' }],
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Turn a generated SvelteKit 2 app into a SvelteKit 3 app.
3
+ *
4
+ * SvelteKit 3 shipped on 2026-10-01. It requires Vite 8, and Vite 8's Rolldown
5
+ * bundler crashes inside StackBlitz's WebContainer ("Invalid atomic access
6
+ * index", webcontainer-core #2105 / #2107), which is why the Studio's default
7
+ * stays on SvelteKit 2 + Vite 7 (see the pin in packageJson()). A project opts
8
+ * in with `kit: 3` and gets this conversion on top of the normal emit.
9
+ *
10
+ * It is a transform over the finished file set rather than a flag threaded
11
+ * through every emitter, for two reasons. The SvelteKit 2 output stays byte for
12
+ * byte what it was, so the default carries no risk from this change. And the
13
+ * official `npx sv migrate sveltekit-3` is the oracle: it was run on a
14
+ * Studio-generated app, and every rewrite below reproduces what it did there,
15
+ * checked against SvelteKit 3's own type definitions where its docs and its
16
+ * types disagreed (they do on `json`, which is deprecated, not removed).
17
+ *
18
+ * What the migrator could not decide for us, and what was decided instead:
19
+ * - env vars must be declared in `src/env.ts`. The migrator's default schema
20
+ * falls back to '' and flags it for review. Every read in the generated app
21
+ * already handles a missing value (`|| 'dev-insecure-...'`, `?? ''`,
22
+ * `if (env.X)`), so '' is the right fallback and is stated here, not flagged.
23
+ * - `drizzle.config.ts` keeps `process.env`: drizzle-kit runs outside Vite,
24
+ * where `$app/env` does not exist. Only `env.X` (no `process.`) is rewritten.
25
+ */
26
+ import type { GeneratedFile } from './scaffold.js';
27
+ /** Toolchain SvelteKit 3 requires. Minimums are from the v3 migration guide. */
28
+ export declare const SVELTEKIT_3_DEV_DEPENDENCIES: Readonly<Record<string, string>>;
29
+ /**
30
+ * The first major of each adapter whose peerDependency is `@sveltejs/kit ^3`,
31
+ * read from npm on 2026-10-08. None of the v3 adapter behaviour changes (node's
32
+ * ORIGIN, vercel's edge runtime, cloudflare's platform APIs) reach Studio
33
+ * output, so this is a version bump only.
34
+ */
35
+ export declare const SVELTEKIT_3_ADAPTERS: Readonly<Record<string, string>>;
36
+ /** Every major version of SvelteKit the Studio can emit. */
37
+ export type KitVersion = 2 | 3;
38
+ export declare function toSvelteKit3(files: readonly GeneratedFile[]): GeneratedFile[];
@@ -0,0 +1,194 @@
1
+ /** Toolchain SvelteKit 3 requires. Minimums are from the v3 migration guide. */
2
+ export const SVELTEKIT_3_DEV_DEPENDENCIES = {
3
+ '@sveltejs/kit': '^3.0.0',
4
+ '@sveltejs/vite-plugin-svelte': '^7.0.0',
5
+ svelte: '^5.57.1',
6
+ 'svelte-check': '^4.7.5',
7
+ typescript: '^6.0.0',
8
+ vite: '^8.0.12',
9
+ };
10
+ /**
11
+ * The first major of each adapter whose peerDependency is `@sveltejs/kit ^3`,
12
+ * read from npm on 2026-10-08. None of the v3 adapter behaviour changes (node's
13
+ * ORIGIN, vercel's edge runtime, cloudflare's platform APIs) reach Studio
14
+ * output, so this is a version bump only.
15
+ */
16
+ export const SVELTEKIT_3_ADAPTERS = {
17
+ '@sveltejs/adapter-auto': '^8.0.0',
18
+ '@sveltejs/adapter-node': '^6.0.0',
19
+ '@sveltejs/adapter-vercel': '^7.0.0',
20
+ '@sveltejs/adapter-netlify': '^7.0.1',
21
+ '@sveltejs/adapter-cloudflare': '^8.0.0',
22
+ };
23
+ const SOURCE = /\.(ts|js|svelte)$/;
24
+ export function toSvelteKit3(files) {
25
+ const paths = new Set(files.map((f) => f.path));
26
+ const adapter = adapterOf(files);
27
+ const envNames = new Set();
28
+ const out = [];
29
+ for (const file of files) {
30
+ // svelte.config.js is gone: its adapter and preprocess move into vite.config.ts.
31
+ if (file.path === 'svelte.config.js')
32
+ continue;
33
+ if (file.path === 'package.json') {
34
+ out.push({ ...file, contents: packageJson3(file.contents) });
35
+ continue;
36
+ }
37
+ if (file.path === 'vite.config.ts') {
38
+ out.push({ ...file, description: `Vite config, including SvelteKit (${adapter}).`, contents: viteConfig3(file.contents, adapter) });
39
+ continue;
40
+ }
41
+ if (file.path === 'tsconfig.json') {
42
+ out.push({ ...file, contents: TSCONFIG_3 });
43
+ continue;
44
+ }
45
+ if (SOURCE.test(file.path) && file.path !== 'drizzle.config.ts') {
46
+ const contents = rewriteSource(file.contents, paths, envNames);
47
+ out.push(contents === file.contents ? file : { ...file, contents });
48
+ continue;
49
+ }
50
+ out.push(file);
51
+ }
52
+ if (envNames.size)
53
+ out.push(envModule([...envNames].sort()));
54
+ return out;
55
+ }
56
+ /* ---------------------------------------------------------------- config */
57
+ function adapterOf(files) {
58
+ const pkg = files.find((f) => f.path === 'package.json');
59
+ if (!pkg)
60
+ return '@sveltejs/adapter-auto';
61
+ const dev = (JSON.parse(pkg.contents).devDependencies ?? {});
62
+ return Object.keys(dev).find((k) => k.startsWith('@sveltejs/adapter-')) ?? '@sveltejs/adapter-auto';
63
+ }
64
+ function packageJson3(contents) {
65
+ const pkg = JSON.parse(contents);
66
+ const dev = { ...(pkg.devDependencies ?? {}) };
67
+ for (const [name, range] of Object.entries(SVELTEKIT_3_DEV_DEPENDENCIES))
68
+ dev[name] = range;
69
+ for (const name of Object.keys(dev))
70
+ if (SVELTEKIT_3_ADAPTERS[name])
71
+ dev[name] = SVELTEKIT_3_ADAPTERS[name];
72
+ pkg.devDependencies = dev;
73
+ // `#lib` is a standard subpath import now, so Node and every tool resolve it,
74
+ // not only Vite. `#lib` alone points at an index; `#lib/*` is the common case.
75
+ pkg.imports = { '#lib': './src/lib/index.js', '#lib/*': './src/lib/*' };
76
+ return JSON.stringify(pkg, null, 2) + '\n';
77
+ }
78
+ /**
79
+ * The v2 vite.config.ts with SvelteKit's config folded into the plugin call.
80
+ * Everything else in the file (the dev-server watch exclusions) is kept.
81
+ */
82
+ function viteConfig3(v2, adapter) {
83
+ const imports = `import adapter from '${adapter}'\n` +
84
+ `import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'\n`;
85
+ const withPlugin = v2.replace('sveltekit()', 'sveltekit({ preprocess: vitePreprocess(), adapter: adapter() })');
86
+ if (withPlugin === v2)
87
+ throw new Error('toSvelteKit3: vite.config.ts has no sveltekit() call to configure');
88
+ return imports + withPlugin;
89
+ }
90
+ // `$app/tsconfig` replaces the generated ./.svelte-kit/tsconfig.json and brings
91
+ // the compiler options that used to be listed here. Matches the migrator.
92
+ const TSCONFIG_3 = `{
93
+ "extends": "$app/tsconfig",
94
+ "compilerOptions": {
95
+ "sourceMap": true,
96
+ "strict": true
97
+ },
98
+ "include": ["src", "vite.config.ts"]
99
+ }
100
+ `;
101
+ /* --------------------------------------------------------------- sources */
102
+ function rewriteSource(src, paths, envNames) {
103
+ let s = src;
104
+ s = rewriteLibImports(s, paths);
105
+ s = rewriteEnv(s, envNames);
106
+ s = rewriteKitImports(s);
107
+ // keepFocus + noScroll are removed. `reset: false` is defined in v3's types as
108
+ // "the current scroll position and focused element are left alone", which is
109
+ // exactly what the two of them did together.
110
+ s = s.replace(/\{\s*keepFocus:\s*true,\s*noScroll:\s*true\s*\}/g, '{ reset: false }');
111
+ // page.url is readonly in v3, so `page.url.searchParams` is a
112
+ // ReadonlyURLSearchParams, which the URLSearchParams constructor does not
113
+ // accept. The generated code only copies it, never mutates it, so copying
114
+ // from the query string is the same thing and type-checks on both majors.
115
+ // The official migrator lists this as a manual task; the server-sorted list
116
+ // screen hit it, and only `verify:app --all-paths` reaches that screen.
117
+ s = s.replace(/new URLSearchParams\(page\.url\.searchParams\)/g, 'new URLSearchParams(page.url.search)');
118
+ return s;
119
+ }
120
+ /**
121
+ * `$lib/x` -> `#lib/x.js`. Subpath imports need a real extension, and the right
122
+ * one depends on what the file actually is, so each specifier is resolved
123
+ * against the generated paths rather than guessed: `x.ts` and `x.svelte.ts`
124
+ * are imported as `.js` (TypeScript maps it back), anything already carrying
125
+ * its extension (`.svelte`, `.js`) is left as it is, and a directory resolves
126
+ * to its index.
127
+ */
128
+ function rewriteLibImports(src, paths) {
129
+ return src.replace(/(['"`])\$lib\/([^'"`]+)\1/g, (whole, q, spec) => {
130
+ const base = `src/lib/${spec}`;
131
+ if (paths.has(base))
132
+ return `${q}#lib/${spec}${q}`;
133
+ if (paths.has(`${base}.ts`) || paths.has(`${base}.js`))
134
+ return `${q}#lib/${spec}.js${q}`;
135
+ if (paths.has(`${base}/index.ts`) || paths.has(`${base}/index.js`))
136
+ return `${q}#lib/${spec}/index.js${q}`;
137
+ // Not a file this app generated (a user-added module, or one emitted
138
+ // conditionally elsewhere). `.js` is what the migrator writes for a TS
139
+ // source, which is overwhelmingly the case in a Studio app.
140
+ return `${q}#lib/${spec}.js${q}`;
141
+ });
142
+ }
143
+ /**
144
+ * `import { env } from '$env/dynamic/private'` + `env.X`
145
+ * -> `import { X } from '$app/env/private'` + `X`
146
+ * Every name read is collected so src/env.ts can declare it, which v3 requires.
147
+ */
148
+ function rewriteEnv(src, envNames) {
149
+ if (!/from\s+['"]\$env\/dynamic\/private['"]/.test(src))
150
+ return src;
151
+ const used = new Set();
152
+ // `(?<![.\w])` keeps `process.env.X` untouched; only the imported `env` changes.
153
+ const body = src.replace(/(?<![.\w])env\.([A-Z_][A-Z0-9_]*)/g, (_m, name) => {
154
+ used.add(name);
155
+ return name;
156
+ });
157
+ for (const n of used)
158
+ envNames.add(n);
159
+ const names = [...used].sort();
160
+ return body.replace(/import\s*\{\s*env\s*\}\s*from\s*(['"])\$env\/dynamic\/private\1/, names.length ? `import { ${names.join(', ')} } from '$app/env/private'` : '');
161
+ }
162
+ /**
163
+ * Types that moved out of `@sveltejs/kit`, and the deprecated `json` helper.
164
+ * `fail`, `redirect` and `error` stay where they are.
165
+ */
166
+ function rewriteKitImports(src) {
167
+ let s = src;
168
+ s = s.replace(/import\s+type\s*\{\s*Handle\s*\}\s*from\s*(['"])@sveltejs\/kit\1/g, `import type { Handle } from '@sveltejs/kit/hooks'`);
169
+ s = s.replace(/import\s+type\s*\{\s*SubmitFunction\s*\}\s*from\s*(['"])@sveltejs\/kit\1/g, `import type { SubmitFunction } from '$app/forms'`);
170
+ // json(...) -> Response.json(...): same (data, init) signature. Only when
171
+ // `json` really is the kit helper - a file importing it from somewhere else,
172
+ // or calling res.json(), is not touched.
173
+ const kitImport = /import\s*\{([^}]*)\}\s*from\s*(['"])@sveltejs\/kit\2/;
174
+ const names = (s.match(kitImport)?.[1] ?? '').split(',').map((x) => x.trim());
175
+ if (names.includes('json')) {
176
+ s = s.replace(/(?<![.\w])json\(/g, 'Response.json(');
177
+ const rest = names.filter((x) => x && x !== 'json');
178
+ s = s.replace(kitImport, rest.length ? `import { ${rest.join(', ')} } from '@sveltejs/kit'` : '');
179
+ }
180
+ return s;
181
+ }
182
+ /* ------------------------------------------------------------------- env */
183
+ function envModule(names) {
184
+ const decls = names.map((n) => ` ${n}: { schema: (input) => input ?? '' },`).join('\n');
185
+ return {
186
+ path: 'src/env.ts',
187
+ description: 'Environment variables the app reads (SvelteKit 3 requires declaring them).',
188
+ contents: `import { defineEnvVars } from '@sveltejs/kit/env'\n\n` +
189
+ `// SvelteKit 3 reads only the variables declared here. A missing one comes\n` +
190
+ `// through as '' - every read in this app already falls back on that\n` +
191
+ `// (a dev default, '?? ""', or an if), so that is the intended behaviour.\n` +
192
+ `export const variables = defineEnvVars({\n${decls}\n})\n`,
193
+ };
194
+ }
@@ -6943,14 +6943,14 @@ export const GENERATED_UI_SURFACE = {
6943
6943
  "key": "columnVirtualization",
6944
6944
  "label": "Column Virtualization",
6945
6945
  "type": "boolean",
6946
- "description": "Render only the columns in view, the horizontal counterpart of `virtualization`. **On by default**, which is what makes a 100-column grid scroll smoothly. It recycles column DOM nodes, so it cannot coexist with sticky pinned columns - turn it off if you need pinning to survive horizontal scrolling.",
6946
+ "description": "Render only the columns in view, the horizontal counterpart of `virtualization`. **On by default**, which is what makes a 100-column grid scroll smoothly. Pinned columns stay rendered while it is on: the pinned-left and pinned-right columns are drawn as their own runs either side of the virtual window, so a wide grid with pinned edges still renders only the columns in view plus the pinned ones.",
6947
6947
  "group": "common"
6948
6948
  },
6949
6949
  {
6950
6950
  "key": "columnOverscan",
6951
6951
  "label": "Column Overscan",
6952
6952
  "type": "number",
6953
- "description": "Extra columns rendered either side of the viewport when `columnVirtualization` is on. Defaults to 3.",
6953
+ "description": "Columns kept rendered beyond the viewport when `columnVirtualization` is on: this many ahead of a horizontal scroll at all times, one behind it. The rendered columns move three at a time, so right after a move up to `columnOverscan + 2` are rendered ahead. Defaults to 3.",
6954
6954
  "group": "common"
6955
6955
  },
6956
6956
  {
@@ -6964,7 +6964,7 @@ export const GENERATED_UI_SURFACE = {
6964
6964
  "key": "initialColumnPinning",
6965
6965
  "label": "Initial Column Pinning",
6966
6966
  "type": "json",
6967
- "description": "Columns pinned to the left/right edge on mount. Each entry is a column id (matches `ColumnDef.field` when no explicit id is set). The internal pinning state is seeded once at mount; user-driven pinning via the column menu still works and overrides this default. Requires `columnVirtualization={false}` to be visible in the menu UI (sticky positioning can't co-exist with column virtualization since the virtualizer recycles DOM nodes).",
6967
+ "description": "Columns pinned to the left/right edge on mount. Each entry is a column id (matches `ColumnDef.field` when no explicit id is set). The internal pinning state is seeded once at mount; user-driven pinning via the column menu still works and overrides this default. Works with column virtualization on (the default).",
6968
6968
  "group": "common"
6969
6969
  },
6970
6970
  {
@@ -7245,7 +7245,7 @@ export const GENERATED_UI_SURFACE = {
7245
7245
  "key": "conditionalFormats",
7246
7246
  "label": "Conditional Formats",
7247
7247
  "type": "json",
7248
- "description": "Excel-style conditional formatting. A list of value-driven rules that color cells: `colorScale` (gradient across the column range), `dataBar` (in-cell proportional bar), `iconSet` (arrows / traffic / triangles by threshold), and `rule` (apply a style when a predicate matches). Scope a format to specific columns with `columns: [...]`, or omit it to apply to every column. Later entries win on conflict.",
7248
+ "description": "Excel-style conditional formatting. A list of value-driven rules that color cells: `colorScale` (gradient across the column range), `dataBar` (in-cell proportional bar), `iconSet` (arrows / traffic / triangles by threshold), and `rule` (apply a style when a predicate matches). Scope a format to specific columns with `columns: [...]`, or omit it to apply to every column. Later entries win on conflict. `colorScale` and `dataBar` apply to data rows only: group, footer and grand-total rows carry subtotals on a different scale, so they keep the `rule` and `iconSet` formats and drop the scaled ones. On a server row model, give scaled formats `minValue` / `maxValue`; the grid does not scan rows it has not loaded for a range.",
7249
7249
  "group": "common"
7250
7250
  },
7251
7251
  {
@@ -7369,6 +7369,17 @@ export const GENERATED_UI_SURFACE = {
7369
7369
  "description": "Merged cells: rectangles drawn as one cell, a spreadsheet's Merge & Center. Each entry is the top-left cell (display indices) and how many rows and columns it covers. The origin's td takes the span and shows the origin's value; the covered cells are not drawn, a selection grows to whole merges, the active cell inside a merge is its origin, and the arrow keys step over a merge as one cell. A merge that crosses the frozen boundary or the rendered window is drawn in parts, one per band. The grid does not write into covered cells on its own; a consumer that merges cells keeps them empty or marks them read-only through the column's `editable`.",
7370
7370
  "group": "common"
7371
7371
  },
7372
+ {
7373
+ "key": "rowLayout",
7374
+ "label": "Row Layout",
7375
+ "type": "select",
7376
+ "options": [
7377
+ "flex",
7378
+ "table"
7379
+ ],
7380
+ "description": "How rows are laid out. `'flex'` (the default) draws each row as a line of fixed-width cells, so adding or removing cells in one row - what a horizontal scroll does every frame - lays out that row only. `'table'` uses the HTML table algorithm, which re-lays every row on such a change but can draw a merged cell spanning rows; the grid switches to it on its own when `mergedCells` has one.",
7381
+ "group": "common"
7382
+ },
7372
7383
  {
7373
7384
  "key": "pinnedBottomRows",
7374
7385
  "label": "Pinned Bottom Rows",
@@ -16,6 +16,7 @@
16
16
  // - SSR-safe: every DOM access is guarded. On the server it's a no-op.
17
17
  // - Zero dependencies, inline styles - no CSS file to import, nothing to
18
18
  // bundle, works in any host app regardless of its styling.
19
+ import { isFirstPartySite } from './first-party.js';
19
20
  const CARD_ATTR = 'data-svgrid-enterprise-upgrade';
20
21
  const PRICING_URL = 'https://svgrid.com/pricing';
21
22
  // ?ref=in-app lets us measure how many trials start from this exact prompt
@@ -32,6 +33,8 @@ export function showUpgradePrompt(feature, opts) {
32
33
  return;
33
34
  if (typeof document === 'undefined' || typeof window === 'undefined')
34
35
  return;
36
+ if (isFirstPartySite())
37
+ return;
35
38
  if (document.querySelector(`[${CARD_ATTR}]`))
36
39
  return;
37
40
  shownThisSession = true;
package/dist/version.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
12
12
  */
13
- export declare const SVGRID_VERSION = "3.1.3";
13
+ export declare const SVGRID_VERSION = "3.1.5";
14
14
  /**
15
15
  * The version of `@svgrid/grid`, which releases on its own line: enterprise is
16
16
  * on 3.1.x while the grid is on 3.0.x.
@@ -23,4 +23,4 @@ export declare const SVGRID_VERSION = "3.1.3";
23
23
  *
24
24
  * `version.test.ts` asserts this matches packages/grid/package.json.
25
25
  */
26
- export declare const GRID_VERSION = "3.0.8";
26
+ export declare const GRID_VERSION = "3.0.9";
package/dist/version.js CHANGED
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
12
12
  */
13
- export const SVGRID_VERSION = '3.1.3';
13
+ export const SVGRID_VERSION = '3.1.5';
14
14
  /**
15
15
  * The version of `@svgrid/grid`, which releases on its own line: enterprise is
16
16
  * on 3.1.x while the grid is on 3.0.x.
@@ -23,4 +23,4 @@ export const SVGRID_VERSION = '3.1.3';
23
23
  *
24
24
  * `version.test.ts` asserts this matches packages/grid/package.json.
25
25
  */
26
- export const GRID_VERSION = '3.0.8';
26
+ export const GRID_VERSION = '3.0.9';
package/dist/watermark.js CHANGED
@@ -1,9 +1,10 @@
1
1
  // Evaluation UX for unlicensed @svgrid/enterprise use. Shows a small clickable
2
2
  // "www.svgrid.com" watermark in the bottom-right corner of every SvGrid on
3
3
  // the page, which fades out after 5 seconds, and emits a one-time console
4
- // nudge. Hard errors (revoked / bad-prefix keys) still throw via license.ts -
5
- // this file is only for the "no key set" case where the feature continues to
6
- // work but the user gets nudged.
4
+ // nudge. Shown for "no key" and for a key that is not the license key alike;
5
+ // neither throws, the feature keeps working and the user gets nudged. Never
6
+ // shown on svgrid.com itself (first-party.ts).
7
+ import { isFirstPartySite } from './first-party.js';
7
8
  const WATERMARK_ATTR = 'data-svgrid-enterprise-watermark';
8
9
  // Marks a grid that has already shown (and faded) its watermark, so the
9
10
  // MutationObserver doesn't re-add it once it fades out and is removed.
@@ -21,6 +22,8 @@ const FADE_MS = 700;
21
22
  let consoleNudgeShown = false;
22
23
  let observer = null;
23
24
  export function emitUnlicensedNudge() {
25
+ if (isFirstPartySite())
26
+ return;
24
27
  showConsoleOnce();
25
28
  if (typeof document === 'undefined' || typeof window === 'undefined')
26
29
  return;
@@ -29,10 +32,40 @@ export function emitUnlicensedNudge() {
29
32
  queueMicrotask(() => attachToAllGrids());
30
33
  // Watch for grids that mount later (route changes, dynamic loads).
31
34
  if (!observer) {
32
- observer = new MutationObserver(() => attachToAllGrids());
35
+ observer = new MutationObserver(onMutations);
33
36
  observer.observe(document.body, { childList: true, subtree: true });
34
37
  }
35
38
  }
39
+ /**
40
+ * The observer sees every DOM change on the page, and a scrolling grid
41
+ * changes the DOM every frame (cells in, cells out). Changes inside a grid
42
+ * that already has its watermark cannot add a grid, so they are skipped, and
43
+ * the rest wait for one check per frame instead of a document-wide query per
44
+ * mutation batch - which had made an unlicensed grid scroll measurably slower.
45
+ */
46
+ let checkScheduled = false;
47
+ function onMutations(records) {
48
+ for (const record of records) {
49
+ const target = record.target;
50
+ if (target.nodeType === 1 && target.closest?.(`.sv-grid-root[${NUDGED_ATTR}]`))
51
+ continue;
52
+ scheduleCheck();
53
+ return;
54
+ }
55
+ }
56
+ function scheduleCheck() {
57
+ if (checkScheduled)
58
+ return;
59
+ checkScheduled = true;
60
+ const run = () => {
61
+ checkScheduled = false;
62
+ attachToAllGrids();
63
+ };
64
+ if (typeof requestAnimationFrame === 'function')
65
+ requestAnimationFrame(run);
66
+ else
67
+ setTimeout(run, 16);
68
+ }
36
69
  export function dismissUnlicensedNudge() {
37
70
  if (observer) {
38
71
  observer.disconnect();