@svgrid/enterprise 2.6.3 → 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.
Files changed (70) hide show
  1. package/dist/SvGridBulkEditDrawer.svelte +158 -0
  2. package/dist/SvGridBulkEditDrawer.svelte.d.ts +9 -0
  3. package/dist/SvGridSelectionBar.svelte +452 -0
  4. package/dist/SvGridSelectionBar.svelte.d.ts +6 -0
  5. package/dist/SvPivotDesigner.svelte +3 -3
  6. package/dist/SvPivotDesigner.svelte.d.ts +3 -3
  7. package/dist/bulk-edit.d.ts +64 -0
  8. package/dist/bulk-edit.js +152 -0
  9. package/dist/cdn/svgrid-enterprise.svelte-external.js +6232 -5824
  10. package/dist/designer/assets/GridMenus-Cgilexpk.js +8 -0
  11. package/dist/designer/assets/SvDateRangeInput-BV5OXYQ2.css +1 -0
  12. package/dist/designer/assets/SvDateRangeInput-CVn-yqrQ.js +1 -0
  13. package/dist/designer/assets/SvDateTimePicker-BdioTrJQ.js +1 -0
  14. package/dist/designer/assets/SvDateTimePicker-CY8TK4BT.css +1 -0
  15. package/dist/designer/assets/SvField-BsqiA5me.js +1 -0
  16. package/dist/designer/assets/SvGridCellEditor-dLY7_kk9.js +2 -0
  17. package/dist/designer/assets/SvListBox-CVYQcLX9.js +1 -0
  18. package/dist/designer/assets/{SvListBox-BMSfFJJT.css → SvListBox-DgN8kvjt.css} +1 -1
  19. package/dist/designer/assets/column-resize-C_inr5zT.js +1 -0
  20. package/dist/designer/assets/date-format-4DJvNtG2.js +1 -0
  21. package/dist/designer/assets/{SvDateTimePicker-Cygij-dr.css → date-format-Dzy4mTGh.css} +1 -1
  22. package/dist/designer/assets/dismissable-BO547MH9.js +1 -0
  23. package/dist/designer/assets/editor-registry-BjP16H0v.js +1 -0
  24. package/dist/designer/assets/{index-aTVomkp-.js → index-BvaLmQSd.js} +128 -128
  25. package/dist/designer/assets/index-Cp7fDJ3b.css +1 -0
  26. package/dist/designer/assets/{js-scroller.svelte-C7LaeUEW.js → js-scroller.svelte-Bhxqb3-c.js} +2 -2
  27. package/dist/designer/assets/preload-helper-CG0n22O7.js +1 -0
  28. package/dist/designer/assets/row-resize-B0FWHcNr.js +1 -0
  29. package/dist/designer/assets/src-7qOYsBug.js +2896 -0
  30. package/dist/designer/assets/src-B7DNcoQK.css +1 -0
  31. package/dist/designer/assets/validate-h-aslRV9.js +1 -0
  32. package/dist/designer/index.html +18 -13
  33. package/dist/export.d.ts +4 -0
  34. package/dist/import.d.ts +7 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.js +8 -0
  37. package/dist/install.d.ts +3 -0
  38. package/dist/install.js +2 -0
  39. package/dist/license-core.d.ts +1 -1
  40. package/dist/license-core.js +1 -1
  41. package/dist/node/studio.js +178 -80
  42. package/dist/pivot.d.ts +11 -0
  43. package/dist/pivot.js +6 -1
  44. package/dist/selection-bar.d.ts +7 -0
  45. package/dist/selection-bar.js +42 -0
  46. package/dist/studio/emit-project.js +22 -7
  47. package/dist/studio/emit-schema.d.ts +1 -4
  48. package/dist/studio/emit-schema.js +7 -29
  49. package/dist/studio/init-flow.js +2 -2
  50. package/dist/studio/scaffold-app.d.ts +8 -0
  51. package/dist/studio/scaffold-app.js +7 -2
  52. package/dist/studio/scaffold.d.ts +8 -0
  53. package/dist/studio/scaffold.js +5 -0
  54. package/dist/studio/themes.d.ts +17 -1
  55. package/dist/studio/themes.js +26 -1
  56. package/dist/studio/ui-components.generated.js +72 -3
  57. package/dist/version.d.ts +1 -1
  58. package/dist/version.js +1 -1
  59. package/package.json +10 -5
  60. package/dist/designer/assets/GridMenus-DuogVgW0.js +0 -7
  61. package/dist/designer/assets/SvDateTimePicker-DOBYyLeN.js +0 -1
  62. package/dist/designer/assets/SvGrid.helpers-C6wxF55r.js +0 -1
  63. package/dist/designer/assets/SvGridCellEditor-CMtmQINJ.js +0 -2
  64. package/dist/designer/assets/SvListBox-CWCIPM4P.js +0 -1
  65. package/dist/designer/assets/dismissable-2EZKoens.js +0 -1
  66. package/dist/designer/assets/editor-registry-BB1Qb5mp.js +0 -1
  67. package/dist/designer/assets/index-BfzoL904.css +0 -1
  68. package/dist/designer/assets/src-B5lLfIhK.css +0 -1
  69. package/dist/designer/assets/src-DLoHsbp5.js +0 -2893
  70. /package/dist/designer/assets/{dismissable-BF-9TJSd.css → SvField-BF-9TJSd.css} +0 -0
@@ -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), homeFile$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
@@ -3089,6 +3169,7 @@ var GENERATED_UI_SURFACE = {
3089
3169
  "key": "block",
3090
3170
  "label": "Block",
3091
3171
  "type": "boolean",
3172
+ "default": false,
3092
3173
  "description": "Fill the container instead of the control's own default width. A default width is right for a control sitting on its own; in a form grid a row of inputs each stopping at a different width reads as broken.",
3093
3174
  "group": "appearance"
3094
3175
  },
@@ -6058,6 +6139,7 @@ var GENERATED_UI_SURFACE = {
6058
6139
  "key": "block",
6059
6140
  "label": "Block",
6060
6141
  "type": "boolean",
6142
+ "default": false,
6061
6143
  "description": "Fill the container instead of the control's own default width. A default width is right for a control sitting on its own; in a form grid a row of inputs each stopping at a different width reads as broken.",
6062
6144
  "group": "appearance"
6063
6145
  },
@@ -6246,6 +6328,7 @@ var GENERATED_UI_SURFACE = {
6246
6328
  "key": "block",
6247
6329
  "label": "Block",
6248
6330
  "type": "boolean",
6331
+ "default": false,
6249
6332
  "description": "Fill the container instead of the control's own default width. A default width is right for a control sitting on its own; in a form grid a row of inputs each stopping at a different width reads as broken.",
6250
6333
  "group": "appearance"
6251
6334
  },
@@ -6440,6 +6523,7 @@ var GENERATED_UI_SURFACE = {
6440
6523
  "key": "block",
6441
6524
  "label": "Block",
6442
6525
  "type": "boolean",
6526
+ "default": false,
6443
6527
  "description": "Fill the container instead of the control's own default width. A default width is right for a control sitting on its own; in a form grid a row of inputs each stopping at a different width reads as broken.",
6444
6528
  "group": "appearance"
6445
6529
  },
@@ -8099,6 +8183,7 @@ var GENERATED_UI_SURFACE = {
8099
8183
  "key": "block",
8100
8184
  "label": "Block",
8101
8185
  "type": "boolean",
8186
+ "default": false,
8102
8187
  "description": "Fill the container instead of the control's own default width. A default width is right for a control sitting on its own; in a form grid a row of inputs each stopping at a different width reads as broken.",
8103
8188
  "group": "appearance"
8104
8189
  },
@@ -8583,6 +8668,13 @@ var GENERATED_UI_SURFACE = {
8583
8668
  "description": "Right-click context menu. `true` shows the default item set (copy, cut, paste, clear, insert row above/below, remove row, remove column). Pass an array to customize: strings are built-in keys, `\"separator\"` is a divider, and objects are custom items. Omitted/`false` disables the menu (native browser menu shows instead).",
8584
8669
  "group": "common"
8585
8670
  },
8671
+ {
8672
+ "key": "selectionBar",
8673
+ "label": "Selection Bar",
8674
+ "type": "boolean",
8675
+ "description": "Floating bar that appears over the grid while rows are selected, showing the count and the actions that apply to the whole selection - the pattern an issue tracker uses for bulk edit. `true` gives the count and a Clear button. An array is the shorthand for `{ actions }`. Pass to also set `position` (`'bottom'` default, or `'top'`), `maxVisible` and `hideClear`. Omitted / `false` is off. Default **off**. This is chrome that floats over your content, not a gesture: it overlaps a row, and what belongs on it is specific to the app. **The prop and its types are free; the bar itself is an Enterprise feature.** Install `@svgrid/enterprise` and call `enableSelectionBar()` (or `installEnterprise`, which does it for you) to render it - without that the grid shows a short upsell note in its place, the same way the scheduler and board views do.",
8676
+ "group": "common"
8677
+ },
8586
8678
  {
8587
8679
  "key": "features",
8588
8680
  "label": "Features",
@@ -8594,19 +8686,28 @@ var GENERATED_UI_SURFACE = {
8594
8686
  "key": "sortable",
8595
8687
  "label": "Sortable",
8596
8688
  "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.",
8689
+ "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
8690
  "group": "common"
8599
8691
  },
8600
8692
  {
8601
8693
  "key": "filterable",
8602
8694
  "label": "Filterable",
8603
8695
  "type": "boolean",
8696
+ "description": "Click a header to filter that column. Injects `columnFilteringFeature`, so you do not import it yourself. Which filter surface appears is a separate question - see `filterMode` (default `'menu'`). <SvGrid {data} {columns} filterable />",
8604
8697
  "group": "common"
8605
8698
  },
8606
8699
  {
8607
8700
  "key": "editable",
8608
8701
  "label": "Editable",
8609
8702
  "type": "boolean",
8703
+ "description": "Inline cell editing. Alias of `enableInlineEditing`, and wins over it when both are set. A column also needs an `editorType` to be editable; without one it falls back to a text editor. <SvGrid {data} {columns} editable />",
8704
+ "group": "common"
8705
+ },
8706
+ {
8707
+ "key": "selectable",
8708
+ "label": "Selectable",
8709
+ "type": "boolean",
8710
+ "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.",
8610
8711
  "group": "common"
8611
8712
  },
8612
8713
  {
@@ -8620,6 +8721,7 @@ var GENERATED_UI_SURFACE = {
8620
8721
  "key": "groupable",
8621
8722
  "label": "Groupable",
8622
8723
  "type": "boolean",
8724
+ "description": "Show the grouping controls: \"Group by this column\" in the column menu, and the group panel when `showGroupPanel` is on. Alias of `showGroupingControls`, and also injects `columnGroupingFeature`. This turns on the UI for grouping. To group without asking the user, set `groupBy` instead. <SvGrid {data} {columns} groupable />",
8623
8725
  "group": "common"
8624
8726
  },
8625
8727
  {
@@ -8675,12 +8777,14 @@ var GENERATED_UI_SURFACE = {
8675
8777
  "key": "pageable",
8676
8778
  "label": "Pageable",
8677
8779
  "type": "boolean",
8780
+ "description": "Show the pagination footer. Alias of `showPagination`, and wins over it when both are set. Page size starts at `pageSize` (default 10). <SvGrid {data} {columns} pageable pageSize={25} />",
8678
8781
  "group": "common"
8679
8782
  },
8680
8783
  {
8681
8784
  "key": "loading",
8682
8785
  "label": "Loading",
8683
8786
  "type": "boolean",
8787
+ "description": "Show the loading state instead of the rows. By default this replaces the grid body with \"Loading...\"; set `loadingOverlay` to keep the current rows visible under a dimmed overlay instead, which is what you usually want for a server-paged grid.",
8684
8788
  "group": "behavior"
8685
8789
  },
8686
8790
  {
@@ -8701,12 +8805,14 @@ var GENERATED_UI_SURFACE = {
8701
8805
  "key": "error",
8702
8806
  "label": "Error",
8703
8807
  "type": "string",
8808
+ "description": "Render an error message in place of the rows. `null` or omitted means no error. Takes precedence over `loading` and over the empty state.",
8704
8809
  "group": "common"
8705
8810
  },
8706
8811
  {
8707
8812
  "key": "emptyMessage",
8708
8813
  "label": "Empty Message",
8709
8814
  "type": "string",
8815
+ "description": "Text shown when there are no rows to display - either the data is empty or a filter matched nothing. Defaults to the localized `noRows` string, so prefer `localization.text.noRows` when you are translating the whole grid.",
8710
8816
  "group": "common"
8711
8817
  },
8712
8818
  {
@@ -8720,12 +8826,14 @@ var GENERATED_UI_SURFACE = {
8720
8826
  "key": "showGlobalFilter",
8721
8827
  "label": "Show Global Filter",
8722
8828
  "type": "boolean",
8829
+ "description": "Show the single search box that filters across every column. Explicitly setting this wins over `filterMode`; leaving it unset means it appears only when `filterMode` is `'global'`.",
8723
8830
  "group": "common"
8724
8831
  },
8725
8832
  {
8726
8833
  "key": "showColumnFilters",
8727
8834
  "label": "Show Column Filters",
8728
8835
  "type": "boolean",
8836
+ "description": "Show the filter section inside each column's menu. Explicitly setting this wins over `filterMode`; unset, it follows `filterMode` (default `'menu'`, so this is normally on). Setting it `true` also adds the inline \"floating filter\" input under each header - that surface requires the explicit opt-in, since it otherwise duplicates the menu's own filter popover.",
8729
8837
  "group": "common"
8730
8838
  },
8731
8839
  {
@@ -8745,18 +8853,21 @@ var GENERATED_UI_SURFACE = {
8745
8853
  "key": "showGroupingControls",
8746
8854
  "label": "Show Grouping Controls",
8747
8855
  "type": "boolean",
8856
+ "description": "Show the grouping affordances (the column menu's \"Group by this column\", and the group panel). The `groupable` shortcut sets this and registers the grouping feature in one go; prefer that unless you have already registered `columnGroupingFeature` yourself.",
8748
8857
  "group": "common"
8749
8858
  },
8750
8859
  {
8751
8860
  "key": "showRowSelection",
8752
8861
  "label": "Show Row Selection",
8753
8862
  "type": "boolean",
8863
+ "description": "Show the row-selection checkbox column, including the select-all checkbox in the header. Unset, it follows `selectionMode` (default `'both'`, so row selection is on).",
8754
8864
  "group": "common"
8755
8865
  },
8756
8866
  {
8757
8867
  "key": "showPagination",
8758
8868
  "label": "Show Pagination",
8759
8869
  "type": "boolean",
8870
+ "description": "Show the pagination footer. Off by default. `pageable` is the shortcut alias and wins when both are set.",
8760
8871
  "group": "common"
8761
8872
  },
8762
8873
  {
@@ -8810,6 +8921,7 @@ var GENERATED_UI_SURFACE = {
8810
8921
  "key": "virtualization",
8811
8922
  "label": "Virtualization",
8812
8923
  "type": "boolean",
8924
+ "description": "Render only the rows in view instead of all of them. **On by default** - this is what keeps a 100,000-row grid responsive. Turn it off when a row's height cannot be known up front and must not be clipped, such as a variable-height master-detail panel. Expect the DOM to hold every row when you do.",
8813
8925
  "group": "common"
8814
8926
  },
8815
8927
  {
@@ -8826,6 +8938,13 @@ var GENERATED_UI_SURFACE = {
8826
8938
  "description": "Size each row to its own content instead of a fixed height: cell text wraps and the row grows to fit the tallest cell. Rows are measured after they render, so this works with virtualization - `rowHeight` (or 30) is the estimate used before a row has been measured, which keeps the scrollbar stable while you scroll into new rows. Costs a measurement pass per row, so prefer a fixed `rowHeight` when your content is uniform. Ignored when `rowHeight` is a function (you are already supplying per-row heights).",
8827
8939
  "group": "common"
8828
8940
  },
8941
+ {
8942
+ "key": "rowResize",
8943
+ "label": "Row Resize",
8944
+ "type": "boolean",
8945
+ "description": "Let the user drag a row's bottom edge to change its height. **Off by default**, because a resizable row needs somewhere to grab and most grids do not want a drag target on every row. The grid remembers the heights itself, so this works as a bare boolean - no `rowHeight` function required. Drag, or focus the grip and use Up/Down (Shift for 1px steps). Turning this on also turns on the **row header column** (* showRowNumbers), because that is where the grip lives and where a spreadsheet puts it - a drag target on the edge of a data cell works but reads as an accident. An explicit `showRowNumbers={false}` still wins; the grip then falls back to the row's first cell, and a column of your own carrying `cellClass: 'sv-row-gutter'` is used ahead of either. Ignored under `autoRowHeight`, where the content decides the height and a manual one would immediately be overwritten. For full control of the heights - persisting them, sharing them between grids - pass a function-valued and use the `rowResize` action directly.",
8946
+ "group": "common"
8947
+ },
8829
8948
  {
8830
8949
  "key": "headerHeight",
8831
8950
  "label": "Header Height",
@@ -8837,6 +8956,7 @@ var GENERATED_UI_SURFACE = {
8837
8956
  "key": "overscan",
8838
8957
  "label": "Overscan",
8839
8958
  "type": "number",
8959
+ "description": "Extra rows rendered above and below the viewport, so fast scrolling does not reach empty space before the next batch renders. Defaults to 8. Raise it for very tall rows, lower it to trim DOM work.",
8840
8960
  "group": "common"
8841
8961
  },
8842
8962
  {
@@ -8850,18 +8970,21 @@ var GENERATED_UI_SURFACE = {
8850
8970
  "key": "columnVirtualization",
8851
8971
  "label": "Column Virtualization",
8852
8972
  "type": "boolean",
8973
+ "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.",
8853
8974
  "group": "common"
8854
8975
  },
8855
8976
  {
8856
8977
  "key": "columnOverscan",
8857
8978
  "label": "Column Overscan",
8858
8979
  "type": "number",
8980
+ "description": "Extra columns rendered either side of the viewport when `columnVirtualization` is on. Defaults to 3.",
8859
8981
  "group": "common"
8860
8982
  },
8861
8983
  {
8862
8984
  "key": "columnWidth",
8863
8985
  "label": "Column Width",
8864
8986
  "type": "number",
8987
+ "description": "Fallback width in pixels for columns whose `ColumnDef` sets no `width`. Defaults to 140.",
8865
8988
  "group": "common"
8866
8989
  },
8867
8990
  {
@@ -8878,6 +9001,13 @@ var GENERATED_UI_SURFACE = {
8878
9001
  "description": "When `true`, columns are scaled proportionally so their total width fills the viewport (no empty space on the right). Disabled by default - explicit `width` values are used as-is. User resizes still win once they happen.",
8879
9002
  "group": "common"
8880
9003
  },
9004
+ {
9005
+ "key": "columnResize",
9006
+ "label": "Column Resize",
9007
+ "type": "boolean",
9008
+ "description": "Let the user drag a column's edge to change its width. **Off by default**, so `width` means the width you asked for until you say otherwise. Turn it on for grids the reader is meant to arrange - a spreadsheet, a dense report, anything with columns whose content varies in length. Leave it off for layouts you control, and for columns that must keep their size: a row-number gutter, a checkbox column, an icon column. It is grid-wide rather than per-column: there is no `resizable: false` on a single column definition. `api.autosizeColumn()`, `fitColumns` and programmatic width changes work either way - this only governs the drag handle. The handles come from the `columnResize` action, loaded on demand: a grid that leaves this off never fetches that code.",
9009
+ "group": "common"
9010
+ },
8881
9011
  {
8882
9012
  "key": "responsive",
8883
9013
  "label": "Responsive",
@@ -8895,12 +9025,21 @@ var GENERATED_UI_SURFACE = {
8895
9025
  "key": "showFilterRow",
8896
9026
  "label": "Show Filter Row",
8897
9027
  "type": "boolean",
9028
+ "description": "Show the always-visible filter row under the header. Explicitly setting this wins over `filterMode`; unset, it appears only when `filterMode` is `'row'`.",
8898
9029
  "group": "common"
8899
9030
  },
8900
9031
  {
8901
9032
  "key": "enableCellSelection",
8902
9033
  "label": "Enable Cell Selection",
8903
9034
  "type": "boolean",
9035
+ "description": "Cell selection - click to select, drag or shift-click to extend to a range, which is what clipboard copy and the range fill handle operate on. Unset, it follows `selectionMode` (default `'both'`, so this is on). `selectable` is the shortcut alias and wins over it.",
9036
+ "group": "common"
9037
+ },
9038
+ {
9039
+ "key": "moveCells",
9040
+ "label": "Move Cells",
9041
+ "type": "boolean",
9042
+ "description": "Excel-style drag-and-drop of a selected cell range: grab the range's border and drag it somewhere else to MOVE the values there, or hold Ctrl (Cmd on macOS) to COPY them instead. The modifier is read at drop time, so it can be pressed or released mid-drag. On by default whenever cell selection is on, matching the fill handle. Set `false` to keep a border pointerdown starting a fresh selection, which is what it did before this existed. The grab strip is the outer 4px of the range border, so a pointerdown anywhere further inside still starts a new selection. A drop is refused outright - nothing changes - when the destination would fall outside the grid, or when any source or destination cell is read-only. It does not move the part that fits and silently drop the rest.",
8904
9043
  "group": "common"
8905
9044
  },
8906
9045
  {
@@ -8929,6 +9068,7 @@ var GENERATED_UI_SURFACE = {
8929
9068
  "key": "enableInlineEditing",
8930
9069
  "label": "Enable Inline Editing",
8931
9070
  "type": "boolean",
9071
+ "description": "Inline cell editing: F2 or double-click opens an editor in the active cell, Enter commits, Esc cancels. Off by default. A column still needs an `editorType` to pick its editor (text, number, date, checkbox, list, ...); without one it gets a plain text editor. `editable` is the shortcut alias and wins over this.",
8932
9072
  "group": "common"
8933
9073
  },
8934
9074
  {
@@ -8942,6 +9082,14 @@ var GENERATED_UI_SURFACE = {
8942
9082
  "key": "enableRowSummaries",
8943
9083
  "label": "Enable Row Summaries",
8944
9084
  "type": "boolean",
9085
+ "description": "Append a sticky footer row aggregating every filtered row: the sum of a numeric column, `Count: N` otherwise. Choose a different aggregate per column with that column's own `summary` option, or set it to `false` there to leave the cell blank. Off by default. `summary` is the shortcut alias and wins when both are set. <SvGrid {data} {columns} summary />",
9086
+ "group": "common"
9087
+ },
9088
+ {
9089
+ "key": "summary",
9090
+ "label": "Summary",
9091
+ "type": "boolean",
9092
+ "description": "Shortcut alias for . Wins over it when both are set, the same precedence `selectable` has over `enableCellSelection`.",
8945
9093
  "group": "common"
8946
9094
  },
8947
9095
  {
@@ -8969,7 +9117,7 @@ var GENERATED_UI_SURFACE = {
8969
9117
  "key": "columnMenuTabs",
8970
9118
  "label": "Column Menu Tabs",
8971
9119
  "type": "boolean",
8972
- "description": "Render the header column menu (⋮) as a tabbed popover - **General**, **Filter**, and **Columns** tabs (the AG-Grid layout). Defaults to `false`, which keeps the flat menu (actions list + \"Choose columns\" submenu).",
9120
+ "description": "Render the header column menu (⋮) as a tabbed popover - **General**, **Filter**, and **Columns** tabs (the tabbed layout). Defaults to `false`, which keeps the flat menu (actions list + \"Choose columns\" submenu).",
8973
9121
  "group": "common"
8974
9122
  },
8975
9123
  {
@@ -9249,7 +9397,8 @@ var GENERATED_UI_SURFACE = {
9249
9397
  {
9250
9398
  "key": "cellValueChange",
9251
9399
  "label": "Cell Value Change",
9252
- "prop": "onCellValueChange"
9400
+ "prop": "onCellValueChange",
9401
+ "description": "Fires after an inline edit commits, with the cell's old and new value and the row it belongs to. This is where you persist the change. The grid has already applied the edit to its own state by the time this runs, so it is a notification, not a veto - reject a value with the column's `valueParser` or a validation rule instead."
9253
9402
  },
9254
9403
  {
9255
9404
  "key": "activeCellChange",
@@ -15320,22 +15469,7 @@ function layoutFile(nav, opts = {}) {
15320
15469
  const footer = shell.footer === void 0 ? "Built with SvGrid Studio" : shell.footer;
15321
15470
  const right = style === "sidebar" && shell.navPosition === "right";
15322
15471
  const defaultMode = opts.dark ? "dark" : "light";
15323
- const withAccent = (m) => {
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
- }
15472
+ const hasSwitch = !!opts.themeSwitch;
15339
15473
  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
15474
  const linkGate = opts.access ? `{#if !item.id || canScreen($currentRole, item.id)}${anchor}{/if}` : anchor;
15341
15475
  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 +15658,7 @@ function layoutFile(nav, opts = {}) {
15524
15658
  $effect(() => { void page.url.pathname; navOpen = false })${hasSwitch ? `
15525
15659
  // Light/dark switcher: defaults to the mode picked in Studio, then honours the
15526
15660
  // visitor's saved choice. Applies via [data-theme] on <html> (see the token
15527
- // sets in <svelte:head>), and persists per browser.
15661
+ // sets in app.css), and persists per browser.
15528
15662
  let theme = $state<'light' | 'dark'>('${defaultMode}')
15529
15663
  function applyTheme(t: 'light' | 'dark') {
15530
15664
  theme = t
@@ -15562,7 +15696,6 @@ function layoutFile(nav, opts = {}) {
15562
15696
  // Close both menus on route change.
15563
15697
  $effect(() => { void page.url.pathname; bellOpen = false; menuOpen = false })` : ""}
15564
15698
  <\/script>
15565
- ${themeHead}
15566
15699
 
15567
15700
  ${opts.auth ? `{#if ${JSON.stringify(opts.authRoutes ?? ["/login"])}.includes(page.url.pathname)}
15568
15701
  {@render children()}
@@ -15728,47 +15861,6 @@ function emitStudioApp(schemas) {
15728
15861
  ];
15729
15862
  }
15730
15863
  //#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
15864
  //#region src/edit-panel.ts
15773
15865
  /** True when any field's behaviour depends on the values (i.e. has a `when`). */
15774
15866
  function hasFieldConditions(schema) {
@@ -15788,7 +15880,7 @@ function hasFieldConditions(schema) {
15788
15880
  *
15789
15881
  * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
15790
15882
  */
15791
- var SVGRID_VERSION = "2.6.3";
15883
+ var SVGRID_VERSION = "2.7.0";
15792
15884
  //#endregion
15793
15885
  //#region src/studio/emit-project.ts
15794
15886
  var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
@@ -18310,12 +18402,9 @@ function emitStudioProject(project) {
18310
18402
  ...companions,
18311
18403
  ...handleFiles,
18312
18404
  layoutFile(navExtras, {
18313
- accent: project.theme?.accent,
18314
18405
  shell: project.theme?.shell,
18315
18406
  title: project.title,
18316
- themeVars: resolveThemeTokens(project.theme),
18317
- lightVars: resolveThemeTokensFor(project.theme, "light"),
18318
- darkVars: resolveThemeTokensFor(project.theme, "dark"),
18407
+ themeSwitch: true,
18319
18408
  dark: isDarkTheme(project.theme),
18320
18409
  access: accessEnabled,
18321
18410
  auth: authEnabled && !supabaseAuth,
@@ -20268,8 +20357,15 @@ var SCAFFOLD_STATIC = [
20268
20357
  contents: `node_modules\n.svelte-kit\n/build\n.env\n.env.*\n!.env.example\n.DS_Store\n`
20269
20358
  }
20270
20359
  ];
20271
- var APP_CSS = `:root { --sg-accent: #4f46e5; color-scheme: light dark; }
20272
- * { box-sizing: border-box; }
20360
+ /** `src/app.css`: the project's theme tokens (see `themeTokenCss`) followed by the
20361
+ * `.st-*` page styles every generated screen uses. This is the ONE place the tokens
20362
+ * are emitted, so the fragment (which drops the layout) and the full app are
20363
+ * themed alike. `layer` is for drop-in outputs - see `themeTokenCss`. */
20364
+ function appCss(project, opts = {}) {
20365
+ return `${themeTokenCss(project.theme, opts)}
20366
+ ${APP_CSS_BODY}`;
20367
+ }
20368
+ var APP_CSS_BODY = `* { box-sizing: border-box; }
20273
20369
  html, body { margin: 0; height: 100%; }
20274
20370
  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
20371
  .st__title { margin: 0; font-size: 22px; font-weight: 720; letter-spacing: -0.015em; }
@@ -20813,8 +20909,8 @@ function emitStudioAppBundle(project) {
20813
20909
  }] : [],
20814
20910
  {
20815
20911
  path: "src/app.css",
20816
- description: "App theme + page styles.",
20817
- contents: APP_CSS
20912
+ description: "App theme tokens + page styles.",
20913
+ contents: appCss(project)
20818
20914
  },
20819
20915
  {
20820
20916
  path: "src/custom.css",
@@ -20889,7 +20985,9 @@ ${depList}
20889
20985
 
20890
20986
  ## 3. Styles
20891
20987
  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.
20988
+ \`.st-*\` page styles the screens use and the \`--sg-*\` design tokens of the theme
20989
+ picked in Studio. The tokens sit in a cascade layer (\`@layer svgrid-studio\`), so if
20990
+ your app already defines its own \`--sg-*\` tokens, yours keep winning.
20893
20991
 
20894
20992
  ## 4. What your app must provide
20895
20993
  The full-app nav shell, home redirect, auth guard, RBAC bootstrap, i18n provider,
@@ -20903,8 +21001,8 @@ ${libFiles.join("\n")}
20903
21001
  ...content,
20904
21002
  {
20905
21003
  path: "src/app.css",
20906
- description: "Page styles (.st-*) + design tokens - import once in your layout.",
20907
- contents: APP_CSS
21004
+ description: "Theme tokens (layered, so yours win) + page styles (.st-*) - import once in your layout.",
21005
+ contents: appCss(project, { layer: "svgrid-studio" })
20908
21006
  },
20909
21007
  {
20910
21008
  path: "FRAGMENT.md",
@@ -40918,14 +41016,14 @@ async function askScreens(schemas, flags, prompts) {
40918
41016
  async function askTheme(flags, prompts) {
40919
41017
  let preset = flags.theme?.trim().toLowerCase();
40920
41018
  if (preset && !getStudioTheme(preset)) {
40921
- prompts.say(` ! Unknown theme "${preset}" - using tailwind.`);
41019
+ prompts.say(` ! Unknown theme "${preset}" - using ember.`);
40922
41020
  preset = void 0;
40923
41021
  }
40924
41022
  if (!preset && !flags.yes) preset = studioThemes[await pick(prompts, "Theme:", studioThemes.map((t) => ({
40925
41023
  label: t.name,
40926
41024
  hint: t.id
40927
41025
  })))]?.id;
40928
- preset ??= "tailwind";
41026
+ preset ??= "ember";
40929
41027
  const dark = flags.dark ?? (flags.yes ? false : isYes(await prompts.ask("Dark mode? (y/N)", "n"), false));
40930
41028
  return {
40931
41029
  preset,
@@ -41438,7 +41536,7 @@ var REVOKED_KEYS = /* @__PURE__ */ new Set([]);
41438
41536
  * in that position) yields null, which reads as "no expiry encoded".
41439
41537
  *
41440
41538
  * Boundaries are UTC so a key does not lapse at a different instant per
41441
- * timezone. Kept in sync with `tools/issue-license.mjs`.
41539
+ * timezone. Kept in sync with the (maintainer-side) key issuer.
41442
41540
  */
41443
41541
  function parseLicenseExpiry(key) {
41444
41542
  if (!key) return null;
package/dist/pivot.d.ts CHANGED
@@ -30,8 +30,11 @@
30
30
  * ```
31
31
  */
32
32
  import type { ColumnDef, CellFormatConfig, RowData, TableFeatures } from '@svgrid/grid';
33
+ /** Built-in pivot measures, named in {@link PivotValueConfig}. */
33
34
  export type PivotAggregatorId = 'sum' | 'avg' | 'min' | 'max' | 'count' | 'countDistinct' | 'first' | 'last';
35
+ /** A custom measure: reduce the values under one cell to the number to display. */
34
36
  export type PivotAggregator = (values: ReadonlyArray<unknown>) => unknown;
37
+ /** One measure: the field to aggregate and how to aggregate it. */
35
38
  export type PivotValueConfig<TData> = {
36
39
  /** Row field whose values get aggregated. */
37
40
  field: keyof TData & string;
@@ -42,6 +45,7 @@ export type PivotValueConfig<TData> = {
42
45
  /** Optional cell formatter. */
43
46
  format?: CellFormatConfig;
44
47
  };
48
+ /** A pivot definition: the row axis, the column axis, and the measures at their intersections. */
45
49
  export type PivotConfig<TData> = {
46
50
  /** Outer-most first. Each entry becomes one level of row grouping. */
47
51
  rows: ReadonlyArray<keyof TData & string>;
@@ -60,6 +64,7 @@ export type PivotConfig<TData> = {
60
64
  /** Optional sort for row-axis values per dim level (defaults to alpha). */
61
65
  rowSort?: (a: unknown, b: unknown, level: number) => number;
62
66
  };
67
+ /** What a pivot row represents - a group header, a subtotal, a leaf, or the grand total. */
63
68
  export type PivotRowKind = 'group' | 'subtotal' | 'leaf' | 'grandTotal';
64
69
  /**
65
70
  * Shape of one entry in `result.rows`. The first-column label lives at
@@ -87,10 +92,16 @@ export type PivotRow = {
87
92
  /** Aggregated value cells - keyed by leaf column id. */
88
93
  [columnId: string]: unknown;
89
94
  };
95
+ /** A built pivot: the rows to display, and the generated column definitions for its column axis. */
90
96
  export type PivotResult<TFeatures extends TableFeatures> = {
91
97
  rows: PivotRow[];
92
98
  columns: Array<ColumnDef<TFeatures, PivotRow>>;
93
99
  };
100
+ /**
101
+ * Build a pivot table from flat rows: cross the row axis with the column axis,
102
+ * aggregate each intersection, and return rows plus generated columns ready to
103
+ * hand to `<SvGrid>`.
104
+ */
94
105
  export declare function createPivotModel<TFeatures extends TableFeatures, TData extends RowData>(data: ReadonlyArray<TData>, config: PivotConfig<TData>): PivotResult<TFeatures>;
95
106
  /**
96
107
  * Public registry of built-in aggregators. Useful for surfacing the