@nextbridgehq/payload-block-builder 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -39,72 +39,207 @@ function MediaPicker({ label, required, value, onChange }) {
39
39
  "x"
40
40
  ))) : /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from Media Library")), /* @__PURE__ */ React.createElement(ListDrawer, { onSelect: handleSelect }));
41
41
  }
42
- function RelationshipPicker({ label, required, collection, value, onChange }) {
42
+ function toRelDoc(v) {
43
+ if (!v) return null;
44
+ if (typeof v === "object") {
45
+ const o = v;
46
+ if (!o.id) return null;
47
+ return { id: o.id, title: o.title ? String(o.title) : null };
48
+ }
49
+ if (typeof v === "string" || typeof v === "number") return { id: v, title: null };
50
+ return null;
51
+ }
52
+ function RelationshipPicker({ label, required, collection, hasMany = false, value, onChange }) {
43
53
  const changeRef = useRef(onChange);
44
54
  const closeRef = useRef(() => {
45
55
  });
46
56
  useEffect(() => {
47
57
  changeRef.current = onChange;
48
58
  });
59
+ const items = hasMany ? Array.isArray(value) ? value.map(toRelDoc).filter(Boolean) : [] : (() => {
60
+ const d = toRelDoc(value);
61
+ return d ? [d] : [];
62
+ })();
63
+ const itemsRef = useRef(items);
64
+ useEffect(() => {
65
+ itemsRef.current = items;
66
+ });
49
67
  const handleSelect = useCallback(
50
68
  ({ docID, doc }) => {
51
- changeRef.current({
52
- id: docID,
53
- title: doc?.title ?? doc?.name ?? doc?.slug ?? null
54
- });
55
- closeRef.current();
69
+ const title = String(doc?.title ?? doc?.name ?? doc?.slug ?? "") || null;
70
+ const entry = { id: docID, title };
71
+ if (hasMany) {
72
+ const alreadyExists = itemsRef.current.some((i) => String(i.id) === String(docID));
73
+ if (!alreadyExists) changeRef.current([...itemsRef.current, entry]);
74
+ } else {
75
+ changeRef.current(entry);
76
+ closeRef.current();
77
+ }
56
78
  },
57
- []
79
+ [hasMany]
58
80
  );
59
- const [ListDrawer, ListDrawerToggler, { closeDrawer }] = useListDrawer({
81
+ const [ListDrawer, ListDrawerToggler, { closeDrawer, openDrawer }] = useListDrawer({
60
82
  collectionSlugs: [collection]
61
83
  });
62
84
  closeRef.current = closeDrawer;
63
- const relObj = value && typeof value === "object" ? value : null;
64
- const relId = relObj?.id ?? (typeof value === "string" || typeof value === "number" ? value : null);
65
- const relTitle = relObj?.title ? String(relObj.title) : null;
66
- const [fetchedTitle, setFetchedTitle] = useState(null);
85
+ const [fetchedTitles, setFetchedTitles] = useState({});
86
+ const fetchingRef = useRef(/* @__PURE__ */ new Set());
67
87
  useEffect(() => {
68
- if (!relId || relTitle) {
69
- setFetchedTitle(null);
70
- return;
88
+ const missing = items.filter(
89
+ (i) => !i.title && !fetchedTitles[String(i.id)] && !fetchingRef.current.has(String(i.id))
90
+ );
91
+ if (missing.length === 0) return;
92
+ missing.forEach((item) => {
93
+ const idStr = String(item.id);
94
+ fetchingRef.current.add(idStr);
95
+ fetch(`/api/${collection}/${idStr}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
96
+ if (doc) {
97
+ const t = doc.title ?? doc.name ?? doc.slug ?? null;
98
+ if (t) setFetchedTitles((prev) => ({ ...prev, [idStr]: String(t) }));
99
+ }
100
+ }).catch((err) => {
101
+ console.error("[Block Builder] Failed to load relation title:", err);
102
+ fetchingRef.current.delete(idStr);
103
+ });
104
+ });
105
+ }, [items, collection]);
106
+ function getTitle(item) {
107
+ return item.title ?? fetchedTitles[String(item.id)] ?? `ID: ${String(item.id)}`;
108
+ }
109
+ function clearOne(e, id) {
110
+ e.stopPropagation();
111
+ if (hasMany) onChange(items.filter((i) => String(i.id) !== String(id)));
112
+ else onChange(null);
113
+ }
114
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ React.createElement("div", { className: `bdf-rel${hasMany ? " bdf-rel--multi" : ""}` }, /* @__PURE__ */ React.createElement(
115
+ "div",
116
+ {
117
+ role: "button",
118
+ tabIndex: 0,
119
+ className: "bdf-rel__control",
120
+ onClick: openDrawer,
121
+ onKeyDown: (e) => {
122
+ if (e.key === "Enter" || e.key === " ") openDrawer();
123
+ }
124
+ },
125
+ /* @__PURE__ */ React.createElement("div", { className: "bdf-rel__values" }, items.length === 0 && /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__placeholder" }, "Select a value..."), !hasMany && items.length > 0 && /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__single" }, getTitle(items[0])), hasMany && items.map((item) => /* @__PURE__ */ React.createElement("span", { key: String(item.id), className: "bdf-rel__chip" }, /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__chip-label" }, getTitle(item)), /* @__PURE__ */ React.createElement(
126
+ "button",
127
+ {
128
+ type: "button",
129
+ className: "bdf-rel__chip-remove",
130
+ onClick: (e) => clearOne(e, item.id),
131
+ "aria-label": `Remove ${getTitle(item)}`
132
+ },
133
+ /* @__PURE__ */ React.createElement("svg", { height: "12", width: "12", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z" }))
134
+ )))),
135
+ /* @__PURE__ */ React.createElement("div", { className: "bdf-rel__indicators" }, !hasMany && items.length > 0 && /* @__PURE__ */ React.createElement(
136
+ "button",
137
+ {
138
+ type: "button",
139
+ className: "bdf-rel__clear",
140
+ onClick: (e) => clearOne(e, items[0].id),
141
+ "aria-label": "Clear"
142
+ },
143
+ /* @__PURE__ */ React.createElement("svg", { height: "16", width: "16", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z" }))
144
+ ), /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__sep" }), /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__chevron" }, /* @__PURE__ */ React.createElement("svg", { height: "16", width: "16", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ React.createElement("path", { d: "M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z" }))))
145
+ ), hasMany && /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-rel__add", onClick: (e) => e.stopPropagation() }, "+")), /* @__PURE__ */ React.createElement("span", { className: "bdf-rel__hint" }, collection, hasMany ? " \xB7 multiple" : ""), /* @__PURE__ */ React.createElement(ListDrawer, { onSelect: handleSelect }));
146
+ }
147
+ function JsonField({ label, required, value, onChange }) {
148
+ const [text, setText] = useState(
149
+ () => value !== void 0 ? JSON.stringify(value, null, 2) : ""
150
+ );
151
+ const [hasError, setHasError] = useState(false);
152
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ React.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ React.createElement(
153
+ "textarea",
154
+ {
155
+ className: "bdf-input bdf-textarea bdf-mono",
156
+ value: text,
157
+ rows: 4,
158
+ onChange: (e) => {
159
+ setText(e.target.value);
160
+ try {
161
+ onChange(JSON.parse(e.target.value));
162
+ setHasError(false);
163
+ } catch {
164
+ setHasError(true);
165
+ }
166
+ }
167
+ }
168
+ ), hasError && /* @__PURE__ */ React.createElement("div", { className: "bdf-error", style: { marginTop: 4 } }, "Invalid JSON \u2014 changes not saved until fixed."));
169
+ }
170
+ function SchemaForm({ schema, value, onChange, depth = 0 }) {
171
+ const set = useCallback(
172
+ (key, val) => onChange({ ...value, [key]: val }),
173
+ [value, onChange]
174
+ );
175
+ if (depth > 10) {
176
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-error" }, "Max nesting depth reached (10).");
177
+ }
178
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, schema.map((field) => {
179
+ if (field.type === "row") {
180
+ return /* @__PURE__ */ React.createElement("div", { key: field.name, className: "bdf-row" }, /* @__PURE__ */ React.createElement(SchemaForm, { schema: field.fields, value, onChange, depth: depth + 1 }));
181
+ }
182
+ if (field.type === "collapsible") {
183
+ return /* @__PURE__ */ React.createElement(CollapsibleSection, { key: field.name, field, value, onChange, depth: depth + 1 });
71
184
  }
72
- fetch(`/api/${collection}/${String(relId)}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
73
- if (doc) {
74
- const t = doc.title ?? doc.name ?? doc.slug ?? null;
75
- setFetchedTitle(t ? String(t) : null);
185
+ if (field.type === "tabs") {
186
+ return /* @__PURE__ */ React.createElement(TabsSection, { key: field.name, field, value, onChange, depth: depth + 1 });
187
+ }
188
+ return /* @__PURE__ */ React.createElement(
189
+ FieldInput,
190
+ {
191
+ key: field.name,
192
+ field,
193
+ value: value[field.name],
194
+ onChange: (v) => set(field.name, v),
195
+ depth
76
196
  }
77
- }).catch(() => {
78
- });
79
- }, [relId, relTitle, collection]);
80
- const displayTitle = relTitle ?? fetchedTitle;
81
- return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ React.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ React.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", collection, ")")), /* @__PURE__ */ React.createElement("div", { className: "bdf-upload-area" }, relId ? /* @__PURE__ */ React.createElement("div", { className: "bdf-upload-selected" }, /* @__PURE__ */ React.createElement("span", { className: "bdf-upload-name" }, displayTitle ?? `ID: ${String(relId)}`), /* @__PURE__ */ React.createElement("div", { className: "bdf-upload-actions" }, /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Change"), /* @__PURE__ */ React.createElement(
197
+ );
198
+ }));
199
+ }
200
+ function CollapsibleSection({
201
+ field,
202
+ value,
203
+ onChange,
204
+ depth
205
+ }) {
206
+ const [open, setOpen] = useState(true);
207
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-collapsible" }, /* @__PURE__ */ React.createElement(
82
208
  "button",
83
209
  {
84
210
  type: "button",
85
- className: "bdf-icon-btn bdf-icon-btn--danger",
86
- title: "Remove",
87
- onClick: () => onChange(null)
211
+ className: "bdf-collapsible__header",
212
+ onClick: () => setOpen((o) => !o)
88
213
  },
89
- "\xD7"
90
- ))) : /* @__PURE__ */ React.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from ", collection)), /* @__PURE__ */ React.createElement(ListDrawer, { onSelect: handleSelect }));
214
+ /* @__PURE__ */ React.createElement("span", { className: `bdf-collapsible__caret${open ? " bdf-collapsible__caret--open" : ""}` }, "\u25B8"),
215
+ field.label
216
+ ), open && /* @__PURE__ */ React.createElement("div", { className: "bdf-collapsible__body" }, /* @__PURE__ */ React.createElement(SchemaForm, { schema: field.fields, value, onChange, depth })));
91
217
  }
92
- function SchemaForm({ schema, value, onChange }) {
218
+ function TabsSection({
219
+ field,
220
+ value,
221
+ onChange,
222
+ depth
223
+ }) {
224
+ const [activeTab, setActiveTab] = useState(0);
93
225
  const set = useCallback(
94
226
  (key, val) => onChange({ ...value, [key]: val }),
95
227
  [value, onChange]
96
228
  );
97
- return /* @__PURE__ */ React.createElement(React.Fragment, null, schema.map((field) => /* @__PURE__ */ React.createElement(
98
- FieldInput,
229
+ const tabs = field.tabs ?? [];
230
+ const tab = tabs[activeTab];
231
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-tabs" }, /* @__PURE__ */ React.createElement("div", { className: "bdf-tabs__list" }, tabs.map((t, i) => /* @__PURE__ */ React.createElement(
232
+ "button",
99
233
  {
100
- key: field.name,
101
- field,
102
- value: value[field.name],
103
- onChange: (v) => set(field.name, v)
104
- }
105
- )));
234
+ key: t.name ?? t.label ?? i,
235
+ type: "button",
236
+ className: `bdf-tabs__tab${i === activeTab ? " bdf-tabs__tab--active" : ""}`,
237
+ onClick: () => setActiveTab(i)
238
+ },
239
+ t.label
240
+ ))), /* @__PURE__ */ React.createElement("div", { className: "bdf-tabs__panel" }, tab && (tab.name ? /* @__PURE__ */ React.createElement(SchemaForm, { schema: tab.fields, value: value[tab.name] ?? {}, onChange: (v) => set(tab.name, v), depth }) : /* @__PURE__ */ React.createElement(SchemaForm, { schema: tab.fields, value, onChange, depth }))));
106
241
  }
107
- function FieldInput({ field, value, onChange }) {
242
+ function FieldInput({ field, value, onChange, depth }) {
108
243
  const label = field.label ?? field.name;
109
244
  switch (field.type) {
110
245
  case "text":
@@ -114,7 +249,7 @@ function FieldInput({ field, value, onChange }) {
114
249
  "input",
115
250
  {
116
251
  className: "bdf-input",
117
- type: field.type === "email" ? "email" : "text",
252
+ type: field.type === "email" ? "email" : field.type === "url" ? "url" : "text",
118
253
  value: value ?? "",
119
254
  onChange: (e) => onChange(e.target.value)
120
255
  }
@@ -167,7 +302,7 @@ function FieldInput({ field, value, onChange }) {
167
302
  className: "bdf-input",
168
303
  type: "number",
169
304
  value: numVal,
170
- onChange: (e) => onChange(e.target.value === "" ? void 0 : e.target.valueAsNumber)
305
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.valueAsNumber)
171
306
  }
172
307
  ));
173
308
  }
@@ -227,33 +362,24 @@ function FieldInput({ field, value, onChange }) {
227
362
  }
228
363
  );
229
364
  case "relationship": {
230
- const collection = field.collection ?? "media";
365
+ const relField = field;
366
+ if (!relField.collection) {
367
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label), /* @__PURE__ */ React.createElement("div", { className: "bdf-error" }, "Relationship field ", /* @__PURE__ */ React.createElement("strong", null, field.name), " has no ", /* @__PURE__ */ React.createElement("code", null, "collection"), " defined in its schema."));
368
+ }
231
369
  return /* @__PURE__ */ React.createElement(
232
370
  RelationshipPicker,
233
371
  {
234
372
  label,
235
373
  required: field.required,
236
- collection,
374
+ collection: relField.collection,
375
+ hasMany: relField.hasMany ?? false,
237
376
  value,
238
377
  onChange
239
378
  }
240
379
  );
241
380
  }
242
381
  case "json":
243
- return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ React.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ React.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ React.createElement(
244
- "textarea",
245
- {
246
- className: "bdf-input bdf-textarea bdf-mono",
247
- value: value !== void 0 ? JSON.stringify(value, null, 2) : "",
248
- rows: 4,
249
- onChange: (e) => {
250
- try {
251
- onChange(JSON.parse(e.target.value));
252
- } catch {
253
- }
254
- }
255
- }
256
- ));
382
+ return /* @__PURE__ */ React.createElement(JsonField, { label, required: field.required, value, onChange });
257
383
  case "array": {
258
384
  const rows = Array.isArray(value) ? value : [];
259
385
  const subFields = field.fields ?? [];
@@ -266,7 +392,8 @@ function FieldInput({ field, value, onChange }) {
266
392
  const next = [...rows];
267
393
  next[i] = updated;
268
394
  onChange(next);
269
- }
395
+ },
396
+ depth: depth + 1
270
397
  }
271
398
  ), /* @__PURE__ */ React.createElement(
272
399
  "button",
@@ -290,7 +417,7 @@ function FieldInput({ field, value, onChange }) {
290
417
  case "group": {
291
418
  const groupVal = value ?? {};
292
419
  const subFields = field.fields ?? [];
293
- return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ React.createElement("div", { className: "bdf-fieldset__header" }, label), /* @__PURE__ */ React.createElement("div", { className: "bdf-fieldset__body" }, /* @__PURE__ */ React.createElement(SchemaForm, { schema: subFields, value: groupVal, onChange }))));
420
+ return /* @__PURE__ */ React.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ React.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ React.createElement("div", { className: "bdf-fieldset__header" }, label), /* @__PURE__ */ React.createElement("div", { className: "bdf-fieldset__body" }, /* @__PURE__ */ React.createElement(SchemaForm, { schema: subFields, value: groupVal, onChange, depth: depth + 1 }))));
294
421
  }
295
422
  default:
296
423
  return /* @__PURE__ */ React.createElement("div", { style: { fontSize: 12, color: "var(--theme-elevation-400)", fontFamily: "var(--font-body)", padding: "4px 0" } }, "Unsupported field type: ", /* @__PURE__ */ React.createElement("strong", null, field.type), " (", label, ")");
@@ -311,6 +438,10 @@ function BlockDataField({ path }) {
311
438
  setError(null);
312
439
  return;
313
440
  }
441
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(versionId))) {
442
+ setError("Invalid version ID format");
443
+ return;
444
+ }
314
445
  setLoading(true);
315
446
  setError(null);
316
447
  fetch(`/api/block-definition-versions/${versionId}?depth=0`, { credentials: "same-origin" }).then((r) => {
@@ -349,9 +480,41 @@ function BlockDataField({ path }) {
349
480
  )));
350
481
  }
351
482
 
483
+ // src/components/BlockVersionSync/index.tsx
484
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
485
+ import { useField as useField2, useFormFields as useFormFields2 } from "@payloadcms/ui";
486
+ function BlockVersionSync({ path }) {
487
+ const blockVersionPath = path.replace(/\.blockDefinition$/, ".blockVersion");
488
+ const { setValue: setVersion } = useField2({ path: blockVersionPath });
489
+ const blockDefValue = useFormFields2(([fields]) => fields[path]?.value);
490
+ const prevDefIdRef = useRef2(null);
491
+ useEffect2(() => {
492
+ const defId = blockDefValue && typeof blockDefValue === "object" ? blockDefValue.id : typeof blockDefValue === "string" || typeof blockDefValue === "number" ? blockDefValue : null;
493
+ if (defId === prevDefIdRef.current) return;
494
+ prevDefIdRef.current = defId;
495
+ if (!defId) {
496
+ setVersion(null);
497
+ return;
498
+ }
499
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(defId))) {
500
+ return;
501
+ }
502
+ fetch(`/api/block-definitions/${String(defId)}?depth=1`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
503
+ if (!doc) return;
504
+ const currentVersion = doc.currentVersion;
505
+ if (!currentVersion) return;
506
+ const versionId = typeof currentVersion === "object" ? currentVersion.id : currentVersion;
507
+ if (versionId) setVersion(versionId);
508
+ }).catch((err) => {
509
+ console.error("[Block Builder] BlockVersionSync fetch error:", err);
510
+ });
511
+ }, [blockDefValue, setVersion]);
512
+ return null;
513
+ }
514
+
352
515
  // src/components/SchemaBuilderField/index.tsx
353
- import React5, { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
354
- import { useField as useField2 } from "@payloadcms/ui";
516
+ import React6, { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
517
+ import { useField as useField3 } from "@payloadcms/ui";
355
518
 
356
519
  // src/components/SchemaBuilderField/FieldRow.tsx
357
520
  import React4, { useState as useState2 } from "react";
@@ -913,128 +1076,72 @@ function FieldRow({
913
1076
  )))));
914
1077
  }
915
1078
 
916
- // src/components/SchemaBuilderField/index.tsx
917
- function parseSchema(raw) {
918
- try {
919
- const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
920
- if (!parsed) return [];
921
- if (Array.isArray(parsed)) return parsed;
922
- if (parsed?.fields && Array.isArray(parsed.fields)) return parsed.fields;
923
- } catch {
1079
+ // src/block-builder/components/ErrorBoundary.tsx
1080
+ import React5, { Component } from "react";
1081
+
1082
+ // src/block-builder/store/builder.store.ts
1083
+ import { create } from "zustand";
1084
+ import { devtools, persist } from "zustand/middleware";
1085
+ import { immer } from "zustand/middleware/immer";
1086
+
1087
+ // src/utils/uuid.ts
1088
+ function uuidv4() {
1089
+ const c = globalThis.crypto;
1090
+ if (typeof c?.randomUUID === "function") {
1091
+ return c.randomUUID();
924
1092
  }
925
- return [];
1093
+ if (typeof c?.getRandomValues === "function") {
1094
+ const bytes = c.getRandomValues(new Uint8Array(16));
1095
+ bytes[6] = bytes[6] & 15 | 64;
1096
+ bytes[8] = bytes[8] & 63 | 128;
1097
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1098
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1099
+ }
1100
+ throw new Error(
1101
+ "No cryptographic random source available. `crypto.randomUUID` requires a secure context (HTTPS or localhost)."
1102
+ );
926
1103
  }
927
- function SchemaBuilderField({ path, readOnly }) {
928
- const { value, setValue } = useField2({ path });
929
- const setValueRef = useRef2(setValue);
930
- useEffect2(() => {
931
- setValueRef.current = setValue;
932
- });
933
- const [fields, setFields] = useState3(() => parseSchema(value));
934
- const hasExistingValue = value !== void 0 && value !== null;
935
- const [hydrated, setHydrated] = useState3(!hasExistingValue);
936
- useEffect2(() => {
937
- if (hydrated) return;
938
- if (value !== void 0 && value !== null) {
939
- setFields(parseSchema(value));
940
- setHydrated(true);
941
- }
942
- }, [value, hydrated]);
943
- useEffect2(() => {
944
- if (!hydrated) return;
945
- setValueRef.current({ fields });
946
- }, [fields, hydrated]);
947
- const addField = useCallback3(() => {
948
- setFields((prev) => [
949
- ...prev,
950
- { name: "", type: "text", label: "", required: false }
951
- ]);
952
- }, []);
953
- const updateField = useCallback3((index, updated) => {
954
- setFields((prev) => prev.map((f, i) => i === index ? updated : f));
955
- }, []);
956
- const removeField = useCallback3((index) => {
957
- setFields((prev) => prev.filter((_, i) => i !== index));
958
- }, []);
959
- const moveField = useCallback3((index, dir) => {
960
- setFields((prev) => {
961
- const next = [...prev];
962
- const target = index + dir;
963
- if (target < 0 || target >= next.length) return prev;
964
- [next[index], next[target]] = [next[target], next[index]];
965
- return next;
966
- });
967
- }, []);
968
- return /* @__PURE__ */ React5.createElement("div", null, /* @__PURE__ */ React5.createElement("div", { className: "sbf-section-heading" }, /* @__PURE__ */ React5.createElement("span", { className: "sbf-section-label" }, "Fields", fields.length > 0 && /* @__PURE__ */ React5.createElement(
969
- "span",
970
- {
971
- style: {
972
- marginLeft: 8,
973
- fontSize: 11,
974
- color: "var(--theme-elevation-400)",
975
- fontWeight: 400
1104
+
1105
+ // src/block-builder/store/builder.store.ts
1106
+ var TAB_PATH_SEP = "::";
1107
+ var BUILDER_PERSIST_KEY = "@nextbridgehq/payload-block-builder";
1108
+ function encodeTabPath(fieldId, tabIndex) {
1109
+ return `${fieldId}${TAB_PATH_SEP}${tabIndex}`;
1110
+ }
1111
+ function walkPath(block, parentPath, create2) {
1112
+ let currentFields = block.fields;
1113
+ for (const segment of parentPath) {
1114
+ const sepIndex = segment.indexOf(TAB_PATH_SEP);
1115
+ if (sepIndex !== -1) {
1116
+ const fieldId = segment.slice(0, sepIndex);
1117
+ const tabIndex = Number(segment.slice(sepIndex + TAB_PATH_SEP.length));
1118
+ const parentField2 = currentFields.find((f) => f.id === fieldId);
1119
+ if (!parentField2 || parentField2.type !== "tabs" || !Array.isArray(parentField2.tabs)) return null;
1120
+ const tab = parentField2.tabs[tabIndex];
1121
+ if (!tab) return null;
1122
+ if (!tab.fields) {
1123
+ if (!create2) return null;
1124
+ tab.fields = [];
976
1125
  }
977
- },
978
- "(",
979
- fields.length,
980
- ")"
981
- ))), fields.length === 0 ? /* @__PURE__ */ React5.createElement("div", { className: "sbf-empty" }, "No fields defined. Click \u201CAdd Field\u201D below to add the first field to this block schema.") : /* @__PURE__ */ React5.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ React5.createElement(
982
- FieldRow,
983
- {
984
- key: i,
985
- index: i,
986
- total: fields.length,
987
- field,
988
- onChange: (updated) => updateField(i, updated),
989
- onRemove: () => removeField(i),
990
- onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
991
- onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
992
- readOnly
1126
+ currentFields = tab.fields;
1127
+ continue;
1128
+ }
1129
+ const parentField = currentFields.find((f) => f.id === segment);
1130
+ if (!parentField) return null;
1131
+ if (!parentField.fields) {
1132
+ if (!create2) return null;
1133
+ parentField.fields = [];
993
1134
  }
994
- ))), !readOnly && /* @__PURE__ */ React5.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)", marginBottom: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ React5.createElement("button", { type: "button", className: "sbf-add-btn", onClick: addField }, /* @__PURE__ */ React5.createElement("span", { className: "sbf-add-btn__icon" }, "+"), "Add Field")));
1135
+ currentFields = parentField.fields;
1136
+ }
1137
+ return currentFields;
995
1138
  }
996
-
997
- // src/components/EditInBuilderButton/index.tsx
998
- import React6 from "react";
999
- import { useDocumentInfo } from "@payloadcms/ui";
1000
- import { useField as useField3 } from "@payloadcms/ui";
1001
- function EditInBuilderButton() {
1002
- const { id } = useDocumentInfo();
1003
- const { value: slug } = useField3({ path: "slug" });
1004
- if (!id || !slug) return null;
1005
- return /* @__PURE__ */ React6.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ React6.createElement(
1006
- "a",
1007
- {
1008
- href: `/block-builder?load=${slug}`,
1009
- target: "_blank",
1010
- rel: "noopener noreferrer",
1011
- style: {
1012
- display: "inline-flex",
1013
- alignItems: "center",
1014
- gap: "6px",
1015
- padding: "8px 16px",
1016
- borderRadius: "6px",
1017
- border: "1px solid #16a34a",
1018
- color: "#16a34a",
1019
- textDecoration: "none",
1020
- fontSize: "13px",
1021
- fontWeight: 500,
1022
- background: "transparent",
1023
- cursor: "pointer"
1024
- }
1025
- },
1026
- "Edit in Block Builder"
1027
- ));
1139
+ function getTargetFields(block, parentPath) {
1140
+ return walkPath(block, parentPath, false);
1141
+ }
1142
+ function ensureTargetFields(block, parentPath) {
1143
+ return walkPath(block, parentPath, true);
1028
1144
  }
1029
-
1030
- // src/block-builder/components/canvas/BuilderShell.tsx
1031
- import React16, { useCallback as useCallback5, useEffect as useEffect5, useState as useState8 } from "react";
1032
-
1033
- // src/block-builder/store/builder.store.ts
1034
- import { create } from "zustand";
1035
- import { devtools, persist } from "zustand/middleware";
1036
- import { immer } from "zustand/middleware/immer";
1037
- import { v4 as uuidv4 } from "uuid";
1038
1145
  function createDefaultField(type) {
1039
1146
  const base = {
1040
1147
  id: uuidv4(),
@@ -1045,7 +1152,6 @@ function createDefaultField(type) {
1045
1152
  };
1046
1153
  switch (type) {
1047
1154
  case "select":
1048
- case "radio":
1049
1155
  return {
1050
1156
  ...base,
1051
1157
  options: [
@@ -1054,15 +1160,31 @@ function createDefaultField(type) {
1054
1160
  ]
1055
1161
  };
1056
1162
  case "relationship":
1057
- return { ...base, relationTo: "", hasMany: false };
1163
+ return { ...base, collection: "", hasMany: false };
1058
1164
  case "array":
1059
1165
  return { ...base, fields: [] };
1060
1166
  case "group":
1061
1167
  return { ...base, fields: [] };
1168
+ case "row":
1169
+ return { ...base, fields: [] };
1170
+ case "collapsible":
1171
+ return { ...base, label: base.label ?? "Collapsible Section", fields: [] };
1172
+ case "tabs":
1173
+ return { ...base, tabs: [{ id: uuidv4(), label: "Tab 1", fields: [] }] };
1062
1174
  default:
1063
1175
  return base;
1064
1176
  }
1065
1177
  }
1178
+ function regenerateFieldIds(fields) {
1179
+ return fields.map((f) => {
1180
+ const next = { ...f, id: uuidv4() };
1181
+ if (next.fields) next.fields = regenerateFieldIds(next.fields);
1182
+ if (next.tabs) {
1183
+ next.tabs = next.tabs.map((tab) => ({ ...tab, fields: regenerateFieldIds(tab.fields) }));
1184
+ }
1185
+ return next;
1186
+ });
1187
+ }
1066
1188
  function createDefaultBlock() {
1067
1189
  return {
1068
1190
  id: uuidv4(),
@@ -1086,11 +1208,13 @@ var useBuilderStore = create()(
1086
1208
  isReadOnly: false,
1087
1209
  loadedVersionId: null,
1088
1210
  blockSlug: null,
1211
+ activeParentPath: [],
1089
1212
  addBlock: () => set((state) => {
1090
1213
  const block = createDefaultBlock();
1091
1214
  state.blocks.push(block);
1092
1215
  state.activeBlockId = block.id;
1093
1216
  state.activeFieldId = null;
1217
+ state.activeParentPath = [];
1094
1218
  state.isDirty = true;
1095
1219
  }),
1096
1220
  removeBlock: (blockId) => set((state) => {
@@ -1098,6 +1222,7 @@ var useBuilderStore = create()(
1098
1222
  if (state.activeBlockId === blockId) {
1099
1223
  state.activeBlockId = state.blocks[0]?.id ?? null;
1100
1224
  state.activeFieldId = null;
1225
+ state.activeParentPath = [];
1101
1226
  }
1102
1227
  state.isDirty = true;
1103
1228
  }),
@@ -1109,15 +1234,16 @@ var useBuilderStore = create()(
1109
1234
  setActiveBlock: (blockId) => set((state) => {
1110
1235
  state.activeBlockId = blockId;
1111
1236
  state.activeFieldId = null;
1237
+ state.activeParentPath = [];
1112
1238
  }),
1113
1239
  duplicateBlock: (blockId) => set((state) => {
1114
1240
  const block = state.blocks.find((b) => b.id === blockId);
1115
1241
  if (!block) return;
1116
1242
  const clone = JSON.parse(JSON.stringify(block));
1117
1243
  clone.id = uuidv4();
1118
- clone.slug = `${block.slug}Copy`;
1244
+ clone.slug = `${block.slug}-copy`;
1119
1245
  clone.interfaceName = block.interfaceName ? `${block.interfaceName}Copy` : void 0;
1120
- clone.fields = clone.fields.map((f) => ({ ...f, id: uuidv4() }));
1246
+ clone.fields = regenerateFieldIds(clone.fields);
1121
1247
  const idx = state.blocks.findIndex((b) => b.id === blockId);
1122
1248
  state.blocks.splice(idx + 1, 0, clone);
1123
1249
  state.activeBlockId = clone.id;
@@ -1126,36 +1252,47 @@ var useBuilderStore = create()(
1126
1252
  addField: (blockId, type) => set((state) => {
1127
1253
  const block = state.blocks.find((b) => b.id === blockId);
1128
1254
  if (!block) return;
1255
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1256
+ if (!targetFields) return;
1129
1257
  const field = createDefaultField(type);
1130
- block.fields.push(field);
1258
+ targetFields.push(field);
1131
1259
  state.activeFieldId = field.id;
1132
1260
  state.isDirty = true;
1133
1261
  }),
1134
1262
  removeField: (blockId, fieldId) => set((state) => {
1135
1263
  const block = state.blocks.find((b) => b.id === blockId);
1136
1264
  if (!block) return;
1137
- block.fields = block.fields.filter((f) => f.id !== fieldId);
1265
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1266
+ if (!targetFields) return;
1267
+ const index = targetFields.findIndex((f) => f.id === fieldId);
1268
+ if (index !== -1) {
1269
+ targetFields.splice(index, 1);
1270
+ }
1138
1271
  if (state.activeFieldId === fieldId) state.activeFieldId = null;
1139
1272
  state.isDirty = true;
1140
1273
  }),
1141
1274
  updateField: (blockId, fieldId, updates) => set((state) => {
1142
1275
  const block = state.blocks.find((b) => b.id === blockId);
1143
1276
  if (!block) return;
1144
- const field = block.fields.find((f) => f.id === fieldId);
1277
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1278
+ if (!targetFields) return;
1279
+ const field = targetFields.find((f) => f.id === fieldId);
1145
1280
  if (field) Object.assign(field, updates);
1146
1281
  state.isDirty = true;
1147
1282
  }),
1148
1283
  reorderFields: (blockId, fromIndex, toIndex) => set((state) => {
1149
1284
  const block = state.blocks.find((b) => b.id === blockId);
1150
1285
  if (!block) return;
1151
- const [moved] = block.fields.splice(fromIndex, 1);
1152
- block.fields.splice(toIndex, 0, moved);
1286
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1287
+ if (!targetFields) return;
1288
+ const [moved] = targetFields.splice(fromIndex, 1);
1289
+ targetFields.splice(toIndex, 0, moved);
1153
1290
  state.isDirty = true;
1154
1291
  }),
1155
1292
  setActiveField: (fieldId) => set((state) => {
1156
1293
  state.activeFieldId = fieldId;
1157
1294
  }),
1158
- reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null })),
1295
+ reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null, activeParentPath: [] })),
1159
1296
  markClean: () => set((state) => {
1160
1297
  state.isDirty = false;
1161
1298
  }),
@@ -1163,6 +1300,7 @@ var useBuilderStore = create()(
1163
1300
  state.blocks = [block];
1164
1301
  state.activeBlockId = block.id;
1165
1302
  state.activeFieldId = null;
1303
+ state.activeParentPath = [];
1166
1304
  state.isDirty = false;
1167
1305
  }),
1168
1306
  setVersionMeta: (versionId, isReadOnly) => set((state) => {
@@ -1171,10 +1309,36 @@ var useBuilderStore = create()(
1171
1309
  }),
1172
1310
  setBlockSlug: (slug) => set((state) => {
1173
1311
  state.blockSlug = slug;
1312
+ }),
1313
+ pushParentPath: (fieldId) => set((state) => {
1314
+ state.activeParentPath.push(fieldId);
1315
+ state.activeFieldId = null;
1316
+ }),
1317
+ popParentPath: () => set((state) => {
1318
+ state.activeParentPath.pop();
1319
+ state.activeFieldId = null;
1320
+ }),
1321
+ // Jump directly to an ancestor level -- `depth` is the number of
1322
+ // segments to keep, so breadcrumb index `i` maps to `i + 1`.
1323
+ truncateParentPath: (depth) => set((state) => {
1324
+ if (depth < 0 || depth >= state.activeParentPath.length) return;
1325
+ state.activeParentPath = state.activeParentPath.slice(0, depth);
1326
+ state.activeFieldId = null;
1327
+ }),
1328
+ resetParentPath: () => set((state) => {
1329
+ state.activeParentPath = [];
1330
+ state.activeFieldId = null;
1174
1331
  })
1175
1332
  })),
1176
1333
  {
1177
- name: "@nextbridgehq/payload-block-builder",
1334
+ name: BUILDER_PERSIST_KEY,
1335
+ version: 1,
1336
+ migrate: (persistedState, version) => {
1337
+ if (version === 0) {
1338
+ return { blocks: [], activeBlockId: null };
1339
+ }
1340
+ return persistedState;
1341
+ },
1178
1342
  partialize: (state) => ({
1179
1343
  blocks: state.blocks,
1180
1344
  activeBlockId: state.activeBlockId
@@ -1184,163 +1348,191 @@ var useBuilderStore = create()(
1184
1348
  )
1185
1349
  );
1186
1350
 
1187
- // src/block-builder/components/canvas/TopBar.tsx
1188
- import React7, { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1189
- import { Blocks, ChevronDown, X } from "lucide-react";
1190
-
1191
- // src/block-builder/lib/mapToSaveRequest.ts
1192
- var TYPE_MAP = {
1193
- richText: "richtext",
1194
- upload: "image",
1195
- radio: "select"
1196
- };
1197
- var UNSUPPORTED = /* @__PURE__ */ new Set(["code", "point", "ui", "tabs", "collapsible", "row"]);
1198
- function normalizeSlug(slug) {
1199
- return slug.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1200
- }
1201
- function mapToSaveRequest(block) {
1202
- const fields = block.fields.filter((f) => {
1203
- if (UNSUPPORTED.has(f.type)) {
1204
- console.warn(`[block-builder] Field type "${f.type}" is not supported in this project \xE2\u20AC" skipping field "${f.name}"`);
1205
- return false;
1206
- }
1207
- return true;
1208
- }).map(({ id: _id, relationTo, ...f }) => ({
1209
- ...f,
1210
- type: TYPE_MAP[f.type] ?? f.type,
1211
- // normalizer reads `collection`, block-builder stores `relationTo`
1212
- ...relationTo ? { collection: relationTo } : {}
1213
- }));
1214
- return {
1215
- blockSlug: normalizeSlug(block.slug),
1216
- name: block.labels?.singular ?? block.slug,
1217
- schema: { fields },
1218
- changelog: "Created via block builder"
1219
- };
1220
- }
1221
-
1222
- // src/block-builder/lib/codegen.ts
1223
- function indent(n) {
1224
- return " ".repeat(n);
1225
- }
1226
- function escStr(s) {
1227
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1228
- }
1229
- function fieldToCode(field, depth = 1) {
1230
- const pad = indent(depth);
1231
- const innerPad = indent(depth + 1);
1232
- const lines = [];
1233
- lines.push(`${pad}name: '${escStr(field.name)}'`);
1234
- lines.push(`${pad}type: '${field.type}'`);
1235
- if (field.label) lines.push(`${pad}label: '${escStr(field.label)}'`);
1236
- if (field.required) lines.push(`${pad}required: true`);
1237
- if (field.unique) lines.push(`${pad}unique: true`);
1238
- if (field.localized) lines.push(`${pad}localized: true`);
1239
- if (field.defaultValue !== void 0) {
1240
- const val = typeof field.defaultValue === "string" ? `'${escStr(String(field.defaultValue))}'` : field.defaultValue;
1241
- lines.push(`${pad}defaultValue: ${val}`);
1242
- }
1243
- if (field.type === "richText") {
1244
- lines.push(`${pad}editor: lexicalEditor({})`);
1245
- }
1246
- if (field.options && field.options.length > 0) {
1247
- const opts = field.options.map((o) => `{ label: '${escStr(o.label)}', value: '${escStr(o.value)}' }`).join(`, `);
1248
- lines.push(`${pad}options: [${opts}]`);
1351
+ // src/block-builder/components/ErrorBoundary.tsx
1352
+ var ErrorBoundary = class extends Component {
1353
+ constructor() {
1354
+ super(...arguments);
1355
+ this.state = {
1356
+ hasError: false,
1357
+ error: null
1358
+ };
1359
+ /**
1360
+ * Clears persisted builder state before reloading. "Try again" alone only
1361
+ * resets the boundary, so if the cause is corrupt persisted state the next
1362
+ * render throws immediately -- this gives the user a way out of that loop.
1363
+ */
1364
+ this.handleResetState = () => {
1365
+ try {
1366
+ window.localStorage.removeItem(BUILDER_PERSIST_KEY);
1367
+ } catch (err) {
1368
+ console.error("[Block Builder] Could not clear persisted state:", err);
1369
+ }
1370
+ window.location.reload();
1371
+ };
1249
1372
  }
1250
- if (field.relationTo) {
1251
- lines.push(`${pad}relationTo: '${escStr(field.relationTo)}'`);
1373
+ static getDerivedStateFromError(error) {
1374
+ return { hasError: true, error };
1252
1375
  }
1253
- if (field.hasMany !== void 0) {
1254
- lines.push(`${pad}hasMany: ${field.hasMany}`);
1376
+ componentDidCatch(error, errorInfo) {
1377
+ console.error("[Block Builder] Uncaught error:", error, errorInfo);
1255
1378
  }
1256
- if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
1257
- if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
1258
- if (field.fields && field.fields.length > 0) {
1259
- const nested = field.fields.map((f) => `${innerPad}{
1260
- ${fieldToCode(f, depth + 2)}
1261
- ${innerPad}}`).join(",\n");
1262
- lines.push(`${pad}fields: [
1263
- ${nested}
1264
- ${innerPad}]`);
1265
- }
1266
- const adminParts = [];
1267
- if (field.admin?.description)
1268
- adminParts.push(`description: '${escStr(field.admin.description)}'`);
1269
- if (field.admin?.placeholder)
1270
- adminParts.push(`placeholder: '${escStr(field.admin.placeholder)}'`);
1271
- if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
1272
- if (field.admin?.hidden) adminParts.push(`hidden: true`);
1273
- if (adminParts.length > 0) {
1274
- lines.push(`${pad}admin: { ${adminParts.join(", ")} }`);
1379
+ render() {
1380
+ if (this.state.hasError) {
1381
+ return /* @__PURE__ */ React5.createElement("div", { className: "bb-error-boundary", role: "alert" }, /* @__PURE__ */ React5.createElement("h2", { className: "bb-error-boundary__title" }, "Something went wrong in the Block Builder."), /* @__PURE__ */ React5.createElement("details", { className: "bb-error-boundary__details" }, /* @__PURE__ */ React5.createElement("summary", null, "Error details"), /* @__PURE__ */ React5.createElement("pre", { className: "bb-error-boundary__trace" }, this.state.error?.toString())), /* @__PURE__ */ React5.createElement("div", { className: "bb-error-boundary__actions" }, /* @__PURE__ */ React5.createElement(
1382
+ "button",
1383
+ {
1384
+ type: "button",
1385
+ onClick: () => this.setState({ hasError: false, error: null }),
1386
+ className: "bb-error-boundary__btn bb-error-boundary__btn--primary"
1387
+ },
1388
+ "Try again"
1389
+ ), /* @__PURE__ */ React5.createElement(
1390
+ "button",
1391
+ {
1392
+ type: "button",
1393
+ onClick: this.handleResetState,
1394
+ className: "bb-error-boundary__btn"
1395
+ },
1396
+ "Reset builder state"
1397
+ )), /* @__PURE__ */ React5.createElement("p", { className: "bb-error-boundary__hint" }, "\u201CReset builder state\u201D discards unsaved local edits and reloads. Published versions are stored in the database and are not affected."));
1398
+ }
1399
+ return this.props.children;
1275
1400
  }
1276
- return lines.join(",\n");
1277
- }
1278
- function generateBlockCode(block) {
1279
- const hasRichText = containsRichText(block.fields);
1280
- const imports = [`import type { Block } from 'payload'`];
1281
- if (hasRichText) {
1282
- imports.push(`import { lexicalEditor } from '@payloadcms/richtext-lexical'`);
1401
+ };
1402
+
1403
+ // src/components/SchemaBuilderField/index.tsx
1404
+ function parseSchema(raw) {
1405
+ try {
1406
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
1407
+ if (!parsed) return [];
1408
+ if (Array.isArray(parsed)) return parsed;
1409
+ if (parsed?.fields && Array.isArray(parsed.fields)) return parsed.fields;
1410
+ } catch {
1283
1411
  }
1284
- const fieldsCode = block.fields.map((f) => ` {
1285
- ${fieldToCode(f, 2)}
1286
- }`).join(",\n");
1287
- const labelsCode = block.labels ? `
1288
- labels: {
1289
- singular: '${escStr(block.labels.singular ?? block.slug)}',
1290
- plural: '${escStr(block.labels.plural ?? block.slug + "s")}',
1291
- },` : "";
1292
- const interfaceLine = block.interfaceName ? `
1293
- interfaceName: '${escStr(block.interfaceName)}',` : "";
1294
- const exportName = block.interfaceName ?? toCamelCase(block.slug);
1295
- return [
1296
- imports.join("\n"),
1297
- "",
1298
- `export const ${exportName}: Block = {`,
1299
- ` slug: '${escStr(block.slug)}',${interfaceLine}${labelsCode}`,
1300
- ` fields: [`,
1301
- fieldsCode,
1302
- ` ],`,
1303
- `}`,
1304
- ""
1305
- ].join("\n");
1306
- }
1307
- function containsRichText(fields) {
1308
- return fields.some(
1309
- (f) => f.type === "richText" || (f.fields ? containsRichText(f.fields) : false)
1310
- );
1311
- }
1312
- function toCamelCase(slug) {
1313
- return slug.split(/[-_]/).map(
1314
- (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
1315
- ).join("");
1316
- }
1317
- function generateBlockOutput(block) {
1318
- return {
1319
- filename: `${block.slug}.ts`,
1320
- code: generateBlockCode(block),
1321
- language: "typescript"
1322
- };
1412
+ return [];
1323
1413
  }
1324
- function generateAllBlocks(blocks) {
1325
- return blocks.map(generateBlockOutput);
1414
+ function SchemaBuilderField({ path, readOnly }) {
1415
+ const { value, setValue } = useField3({ path });
1416
+ const setValueRef = useRef3(setValue);
1417
+ useEffect3(() => {
1418
+ setValueRef.current = setValue;
1419
+ });
1420
+ const [fields, setFields] = useState3(() => parseSchema(value));
1421
+ const hasExistingValue = value !== void 0 && value !== null;
1422
+ const [hydrated, setHydrated] = useState3(!hasExistingValue);
1423
+ useEffect3(() => {
1424
+ if (hydrated) return;
1425
+ if (value !== void 0 && value !== null) {
1426
+ setFields(parseSchema(value));
1427
+ setHydrated(true);
1428
+ }
1429
+ }, [value, hydrated]);
1430
+ useEffect3(() => {
1431
+ if (!hydrated) return;
1432
+ setValueRef.current({ fields });
1433
+ }, [fields, hydrated]);
1434
+ const addField = useCallback3(() => {
1435
+ setFields((prev) => [
1436
+ ...prev,
1437
+ { name: "", type: "text", label: "", required: false }
1438
+ ]);
1439
+ }, []);
1440
+ const updateField = useCallback3((index, updated) => {
1441
+ setFields((prev) => prev.map((f, i) => i === index ? updated : f));
1442
+ }, []);
1443
+ const removeField = useCallback3((index) => {
1444
+ setFields((prev) => prev.filter((_, i) => i !== index));
1445
+ }, []);
1446
+ const moveField = useCallback3((index, dir) => {
1447
+ setFields((prev) => {
1448
+ const next = [...prev];
1449
+ const target = index + dir;
1450
+ if (target < 0 || target >= next.length) return prev;
1451
+ [next[index], next[target]] = [next[target], next[index]];
1452
+ return next;
1453
+ });
1454
+ }, []);
1455
+ return /* @__PURE__ */ React6.createElement(ErrorBoundary, null, /* @__PURE__ */ React6.createElement("div", null, /* @__PURE__ */ React6.createElement("div", { className: "sbf-section-heading" }, /* @__PURE__ */ React6.createElement("span", { className: "sbf-section-label" }, "Fields", fields.length > 0 && /* @__PURE__ */ React6.createElement(
1456
+ "span",
1457
+ {
1458
+ style: {
1459
+ marginLeft: 8,
1460
+ fontSize: 11,
1461
+ color: "var(--theme-elevation-400)",
1462
+ fontWeight: 400
1463
+ }
1464
+ },
1465
+ "(",
1466
+ fields.length,
1467
+ ")"
1468
+ ))), fields.length === 0 ? /* @__PURE__ */ React6.createElement("div", { className: "sbf-empty" }, "No fields defined. Click \u201CAdd Field\u201D below to add the first field to this block schema.") : /* @__PURE__ */ React6.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ React6.createElement(
1469
+ FieldRow,
1470
+ {
1471
+ key: i,
1472
+ index: i,
1473
+ total: fields.length,
1474
+ field,
1475
+ onChange: (updated) => updateField(i, updated),
1476
+ onRemove: () => removeField(i),
1477
+ onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
1478
+ onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
1479
+ readOnly
1480
+ }
1481
+ ))), !readOnly && /* @__PURE__ */ React6.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)", marginBottom: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ React6.createElement("button", { type: "button", className: "sbf-add-btn", onClick: addField }, /* @__PURE__ */ React6.createElement("span", { className: "sbf-add-btn__icon" }, "+"), "Add Field"))));
1326
1482
  }
1327
- function generateIndexFile(blocks) {
1328
- const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
1329
- const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
1330
- const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
1331
- const code = [
1332
- imports,
1333
- "",
1334
- `export const blocks = [`,
1335
- exportList,
1336
- `] as const`,
1337
- ""
1338
- ].join("\n");
1339
- return { filename: "index.ts", code, language: "typescript" };
1483
+
1484
+ // src/components/EditInBuilderButton/index.tsx
1485
+ import React7 from "react";
1486
+ import { useDocumentInfo } from "@payloadcms/ui";
1487
+ import { useField as useField4 } from "@payloadcms/ui";
1488
+ function EditInBuilderButton() {
1489
+ const { id } = useDocumentInfo();
1490
+ const { value: slug } = useField4({ path: "slug" });
1491
+ if (!id || !slug) return null;
1492
+ return /* @__PURE__ */ React7.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ React7.createElement(
1493
+ "a",
1494
+ {
1495
+ href: `/block-builder?load=${slug}`,
1496
+ target: "_blank",
1497
+ rel: "noopener noreferrer",
1498
+ style: {
1499
+ display: "inline-flex",
1500
+ alignItems: "center",
1501
+ gap: "6px",
1502
+ padding: "8px 16px",
1503
+ borderRadius: "6px",
1504
+ border: "1px solid #16a34a",
1505
+ color: "#16a34a",
1506
+ textDecoration: "none",
1507
+ fontSize: "13px",
1508
+ fontWeight: 500,
1509
+ background: "transparent",
1510
+ cursor: "pointer"
1511
+ }
1512
+ },
1513
+ "Edit in Block Builder"
1514
+ ));
1340
1515
  }
1341
1516
 
1517
+ // src/block-builder/components/canvas/BuilderShell.tsx
1518
+ import React18, { useCallback as useCallback5, useEffect as useEffect6, useState as useState9 } from "react";
1519
+
1342
1520
  // src/block-builder/components/canvas/TopBar.tsx
1343
- function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish }) {
1521
+ import React8, { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
1522
+ import { Blocks, ChevronDown, X } from "lucide-react";
1523
+ function ensureFieldIds(fields) {
1524
+ if (!Array.isArray(fields)) return [];
1525
+ return fields.map((field) => {
1526
+ const f = { ...field };
1527
+ if (!f.id) f.id = uuidv4();
1528
+ if (Array.isArray(f.fields)) f.fields = ensureFieldIds(f.fields);
1529
+ if (Array.isArray(f.tabs)) {
1530
+ f.tabs = f.tabs.map((tab) => ({ ...tab, fields: ensureFieldIds(tab.fields) }));
1531
+ }
1532
+ return f;
1533
+ });
1534
+ }
1535
+ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish, notification, onSetNotification, previewOpen, onTogglePreview }) {
1344
1536
  const blocks = useBuilderStore((s) => s.blocks);
1345
1537
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1346
1538
  const activeBlock = blocks.find((b) => b.id === activeBlockId);
@@ -1348,21 +1540,23 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1348
1540
  const isDirty = useBuilderStore((s) => s.isDirty);
1349
1541
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1350
1542
  const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
1351
- const [notification, setNotification] = useState4(null);
1543
+ const loadBlock = useBuilderStore((s) => s.loadBlock);
1544
+ const setNotification = onSetNotification;
1352
1545
  const [versionDropdownOpen, setVersionDropdownOpen] = useState4(false);
1353
1546
  const [blockPickerOpen, setBlockPickerOpen] = useState4(false);
1354
- const dropdownRef = useRef3(null);
1355
- const blockPickerRef = useRef3(null);
1547
+ const dropdownRef = useRef4(null);
1548
+ const blockPickerRef = useRef4(null);
1549
+ const fileInputRef = useRef4(null);
1356
1550
  const selectedVersion = versions.find((v) => v.id === selectedVersionId);
1357
1551
  const currentVersion = versions.find((v) => v.isCurrent);
1358
1552
  const activeBlockDef = blockDefs.find((b) => b.slug === activeSlug);
1359
- useEffect3(() => {
1553
+ useEffect4(() => {
1360
1554
  if (notification?.status === "success") {
1361
1555
  const t = setTimeout(() => setNotification(null), 3e3);
1362
1556
  return () => clearTimeout(t);
1363
1557
  }
1364
1558
  }, [notification]);
1365
- useEffect3(() => {
1559
+ useEffect4(() => {
1366
1560
  function handleClick(e) {
1367
1561
  if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
1368
1562
  setVersionDropdownOpen(false);
@@ -1378,10 +1572,17 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1378
1572
  if (!activeBlock || isReadOnly) return false;
1379
1573
  setNotification({ status: "publishing" });
1380
1574
  try {
1381
- const req = mapToSaveRequest(activeBlock);
1575
+ const normalizeSlug = (s) => s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1576
+ const publishedSlug = normalizeSlug(activeBlock.slug);
1577
+ const req = {
1578
+ blockSlug: publishedSlug,
1579
+ name: activeBlock.labels?.singular ?? activeBlock.slug,
1580
+ schema: { fields: activeBlock.fields },
1581
+ changelog: "Created via block builder"
1582
+ };
1382
1583
  const res = await fetch("/api/blocks/save", {
1383
1584
  method: "POST",
1384
- headers: { "Content-Type": "application/json" },
1585
+ headers: { "Content-Type": "application/json", "X-Block-Builder": "1" },
1385
1586
  body: JSON.stringify(req)
1386
1587
  });
1387
1588
  const json = await res.json();
@@ -1392,7 +1593,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1392
1593
  status: "success",
1393
1594
  msg: `v${json.versionNumber ?? "?"} published successfully!`
1394
1595
  });
1395
- onAfterPublish();
1596
+ onAfterPublish(publishedSlug);
1396
1597
  return true;
1397
1598
  } else {
1398
1599
  setNotification({
@@ -1411,35 +1612,54 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1411
1612
  return false;
1412
1613
  }
1413
1614
  }
1414
- function handleExport() {
1415
- if (blocks.length === 0) return;
1416
- const outputs = [...generateAllBlocks(blocks), generateIndexFile(blocks)];
1417
- for (const out of outputs) {
1418
- const blob = new Blob([out.code], { type: "text/plain" });
1419
- const url = URL.createObjectURL(blob);
1420
- const a = document.createElement("a");
1421
- a.href = url;
1422
- a.download = out.filename;
1423
- document.body.appendChild(a);
1424
- a.click();
1425
- document.body.removeChild(a);
1426
- URL.revokeObjectURL(url);
1427
- }
1615
+ function handleExportJson() {
1616
+ if (!activeBlock) return;
1617
+ const blob = new Blob([JSON.stringify(activeBlock, null, 2)], { type: "application/json" });
1618
+ const url = URL.createObjectURL(blob);
1619
+ const a = document.createElement("a");
1620
+ a.href = url;
1621
+ a.download = `${activeBlock.slug}-schema.json`;
1622
+ document.body.appendChild(a);
1623
+ a.click();
1624
+ document.body.removeChild(a);
1625
+ URL.revokeObjectURL(url);
1626
+ }
1627
+ function handleImportJson(e) {
1628
+ const file = e.target.files?.[0];
1629
+ if (!file) return;
1630
+ const reader = new FileReader();
1631
+ reader.onload = (event) => {
1632
+ try {
1633
+ const json = JSON.parse(event.target?.result);
1634
+ if (json && json.slug && Array.isArray(json.fields)) {
1635
+ if (!json.id) json.id = uuidv4();
1636
+ json.fields = ensureFieldIds(json.fields);
1637
+ loadBlock(json);
1638
+ setNotification({ status: "success", msg: "Block imported successfully!" });
1639
+ } else {
1640
+ setNotification({ status: "error", title: "Invalid JSON", errors: ["The file does not contain a valid block schema."] });
1641
+ }
1642
+ } catch (err) {
1643
+ setNotification({ status: "error", title: "Parse Error", errors: ["Could not parse JSON file."] });
1644
+ }
1645
+ };
1646
+ reader.readAsText(file);
1647
+ if (fileInputRef.current) fileInputRef.current.value = "";
1428
1648
  }
1429
1649
  function formatDate(iso) {
1430
1650
  return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
1431
1651
  }
1432
- return /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("div", { className: "bb-topbar" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-topbar__brand" }, /* @__PURE__ */ React7.createElement("span", { className: "bb-topbar__title" }, "Block Builder"), isDirty && !isReadOnly && /* @__PURE__ */ React7.createElement("span", { className: "bb-topbar__dirty" }, "* unsaved")), /* @__PURE__ */ React7.createElement("div", { className: "bb-topbar__selectors" }, blockDefs.length > 0 && /* @__PURE__ */ React7.createElement("div", { className: "bb-block-picker", ref: blockPickerRef }, /* @__PURE__ */ React7.createElement(
1652
+ return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement("div", { className: "bb-topbar" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-topbar__brand" }, /* @__PURE__ */ React8.createElement("span", { className: "bb-topbar__title" }, "Block Builder"), isDirty && !isReadOnly && /* @__PURE__ */ React8.createElement("span", { className: "bb-topbar__dirty" }, "* unsaved")), /* @__PURE__ */ React8.createElement("div", { className: "bb-topbar__selectors" }, blockDefs.length > 0 && /* @__PURE__ */ React8.createElement("div", { className: "bb-block-picker", ref: blockPickerRef }, /* @__PURE__ */ React8.createElement(
1433
1653
  "button",
1434
1654
  {
1435
1655
  type: "button",
1436
1656
  className: "bb-block-picker__trigger",
1437
1657
  onClick: () => setBlockPickerOpen((o) => !o)
1438
1658
  },
1439
- /* @__PURE__ */ React7.createElement(Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
1440
- /* @__PURE__ */ React7.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
1441
- /* @__PURE__ */ React7.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1442
- ), blockPickerOpen && /* @__PURE__ */ React7.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ React7.createElement(
1659
+ /* @__PURE__ */ React8.createElement(Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
1660
+ /* @__PURE__ */ React8.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
1661
+ /* @__PURE__ */ React8.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1662
+ ), blockPickerOpen && /* @__PURE__ */ React8.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ React8.createElement(
1443
1663
  "button",
1444
1664
  {
1445
1665
  key: b.id,
@@ -1450,20 +1670,20 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1450
1670
  onBlockSelect(b.slug);
1451
1671
  }
1452
1672
  },
1453
- /* @__PURE__ */ React7.createElement("span", { className: "bb-block-picker__item-name" }, b.name),
1454
- /* @__PURE__ */ React7.createElement("span", { className: "bb-version-dropdown__meta" }, b.slug)
1455
- )))), versions.length > 0 && /* @__PURE__ */ React7.createElement("div", { className: "bb-version-selector", ref: dropdownRef }, /* @__PURE__ */ React7.createElement(
1673
+ /* @__PURE__ */ React8.createElement("span", { className: "bb-block-picker__item-name" }, b.name),
1674
+ /* @__PURE__ */ React8.createElement("span", { className: "bb-version-dropdown__meta" }, b.slug)
1675
+ )))), versions.length > 0 && /* @__PURE__ */ React8.createElement("div", { className: "bb-version-selector", ref: dropdownRef }, /* @__PURE__ */ React8.createElement(
1456
1676
  "button",
1457
1677
  {
1458
1678
  type: "button",
1459
1679
  className: `bb-version-selector__trigger${isReadOnly ? " bb-version-selector__trigger--readonly" : ""}`,
1460
1680
  onClick: () => setVersionDropdownOpen((o) => !o)
1461
1681
  },
1462
- /* @__PURE__ */ React7.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1463
- /* @__PURE__ */ React7.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
1464
- selectedVersion?.isCurrent && /* @__PURE__ */ React7.createElement("span", { className: "bb-version-selector__badge" }, "current"),
1465
- /* @__PURE__ */ React7.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1466
- ), versionDropdownOpen && /* @__PURE__ */ React7.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ React7.createElement(
1682
+ /* @__PURE__ */ React8.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1683
+ /* @__PURE__ */ React8.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
1684
+ selectedVersion?.isCurrent && /* @__PURE__ */ React8.createElement("span", { className: "bb-version-selector__badge" }, "current"),
1685
+ /* @__PURE__ */ React8.createElement(ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1686
+ ), versionDropdownOpen && /* @__PURE__ */ React8.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ React8.createElement(
1467
1687
  "button",
1468
1688
  {
1469
1689
  key: v.id,
@@ -1474,20 +1694,45 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1474
1694
  onVersionSelect(v.id);
1475
1695
  }
1476
1696
  },
1477
- /* @__PURE__ */ React7.createElement("span", { className: `bb-version-selector__dot${v.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1478
- /* @__PURE__ */ React7.createElement("span", { className: "bb-version-dropdown__label" }, v.label, v.isCurrent && /* @__PURE__ */ React7.createElement("span", { className: "bb-version-selector__badge" }, "current")),
1479
- /* @__PURE__ */ React7.createElement("span", { className: "bb-version-dropdown__meta" }, v.changelog ? `${v.changelog.slice(0, 32)}${v.changelog.length > 32 ? "..." : ""}` : formatDate(v.createdAt))
1480
- ))))), /* @__PURE__ */ React7.createElement("div", { className: "bb-topbar__actions" }, /* @__PURE__ */ React7.createElement(
1697
+ /* @__PURE__ */ React8.createElement("span", { className: `bb-version-selector__dot${v.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1698
+ /* @__PURE__ */ React8.createElement("span", { className: "bb-version-dropdown__label" }, v.label, v.isCurrent && /* @__PURE__ */ React8.createElement("span", { className: "bb-version-selector__badge" }, "current")),
1699
+ /* @__PURE__ */ React8.createElement("span", { className: "bb-version-dropdown__meta" }, v.changelog ? `${v.changelog.slice(0, 32)}${v.changelog.length > 32 ? "..." : ""}` : formatDate(v.createdAt))
1700
+ ))))), /* @__PURE__ */ React8.createElement("div", { className: "bb-topbar__actions" }, /* @__PURE__ */ React8.createElement(
1701
+ "input",
1702
+ {
1703
+ type: "file",
1704
+ accept: ".json",
1705
+ ref: fileInputRef,
1706
+ style: { display: "none" },
1707
+ onChange: handleImportJson
1708
+ }
1709
+ ), /* @__PURE__ */ React8.createElement(
1710
+ "button",
1711
+ {
1712
+ type: "button",
1713
+ onClick: () => fileInputRef.current?.click(),
1714
+ className: "bb-btn bb-btn--secondary"
1715
+ },
1716
+ "Import JSON"
1717
+ ), /* @__PURE__ */ React8.createElement(
1481
1718
  "button",
1482
1719
  {
1483
1720
  type: "button",
1484
- onClick: handleExport,
1485
- disabled: blocks.length === 0,
1486
- className: "bb-btn bb-btn--secondary",
1487
- style: { display: "none" }
1721
+ onClick: handleExportJson,
1722
+ disabled: !activeBlock,
1723
+ className: "bb-btn bb-btn--secondary"
1488
1724
  },
1489
- "Export .ts"
1490
- ), isReadOnly ? /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement(
1725
+ "Export JSON"
1726
+ ), /* @__PURE__ */ React8.createElement("div", { style: { width: 1, height: 24, background: "var(--bb-border)", margin: "0 4px" } }), /* @__PURE__ */ React8.createElement(
1727
+ "button",
1728
+ {
1729
+ type: "button",
1730
+ onClick: onTogglePreview,
1731
+ disabled: !activeBlock,
1732
+ className: `bb-btn ${previewOpen ? "bb-btn--primary" : "bb-btn--secondary"}`
1733
+ },
1734
+ previewOpen ? "Close Preview" : "Live Preview"
1735
+ ), isReadOnly ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
1491
1736
  "button",
1492
1737
  {
1493
1738
  type: "button",
@@ -1498,7 +1743,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1498
1743
  disabled: !currentVersion
1499
1744
  },
1500
1745
  "Back to current"
1501
- ), /* @__PURE__ */ React7.createElement(
1746
+ ), /* @__PURE__ */ React8.createElement(
1502
1747
  "button",
1503
1748
  {
1504
1749
  type: "button",
@@ -1510,7 +1755,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1510
1755
  className: "bb-btn bb-btn--warning"
1511
1756
  },
1512
1757
  notification?.status === "publishing" ? "Restoring..." : "Restore as new version"
1513
- )) : /* @__PURE__ */ React7.createElement(
1758
+ )) : /* @__PURE__ */ React8.createElement(
1514
1759
  "button",
1515
1760
  {
1516
1761
  type: "button",
@@ -1519,37 +1764,55 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1519
1764
  className: "bb-btn bb-btn--primary"
1520
1765
  },
1521
1766
  notification?.status === "publishing" ? "Publishing..." : "Publish to Payload"
1522
- ))), notification?.status === "publishing" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ React7.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ React7.createElement(X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ React7.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React7.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ React7.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ React7.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ React7.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ React7.createElement("li", { key: i }, e)))), /* @__PURE__ */ React7.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ React7.createElement(X, { size: 12, strokeWidth: 2 })))));
1767
+ ))), notification?.status === "publishing" && /* @__PURE__ */ React8.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React8.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ React8.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ React8.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React8.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React8.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ React8.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ React8.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ React8.createElement(X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ React8.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ React8.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ React8.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ React8.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ React8.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ React8.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ React8.createElement("li", { key: i }, e)))), /* @__PURE__ */ React8.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ React8.createElement(X, { size: 12, strokeWidth: 2 })))));
1523
1768
  }
1524
1769
 
1525
1770
  // src/block-builder/components/canvas/BlockList.tsx
1526
- import React8 from "react";
1771
+ import React9 from "react";
1527
1772
  import { Copy, Trash2, Plus } from "lucide-react";
1528
- function BlockList() {
1773
+ function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
1529
1774
  const blocks = useBuilderStore((s) => s.blocks);
1530
1775
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1531
1776
  const addBlock = useBuilderStore((s) => s.addBlock);
1532
1777
  const removeBlock = useBuilderStore((s) => s.removeBlock);
1533
1778
  const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
1534
1779
  const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
1535
- return /* @__PURE__ */ React8.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ React8.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ React8.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ React8.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ React8.createElement(Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ React8.createElement("div", { className: "bb-sidebar__body" }, blocks.length === 0 && /* @__PURE__ */ React8.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ React8.createElement("br", null), "Click + to create one."), blocks.map((block) => {
1780
+ const useApiNav = blockDefs.length > 0;
1781
+ const loadedBlock = useApiNav ? blocks.find((b) => b.slug === activeSlug) : null;
1782
+ const localOnlyBlocks = blocks.filter(
1783
+ (b) => !blockDefs.some((d) => d.slug === b.slug)
1784
+ );
1785
+ return /* @__PURE__ */ React9.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ React9.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ React9.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ React9.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ React9.createElement(Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ React9.createElement("div", { className: "bb-sidebar__body" }, useApiNav && blockDefs.map((def) => {
1786
+ const isActive = def.slug === activeSlug;
1787
+ const fieldCount = isActive && loadedBlock ? loadedBlock.fields.length : null;
1788
+ return /* @__PURE__ */ React9.createElement(
1789
+ "div",
1790
+ {
1791
+ key: def.id,
1792
+ onClick: () => onBlockSelect?.(def.slug),
1793
+ className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1794
+ },
1795
+ /* @__PURE__ */ React9.createElement("div", { className: "bb-block-item__slug" }, def.slug),
1796
+ /* @__PURE__ */ React9.createElement("div", { className: "bb-block-item__meta" }, fieldCount !== null ? `${fieldCount} field${fieldCount !== 1 ? "s" : ""}` : def.name)
1797
+ );
1798
+ }), localOnlyBlocks.map((block) => {
1536
1799
  const isActive = block.id === activeBlockId;
1537
- return /* @__PURE__ */ React8.createElement(
1800
+ return /* @__PURE__ */ React9.createElement(
1538
1801
  "div",
1539
1802
  {
1540
1803
  key: block.id,
1541
1804
  onClick: () => setActiveBlock(block.id),
1542
1805
  className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1543
1806
  },
1544
- /* @__PURE__ */ React8.createElement("div", { className: "bb-block-item__slug" }, block.slug),
1545
- /* @__PURE__ */ React8.createElement("div", { className: "bb-block-item__meta" }, block.fields.length, " field", block.fields.length !== 1 ? "s" : ""),
1546
- /* @__PURE__ */ React8.createElement(
1807
+ /* @__PURE__ */ React9.createElement("div", { className: "bb-block-item__slug" }, block.slug),
1808
+ /* @__PURE__ */ React9.createElement("div", { className: "bb-block-item__meta" }, block.fields.length, " field", block.fields.length !== 1 ? "s" : ""),
1809
+ /* @__PURE__ */ React9.createElement(
1547
1810
  "div",
1548
1811
  {
1549
1812
  className: "bb-block-item__actions",
1550
1813
  onClick: (e) => e.stopPropagation()
1551
1814
  },
1552
- /* @__PURE__ */ React8.createElement(
1815
+ /* @__PURE__ */ React9.createElement(
1553
1816
  "button",
1554
1817
  {
1555
1818
  type: "button",
@@ -1557,9 +1820,9 @@ function BlockList() {
1557
1820
  className: "bb-block-action",
1558
1821
  title: "Duplicate"
1559
1822
  },
1560
- /* @__PURE__ */ React8.createElement(Copy, { size: 12, strokeWidth: 1.75 })
1823
+ /* @__PURE__ */ React9.createElement(Copy, { size: 12, strokeWidth: 1.75 })
1561
1824
  ),
1562
- /* @__PURE__ */ React8.createElement(
1825
+ /* @__PURE__ */ React9.createElement(
1563
1826
  "button",
1564
1827
  {
1565
1828
  type: "button",
@@ -1567,15 +1830,15 @@ function BlockList() {
1567
1830
  className: "bb-block-action bb-block-action--danger",
1568
1831
  title: "Delete"
1569
1832
  },
1570
- /* @__PURE__ */ React8.createElement(Trash2, { size: 12, strokeWidth: 1.75 })
1833
+ /* @__PURE__ */ React9.createElement(Trash2, { size: 12, strokeWidth: 1.75 })
1571
1834
  )
1572
1835
  )
1573
1836
  );
1574
- })));
1837
+ }), !useApiNav && blocks.length === 0 && /* @__PURE__ */ React9.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ React9.createElement("br", null), "Click + to create one.")));
1575
1838
  }
1576
1839
 
1577
1840
  // src/block-builder/components/canvas/BuilderCanvas.tsx
1578
- import React10 from "react";
1841
+ import React11 from "react";
1579
1842
  import {
1580
1843
  DndContext,
1581
1844
  closestCenter,
@@ -1592,68 +1855,107 @@ import {
1592
1855
  import { restrictToVerticalAxis, restrictToParentElement } from "@dnd-kit/modifiers";
1593
1856
 
1594
1857
  // src/block-builder/components/canvas/SortableFieldCard.tsx
1595
- import React9 from "react";
1858
+ import React10 from "react";
1596
1859
  import { useSortable } from "@dnd-kit/sortable";
1597
1860
  import { CSS } from "@dnd-kit/utilities";
1598
1861
  import {
1599
1862
  Type,
1600
1863
  AlignLeft,
1864
+ AlignJustify,
1601
1865
  Hash,
1602
1866
  Mail,
1603
1867
  Calendar,
1604
1868
  CheckSquare,
1605
1869
  ChevronDown as ChevronDown2,
1606
- Circle,
1607
1870
  Upload,
1871
+ Image,
1608
1872
  Link,
1609
1873
  Braces,
1874
+ List,
1875
+ Box,
1876
+ Columns,
1877
+ Folder,
1610
1878
  X as X2
1611
1879
  } from "lucide-react";
1612
1880
  var ICON_MAP = {
1613
1881
  text: Type,
1614
1882
  textarea: AlignLeft,
1883
+ richtext: AlignJustify,
1615
1884
  number: Hash,
1616
1885
  email: Mail,
1617
1886
  date: Calendar,
1618
1887
  checkbox: CheckSquare,
1619
1888
  select: ChevronDown2,
1620
- radio: Circle,
1621
- upload: Upload,
1889
+ image: Image,
1890
+ file: Upload,
1622
1891
  relationship: Link,
1623
- json: Braces
1892
+ json: Braces,
1893
+ array: List,
1894
+ group: Box,
1895
+ row: Columns,
1896
+ tabs: Folder,
1897
+ collapsible: ChevronDown2
1624
1898
  };
1625
- function SortableFieldCard({ field, blockId, index }) {
1899
+ var SortableFieldCard = React10.memo(function SortableFieldCard2({
1900
+ field,
1901
+ blockId,
1902
+ index,
1903
+ isActive,
1904
+ isReadOnly,
1905
+ onDrillDown
1906
+ }) {
1626
1907
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
1627
1908
  id: field.id
1628
1909
  });
1629
- const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1630
1910
  const setActiveField = useBuilderStore((s) => s.setActiveField);
1631
1911
  const removeField = useBuilderStore((s) => s.removeField);
1632
- const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1633
- const isActive = activeFieldId === field.id;
1634
1912
  const wrapStyle = {
1635
1913
  transform: CSS.Transform.toString(transform),
1636
1914
  transition
1637
1915
  };
1638
- return /* @__PURE__ */ React9.createElement(
1916
+ return /* @__PURE__ */ React10.createElement(
1639
1917
  "div",
1640
1918
  {
1641
1919
  ref: setNodeRef,
1642
1920
  style: wrapStyle,
1643
1921
  className: `bb-field-card-wrap${isDragging ? " bb-field-card-wrap--dragging" : ""}`,
1644
1922
  ...attributes,
1645
- ...listeners
1923
+ ...listeners,
1924
+ "aria-label": `Draggable field card for ${field.name || "unnamed"}`
1646
1925
  },
1647
- /* @__PURE__ */ React9.createElement(
1926
+ /* @__PURE__ */ React10.createElement(
1648
1927
  "div",
1649
1928
  {
1650
1929
  className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
1651
1930
  onClick: () => setActiveField(isActive ? null : field.id)
1652
1931
  },
1653
- /* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ React9.createElement(FieldIcon, { type: field.type })),
1654
- /* @__PURE__ */ React9.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ React9.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ React9.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__required" }, "*"))),
1655
- /* @__PURE__ */ React9.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
1656
- !isReadOnly && /* @__PURE__ */ React9.createElement(
1932
+ /* @__PURE__ */ React10.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ React10.createElement(FieldIcon, { type: field.type })),
1933
+ /* @__PURE__ */ React10.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ React10.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ React10.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ React10.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ React10.createElement("span", { className: "bb-field-card__required" }, "*"))),
1934
+ /* @__PURE__ */ React10.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
1935
+ onDrillDown && /* @__PURE__ */ React10.createElement(
1936
+ "button",
1937
+ {
1938
+ type: "button",
1939
+ onPointerDown: (e) => e.stopPropagation(),
1940
+ onClick: (e) => {
1941
+ e.stopPropagation();
1942
+ onDrillDown();
1943
+ },
1944
+ className: "bb-field-card__drill",
1945
+ title: "Edit Inner Fields",
1946
+ style: {
1947
+ marginLeft: "auto",
1948
+ fontSize: 12,
1949
+ padding: "2px 8px",
1950
+ borderRadius: 4,
1951
+ border: "1px solid var(--theme-border)",
1952
+ background: "var(--theme-elevation-100)",
1953
+ cursor: "pointer"
1954
+ }
1955
+ },
1956
+ "Edit Fields"
1957
+ ),
1958
+ !isReadOnly && /* @__PURE__ */ React10.createElement(
1657
1959
  "button",
1658
1960
  {
1659
1961
  type: "button",
@@ -1665,21 +1967,46 @@ function SortableFieldCard({ field, blockId, index }) {
1665
1967
  className: "bb-field-card__delete",
1666
1968
  title: "Remove field"
1667
1969
  },
1668
- /* @__PURE__ */ React9.createElement(X2, { size: 12, strokeWidth: 2 })
1970
+ /* @__PURE__ */ React10.createElement(X2, { size: 12, strokeWidth: 2 })
1669
1971
  )
1670
1972
  )
1671
1973
  );
1672
- }
1974
+ });
1673
1975
  function FieldIcon({ type }) {
1674
1976
  const Icon = ICON_MAP[type];
1675
- return Icon ? /* @__PURE__ */ React9.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
1977
+ return Icon ? /* @__PURE__ */ React10.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
1676
1978
  }
1677
1979
 
1678
1980
  // src/block-builder/components/canvas/BuilderCanvas.tsx
1981
+ function resolvePathLabels(block, parentPath) {
1982
+ const labels = [];
1983
+ let currentFields = block.fields;
1984
+ for (const segment of parentPath) {
1985
+ const sepIndex = segment.indexOf("::");
1986
+ if (sepIndex !== -1) {
1987
+ const fieldId = segment.slice(0, sepIndex);
1988
+ const tabIndex = Number(segment.slice(sepIndex + 2));
1989
+ const tabsField = currentFields?.find((f) => f.id === fieldId);
1990
+ const tab = tabsField?.tabs?.[tabIndex];
1991
+ labels.push(tab?.label || tabsField?.name || "Tab");
1992
+ currentFields = tab?.fields;
1993
+ continue;
1994
+ }
1995
+ const field = currentFields?.find((f) => f.id === segment);
1996
+ labels.push(field?.name || field?.label || "Nested Field");
1997
+ currentFields = field?.fields;
1998
+ }
1999
+ return labels;
2000
+ }
1679
2001
  function BuilderCanvas() {
1680
2002
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1681
2003
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1682
2004
  const reorderFields = useBuilderStore((s) => s.reorderFields);
2005
+ const activeFieldId = useBuilderStore((s) => s.activeFieldId);
2006
+ const activeParentPath = useBuilderStore((s) => s.activeParentPath);
2007
+ const truncateParentPath = useBuilderStore((s) => s.truncateParentPath);
2008
+ const resetParentPath = useBuilderStore((s) => s.resetParentPath);
2009
+ const pushParentPath = useBuilderStore((s) => s.pushParentPath);
1683
2010
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1684
2011
  const sensors = useSensors(
1685
2012
  useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
@@ -1689,16 +2016,41 @@ function BuilderCanvas() {
1689
2016
  if (isReadOnly) return;
1690
2017
  const { active, over } = event;
1691
2018
  if (!over || active.id === over.id || !block) return;
1692
- const fromIndex = block.fields.findIndex((f) => f.id === active.id);
1693
- const toIndex = block.fields.findIndex((f) => f.id === over.id);
2019
+ const targetFields2 = getTargetFields(block, activeParentPath);
2020
+ if (!targetFields2) return;
2021
+ const fromIndex = targetFields2.findIndex((f) => f.id === active.id);
2022
+ const toIndex = targetFields2.findIndex((f) => f.id === over.id);
1694
2023
  if (fromIndex !== -1 && toIndex !== -1) {
1695
2024
  reorderFields(block.id, fromIndex, toIndex);
1696
2025
  }
1697
2026
  }
1698
2027
  if (!block) {
1699
- return /* @__PURE__ */ React10.createElement("div", { className: "bb-canvas", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ React10.createElement("span", { className: "bb-canvas__no-block" }, "Select or create a block from the left panel."));
2028
+ return /* @__PURE__ */ React11.createElement("div", { className: "bb-canvas", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ React11.createElement("span", { className: "bb-canvas__no-block" }, "Select or create a block from the left panel."));
1700
2029
  }
1701
- return /* @__PURE__ */ React10.createElement("div", { className: `bb-canvas${isReadOnly ? " bb-canvas--readonly" : ""}` }, /* @__PURE__ */ React10.createElement("div", { className: "bb-canvas__inner" }, /* @__PURE__ */ React10.createElement("div", { className: "bb-canvas__header" }, block.slug, " - ", block.fields.length, " field", block.fields.length !== 1 ? "s" : ""), block.fields.length === 0 ? /* @__PURE__ */ React10.createElement("div", { className: "bb-canvas__empty" }, "Add fields from the palette on the left") : /* @__PURE__ */ React10.createElement(
2030
+ const targetFields = getTargetFields(block, activeParentPath) || [];
2031
+ const pathLabels = resolvePathLabels(block, activeParentPath);
2032
+ return /* @__PURE__ */ React11.createElement("div", { className: `bb-canvas${isReadOnly ? " bb-canvas--readonly" : ""}` }, /* @__PURE__ */ React11.createElement("div", { className: "bb-canvas__inner" }, /* @__PURE__ */ React11.createElement("div", { className: "bb-canvas__header" }, activeParentPath.length === 0 ? /* @__PURE__ */ React11.createElement(React11.Fragment, null, block.slug, " - ", targetFields.length, " field", targetFields.length !== 1 ? "s" : "") : /* @__PURE__ */ React11.createElement("nav", { className: "bb-breadcrumb", "aria-label": "Field path" }, /* @__PURE__ */ React11.createElement(
2033
+ "button",
2034
+ {
2035
+ type: "button",
2036
+ onClick: () => resetParentPath(),
2037
+ className: "bb-breadcrumb__item"
2038
+ },
2039
+ block.slug
2040
+ ), /* @__PURE__ */ React11.createElement("span", { className: "bb-breadcrumb__sep" }, "/"), activeParentPath.map((segment, index) => {
2041
+ const isLast = index === activeParentPath.length - 1;
2042
+ return /* @__PURE__ */ React11.createElement(React11.Fragment, { key: segment }, /* @__PURE__ */ React11.createElement(
2043
+ "button",
2044
+ {
2045
+ type: "button",
2046
+ onClick: isLast ? void 0 : () => truncateParentPath(index + 1),
2047
+ className: "bb-breadcrumb__item",
2048
+ "aria-current": isLast ? "location" : void 0,
2049
+ "data-current": isLast ? "true" : void 0
2050
+ },
2051
+ pathLabels[index]
2052
+ ), !isLast && /* @__PURE__ */ React11.createElement("span", { className: "bb-breadcrumb__sep" }, "/"));
2053
+ }))), targetFields.length === 0 ? /* @__PURE__ */ React11.createElement("div", { className: "bb-canvas__empty" }, "Add fields from the palette on the left") : /* @__PURE__ */ React11.createElement(
1702
2054
  DndContext,
1703
2055
  {
1704
2056
  sensors,
@@ -1706,19 +2058,22 @@ function BuilderCanvas() {
1706
2058
  modifiers: [restrictToVerticalAxis, restrictToParentElement],
1707
2059
  onDragEnd: handleDragEnd
1708
2060
  },
1709
- /* @__PURE__ */ React10.createElement(
2061
+ /* @__PURE__ */ React11.createElement(
1710
2062
  SortableContext,
1711
2063
  {
1712
- items: block.fields.map((f) => f.id),
2064
+ items: targetFields.map((f) => f.id),
1713
2065
  strategy: verticalListSortingStrategy
1714
2066
  },
1715
- /* @__PURE__ */ React10.createElement("div", { className: "bb-canvas__field-list" }, block.fields.map((field, i) => /* @__PURE__ */ React10.createElement(
2067
+ /* @__PURE__ */ React11.createElement("div", { className: "bb-canvas__field-list" }, targetFields.map((field, i) => /* @__PURE__ */ React11.createElement(
1716
2068
  SortableFieldCard,
1717
2069
  {
1718
2070
  key: field.id,
1719
2071
  field,
1720
2072
  blockId: block.id,
1721
- index: i
2073
+ index: i,
2074
+ isActive: activeFieldId === field.id,
2075
+ isReadOnly,
2076
+ onDrillDown: ["group", "row", "array", "collapsible"].includes(field.type) ? () => pushParentPath(field.id) : void 0
1722
2077
  }
1723
2078
  )))
1724
2079
  )
@@ -1726,18 +2081,18 @@ function BuilderCanvas() {
1726
2081
  }
1727
2082
 
1728
2083
  // src/block-builder/components/canvas/ConfigPanel.tsx
1729
- import React13, { useState as useState5 } from "react";
2084
+ import React14, { useState as useState5 } from "react";
1730
2085
 
1731
2086
  // src/block-builder/components/config/BlockConfig.tsx
1732
- import React11 from "react";
2087
+ import React12 from "react";
1733
2088
  function BlockConfig() {
1734
2089
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1735
2090
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1736
2091
  const updateBlock = useBuilderStore((s) => s.updateBlock);
1737
2092
  if (!block) {
1738
- return /* @__PURE__ */ React11.createElement("div", { className: "bb-form__empty" }, "No block selected.");
2093
+ return /* @__PURE__ */ React12.createElement("div", { className: "bb-form__empty" }, "No block selected.");
1739
2094
  }
1740
- return /* @__PURE__ */ React11.createElement("div", { className: "bb-form" }, /* @__PURE__ */ React11.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React11.createElement("label", { className: "bb-form__label" }, "Slug *"), /* @__PURE__ */ React11.createElement(
2095
+ return /* @__PURE__ */ React12.createElement("div", { className: "bb-form" }, /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Slug *"), /* @__PURE__ */ React12.createElement(
1741
2096
  "input",
1742
2097
  {
1743
2098
  type: "text",
@@ -1746,7 +2101,7 @@ function BlockConfig() {
1746
2101
  placeholder: "myBlock",
1747
2102
  className: "bb-input"
1748
2103
  }
1749
- ), /* @__PURE__ */ React11.createElement("span", { className: "bb-form__hint" }, "Unique identifier used in code and database")), /* @__PURE__ */ React11.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React11.createElement("label", { className: "bb-form__label" }, "Interface Name"), /* @__PURE__ */ React11.createElement(
2104
+ ), /* @__PURE__ */ React12.createElement("span", { className: "bb-form__hint" }, "Unique identifier used in code and database")), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Interface Name"), /* @__PURE__ */ React12.createElement(
1750
2105
  "input",
1751
2106
  {
1752
2107
  type: "text",
@@ -1755,7 +2110,7 @@ function BlockConfig() {
1755
2110
  placeholder: "MyBlock",
1756
2111
  className: "bb-input"
1757
2112
  }
1758
- )), /* @__PURE__ */ React11.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ React11.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React11.createElement("label", { className: "bb-form__label" }, "Singular Label"), /* @__PURE__ */ React11.createElement(
2113
+ )), /* @__PURE__ */ React12.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Singular Label"), /* @__PURE__ */ React12.createElement(
1759
2114
  "input",
1760
2115
  {
1761
2116
  type: "text",
@@ -1764,7 +2119,7 @@ function BlockConfig() {
1764
2119
  placeholder: "My Block",
1765
2120
  className: "bb-input"
1766
2121
  }
1767
- )), /* @__PURE__ */ React11.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React11.createElement("label", { className: "bb-form__label" }, "Plural Label"), /* @__PURE__ */ React11.createElement(
2122
+ )), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Plural Label"), /* @__PURE__ */ React12.createElement(
1768
2123
  "input",
1769
2124
  {
1770
2125
  type: "text",
@@ -1773,7 +2128,7 @@ function BlockConfig() {
1773
2128
  placeholder: "My Blocks",
1774
2129
  className: "bb-input"
1775
2130
  }
1776
- ))), /* @__PURE__ */ React11.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React11.createElement("label", { className: "bb-form__label" }, "Image URL"), /* @__PURE__ */ React11.createElement(
2131
+ ))), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Image URL"), /* @__PURE__ */ React12.createElement(
1777
2132
  "input",
1778
2133
  {
1779
2134
  type: "text",
@@ -1782,38 +2137,62 @@ function BlockConfig() {
1782
2137
  placeholder: "https://...",
1783
2138
  className: "bb-input"
1784
2139
  }
1785
- )), /* @__PURE__ */ React11.createElement("div", { className: "bb-stat-box" }, /* @__PURE__ */ React11.createElement("strong", null, block.fields.length), " field", block.fields.length !== 1 ? "s" : "", " defined"));
2140
+ )), /* @__PURE__ */ React12.createElement("div", { className: "bb-stat-box" }, /* @__PURE__ */ React12.createElement("strong", null, block.fields.length), " field", block.fields.length !== 1 ? "s" : "", " defined"));
1786
2141
  }
1787
2142
 
1788
2143
  // src/block-builder/components/config/FieldConfig.tsx
1789
- import React12 from "react";
2144
+ import React13 from "react";
1790
2145
  var ALL_TYPES = [
1791
2146
  "text",
1792
2147
  "textarea",
2148
+ "richtext",
1793
2149
  "number",
1794
2150
  "email",
2151
+ "url",
2152
+ "color",
1795
2153
  "date",
1796
2154
  "checkbox",
1797
2155
  "select",
1798
- "radio",
1799
- "upload",
2156
+ "multiselect",
2157
+ "image",
2158
+ "file",
2159
+ "relationship",
2160
+ "json",
2161
+ "array",
2162
+ "group",
2163
+ "blocks",
2164
+ "row",
2165
+ "tabs",
2166
+ "collapsible"
2167
+ ];
2168
+ var NO_DEFAULT_VALUE_TYPES = /* @__PURE__ */ new Set([
2169
+ "array",
2170
+ "group",
2171
+ "blocks",
2172
+ "row",
2173
+ "tabs",
2174
+ "collapsible",
2175
+ "image",
2176
+ "file",
1800
2177
  "relationship",
1801
2178
  "json"
1802
- ];
2179
+ ]);
1803
2180
  function FieldConfig() {
1804
2181
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1805
2182
  const activeFieldId = useBuilderStore((s) => s.activeFieldId);
2183
+ const activeParentPath = useBuilderStore((s) => s.activeParentPath);
1806
2184
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1807
- const field = block?.fields.find((f) => f.id === activeFieldId);
2185
+ const field = block ? getTargetFields(block, activeParentPath)?.find((f) => f.id === activeFieldId) : void 0;
1808
2186
  const updateField = useBuilderStore((s) => s.updateField);
2187
+ const pushParentPath = useBuilderStore((s) => s.pushParentPath);
1809
2188
  if (!activeBlockId || !activeFieldId || !field) {
1810
- return /* @__PURE__ */ React12.createElement("div", { className: "bb-form__empty" }, "Select a field to configure it.");
2189
+ return /* @__PURE__ */ React13.createElement("div", { className: "bb-form__empty" }, "Select a field to configure it.");
1811
2190
  }
1812
2191
  function upd(updates) {
1813
2192
  updateField(activeBlockId, activeFieldId, updates);
1814
2193
  }
1815
- const needsOptions = field.type === "select" || field.type === "radio";
1816
- return /* @__PURE__ */ React12.createElement("div", { className: "bb-form" }, /* @__PURE__ */ React12.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Field Name *"), /* @__PURE__ */ React12.createElement(
2194
+ const needsOptions = field.type === "select" || field.type === "multiselect";
2195
+ return /* @__PURE__ */ React13.createElement("div", { className: "bb-form" }, /* @__PURE__ */ React13.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Field Name *"), /* @__PURE__ */ React13.createElement(
1817
2196
  "input",
1818
2197
  {
1819
2198
  type: "text",
@@ -1822,15 +2201,15 @@ function FieldConfig() {
1822
2201
  placeholder: "fieldName",
1823
2202
  className: "bb-input"
1824
2203
  }
1825
- )), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Type *"), /* @__PURE__ */ React12.createElement(
2204
+ )), /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Type *"), /* @__PURE__ */ React13.createElement(
1826
2205
  "select",
1827
2206
  {
1828
2207
  value: field.type,
1829
2208
  onChange: (e) => upd({ type: e.target.value }),
1830
2209
  className: "bb-input bb-select"
1831
2210
  },
1832
- ALL_TYPES.map((t) => /* @__PURE__ */ React12.createElement("option", { key: t, value: t }, t))
1833
- ))), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Label"), /* @__PURE__ */ React12.createElement(
2211
+ ALL_TYPES.map((t) => /* @__PURE__ */ React13.createElement("option", { key: t, value: t }, t))
2212
+ ))), /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Label"), /* @__PURE__ */ React13.createElement(
1834
2213
  "input",
1835
2214
  {
1836
2215
  type: "text",
@@ -1839,28 +2218,44 @@ function FieldConfig() {
1839
2218
  placeholder: "Human-readable label",
1840
2219
  className: "bb-input"
1841
2220
  }
1842
- )), /* @__PURE__ */ React12.createElement("div", { className: "bb-flags-row" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React12.createElement(
2221
+ )), /* @__PURE__ */ React13.createElement("div", { className: "bb-flags-row" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React13.createElement(
1843
2222
  "input",
1844
2223
  {
1845
2224
  type: "checkbox",
1846
2225
  checked: field.required ?? false,
1847
2226
  onChange: (e) => upd({ required: e.target.checked })
1848
2227
  }
1849
- ), "Required"), /* @__PURE__ */ React12.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React12.createElement(
2228
+ ), "Required"), /* @__PURE__ */ React13.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React13.createElement(
1850
2229
  "input",
1851
2230
  {
1852
2231
  type: "checkbox",
1853
2232
  checked: field.unique ?? false,
1854
2233
  onChange: (e) => upd({ unique: e.target.checked })
1855
2234
  }
1856
- ), "Unique"), /* @__PURE__ */ React12.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React12.createElement(
2235
+ ), "Unique"), /* @__PURE__ */ React13.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React13.createElement(
1857
2236
  "input",
1858
2237
  {
1859
2238
  type: "checkbox",
1860
2239
  checked: field.localized ?? false,
1861
2240
  onChange: (e) => upd({ localized: e.target.checked })
1862
2241
  }
1863
- ), "Localized")), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Default Value"), /* @__PURE__ */ React12.createElement(
2242
+ ), "Localized")), !NO_DEFAULT_VALUE_TYPES.has(field.type) && /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Default Value"), field.type === "checkbox" ? /* @__PURE__ */ React13.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React13.createElement(
2243
+ "input",
2244
+ {
2245
+ type: "checkbox",
2246
+ checked: field.defaultValue === true,
2247
+ onChange: (e) => upd({ defaultValue: e.target.checked })
2248
+ }
2249
+ ), "Checked by default") : field.type === "number" ? /* @__PURE__ */ React13.createElement(
2250
+ "input",
2251
+ {
2252
+ type: "number",
2253
+ value: typeof field.defaultValue === "number" ? field.defaultValue : "",
2254
+ onChange: (e) => upd({ defaultValue: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
2255
+ placeholder: "0",
2256
+ className: "bb-input"
2257
+ }
2258
+ ) : /* @__PURE__ */ React13.createElement(
1864
2259
  "input",
1865
2260
  {
1866
2261
  type: "text",
@@ -1869,23 +2264,23 @@ function FieldConfig() {
1869
2264
  placeholder: "Default value",
1870
2265
  className: "bb-input"
1871
2266
  }
1872
- )), field.type === "relationship" && /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Relation To (collection slug)"), /* @__PURE__ */ React12.createElement(
2267
+ )), field.type === "relationship" && /* @__PURE__ */ React13.createElement(React13.Fragment, null, /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Relation To (collection slug)"), /* @__PURE__ */ React13.createElement(
1873
2268
  "input",
1874
2269
  {
1875
2270
  type: "text",
1876
- value: field.relationTo ?? "",
1877
- onChange: (e) => upd({ relationTo: e.target.value }),
2271
+ value: field.collection ?? "",
2272
+ onChange: (e) => upd({ collection: e.target.value }),
1878
2273
  placeholder: "pages",
1879
2274
  className: "bb-input"
1880
2275
  }
1881
- )), /* @__PURE__ */ React12.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React12.createElement(
2276
+ )), /* @__PURE__ */ React13.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ React13.createElement(
1882
2277
  "input",
1883
2278
  {
1884
2279
  type: "checkbox",
1885
2280
  checked: field.hasMany ?? false,
1886
2281
  onChange: (e) => upd({ hasMany: e.target.checked })
1887
2282
  }
1888
- ), "Has Many")), field.type === "array" && /* @__PURE__ */ React12.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Min Rows"), /* @__PURE__ */ React12.createElement(
2283
+ ), "Has Many")), field.type === "array" && /* @__PURE__ */ React13.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Min Rows"), /* @__PURE__ */ React13.createElement(
1889
2284
  "input",
1890
2285
  {
1891
2286
  type: "number",
@@ -1893,7 +2288,7 @@ function FieldConfig() {
1893
2288
  onChange: (e) => upd({ minRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
1894
2289
  className: "bb-input"
1895
2290
  }
1896
- )), /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("label", { className: "bb-form__label" }, "Max Rows"), /* @__PURE__ */ React12.createElement(
2291
+ )), /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("label", { className: "bb-form__label" }, "Max Rows"), /* @__PURE__ */ React13.createElement(
1897
2292
  "input",
1898
2293
  {
1899
2294
  type: "number",
@@ -1901,7 +2296,54 @@ function FieldConfig() {
1901
2296
  onChange: (e) => upd({ maxRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
1902
2297
  className: "bb-input"
1903
2298
  }
1904
- ))), needsOptions && /* @__PURE__ */ React12.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React12.createElement("div", { className: "bb-options-label" }, "Options"), (field.options ?? []).map((opt, i) => /* @__PURE__ */ React12.createElement("div", { key: i, className: "bb-option-row" }, /* @__PURE__ */ React12.createElement(
2299
+ ))), field.type === "tabs" && /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("div", { className: "bb-options-label" }, "Tabs"), (field.tabs ?? []).map((tab, i) => /* @__PURE__ */ React13.createElement("div", { key: tab.id ?? i, className: "bb-option-row" }, /* @__PURE__ */ React13.createElement(
2300
+ "input",
2301
+ {
2302
+ type: "text",
2303
+ value: tab.label,
2304
+ placeholder: "Tab label",
2305
+ onChange: (e) => {
2306
+ const next = [...field.tabs ?? []];
2307
+ next[i] = { ...next[i], label: e.target.value };
2308
+ upd({ tabs: next });
2309
+ },
2310
+ className: "bb-input"
2311
+ }
2312
+ ), /* @__PURE__ */ React13.createElement(
2313
+ "button",
2314
+ {
2315
+ type: "button",
2316
+ onClick: () => pushParentPath(encodeTabPath(field.id, i)),
2317
+ className: "bb-add-option",
2318
+ title: "Edit this tab's fields"
2319
+ },
2320
+ "Edit Fields"
2321
+ ), /* @__PURE__ */ React13.createElement(
2322
+ "button",
2323
+ {
2324
+ type: "button",
2325
+ onClick: () => {
2326
+ const next = (field.tabs ?? []).filter((_, k) => k !== i);
2327
+ upd({ tabs: next });
2328
+ },
2329
+ className: "bb-option-delete",
2330
+ title: "Remove tab"
2331
+ },
2332
+ "x"
2333
+ ))), /* @__PURE__ */ React13.createElement(
2334
+ "button",
2335
+ {
2336
+ type: "button",
2337
+ onClick: () => upd({
2338
+ tabs: [
2339
+ ...field.tabs ?? [],
2340
+ { id: uuidv4(), label: `Tab ${(field.tabs?.length ?? 0) + 1}`, fields: [] }
2341
+ ]
2342
+ }),
2343
+ className: "bb-add-option"
2344
+ },
2345
+ "+ Add Tab"
2346
+ )), needsOptions && /* @__PURE__ */ React13.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ React13.createElement("div", { className: "bb-options-label" }, "Options"), (field.options ?? []).map((opt, i) => /* @__PURE__ */ React13.createElement("div", { key: i, className: "bb-option-row" }, /* @__PURE__ */ React13.createElement(
1905
2347
  "input",
1906
2348
  {
1907
2349
  type: "text",
@@ -1914,7 +2356,7 @@ function FieldConfig() {
1914
2356
  },
1915
2357
  className: "bb-input"
1916
2358
  }
1917
- ), /* @__PURE__ */ React12.createElement(
2359
+ ), /* @__PURE__ */ React13.createElement(
1918
2360
  "input",
1919
2361
  {
1920
2362
  type: "text",
@@ -1927,7 +2369,7 @@ function FieldConfig() {
1927
2369
  },
1928
2370
  className: "bb-input"
1929
2371
  }
1930
- ), /* @__PURE__ */ React12.createElement(
2372
+ ), /* @__PURE__ */ React13.createElement(
1931
2373
  "button",
1932
2374
  {
1933
2375
  type: "button",
@@ -1939,7 +2381,7 @@ function FieldConfig() {
1939
2381
  title: "Remove option"
1940
2382
  },
1941
2383
  "x"
1942
- ))), /* @__PURE__ */ React12.createElement(
2384
+ ))), /* @__PURE__ */ React13.createElement(
1943
2385
  "button",
1944
2386
  {
1945
2387
  type: "button",
@@ -1956,7 +2398,7 @@ function ConfigPanel() {
1956
2398
  const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1957
2399
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1958
2400
  const activeTab = activeFieldId ? "field" : tab;
1959
- return /* @__PURE__ */ React13.createElement("div", { className: `bb-config${isReadOnly ? " bb-config--readonly" : ""}` }, /* @__PURE__ */ React13.createElement("div", { className: "bb-config__tabs" }, ["block", "field"].map((t) => /* @__PURE__ */ React13.createElement(
2401
+ return /* @__PURE__ */ React14.createElement("div", { className: `bb-config${isReadOnly ? " bb-config--readonly" : ""}` }, /* @__PURE__ */ React14.createElement("div", { className: "bb-config__tabs" }, ["block", "field"].map((t) => /* @__PURE__ */ React14.createElement(
1960
2402
  "button",
1961
2403
  {
1962
2404
  key: t,
@@ -1965,26 +2407,29 @@ function ConfigPanel() {
1965
2407
  className: `bb-config__tab${activeTab === t ? " bb-config__tab--active" : ""}`
1966
2408
  },
1967
2409
  t
1968
- ))), /* @__PURE__ */ React13.createElement("div", { className: "bb-config__body" }, activeTab === "block" ? /* @__PURE__ */ React13.createElement(BlockConfig, null) : /* @__PURE__ */ React13.createElement(FieldConfig, null)), isReadOnly && /* @__PURE__ */ React13.createElement("div", { className: "bb-config__readonly-overlay" }, /* @__PURE__ */ React13.createElement("span", { className: "bb-config__readonly-label" }, "Read only")));
2410
+ ))), /* @__PURE__ */ React14.createElement("div", { className: "bb-config__body" }, activeTab === "block" ? /* @__PURE__ */ React14.createElement(BlockConfig, null) : /* @__PURE__ */ React14.createElement(FieldConfig, null)), isReadOnly && /* @__PURE__ */ React14.createElement("div", { className: "bb-config__readonly-overlay" }, /* @__PURE__ */ React14.createElement("span", { className: "bb-config__readonly-label" }, "Read only")));
1969
2411
  }
1970
2412
 
1971
2413
  // src/block-builder/components/sidebar/FieldPalette.tsx
1972
- import React14, { useState as useState6 } from "react";
2414
+ import React15, { useState as useState6 } from "react";
1973
2415
  import {
1974
2416
  Type as Type2,
1975
2417
  AlignLeft as AlignLeft2,
1976
- AlignJustify,
2418
+ AlignJustify as AlignJustify2,
1977
2419
  Hash as Hash2,
1978
2420
  Mail as Mail2,
1979
2421
  Calendar as Calendar2,
1980
2422
  CheckSquare as CheckSquare2,
1981
2423
  ChevronDown as ChevronDown3,
1982
- Circle as Circle2,
2424
+ Circle,
1983
2425
  Upload as Upload2,
1984
2426
  Link as Link2,
1985
- List,
1986
- Folder,
1987
- Braces as Braces2
2427
+ List as List2,
2428
+ Folder as Folder2,
2429
+ Braces as Braces2,
2430
+ Image as Image2,
2431
+ Box as Box2,
2432
+ Columns as Columns2
1988
2433
  } from "lucide-react";
1989
2434
 
1990
2435
  // src/block-builder/lib/field-palette.ts
@@ -1998,11 +2443,17 @@ var FIELD_PALETTE = [
1998
2443
  { type: "checkbox", label: "Checkbox", description: "Boolean toggle", icon: "CheckSquare", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1999
2444
  // Choice
2000
2445
  { type: "select", label: "Select", description: "Dropdown selection", icon: "ChevronDown", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
2001
- { type: "radio", label: "Radio", description: "Radio button group", icon: "Circle", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
2002
2446
  // Media
2003
- { type: "upload", label: "Upload", description: "File or media upload", icon: "Upload", category: "media", color: "bg-green-500/20 text-green-400 border-green-500/30" },
2447
+ { type: "image", label: "Image", description: "Image upload", icon: "Image", category: "media", color: "bg-green-500/20 text-green-400 border-green-500/30" },
2448
+ { type: "file", label: "File", description: "File upload", icon: "Upload", category: "media", color: "bg-green-500/20 text-green-400 border-green-500/30" },
2004
2449
  // Relational
2005
2450
  { type: "relationship", label: "Relationship", description: "Link to another collection", icon: "Link", category: "relational", color: "bg-orange-500/20 text-orange-400 border-orange-500/30" },
2451
+ // Layout
2452
+ { type: "array", label: "Array", description: "Repeating list of fields", icon: "List", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2453
+ { type: "group", label: "Group", description: "Grouped fields object", icon: "Box", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2454
+ { type: "row", label: "Row", description: "Horizontal layout row", icon: "Columns", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2455
+ { type: "tabs", label: "Tabs", description: "Tabbed layout container", icon: "Folder", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2456
+ { type: "collapsible", label: "Collapsible", description: "Expandable field group", icon: "ChevronDown", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2006
2457
  // Advanced
2007
2458
  { type: "json", label: "JSON", description: "Raw JSON data field", icon: "Braces", category: "advanced", color: "bg-red-500/20 text-red-400 border-red-500/30" }
2008
2459
  ];
@@ -2011,6 +2462,7 @@ var FIELD_CATEGORIES = [
2011
2462
  { id: "choice", label: "Choice" },
2012
2463
  { id: "media", label: "Media" },
2013
2464
  { id: "relational", label: "Relational" },
2465
+ { id: "layout", label: "Layout" },
2014
2466
  { id: "advanced", label: "Advanced" }
2015
2467
  ];
2016
2468
  function getFieldMeta(type) {
@@ -2021,18 +2473,21 @@ function getFieldMeta(type) {
2021
2473
  var ICON_MAP2 = {
2022
2474
  Type: Type2,
2023
2475
  AlignLeft: AlignLeft2,
2024
- AlignJustify,
2476
+ AlignJustify: AlignJustify2,
2025
2477
  Hash: Hash2,
2026
2478
  Mail: Mail2,
2027
2479
  Calendar: Calendar2,
2028
2480
  CheckSquare: CheckSquare2,
2029
2481
  ChevronDown: ChevronDown3,
2030
- Circle: Circle2,
2482
+ Circle,
2031
2483
  Upload: Upload2,
2032
2484
  Link: Link2,
2033
- List,
2034
- Folder,
2035
- Braces: Braces2
2485
+ List: List2,
2486
+ Folder: Folder2,
2487
+ Braces: Braces2,
2488
+ Image: Image2,
2489
+ Box: Box2,
2490
+ Columns: Columns2
2036
2491
  };
2037
2492
  function FieldPalette() {
2038
2493
  const [search, setSearch] = useState6("");
@@ -2046,7 +2501,7 @@ function FieldPalette() {
2046
2501
  if (!activeBlockId || isReadOnly) return;
2047
2502
  addField(activeBlockId, type);
2048
2503
  }
2049
- return /* @__PURE__ */ React14.createElement("div", { className: `bb-sidebar bb-sidebar--200 bb-sidebar--palette${isReadOnly ? " bb-sidebar--readonly" : ""}` }, /* @__PURE__ */ React14.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ React14.createElement("span", { className: "bb-sidebar__title" }, "Fields")), /* @__PURE__ */ React14.createElement("div", { className: "bb-palette__search-wrap" }, /* @__PURE__ */ React14.createElement(
2504
+ return /* @__PURE__ */ React15.createElement("div", { className: `bb-sidebar bb-sidebar--200 bb-sidebar--palette${isReadOnly ? " bb-sidebar--readonly" : ""}` }, /* @__PURE__ */ React15.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ React15.createElement("span", { className: "bb-sidebar__title" }, "Fields")), /* @__PURE__ */ React15.createElement("div", { className: "bb-palette__search-wrap" }, /* @__PURE__ */ React15.createElement(
2050
2505
  "input",
2051
2506
  {
2052
2507
  type: "text",
@@ -2055,7 +2510,7 @@ function FieldPalette() {
2055
2510
  placeholder: "Search...",
2056
2511
  className: "bb-palette__search"
2057
2512
  }
2058
- )), /* @__PURE__ */ React14.createElement("div", { className: "bb-sidebar__body" }, search.trim() ? /* @__PURE__ */ React14.createElement("div", { className: "bb-palette__items" }, filtered.map((item) => /* @__PURE__ */ React14.createElement(
2513
+ )), /* @__PURE__ */ React15.createElement("div", { className: "bb-sidebar__body" }, search.trim() ? /* @__PURE__ */ React15.createElement("div", { className: "bb-palette__items" }, filtered.map((item) => /* @__PURE__ */ React15.createElement(
2059
2514
  FieldButton,
2060
2515
  {
2061
2516
  key: item.type,
@@ -2067,7 +2522,7 @@ function FieldPalette() {
2067
2522
  }
2068
2523
  ))) : FIELD_CATEGORIES.map((cat) => {
2069
2524
  const items = FIELD_PALETTE.filter((f) => f.category === cat.id);
2070
- return /* @__PURE__ */ React14.createElement("div", { key: cat.id, style: { marginTop: 6 } }, /* @__PURE__ */ React14.createElement("div", { className: "bb-palette__cat-label" }, cat.label), /* @__PURE__ */ React14.createElement("div", { className: "bb-palette__items" }, items.map((item) => /* @__PURE__ */ React14.createElement(
2525
+ return /* @__PURE__ */ React15.createElement("div", { key: cat.id, style: { marginTop: 6 } }, /* @__PURE__ */ React15.createElement("div", { className: "bb-palette__cat-label" }, cat.label), /* @__PURE__ */ React15.createElement("div", { className: "bb-palette__items" }, items.map((item) => /* @__PURE__ */ React15.createElement(
2071
2526
  FieldButton,
2072
2527
  {
2073
2528
  key: item.type,
@@ -2078,7 +2533,7 @@ function FieldPalette() {
2078
2533
  disabled: !activeBlockId || isReadOnly
2079
2534
  }
2080
2535
  ))));
2081
- })), !activeBlockId && /* @__PURE__ */ React14.createElement("div", { className: "bb-sidebar__footer" }, "Select a block first"));
2536
+ })), !activeBlockId && /* @__PURE__ */ React15.createElement("div", { className: "bb-sidebar__footer" }, "Select a block first"));
2082
2537
  }
2083
2538
  function FieldButton({
2084
2539
  type,
@@ -2089,7 +2544,7 @@ function FieldButton({
2089
2544
  }) {
2090
2545
  const meta = getFieldMeta(type);
2091
2546
  const Icon = ICON_MAP2[icon];
2092
- return /* @__PURE__ */ React14.createElement(
2547
+ return /* @__PURE__ */ React15.createElement(
2093
2548
  "button",
2094
2549
  {
2095
2550
  type: "button",
@@ -2098,14 +2553,291 @@ function FieldButton({
2098
2553
  title: meta?.description,
2099
2554
  className: "bb-palette__item"
2100
2555
  },
2101
- /* @__PURE__ */ React14.createElement("span", { className: "bb-palette__item__icon" }, Icon ? /* @__PURE__ */ React14.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null),
2102
- /* @__PURE__ */ React14.createElement("span", { className: "bb-palette__item__label" }, label),
2103
- /* @__PURE__ */ React14.createElement("span", { className: "bb-palette__item__type" }, type)
2556
+ /* @__PURE__ */ React15.createElement("span", { className: "bb-palette__item__icon" }, Icon ? /* @__PURE__ */ React15.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null),
2557
+ /* @__PURE__ */ React15.createElement("span", { className: "bb-palette__item__label" }, label),
2558
+ /* @__PURE__ */ React15.createElement("span", { className: "bb-palette__item__type" }, type)
2559
+ );
2560
+ }
2561
+
2562
+ // src/block-builder/components/canvas/CodePreview.tsx
2563
+ import React16, { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState7 } from "react";
2564
+
2565
+ // src/block-builder/lib/codegen.ts
2566
+ function indent(n) {
2567
+ return " ".repeat(n);
2568
+ }
2569
+ function safeStr(s) {
2570
+ return JSON.stringify(s);
2571
+ }
2572
+ function listToCode(items, body, depth) {
2573
+ if (items.length === 0) return "[]";
2574
+ const pad = indent(depth);
2575
+ const innerPad = indent(depth + 1);
2576
+ const entries = items.map((item) => `${innerPad}{
2577
+ ${body(item)}
2578
+ ${innerPad}}`).join(",\n");
2579
+ return `[
2580
+ ${entries}
2581
+ ${pad}]`;
2582
+ }
2583
+ var UNNAMED_TYPES = /* @__PURE__ */ new Set(["row", "tabs", "collapsible"]);
2584
+ var NO_LABEL_TYPES = /* @__PURE__ */ new Set(["row"]);
2585
+ var NO_LOCALIZED_TYPES = /* @__PURE__ */ new Set(["row", "tabs", "collapsible"]);
2586
+ var NO_ADMIN_DESCRIPTION_TYPES = /* @__PURE__ */ new Set(["row", "tabs"]);
2587
+ var FIELDS_CONTAINER_TYPES = /* @__PURE__ */ new Set([
2588
+ "array",
2589
+ "group",
2590
+ "row",
2591
+ "collapsible"
2592
+ ]);
2593
+ var PAYLOAD_TYPE = {
2594
+ richtext: "richText",
2595
+ image: "upload",
2596
+ file: "upload",
2597
+ multiselect: "select",
2598
+ url: "text",
2599
+ color: "text"
2600
+ };
2601
+ var UPLOAD_TYPES = /* @__PURE__ */ new Set(["image", "file"]);
2602
+ var DEFAULT_UPLOAD_COLLECTION = "media";
2603
+ function payloadType(type) {
2604
+ return PAYLOAD_TYPE[type] ?? type;
2605
+ }
2606
+ function fieldToCode(field, depth = 1) {
2607
+ const pad = indent(depth);
2608
+ const innerPad = indent(depth + 1);
2609
+ const lines = [];
2610
+ if (!UNNAMED_TYPES.has(field.type)) {
2611
+ lines.push(`${pad}name: ${safeStr(field.name)}`);
2612
+ }
2613
+ lines.push(`${pad}type: '${payloadType(field.type)}'`);
2614
+ if (field.label && !NO_LABEL_TYPES.has(field.type)) {
2615
+ lines.push(`${pad}label: ${safeStr(field.label)}`);
2616
+ }
2617
+ if (field.required) lines.push(`${pad}required: true`);
2618
+ if (field.unique) lines.push(`${pad}unique: true`);
2619
+ if (field.localized && !NO_LOCALIZED_TYPES.has(field.type)) {
2620
+ lines.push(`${pad}localized: true`);
2621
+ }
2622
+ if (field.defaultValue !== void 0) {
2623
+ const val = typeof field.defaultValue === "string" ? safeStr(String(field.defaultValue)) : field.defaultValue;
2624
+ lines.push(`${pad}defaultValue: ${val}`);
2625
+ }
2626
+ if (field.type === "richtext") {
2627
+ lines.push(`${pad}editor: lexicalEditor({})`);
2628
+ }
2629
+ if (field.options && field.options.length > 0) {
2630
+ const opts = field.options.map((o) => `{ label: ${safeStr(o.label)}, value: ${safeStr(o.value)} }`).join(`, `);
2631
+ lines.push(`${pad}options: [${opts}]`);
2632
+ }
2633
+ if (UPLOAD_TYPES.has(field.type)) {
2634
+ lines.push(
2635
+ `${pad}relationTo: ${safeStr(field.collection || DEFAULT_UPLOAD_COLLECTION)}`
2636
+ );
2637
+ } else if (field.collection) {
2638
+ lines.push(`${pad}relationTo: ${safeStr(field.collection)}`);
2639
+ }
2640
+ if (field.type === "multiselect") {
2641
+ lines.push(`${pad}hasMany: true`);
2642
+ } else if (field.hasMany !== void 0) {
2643
+ lines.push(`${pad}hasMany: ${field.hasMany}`);
2644
+ }
2645
+ if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
2646
+ if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
2647
+ const children = field.fields ?? [];
2648
+ if (children.length > 0 || FIELDS_CONTAINER_TYPES.has(field.type)) {
2649
+ lines.push(
2650
+ `${pad}fields: ${listToCode(children, (f) => fieldToCode(f, depth + 2), depth)}`
2651
+ );
2652
+ }
2653
+ if (field.type === "blocks") {
2654
+ lines.push(`${pad}blocks: []`);
2655
+ }
2656
+ if (field.type === "tabs") {
2657
+ const tabsCode = (field.tabs ?? []).map((tab) => {
2658
+ const tabPad = indent(depth + 2);
2659
+ const tabLines = [];
2660
+ if (tab.name) tabLines.push(`${tabPad}name: ${safeStr(tab.name)}`);
2661
+ tabLines.push(`${tabPad}label: ${safeStr(tab.label)}`);
2662
+ tabLines.push(
2663
+ `${tabPad}fields: ${listToCode(tab.fields ?? [], (f) => fieldToCode(f, depth + 4), depth + 2)}`
2664
+ );
2665
+ return `${innerPad}{
2666
+ ${tabLines.join(",\n")}
2667
+ ${innerPad}}`;
2668
+ }).join(",\n");
2669
+ lines.push(
2670
+ `${pad}tabs: ${(field.tabs ?? []).length > 0 ? `[
2671
+ ${tabsCode}
2672
+ ${pad}]` : "[]"}`
2673
+ );
2674
+ }
2675
+ const adminParts = [];
2676
+ if (field.admin?.description && !NO_ADMIN_DESCRIPTION_TYPES.has(field.type))
2677
+ adminParts.push(`description: ${safeStr(field.admin.description)}`);
2678
+ if (field.admin?.placeholder)
2679
+ adminParts.push(`placeholder: ${safeStr(field.admin.placeholder)}`);
2680
+ if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
2681
+ if (field.admin?.hidden) adminParts.push(`hidden: true`);
2682
+ if (adminParts.length > 0) {
2683
+ lines.push(`${pad}admin: { ${adminParts.join(", ")} }`);
2684
+ }
2685
+ return lines.join(",\n");
2686
+ }
2687
+ function generateBlockCode(block) {
2688
+ const hasRichText = containsRichText(block.fields);
2689
+ const imports = [`import type { Block } from 'payload'`];
2690
+ if (hasRichText) {
2691
+ imports.push(`import { lexicalEditor } from '@payloadcms/richtext-lexical'`);
2692
+ }
2693
+ const fieldsCode = block.fields.map((f) => ` {
2694
+ ${fieldToCode(f, 3)}
2695
+ }`).join(",\n");
2696
+ const labelsCode = block.labels ? `
2697
+ labels: {
2698
+ singular: ${safeStr(block.labels.singular ?? block.slug)},
2699
+ plural: ${safeStr(block.labels.plural ?? block.slug + "s")},
2700
+ },` : "";
2701
+ const interfaceLine = block.interfaceName ? `
2702
+ interfaceName: ${safeStr(block.interfaceName)},` : "";
2703
+ const exportName = block.interfaceName ?? toCamelCase(block.slug);
2704
+ return [
2705
+ imports.join("\n"),
2706
+ "",
2707
+ `export const ${exportName}: Block = {`,
2708
+ ` slug: ${safeStr(block.slug)},${interfaceLine}${labelsCode}`,
2709
+ ` fields: [`,
2710
+ fieldsCode,
2711
+ ` ],`,
2712
+ `}`,
2713
+ ""
2714
+ ].join("\n");
2715
+ }
2716
+ function containsRichText(fields) {
2717
+ return fields.some((f) => {
2718
+ if (f.type === "richtext") return true;
2719
+ if (f.fields && containsRichText(f.fields)) return true;
2720
+ if (f.tabs?.some((tab) => containsRichText(tab.fields ?? []))) return true;
2721
+ return false;
2722
+ });
2723
+ }
2724
+ function toCamelCase(slug) {
2725
+ return slug.split(/[-_]/).map(
2726
+ (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
2727
+ ).join("");
2728
+ }
2729
+ function toPascalCase(slug) {
2730
+ return slug.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2731
+ }
2732
+ function getTsType(field) {
2733
+ switch (field.type) {
2734
+ case "number":
2735
+ return "number";
2736
+ case "checkbox":
2737
+ return "boolean";
2738
+ case "text":
2739
+ case "textarea":
2740
+ case "email":
2741
+ case "url":
2742
+ case "color":
2743
+ case "select":
2744
+ case "date":
2745
+ return "string";
2746
+ case "multiselect":
2747
+ return "string[]";
2748
+ case "group":
2749
+ return field.fields ? objectType(flattenProps(field.fields)) : "any";
2750
+ case "array":
2751
+ if (field.fields) {
2752
+ const inner = flattenProps(field.fields).map((p) => `${p.name}: ${p.type}`).join("; ");
2753
+ return `Array<{ id: string; ${inner} }>`;
2754
+ }
2755
+ return "any[]";
2756
+ default:
2757
+ return "any";
2758
+ }
2759
+ }
2760
+ function objectType(props) {
2761
+ return props.length > 0 ? `{ ${props.map((p) => `${p.name}: ${p.type}`).join("; ")} }` : "Record<string, unknown>";
2762
+ }
2763
+ function flattenProps(fields) {
2764
+ const out = [];
2765
+ for (const f of fields) {
2766
+ if (f.type === "row" || f.type === "collapsible") {
2767
+ out.push(...flattenProps(f.fields ?? []));
2768
+ } else if (f.type === "tabs") {
2769
+ for (const tab of f.tabs ?? []) {
2770
+ if (tab.name) {
2771
+ out.push({ name: tab.name, type: objectType(flattenProps(tab.fields ?? [])) });
2772
+ } else {
2773
+ out.push(...flattenProps(tab.fields ?? []));
2774
+ }
2775
+ }
2776
+ } else {
2777
+ out.push({ name: f.name, type: getTsType(f) });
2778
+ }
2779
+ }
2780
+ return out;
2781
+ }
2782
+ function generateReactComponent(block) {
2783
+ const componentName = block.interfaceName ?? toPascalCase(block.slug);
2784
+ const propsName = `${componentName}Props`;
2785
+ const propList = flattenProps(block.fields);
2786
+ const propsCode = propList.map((p) => ` ${p.name}: ${p.type}`).join("\n");
2787
+ const fieldsJsx = propList.map((p) => ` <div className="field-${p.name}">
2788
+ {/* ${p.name} */}
2789
+ {String(props.${p.name})}
2790
+ </div>`).join("\n");
2791
+ const code = [
2792
+ `import React from 'react'`,
2793
+ ``,
2794
+ `export type ${propsName} = {`,
2795
+ propsCode,
2796
+ `}`,
2797
+ ``,
2798
+ `export function ${componentName}(props: ${propsName}) {`,
2799
+ ` return (`,
2800
+ ` <div className="${block.slug}">`,
2801
+ fieldsJsx,
2802
+ ` </div>`,
2803
+ ` )`,
2804
+ `}`,
2805
+ ``
2806
+ ].join("\n");
2807
+ return {
2808
+ filename: `${componentName}.tsx`,
2809
+ code,
2810
+ language: "typescript"
2811
+ };
2812
+ }
2813
+ function generateBlockOutput(block) {
2814
+ return {
2815
+ filename: `${block.slug}.ts`,
2816
+ code: generateBlockCode(block),
2817
+ language: "typescript"
2818
+ };
2819
+ }
2820
+ function generateAllBlocks(blocks, options = {}) {
2821
+ return blocks.flatMap(
2822
+ (block) => options.react ? [generateBlockOutput(block), generateReactComponent(block)] : [generateBlockOutput(block)]
2104
2823
  );
2105
2824
  }
2825
+ function generateIndexFile(blocks) {
2826
+ const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
2827
+ const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
2828
+ const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
2829
+ const code = [
2830
+ imports,
2831
+ "",
2832
+ `export const blocks = [`,
2833
+ exportList,
2834
+ `] as const`,
2835
+ ""
2836
+ ].join("\n");
2837
+ return { filename: "index.ts", code, language: "typescript" };
2838
+ }
2106
2839
 
2107
2840
  // src/block-builder/components/canvas/CodePreview.tsx
2108
- import React15, { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
2109
2841
  var KEYWORDS = /* @__PURE__ */ new Set([
2110
2842
  "import",
2111
2843
  "export",
@@ -2193,22 +2925,22 @@ var TOKEN_COLORS = {
2193
2925
  };
2194
2926
  function HighlightedCode({ code }) {
2195
2927
  const tokens = tokenise(code);
2196
- return /* @__PURE__ */ React15.createElement("code", { style: { fontFamily: "inherit" } }, tokens.map((tok, i) => /* @__PURE__ */ React15.createElement("span", { key: i, style: { color: TOKEN_COLORS[tok.kind] } }, tok.text)));
2928
+ return /* @__PURE__ */ React16.createElement("code", { style: { fontFamily: "inherit" } }, tokens.map((tok, i) => /* @__PURE__ */ React16.createElement("span", { key: i, style: { color: TOKEN_COLORS[tok.kind] } }, tok.text)));
2197
2929
  }
2198
2930
  function CodePreview() {
2199
2931
  const blocks = useBuilderStore((s) => s.blocks);
2200
2932
  const [fileMap, setFileMap] = useState7({});
2201
2933
  const [activeFile, setActiveFile] = useState7(null);
2202
2934
  const [copied, setCopied] = useState7(false);
2203
- const timerRef = useRef4(null);
2204
- const copyTimerRef = useRef4(null);
2935
+ const timerRef = useRef5(null);
2936
+ const copyTimerRef = useRef5(null);
2205
2937
  const regenerate = useCallback4(() => {
2206
2938
  if (blocks.length === 0) {
2207
2939
  setFileMap({});
2208
2940
  setActiveFile(null);
2209
2941
  return;
2210
2942
  }
2211
- const blockOutputs = generateAllBlocks(blocks);
2943
+ const blockOutputs = generateAllBlocks(blocks, { react: true });
2212
2944
  const indexOutput = generateIndexFile(blocks);
2213
2945
  const next = {};
2214
2946
  for (const out of blockOutputs) next[out.filename] = out.code;
@@ -2219,7 +2951,7 @@ function CodePreview() {
2219
2951
  return prev && keys.includes(prev) ? prev : keys[0] ?? null;
2220
2952
  });
2221
2953
  }, [blocks]);
2222
- useEffect4(() => {
2954
+ useEffect5(() => {
2223
2955
  if (timerRef.current) clearTimeout(timerRef.current);
2224
2956
  timerRef.current = setTimeout(regenerate, 300);
2225
2957
  return () => {
@@ -2238,7 +2970,7 @@ function CodePreview() {
2238
2970
  const activeCode = activeFile ? fileMap[activeFile] ?? "" : "";
2239
2971
  const lineCount = activeCode ? activeCode.split("\n").length : 0;
2240
2972
  if (fileNames.length === 0) {
2241
- return /* @__PURE__ */ React15.createElement(
2973
+ return /* @__PURE__ */ React16.createElement(
2242
2974
  "div",
2243
2975
  {
2244
2976
  className: "code-preview",
@@ -2255,7 +2987,7 @@ function CodePreview() {
2255
2987
  "Add a block to see generated TypeScript code"
2256
2988
  );
2257
2989
  }
2258
- return /* @__PURE__ */ React15.createElement("div", { className: "code-preview", style: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }, /* @__PURE__ */ React15.createElement(
2990
+ return /* @__PURE__ */ React16.createElement("div", { className: "code-preview", style: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }, /* @__PURE__ */ React16.createElement(
2259
2991
  "div",
2260
2992
  {
2261
2993
  style: {
@@ -2269,7 +3001,7 @@ function CodePreview() {
2269
3001
  },
2270
3002
  fileNames.map((name) => {
2271
3003
  const isActive = activeFile === name;
2272
- return /* @__PURE__ */ React15.createElement(
3004
+ return /* @__PURE__ */ React16.createElement(
2273
3005
  "button",
2274
3006
  {
2275
3007
  key: name,
@@ -2292,8 +3024,8 @@ function CodePreview() {
2292
3024
  name
2293
3025
  );
2294
3026
  }),
2295
- /* @__PURE__ */ React15.createElement("div", { style: { flex: 1 } }),
2296
- lineCount > 0 && /* @__PURE__ */ React15.createElement(
3027
+ /* @__PURE__ */ React16.createElement("div", { style: { flex: 1 } }),
3028
+ lineCount > 0 && /* @__PURE__ */ React16.createElement(
2297
3029
  "span",
2298
3030
  {
2299
3031
  style: {
@@ -2306,7 +3038,7 @@ function CodePreview() {
2306
3038
  lineCount,
2307
3039
  " lines"
2308
3040
  ),
2309
- /* @__PURE__ */ React15.createElement(
3041
+ /* @__PURE__ */ React16.createElement(
2310
3042
  "button",
2311
3043
  {
2312
3044
  type: "button",
@@ -2329,7 +3061,7 @@ function CodePreview() {
2329
3061
  },
2330
3062
  copied ? "Copied" : "Copy"
2331
3063
  )
2332
- ), /* @__PURE__ */ React15.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ React15.createElement(
3064
+ ), /* @__PURE__ */ React16.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ React16.createElement(
2333
3065
  "div",
2334
3066
  {
2335
3067
  style: {
@@ -2346,8 +3078,8 @@ function CodePreview() {
2346
3078
  },
2347
3079
  "aria-hidden": "true"
2348
3080
  },
2349
- activeCode.split("\n").map((_, i) => /* @__PURE__ */ React15.createElement("div", { key: i }, i + 1))
2350
- ), /* @__PURE__ */ React15.createElement(
3081
+ activeCode.split("\n").map((_, i) => /* @__PURE__ */ React16.createElement("div", { key: i }, i + 1))
3082
+ ), /* @__PURE__ */ React16.createElement(
2351
3083
  "pre",
2352
3084
  {
2353
3085
  style: {
@@ -2362,37 +3094,198 @@ function CodePreview() {
2362
3094
  whiteSpace: "pre"
2363
3095
  }
2364
3096
  },
2365
- /* @__PURE__ */ React15.createElement(HighlightedCode, { code: activeCode })
3097
+ /* @__PURE__ */ React16.createElement(HighlightedCode, { code: activeCode })
2366
3098
  )));
2367
3099
  }
2368
3100
 
3101
+ // src/block-builder/components/canvas/LivePreview.tsx
3102
+ import React17, { useState as useState8 } from "react";
3103
+ import {
3104
+ Type as Type3,
3105
+ AlignLeft as AlignLeft3,
3106
+ AlignJustify as AlignJustify3,
3107
+ Hash as Hash3,
3108
+ Mail as Mail3,
3109
+ Calendar as Calendar3,
3110
+ CheckSquare as CheckSquare3,
3111
+ ChevronDown as ChevronDown4,
3112
+ Upload as Upload3,
3113
+ Image as ImageIcon,
3114
+ Link as LinkIcon,
3115
+ Braces as Braces3,
3116
+ List as List3,
3117
+ Box as Box3,
3118
+ Columns as Columns3,
3119
+ Folder as Folder3,
3120
+ LayoutTemplate
3121
+ } from "lucide-react";
3122
+ var TYPE_ICON = {
3123
+ text: Type3,
3124
+ textarea: AlignLeft3,
3125
+ richtext: AlignJustify3,
3126
+ number: Hash3,
3127
+ email: Mail3,
3128
+ url: LinkIcon,
3129
+ date: Calendar3,
3130
+ checkbox: CheckSquare3,
3131
+ select: ChevronDown4,
3132
+ image: ImageIcon,
3133
+ file: Upload3,
3134
+ relationship: LinkIcon,
3135
+ json: Braces3,
3136
+ array: List3,
3137
+ group: Box3,
3138
+ row: Columns3,
3139
+ tabs: Folder3,
3140
+ collapsible: ChevronDown4
3141
+ };
3142
+ function TypeIcon({ type }) {
3143
+ const Icon = TYPE_ICON[type] ?? Type3;
3144
+ return /* @__PURE__ */ React17.createElement(Icon, { size: 13, strokeWidth: 1.75 });
3145
+ }
3146
+ function fieldDisplayLabel(field) {
3147
+ return field.label || field.name || "Untitled field";
3148
+ }
3149
+ function fullFieldPath(field, prefix) {
3150
+ const name = field.name || "\u2014";
3151
+ return prefix ? `${prefix}.${name}` : name;
3152
+ }
3153
+ function fieldPlaceholder(field) {
3154
+ if (field.admin?.placeholder) return field.admin.placeholder;
3155
+ switch (field.type) {
3156
+ case "email":
3157
+ return "name@example.com";
3158
+ case "url":
3159
+ return "https://example.com";
3160
+ case "textarea":
3161
+ case "richtext":
3162
+ return `Write ${fieldDisplayLabel(field).toLowerCase()} here\u2026`;
3163
+ case "text":
3164
+ default:
3165
+ return `Enter ${fieldDisplayLabel(field).toLowerCase()}`;
3166
+ }
3167
+ }
3168
+ function pluralize(count, word) {
3169
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
3170
+ }
3171
+ function FieldMeta({ field, path }) {
3172
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field__meta" }, path !== null && /* @__PURE__ */ React17.createElement("code", { className: "bb-preview-field__slug" }, path), /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__badge" }, field.type));
3173
+ }
3174
+ function FieldHead({ field, prefix }) {
3175
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field__head" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ React17.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field), field.required && /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__required" }, "*")), /* @__PURE__ */ React17.createElement(FieldMeta, { field, path: fullFieldPath(field, prefix) })));
3176
+ }
3177
+ function PreviewField({ field, prefix = "" }) {
3178
+ switch (field.type) {
3179
+ case "text":
3180
+ case "url":
3181
+ case "email":
3182
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("input", { className: "bb-preview-input", disabled: true, placeholder: fieldPlaceholder(field) }));
3183
+ case "textarea":
3184
+ case "richtext":
3185
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("textarea", { className: "bb-preview-textarea", disabled: true, rows: 3, placeholder: fieldPlaceholder(field) }));
3186
+ case "number":
3187
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("input", { className: "bb-preview-input", type: "number", disabled: true, placeholder: field.admin?.placeholder ?? "0" }));
3188
+ case "date":
3189
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("input", { className: "bb-preview-input", type: "date", disabled: true }));
3190
+ case "checkbox":
3191
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement("label", { className: "bb-preview-checkbox" }, /* @__PURE__ */ React17.createElement("input", { type: "checkbox", className: "bb-preview-checkbox__input", disabled: true }), /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ React17.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field), field.required && /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__required" }, "*")), /* @__PURE__ */ React17.createElement(FieldMeta, { field, path: fullFieldPath(field, prefix) }))));
3192
+ case "select":
3193
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("select", { className: "bb-preview-select", disabled: true }, /* @__PURE__ */ React17.createElement("option", null, field.options?.[0]?.label ?? "Select an option")));
3194
+ case "json":
3195
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-placeholder" }, "Raw JSON data"));
3196
+ case "image":
3197
+ case "file":
3198
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-placeholder bb-preview-placeholder--upload" }, /* @__PURE__ */ React17.createElement(TypeIcon, { type: field.type }), field.type === "image" ? "Image upload" : "File upload"));
3199
+ case "relationship":
3200
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-placeholder bb-preview-placeholder--upload" }, /* @__PURE__ */ React17.createElement(TypeIcon, { type: field.type }), field.collection ? `Linked to \u201C${field.collection}\u201D` : "No collection set", field.hasMany ? " \xB7 multiple" : ""));
3201
+ case "array": {
3202
+ const ownPath = fullFieldPath(field, prefix);
3203
+ return /* @__PURE__ */ React17.createElement(PreviewGroup, { field, ownPath, sublabel: `repeating \xB7 ${pluralize(field.fields?.length ?? 0, "field")} per row` }, (field.fields ?? []).map((f) => /* @__PURE__ */ React17.createElement(PreviewField, { key: f.id, field: f, prefix: `${ownPath}[]` })));
3204
+ }
3205
+ case "group": {
3206
+ const ownPath = fullFieldPath(field, prefix);
3207
+ return /* @__PURE__ */ React17.createElement(PreviewGroup, { field, ownPath }, (field.fields ?? []).map((f) => /* @__PURE__ */ React17.createElement(PreviewField, { key: f.id, field: f, prefix: ownPath })));
3208
+ }
3209
+ case "collapsible":
3210
+ return /* @__PURE__ */ React17.createElement(PreviewGroup, { field, ownPath: null }, (field.fields ?? []).map((f) => /* @__PURE__ */ React17.createElement(PreviewField, { key: f.id, field: f, prefix })));
3211
+ case "row":
3212
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-row" }, (field.fields ?? []).map((f) => /* @__PURE__ */ React17.createElement(PreviewField, { key: f.id, field: f, prefix })));
3213
+ case "tabs":
3214
+ return /* @__PURE__ */ React17.createElement(PreviewTabs, { field, prefix });
3215
+ default:
3216
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ React17.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-placeholder" }, "Unsupported preview for \u201C", field.type, "\u201D"));
3217
+ }
3218
+ }
3219
+ function PreviewGroup({ field, ownPath, sublabel, children }) {
3220
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-group" }, /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-group__header" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ React17.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field), sublabel && /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-group__sublabel" }, sublabel)), /* @__PURE__ */ React17.createElement(FieldMeta, { field, path: ownPath }))), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-group__body" }, React17.Children.count(children) > 0 ? children : /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-placeholder" }, "No fields inside yet")));
3221
+ }
3222
+ function PreviewTabs({ field, prefix }) {
3223
+ const [active, setActive] = useState8(0);
3224
+ const tabs = field.tabs ?? [];
3225
+ const tab = tabs[active];
3226
+ const tabPrefix = tab?.name ? prefix ? `${prefix}.${tab.name}` : tab.name : prefix;
3227
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-group" }, /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-group__header" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ React17.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field)), /* @__PURE__ */ React17.createElement(FieldMeta, { field, path: null }))), /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-tabs__list" }, tabs.length === 0 && /* @__PURE__ */ React17.createElement("span", { className: "bb-preview-placeholder", style: { margin: 8 } }, "No tabs yet"), tabs.map((t, i) => /* @__PURE__ */ React17.createElement(
3228
+ "button",
3229
+ {
3230
+ key: t.id ?? i,
3231
+ type: "button",
3232
+ className: `bb-preview-tabs__tab${i === active ? " bb-preview-tabs__tab--active" : ""}`,
3233
+ onClick: () => setActive(i)
3234
+ },
3235
+ t.label
3236
+ ))), tab && /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-group__body" }, tab.name && /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-tabs__path" }, "Nested under ", /* @__PURE__ */ React17.createElement("code", { className: "bb-preview-field__slug" }, tabPrefix)), (tab.fields ?? []).length > 0 ? (tab.fields ?? []).map((f) => /* @__PURE__ */ React17.createElement(PreviewField, { key: f.id, field: f, prefix: tabPrefix })) : /* @__PURE__ */ React17.createElement("div", { className: "bb-preview-placeholder" }, "No fields inside yet")));
3237
+ }
3238
+ function LivePreview() {
3239
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
3240
+ const blocks = useBuilderStore((s) => s.blocks);
3241
+ const block = blocks.find((b) => b.id === activeBlockId);
3242
+ if (!block || block.fields.length === 0) {
3243
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-live-preview bb-live-preview--empty" }, /* @__PURE__ */ React17.createElement(LayoutTemplate, { size: 32, strokeWidth: 1.5, color: "var(--bb-text-subtle)", style: { marginBottom: 16 } }), /* @__PURE__ */ React17.createElement("p", { style: { fontWeight: 600, marginBottom: 8, color: "var(--bb-text)" } }, "Nothing to preview yet"), /* @__PURE__ */ React17.createElement("p", { style: { fontSize: 12, color: "var(--bb-text-subtle)", textAlign: "center", maxWidth: 260 } }, "Add fields from the palette on the left \u2014 this panel updates live as you build."));
3244
+ }
3245
+ return /* @__PURE__ */ React17.createElement("div", { className: "bb-live-preview" }, /* @__PURE__ */ React17.createElement("div", { className: "bb-live-preview__header" }, /* @__PURE__ */ React17.createElement("span", { className: "bb-live-preview__title" }, "Live Preview"), /* @__PURE__ */ React17.createElement("span", { className: "bb-live-preview__meta" }, block.labels?.singular ?? block.slug, " \xB7 ", pluralize(block.fields.length, "field"))), /* @__PURE__ */ React17.createElement("div", { className: "bb-live-preview__body" }, /* @__PURE__ */ React17.createElement("div", { className: "bb-live-preview__page" }, block.fields.map((field) => /* @__PURE__ */ React17.createElement(PreviewField, { key: field.id, field })))));
3246
+ }
3247
+
2369
3248
  // src/block-builder/components/canvas/BuilderShell.tsx
2370
3249
  function BuilderShell({ loadSlug }) {
2371
3250
  const loadBlock = useBuilderStore((s) => s.loadBlock);
2372
3251
  const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
2373
3252
  const setBlockSlug = useBuilderStore((s) => s.setBlockSlug);
2374
3253
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
2375
- const [activeSlug, setActiveSlug] = useState8(loadSlug ?? null);
2376
- const [loading, setLoading] = useState8(!!loadSlug);
2377
- const [loadError, setLoadError] = useState8(null);
2378
- const [showCodePreview, setShowCodePreview] = useState8(false);
2379
- const [versions, setVersions] = useState8([]);
2380
- const [selectedVersionId, setSelectedVersionId] = useState8(null);
2381
- const [blockDefs, setBlockDefs] = useState8([]);
2382
- const [mobilePanelTab, setMobilePanelTab] = useState8("blocks");
2383
- useEffect5(() => {
2384
- fetch("/api/block-definitions?limit=200&depth=0").then((r) => r.json()).then((json) => {
3254
+ const [activeSlug, setActiveSlug] = useState9(loadSlug ?? null);
3255
+ const [loading, setLoading] = useState9(!!loadSlug);
3256
+ const [notification, setNotification] = useState9(null);
3257
+ const [showCodePreview, setShowCodePreview] = useState9(false);
3258
+ const [versions, setVersions] = useState9([]);
3259
+ const [selectedVersionId, setSelectedVersionId] = useState9(null);
3260
+ const [blockDefs, setBlockDefs] = useState9([]);
3261
+ const [mobilePanelTab, setMobilePanelTab] = useState9("blocks");
3262
+ const [isMounted, setIsMounted] = useState9(false);
3263
+ const [previewOpen, setPreviewOpen] = useState9(false);
3264
+ useEffect6(() => {
3265
+ setIsMounted(true);
3266
+ }, []);
3267
+ const refreshBlockDefs = useCallback5(async (reportErrors = false) => {
3268
+ try {
3269
+ const res = await fetch("/api/block-definitions?limit=200&depth=0");
3270
+ const json = await res.json();
2385
3271
  setBlockDefs(
2386
3272
  (json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
2387
3273
  );
2388
- }).catch((err) => {
3274
+ } catch (err) {
2389
3275
  console.error("[block-builder] Failed to load block definitions:", err);
2390
- setLoadError("Could not load block definitions. Please refresh the page.");
2391
- });
3276
+ if (reportErrors) {
3277
+ setNotification({ status: "error", title: "Failed to load block definitions", errors: ["Could not load block definitions. Please refresh the page."] });
3278
+ }
3279
+ }
2392
3280
  }, []);
3281
+ useEffect6(() => {
3282
+ refreshBlockDefs(true);
3283
+ }, [refreshBlockDefs]);
2393
3284
  const loadVersionsForSlug = useCallback5(async (slug) => {
2394
3285
  try {
2395
- const res = await fetch(`/api/block-builder/versions/${encodeURIComponent(slug)}`);
3286
+ const res = await fetch(`/api/block-builder/versions/${encodeURIComponent(slug)}`, {
3287
+ headers: { "X-Block-Builder": "1" }
3288
+ });
2396
3289
  const json = await res.json();
2397
3290
  return json.versions ?? [];
2398
3291
  } catch {
@@ -2401,20 +3294,22 @@ function BuilderShell({ loadSlug }) {
2401
3294
  }, []);
2402
3295
  const loadVersion = useCallback5(async (slug, versionId) => {
2403
3296
  setLoading(true);
2404
- setLoadError(null);
3297
+ setNotification(null);
2405
3298
  const url = versionId ? `/api/block-builder/load/${encodeURIComponent(slug)}?versionId=${encodeURIComponent(versionId)}` : `/api/block-builder/load/${encodeURIComponent(slug)}`;
2406
3299
  try {
2407
- const res = await fetch(url);
3300
+ const res = await fetch(url, {
3301
+ headers: { "X-Block-Builder": "1" }
3302
+ });
2408
3303
  const json = await res.json();
2409
3304
  if (json.block) {
2410
3305
  loadBlock(json.block);
2411
3306
  setVersionMeta(json.versionId ?? null, !(json.isCurrent ?? true));
2412
3307
  setSelectedVersionId(json.versionId ?? null);
2413
3308
  } else {
2414
- setLoadError(json.error ?? "Failed to load block");
3309
+ setNotification({ status: "error", title: "Failed to load block", errors: [json.error ?? "Unknown error"] });
2415
3310
  }
2416
3311
  } catch (err) {
2417
- setLoadError(err instanceof Error ? err.message : "Network error");
3312
+ setNotification({ status: "error", title: "Network error", errors: [err instanceof Error ? err.message : "Could not reach the server."] });
2418
3313
  } finally {
2419
3314
  setLoading(false);
2420
3315
  }
@@ -2424,7 +3319,7 @@ function BuilderShell({ loadSlug }) {
2424
3319
  setBlockSlug(slug);
2425
3320
  setVersions([]);
2426
3321
  setSelectedVersionId(null);
2427
- setLoadError(null);
3322
+ setNotification(null);
2428
3323
  setMobilePanelTab("canvas");
2429
3324
  await loadVersion(slug);
2430
3325
  const list = await loadVersionsForSlug(slug);
@@ -2432,7 +3327,7 @@ function BuilderShell({ loadSlug }) {
2432
3327
  const current = list.find((v) => v.isCurrent) ?? list[0];
2433
3328
  if (current) setSelectedVersionId(current.id);
2434
3329
  }, [loadVersion, loadVersionsForSlug, setBlockSlug]);
2435
- useEffect5(() => {
3330
+ useEffect6(() => {
2436
3331
  if (!loadSlug) return;
2437
3332
  loadBlockBySlug(loadSlug);
2438
3333
  }, [loadSlug]);
@@ -2448,14 +3343,22 @@ function BuilderShell({ loadSlug }) {
2448
3343
  const current = list.find((v) => v.isCurrent) ?? list[0];
2449
3344
  if (current) setSelectedVersionId(current.id);
2450
3345
  }
2451
- async function handleAfterPublish() {
2452
- if (!activeSlug) return;
2453
- const list = await loadVersionsForSlug(activeSlug);
3346
+ async function handleAfterPublish(publishedSlug) {
3347
+ setActiveSlug(publishedSlug);
3348
+ setBlockSlug(publishedSlug);
3349
+ const [list] = await Promise.all([
3350
+ loadVersionsForSlug(publishedSlug),
3351
+ // A first publish creates a definition the picker has never seen.
3352
+ refreshBlockDefs()
3353
+ ]);
2454
3354
  setVersions(list);
2455
3355
  const current = list.find((v) => v.isCurrent) ?? list[0];
2456
- if (current) setSelectedVersionId(current.id);
3356
+ setSelectedVersionId(current ? current.id : null);
2457
3357
  }
2458
- return /* @__PURE__ */ React16.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ React16.createElement(
3358
+ if (!isMounted) {
3359
+ return /* @__PURE__ */ React18.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ React18.createElement("div", { className: "bb-loading-bar" }, "Initializing Builder..."));
3360
+ }
3361
+ return /* @__PURE__ */ React18.createElement(ErrorBoundary, null, /* @__PURE__ */ React18.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ React18.createElement(
2459
3362
  TopBar,
2460
3363
  {
2461
3364
  blockDefs,
@@ -2465,9 +3368,13 @@ function BuilderShell({ loadSlug }) {
2465
3368
  selectedVersionId,
2466
3369
  onVersionSelect: handleVersionSelect,
2467
3370
  onRestoreVersion: handleRestoreVersion,
2468
- onAfterPublish: handleAfterPublish
3371
+ onAfterPublish: handleAfterPublish,
3372
+ notification,
3373
+ onSetNotification: setNotification,
3374
+ previewOpen,
3375
+ onTogglePreview: () => setPreviewOpen((p) => !p)
2469
3376
  }
2470
- ), loading && /* @__PURE__ */ React16.createElement("div", { className: "bb-loading-bar" }, "Loading..."), loadError && /* @__PURE__ */ React16.createElement("div", { className: "bb-error-bar" }, "Error: ", loadError), isReadOnly && !loading && /* @__PURE__ */ React16.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ React16.createElement("span", { className: "bb-readonly-banner__icon" }, "[i]"), /* @__PURE__ */ React16.createElement("span", null, "You are viewing a previous version - read only.", /* @__PURE__ */ React16.createElement(
3377
+ ), loading && /* @__PURE__ */ React18.createElement("div", { className: "bb-loading-bar" }, "Loading..."), isReadOnly && !loading && /* @__PURE__ */ React18.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ React18.createElement("span", { className: "bb-readonly-banner__icon" }, "[i]"), /* @__PURE__ */ React18.createElement("span", null, "You are viewing a previous version - read only.", /* @__PURE__ */ React18.createElement(
2471
3378
  "button",
2472
3379
  {
2473
3380
  type: "button",
@@ -2475,7 +3382,7 @@ function BuilderShell({ loadSlug }) {
2475
3382
  onClick: handleRestoreVersion
2476
3383
  },
2477
3384
  "Switch to latest"
2478
- ))), /* @__PURE__ */ React16.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ React16.createElement(BlockList, null), /* @__PURE__ */ React16.createElement("div", { className: "bb-main__center" }, /* @__PURE__ */ React16.createElement(FieldPalette, null), /* @__PURE__ */ React16.createElement(BuilderCanvas, null)), /* @__PURE__ */ React16.createElement(ConfigPanel, null)), /* @__PURE__ */ React16.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ React16.createElement(
3385
+ ))), /* @__PURE__ */ React18.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ React18.createElement(BlockList, { blockDefs, activeSlug, onBlockSelect: loadBlockBySlug }), /* @__PURE__ */ React18.createElement("div", { className: "bb-main__center", style: { display: "flex", flex: 1 } }, /* @__PURE__ */ React18.createElement(FieldPalette, null), /* @__PURE__ */ React18.createElement(BuilderCanvas, null), previewOpen && /* @__PURE__ */ React18.createElement(LivePreview, null)), /* @__PURE__ */ React18.createElement(ConfigPanel, null)), /* @__PURE__ */ React18.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ React18.createElement(
2479
3386
  "button",
2480
3387
  {
2481
3388
  type: "button",
@@ -2484,12 +3391,12 @@ function BuilderShell({ loadSlug }) {
2484
3391
  },
2485
3392
  showCodePreview ? "v" : ">",
2486
3393
  " Code Preview"
2487
- ), showCodePreview && /* @__PURE__ */ React16.createElement("div", { className: "bb-footer__content" }, /* @__PURE__ */ React16.createElement(CodePreview, null))), /* @__PURE__ */ React16.createElement("nav", { className: "bb-mobile-nav", "aria-label": "Panel navigation" }, [
3394
+ ), showCodePreview && /* @__PURE__ */ React18.createElement("div", { className: "bb-footer__content" }, /* @__PURE__ */ React18.createElement(CodePreview, null))), /* @__PURE__ */ React18.createElement("nav", { className: "bb-mobile-nav", "aria-label": "Panel navigation" }, [
2488
3395
  { id: "blocks", icon: "B", label: "Blocks" },
2489
3396
  { id: "canvas", icon: "[]", label: "Canvas" },
2490
3397
  { id: "palette", icon: "+", label: "Fields" },
2491
3398
  { id: "config", icon: "*", label: "Config" }
2492
- ].map(({ id, icon, label }) => /* @__PURE__ */ React16.createElement(
3399
+ ].map(({ id, icon, label }) => /* @__PURE__ */ React18.createElement(
2493
3400
  "button",
2494
3401
  {
2495
3402
  key: id,
@@ -2497,16 +3404,16 @@ function BuilderShell({ loadSlug }) {
2497
3404
  className: `bb-mobile-nav__tab${mobilePanelTab === id ? " bb-mobile-nav__tab--active" : ""}`,
2498
3405
  onClick: () => setMobilePanelTab(id)
2499
3406
  },
2500
- /* @__PURE__ */ React16.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
3407
+ /* @__PURE__ */ React18.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
2501
3408
  label
2502
- ))));
3409
+ )))));
2503
3410
  }
2504
3411
 
2505
3412
  // src/components/BlockBuilderNavLink/index.tsx
2506
- import React17 from "react";
3413
+ import React19 from "react";
2507
3414
  import Link3 from "next/link";
2508
3415
  function BlockBuilderNavLink() {
2509
- return /* @__PURE__ */ React17.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ React17.createElement(
3416
+ return /* @__PURE__ */ React19.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ React19.createElement(
2510
3417
  Link3,
2511
3418
  {
2512
3419
  href: "/block-builder",
@@ -2532,6 +3439,7 @@ function BlockBuilderNavLink() {
2532
3439
  export {
2533
3440
  BlockBuilderNavLink,
2534
3441
  BlockDataField,
3442
+ BlockVersionSync,
2535
3443
  BuilderShell,
2536
3444
  EditInBuilderButton,
2537
3445
  SchemaBuilderField