@svgrid/enterprise 2.6.0 → 2.6.1

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.
@@ -66,6 +66,15 @@ function hiddenFor(field, surface) {
66
66
  return false;
67
67
  }
68
68
  /**
69
+ * Whether a field is hidden from one surface. Public because the `hidden`
70
+ * union (`true` means both surfaces, an object names them) is exactly the kind
71
+ * of logic that drifts when every caller re-derives it - the form builder's
72
+ * "Hidden from this form" tray is one such caller.
73
+ */
74
+ function isFieldHidden(field, surface) {
75
+ return hiddenFor(field, surface);
76
+ }
77
+ /**
69
78
  * Derive grid columns from a schema. Read-only fields become non-editable
70
79
  * columns; `enum` carries its options through as `editorOptions`. The
71
80
  * per-field `column` escape hatch is merged last, so any explicit grid prop
@@ -2552,6 +2561,13 @@ var GENERATED_UI_SURFACE = {
2552
2561
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
2553
2562
  "group": "behavior"
2554
2563
  },
2564
+ {
2565
+ "key": "block",
2566
+ "label": "Block",
2567
+ "type": "boolean",
2568
+ "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.",
2569
+ "group": "appearance"
2570
+ },
2555
2571
  {
2556
2572
  "key": "error",
2557
2573
  "label": "Error",
@@ -2674,6 +2690,13 @@ var GENERATED_UI_SURFACE = {
2674
2690
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
2675
2691
  "group": "behavior"
2676
2692
  },
2693
+ {
2694
+ "key": "block",
2695
+ "label": "Block",
2696
+ "type": "boolean",
2697
+ "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.",
2698
+ "group": "appearance"
2699
+ },
2677
2700
  {
2678
2701
  "key": "error",
2679
2702
  "label": "Error",
@@ -2809,6 +2832,14 @@ var GENERATED_UI_SURFACE = {
2809
2832
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
2810
2833
  "group": "behavior"
2811
2834
  },
2835
+ {
2836
+ "key": "block",
2837
+ "label": "Block",
2838
+ "type": "boolean",
2839
+ "default": false,
2840
+ "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.",
2841
+ "group": "appearance"
2842
+ },
2812
2843
  {
2813
2844
  "key": "error",
2814
2845
  "label": "Error",
@@ -3039,6 +3070,13 @@ var GENERATED_UI_SURFACE = {
3039
3070
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
3040
3071
  "group": "behavior"
3041
3072
  },
3073
+ {
3074
+ "key": "block",
3075
+ "label": "Block",
3076
+ "type": "boolean",
3077
+ "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.",
3078
+ "group": "appearance"
3079
+ },
3042
3080
  {
3043
3081
  "key": "error",
3044
3082
  "label": "Error",
@@ -3214,6 +3252,14 @@ var GENERATED_UI_SURFACE = {
3214
3252
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
3215
3253
  "group": "behavior"
3216
3254
  },
3255
+ {
3256
+ "key": "block",
3257
+ "label": "Block",
3258
+ "type": "boolean",
3259
+ "default": false,
3260
+ "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.",
3261
+ "group": "appearance"
3262
+ },
3217
3263
  {
3218
3264
  "key": "error",
3219
3265
  "label": "Error",
@@ -3438,6 +3484,14 @@ var GENERATED_UI_SURFACE = {
3438
3484
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
3439
3485
  "group": "behavior"
3440
3486
  },
3487
+ {
3488
+ "key": "block",
3489
+ "label": "Block",
3490
+ "type": "boolean",
3491
+ "default": false,
3492
+ "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.",
3493
+ "group": "appearance"
3494
+ },
3441
3495
  {
3442
3496
  "key": "error",
3443
3497
  "label": "Error",
@@ -3612,6 +3666,14 @@ var GENERATED_UI_SURFACE = {
3612
3666
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
3613
3667
  "group": "behavior"
3614
3668
  },
3669
+ {
3670
+ "key": "block",
3671
+ "label": "Block",
3672
+ "type": "boolean",
3673
+ "default": false,
3674
+ "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.",
3675
+ "group": "appearance"
3676
+ },
3615
3677
  {
3616
3678
  "key": "error",
3617
3679
  "label": "Error",
@@ -3763,6 +3825,13 @@ var GENERATED_UI_SURFACE = {
3763
3825
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
3764
3826
  "group": "behavior"
3765
3827
  },
3828
+ {
3829
+ "key": "block",
3830
+ "label": "Block",
3831
+ "type": "boolean",
3832
+ "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.",
3833
+ "group": "appearance"
3834
+ },
3766
3835
  {
3767
3836
  "key": "error",
3768
3837
  "label": "Error",
@@ -3898,6 +3967,13 @@ var GENERATED_UI_SURFACE = {
3898
3967
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
3899
3968
  "group": "behavior"
3900
3969
  },
3970
+ {
3971
+ "key": "block",
3972
+ "label": "Block",
3973
+ "type": "boolean",
3974
+ "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.",
3975
+ "group": "appearance"
3976
+ },
3901
3977
  {
3902
3978
  "key": "error",
3903
3979
  "label": "Error",
@@ -4049,6 +4125,14 @@ var GENERATED_UI_SURFACE = {
4049
4125
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
4050
4126
  "group": "behavior"
4051
4127
  },
4128
+ {
4129
+ "key": "block",
4130
+ "label": "Block",
4131
+ "type": "boolean",
4132
+ "default": false,
4133
+ "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.",
4134
+ "group": "appearance"
4135
+ },
4052
4136
  {
4053
4137
  "key": "error",
4054
4138
  "label": "Error",
@@ -4770,6 +4854,14 @@ var GENERATED_UI_SURFACE = {
4770
4854
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
4771
4855
  "group": "behavior"
4772
4856
  },
4857
+ {
4858
+ "key": "block",
4859
+ "label": "Block",
4860
+ "type": "boolean",
4861
+ "default": false,
4862
+ "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.",
4863
+ "group": "appearance"
4864
+ },
4773
4865
  {
4774
4866
  "key": "error",
4775
4867
  "label": "Error",
@@ -4944,6 +5036,14 @@ var GENERATED_UI_SURFACE = {
4944
5036
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
4945
5037
  "group": "behavior"
4946
5038
  },
5039
+ {
5040
+ "key": "block",
5041
+ "label": "Block",
5042
+ "type": "boolean",
5043
+ "default": false,
5044
+ "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.",
5045
+ "group": "appearance"
5046
+ },
4947
5047
  {
4948
5048
  "key": "error",
4949
5049
  "label": "Error",
@@ -5103,6 +5203,14 @@ var GENERATED_UI_SURFACE = {
5103
5203
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
5104
5204
  "group": "behavior"
5105
5205
  },
5206
+ {
5207
+ "key": "block",
5208
+ "label": "Block",
5209
+ "type": "boolean",
5210
+ "default": false,
5211
+ "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.",
5212
+ "group": "appearance"
5213
+ },
5106
5214
  {
5107
5215
  "key": "error",
5108
5216
  "label": "Error",
@@ -5252,6 +5360,14 @@ var GENERATED_UI_SURFACE = {
5252
5360
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
5253
5361
  "group": "behavior"
5254
5362
  },
5363
+ {
5364
+ "key": "block",
5365
+ "label": "Block",
5366
+ "type": "boolean",
5367
+ "default": false,
5368
+ "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.",
5369
+ "group": "appearance"
5370
+ },
5255
5371
  {
5256
5372
  "key": "error",
5257
5373
  "label": "Error",
@@ -5434,6 +5550,13 @@ var GENERATED_UI_SURFACE = {
5434
5550
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
5435
5551
  "group": "behavior"
5436
5552
  },
5553
+ {
5554
+ "key": "block",
5555
+ "label": "Block",
5556
+ "type": "boolean",
5557
+ "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.",
5558
+ "group": "appearance"
5559
+ },
5437
5560
  {
5438
5561
  "key": "error",
5439
5562
  "label": "Error",
@@ -5568,6 +5691,13 @@ var GENERATED_UI_SURFACE = {
5568
5691
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
5569
5692
  "group": "behavior"
5570
5693
  },
5694
+ {
5695
+ "key": "block",
5696
+ "label": "Block",
5697
+ "type": "boolean",
5698
+ "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.",
5699
+ "group": "appearance"
5700
+ },
5571
5701
  {
5572
5702
  "key": "error",
5573
5703
  "label": "Error",
@@ -5719,6 +5849,14 @@ var GENERATED_UI_SURFACE = {
5719
5849
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
5720
5850
  "group": "behavior"
5721
5851
  },
5852
+ {
5853
+ "key": "block",
5854
+ "label": "Block",
5855
+ "type": "boolean",
5856
+ "default": false,
5857
+ "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.",
5858
+ "group": "appearance"
5859
+ },
5722
5860
  {
5723
5861
  "key": "error",
5724
5862
  "label": "Error",
@@ -5901,6 +6039,13 @@ var GENERATED_UI_SURFACE = {
5901
6039
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
5902
6040
  "group": "behavior"
5903
6041
  },
6042
+ {
6043
+ "key": "block",
6044
+ "label": "Block",
6045
+ "type": "boolean",
6046
+ "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.",
6047
+ "group": "appearance"
6048
+ },
5904
6049
  {
5905
6050
  "key": "error",
5906
6051
  "label": "Error",
@@ -6082,6 +6227,13 @@ var GENERATED_UI_SURFACE = {
6082
6227
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
6083
6228
  "group": "behavior"
6084
6229
  },
6230
+ {
6231
+ "key": "block",
6232
+ "label": "Block",
6233
+ "type": "boolean",
6234
+ "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.",
6235
+ "group": "appearance"
6236
+ },
6085
6237
  {
6086
6238
  "key": "error",
6087
6239
  "label": "Error",
@@ -6269,6 +6421,13 @@ var GENERATED_UI_SURFACE = {
6269
6421
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
6270
6422
  "group": "behavior"
6271
6423
  },
6424
+ {
6425
+ "key": "block",
6426
+ "label": "Block",
6427
+ "type": "boolean",
6428
+ "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.",
6429
+ "group": "appearance"
6430
+ },
6272
6431
  {
6273
6432
  "key": "error",
6274
6433
  "label": "Error",
@@ -6830,6 +6989,14 @@ var GENERATED_UI_SURFACE = {
6830
6989
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
6831
6990
  "group": "behavior"
6832
6991
  },
6992
+ {
6993
+ "key": "block",
6994
+ "label": "Block",
6995
+ "type": "boolean",
6996
+ "default": false,
6997
+ "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.",
6998
+ "group": "appearance"
6999
+ },
6833
7000
  {
6834
7001
  "key": "error",
6835
7002
  "label": "Error",
@@ -7504,6 +7671,14 @@ var GENERATED_UI_SURFACE = {
7504
7671
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
7505
7672
  "group": "behavior"
7506
7673
  },
7674
+ {
7675
+ "key": "block",
7676
+ "label": "Block",
7677
+ "type": "boolean",
7678
+ "default": false,
7679
+ "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.",
7680
+ "group": "appearance"
7681
+ },
7507
7682
  {
7508
7683
  "key": "error",
7509
7684
  "label": "Error",
@@ -7905,6 +8080,13 @@ var GENERATED_UI_SURFACE = {
7905
8080
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
7906
8081
  "group": "behavior"
7907
8082
  },
8083
+ {
8084
+ "key": "block",
8085
+ "label": "Block",
8086
+ "type": "boolean",
8087
+ "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.",
8088
+ "group": "appearance"
8089
+ },
7908
8090
  {
7909
8091
  "key": "error",
7910
8092
  "label": "Error",
@@ -8201,6 +8383,13 @@ var GENERATED_UI_SURFACE = {
8201
8383
  "description": "Marks the control invalid (`aria-invalid` + error styling).",
8202
8384
  "group": "behavior"
8203
8385
  },
8386
+ {
8387
+ "key": "block",
8388
+ "label": "Block",
8389
+ "type": "boolean",
8390
+ "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.",
8391
+ "group": "appearance"
8392
+ },
8204
8393
  {
8205
8394
  "key": "error",
8206
8395
  "label": "Error",
@@ -11064,6 +11253,18 @@ function gridApiSettableProps() {
11064
11253
  }
11065
11254
  //#endregion
11066
11255
  //#region src/studio/project.ts
11256
+ /**
11257
+ * Studio project model - the declarative spine the visual designer edits and the
11258
+ * codegen emits. A project is a multi-entity app: `entities` (the data model) +
11259
+ * `screens` (one per entity by default), where a screen is an ordered list of
11260
+ * data-bound `blocks` (grid, form, chart, dashboard, KPI, master-detail, lookup).
11261
+ *
11262
+ * Pure + node-safe (lives in the `./studio` subtree - `.js` imports, no Svelte,
11263
+ * no `sources/`), mirroring the immutable-ops style of `schema-designer.ts`:
11264
+ * every mutation returns a NEW project so Svelte reactivity + undo stay simple.
11265
+ * Block-config unions are defined here (not imported from `sources/`) to keep the
11266
+ * module resolvable under node16.
11267
+ */
11067
11268
  /** Block kinds allowed inside a Tabs / Accordion container: the controller-free,
11068
11269
  * `allRows`-driven display blocks (no grid / form / master-detail, which need the
11069
11270
  * screen controller). `component` covers any UI-kit component added by key. */
@@ -11652,13 +11853,18 @@ function setJob(project, id, job) {
11652
11853
  jobs: next.length ? next : void 0
11653
11854
  };
11654
11855
  }
11655
- /** The designer's block palette, in menu order. (Editing is a Grid property, so
11656
- * there's no standalone form block.) */
11856
+ /** The designer's block palette, in menu order. A grid owns edit-in-popup and a
11857
+ * record panel edits the selected row, so the standalone Form block is the one
11858
+ * that creates: a form on its own page, with no grid behind it. */
11657
11859
  var blockPalette = [
11658
11860
  {
11659
11861
  kind: "grid",
11660
11862
  label: "Grid"
11661
11863
  },
11864
+ {
11865
+ kind: "form",
11866
+ label: "Form"
11867
+ },
11662
11868
  {
11663
11869
  kind: "chart",
11664
11870
  label: "Chart",
@@ -11779,7 +11985,8 @@ function defaultBlockConfig(kind, entity) {
11779
11985
  };
11780
11986
  case "form": return {
11781
11987
  kind,
11782
- presentation: "modal"
11988
+ presentation: "inline",
11989
+ afterSave: "reset"
11783
11990
  };
11784
11991
  case "chart": {
11785
11992
  const measure = pickMeasure(entity);
@@ -12398,6 +12605,403 @@ function removeEntity(project, name) {
12398
12605
  screens: project.screens.filter((s) => s.entity !== name)
12399
12606
  };
12400
12607
  }
12608
+ /**
12609
+ * The fields a form actually renders, in schema order. Arranging anything else
12610
+ * would be a lie: a field hidden from the form cannot be put in a section and
12611
+ * then appear there.
12612
+ */
12613
+ function formFieldNames(schema) {
12614
+ return schemaToFormFields(schema).map((f) => f.field);
12615
+ }
12616
+ /**
12617
+ * The sections as a builder should show them, plus the fields no section claims.
12618
+ *
12619
+ * Sections name their fields by string, so a rename, a delete, or hiding a field
12620
+ * from the form can leave a name behind. Those are dropped here rather than
12621
+ * drawn as ghosts, and a field in no section comes back in `unassigned` - which
12622
+ * is where the form itself puts it too, in a trailing untitled group.
12623
+ */
12624
+ function formPlan(schema, override) {
12625
+ const inForm = formFieldNames(schema);
12626
+ const known = new Set(inForm);
12627
+ const source = override ?? schema.form?.sections ?? [];
12628
+ const seen = /* @__PURE__ */ new Set();
12629
+ return {
12630
+ sections: source.map((section) => {
12631
+ const fields = section.fields.filter((f) => {
12632
+ if (!known.has(f) || seen.has(f)) return false;
12633
+ seen.add(f);
12634
+ return true;
12635
+ });
12636
+ return {
12637
+ ...section,
12638
+ fields
12639
+ };
12640
+ }),
12641
+ unassigned: inForm.filter((f) => !seen.has(f))
12642
+ };
12643
+ }
12644
+ /** Replace an entity's form layout. `undefined` drops it back to the default. */
12645
+ function setEntityForm(project, entityName, form) {
12646
+ return mapEntity(project, entityName, (schema) => {
12647
+ const next = { ...schema };
12648
+ if (form && (form.columns !== void 0 || form.sections?.length)) next.form = form;
12649
+ else delete next.form;
12650
+ return next;
12651
+ });
12652
+ }
12653
+ /** Set the form's column count, or clear it back to the default. */
12654
+ function setFormColumns(project, entityName, columns) {
12655
+ const schema = entityOf(project, entityName);
12656
+ if (!schema) return project;
12657
+ return setEntityForm(project, entityName, {
12658
+ ...schema.form,
12659
+ columns
12660
+ });
12661
+ }
12662
+ /** Append a section. New sections start empty - fields move in afterwards. */
12663
+ function addFormSection(project, entityName, title = "Section") {
12664
+ const schema = entityOf(project, entityName);
12665
+ if (!schema) return project;
12666
+ return setEntityForm(project, entityName, {
12667
+ ...schema.form,
12668
+ sections: [...schema.form?.sections ?? [], {
12669
+ title,
12670
+ fields: []
12671
+ }]
12672
+ });
12673
+ }
12674
+ /** Patch one section. An out-of-range index is a no-op rather than an error. */
12675
+ function updateFormSection(project, entityName, index, patch) {
12676
+ const schema = entityOf(project, entityName);
12677
+ const sections = schema?.form?.sections;
12678
+ if (!sections?.[index]) return project;
12679
+ const next = sections.map((s, i) => {
12680
+ if (i !== index) return s;
12681
+ const merged = {
12682
+ ...s,
12683
+ ...patch
12684
+ };
12685
+ if (!merged.title) delete merged.title;
12686
+ if (!merged.description) delete merged.description;
12687
+ if (!merged.visibleWhen) delete merged.visibleWhen;
12688
+ if (!merged.columns) delete merged.columns;
12689
+ if (!merged.collapsible) {
12690
+ delete merged.collapsible;
12691
+ delete merged.collapsed;
12692
+ }
12693
+ if (!merged.collapsed) delete merged.collapsed;
12694
+ return merged;
12695
+ });
12696
+ return setEntityForm(project, entityName, {
12697
+ ...schema.form,
12698
+ sections: next
12699
+ });
12700
+ }
12701
+ /**
12702
+ * Remove a section. Its fields stay in the form - they fall back to the trailing
12703
+ * untitled group, so deleting a heading never deletes the questions under it.
12704
+ */
12705
+ function removeFormSection(project, entityName, index) {
12706
+ const schema = entityOf(project, entityName);
12707
+ const sections = schema?.form?.sections;
12708
+ if (!sections?.[index]) return project;
12709
+ return setEntityForm(project, entityName, {
12710
+ ...schema.form,
12711
+ sections: sections.filter((_, i) => i !== index)
12712
+ });
12713
+ }
12714
+ /** Reorder the sections themselves. */
12715
+ function moveFormSection(project, entityName, from, to) {
12716
+ const schema = entityOf(project, entityName);
12717
+ const sections = schema?.form?.sections;
12718
+ if (!sections?.[from] || to < 0 || to >= sections.length || from === to) return project;
12719
+ const next = [...sections];
12720
+ next.splice(to, 0, next.splice(from, 1)[0]);
12721
+ return setEntityForm(project, entityName, {
12722
+ ...schema.form,
12723
+ sections: next
12724
+ });
12725
+ }
12726
+ /**
12727
+ * Move a field into a section at a position, or out of every section when
12728
+ * `toSection` is null. The field is pulled out of wherever it was first, so it
12729
+ * can never end up in two sections - which would render the control twice.
12730
+ */
12731
+ function moveFormField(project, entityName, field, toSection, toIndex = Number.MAX_SAFE_INTEGER) {
12732
+ const schema = entityOf(project, entityName);
12733
+ if (!schema || !formFieldNames(schema).includes(field)) return project;
12734
+ const sections = schema.form?.sections ?? [];
12735
+ if (toSection !== null && !sections[toSection]) return project;
12736
+ const stripped = sections.map((s) => ({
12737
+ ...s,
12738
+ fields: s.fields.filter((f) => f !== field)
12739
+ }));
12740
+ if (toSection === null) return setEntityForm(project, entityName, {
12741
+ ...schema.form,
12742
+ sections: stripped
12743
+ });
12744
+ const target = stripped[toSection];
12745
+ const at = Math.max(0, Math.min(toIndex, target.fields.length));
12746
+ const fields = [...target.fields];
12747
+ fields.splice(at, 0, field);
12748
+ return setEntityForm(project, entityName, {
12749
+ ...schema.form,
12750
+ sections: stripped.map((s, i) => i === toSection ? {
12751
+ ...s,
12752
+ fields
12753
+ } : s)
12754
+ });
12755
+ }
12756
+ /**
12757
+ * Move several fields at once, as one contiguous block in the order given.
12758
+ *
12759
+ * Not a loop of {@link moveFormField}: each single move both shifts the target
12760
+ * indices and re-resolves the plan, so a loop lands the group scattered or
12761
+ * reversed. Strip every named field first, then splice the whole group at the
12762
+ * index computed against the stripped target.
12763
+ *
12764
+ * Unknown and hidden-from-form names are dropped, duplicates keep their first
12765
+ * position, and an input that resolves to nothing returns the project
12766
+ * unchanged (same reference), so an undo stack never records a no-op.
12767
+ */
12768
+ function moveFormFields(project, entityName, fields, toSection, toIndex = Number.MAX_SAFE_INTEGER) {
12769
+ const schema = entityOf(project, entityName);
12770
+ if (!schema) return project;
12771
+ const movable = new Set(formFieldNames(schema));
12772
+ const group = [];
12773
+ for (const f of fields) if (movable.has(f) && !group.includes(f)) group.push(f);
12774
+ if (!group.length) return project;
12775
+ const sections = schema.form?.sections ?? [];
12776
+ if (toSection !== null && !sections[toSection]) return project;
12777
+ const moving = new Set(group);
12778
+ const stripped = sections.map((s) => ({
12779
+ ...s,
12780
+ fields: s.fields.filter((f) => !moving.has(f))
12781
+ }));
12782
+ if (toSection === null) return setEntityForm(project, entityName, {
12783
+ ...schema.form,
12784
+ sections: stripped
12785
+ });
12786
+ const target = stripped[toSection];
12787
+ const at = Math.max(0, Math.min(toIndex, target.fields.length));
12788
+ const next = [...target.fields];
12789
+ next.splice(at, 0, ...group);
12790
+ return setEntityForm(project, entityName, {
12791
+ ...schema.form,
12792
+ sections: stripped.map((s, i) => i === toSection ? {
12793
+ ...s,
12794
+ fields: next
12795
+ } : s)
12796
+ });
12797
+ }
12798
+ /**
12799
+ * Set (or clear) a field's value-driven conditions. Each is a `PredicateExpr`,
12800
+ * so it stays data all the way into the generated app - see `EntityField.when`.
12801
+ */
12802
+ function setFieldConditions(project, entityName, field, when) {
12803
+ return mapEntity(project, entityName, (schema) => ({
12804
+ ...schema,
12805
+ fields: schema.fields.map((f) => {
12806
+ if (f.field !== field) return f;
12807
+ const next = { ...f };
12808
+ if (when?.visible || when?.disabled || when?.required) next.when = {
12809
+ ...when.visible && { visible: when.visible },
12810
+ ...when.disabled && { disabled: when.disabled },
12811
+ ...when.required && { required: when.required }
12812
+ };
12813
+ else delete next.when;
12814
+ return next;
12815
+ })
12816
+ }));
12817
+ }
12818
+ /**
12819
+ * Patch one field of one entity. The form builder edits an entity that is not
12820
+ * necessarily the selected screen's, so it needs the project-level door rather
12821
+ * than the schema-level `updateField`.
12822
+ *
12823
+ * Deliberately shallow: `type` changes belong to the schema designer, which also
12824
+ * has to clean up `options` / `relation` when the type moves.
12825
+ */
12826
+ function updateEntityField(project, entityName, field, patch) {
12827
+ return mapEntity(project, entityName, (schema) => ({
12828
+ ...schema,
12829
+ fields: schema.fields.map((f) => f.field === field ? {
12830
+ ...f,
12831
+ ...patch
12832
+ } : f)
12833
+ }));
12834
+ }
12835
+ /**
12836
+ * Patch a field's form presentation (`EntityField.input`). Merges rather than
12837
+ * replaces, and a key set to `undefined` or `''` is removed - so clearing a
12838
+ * placeholder leaves no empty string behind, and emptying the last key drops
12839
+ * `input` itself rather than persisting `input: {}`.
12840
+ */
12841
+ function setFieldInput(project, entityName, field, patch) {
12842
+ return mapEntity(project, entityName, (schema) => ({
12843
+ ...schema,
12844
+ fields: schema.fields.map((f) => {
12845
+ if (f.field !== field) return f;
12846
+ const input = { ...f.input };
12847
+ for (const [key, value] of Object.entries(patch)) if (value === void 0 || value === "") delete input[key];
12848
+ else input[key] = value;
12849
+ const next = { ...f };
12850
+ if (Object.keys(input).length) next.input = input;
12851
+ else delete next.input;
12852
+ return next;
12853
+ })
12854
+ }));
12855
+ }
12856
+ /**
12857
+ * Show or hide a field on one surface, keeping the other surface's answer.
12858
+ * `hidden: true` means both, so it is expanded before one half is changed -
12859
+ * otherwise hiding a field from the form would silently un-hide its column.
12860
+ */
12861
+ function setFieldHidden(project, entityName, field, surface, hidden) {
12862
+ return mapEntity(project, entityName, (schema) => ({
12863
+ ...schema,
12864
+ fields: schema.fields.map((f) => {
12865
+ if (f.field !== field) return f;
12866
+ const merged = {
12867
+ ...f.hidden === true ? {
12868
+ grid: true,
12869
+ form: true
12870
+ } : { ...f.hidden || {} },
12871
+ [surface]: hidden
12872
+ };
12873
+ const next = { ...f };
12874
+ if (merged.grid && merged.form) next.hidden = true;
12875
+ else if (merged.grid || merged.form) next.hidden = {
12876
+ ...merged.grid && { grid: true },
12877
+ ...merged.form && { form: true }
12878
+ };
12879
+ else delete next.hidden;
12880
+ return next;
12881
+ })
12882
+ }));
12883
+ }
12884
+ /**
12885
+ * The form controls worth offering for a field type. A form builder that listed
12886
+ * every editor for every type would be a wall of mostly-wrong choices - a date
12887
+ * field has no business rendering as a colour picker. The first entry is what
12888
+ * the field renders as when `input.editorType` is unset.
12889
+ */
12890
+ function formControlsFor(type) {
12891
+ switch (type) {
12892
+ case "number": return [
12893
+ "number",
12894
+ "slider",
12895
+ "rating",
12896
+ "text"
12897
+ ];
12898
+ case "boolean": return ["checkbox"];
12899
+ case "date":
12900
+ case "dateString": return ["date", "date-native"];
12901
+ case "datetime": return [
12902
+ "datetime",
12903
+ "datetime-native",
12904
+ "time"
12905
+ ];
12906
+ case "enum": return [
12907
+ "select",
12908
+ "rich-select",
12909
+ "list",
12910
+ "chips",
12911
+ "autocomplete"
12912
+ ];
12913
+ case "relation": return ["select", "autocomplete"];
12914
+ case "json": return ["textarea", "text"];
12915
+ default: return [
12916
+ "text",
12917
+ "textarea",
12918
+ "password",
12919
+ "phone",
12920
+ "country",
12921
+ "mask",
12922
+ "color",
12923
+ "autocomplete",
12924
+ "chips"
12925
+ ];
12926
+ }
12927
+ }
12928
+ function formControlSettings(control, type) {
12929
+ const kind = control ?? formControlsFor(type)[0];
12930
+ if (kind === "mask") return ["mask"];
12931
+ if (kind === "slider" || kind === "rating") return ["range", "step"];
12932
+ if (kind === "number" || type === "number" && kind !== "text") return [
12933
+ "range",
12934
+ "step",
12935
+ "precision",
12936
+ "affix"
12937
+ ];
12938
+ return [];
12939
+ }
12940
+ /**
12941
+ * A first pass at sectioning an unarranged form, by what the field names say
12942
+ * they are. Studio's promise is a working app in a few clicks, and an entity of
12943
+ * fifteen fields in one flat list is not that.
12944
+ *
12945
+ * A guess, offered once: it returns sections for a builder to drop in and then
12946
+ * edit, and it never invents a group for a form small enough not to need one.
12947
+ */
12948
+ var SECTION_HINTS = [
12949
+ {
12950
+ title: "Contact",
12951
+ match: /^(email|phone|mobile|fax|website|url|contact)/i
12952
+ },
12953
+ {
12954
+ title: "Address",
12955
+ match: /(address|street|city|state|province|zip|postcode|postal|country|region)/i
12956
+ },
12957
+ {
12958
+ title: "Money",
12959
+ match: /(amount|price|cost|total|revenue|mrr|arr|salary|budget|value|balance|fee|tax|discount)/i
12960
+ },
12961
+ {
12962
+ title: "Dates",
12963
+ match: /(date|_at$|At$|deadline|due|expires|starts|ends)/i
12964
+ },
12965
+ {
12966
+ title: "Notes",
12967
+ match: /(note|notes|comment|comments|description|summary|remark)/i
12968
+ }
12969
+ ];
12970
+ function suggestFormSections(schema) {
12971
+ const names = formFieldNames(schema);
12972
+ if (names.length < 6) return [];
12973
+ const buckets = /* @__PURE__ */ new Map();
12974
+ const rest = [];
12975
+ for (const name of names) {
12976
+ const hint = SECTION_HINTS.find((h) => h.match.test(name));
12977
+ if (!hint) {
12978
+ rest.push(name);
12979
+ continue;
12980
+ }
12981
+ const bucket = buckets.get(hint.title);
12982
+ if (bucket) bucket.push(name);
12983
+ else buckets.set(hint.title, [name]);
12984
+ }
12985
+ const sections = [];
12986
+ for (const [title, fields] of buckets) if (fields.length > 1) sections.push({
12987
+ title,
12988
+ fields
12989
+ });
12990
+ else rest.push(...fields);
12991
+ if (!sections.length) return [];
12992
+ return rest.length ? [{
12993
+ title: "Details",
12994
+ fields: rest
12995
+ }, ...sections] : sections;
12996
+ }
12997
+ /** Apply a change to one entity's schema, leaving the project alone if it is unknown. */
12998
+ function mapEntity(project, entityName, fn) {
12999
+ if (!entityOf(project, entityName)) return project;
13000
+ return {
13001
+ ...project,
13002
+ entities: project.entities.map((e) => e.name === entityName ? fn(e) : e)
13003
+ };
13004
+ }
12401
13005
  /** Append a screen, making its id AND route unique against the existing set. */
12402
13006
  function appendScreen(project, base, keyBase) {
12403
13007
  const ids = new Set(project.screens.map((s) => s.id));
@@ -13550,7 +14154,13 @@ function validateProject(project) {
13550
14154
  block: b.id
13551
14155
  });
13552
14156
  const c = b.config;
13553
- if (c.kind === "master-detail") {
14157
+ if (c.kind === "form") {
14158
+ if (!s.entity) issues.push(at("A Form block needs a screen bound to an entity.", "error"));
14159
+ if (c.afterSave === "navigate") {
14160
+ if (!c.navigateTo) issues.push(at("The form is set to open a screen after saving, but no screen is chosen."));
14161
+ else if (!project.screens.some((x) => x.id === c.navigateTo)) issues.push(at(`The form opens a missing screen "${c.navigateTo}" after saving.`, "error"));
14162
+ }
14163
+ } else if (c.kind === "master-detail") {
13554
14164
  if (!c.childEntity || !c.foreignKey) issues.push(at("Master/detail needs a child entity + foreign key."));
13555
14165
  else if (!entityOf(project, c.childEntity)) issues.push(at(`Master/detail points at a missing child entity "${c.childEntity}".`));
13556
14166
  } else if (c.kind === "tree") {
@@ -15528,9 +16138,35 @@ ${panels}
15528
16138
  }
15529
16139
  case "lookup": return ` <div ${span}${cls}><!-- lookup (${cfg.field}): shown in the edit form --></div>`;
15530
16140
  case "component": return componentBlockMarkup(block, cfg, ctx.handleNames?.get(block.id), rowsExpr);
16141
+ case "form": return createFormMarkup(entity, schemaVar, block, cfg);
15531
16142
  default: return "";
15532
16143
  }
15533
16144
  }
16145
+ /**
16146
+ * A standalone create form: blank, always on the page, submits a new row.
16147
+ *
16148
+ * Keyed on `formSaves` so a successful create remounts the panel with empty
16149
+ * values - the panel seeds itself from `row` once, so without the key the
16150
+ * previous entry would still be sitting in the fields.
16151
+ */
16152
+ function createFormMarkup(entity, schemaVar, block, cfg) {
16153
+ const span = wrapperStyle(block);
16154
+ const cls = wrapperClass(block);
16155
+ const attrs = [
16156
+ `schema={${schemaVar}}`,
16157
+ "row={null}",
16158
+ "presentation=\"inline\""
16159
+ ];
16160
+ attrs.push(`title={${jsStr(cfg.title ?? `New ${entity.label ?? entity.name}`)}}`);
16161
+ if (cfg.submitLabel) attrs.push(`submitLabel={${jsStr(cfg.submitLabel)}}`);
16162
+ if (cfg.width && cfg.width !== "md") attrs.push(`formSize=${jsStr(cfg.width)}`);
16163
+ return ` <div ${span}${cls}>${cfg.afterSave === "navigate" ? "" : `
16164
+ {#if formSaves}<p class="st-form__done" role="status">Saved. Add another below.</p>{/if}`}
16165
+ {#key formSaves}
16166
+ <SvGridEditPanel ${attrs.join(" ")} onSubmit={createRecord} />
16167
+ {/key}
16168
+ </div>`;
16169
+ }
15534
16170
  /** Emits a UI-kit component block (see `UI_COMPONENT_REGISTRY`): a literal
15535
16171
  * `<SvXxx .../>` tag carrying its configured "chrome" props + optional text
15536
16172
  * content. Entity-agnostic - used both from `blockMarkup` (mixed onto an
@@ -16707,14 +17343,14 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
16707
17343
  const codeGridBlockId = firstGridBlockId(screen);
16708
17344
  const handleNames = handleNameMap(screen);
16709
17345
  const codeWire = codeEnabled ? codeWiring(screen, n.type, void 0) : null;
16710
- const hasForm = has(blocks, "form");
17346
+ const createForm = blocks.map((b) => b.config).find((c) => c.kind === "form");
16711
17347
  const gridConfigs = blocks.map((b) => b.config).filter((c) => c.kind === "grid");
16712
17348
  const formGrid = gridConfigs.find((c) => c.editing === "form");
16713
17349
  const hasEditAction = gridConfigs.some((c) => c.rowActions?.some((a) => a.kind === "edit"));
16714
17350
  const hasRowActions = gridConfigs.some((c) => (c.rowActions?.length ?? 0) > 0);
16715
17351
  const cellRenderKinds = new Set(gridConfigs.flatMap((c) => c.columns.filter((col) => col.show && col.cellType).map((col) => col.cellType.kind)));
16716
17352
  const hasCellRenderers = cellRenderKinds.size > 0;
16717
- const wantsForm = !!formGrid || hasForm || hasEditAction;
17353
+ const wantsForm = !!formGrid || hasEditAction;
16718
17354
  const gridPageSize = gridConfigs[0] ? gridConfigs[0].paginated !== false ? gridConfigs[0].pageSize ?? 10 : 1e3 : 10;
16719
17355
  const formPres = formGrid?.formPresentation ?? "modal";
16720
17356
  const formLayoutProps = editPanelLayoutProps(formGrid);
@@ -16722,7 +17358,7 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
16722
17358
  const hasFilter = has(blocks, "filter");
16723
17359
  const hasRecord = has(blocks, "record");
16724
17360
  const recordEditable = blocks.some((b) => b.config.kind === "record" && b.config.editable);
16725
- const needsController = hasGrid || wantsForm || hasFilter || hasRecord;
17361
+ const needsController = hasGrid || wantsForm || hasFilter || hasRecord || !!createForm;
16726
17362
  const rtSupabase = !ssrData && needsController && entSource?.kind === "supabase" && entSource.realtime === true;
16727
17363
  const rtTable = rtSupabase ? entSource.table : "";
16728
17364
  const hasAgg = has(allBlocks, "chart") || has(allBlocks, "dashboard") || has(allBlocks, "kpi") || has(allBlocks, "gauge") || has(allBlocks, "tree");
@@ -16790,7 +17426,7 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
16790
17426
  ...childSourceVars,
16791
17427
  ...wantsForm ? [...lookupVars, "nextId"] : []
16792
17428
  ])].filter(Boolean);
16793
- const usesGoto = blocks.some((b) => b.config.kind === "grid" && b.config.rowLink && routeById.has(b.config.rowLink.screen) || b.config.kind === "grid" && b.config.rowActions?.some((a) => a.kind === "navigate" && a.screen && routeById.has(a.screen)) || b.config.kind === "chart" && b.config.drillScreen && routeById.has(b.config.drillScreen) || (b.config.kind === "board" || b.config.kind === "calendar") && b.config.openScreen != null && routeById.has(b.config.openScreen) || b.config.kind === "master-detail" && b.config.linkScreen != null && routeById.has(b.config.linkScreen));
17429
+ const usesGoto = blocks.some((b) => b.config.kind === "grid" && b.config.rowLink && routeById.has(b.config.rowLink.screen) || b.config.kind === "grid" && b.config.rowActions?.some((a) => a.kind === "navigate" && a.screen && routeById.has(a.screen)) || b.config.kind === "chart" && b.config.drillScreen && routeById.has(b.config.drillScreen) || (b.config.kind === "board" || b.config.kind === "calendar") && b.config.openScreen != null && routeById.has(b.config.openScreen) || b.config.kind === "master-detail" && b.config.linkScreen != null && routeById.has(b.config.linkScreen) || b.config.kind === "form" && b.config.afterSave === "navigate" && b.config.navigateTo != null && routeById.has(b.config.navigateTo));
16794
17430
  const applyUrlFilters = drillEnabled && needsController;
16795
17431
  const filterableFieldNames = schema.fields.filter((f) => !f.primaryKey).map((f) => f.field);
16796
17432
  const gatesUi = accessEnabled && (wantsForm || gridConfigs.some((c) => c.editing === "inline") || hasRowActions);
@@ -16877,6 +17513,15 @@ function screenPage(schema, rawSchema, screen, resolve, rawResolve, accessEnable
16877
17513
  if (mode === 'create') { await controller.createRow({ [idField]: nextId('${n.idPrefix}'), ...values } as Partial<${n.type}>); controller.setPage(view.pageCount - 1) }
16878
17514
  else if (id) { await controller.updateRow(id, values) }${submitBody}
16879
17515
  editing = undefined${needsAllRows ? "\n await loadAll()" : ""}
17516
+ }`);
17517
+ }
17518
+ if (createForm) {
17519
+ const nav = createForm.afterSave === "navigate" && createForm.navigateTo ? routeById.get(createForm.navigateTo) : void 0;
17520
+ const after = nav ? `\n await goto('/${nav}')` : "";
17521
+ parts.push(`let formSaves = $state(0)
17522
+ async function createRecord({ values }: { mode: 'create' | 'edit'; id: string | null; values: Partial<${n.type}> }) {
17523
+ await controller.createRow({ [idField]: nextId('${n.idPrefix}'), ...values } as Partial<${n.type}>)${submitBody}
17524
+ formSaves += 1${after}
16880
17525
  }`);
16881
17526
  }
16882
17527
  if (hasRecord) parts.push(`let selectedRecord = $state<${n.type} | null>(null)${recordEditable ? `
@@ -17303,14 +17948,19 @@ ${hint ? ` {#if !form?.errors?.[${key}]}<small class="sk-hint">${htmlEs
17303
17948
  const ssrSections = layout?.sections?.length ? (() => {
17304
17949
  const assigned = /* @__PURE__ */ new Set();
17305
17950
  const groups = layout.sections.map((s) => {
17951
+ const names = [];
17306
17952
  const items = s.fields.map((name) => {
17307
17953
  assigned.add(name);
17954
+ names.push(name);
17308
17955
  return blockFor(name);
17309
17956
  }).filter(Boolean);
17310
17957
  return {
17311
17958
  title: s.title,
17312
17959
  description: s.description,
17313
17960
  columns: s.columns,
17961
+ collapsible: s.collapsible,
17962
+ collapsed: s.collapsed,
17963
+ names,
17314
17964
  items
17315
17965
  };
17316
17966
  }).filter((g) => g.items.length);
@@ -17319,13 +17969,26 @@ ${hint ? ` {#if !form?.errors?.[${key}]}<small class="sk-hint">${htmlEs
17319
17969
  title: void 0,
17320
17970
  description: void 0,
17321
17971
  columns: void 0,
17972
+ collapsible: false,
17973
+ collapsed: false,
17974
+ names: [],
17322
17975
  items: rest
17323
17976
  }] : groups;
17324
17977
  })() : null;
17325
17978
  const formCols = layout?.columns ?? 1;
17326
- const fieldsMarkup = ssrSections ? ssrSections.map((g) => ` <fieldset class="sk-group" style="--sk-cols: ${g.columns ?? formCols}">
17327
- ${g.title ? ` <legend>${htmlEsc(g.title)}</legend>\n` : ""}${g.description ? ` <p class="sk-group__desc">${htmlEsc(g.description)}</p>\n` : ""}${g.items.join("\n")}
17328
- </fieldset>`).join("\n") : ` <div class="sk-group" style="--sk-cols: ${formCols}">\n${fieldBlocks.join("\n")}\n </div>`;
17979
+ const fieldsMarkup = ssrSections ? ssrSections.map((g) => {
17980
+ const inner = `${g.description ? ` <p class="sk-group__desc">${htmlEsc(g.description)}</p>\n` : ""}${g.items.join("\n")}`;
17981
+ if (g.collapsible && g.title) {
17982
+ const open = g.collapsed ? `{${JSON.stringify(g.names)}.some((f) => form?.errors?.[f])}` : "{true}";
17983
+ return ` <details class="sk-group sk-group--fold" style="--sk-cols: ${g.columns ?? formCols}" open=${open}>
17984
+ <summary>${htmlEsc(g.title)}</summary>
17985
+ ${inner}
17986
+ </details>`;
17987
+ }
17988
+ return ` <fieldset class="sk-group" style="--sk-cols: ${g.columns ?? formCols}">
17989
+ ${g.title ? ` <legend>${htmlEsc(g.title)}</legend>\n` : ""}${inner}
17990
+ </fieldset>`;
17991
+ }).join("\n") : ` <div class="sk-group" style="--sk-cols: ${formCols}">\n${fieldBlocks.join("\n")}\n </div>`;
17329
17992
  const page = `<script lang="ts">
17330
17993
  import { SvGrid, renderSnippet, type ColumnDef, type CellContext } from '@svgrid/grid'
17331
17994
  import { schemaToColumns } from '@svgrid/enterprise'
@@ -17437,6 +18100,13 @@ ${facetCss} .sk-rowact { display: flex; gap: 10px; }
17437
18100
  .sk-group + .sk-group { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--sg-border, #e2e8f0); }
17438
18101
  .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); }
17439
18102
  .sk-group__desc { grid-column: 1 / -1; margin: 0; font-size: 12px; color: var(--sg-muted, #64748b); }
18103
+ /* A foldable group is a native <details>, so it works with JavaScript off.
18104
+ display:grid on the element itself would lay the <summary> out as a grid
18105
+ item and break the disclosure, so the grid moves to [open] children. */
18106
+ .sk-group--fold { display: block; }
18107
+ .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; }
18108
+ .sk-group--fold[open] { display: grid; }
18109
+ .sk-group--fold[open] > summary { margin-bottom: 0; }
17440
18110
  .sk-field--wide { grid-column: 1 / -1; }
17441
18111
  .sk-hint { color: var(--sg-muted, #64748b); font-size: 11.5px; }
17442
18112
  @media (max-width: 560px) { .sk-group { grid-template-columns: 1fr; } }
@@ -40770,4 +41440,4 @@ function checkLicenseKey(key, now = /* @__PURE__ */ new Date()) {
40770
41440
  };
40771
41441
  }
40772
41442
  //#endregion
40773
- export { CANVAS_COLS, CANVAS_GAP_PX, CANVAS_ROW_PX, CONTAINER_CHILD_KINDS, CONTAINER_DISPLAY_KINDS, CRUD_ACTIONS, DEFAULT_PORT, DRIVER_PACKAGE, FORM_SUBMIT, GRID_API_MEMBERS, GRID_CURATED_PROPS, GRID_EVENTS, GRID_PRESETS, MANAGED_END, MANAGED_START, ON_LOAD, PANE_LAYOUTS, STANDARD_UI_EVENTS, TAB_CHILD_KINDS, TRIGGER_EVENTS, UI_COMPONENT_REGISTRY, UserError, addAccordionBlock, addAccordionComponent, addAccordionSection, addBlock, addBlockAt, addComponentBlock, addCrudSuite, addEntity, addFreestandingScreen, addHandlerStep, addScreen, addScreenAction, addScreenFromTemplate, addStateVar, addTab, addTabBlock, addTabComponent, applyGridPreset, blockClassName, blockColumns, blockPalette, blockStyleCss, buildCanvasLayout, buildConnectionString, buildCopilotMessages, buildDockLayout, buildStudioBugReport, canvasOpts, canvasRectOf, changeSlot, checkLicenseKey, clickSlot, compileCondition, compileHandlerSteps, compileStep, compileTriggerStep, compileTriggerSteps, compileValue, componentHandleMembers, componentHandleName, componentHasBindings, countTableRows, createProject, crudAppFromSchemas, crudSuiteScreens, csvToEntity, ctxAmbientDts, ctxCompletions, dashScreen, defaultBlockConfig, defaultEntitySource, defaultScreenFor, defaultStudioTheme, deployCommands, detailScreen, detectDelimiter, disableScreenCode, dockOpts, dockPaneIds, dockPaneTitleOf, duplicateBlock, duplicateScreen, emitEntityModules, emitStudioApp, emitStudioAppBundle, emitStudioFragment, emitStudioProject, enableScreenCode, entityDataSource, entityOf, entityScreenPage, eventSlot, flattenBlocks, formScreen, generateRows, generateValue, getLiveDataSample, getSampleApp, getStarterDataset, getStudioTheme, gridApiSettableProps, gridColumns, gridConfig, gridOpts, gridPresetLabel, gridPropSurface, inferType, insertBlock, introspectDatabase, introspectDrizzle, introspectDrizzleAll, introspectJson, introspectOpenApi, introspectPrisma, introspectPrismaAll, isDarkTheme, isFileDialect, isPaneLayout, isProjectValid, isTenantScoped, isUserError, linkRelationLabels, listDatabaseTables, listScreen, liveDataSamples, mapSqlType, mergeBlockStyle, mergeManaged, missingEnvKeys, moveBlock, moveHandlerStep, parseConnectionString, parseCsv, parseProject, parseSelection, pickLabelField, prepareEntities, probeConnection, projectFromModelText, reconcileDock, redactConnectionString, refineField, refineFields, removeAccordionBlock, removeAccordionSection, removeBlock, removeEntity, removeScreen, removeScreenAction, removeStateVar, removeTab, removeTabBlock, renameAccordionSection, renameTab, reorderBlock, reorderScreen, reseedScreenDock, resolveDeployTarget, resolveIdField, resolveSchema, resolveSchemas, resolveThemeTokens, resolveThemeTokensFor, roleCanAction, roleCanScreen, rowSelectSlot, runStudioAdd, runStudioAddApp, runStudioInit, runtimeDeps, sampleApps, sanitizeClassName, sanitizeProject, sanitizeStudioProject, scaffold, scaffoldApp, schemaToColumns, schemaToFormFields, screenDataset, screenFromTemplate, screenHandles, screenLayoutOf, seedUsers, serializeProject, setAccordionMultiple, setAuth, setCanvasRect, setComponentBinding, setComponentName, setDataLayer, setDataSource, setDeployTarget, setDockPaneTitle, setEntityDataSource, setHandlerBody, setHandlerSteps, setJob, setLayoutOpts, setScreenDock, setScreenHandlersSource, setScreenLayout, setScreenRenderGrid, setScreenRenderMode, setShell, setTenancy, setTheme, setThemePreset, setTrigger, skipUserOwned, splitOpts, ssrEligible, ssrScreenShape, stackOpts, starterDatasets, starterProject, stateInitExpr, stateTsType, statusPills, stepsToCode, studioDeployInfo, studioThemes, summarizeVerify, syncDockPanes, tenantField, themeStyleString, triggersOf, uiComponentSpec, updateBlock, updateEntity, updateHandlerStep, updateScreen, updateStateVar, validateProject, verifyScaffold, withSsrDefaults };
41443
+ export { CANVAS_COLS, CANVAS_GAP_PX, CANVAS_ROW_PX, CONTAINER_CHILD_KINDS, CONTAINER_DISPLAY_KINDS, CRUD_ACTIONS, DEFAULT_PORT, DRIVER_PACKAGE, FORM_SUBMIT, GRID_API_MEMBERS, GRID_CURATED_PROPS, GRID_EVENTS, GRID_PRESETS, MANAGED_END, MANAGED_START, ON_LOAD, PANE_LAYOUTS, STANDARD_UI_EVENTS, TAB_CHILD_KINDS, TRIGGER_EVENTS, UI_COMPONENT_REGISTRY, UserError, addAccordionBlock, addAccordionComponent, addAccordionSection, addBlock, addBlockAt, addComponentBlock, addCrudSuite, addEntity, addFormSection, addFreestandingScreen, addHandlerStep, addScreen, addScreenAction, addScreenFromTemplate, addStateVar, addTab, addTabBlock, addTabComponent, applyGridPreset, blockClassName, blockColumns, blockPalette, blockStyleCss, buildCanvasLayout, buildConnectionString, buildCopilotMessages, buildDockLayout, buildStudioBugReport, canvasOpts, canvasRectOf, changeSlot, checkLicenseKey, clickSlot, compileCondition, compileHandlerSteps, compileStep, compileTriggerStep, compileTriggerSteps, compileValue, componentHandleMembers, componentHandleName, componentHasBindings, countTableRows, createProject, crudAppFromSchemas, crudSuiteScreens, csvToEntity, ctxAmbientDts, ctxCompletions, dashScreen, defaultBlockConfig, defaultEntitySource, defaultScreenFor, defaultStudioTheme, deployCommands, detailScreen, detectDelimiter, disableScreenCode, dockOpts, dockPaneIds, dockPaneTitleOf, duplicateBlock, duplicateScreen, emitEntityModules, emitStudioApp, emitStudioAppBundle, emitStudioFragment, emitStudioProject, enableScreenCode, entityDataSource, entityOf, entityScreenPage, eventSlot, flattenBlocks, formControlSettings, formControlsFor, formPlan, formScreen, generateRows, generateValue, getLiveDataSample, getSampleApp, getStarterDataset, getStudioTheme, gridApiSettableProps, gridColumns, gridConfig, gridOpts, gridPresetLabel, gridPropSurface, inferType, insertBlock, introspectDatabase, introspectDrizzle, introspectDrizzleAll, introspectJson, introspectOpenApi, introspectPrisma, introspectPrismaAll, isDarkTheme, isFieldHidden, isFileDialect, isPaneLayout, isProjectValid, isTenantScoped, isUserError, linkRelationLabels, listDatabaseTables, listScreen, liveDataSamples, mapSqlType, mergeBlockStyle, mergeManaged, missingEnvKeys, moveBlock, moveFormField, moveFormFields, moveFormSection, moveHandlerStep, parseConnectionString, parseCsv, parseProject, parseSelection, pickLabelField, prepareEntities, probeConnection, projectFromModelText, reconcileDock, redactConnectionString, refineField, refineFields, removeAccordionBlock, removeAccordionSection, removeBlock, removeEntity, removeFormSection, removeScreen, removeScreenAction, removeStateVar, removeTab, removeTabBlock, renameAccordionSection, renameTab, reorderBlock, reorderScreen, reseedScreenDock, resolveDeployTarget, resolveIdField, resolveSchema, resolveSchemas, resolveThemeTokens, resolveThemeTokensFor, roleCanAction, roleCanScreen, rowSelectSlot, runStudioAdd, runStudioAddApp, runStudioInit, runtimeDeps, sampleApps, sanitizeClassName, sanitizeProject, sanitizeStudioProject, scaffold, scaffoldApp, schemaToColumns, schemaToFormFields, screenDataset, screenFromTemplate, screenHandles, screenLayoutOf, seedUsers, serializeProject, setAccordionMultiple, setAuth, setCanvasRect, setComponentBinding, setComponentName, setDataLayer, setDataSource, setDeployTarget, setDockPaneTitle, setEntityDataSource, setEntityForm, setFieldConditions, setFieldHidden, setFieldInput, setFormColumns, setHandlerBody, setHandlerSteps, setJob, setLayoutOpts, setScreenDock, setScreenHandlersSource, setScreenLayout, setScreenRenderGrid, setScreenRenderMode, setShell, setTenancy, setTheme, setThemePreset, setTrigger, skipUserOwned, splitOpts, ssrEligible, ssrScreenShape, stackOpts, starterDatasets, starterProject, stateInitExpr, stateTsType, statusPills, stepsToCode, studioDeployInfo, studioThemes, suggestFormSections, summarizeVerify, syncDockPanes, tenantField, themeStyleString, triggersOf, uiComponentSpec, updateBlock, updateEntity, updateEntityField, updateFormSection, updateHandlerStep, updateScreen, updateStateVar, validateProject, verifyScaffold, withSsrDefaults };