@duke333/builder-ui 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -53,6 +53,24 @@ loading, empty, errors, focus restoration, and row action isolation. Runtime for
53
53
 
54
54
  The existing `createDynamic*Component` factories remain available as the advanced headless API.
55
55
 
56
+ ## Form Layout Presentation
57
+
58
+ Form layout groups stay backward-compatible and render as stacked sections by default. Set the
59
+ layout presentation to `tabs` when each group should be shown as a tab:
60
+
61
+ ```ts
62
+ layout: {
63
+ presentation: "tabs",
64
+ groups: [
65
+ { id: "general", title: "General", columns: 12, fields: [] },
66
+ { id: "content", title: "Content", columns: 12, fields: [] },
67
+ ],
68
+ }
69
+ ```
70
+
71
+ The ready runtime keeps inactive tab fields mounted so their form state and validation remain
72
+ intact. Headless consumers should preserve the same behavior in their form adapter.
73
+
56
74
  ## Validation
57
75
 
58
76
  ```bash
@@ -46,6 +46,10 @@ type BuilderEditorField<TFieldType extends string> = {
46
46
  required: boolean;
47
47
  hidden?: boolean;
48
48
  disabled?: boolean;
49
+ storage?: "s3" | "server";
50
+ contentKind?: "image" | "file";
51
+ multiple?: boolean;
52
+ maxBytes?: number;
49
53
  placeholder?: string;
50
54
  description?: string;
51
55
  options?: BuilderEditorSelectOption[];
@@ -83,6 +87,7 @@ type BuilderEditorFormLayoutGroup = {
83
87
  fields?: BuilderEditorFormLayoutField[];
84
88
  };
85
89
  type BuilderEditorFormLayout = {
90
+ presentation?: "stacked" | "tabs";
86
91
  groups?: BuilderEditorFormLayoutGroup[];
87
92
  };
88
93
  type BuilderEditorColumn = {
@@ -123,6 +128,10 @@ type BuilderFormEditorFieldValues<TFieldType extends string = string> = {
123
128
  required: boolean;
124
129
  hidden: boolean;
125
130
  disabled: boolean;
131
+ storage?: "s3" | "server";
132
+ contentKind?: "image" | "file";
133
+ multiple?: boolean;
134
+ maxMegabytes?: number;
126
135
  placeholder?: string;
127
136
  optionsText?: string;
128
137
  optionsMode: "static" | "table";
@@ -181,6 +190,7 @@ type BuilderFormEditorValues<TFieldType extends string = string, TTriggerEvent e
181
190
  sourceTable: string;
182
191
  apiBasePath?: string;
183
192
  fields: BuilderFormEditorFieldValues<TFieldType>[];
193
+ layoutPresentation: "stacked" | "tabs";
184
194
  layoutGroups: BuilderFormEditorLayoutGroupValues[];
185
195
  subforms?: BuilderFormEditorSubformValues[];
186
196
  triggers: BuilderEditorTriggerValues<TTriggerEvent>[];
@@ -261,6 +271,10 @@ declare function builderFormEditorInputFromValues<TFieldType extends string, TTr
261
271
  required: boolean;
262
272
  hidden: boolean;
263
273
  disabled: boolean;
274
+ storage: "s3" | "server" | undefined;
275
+ contentKind: "file" | "image" | undefined;
276
+ multiple: boolean | undefined;
277
+ maxBytes: number | undefined;
264
278
  placeholder: string;
265
279
  options: BuilderEditorSelectOption[] | undefined;
266
280
  optionsSource: {
@@ -283,6 +297,7 @@ declare function builderFormEditorInputFromValues<TFieldType extends string, TTr
283
297
  }[];
284
298
  }[];
285
299
  layout: {
300
+ presentation: "stacked" | "tabs";
286
301
  groups: BuilderFormEditorLayoutGroupValues[];
287
302
  };
288
303
  subforms: {
@@ -2,7 +2,7 @@ import * as react from 'react';
2
2
  import { ComponentType, ReactNode } from 'react';
3
3
  import { BuilderApiContracts } from './api/index.js';
4
4
  import { BuilderHooks } from './hooks/index.js';
5
- import { BuilderRuntimeFormSchemaLike, BuilderRuntimeFormValues, BuilderRuntimeEntryLike, BuilderRuntimeViewLike, BuilderRuntimePresentation, BuilderRuntimeEntryValues } from './runtime/index.js';
5
+ import { a as BuilderRuntimeFormSchemaLike, d as BuilderRuntimeFormValues, b as BuilderRuntimeEntryLike, e as BuilderRuntimeViewLike, B as BuilderRuntimePresentation, c as BuilderRuntimeEntryValues } from './runtimeUtils-CCtLyIf1.js';
6
6
 
7
7
  type DynamicFormContracts = BuilderApiContracts & {
8
8
  BuilderForm: {
@@ -1,5 +1,5 @@
1
- import { C as BuilderViewEditorEntity, D as BuilderViewEditorValues, X as BuilderFormEditorLayoutGroupValues, u as BuilderFormEditorValues, s as BuilderFormEditorFieldValues, Y as BuilderFormEditorSubformValues } from '../BuilderAdminPages-DrOrFyIX.js';
2
- export { B as BUILDER_EDITOR_NONE_VALUE, a as BuilderAdminPageContracts, b as BuilderAdminPagesOptions, c as BuilderAdminPagesRoutes, d as BuilderDefaultEntity, e as BuilderDefaultEntityListResult, f as BuilderDefaultEntityManagerDependencies, g as BuilderDefaultEntityManagerLabels, h as BuilderDefaultEntityManagerState, i as BuilderDefaultEntityMutation, j as BuilderDefaultEntityQueryResult, k as BuilderDefaultFormEditorBodyOptions, l as BuilderDefaultListEditorBodyOptions, m as BuilderDefaultOption, n as BuilderEditorDbTable, o as BuilderEditorSelectOption, p as BuilderEditorTriggerValues, q as BuilderFormEditorBodyProps, r as BuilderFormEditorEntity, t as BuilderFormEditorPageDependencies, v as BuilderListEditorBodyProps, w as BuilderListEditorColumnValues, x as BuilderListEditorEntity, y as BuilderListEditorOrderByValues, z as BuilderListEditorPageDependencies, A as BuilderListEditorValues, E as builderEditorOptionsToText, F as builderEditorSearchFieldsToText, G as builderEditorTextToOptions, H as builderEditorTextToSearchFields, Z as builderFormEditorDefaultLayoutGroups, _ as builderFormEditorDefaultSpan, I as builderFormEditorFieldsFromTable, J as builderFormEditorInputFromValues, K as builderFormEditorValuesFromEntity, L as builderListEditorColumnsFromTable, M as builderListEditorInputFromValues, N as builderListEditorValuesFromEntity, O as builderViewEditorInputFromValues, P as builderViewEditorValuesFromEntity, Q as createBuilderAdminPages, R as createBuilderDefaultEntityManagerComponent, S as createBuilderDefaultFormEditorBody, T as createBuilderDefaultListEditorBody, U as createBuilderFormEditorPageComponent, V as createBuilderListEditorPageComponent, W as getBuilderEditorTriggerRoute } from '../BuilderAdminPages-DrOrFyIX.js';
1
+ import { C as BuilderViewEditorEntity, D as BuilderViewEditorValues, X as BuilderFormEditorLayoutGroupValues, u as BuilderFormEditorValues, s as BuilderFormEditorFieldValues, Y as BuilderFormEditorSubformValues } from '../BuilderAdminPages-bV-lwDOp.js';
2
+ export { B as BUILDER_EDITOR_NONE_VALUE, a as BuilderAdminPageContracts, b as BuilderAdminPagesOptions, c as BuilderAdminPagesRoutes, d as BuilderDefaultEntity, e as BuilderDefaultEntityListResult, f as BuilderDefaultEntityManagerDependencies, g as BuilderDefaultEntityManagerLabels, h as BuilderDefaultEntityManagerState, i as BuilderDefaultEntityMutation, j as BuilderDefaultEntityQueryResult, k as BuilderDefaultFormEditorBodyOptions, l as BuilderDefaultListEditorBodyOptions, m as BuilderDefaultOption, n as BuilderEditorDbTable, o as BuilderEditorSelectOption, p as BuilderEditorTriggerValues, q as BuilderFormEditorBodyProps, r as BuilderFormEditorEntity, t as BuilderFormEditorPageDependencies, v as BuilderListEditorBodyProps, w as BuilderListEditorColumnValues, x as BuilderListEditorEntity, y as BuilderListEditorOrderByValues, z as BuilderListEditorPageDependencies, A as BuilderListEditorValues, E as builderEditorOptionsToText, F as builderEditorSearchFieldsToText, G as builderEditorTextToOptions, H as builderEditorTextToSearchFields, Z as builderFormEditorDefaultLayoutGroups, _ as builderFormEditorDefaultSpan, I as builderFormEditorFieldsFromTable, J as builderFormEditorInputFromValues, K as builderFormEditorValuesFromEntity, L as builderListEditorColumnsFromTable, M as builderListEditorInputFromValues, N as builderListEditorValuesFromEntity, O as builderViewEditorInputFromValues, P as builderViewEditorValuesFromEntity, Q as createBuilderAdminPages, R as createBuilderDefaultEntityManagerComponent, S as createBuilderDefaultFormEditorBody, T as createBuilderDefaultListEditorBody, U as createBuilderFormEditorPageComponent, V as createBuilderListEditorPageComponent, W as getBuilderEditorTriggerRoute } from '../BuilderAdminPages-bV-lwDOp.js';
3
3
  import * as react from 'react';
4
4
  import { ComponentType, ReactNode } from 'react';
5
5
  import { BuilderApiContracts } from '../api/index.js';
@@ -45,7 +45,7 @@ function clampLayoutSpan(span, columns) {
45
45
  return Math.max(1, Math.min(columns, Math.trunc(span)));
46
46
  }
47
47
  function builderFormEditorDefaultSpan(field) {
48
- if (field.type === "textarea" || field.type === "checkbox") return 12;
48
+ if (field.type === "textarea" || field.type === "richtext" || field.type === "checkbox") return 12;
49
49
  return 6;
50
50
  }
51
51
  function builderFormEditorDefaultLayoutGroups(fields, subforms = []) {
@@ -112,6 +112,10 @@ function builderFormEditorValuesFromEntity(form, emptyValues) {
112
112
  required: field.required,
113
113
  hidden: field.hidden ?? false,
114
114
  disabled: field.disabled ?? false,
115
+ storage: field.storage ?? "s3",
116
+ contentKind: field.contentKind ?? "image",
117
+ multiple: field.multiple ?? false,
118
+ maxMegabytes: field.maxBytes ? field.maxBytes / (1024 * 1024) : 10,
115
119
  placeholder: field.placeholder ?? "",
116
120
  optionsText: builderEditorOptionsToText(field.options),
117
121
  optionsMode: field.optionsSource?.type === "dbTable" ? "table" : "static",
@@ -141,6 +145,7 @@ function builderFormEditorValuesFromEntity(form, emptyValues) {
141
145
  sourceTable: source?.type === "dbTable" ? source.tableName : BUILDER_EDITOR_NONE_VALUE,
142
146
  apiBasePath: source?.type === "apiEndpoint" ? source.basePath : "",
143
147
  fields,
148
+ layoutPresentation: form.schema.layout?.presentation ?? "stacked",
144
149
  layoutGroups: normalizeBuilderFormEditorLayoutGroups(
145
150
  form.schema.layout?.groups,
146
151
  fields,
@@ -194,6 +199,10 @@ function builderFormEditorFieldsFromTable(table) {
194
199
  required: !column.nullable,
195
200
  hidden: false,
196
201
  disabled: false,
202
+ storage: "s3",
203
+ contentKind: "image",
204
+ multiple: false,
205
+ maxMegabytes: 10,
197
206
  placeholder: column.label,
198
207
  optionsText: "",
199
208
  optionsMode: "static",
@@ -230,6 +239,10 @@ function builderFormEditorInputFromValues(values) {
230
239
  required: field.required,
231
240
  hidden: field.hidden,
232
241
  disabled: field.disabled,
242
+ storage: field.type === "file" ? field.storage : void 0,
243
+ contentKind: field.type === "file" ? field.contentKind : void 0,
244
+ multiple: field.type === "file" ? field.multiple : void 0,
245
+ maxBytes: field.type === "file" && typeof field.maxMegabytes === "number" && field.maxMegabytes > 0 ? Math.round(field.maxMegabytes * 1024 * 1024) : void 0,
233
246
  placeholder: field.placeholder?.trim() || field.label,
234
247
  options: field.type === "select" && field.optionsMode === "static" ? builderEditorTextToOptions(field.optionsText) : void 0,
235
248
  optionsSource: field.type === "select" && field.optionsMode === "table" && field.optionsTable !== BUILDER_EDITOR_NONE_VALUE && field.optionsValueField !== BUILDER_EDITOR_NONE_VALUE && field.optionsLabelField !== BUILDER_EDITOR_NONE_VALUE ? {
@@ -252,6 +265,7 @@ function builderFormEditorInputFromValues(values) {
252
265
  }))
253
266
  })),
254
267
  layout: {
268
+ presentation: values.layoutPresentation,
255
269
  groups: normalizeBuilderFormEditorLayoutGroups(
256
270
  values.layoutGroups,
257
271
  values.fields,
@@ -320,7 +334,7 @@ function builderViewEditorInputFromValues(values, parseTriggers) {
320
334
  }
321
335
 
322
336
  // src/admin/BuilderDefaultEditorBodies.tsx
323
- import { jsx, jsxs } from "react/jsx-runtime";
337
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
324
338
  var styles = {
325
339
  page: {
326
340
  display: "grid",
@@ -583,6 +597,30 @@ function createBuilderDefaultFormEditorBody(options) {
583
597
  /* @__PURE__ */ jsx("input", { type: "checkbox", ...form.register(`fields.${index}.disabled`) }),
584
598
  "Disabled"
585
599
  ] }),
600
+ fields[index]?.type === "file" ? /* @__PURE__ */ jsxs(Fragment, { children: [
601
+ /* @__PURE__ */ jsxs("label", { style: styles.field, children: [
602
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Storage" }),
603
+ /* @__PURE__ */ jsxs("select", { style: styles.input, ...form.register(`fields.${index}.storage`), children: [
604
+ /* @__PURE__ */ jsx("option", { value: "s3", children: "S3" }),
605
+ /* @__PURE__ */ jsx("option", { value: "server", children: "Server" })
606
+ ] })
607
+ ] }),
608
+ /* @__PURE__ */ jsxs("label", { style: styles.field, children: [
609
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Content type" }),
610
+ /* @__PURE__ */ jsxs("select", { style: styles.input, ...form.register(`fields.${index}.contentKind`), children: [
611
+ /* @__PURE__ */ jsx("option", { value: "image", children: "Image" }),
612
+ /* @__PURE__ */ jsx("option", { value: "file", children: "File" })
613
+ ] })
614
+ ] }),
615
+ /* @__PURE__ */ jsxs("label", { style: styles.checkboxField, children: [
616
+ /* @__PURE__ */ jsx("input", { type: "checkbox", ...form.register(`fields.${index}.multiple`) }),
617
+ "Multiple"
618
+ ] }),
619
+ /* @__PURE__ */ jsxs("label", { style: styles.field, children: [
620
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Max size (MB)" }),
621
+ /* @__PURE__ */ jsx("input", { type: "number", min: "1", max: "100", style: styles.input, ...form.register(`fields.${index}.maxMegabytes`, { valueAsNumber: true }) })
622
+ ] })
623
+ ] }) : null,
586
624
  /* @__PURE__ */ jsxs("label", { style: styles.field, children: [
587
625
  /* @__PURE__ */ jsx("span", { style: styles.label, children: "Placeholder" }),
588
626
  /* @__PURE__ */ jsx("input", { style: styles.input, ...form.register(`fields.${index}.placeholder`) })
@@ -621,6 +659,13 @@ function createBuilderDefaultFormEditorBody(options) {
621
659
  }
622
660
  )
623
661
  ] }),
662
+ /* @__PURE__ */ jsx("div", { style: styles.grid, children: /* @__PURE__ */ jsxs("label", { style: styles.field, children: [
663
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: "Group presentation" }),
664
+ /* @__PURE__ */ jsxs("select", { style: styles.input, ...form.register("layoutPresentation"), children: [
665
+ /* @__PURE__ */ jsx("option", { value: "stacked", children: "Stacked sections" }),
666
+ /* @__PURE__ */ jsx("option", { value: "tabs", children: "Tabs" })
667
+ ] })
668
+ ] }) }),
624
669
  form.watch("layoutGroups").map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { style: styles.row, children: [
625
670
  /* @__PURE__ */ jsxs("label", { style: styles.field, children: [
626
671
  /* @__PURE__ */ jsx("span", { style: styles.label, children: "Group title" }),
@@ -1193,7 +1238,7 @@ function createBuilderDefaultEntityManagerComponent(deps) {
1193
1238
  // src/admin/BuilderDefaultViewEditorPage.tsx
1194
1239
  import { useEffect } from "react";
1195
1240
  import { useForm } from "react-hook-form";
1196
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1241
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1197
1242
  var styles3 = {
1198
1243
  page: {
1199
1244
  display: "grid",
@@ -1380,7 +1425,7 @@ function createBuilderDefaultViewEditorPage(options) {
1380
1425
  (lists.data ?? []).map((item) => /* @__PURE__ */ jsx3("option", { value: item.id, children: item.title }, item.id))
1381
1426
  ] })
1382
1427
  ] }),
1383
- showPermissions ? /* @__PURE__ */ jsxs3(Fragment, { children: [
1428
+ showPermissions ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1384
1429
  /* @__PURE__ */ jsxs3("label", { style: styles3.field, children: [
1385
1430
  /* @__PURE__ */ jsx3("span", { style: styles3.label, children: "Read permission" }),
1386
1431
  /* @__PURE__ */ jsxs3("select", { style: styles3.input, ...form.register("permissionRead"), children: [
@@ -1544,6 +1589,7 @@ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1544
1589
  var defaultFieldTypeOptions = [
1545
1590
  { value: "text", label: "Text" },
1546
1591
  { value: "textarea", label: "Textarea" },
1592
+ { value: "richtext", label: "Rich text" },
1547
1593
  { value: "number", label: "Number" },
1548
1594
  { value: "money", label: "Money" },
1549
1595
  { value: "select", label: "Select" },
@@ -1654,6 +1700,10 @@ function createBuilderAdminPages({
1654
1700
  required: true,
1655
1701
  hidden: false,
1656
1702
  disabled: false,
1703
+ storage: "s3",
1704
+ contentKind: "image",
1705
+ multiple: false,
1706
+ maxMegabytes: 10,
1657
1707
  placeholder: "",
1658
1708
  optionsText: "",
1659
1709
  optionsMode: "static",
@@ -1673,6 +1723,7 @@ function createBuilderAdminPages({
1673
1723
  sourceTable: BUILDER_EDITOR_NONE_VALUE,
1674
1724
  apiBasePath: "",
1675
1725
  fields: [emptyField],
1726
+ layoutPresentation: "stacked",
1676
1727
  layoutGroups: builderFormEditorDefaultLayoutGroups([emptyField]),
1677
1728
  subforms: [],
1678
1729
  triggers: [],