@svgrid/enterprise 2.6.0 → 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.
- package/dist/ExpressionEditorHarness.svelte +47 -0
- package/dist/ExpressionEditorHarness.svelte.d.ts +15 -0
- package/dist/SvAdvancedFilter.svelte +208 -0
- package/dist/SvAdvancedFilter.svelte.d.ts +37 -0
- package/dist/SvExpressionEditor.svelte +255 -124
- package/dist/SvGridEditPanel.svelte +1157 -1012
- package/dist/SvGridEditPanel.svelte.d.ts +2 -0
- package/dist/advanced-filter/expr-columns-from-grid.d.ts +30 -0
- package/dist/advanced-filter/expr-columns-from-grid.js +44 -0
- package/dist/advanced-filter-enable.d.ts +5 -0
- package/dist/advanced-filter-enable.js +35 -0
- package/dist/cdn/svgrid-enterprise.svelte-external.js +7663 -6482
- package/dist/designer/assets/GridMenus-Bjr19Tz_.js +7 -0
- package/dist/designer/assets/{SvListBox-BVzVfCG3.js → SvListBox-BB8rt4aP.js} +1 -1
- package/dist/designer/assets/index-BfzoL904.css +1 -0
- package/dist/designer/assets/{index-BF0UH638.js → index-xjfKckuH.js} +139 -139
- package/dist/designer/assets/{js-scroller.svelte-tHanKJx0.js → js-scroller.svelte-CGkUUXwV.js} +2 -2
- package/dist/designer/assets/src-BT3LjWJL.css +1 -0
- package/dist/designer/assets/src-DDhAK0WP.js +2893 -0
- package/dist/designer/index.html +6 -6
- package/dist/expressions/builder-tree.d.ts +62 -0
- package/dist/expressions/builder-tree.js +133 -0
- package/dist/expressions/compile.d.ts +14 -0
- package/dist/expressions/compile.js +286 -0
- package/dist/expressions/expression-columns.d.ts +1 -11
- package/dist/expressions/expression-columns.js +40 -11
- package/dist/index.d.ts +7 -3
- package/dist/index.js +7 -3
- package/dist/install.js +2 -0
- package/dist/node/studio.js +743 -25
- package/dist/schema.d.ts +39 -0
- package/dist/schema.js +9 -0
- package/dist/sources/rest.js +25 -0
- package/dist/sources/supabase.js +49 -3
- package/dist/studio/emit-project.js +85 -11
- package/dist/studio/index.d.ts +2 -2
- package/dist/studio/index.js +5 -2
- package/dist/studio/project.d.ts +124 -3
- package/dist/studio/project.js +393 -4
- package/dist/studio/ui-components.generated.js +213 -0
- package/dist/sveltekit/in-memory.js +96 -3
- package/dist/sveltekit/query-plan.d.ts +32 -1
- package/dist/sveltekit/query-plan.js +88 -1
- package/dist/sveltekit/sql.d.ts +20 -0
- package/dist/sveltekit/sql.js +26 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/designer/assets/GridMenus-CW-rnHS9.js +0 -7
- package/dist/designer/assets/index-oYok52zd.css +0 -1
- package/dist/designer/assets/src-BPJruwB9.js +0 -2873
- package/dist/designer/assets/src-D-O2F805.css +0 -1
package/dist/schema.d.ts
CHANGED
|
@@ -268,6 +268,19 @@ export type FormSection = {
|
|
|
268
268
|
fields: string[];
|
|
269
269
|
/** Show the section only while this holds - see `EntityField.when`. */
|
|
270
270
|
visibleWhen?: PredicateExpr;
|
|
271
|
+
/**
|
|
272
|
+
* Let the user fold this group away. Turns the heading into a disclosure
|
|
273
|
+
* button, so a long form opens at a readable length instead of a wall of
|
|
274
|
+
* inputs.
|
|
275
|
+
*
|
|
276
|
+
* A collapsed section is still **filled in and still validated** - it is a
|
|
277
|
+
* display state, not a condition. Use `visibleWhen` to actually drop fields
|
|
278
|
+
* from the form. If a collapsed section holds an error the panel opens it, so
|
|
279
|
+
* a failed submit can never point at something the user cannot see.
|
|
280
|
+
*/
|
|
281
|
+
collapsible?: boolean;
|
|
282
|
+
/** Start folded. Only meaningful with `collapsible`. */
|
|
283
|
+
collapsed?: boolean;
|
|
271
284
|
};
|
|
272
285
|
/**
|
|
273
286
|
* How the entity's form is laid out. Lives on the schema rather than on the
|
|
@@ -281,6 +294,25 @@ export type FormSection = {
|
|
|
281
294
|
export type FormLayout = {
|
|
282
295
|
columns?: 1 | 2 | 3;
|
|
283
296
|
sections?: FormSection[];
|
|
297
|
+
/**
|
|
298
|
+
* Ask one section at a time, with Back / Next and a progress line - an intake
|
|
299
|
+
* form or an onboarding flow rather than a page of inputs.
|
|
300
|
+
*
|
|
301
|
+
* Each section is a step, so the sections ARE the design: no second list to
|
|
302
|
+
* keep in sync. **Next validates only the step you are on**, so a long form
|
|
303
|
+
* fails early and locally instead of dumping every error at the end. A section
|
|
304
|
+
* hidden by `visibleWhen` is skipped rather than shown empty, so the step count
|
|
305
|
+
* follows the answers.
|
|
306
|
+
*
|
|
307
|
+
* Needs sections (with none, it is one page and this does nothing) and makes
|
|
308
|
+
* `collapsible` moot - a step is already one group at a time.
|
|
309
|
+
*
|
|
310
|
+
* A server-rendered screen renders the steps as ordinary sections: stepping
|
|
311
|
+
* through a `<form>` with no JavaScript would mean a round-trip per step and
|
|
312
|
+
* somewhere to keep the half-finished record. The server still validates
|
|
313
|
+
* everything either way.
|
|
314
|
+
*/
|
|
315
|
+
steps?: boolean;
|
|
284
316
|
};
|
|
285
317
|
/** Normalized descriptor the edit panel renders from (one per visible-in-form field). */
|
|
286
318
|
export type FormFieldDescriptor = {
|
|
@@ -325,6 +357,13 @@ export declare function titleCase(field: string): string;
|
|
|
325
357
|
* `getRowId`, edit panel, adapters) needs a stable key.
|
|
326
358
|
*/
|
|
327
359
|
export declare function resolveIdField<TData extends RowData>(schema: EntitySchema<TData>): string;
|
|
360
|
+
/**
|
|
361
|
+
* Whether a field is hidden from one surface. Public because the `hidden`
|
|
362
|
+
* union (`true` means both surfaces, an object names them) is exactly the kind
|
|
363
|
+
* of logic that drifts when every caller re-derives it - the form builder's
|
|
364
|
+
* "Hidden from this form" tray is one such caller.
|
|
365
|
+
*/
|
|
366
|
+
export declare function isFieldHidden(field: Pick<EntityField, 'hidden'>, surface: 'grid' | 'form'): boolean;
|
|
328
367
|
/**
|
|
329
368
|
* Derive grid columns from a schema. Read-only fields become non-editable
|
|
330
369
|
* columns; `enum` carries its options through as `editorOptions`. The
|
package/dist/schema.js
CHANGED
|
@@ -75,6 +75,15 @@ function hiddenFor(field, surface) {
|
|
|
75
75
|
return field.hidden[surface] === true;
|
|
76
76
|
return false;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether a field is hidden from one surface. Public because the `hidden`
|
|
80
|
+
* union (`true` means both surfaces, an object names them) is exactly the kind
|
|
81
|
+
* of logic that drifts when every caller re-derives it - the form builder's
|
|
82
|
+
* "Hidden from this form" tray is one such caller.
|
|
83
|
+
*/
|
|
84
|
+
export function isFieldHidden(field, surface) {
|
|
85
|
+
return hiddenFor(field, surface);
|
|
86
|
+
}
|
|
78
87
|
/**
|
|
79
88
|
* Derive grid columns from a schema. Read-only fields become non-editable
|
|
80
89
|
* columns; `enum` carries its options through as `editorOptions`. The
|
package/dist/sources/rest.js
CHANGED
|
@@ -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®ion=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) {
|
package/dist/sources/supabase.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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);
|
|
@@ -539,10 +539,40 @@ ${panels}
|
|
|
539
539
|
case 'component':
|
|
540
540
|
return componentBlockMarkup(block, cfg, ctx.handleNames?.get(block.id), rowsExpr);
|
|
541
541
|
case 'form':
|
|
542
|
+
return createFormMarkup(entity, schemaVar, block, cfg);
|
|
542
543
|
default:
|
|
543
|
-
return '';
|
|
544
|
+
return '';
|
|
544
545
|
}
|
|
545
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* A standalone create form: blank, always on the page, submits a new row.
|
|
549
|
+
*
|
|
550
|
+
* Keyed on `formSaves` so a successful create remounts the panel with empty
|
|
551
|
+
* values - the panel seeds itself from `row` once, so without the key the
|
|
552
|
+
* previous entry would still be sitting in the fields.
|
|
553
|
+
*/
|
|
554
|
+
function createFormMarkup(entity, schemaVar, block, cfg) {
|
|
555
|
+
const span = wrapperStyle(block);
|
|
556
|
+
const cls = wrapperClass(block);
|
|
557
|
+
const attrs = [`schema={${schemaVar}}`, 'row={null}', 'presentation="inline"'];
|
|
558
|
+
attrs.push(`title={${jsStr(cfg.title ?? `New ${entity.label ?? entity.name}`)}}`);
|
|
559
|
+
if (cfg.submitLabel)
|
|
560
|
+
attrs.push(`submitLabel={${jsStr(cfg.submitLabel)}}`);
|
|
561
|
+
// Inline fills its block unless told otherwise, so only a real choice is emitted.
|
|
562
|
+
if (cfg.width && cfg.width !== 'md')
|
|
563
|
+
attrs.push(`formSize=${jsStr(cfg.width)}`);
|
|
564
|
+
// A confirmation only makes sense when the page stays put; navigating away
|
|
565
|
+
// makes the new screen the confirmation.
|
|
566
|
+
const done = cfg.afterSave === 'navigate'
|
|
567
|
+
? ''
|
|
568
|
+
: `
|
|
569
|
+
{#if formSaves}<p class="st-form__done" role="status">Saved. Add another below.</p>{/if}`;
|
|
570
|
+
return ` <div ${span}${cls}>${done}
|
|
571
|
+
{#key formSaves}
|
|
572
|
+
<SvGridEditPanel ${attrs.join(' ')} onSubmit={createRecord} />
|
|
573
|
+
{/key}
|
|
574
|
+
</div>`;
|
|
575
|
+
}
|
|
546
576
|
/** Emits a UI-kit component block (see `UI_COMPONENT_REGISTRY`): a literal
|
|
547
577
|
* `<SvXxx .../>` tag carrying its configured "chrome" props + optional text
|
|
548
578
|
* content. Entity-agnostic - used both from `blockMarkup` (mixed onto an
|
|
@@ -1852,7 +1882,10 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
|
|
|
1852
1882
|
// literal shared with the PageContext type. Data handles read the entity row type.
|
|
1853
1883
|
const handleNames = handleNameMap(screen);
|
|
1854
1884
|
const codeWire = codeEnabled ? codeWiring(screen, n.type, undefined) : null;
|
|
1855
|
-
|
|
1885
|
+
// A standalone Form block creates a record. It does NOT want the grid's edit
|
|
1886
|
+
// modal (that edits an existing row), so it is deliberately kept out of
|
|
1887
|
+
// `wantsForm` - it emits its own always-visible panel and its own handler.
|
|
1888
|
+
const createForm = blocks.map((b) => b.config).find((c) => c.kind === 'form');
|
|
1856
1889
|
// Editing is a Grid property: a grid with editing 'form' opens the edit panel.
|
|
1857
1890
|
const gridConfigs = blocks.map((b) => b.config).filter((c) => c.kind === 'grid');
|
|
1858
1891
|
const formGrid = gridConfigs.find((c) => c.editing === 'form');
|
|
@@ -1862,7 +1895,7 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
|
|
|
1862
1895
|
// Rich cell renderers (badge / progress / link) each emit a `cell` snippet.
|
|
1863
1896
|
const cellRenderKinds = new Set(gridConfigs.flatMap((c) => c.columns.filter((col) => col.show && col.cellType).map((col) => col.cellType.kind)));
|
|
1864
1897
|
const hasCellRenderers = cellRenderKinds.size > 0;
|
|
1865
|
-
const wantsForm = !!formGrid ||
|
|
1898
|
+
const wantsForm = !!formGrid || hasEditAction;
|
|
1866
1899
|
// An unpaginated grid loads everything (one big page); else its configured size.
|
|
1867
1900
|
const gridPageSize = gridConfigs[0] ? (gridConfigs[0].paginated !== false ? (gridConfigs[0].pageSize ?? 10) : 1000) : 10;
|
|
1868
1901
|
const formPres = formGrid?.formPresentation ?? 'modal';
|
|
@@ -1875,7 +1908,7 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
|
|
|
1875
1908
|
const recordEditable = blocks.some((b) => b.config.kind === 'record' && b.config.editable);
|
|
1876
1909
|
// Filter panels drive the grid's controller; record panels read the grid's
|
|
1877
1910
|
// selection - both need the controller even if the grid isn't editable.
|
|
1878
|
-
const needsController = hasGrid || wantsForm || hasFilter || hasRecord;
|
|
1911
|
+
const needsController = hasGrid || wantsForm || hasFilter || hasRecord || !!createForm;
|
|
1879
1912
|
// Supabase Realtime: when the screen's entity is Supabase-backed and opts into
|
|
1880
1913
|
// live updates, subscribe to Postgres change streams and refresh() the paged
|
|
1881
1914
|
// grid on any INSERT / UPDATE / DELETE (respects the active sort/filter/page).
|
|
@@ -2000,7 +2033,8 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
|
|
|
2000
2033
|
(b.config.kind === 'grid' && b.config.rowActions?.some((a) => a.kind === 'navigate' && a.screen && routeById.has(a.screen))) ||
|
|
2001
2034
|
(b.config.kind === 'chart' && b.config.drillScreen && routeById.has(b.config.drillScreen)) ||
|
|
2002
2035
|
((b.config.kind === 'board' || b.config.kind === 'calendar') && b.config.openScreen != null && routeById.has(b.config.openScreen)) ||
|
|
2003
|
-
(b.config.kind === 'master-detail' && b.config.linkScreen != null && routeById.has(b.config.linkScreen))
|
|
2036
|
+
(b.config.kind === 'master-detail' && b.config.linkScreen != null && routeById.has(b.config.linkScreen)) ||
|
|
2037
|
+
(b.config.kind === 'form' && b.config.afterSave === 'navigate' && b.config.navigateTo != null && routeById.has(b.config.navigateTo)));
|
|
2004
2038
|
const applyUrlFilters = drillEnabled && needsController;
|
|
2005
2039
|
const filterableFieldNames = schema.fields.filter((f) => !f.primaryKey).map((f) => f.field);
|
|
2006
2040
|
// RBAC gates the UI only where there's a create/update affordance to gate.
|
|
@@ -2138,6 +2172,21 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
|
|
|
2138
2172
|
if (mode === 'create') { await controller.createRow({ [idField]: nextId('${n.idPrefix}'), ...values } as Partial<${n.type}>); controller.setPage(view.pageCount - 1) }
|
|
2139
2173
|
else if (id) { await controller.updateRow(id, values) }${submitBody}
|
|
2140
2174
|
editing = undefined${needsAllRows ? '\n await loadAll()' : ''}
|
|
2175
|
+
}`);
|
|
2176
|
+
}
|
|
2177
|
+
// Standalone create form: a counter that both blanks the panel (it is the
|
|
2178
|
+
// {#key}) and drives the "Saved" confirmation.
|
|
2179
|
+
if (createForm) {
|
|
2180
|
+
const nav = createForm.afterSave === 'navigate' && createForm.navigateTo
|
|
2181
|
+
? routeById.get(createForm.navigateTo)
|
|
2182
|
+
: undefined;
|
|
2183
|
+
const after = nav
|
|
2184
|
+
? `\n await goto('/${nav}')`
|
|
2185
|
+
: '';
|
|
2186
|
+
parts.push(`let formSaves = $state(0)
|
|
2187
|
+
async function createRecord({ values }: { mode: 'create' | 'edit'; id: string | null; values: Partial<${n.type}> }) {
|
|
2188
|
+
await controller.createRow({ [idField]: nextId('${n.idPrefix}'), ...values } as Partial<${n.type}>)${submitBody}
|
|
2189
|
+
formSaves += 1${after}
|
|
2141
2190
|
}`);
|
|
2142
2191
|
}
|
|
2143
2192
|
// Record panel: the row selected in the grid, plus (when editable) a save hook.
|
|
@@ -2685,19 +2734,37 @@ ${hint ? ` {#if !form?.errors?.[${key}]}<small class="sk-hint">${htmlEs
|
|
|
2685
2734
|
? (() => {
|
|
2686
2735
|
const assigned = new Set();
|
|
2687
2736
|
const groups = layout.sections.map((s) => {
|
|
2688
|
-
const
|
|
2689
|
-
|
|
2737
|
+
const names = [];
|
|
2738
|
+
const items = s.fields.map((name) => { assigned.add(name); names.push(name); return blockFor(name); }).filter(Boolean);
|
|
2739
|
+
return { title: s.title, description: s.description, columns: s.columns, collapsible: s.collapsible, collapsed: s.collapsed, names, items };
|
|
2690
2740
|
}).filter((g) => g.items.length);
|
|
2691
2741
|
const rest = formFields.filter((f) => !assigned.has(f.field)).map((f) => blockFor(f.field)).filter(Boolean);
|
|
2692
|
-
return rest.length ? [...groups, { title: undefined, description: undefined, columns: undefined, items: rest }] : groups;
|
|
2742
|
+
return rest.length ? [...groups, { title: undefined, description: undefined, columns: undefined, collapsible: false, collapsed: false, names: [], items: rest }] : groups;
|
|
2693
2743
|
})()
|
|
2694
2744
|
: null;
|
|
2695
2745
|
const formCols = layout?.columns ?? 1;
|
|
2696
2746
|
const fieldsMarkup = ssrSections
|
|
2697
2747
|
? ssrSections
|
|
2698
|
-
.map((g) =>
|
|
2699
|
-
|
|
2700
|
-
|
|
2748
|
+
.map((g) => {
|
|
2749
|
+
const inner = `${g.description ? ` <p class="sk-group__desc">${htmlEsc(g.description)}</p>\n` : ''}${g.items.join('\n')}`;
|
|
2750
|
+
// A collapsible group is a native <details>, so it folds with no JS at
|
|
2751
|
+
// all - which is the point on a server-rendered page. A group that
|
|
2752
|
+
// starts folded is forced open when the server sent back an error for
|
|
2753
|
+
// one of its fields, so a rejected submit is never pointing at
|
|
2754
|
+
// something the user cannot see.
|
|
2755
|
+
if (g.collapsible && g.title) {
|
|
2756
|
+
const open = g.collapsed
|
|
2757
|
+
? `{${JSON.stringify(g.names)}.some((f) => form?.errors?.[f])}`
|
|
2758
|
+
: '{true}';
|
|
2759
|
+
return ` <details class="sk-group sk-group--fold" style="--sk-cols: ${g.columns ?? formCols}" open=${open}>
|
|
2760
|
+
<summary>${htmlEsc(g.title)}</summary>
|
|
2761
|
+
${inner}
|
|
2762
|
+
</details>`;
|
|
2763
|
+
}
|
|
2764
|
+
return ` <fieldset class="sk-group" style="--sk-cols: ${g.columns ?? formCols}">
|
|
2765
|
+
${g.title ? ` <legend>${htmlEsc(g.title)}</legend>\n` : ''}${inner}
|
|
2766
|
+
</fieldset>`;
|
|
2767
|
+
})
|
|
2701
2768
|
.join('\n')
|
|
2702
2769
|
: ` <div class="sk-group" style="--sk-cols: ${formCols}">\n${fieldBlocks.join('\n')}\n </div>`;
|
|
2703
2770
|
const page = `<script lang="ts">
|
|
@@ -2811,6 +2878,13 @@ ${facetCss} .sk-rowact { display: flex; gap: 10px; }
|
|
|
2811
2878
|
.sk-group + .sk-group { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--sg-border, #e2e8f0); }
|
|
2812
2879
|
.sk-group legend { grid-column: 1 / -1; padding: 0; font-size: 12px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase; color: var(--sg-muted, #64748b); }
|
|
2813
2880
|
.sk-group__desc { grid-column: 1 / -1; margin: 0; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
2881
|
+
/* A foldable group is a native <details>, so it works with JavaScript off.
|
|
2882
|
+
display:grid on the element itself would lay the <summary> out as a grid
|
|
2883
|
+
item and break the disclosure, so the grid moves to [open] children. */
|
|
2884
|
+
.sk-group--fold { display: block; }
|
|
2885
|
+
.sk-group--fold > summary { grid-column: 1 / -1; margin-bottom: 12px; font-size: 13.5px; font-weight: 650; color: var(--sg-fg, #0f172a); cursor: pointer; list-style-position: inside; }
|
|
2886
|
+
.sk-group--fold[open] { display: grid; }
|
|
2887
|
+
.sk-group--fold[open] > summary { margin-bottom: 0; }
|
|
2814
2888
|
.sk-field--wide { grid-column: 1 / -1; }
|
|
2815
2889
|
.sk-hint { color: var(--sg-muted, #64748b); font-size: 11.5px; }
|
|
2816
2890
|
@media (max-width: 560px) { .sk-group { grid-template-columns: 1fr; } }
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -25,11 +25,11 @@ export { crudSuiteScreens, addCrudSuite, crudAppFromSchemas, listScreen, formScr
|
|
|
25
25
|
export { sanitizeStudioProject, buildStudioBugReport, type SanitizeResult, type BugReport, type BugReportInput, type StudioEnv, } from './bug-report.js';
|
|
26
26
|
export { generateValue, generateRows } from './sample-data.js';
|
|
27
27
|
export { studioThemes, defaultStudioTheme, getStudioTheme, resolveThemeTokens, resolveThemeTokensFor, isDarkTheme, themeStyleString, type StudioTheme } from './themes.js';
|
|
28
|
-
export { createProject, defaultScreenFor, defaultBlockConfig, gridColumns, entityOf, blockPalette, addBlock, addBlockAt, addComponentBlock, removeBlock, duplicateBlock, moveBlock, reorderBlock, updateBlock, addEntity, removeEntity, updateEntity, 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, type CanvasRect, applyGridPreset, gridPresetLabel, GRID_PRESETS, setScreenDock, reseedScreenDock, setDockPaneTitle, dockPaneTitleOf, dockPaneIds, setLayoutOpts, gridOpts, stackOpts, splitOpts, dockOpts, canvasOpts, type ScreenLayout, type GridPreset, type LayoutOpts, type GridLayoutOpts, type StackLayoutOpts, type SplitLayoutOpts, type DockLayoutOpts, type CanvasLayoutOpts, setHandlerBody, setScreenHandlersSource, setHandlerSteps, addHandlerStep, updateHandlerStep, moveHandlerStep, stepsToCode, compileStep, compileHandlerSteps, compileValue, compileCondition, compileTriggerStep, compileTriggerSteps, triggersOf, setTrigger, TRIGGER_EVENTS, type TriggerStep, type TriggerEvent, type EntityTriggers, setJob, type ScheduledJob, type ScheduledJobKind, setTenancy, tenantField, isTenantScoped, type TenancyConfig, clickSlot, ssrScreenShape, ssrEligible, withSsrDefaults, setScreenRenderMode, eventSlot, rowSelectSlot, changeSlot, FORM_SUBMIT, GRID_EVENTS, type GridEventDef, addStateVar, updateStateVar, removeStateVar, stateInitExpr, stateTsType, type ActionStep, type ActionStepType, type StateVar, type StateVarType, type LogicValue, type LogicOp, type Condition, type FieldValue, 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, type BlockStyle, roleCanScreen, roleCanAction, CRUD_ACTIONS, type CrudAction, type RoleAccess, type AccessControl, type AuthConfig, type OAuthProvider, type SeedUser, type I18nConfig, type DeployTarget, type StudioProject, type Screen, type ScreenNav, type Block, type BlockKind, type BlockConfig, type GridConfig, type GridColumnConfig, type ColumnFormat, type ColumnCellType, type GridExportConfig, type GridFilterUi, type TreeDataConfig, type SchedulerViewConfig, type SchedulerViewMode, type RowLink, type RowAction, type RowActionKind, type ActionConfig, type ComponentConfig, type ComponentBinding, type FormatOp, type FormatRule, type GridEditing, type GridDensity, type GridAlign, type PagerPosition, type FormConfig, type ChartConfig, type KpiConfig, type KpiFormat, type GaugeConfig, type TreeConfig, type TabsConfig, type StudioTab, type AccordionConfig, type AccordionSection, type DashboardConfig, type MasterDetailConfig, type LookupConfig, type PivotConfig, type FilterPanelConfig, type RecordConfig, type BoardConfig, type CalendarConfig, type DetailConfig, type DetailRelated, type Reduce, type DataSourceKind, type EntityDataSource, type MemorySource, type RestSource, type RestAdapterConfig, type SqlSource, type SupabaseSource, type PgliteSource, type RestMethod, type ParamLocation, type ParamType, type RequestParam, type SqlDialectKind, type ProjectTheme, type ShellConfig, type ShellStyle, type ScreenTemplate, type PaletteItem, type ProjectIssue, } from './project.js';
|
|
28
|
+
export { createProject, defaultScreenFor, defaultBlockConfig, gridColumns, entityOf, blockPalette, addBlock, addBlockAt, addComponentBlock, removeBlock, duplicateBlock, moveBlock, reorderBlock, updateBlock, addEntity, removeEntity, updateEntity, 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, type CanvasRect, applyGridPreset, gridPresetLabel, GRID_PRESETS, setScreenDock, reseedScreenDock, setDockPaneTitle, dockPaneTitleOf, dockPaneIds, setLayoutOpts, gridOpts, stackOpts, splitOpts, dockOpts, canvasOpts, type ScreenLayout, type GridPreset, type LayoutOpts, type GridLayoutOpts, type StackLayoutOpts, type SplitLayoutOpts, type DockLayoutOpts, type CanvasLayoutOpts, setHandlerBody, setScreenHandlersSource, setHandlerSteps, addHandlerStep, updateHandlerStep, moveHandlerStep, stepsToCode, compileStep, compileHandlerSteps, compileValue, compileCondition, compileTriggerStep, compileTriggerSteps, triggersOf, setTrigger, TRIGGER_EVENTS, type TriggerStep, type TriggerEvent, type EntityTriggers, setJob, type ScheduledJob, type ScheduledJobKind, setTenancy, tenantField, isTenantScoped, type TenancyConfig, clickSlot, ssrScreenShape, ssrEligible, withSsrDefaults, setScreenRenderMode, eventSlot, rowSelectSlot, changeSlot, FORM_SUBMIT, GRID_EVENTS, type GridEventDef, addStateVar, updateStateVar, removeStateVar, stateInitExpr, stateTsType, type ActionStep, type ActionStepType, type StateVar, type StateVarType, type LogicValue, type LogicOp, type Condition, type FieldValue, 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, type BlockStyle, roleCanScreen, roleCanAction, CRUD_ACTIONS, type CrudAction, type RoleAccess, type AccessControl, type AuthConfig, type OAuthProvider, type SeedUser, type I18nConfig, type DeployTarget, type StudioProject, type Screen, type ScreenNav, type Block, type BlockKind, type BlockConfig, type GridConfig, type GridColumnConfig, type ColumnFormat, type ColumnCellType, type GridExportConfig, type GridFilterUi, type TreeDataConfig, type SchedulerViewConfig, type SchedulerViewMode, type RowLink, type RowAction, type RowActionKind, type ActionConfig, type ComponentConfig, type ComponentBinding, type FormatOp, type FormatRule, type GridEditing, type GridDensity, type GridAlign, type PagerPosition, type FormConfig, type ChartConfig, type KpiConfig, type KpiFormat, type GaugeConfig, type TreeConfig, type TabsConfig, type StudioTab, type AccordionConfig, type AccordionSection, type DashboardConfig, type MasterDetailConfig, type LookupConfig, type PivotConfig, type FilterPanelConfig, type RecordConfig, type BoardConfig, type CalendarConfig, type DetailConfig, type DetailRelated, type Reduce, type DataSourceKind, type EntityDataSource, type MemorySource, type RestSource, type RestAdapterConfig, type SqlSource, type SupabaseSource, type PgliteSource, type RestMethod, type ParamLocation, type ParamType, type RequestParam, type SqlDialectKind, type ProjectTheme, type ShellConfig, type ShellStyle, type ScreenTemplate, type PaletteItem, type ProjectIssue, } from './project.js';
|
|
29
29
|
export { verifyScaffold, summarizeVerify, type VerifyResult, type VerifyIssue, type VerifySeverity, } from './verify.js';
|
|
30
30
|
export { UI_COMPONENT_REGISTRY, uiComponentSpec, gridPropSurface, gridApiSettableProps, GRID_CURATED_PROPS, STANDARD_UI_EVENTS, type UiPropType, type UiPropGroup, type UiComponentEvent, type UiComponentProp, type UiComponentSpec, } from './ui-components.js';
|
|
31
31
|
export { buildCopilotMessages, projectFromModelText, type CopilotMessages } from './copilot-core.js';
|
|
32
32
|
export { resolveDeployTarget, deployCommands, missingEnvKeys, type DeployProvider, type DeployResolution, type DeployPlanCommands, } from './deploy-cli.js';
|
|
33
33
|
export { runStudioAdd, runStudioAddApp, resolveSchema, resolveSchemas, type StudioIO, type AddOptions, type AddResult, type AddAppResult, } from './cli.js';
|
|
34
|
-
export { resolveIdField, schemaToColumns, schemaToFormFields, linkRelationLabels, pickLabelField, type EntitySchema, type EntityField, type EntityFieldType, } from '../schema.js';
|
|
34
|
+
export { resolveIdField, schemaToColumns, schemaToFormFields, isFieldHidden, linkRelationLabels, pickLabelField, type EntitySchema, type EntityField, type EntityFieldType, } from '../schema.js';
|
|
35
35
|
export { checkLicenseKey, type LicenseInfo, type LicenseStatus } from '../license-core.js';
|
package/dist/studio/index.js
CHANGED
|
@@ -25,14 +25,17 @@ export { crudSuiteScreens, addCrudSuite, crudAppFromSchemas, listScreen, formScr
|
|
|
25
25
|
export { sanitizeStudioProject, buildStudioBugReport, } from './bug-report.js';
|
|
26
26
|
export { generateValue, generateRows } from './sample-data.js';
|
|
27
27
|
export { studioThemes, defaultStudioTheme, getStudioTheme, resolveThemeTokens, resolveThemeTokensFor, isDarkTheme, themeStyleString } from './themes.js';
|
|
28
|
-
export { createProject, defaultScreenFor, defaultBlockConfig, gridColumns, entityOf, blockPalette, addBlock, addBlockAt, addComponentBlock, removeBlock, duplicateBlock, moveBlock, reorderBlock, updateBlock, addEntity, removeEntity, updateEntity,
|
|
28
|
+
export { createProject, defaultScreenFor, defaultBlockConfig, gridColumns, entityOf, blockPalette, addBlock, addBlockAt, addComponentBlock, removeBlock, duplicateBlock, moveBlock, reorderBlock, updateBlock, addEntity, removeEntity, updateEntity,
|
|
29
|
+
// Form layout: the operations a form builder drives, on the entity's own
|
|
30
|
+
// `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';
|
|
29
32
|
export { verifyScaffold, summarizeVerify, } from './verify.js';
|
|
30
33
|
export { UI_COMPONENT_REGISTRY, uiComponentSpec, gridPropSurface, gridApiSettableProps, GRID_CURATED_PROPS, STANDARD_UI_EVENTS, } from './ui-components.js';
|
|
31
34
|
export { buildCopilotMessages, projectFromModelText } from './copilot-core.js';
|
|
32
35
|
export { resolveDeployTarget, deployCommands, missingEnvKeys, } from './deploy-cli.js';
|
|
33
36
|
export { runStudioAdd, runStudioAddApp, resolveSchema, resolveSchemas, } from './cli.js';
|
|
34
37
|
// Re-export the schema surface so consumers get everything from one entry.
|
|
35
|
-
export { resolveIdField, schemaToColumns, schemaToFormFields, linkRelationLabels, pickLabelField, } from '../schema.js';
|
|
38
|
+
export { resolveIdField, schemaToColumns, schemaToFormFields, isFieldHidden, linkRelationLabels, pickLabelField, } from '../schema.js';
|
|
36
39
|
// Pure license classification, so the Node/MCP generator can soft-gate with the
|
|
37
40
|
// same rules as the browser (see license-core.ts). No DOM, safe in Node.
|
|
38
41
|
export { checkLicenseKey } from '../license-core.js';
|
package/dist/studio/project.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Block-config unions are defined here (not imported from `sources/`) to keep the
|
|
11
11
|
* module resolvable under node16.
|
|
12
12
|
*/
|
|
13
|
-
import type
|
|
13
|
+
import { type EntityField, type EntityFieldType, type EntitySchema, type FormLayout, type FormSection, type StudioEditorType } from '../schema.js';
|
|
14
14
|
import type { ChartType, DockManagerState } from '@svgrid/grid';
|
|
15
15
|
export type Reduce = 'sum' | 'avg' | 'count' | 'min' | 'max';
|
|
16
16
|
export type DataSourceKind = 'memory' | 'sql' | 'supabase' | 'rest' | 'pglite';
|
|
@@ -317,9 +317,37 @@ export type ActionConfig = {
|
|
|
317
317
|
};
|
|
318
318
|
/** Legacy standalone edit-form block. Editing is now a Grid property; kept so old
|
|
319
319
|
* `studio.config.json` files still parse. Not offered in the palette. */
|
|
320
|
+
/**
|
|
321
|
+
* A standalone form for creating a record: a "New ticket" page, an intake
|
|
322
|
+
* screen, a signup. Blank on load, submits, and creates a row.
|
|
323
|
+
*
|
|
324
|
+
* Deliberately create-only, because the other two directions are already taken:
|
|
325
|
+
* a grid with `editing: 'form'` edits an existing row in a popup, and a
|
|
326
|
+
* `record` block with `editable` edits whatever row is selected. What no block
|
|
327
|
+
* covered was a form that stands on its own with no grid behind it.
|
|
328
|
+
*
|
|
329
|
+
* Its fields, order, sections and conditions come from the entity's own
|
|
330
|
+
* `EntitySchema.form` - the same layout the form builder edits - so a form
|
|
331
|
+
* designed once looks the same wherever it is placed.
|
|
332
|
+
*/
|
|
320
333
|
export type FormConfig = {
|
|
321
334
|
kind: 'form';
|
|
322
335
|
presentation: Presentation;
|
|
336
|
+
/** Heading above the form. Defaults to "New <entity>". */
|
|
337
|
+
title?: string;
|
|
338
|
+
/** Submit button text. Defaults to "Create". */
|
|
339
|
+
submitLabel?: string;
|
|
340
|
+
/** After a successful create: blank the form for another entry (default), or
|
|
341
|
+
* go to another screen. */
|
|
342
|
+
afterSave?: 'reset' | 'navigate';
|
|
343
|
+
/** Screen id to open when `afterSave` is 'navigate'. */
|
|
344
|
+
navigateTo?: string;
|
|
345
|
+
/**
|
|
346
|
+
* How wide the form draws. Inline forms fill their block by default, which is
|
|
347
|
+
* right for a narrow column and too wide to read across a full page - this
|
|
348
|
+
* caps it. Maps to `SvGridEditPanel`'s `formSize`.
|
|
349
|
+
*/
|
|
350
|
+
width?: 'sm' | 'md' | 'lg';
|
|
323
351
|
};
|
|
324
352
|
/** A chart, optionally drilling into `drillScreen` (filtered by the clicked category). */
|
|
325
353
|
export type ChartConfig = {
|
|
@@ -1068,8 +1096,9 @@ export type PaletteItem = {
|
|
|
1068
1096
|
label: string;
|
|
1069
1097
|
needs?: 'measure' | 'child';
|
|
1070
1098
|
};
|
|
1071
|
-
/** The designer's block palette, in menu order.
|
|
1072
|
-
*
|
|
1099
|
+
/** The designer's block palette, in menu order. A grid owns edit-in-popup and a
|
|
1100
|
+
* record panel edits the selected row, so the standalone Form block is the one
|
|
1101
|
+
* that creates: a form on its own page, with no grid behind it. */
|
|
1073
1102
|
export declare const blockPalette: ReadonlyArray<PaletteItem>;
|
|
1074
1103
|
export declare function entityOf(project: StudioProject, name: string | undefined): EntitySchema | undefined;
|
|
1075
1104
|
/** Sensible grid columns for an entity: every non-grid-hidden field, shown. */
|
|
@@ -1151,6 +1180,98 @@ export declare function updateEntity(project: StudioProject, name: string, schem
|
|
|
1151
1180
|
export declare function addEntity(project: StudioProject, schema: EntitySchema): StudioProject;
|
|
1152
1181
|
/** Remove an entity and every screen bound to it. */
|
|
1153
1182
|
export declare function removeEntity(project: StudioProject, name: string): StudioProject;
|
|
1183
|
+
/**
|
|
1184
|
+
* The sections as a builder should show them, plus the fields no section claims.
|
|
1185
|
+
*
|
|
1186
|
+
* Sections name their fields by string, so a rename, a delete, or hiding a field
|
|
1187
|
+
* from the form can leave a name behind. Those are dropped here rather than
|
|
1188
|
+
* drawn as ghosts, and a field in no section comes back in `unassigned` - which
|
|
1189
|
+
* is where the form itself puts it too, in a trailing untitled group.
|
|
1190
|
+
*/
|
|
1191
|
+
export declare function formPlan(schema: EntitySchema, override?: ReadonlyArray<FormSection>): {
|
|
1192
|
+
sections: FormSection[];
|
|
1193
|
+
unassigned: string[];
|
|
1194
|
+
};
|
|
1195
|
+
/** Replace an entity's form layout. `undefined` drops it back to the default. */
|
|
1196
|
+
export declare function setEntityForm(project: StudioProject, entityName: string, form: FormLayout | undefined): StudioProject;
|
|
1197
|
+
/** Set the form's column count, or clear it back to the default. */
|
|
1198
|
+
export declare function setFormColumns(project: StudioProject, entityName: string, columns: 1 | 2 | 3 | undefined): StudioProject;
|
|
1199
|
+
/** Append a section. New sections start empty - fields move in afterwards. */
|
|
1200
|
+
export declare function addFormSection(project: StudioProject, entityName: string, title?: string): StudioProject;
|
|
1201
|
+
/** Patch one section. An out-of-range index is a no-op rather than an error. */
|
|
1202
|
+
export declare function updateFormSection(project: StudioProject, entityName: string, index: number, patch: Partial<FormSection>): StudioProject;
|
|
1203
|
+
/**
|
|
1204
|
+
* Remove a section. Its fields stay in the form - they fall back to the trailing
|
|
1205
|
+
* untitled group, so deleting a heading never deletes the questions under it.
|
|
1206
|
+
*/
|
|
1207
|
+
export declare function removeFormSection(project: StudioProject, entityName: string, index: number): StudioProject;
|
|
1208
|
+
/** Reorder the sections themselves. */
|
|
1209
|
+
export declare function moveFormSection(project: StudioProject, entityName: string, from: number, to: number): StudioProject;
|
|
1210
|
+
/**
|
|
1211
|
+
* Move a field into a section at a position, or out of every section when
|
|
1212
|
+
* `toSection` is null. The field is pulled out of wherever it was first, so it
|
|
1213
|
+
* can never end up in two sections - which would render the control twice.
|
|
1214
|
+
*/
|
|
1215
|
+
export declare function moveFormField(project: StudioProject, entityName: string, field: string, toSection: number | null, toIndex?: number): StudioProject;
|
|
1216
|
+
/**
|
|
1217
|
+
* Move several fields at once, as one contiguous block in the order given.
|
|
1218
|
+
*
|
|
1219
|
+
* Not a loop of {@link moveFormField}: each single move both shifts the target
|
|
1220
|
+
* indices and re-resolves the plan, so a loop lands the group scattered or
|
|
1221
|
+
* reversed. Strip every named field first, then splice the whole group at the
|
|
1222
|
+
* index computed against the stripped target.
|
|
1223
|
+
*
|
|
1224
|
+
* Unknown and hidden-from-form names are dropped, duplicates keep their first
|
|
1225
|
+
* position, and an input that resolves to nothing returns the project
|
|
1226
|
+
* unchanged (same reference), so an undo stack never records a no-op.
|
|
1227
|
+
*/
|
|
1228
|
+
export declare function moveFormFields(project: StudioProject, entityName: string, fields: ReadonlyArray<string>, toSection: number | null, toIndex?: number): StudioProject;
|
|
1229
|
+
/**
|
|
1230
|
+
* Set (or clear) a field's value-driven conditions. Each is a `PredicateExpr`,
|
|
1231
|
+
* so it stays data all the way into the generated app - see `EntityField.when`.
|
|
1232
|
+
*/
|
|
1233
|
+
export declare function setFieldConditions(project: StudioProject, entityName: string, field: string, when: EntityField['when'] | undefined): StudioProject;
|
|
1234
|
+
/**
|
|
1235
|
+
* Patch one field of one entity. The form builder edits an entity that is not
|
|
1236
|
+
* necessarily the selected screen's, so it needs the project-level door rather
|
|
1237
|
+
* than the schema-level `updateField`.
|
|
1238
|
+
*
|
|
1239
|
+
* Deliberately shallow: `type` changes belong to the schema designer, which also
|
|
1240
|
+
* has to clean up `options` / `relation` when the type moves.
|
|
1241
|
+
*/
|
|
1242
|
+
export declare function updateEntityField(project: StudioProject, entityName: string, field: string, patch: Partial<EntityField>): StudioProject;
|
|
1243
|
+
/**
|
|
1244
|
+
* Patch a field's form presentation (`EntityField.input`). Merges rather than
|
|
1245
|
+
* replaces, and a key set to `undefined` or `''` is removed - so clearing a
|
|
1246
|
+
* placeholder leaves no empty string behind, and emptying the last key drops
|
|
1247
|
+
* `input` itself rather than persisting `input: {}`.
|
|
1248
|
+
*/
|
|
1249
|
+
export declare function setFieldInput(project: StudioProject, entityName: string, field: string, patch: Partial<NonNullable<EntityField['input']>>): StudioProject;
|
|
1250
|
+
/**
|
|
1251
|
+
* Show or hide a field on one surface, keeping the other surface's answer.
|
|
1252
|
+
* `hidden: true` means both, so it is expanded before one half is changed -
|
|
1253
|
+
* otherwise hiding a field from the form would silently un-hide its column.
|
|
1254
|
+
*/
|
|
1255
|
+
export declare function setFieldHidden(project: StudioProject, entityName: string, field: string, surface: 'grid' | 'form', hidden: boolean): StudioProject;
|
|
1256
|
+
/**
|
|
1257
|
+
* The form controls worth offering for a field type. A form builder that listed
|
|
1258
|
+
* every editor for every type would be a wall of mostly-wrong choices - a date
|
|
1259
|
+
* field has no business rendering as a colour picker. The first entry is what
|
|
1260
|
+
* the field renders as when `input.editorType` is unset.
|
|
1261
|
+
*/
|
|
1262
|
+
export declare function formControlsFor(type: EntityFieldType): StudioEditorType[];
|
|
1263
|
+
/**
|
|
1264
|
+
* Which extra settings a control actually uses, so a builder can show those and
|
|
1265
|
+
* nothing else. Picking "mask" and then having nowhere to type the pattern - or
|
|
1266
|
+
* "slider" with no way to set its range - is a dead end, and listing all of them
|
|
1267
|
+
* against every control is a wall of mostly-irrelevant boxes.
|
|
1268
|
+
*
|
|
1269
|
+
* `range` is min/max, which live on the field itself rather than under `input`
|
|
1270
|
+
* (they validate as well as bound the control).
|
|
1271
|
+
*/
|
|
1272
|
+
export type ControlSetting = 'range' | 'step' | 'precision' | 'affix' | 'mask';
|
|
1273
|
+
export declare function formControlSettings(control: StudioEditorType | undefined, type: EntityFieldType): ControlSetting[];
|
|
1274
|
+
export declare function suggestFormSections(schema: EntitySchema): FormSection[];
|
|
1154
1275
|
export declare function addScreen(project: StudioProject, entity: string): StudioProject;
|
|
1155
1276
|
export declare function removeScreen(project: StudioProject, screenId: string): StudioProject;
|
|
1156
1277
|
export declare function updateScreen(project: StudioProject, screenId: string, patch: Partial<Pick<Screen, 'title' | 'route' | 'entity' | 'nav' | 'actions' | 'className' | 'renderMode'>>): StudioProject;
|