@svgrid/enterprise 2.6.1 → 2.6.2

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 (43) hide show
  1. package/dist/ExpressionEditorHarness.svelte +47 -0
  2. package/dist/ExpressionEditorHarness.svelte.d.ts +15 -0
  3. package/dist/SvAdvancedFilter.svelte +208 -0
  4. package/dist/SvAdvancedFilter.svelte.d.ts +37 -0
  5. package/dist/SvExpressionEditor.svelte +251 -123
  6. package/dist/SvGridEditPanel.svelte +1157 -1157
  7. package/dist/advanced-filter/expr-columns-from-grid.d.ts +30 -0
  8. package/dist/advanced-filter/expr-columns-from-grid.js +44 -0
  9. package/dist/advanced-filter-enable.d.ts +5 -0
  10. package/dist/advanced-filter-enable.js +35 -0
  11. package/dist/cdn/svgrid-enterprise.svelte-external.js +8274 -7706
  12. package/dist/designer/assets/GridMenus-Bjr19Tz_.js +7 -0
  13. package/dist/designer/assets/{SvListBox-BVzVfCG3.js → SvListBox-BB8rt4aP.js} +1 -1
  14. package/dist/designer/assets/{index-CKdwDseq.js → index-xjfKckuH.js} +43 -43
  15. package/dist/designer/assets/{js-scroller.svelte-tHanKJx0.js → js-scroller.svelte-CGkUUXwV.js} +2 -2
  16. package/dist/designer/assets/src-BT3LjWJL.css +1 -0
  17. package/dist/designer/assets/src-DDhAK0WP.js +2893 -0
  18. package/dist/designer/index.html +5 -5
  19. package/dist/expressions/builder-tree.d.ts +62 -0
  20. package/dist/expressions/builder-tree.js +133 -0
  21. package/dist/expressions/compile.d.ts +14 -0
  22. package/dist/expressions/compile.js +286 -0
  23. package/dist/expressions/expression-columns.d.ts +1 -11
  24. package/dist/expressions/expression-columns.js +40 -11
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +4 -0
  27. package/dist/install.js +2 -0
  28. package/dist/node/studio.js +61 -13
  29. package/dist/sources/rest.js +25 -0
  30. package/dist/sources/supabase.js +49 -3
  31. package/dist/studio/emit-project.js +2125 -2125
  32. package/dist/studio/ui-components.generated.js +33 -0
  33. package/dist/sveltekit/in-memory.js +96 -3
  34. package/dist/sveltekit/query-plan.d.ts +32 -1
  35. package/dist/sveltekit/query-plan.js +88 -1
  36. package/dist/sveltekit/sql.d.ts +20 -0
  37. package/dist/sveltekit/sql.js +26 -0
  38. package/dist/version.d.ts +1 -1
  39. package/dist/version.js +1 -1
  40. package/package.json +3 -3
  41. package/dist/designer/assets/GridMenus-CW-rnHS9.js +0 -7
  42. package/dist/designer/assets/src-DCKhP5Xy.css +0 -1
  43. package/dist/designer/assets/src-DLsSdh3J.js +0 -2893
@@ -1,4 +1,4 @@
1
- import "@svgrid/grid/filtering";
1
+ import { isRangeOperator, isSetOperator, isValuelessOperator } from "@svgrid/grid/filtering";
2
2
  import { defaultThemePreset, getThemePreset, resolveThemeTokens as resolveThemeTokens$1, themePresets } from "@svgrid/grid/themes";
3
3
  //#region src/schema.ts
4
4
  /** `first_name` / `firstName` / `first-name` -> `First Name`. */
@@ -1932,6 +1932,21 @@ function scaffoldApp(schemas, options = {}) {
1932
1932
  }
1933
1933
  //#endregion
1934
1934
  //#region src/expressions/expression-columns.ts
1935
+ /**
1936
+ * Column metadata for the expression layer - the small, self-contained operator
1937
+ * catalogue the editor draws from, plus name <-> id resolution for the
1938
+ * `[Bracketed Name]` reference syntax.
1939
+ *
1940
+ * The operator set mirrors the grid's own filter row (text / number / date),
1941
+ * so an expression's operators line up with what a user already knows from the
1942
+ * column menu.
1943
+ *
1944
+ * Identity and input-shape semantics come from the grid's shared catalogue, so
1945
+ * the two surfaces cannot disagree about which operators exist or which need a
1946
+ * chip input / no input / a second value. Labels and ordering stay local on
1947
+ * purpose: the editor leads with `Equals` where the filter menu leads with
1948
+ * `Contains`, and each is right for its context.
1949
+ */
1935
1950
  var TEXT = ["text"];
1936
1951
  var NUM = ["number"];
1937
1952
  var DATES = ["date", "datetime"];
@@ -1992,34 +2007,34 @@ var OPERATORS = [
1992
2007
  {
1993
2008
  value: "between",
1994
2009
  label: "Between",
1995
- types: [...NUM, ...DATES],
1996
- range: true
2010
+ types: [...NUM, ...DATES]
1997
2011
  },
1998
2012
  {
1999
2013
  value: "in",
2000
2014
  label: "In",
2001
- types: ["text", "number"],
2002
- set: true
2015
+ types: ["text", "number"]
2003
2016
  },
2004
2017
  {
2005
2018
  value: "notIn",
2006
2019
  label: "Not in",
2007
- types: ["text", "number"],
2008
- set: true
2020
+ types: ["text", "number"]
2009
2021
  },
2010
2022
  {
2011
2023
  value: "isBlank",
2012
2024
  label: "Is blank",
2013
- types: ALL,
2014
- valueless: true
2025
+ types: ALL
2015
2026
  },
2016
2027
  {
2017
2028
  value: "isNotBlank",
2018
2029
  label: "Is not blank",
2019
- types: ALL,
2020
- valueless: true
2030
+ types: ALL
2021
2031
  }
2022
- ];
2032
+ ].map((op) => ({
2033
+ ...op,
2034
+ ...isValuelessOperator(op.value) ? { valueless: true } : {},
2035
+ ...isSetOperator(op.value) ? { set: true } : {},
2036
+ ...isRangeOperator(op.value) ? { range: true } : {}
2037
+ }));
2023
2038
  new Map(OPERATORS.map((op) => [op.value, op]));
2024
2039
  //#endregion
2025
2040
  //#region src/expressions/parse.ts
@@ -8607,6 +8622,20 @@ var GENERATED_UI_SURFACE = {
8607
8622
  "type": "boolean",
8608
8623
  "group": "common"
8609
8624
  },
8625
+ {
8626
+ "key": "groupBy",
8627
+ "label": "Group By",
8628
+ "type": "json",
8629
+ "description": "Group the rows by these column ids, outermost first - `['region', 'country']` rolls country up inside region. ```svelte <SvGrid {data} {columns} {features} groupBy={['department']} /> ``` The prop seeds the group-by list and re-applies whenever it changes, so it works both as initial state and as a controlled value. The column menu and `api.setGroupBy()` write the same state; a change from either survives until this prop's own value changes. Ignored when `treeData` is set - a row cannot be both a hierarchy node and bucketed under a group banner.",
8630
+ "group": "common"
8631
+ },
8632
+ {
8633
+ "key": "expanded",
8634
+ "label": "Expanded",
8635
+ "type": "json",
8636
+ "description": "Which group / tree rows are expanded, keyed by row id. Expansion is owned by the engine by default; pass this prop to hoist it (saved views, \"expand everything on load\", persisted UI state). Like `groupBy` it seeds the state and re-applies whenever it changes. Group row ids are built from the grouping path, not the display label: `group_department_Engineering`, and one level deeper `group_department_Engineering_role_Senior`. Tree rows key off the engine's row id instead (set `getRowId` to make that your own id). Prefer capturing the map from `onExpandedChange` over hand-building these keys.",
8637
+ "group": "common"
8638
+ },
8610
8639
  {
8611
8640
  "key": "groupFooters",
8612
8641
  "label": "Group Footers",
@@ -9006,6 +9035,13 @@ var GENERATED_UI_SURFACE = {
9006
9035
  "description": "The sort state to start in - `{ id, desc }` entries. Use with `externalSort` when the initial ordering is decided server-side (e.g. a URL `?sort=` param read in a SvelteKit `load`), so the header sort indicators match the already- sorted data on first render. Applied once at mount; user sorting takes over.",
9007
9036
  "group": "common"
9008
9037
  },
9038
+ {
9039
+ "key": "initialAdvancedFilter",
9040
+ "label": "Initial Advanced Filter",
9041
+ "type": "json",
9042
+ "description": "The advanced-filter expression to start in (Pro). Applied once at mount; change it afterwards through `api.setAdvancedFilter()`. Filtering only happens once `@svgrid/enterprise`'s `enableAdvancedFilter()` has registered a compiler - without it the expression is stored and reported but no rows are removed, so the free grid degrades to a no-op rather than to a wrong row set.",
9043
+ "group": "common"
9044
+ },
9009
9045
  {
9010
9046
  "key": "externalFilter",
9011
9047
  "label": "External Filter",
@@ -9156,6 +9192,12 @@ var GENERATED_UI_SURFACE = {
9156
9192
  "prop": "onPivotModeChange",
9157
9193
  "description": "Fired when the in-grid Pivot toggle flips `pivotMode`."
9158
9194
  },
9195
+ {
9196
+ "key": "expandedChange",
9197
+ "label": "Expanded Change",
9198
+ "prop": "onExpandedChange",
9199
+ "description": "Fired whenever the expanded set changes - chevron clicks, `api.setRowExpanded()`, `expandAllGroups()` / `collapseAllGroups()`. Receives the full next map, so it can be written straight back into `expanded` for a controlled setup."
9200
+ },
9159
9201
  {
9160
9202
  "key": "paginationChange",
9161
9203
  "label": "Pagination Change",
@@ -9186,6 +9228,12 @@ var GENERATED_UI_SURFACE = {
9186
9228
  "prop": "onSortingChange",
9187
9229
  "description": "Fires whenever the sort clauses change. Receives the new array of `{ id, desc }` entries. Pair with `externalSort={true}` when the consumer wants to own the row ordering."
9188
9230
  },
9231
+ {
9232
+ "key": "advancedFilterChange",
9233
+ "label": "Advanced Filter Change",
9234
+ "prop": "onAdvancedFilterChange",
9235
+ "description": "Fires whenever the advanced-filter expression changes, whatever changed it: `api.setAdvancedFilter()`, `clearAllFilters()`, or the toolbar's clear control. The panel that authors the expression is mounted by you, outside the grid, so it cannot see a change the grid made on its own. Without this, clearing from the toolbar leaves that panel showing a filter the grid is no longer applying. Feed this back into the panel's `expression` prop to keep them agreeing."
9236
+ },
9189
9237
  {
9190
9238
  "key": "filtersChange",
9191
9239
  "label": "Filters Change",
@@ -15740,7 +15788,7 @@ function hasFieldConditions(schema) {
15740
15788
  *
15741
15789
  * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
15742
15790
  */
15743
- var SVGRID_VERSION = "2.6.0";
15791
+ var SVGRID_VERSION = "2.6.2";
15744
15792
  //#endregion
15745
15793
  //#region src/studio/emit-project.ts
15746
15794
  var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
@@ -23,6 +23,31 @@ function defaultBuildQuery(request) {
23
23
  params.search = search;
24
24
  for (const p of predicates)
25
25
  params[p.column] = encodePredicate(p);
26
+ // ---- Server-side grouping ----------------------------------------------
27
+ // The grid asks for one level at a time. The path already chosen is sent as
28
+ // ordinary equality filters (the same shape any other filter uses), so an
29
+ // endpoint that already understands `?region=eq:EMEA` only needs to learn
30
+ // `groupBy` and `aggregate`.
31
+ //
32
+ // ?groupBy=region&aggregate=sum:amount,count:id
33
+ //
34
+ // and, one level down:
35
+ //
36
+ // ?groupBy=rep&region=eq:EMEA&aggregate=sum:amount
37
+ //
38
+ // The response is then one object per distinct key, each carrying the
39
+ // aggregate under its SOURCE column name - that is where the grid reads it.
40
+ const groupCols = request.groupBy ?? [];
41
+ const groupKeys = request.groupKeys ?? [];
42
+ for (let i = 0; i < groupKeys.length && i < groupCols.length; i += 1) {
43
+ params[groupCols[i]] = `eq:${groupKeys[i]}`;
44
+ }
45
+ if (groupKeys.length < groupCols.length) {
46
+ params.groupBy = groupCols[groupKeys.length];
47
+ const aggs = request.aggregations ?? [];
48
+ if (aggs.length)
49
+ params.aggregate = aggs.map((a) => `${a.fn}:${a.col}`).join(',');
50
+ }
26
51
  return params;
27
52
  }
28
53
  function defaultParse(body, response) {
@@ -10,7 +10,33 @@ export function createSupabaseDataSource(config) {
10
10
  schema.fields.filter((f) => f.type === 'text' || f.type === 'enum').map((f) => String(f.field));
11
11
  return {
12
12
  async getRows(request) {
13
- let q = client.from(table).select('*', { count: 'exact' });
13
+ // ---- Server-side grouping -------------------------------------------
14
+ // The grid asks one level at a time. The already-chosen path becomes
15
+ // ordinary `.eq()` filters below; at this level we ask PostgREST for the
16
+ // group column plus aggregates, which implicitly groups by the
17
+ // non-aggregate columns in `select`.
18
+ //
19
+ // Each aggregate is aliased back to its SOURCE column name, because that
20
+ // is the key the grid reads it from on the group row.
21
+ //
22
+ // REQUIRES PostgREST 12+ with aggregate functions enabled
23
+ // (`db-aggregates-enabled`, off by default on some Supabase projects). On
24
+ // an older or restricted instance the request errors rather than silently
25
+ // returning ungrouped rows, so the failure is visible.
26
+ const groupCols = request.groupBy ?? [];
27
+ const groupKeys = request.groupKeys ?? [];
28
+ const groupField = groupKeys.length < groupCols.length ? groupCols[groupKeys.length] : undefined;
29
+ const selectExpr = groupField
30
+ ? [
31
+ groupField,
32
+ ...(request.aggregations ?? []).map((a) => a.fn === 'count' ? `${a.col}:count()` : `${a.col}:${a.col}.${a.fn}()`),
33
+ ].join(',')
34
+ : '*';
35
+ let q = client.from(table).select(selectExpr, { count: 'exact' });
36
+ // The path constrains the rows before grouping.
37
+ for (let i = 0; i < groupKeys.length && i < groupCols.length; i += 1) {
38
+ q = q.eq(groupCols[i], groupKeys[i]);
39
+ }
14
40
  const { predicates, search } = normalizeFilters(request.filterModel);
15
41
  for (const p of predicates) {
16
42
  switch (p.op) {
@@ -44,8 +70,28 @@ export function createSupabaseDataSource(config) {
44
70
  const term = sanitize(search);
45
71
  q = q.or(searchColumns.map((c) => `${c}.ilike.%${term}%`).join(','));
46
72
  }
47
- for (const s of request.sortModel)
48
- q = q.order(s.id, { ascending: !s.desc });
73
+ // When grouping, only columns the grouped select actually produces can be
74
+ // ordered on - PostgREST rejects an ORDER BY over a column that is
75
+ // neither grouped nor aggregated. Fall back to the group key so the
76
+ // result has a stable order either way.
77
+ if (groupField) {
78
+ const produced = new Set([
79
+ groupField,
80
+ ...(request.aggregations ?? []).map((a) => a.col),
81
+ ]);
82
+ const usable = request.sortModel.filter((s) => produced.has(s.id));
83
+ if (usable.length) {
84
+ for (const s of usable)
85
+ q = q.order(s.id, { ascending: !s.desc });
86
+ }
87
+ else {
88
+ q = q.order(groupField, { ascending: true });
89
+ }
90
+ }
91
+ else {
92
+ for (const s of request.sortModel)
93
+ q = q.order(s.id, { ascending: !s.desc });
94
+ }
49
95
  const { data, count, error } = await q.range(request.startRow, request.endRow - 1);
50
96
  if (error)
51
97
  throw new Error(error.message);