@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.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  'use client'
2
+ "use strict";
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -32,6 +33,7 @@ var client_exports = {};
32
33
  __export(client_exports, {
33
34
  BlockBuilderNavLink: () => BlockBuilderNavLink,
34
35
  BlockDataField: () => BlockDataField,
36
+ BlockVersionSync: () => BlockVersionSync,
35
37
  BuilderShell: () => BuilderShell,
36
38
  EditInBuilderButton: () => EditInBuilderButton,
37
39
  SchemaBuilderField: () => SchemaBuilderField
@@ -77,72 +79,207 @@ function MediaPicker({ label, required, value, onChange }) {
77
79
  "x"
78
80
  ))) : /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from Media Library")), /* @__PURE__ */ import_react.default.createElement(ListDrawer, { onSelect: handleSelect }));
79
81
  }
80
- function RelationshipPicker({ label, required, collection, value, onChange }) {
82
+ function toRelDoc(v) {
83
+ if (!v) return null;
84
+ if (typeof v === "object") {
85
+ const o = v;
86
+ if (!o.id) return null;
87
+ return { id: o.id, title: o.title ? String(o.title) : null };
88
+ }
89
+ if (typeof v === "string" || typeof v === "number") return { id: v, title: null };
90
+ return null;
91
+ }
92
+ function RelationshipPicker({ label, required, collection, hasMany = false, value, onChange }) {
81
93
  const changeRef = (0, import_react.useRef)(onChange);
82
94
  const closeRef = (0, import_react.useRef)(() => {
83
95
  });
84
96
  (0, import_react.useEffect)(() => {
85
97
  changeRef.current = onChange;
86
98
  });
99
+ const items = hasMany ? Array.isArray(value) ? value.map(toRelDoc).filter(Boolean) : [] : (() => {
100
+ const d = toRelDoc(value);
101
+ return d ? [d] : [];
102
+ })();
103
+ const itemsRef = (0, import_react.useRef)(items);
104
+ (0, import_react.useEffect)(() => {
105
+ itemsRef.current = items;
106
+ });
87
107
  const handleSelect = (0, import_react.useCallback)(
88
108
  ({ docID, doc }) => {
89
- changeRef.current({
90
- id: docID,
91
- title: doc?.title ?? doc?.name ?? doc?.slug ?? null
92
- });
93
- closeRef.current();
109
+ const title = String(doc?.title ?? doc?.name ?? doc?.slug ?? "") || null;
110
+ const entry = { id: docID, title };
111
+ if (hasMany) {
112
+ const alreadyExists = itemsRef.current.some((i) => String(i.id) === String(docID));
113
+ if (!alreadyExists) changeRef.current([...itemsRef.current, entry]);
114
+ } else {
115
+ changeRef.current(entry);
116
+ closeRef.current();
117
+ }
94
118
  },
95
- []
119
+ [hasMany]
96
120
  );
97
- const [ListDrawer, ListDrawerToggler, { closeDrawer }] = (0, import_ui.useListDrawer)({
121
+ const [ListDrawer, ListDrawerToggler, { closeDrawer, openDrawer }] = (0, import_ui.useListDrawer)({
98
122
  collectionSlugs: [collection]
99
123
  });
100
124
  closeRef.current = closeDrawer;
101
- const relObj = value && typeof value === "object" ? value : null;
102
- const relId = relObj?.id ?? (typeof value === "string" || typeof value === "number" ? value : null);
103
- const relTitle = relObj?.title ? String(relObj.title) : null;
104
- const [fetchedTitle, setFetchedTitle] = (0, import_react.useState)(null);
125
+ const [fetchedTitles, setFetchedTitles] = (0, import_react.useState)({});
126
+ const fetchingRef = (0, import_react.useRef)(/* @__PURE__ */ new Set());
105
127
  (0, import_react.useEffect)(() => {
106
- if (!relId || relTitle) {
107
- setFetchedTitle(null);
108
- return;
128
+ const missing = items.filter(
129
+ (i) => !i.title && !fetchedTitles[String(i.id)] && !fetchingRef.current.has(String(i.id))
130
+ );
131
+ if (missing.length === 0) return;
132
+ missing.forEach((item) => {
133
+ const idStr = String(item.id);
134
+ fetchingRef.current.add(idStr);
135
+ fetch(`/api/${collection}/${idStr}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
136
+ if (doc) {
137
+ const t = doc.title ?? doc.name ?? doc.slug ?? null;
138
+ if (t) setFetchedTitles((prev) => ({ ...prev, [idStr]: String(t) }));
139
+ }
140
+ }).catch((err) => {
141
+ console.error("[Block Builder] Failed to load relation title:", err);
142
+ fetchingRef.current.delete(idStr);
143
+ });
144
+ });
145
+ }, [items, collection]);
146
+ function getTitle(item) {
147
+ return item.title ?? fetchedTitles[String(item.id)] ?? `ID: ${String(item.id)}`;
148
+ }
149
+ function clearOne(e, id) {
150
+ e.stopPropagation();
151
+ if (hasMany) onChange(items.filter((i) => String(i.id) !== String(id)));
152
+ else onChange(null);
153
+ }
154
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement("div", { className: `bdf-rel${hasMany ? " bdf-rel--multi" : ""}` }, /* @__PURE__ */ import_react.default.createElement(
155
+ "div",
156
+ {
157
+ role: "button",
158
+ tabIndex: 0,
159
+ className: "bdf-rel__control",
160
+ onClick: openDrawer,
161
+ onKeyDown: (e) => {
162
+ if (e.key === "Enter" || e.key === " ") openDrawer();
163
+ }
164
+ },
165
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-rel__values" }, items.length === 0 && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-rel__placeholder" }, "Select a value..."), !hasMany && items.length > 0 && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-rel__single" }, getTitle(items[0])), hasMany && items.map((item) => /* @__PURE__ */ import_react.default.createElement("span", { key: String(item.id), className: "bdf-rel__chip" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-rel__chip-label" }, getTitle(item)), /* @__PURE__ */ import_react.default.createElement(
166
+ "button",
167
+ {
168
+ type: "button",
169
+ className: "bdf-rel__chip-remove",
170
+ onClick: (e) => clearOne(e, item.id),
171
+ "aria-label": `Remove ${getTitle(item)}`
172
+ },
173
+ /* @__PURE__ */ import_react.default.createElement("svg", { height: "12", width: "12", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ import_react.default.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" }))
174
+ )))),
175
+ /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-rel__indicators" }, !hasMany && items.length > 0 && /* @__PURE__ */ import_react.default.createElement(
176
+ "button",
177
+ {
178
+ type: "button",
179
+ className: "bdf-rel__clear",
180
+ onClick: (e) => clearOne(e, items[0].id),
181
+ "aria-label": "Clear"
182
+ },
183
+ /* @__PURE__ */ import_react.default.createElement("svg", { height: "16", width: "16", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ import_react.default.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" }))
184
+ ), /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-rel__sep" }), /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-rel__chevron" }, /* @__PURE__ */ import_react.default.createElement("svg", { height: "16", width: "16", viewBox: "0 0 20 20", "aria-hidden": "true", focusable: "false", fill: "currentColor" }, /* @__PURE__ */ import_react.default.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" }))))
185
+ ), hasMany && /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-rel__add", onClick: (e) => e.stopPropagation() }, "+")), /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-rel__hint" }, collection, hasMany ? " \xB7 multiple" : ""), /* @__PURE__ */ import_react.default.createElement(ListDrawer, { onSelect: handleSelect }));
186
+ }
187
+ function JsonField({ label, required, value, onChange }) {
188
+ const [text, setText] = (0, import_react.useState)(
189
+ () => value !== void 0 ? JSON.stringify(value, null, 2) : ""
190
+ );
191
+ const [hasError, setHasError] = (0, import_react.useState)(false);
192
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ import_react.default.createElement(
193
+ "textarea",
194
+ {
195
+ className: "bdf-input bdf-textarea bdf-mono",
196
+ value: text,
197
+ rows: 4,
198
+ onChange: (e) => {
199
+ setText(e.target.value);
200
+ try {
201
+ onChange(JSON.parse(e.target.value));
202
+ setHasError(false);
203
+ } catch {
204
+ setHasError(true);
205
+ }
206
+ }
207
+ }
208
+ ), hasError && /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-error", style: { marginTop: 4 } }, "Invalid JSON \u2014 changes not saved until fixed."));
209
+ }
210
+ function SchemaForm({ schema, value, onChange, depth = 0 }) {
211
+ const set = (0, import_react.useCallback)(
212
+ (key, val) => onChange({ ...value, [key]: val }),
213
+ [value, onChange]
214
+ );
215
+ if (depth > 10) {
216
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-error" }, "Max nesting depth reached (10).");
217
+ }
218
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, schema.map((field) => {
219
+ if (field.type === "row") {
220
+ return /* @__PURE__ */ import_react.default.createElement("div", { key: field.name, className: "bdf-row" }, /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: field.fields, value, onChange, depth: depth + 1 }));
221
+ }
222
+ if (field.type === "collapsible") {
223
+ return /* @__PURE__ */ import_react.default.createElement(CollapsibleSection, { key: field.name, field, value, onChange, depth: depth + 1 });
109
224
  }
110
- fetch(`/api/${collection}/${String(relId)}?depth=0`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
111
- if (doc) {
112
- const t = doc.title ?? doc.name ?? doc.slug ?? null;
113
- setFetchedTitle(t ? String(t) : null);
225
+ if (field.type === "tabs") {
226
+ return /* @__PURE__ */ import_react.default.createElement(TabsSection, { key: field.name, field, value, onChange, depth: depth + 1 });
227
+ }
228
+ return /* @__PURE__ */ import_react.default.createElement(
229
+ FieldInput,
230
+ {
231
+ key: field.name,
232
+ field,
233
+ value: value[field.name],
234
+ onChange: (v) => set(field.name, v),
235
+ depth
114
236
  }
115
- }).catch(() => {
116
- });
117
- }, [relId, relTitle, collection]);
118
- const displayTitle = relTitle ?? fetchedTitle;
119
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", collection, ")")), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-area" }, relId ? /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-selected" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-upload-name" }, displayTitle ?? `ID: ${String(relId)}`), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-actions" }, /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Change"), /* @__PURE__ */ import_react.default.createElement(
237
+ );
238
+ }));
239
+ }
240
+ function CollapsibleSection({
241
+ field,
242
+ value,
243
+ onChange,
244
+ depth
245
+ }) {
246
+ const [open, setOpen] = (0, import_react.useState)(true);
247
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-collapsible" }, /* @__PURE__ */ import_react.default.createElement(
120
248
  "button",
121
249
  {
122
250
  type: "button",
123
- className: "bdf-icon-btn bdf-icon-btn--danger",
124
- title: "Remove",
125
- onClick: () => onChange(null)
251
+ className: "bdf-collapsible__header",
252
+ onClick: () => setOpen((o) => !o)
126
253
  },
127
- "\xD7"
128
- ))) : /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from ", collection)), /* @__PURE__ */ import_react.default.createElement(ListDrawer, { onSelect: handleSelect }));
254
+ /* @__PURE__ */ import_react.default.createElement("span", { className: `bdf-collapsible__caret${open ? " bdf-collapsible__caret--open" : ""}` }, "\u25B8"),
255
+ field.label
256
+ ), open && /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-collapsible__body" }, /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: field.fields, value, onChange, depth })));
129
257
  }
130
- function SchemaForm({ schema, value, onChange }) {
258
+ function TabsSection({
259
+ field,
260
+ value,
261
+ onChange,
262
+ depth
263
+ }) {
264
+ const [activeTab, setActiveTab] = (0, import_react.useState)(0);
131
265
  const set = (0, import_react.useCallback)(
132
266
  (key, val) => onChange({ ...value, [key]: val }),
133
267
  [value, onChange]
134
268
  );
135
- return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, schema.map((field) => /* @__PURE__ */ import_react.default.createElement(
136
- FieldInput,
269
+ const tabs = field.tabs ?? [];
270
+ const tab = tabs[activeTab];
271
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-tabs" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-tabs__list" }, tabs.map((t, i) => /* @__PURE__ */ import_react.default.createElement(
272
+ "button",
137
273
  {
138
- key: field.name,
139
- field,
140
- value: value[field.name],
141
- onChange: (v) => set(field.name, v)
142
- }
143
- )));
274
+ key: t.name ?? t.label ?? i,
275
+ type: "button",
276
+ className: `bdf-tabs__tab${i === activeTab ? " bdf-tabs__tab--active" : ""}`,
277
+ onClick: () => setActiveTab(i)
278
+ },
279
+ t.label
280
+ ))), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-tabs__panel" }, tab && (tab.name ? /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: tab.fields, value: value[tab.name] ?? {}, onChange: (v) => set(tab.name, v), depth }) : /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: tab.fields, value, onChange, depth }))));
144
281
  }
145
- function FieldInput({ field, value, onChange }) {
282
+ function FieldInput({ field, value, onChange, depth }) {
146
283
  const label = field.label ?? field.name;
147
284
  switch (field.type) {
148
285
  case "text":
@@ -152,7 +289,7 @@ function FieldInput({ field, value, onChange }) {
152
289
  "input",
153
290
  {
154
291
  className: "bdf-input",
155
- type: field.type === "email" ? "email" : "text",
292
+ type: field.type === "email" ? "email" : field.type === "url" ? "url" : "text",
156
293
  value: value ?? "",
157
294
  onChange: (e) => onChange(e.target.value)
158
295
  }
@@ -205,7 +342,7 @@ function FieldInput({ field, value, onChange }) {
205
342
  className: "bdf-input",
206
343
  type: "number",
207
344
  value: numVal,
208
- onChange: (e) => onChange(e.target.value === "" ? void 0 : e.target.valueAsNumber)
345
+ onChange: (e) => onChange(e.target.value === "" ? null : e.target.valueAsNumber)
209
346
  }
210
347
  ));
211
348
  }
@@ -265,33 +402,24 @@ function FieldInput({ field, value, onChange }) {
265
402
  }
266
403
  );
267
404
  case "relationship": {
268
- const collection = field.collection ?? "media";
405
+ const relField = field;
406
+ if (!relField.collection) {
407
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-error" }, "Relationship field ", /* @__PURE__ */ import_react.default.createElement("strong", null, field.name), " has no ", /* @__PURE__ */ import_react.default.createElement("code", null, "collection"), " defined in its schema."));
408
+ }
269
409
  return /* @__PURE__ */ import_react.default.createElement(
270
410
  RelationshipPicker,
271
411
  {
272
412
  label,
273
413
  required: field.required,
274
- collection,
414
+ collection: relField.collection,
415
+ hasMany: relField.hasMany ?? false,
275
416
  value,
276
417
  onChange
277
418
  }
278
419
  );
279
420
  }
280
421
  case "json":
281
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ import_react.default.createElement(
282
- "textarea",
283
- {
284
- className: "bdf-input bdf-textarea bdf-mono",
285
- value: value !== void 0 ? JSON.stringify(value, null, 2) : "",
286
- rows: 4,
287
- onChange: (e) => {
288
- try {
289
- onChange(JSON.parse(e.target.value));
290
- } catch {
291
- }
292
- }
293
- }
294
- ));
422
+ return /* @__PURE__ */ import_react.default.createElement(JsonField, { label, required: field.required, value, onChange });
295
423
  case "array": {
296
424
  const rows = Array.isArray(value) ? value : [];
297
425
  const subFields = field.fields ?? [];
@@ -304,7 +432,8 @@ function FieldInput({ field, value, onChange }) {
304
432
  const next = [...rows];
305
433
  next[i] = updated;
306
434
  onChange(next);
307
- }
435
+ },
436
+ depth: depth + 1
308
437
  }
309
438
  ), /* @__PURE__ */ import_react.default.createElement(
310
439
  "button",
@@ -328,7 +457,7 @@ function FieldInput({ field, value, onChange }) {
328
457
  case "group": {
329
458
  const groupVal = value ?? {};
330
459
  const subFields = field.fields ?? [];
331
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__header" }, label), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__body" }, /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: subFields, value: groupVal, onChange }))));
460
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__header" }, label), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__body" }, /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: subFields, value: groupVal, onChange, depth: depth + 1 }))));
332
461
  }
333
462
  default:
334
463
  return /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 12, color: "var(--theme-elevation-400)", fontFamily: "var(--font-body)", padding: "4px 0" } }, "Unsupported field type: ", /* @__PURE__ */ import_react.default.createElement("strong", null, field.type), " (", label, ")");
@@ -349,6 +478,10 @@ function BlockDataField({ path }) {
349
478
  setError(null);
350
479
  return;
351
480
  }
481
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(versionId))) {
482
+ setError("Invalid version ID format");
483
+ return;
484
+ }
352
485
  setLoading(true);
353
486
  setError(null);
354
487
  fetch(`/api/block-definition-versions/${versionId}?depth=0`, { credentials: "same-origin" }).then((r) => {
@@ -387,15 +520,47 @@ function BlockDataField({ path }) {
387
520
  )));
388
521
  }
389
522
 
390
- // src/components/SchemaBuilderField/index.tsx
391
- var import_react5 = __toESM(require("react"), 1);
523
+ // src/components/BlockVersionSync/index.tsx
524
+ var import_react2 = require("react");
392
525
  var import_ui2 = require("@payloadcms/ui");
526
+ function BlockVersionSync({ path }) {
527
+ const blockVersionPath = path.replace(/\.blockDefinition$/, ".blockVersion");
528
+ const { setValue: setVersion } = (0, import_ui2.useField)({ path: blockVersionPath });
529
+ const blockDefValue = (0, import_ui2.useFormFields)(([fields]) => fields[path]?.value);
530
+ const prevDefIdRef = (0, import_react2.useRef)(null);
531
+ (0, import_react2.useEffect)(() => {
532
+ const defId = blockDefValue && typeof blockDefValue === "object" ? blockDefValue.id : typeof blockDefValue === "string" || typeof blockDefValue === "number" ? blockDefValue : null;
533
+ if (defId === prevDefIdRef.current) return;
534
+ prevDefIdRef.current = defId;
535
+ if (!defId) {
536
+ setVersion(null);
537
+ return;
538
+ }
539
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(defId))) {
540
+ return;
541
+ }
542
+ fetch(`/api/block-definitions/${String(defId)}?depth=1`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
543
+ if (!doc) return;
544
+ const currentVersion = doc.currentVersion;
545
+ if (!currentVersion) return;
546
+ const versionId = typeof currentVersion === "object" ? currentVersion.id : currentVersion;
547
+ if (versionId) setVersion(versionId);
548
+ }).catch((err) => {
549
+ console.error("[Block Builder] BlockVersionSync fetch error:", err);
550
+ });
551
+ }, [blockDefValue, setVersion]);
552
+ return null;
553
+ }
554
+
555
+ // src/components/SchemaBuilderField/index.tsx
556
+ var import_react7 = __toESM(require("react"), 1);
557
+ var import_ui3 = require("@payloadcms/ui");
393
558
 
394
559
  // src/components/SchemaBuilderField/FieldRow.tsx
395
- var import_react4 = __toESM(require("react"), 1);
560
+ var import_react5 = __toESM(require("react"), 1);
396
561
 
397
562
  // src/components/SchemaBuilderField/OptionsEditor.tsx
398
- var import_react2 = __toESM(require("react"), 1);
563
+ var import_react3 = __toESM(require("react"), 1);
399
564
  function OptionsEditor({ options, onChange, readOnly }) {
400
565
  function addOption() {
401
566
  onChange([...options, { label: "", value: "" }]);
@@ -406,7 +571,7 @@ function OptionsEditor({ options, onChange, readOnly }) {
406
571
  function removeOption(index) {
407
572
  onChange(options.filter((_, i) => i !== index));
408
573
  }
409
- return /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ import_react2.default.createElement(
574
+ return /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ import_react3.default.createElement(
410
575
  "span",
411
576
  {
412
577
  style: {
@@ -421,8 +586,8 @@ function OptionsEditor({ options, onChange, readOnly }) {
421
586
  }
422
587
  },
423
588
  "Options",
424
- options.length > 0 && /* @__PURE__ */ import_react2.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", options.length, ")")
425
- ), options.length === 0 && /* @__PURE__ */ import_react2.default.createElement(
589
+ options.length > 0 && /* @__PURE__ */ import_react3.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", options.length, ")")
590
+ ), options.length === 0 && /* @__PURE__ */ import_react3.default.createElement(
426
591
  "p",
427
592
  {
428
593
  style: {
@@ -434,7 +599,7 @@ function OptionsEditor({ options, onChange, readOnly }) {
434
599
  }
435
600
  },
436
601
  "No options yet."
437
- ), options.map((opt, i) => /* @__PURE__ */ import_react2.default.createElement("div", { key: i, className: "sbf-options-row" }, /* @__PURE__ */ import_react2.default.createElement(
602
+ ), options.map((opt, i) => /* @__PURE__ */ import_react3.default.createElement("div", { key: i, className: "sbf-options-row" }, /* @__PURE__ */ import_react3.default.createElement(
438
603
  "input",
439
604
  {
440
605
  className: "sbf-input",
@@ -444,7 +609,7 @@ function OptionsEditor({ options, onChange, readOnly }) {
444
609
  disabled: readOnly,
445
610
  onChange: (e) => updateOption(i, "label", e.target.value)
446
611
  }
447
- ), /* @__PURE__ */ import_react2.default.createElement(
612
+ ), /* @__PURE__ */ import_react3.default.createElement(
448
613
  "input",
449
614
  {
450
615
  className: "sbf-input",
@@ -454,7 +619,7 @@ function OptionsEditor({ options, onChange, readOnly }) {
454
619
  disabled: readOnly,
455
620
  onChange: (e) => updateOption(i, "value", e.target.value)
456
621
  }
457
- ), !readOnly && /* @__PURE__ */ import_react2.default.createElement(
622
+ ), !readOnly && /* @__PURE__ */ import_react3.default.createElement(
458
623
  "button",
459
624
  {
460
625
  type: "button",
@@ -463,37 +628,37 @@ function OptionsEditor({ options, onChange, readOnly }) {
463
628
  onClick: () => removeOption(i)
464
629
  },
465
630
  "x"
466
- ))), !readOnly && /* @__PURE__ */ import_react2.default.createElement(
631
+ ))), !readOnly && /* @__PURE__ */ import_react3.default.createElement(
467
632
  "button",
468
633
  {
469
634
  type: "button",
470
635
  className: "sbf-add-btn sbf-add-btn--small",
471
636
  onClick: addOption
472
637
  },
473
- /* @__PURE__ */ import_react2.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
638
+ /* @__PURE__ */ import_react3.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
474
639
  "Add Option"
475
640
  ));
476
641
  }
477
642
 
478
643
  // src/components/SchemaBuilderField/NestedFieldsEditor.tsx
479
- var import_react3 = __toESM(require("react"), 1);
644
+ var import_react4 = __toESM(require("react"), 1);
480
645
  function NestedFieldsEditor({ fields, onChange, readOnly, depth = 1 }) {
481
- const addField = (0, import_react3.useCallback)(() => {
646
+ const addField = (0, import_react4.useCallback)(() => {
482
647
  onChange([...fields, { name: "", type: "text", label: "", required: false }]);
483
648
  }, [fields, onChange]);
484
- const updateField = (0, import_react3.useCallback)(
649
+ const updateField = (0, import_react4.useCallback)(
485
650
  (index, updated) => {
486
651
  onChange(fields.map((f, i) => i === index ? updated : f));
487
652
  },
488
653
  [fields, onChange]
489
654
  );
490
- const removeField = (0, import_react3.useCallback)(
655
+ const removeField = (0, import_react4.useCallback)(
491
656
  (index) => {
492
657
  onChange(fields.filter((_, i) => i !== index));
493
658
  },
494
659
  [fields, onChange]
495
660
  );
496
- const moveField = (0, import_react3.useCallback)(
661
+ const moveField = (0, import_react4.useCallback)(
497
662
  (index, dir) => {
498
663
  const next = [...fields];
499
664
  const target = index + dir;
@@ -503,7 +668,7 @@ function NestedFieldsEditor({ fields, onChange, readOnly, depth = 1 }) {
503
668
  },
504
669
  [fields, onChange]
505
670
  );
506
- return /* @__PURE__ */ import_react3.default.createElement("div", { className: "sbf-nested" }, /* @__PURE__ */ import_react3.default.createElement(
671
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-nested" }, /* @__PURE__ */ import_react4.default.createElement(
507
672
  "span",
508
673
  {
509
674
  style: {
@@ -518,8 +683,8 @@ function NestedFieldsEditor({ fields, onChange, readOnly, depth = 1 }) {
518
683
  }
519
684
  },
520
685
  "Nested fields",
521
- fields.length > 0 && /* @__PURE__ */ import_react3.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", fields.length, ")")
522
- ), fields.length === 0 && /* @__PURE__ */ import_react3.default.createElement(
686
+ fields.length > 0 && /* @__PURE__ */ import_react4.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", fields.length, ")")
687
+ ), fields.length === 0 && /* @__PURE__ */ import_react4.default.createElement(
523
688
  "p",
524
689
  {
525
690
  style: {
@@ -531,7 +696,7 @@ function NestedFieldsEditor({ fields, onChange, readOnly, depth = 1 }) {
531
696
  }
532
697
  },
533
698
  "No nested fields yet."
534
- ), fields.length > 0 && /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react3.default.createElement(
699
+ ), fields.length > 0 && /* @__PURE__ */ import_react4.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react4.default.createElement(
535
700
  FieldRow,
536
701
  {
537
702
  key: i,
@@ -545,14 +710,14 @@ function NestedFieldsEditor({ fields, onChange, readOnly, depth = 1 }) {
545
710
  readOnly,
546
711
  depth
547
712
  }
548
- ))), !readOnly && /* @__PURE__ */ import_react3.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react3.default.createElement(
713
+ ))), !readOnly && /* @__PURE__ */ import_react4.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react4.default.createElement(
549
714
  "button",
550
715
  {
551
716
  type: "button",
552
717
  className: "sbf-add-btn sbf-add-btn--small",
553
718
  onClick: addField
554
719
  },
555
- /* @__PURE__ */ import_react3.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
720
+ /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
556
721
  "Add Nested Field"
557
722
  )));
558
723
  }
@@ -599,10 +764,10 @@ var TYPE_BADGE_COLORS = {
599
764
  blocks: "var(--theme-elevation-150)"
600
765
  };
601
766
  function FieldLabel({ children, required }) {
602
- return /* @__PURE__ */ import_react4.default.createElement("label", { className: `sbf-label${required ? " sbf-label--required" : ""}` }, children);
767
+ return /* @__PURE__ */ import_react5.default.createElement("label", { className: `sbf-label${required ? " sbf-label--required" : ""}` }, children);
603
768
  }
604
769
  function FieldWrap({ children }) {
605
- return /* @__PURE__ */ import_react4.default.createElement("div", { style: { display: "flex", flexDirection: "column" } }, children);
770
+ return /* @__PURE__ */ import_react5.default.createElement("div", { style: { display: "flex", flexDirection: "column" } }, children);
606
771
  }
607
772
  function FieldRow({
608
773
  index,
@@ -614,9 +779,9 @@ function FieldRow({
614
779
  readOnly,
615
780
  depth = 0
616
781
  }) {
617
- const [expanded, setExpanded] = (0, import_react4.useState)(false);
618
- const [showAdmin, setShowAdmin] = (0, import_react4.useState)(false);
619
- const [showConditions, setShowConditions] = (0, import_react4.useState)(false);
782
+ const [expanded, setExpanded] = (0, import_react5.useState)(false);
783
+ const [showAdmin, setShowAdmin] = (0, import_react5.useState)(false);
784
+ const [showConditions, setShowConditions] = (0, import_react5.useState)(false);
620
785
  const maxDepth = 3;
621
786
  const canNest = depth < maxDepth;
622
787
  const needsOptions = field.type === "select" || field.type === "multiselect";
@@ -625,7 +790,7 @@ function FieldRow({
625
790
  onChange({ ...field, [key]: val });
626
791
  }
627
792
  const badgeBg = TYPE_BADGE_COLORS[field.type] ?? "var(--theme-elevation-150)";
628
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: `sbf-row${expanded ? "" : " sbf-row--collapsed"}` }, /* @__PURE__ */ import_react4.default.createElement(
793
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: `sbf-row${expanded ? "" : " sbf-row--collapsed"}` }, /* @__PURE__ */ import_react5.default.createElement(
629
794
  "div",
630
795
  {
631
796
  className: "sbf-row__header",
@@ -633,7 +798,7 @@ function FieldRow({
633
798
  "aria-expanded": expanded,
634
799
  onClick: () => setExpanded((p) => !p)
635
800
  },
636
- /* @__PURE__ */ import_react4.default.createElement(
801
+ /* @__PURE__ */ import_react5.default.createElement(
637
802
  "span",
638
803
  {
639
804
  style: {
@@ -650,9 +815,9 @@ function FieldRow({
650
815
  },
651
816
  ">"
652
817
  ),
653
- /* @__PURE__ */ import_react4.default.createElement("span", { className: `sbf-row__label${!field.name ? " sbf-row__label--empty" : ""}` }, field.name || "unnamed field"),
654
- field.required && /* @__PURE__ */ import_react4.default.createElement("span", { style: { color: "var(--theme-error-500, #ef4444)", fontSize: 16, lineHeight: 1, flexShrink: 0 } }, "*"),
655
- /* @__PURE__ */ import_react4.default.createElement(
818
+ /* @__PURE__ */ import_react5.default.createElement("span", { className: `sbf-row__label${!field.name ? " sbf-row__label--empty" : ""}` }, field.name || "unnamed field"),
819
+ field.required && /* @__PURE__ */ import_react5.default.createElement("span", { style: { color: "var(--theme-error-500, #ef4444)", fontSize: 16, lineHeight: 1, flexShrink: 0 } }, "*"),
820
+ /* @__PURE__ */ import_react5.default.createElement(
656
821
  "span",
657
822
  {
658
823
  className: "sbf-type-badge",
@@ -660,15 +825,15 @@ function FieldRow({
660
825
  },
661
826
  field.type
662
827
  ),
663
- !readOnly && /* @__PURE__ */ import_react4.default.createElement(
828
+ !readOnly && /* @__PURE__ */ import_react5.default.createElement(
664
829
  "div",
665
830
  {
666
831
  style: { display: "flex", gap: 2, flexShrink: 0, alignItems: "center" },
667
832
  onClick: (e) => e.stopPropagation()
668
833
  },
669
- onMoveUp && /* @__PURE__ */ import_react4.default.createElement("button", { type: "button", className: "sbf-icon-btn", onClick: onMoveUp, title: "Move up" }, "^"),
670
- onMoveDown && /* @__PURE__ */ import_react4.default.createElement("button", { type: "button", className: "sbf-icon-btn", onClick: onMoveDown, title: "Move down" }, "v"),
671
- /* @__PURE__ */ import_react4.default.createElement(
834
+ onMoveUp && /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", className: "sbf-icon-btn", onClick: onMoveUp, title: "Move up" }, "^"),
835
+ onMoveDown && /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", className: "sbf-icon-btn", onClick: onMoveDown, title: "Move down" }, "v"),
836
+ /* @__PURE__ */ import_react5.default.createElement(
672
837
  "button",
673
838
  {
674
839
  type: "button",
@@ -679,7 +844,7 @@ function FieldRow({
679
844
  "x"
680
845
  )
681
846
  )
682
- ), expanded && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-row__content" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, { required: true }, "Field Name"), /* @__PURE__ */ import_react4.default.createElement(
847
+ ), expanded && /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-row__content" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, { required: true }, "Field Name"), /* @__PURE__ */ import_react5.default.createElement(
683
848
  "input",
684
849
  {
685
850
  className: "sbf-input",
@@ -689,7 +854,7 @@ function FieldRow({
689
854
  disabled: readOnly,
690
855
  onChange: (e) => set("name", e.target.value)
691
856
  }
692
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, { required: true }, "Type"), /* @__PURE__ */ import_react4.default.createElement(
857
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, { required: true }, "Type"), /* @__PURE__ */ import_react5.default.createElement(
693
858
  "select",
694
859
  {
695
860
  className: "sbf-input sbf-select",
@@ -697,8 +862,8 @@ function FieldRow({
697
862
  disabled: readOnly,
698
863
  onChange: (e) => set("type", e.target.value)
699
864
  },
700
- ALL_FIELD_TYPES.map((t) => /* @__PURE__ */ import_react4.default.createElement("option", { key: t, value: t }, t))
701
- ))), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Label"), /* @__PURE__ */ import_react4.default.createElement(
865
+ ALL_FIELD_TYPES.map((t) => /* @__PURE__ */ import_react5.default.createElement("option", { key: t, value: t }, t))
866
+ ))), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Label"), /* @__PURE__ */ import_react5.default.createElement(
702
867
  "input",
703
868
  {
704
869
  className: "sbf-input",
@@ -708,7 +873,7 @@ function FieldRow({
708
873
  disabled: readOnly,
709
874
  onChange: (e) => set("label", e.target.value)
710
875
  }
711
- )), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
876
+ )), /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react5.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react5.default.createElement(
712
877
  "input",
713
878
  {
714
879
  type: "checkbox",
@@ -716,7 +881,7 @@ function FieldRow({
716
881
  disabled: readOnly,
717
882
  onChange: (e) => set("required", e.target.checked)
718
883
  }
719
- ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Required"))), field.type === "number" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Min"), /* @__PURE__ */ import_react4.default.createElement(
884
+ ), /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-checkbox-label" }, "Required"))), field.type === "number" && /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Min"), /* @__PURE__ */ import_react5.default.createElement(
720
885
  "input",
721
886
  {
722
887
  className: "sbf-input",
@@ -725,7 +890,7 @@ function FieldRow({
725
890
  disabled: readOnly,
726
891
  onChange: (e) => set("min", e.target.value === "" ? void 0 : e.target.valueAsNumber)
727
892
  }
728
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Max"), /* @__PURE__ */ import_react4.default.createElement(
893
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Max"), /* @__PURE__ */ import_react5.default.createElement(
729
894
  "input",
730
895
  {
731
896
  className: "sbf-input",
@@ -734,7 +899,7 @@ function FieldRow({
734
899
  disabled: readOnly,
735
900
  onChange: (e) => set("max", e.target.value === "" ? void 0 : e.target.valueAsNumber)
736
901
  }
737
- ))), (field.type === "text" || field.type === "textarea") && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Min Length"), /* @__PURE__ */ import_react4.default.createElement(
902
+ ))), (field.type === "text" || field.type === "textarea") && /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Min Length"), /* @__PURE__ */ import_react5.default.createElement(
738
903
  "input",
739
904
  {
740
905
  className: "sbf-input",
@@ -743,7 +908,7 @@ function FieldRow({
743
908
  disabled: readOnly,
744
909
  onChange: (e) => set("minLength", e.target.value === "" ? void 0 : e.target.valueAsNumber)
745
910
  }
746
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Max Length"), /* @__PURE__ */ import_react4.default.createElement(
911
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Max Length"), /* @__PURE__ */ import_react5.default.createElement(
747
912
  "input",
748
913
  {
749
914
  className: "sbf-input",
@@ -752,7 +917,7 @@ function FieldRow({
752
917
  disabled: readOnly,
753
918
  onChange: (e) => set("maxLength", e.target.value === "" ? void 0 : e.target.valueAsNumber)
754
919
  }
755
- ))), field.type === "array" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Min Rows"), /* @__PURE__ */ import_react4.default.createElement(
920
+ ))), field.type === "array" && /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Min Rows"), /* @__PURE__ */ import_react5.default.createElement(
756
921
  "input",
757
922
  {
758
923
  className: "sbf-input",
@@ -761,7 +926,7 @@ function FieldRow({
761
926
  disabled: readOnly,
762
927
  onChange: (e) => set("minRows", e.target.value === "" ? void 0 : e.target.valueAsNumber)
763
928
  }
764
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Max Rows"), /* @__PURE__ */ import_react4.default.createElement(
929
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Max Rows"), /* @__PURE__ */ import_react5.default.createElement(
765
930
  "input",
766
931
  {
767
932
  className: "sbf-input",
@@ -770,7 +935,7 @@ function FieldRow({
770
935
  disabled: readOnly,
771
936
  onChange: (e) => set("maxRows", e.target.value === "" ? void 0 : e.target.valueAsNumber)
772
937
  }
773
- ))), field.type === "relationship" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, { required: true }, "Collection slug"), /* @__PURE__ */ import_react4.default.createElement(
938
+ ))), field.type === "relationship" && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, { required: true }, "Collection slug"), /* @__PURE__ */ import_react5.default.createElement(
774
939
  "input",
775
940
  {
776
941
  className: "sbf-input",
@@ -780,7 +945,7 @@ function FieldRow({
780
945
  disabled: readOnly,
781
946
  onChange: (e) => set("collection", e.target.value)
782
947
  }
783
- )), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
948
+ )), /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react5.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react5.default.createElement(
784
949
  "input",
785
950
  {
786
951
  type: "checkbox",
@@ -788,14 +953,14 @@ function FieldRow({
788
953
  disabled: readOnly,
789
954
  onChange: (e) => set("hasMany", e.target.checked)
790
955
  }
791
- ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Has Many")))), needsOptions && /* @__PURE__ */ import_react4.default.createElement(
956
+ ), /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-checkbox-label" }, "Has Many")))), needsOptions && /* @__PURE__ */ import_react5.default.createElement(
792
957
  OptionsEditor,
793
958
  {
794
959
  options: field.options ?? [],
795
960
  onChange: (opts) => set("options", opts),
796
961
  readOnly
797
962
  }
798
- ), needsNested && canNest && /* @__PURE__ */ import_react4.default.createElement(
963
+ ), needsNested && canNest && /* @__PURE__ */ import_react5.default.createElement(
799
964
  NestedFieldsEditor,
800
965
  {
801
966
  fields: field.fields ?? [],
@@ -803,7 +968,7 @@ function FieldRow({
803
968
  readOnly,
804
969
  depth: depth + 1
805
970
  }
806
- ), needsNested && !canNest && /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-description" }, "Maximum nesting depth (", depth, ") reached."), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section" }, /* @__PURE__ */ import_react4.default.createElement(
971
+ ), needsNested && !canNest && /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-description" }, "Maximum nesting depth (", depth, ") reached."), /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-section" }, /* @__PURE__ */ import_react5.default.createElement(
807
972
  "button",
808
973
  {
809
974
  type: "button",
@@ -811,8 +976,8 @@ function FieldRow({
811
976
  onClick: () => setShowAdmin((p) => !p)
812
977
  },
813
978
  "Admin & UI settings",
814
- /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-section__toggle-icon" }, ">")
815
- ), showAdmin && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section__body" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Description"), /* @__PURE__ */ import_react4.default.createElement(
979
+ /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-section__toggle-icon" }, ">")
980
+ ), showAdmin && /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-section__body" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Description"), /* @__PURE__ */ import_react5.default.createElement(
816
981
  "input",
817
982
  {
818
983
  className: "sbf-input",
@@ -822,7 +987,7 @@ function FieldRow({
822
987
  disabled: readOnly,
823
988
  onChange: (e) => set("admin", { ...field.admin, description: e.target.value })
824
989
  }
825
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Placeholder"), /* @__PURE__ */ import_react4.default.createElement(
990
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Placeholder"), /* @__PURE__ */ import_react5.default.createElement(
826
991
  "input",
827
992
  {
828
993
  className: "sbf-input",
@@ -832,7 +997,7 @@ function FieldRow({
832
997
  disabled: readOnly,
833
998
  onChange: (e) => set("admin", { ...field.admin, placeholder: e.target.value })
834
999
  }
835
- )), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
1000
+ )), /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react5.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react5.default.createElement(
836
1001
  "input",
837
1002
  {
838
1003
  type: "checkbox",
@@ -840,7 +1005,7 @@ function FieldRow({
840
1005
  disabled: readOnly,
841
1006
  onChange: (e) => set("admin", { ...field.admin, readOnly: e.target.checked })
842
1007
  }
843
- ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Read Only")), /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
1008
+ ), /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-checkbox-label" }, "Read Only")), /* @__PURE__ */ import_react5.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react5.default.createElement(
844
1009
  "input",
845
1010
  {
846
1011
  type: "checkbox",
@@ -848,7 +1013,7 @@ function FieldRow({
848
1013
  disabled: readOnly,
849
1014
  onChange: (e) => set("admin", { ...field.admin, hidden: e.target.checked })
850
1015
  }
851
- ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Hidden"))))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section" }, /* @__PURE__ */ import_react4.default.createElement(
1016
+ ), /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-checkbox-label" }, "Hidden"))))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-section" }, /* @__PURE__ */ import_react5.default.createElement(
852
1017
  "button",
853
1018
  {
854
1019
  type: "button",
@@ -856,7 +1021,7 @@ function FieldRow({
856
1021
  onClick: () => setShowConditions((p) => !p)
857
1022
  },
858
1023
  "Conditional logic",
859
- (field.conditions?.length ?? 0) > 0 && /* @__PURE__ */ import_react4.default.createElement(
1024
+ (field.conditions?.length ?? 0) > 0 && /* @__PURE__ */ import_react5.default.createElement(
860
1025
  "span",
861
1026
  {
862
1027
  style: {
@@ -870,8 +1035,8 @@ function FieldRow({
870
1035
  " rule",
871
1036
  field.conditions.length !== 1 ? "s" : ""
872
1037
  ),
873
- /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-section__toggle-icon" }, ">")
874
- ), showConditions && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section__body" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Condition Mode"), /* @__PURE__ */ import_react4.default.createElement(
1038
+ /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-section__toggle-icon" }, ">")
1039
+ ), showConditions && /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-section__body" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Condition Mode"), /* @__PURE__ */ import_react5.default.createElement(
875
1040
  "select",
876
1041
  {
877
1042
  className: "sbf-input sbf-select",
@@ -880,9 +1045,9 @@ function FieldRow({
880
1045
  onChange: (e) => set("conditionMode", e.target.value),
881
1046
  style: { width: "auto", minWidth: 240 }
882
1047
  },
883
- /* @__PURE__ */ import_react4.default.createElement("option", { value: "AND" }, "AND - all conditions must match"),
884
- /* @__PURE__ */ import_react4.default.createElement("option", { value: "OR" }, "OR - any condition must match")
885
- )), (field.conditions ?? []).map((cond, ci) => /* @__PURE__ */ import_react4.default.createElement("div", { key: ci, className: "sbf-conditions-grid" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Field"), /* @__PURE__ */ import_react4.default.createElement(
1048
+ /* @__PURE__ */ import_react5.default.createElement("option", { value: "AND" }, "AND - all conditions must match"),
1049
+ /* @__PURE__ */ import_react5.default.createElement("option", { value: "OR" }, "OR - any condition must match")
1050
+ )), (field.conditions ?? []).map((cond, ci) => /* @__PURE__ */ import_react5.default.createElement("div", { key: ci, className: "sbf-conditions-grid" }, /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Field"), /* @__PURE__ */ import_react5.default.createElement(
886
1051
  "input",
887
1052
  {
888
1053
  className: "sbf-input",
@@ -896,7 +1061,7 @@ function FieldRow({
896
1061
  set("conditions", next);
897
1062
  }
898
1063
  }
899
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Operator"), /* @__PURE__ */ import_react4.default.createElement(
1064
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Operator"), /* @__PURE__ */ import_react5.default.createElement(
900
1065
  "select",
901
1066
  {
902
1067
  className: "sbf-input sbf-select",
@@ -908,8 +1073,8 @@ function FieldRow({
908
1073
  set("conditions", next);
909
1074
  }
910
1075
  },
911
- ["equals", "not_equals", "contains", "not_contains", "greater_than", "less_than", "in", "not_in", "exists", "empty"].map((op) => /* @__PURE__ */ import_react4.default.createElement("option", { key: op, value: op }, op.replace(/_/g, " ")))
912
- )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Value"), /* @__PURE__ */ import_react4.default.createElement(
1076
+ ["equals", "not_equals", "contains", "not_contains", "greater_than", "less_than", "in", "not_in", "exists", "empty"].map((op) => /* @__PURE__ */ import_react5.default.createElement("option", { key: op, value: op }, op.replace(/_/g, " ")))
1077
+ )), /* @__PURE__ */ import_react5.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react5.default.createElement(FieldLabel, null, "Value"), /* @__PURE__ */ import_react5.default.createElement(
913
1078
  "input",
914
1079
  {
915
1080
  className: "sbf-input",
@@ -922,7 +1087,7 @@ function FieldRow({
922
1087
  set("conditions", next);
923
1088
  }
924
1089
  }
925
- )), !readOnly && /* @__PURE__ */ import_react4.default.createElement(
1090
+ )), !readOnly && /* @__PURE__ */ import_react5.default.createElement(
926
1091
  "button",
927
1092
  {
928
1093
  type: "button",
@@ -934,7 +1099,7 @@ function FieldRow({
934
1099
  }
935
1100
  },
936
1101
  "x"
937
- ))), !readOnly && /* @__PURE__ */ import_react4.default.createElement(
1102
+ ))), !readOnly && /* @__PURE__ */ import_react5.default.createElement(
938
1103
  "button",
939
1104
  {
940
1105
  type: "button",
@@ -946,136 +1111,80 @@ function FieldRow({
946
1111
  ]);
947
1112
  }
948
1113
  },
949
- /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
1114
+ /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
950
1115
  "Add Condition"
951
1116
  )))));
952
1117
  }
953
1118
 
954
- // src/components/SchemaBuilderField/index.tsx
955
- function parseSchema(raw) {
956
- try {
957
- const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
958
- if (!parsed) return [];
959
- if (Array.isArray(parsed)) return parsed;
960
- if (parsed?.fields && Array.isArray(parsed.fields)) return parsed.fields;
961
- } catch {
962
- }
963
- return [];
964
- }
965
- function SchemaBuilderField({ path, readOnly }) {
966
- const { value, setValue } = (0, import_ui2.useField)({ path });
967
- const setValueRef = (0, import_react5.useRef)(setValue);
968
- (0, import_react5.useEffect)(() => {
969
- setValueRef.current = setValue;
970
- });
971
- const [fields, setFields] = (0, import_react5.useState)(() => parseSchema(value));
972
- const hasExistingValue = value !== void 0 && value !== null;
973
- const [hydrated, setHydrated] = (0, import_react5.useState)(!hasExistingValue);
974
- (0, import_react5.useEffect)(() => {
975
- if (hydrated) return;
976
- if (value !== void 0 && value !== null) {
977
- setFields(parseSchema(value));
978
- setHydrated(true);
979
- }
980
- }, [value, hydrated]);
981
- (0, import_react5.useEffect)(() => {
982
- if (!hydrated) return;
983
- setValueRef.current({ fields });
984
- }, [fields, hydrated]);
985
- const addField = (0, import_react5.useCallback)(() => {
986
- setFields((prev) => [
987
- ...prev,
988
- { name: "", type: "text", label: "", required: false }
989
- ]);
990
- }, []);
991
- const updateField = (0, import_react5.useCallback)((index, updated) => {
992
- setFields((prev) => prev.map((f, i) => i === index ? updated : f));
993
- }, []);
994
- const removeField = (0, import_react5.useCallback)((index) => {
995
- setFields((prev) => prev.filter((_, i) => i !== index));
996
- }, []);
997
- const moveField = (0, import_react5.useCallback)((index, dir) => {
998
- setFields((prev) => {
999
- const next = [...prev];
1000
- const target = index + dir;
1001
- if (target < 0 || target >= next.length) return prev;
1002
- [next[index], next[target]] = [next[target], next[index]];
1003
- return next;
1004
- });
1005
- }, []);
1006
- return /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-section-heading" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-section-label" }, "Fields", fields.length > 0 && /* @__PURE__ */ import_react5.default.createElement(
1007
- "span",
1008
- {
1009
- style: {
1010
- marginLeft: 8,
1011
- fontSize: 11,
1012
- color: "var(--theme-elevation-400)",
1013
- fontWeight: 400
1014
- }
1015
- },
1016
- "(",
1017
- fields.length,
1018
- ")"
1019
- ))), fields.length === 0 ? /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-empty" }, "No fields defined. Click \u201CAdd Field\u201D below to add the first field to this block schema.") : /* @__PURE__ */ import_react5.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react5.default.createElement(
1020
- FieldRow,
1021
- {
1022
- key: i,
1023
- index: i,
1024
- total: fields.length,
1025
- field,
1026
- onChange: (updated) => updateField(i, updated),
1027
- onRemove: () => removeField(i),
1028
- onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
1029
- onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
1030
- readOnly
1031
- }
1032
- ))), !readOnly && /* @__PURE__ */ import_react5.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)", marginBottom: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", className: "sbf-add-btn", onClick: addField }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"), "Add Field")));
1033
- }
1034
-
1035
- // src/components/EditInBuilderButton/index.tsx
1119
+ // src/block-builder/components/ErrorBoundary.tsx
1036
1120
  var import_react6 = __toESM(require("react"), 1);
1037
- var import_ui3 = require("@payloadcms/ui");
1038
- var import_ui4 = require("@payloadcms/ui");
1039
- function EditInBuilderButton() {
1040
- const { id } = (0, import_ui3.useDocumentInfo)();
1041
- const { value: slug } = (0, import_ui4.useField)({ path: "slug" });
1042
- if (!id || !slug) return null;
1043
- return /* @__PURE__ */ import_react6.default.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ import_react6.default.createElement(
1044
- "a",
1045
- {
1046
- href: `/block-builder?load=${slug}`,
1047
- target: "_blank",
1048
- rel: "noopener noreferrer",
1049
- style: {
1050
- display: "inline-flex",
1051
- alignItems: "center",
1052
- gap: "6px",
1053
- padding: "8px 16px",
1054
- borderRadius: "6px",
1055
- border: "1px solid #16a34a",
1056
- color: "#16a34a",
1057
- textDecoration: "none",
1058
- fontSize: "13px",
1059
- fontWeight: 500,
1060
- background: "transparent",
1061
- cursor: "pointer"
1062
- }
1063
- },
1064
- "Edit in Block Builder"
1065
- ));
1066
- }
1067
-
1068
- // src/block-builder/components/canvas/BuilderShell.tsx
1069
- var import_react16 = __toESM(require("react"), 1);
1070
1121
 
1071
1122
  // src/block-builder/store/builder.store.ts
1072
1123
  var import_zustand = require("zustand");
1073
1124
  var import_middleware = require("zustand/middleware");
1074
1125
  var import_immer = require("zustand/middleware/immer");
1075
- var import_uuid = require("uuid");
1126
+
1127
+ // src/utils/uuid.ts
1128
+ function uuidv4() {
1129
+ const c = globalThis.crypto;
1130
+ if (typeof c?.randomUUID === "function") {
1131
+ return c.randomUUID();
1132
+ }
1133
+ if (typeof c?.getRandomValues === "function") {
1134
+ const bytes = c.getRandomValues(new Uint8Array(16));
1135
+ bytes[6] = bytes[6] & 15 | 64;
1136
+ bytes[8] = bytes[8] & 63 | 128;
1137
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
1138
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1139
+ }
1140
+ throw new Error(
1141
+ "No cryptographic random source available. `crypto.randomUUID` requires a secure context (HTTPS or localhost)."
1142
+ );
1143
+ }
1144
+
1145
+ // src/block-builder/store/builder.store.ts
1146
+ var TAB_PATH_SEP = "::";
1147
+ var BUILDER_PERSIST_KEY = "@nextbridgehq/payload-block-builder";
1148
+ function encodeTabPath(fieldId, tabIndex) {
1149
+ return `${fieldId}${TAB_PATH_SEP}${tabIndex}`;
1150
+ }
1151
+ function walkPath(block, parentPath, create2) {
1152
+ let currentFields = block.fields;
1153
+ for (const segment of parentPath) {
1154
+ const sepIndex = segment.indexOf(TAB_PATH_SEP);
1155
+ if (sepIndex !== -1) {
1156
+ const fieldId = segment.slice(0, sepIndex);
1157
+ const tabIndex = Number(segment.slice(sepIndex + TAB_PATH_SEP.length));
1158
+ const parentField2 = currentFields.find((f) => f.id === fieldId);
1159
+ if (!parentField2 || parentField2.type !== "tabs" || !Array.isArray(parentField2.tabs)) return null;
1160
+ const tab = parentField2.tabs[tabIndex];
1161
+ if (!tab) return null;
1162
+ if (!tab.fields) {
1163
+ if (!create2) return null;
1164
+ tab.fields = [];
1165
+ }
1166
+ currentFields = tab.fields;
1167
+ continue;
1168
+ }
1169
+ const parentField = currentFields.find((f) => f.id === segment);
1170
+ if (!parentField) return null;
1171
+ if (!parentField.fields) {
1172
+ if (!create2) return null;
1173
+ parentField.fields = [];
1174
+ }
1175
+ currentFields = parentField.fields;
1176
+ }
1177
+ return currentFields;
1178
+ }
1179
+ function getTargetFields(block, parentPath) {
1180
+ return walkPath(block, parentPath, false);
1181
+ }
1182
+ function ensureTargetFields(block, parentPath) {
1183
+ return walkPath(block, parentPath, true);
1184
+ }
1076
1185
  function createDefaultField(type) {
1077
1186
  const base = {
1078
- id: (0, import_uuid.v4)(),
1187
+ id: uuidv4(),
1079
1188
  type,
1080
1189
  name: `${type}Field`,
1081
1190
  label: `${type.charAt(0).toUpperCase()}${type.slice(1)} Field`,
@@ -1083,7 +1192,6 @@ function createDefaultField(type) {
1083
1192
  };
1084
1193
  switch (type) {
1085
1194
  case "select":
1086
- case "radio":
1087
1195
  return {
1088
1196
  ...base,
1089
1197
  options: [
@@ -1092,18 +1200,34 @@ function createDefaultField(type) {
1092
1200
  ]
1093
1201
  };
1094
1202
  case "relationship":
1095
- return { ...base, relationTo: "", hasMany: false };
1203
+ return { ...base, collection: "", hasMany: false };
1096
1204
  case "array":
1097
1205
  return { ...base, fields: [] };
1098
1206
  case "group":
1099
1207
  return { ...base, fields: [] };
1208
+ case "row":
1209
+ return { ...base, fields: [] };
1210
+ case "collapsible":
1211
+ return { ...base, label: base.label ?? "Collapsible Section", fields: [] };
1212
+ case "tabs":
1213
+ return { ...base, tabs: [{ id: uuidv4(), label: "Tab 1", fields: [] }] };
1100
1214
  default:
1101
1215
  return base;
1102
1216
  }
1103
1217
  }
1218
+ function regenerateFieldIds(fields) {
1219
+ return fields.map((f) => {
1220
+ const next = { ...f, id: uuidv4() };
1221
+ if (next.fields) next.fields = regenerateFieldIds(next.fields);
1222
+ if (next.tabs) {
1223
+ next.tabs = next.tabs.map((tab) => ({ ...tab, fields: regenerateFieldIds(tab.fields) }));
1224
+ }
1225
+ return next;
1226
+ });
1227
+ }
1104
1228
  function createDefaultBlock() {
1105
1229
  return {
1106
- id: (0, import_uuid.v4)(),
1230
+ id: uuidv4(),
1107
1231
  slug: "myBlock",
1108
1232
  interfaceName: "MyBlock",
1109
1233
  labels: { singular: "My Block", plural: "My Blocks" },
@@ -1124,11 +1248,13 @@ var useBuilderStore = (0, import_zustand.create)()(
1124
1248
  isReadOnly: false,
1125
1249
  loadedVersionId: null,
1126
1250
  blockSlug: null,
1251
+ activeParentPath: [],
1127
1252
  addBlock: () => set((state) => {
1128
1253
  const block = createDefaultBlock();
1129
1254
  state.blocks.push(block);
1130
1255
  state.activeBlockId = block.id;
1131
1256
  state.activeFieldId = null;
1257
+ state.activeParentPath = [];
1132
1258
  state.isDirty = true;
1133
1259
  }),
1134
1260
  removeBlock: (blockId) => set((state) => {
@@ -1136,6 +1262,7 @@ var useBuilderStore = (0, import_zustand.create)()(
1136
1262
  if (state.activeBlockId === blockId) {
1137
1263
  state.activeBlockId = state.blocks[0]?.id ?? null;
1138
1264
  state.activeFieldId = null;
1265
+ state.activeParentPath = [];
1139
1266
  }
1140
1267
  state.isDirty = true;
1141
1268
  }),
@@ -1147,15 +1274,16 @@ var useBuilderStore = (0, import_zustand.create)()(
1147
1274
  setActiveBlock: (blockId) => set((state) => {
1148
1275
  state.activeBlockId = blockId;
1149
1276
  state.activeFieldId = null;
1277
+ state.activeParentPath = [];
1150
1278
  }),
1151
1279
  duplicateBlock: (blockId) => set((state) => {
1152
1280
  const block = state.blocks.find((b) => b.id === blockId);
1153
1281
  if (!block) return;
1154
1282
  const clone = JSON.parse(JSON.stringify(block));
1155
- clone.id = (0, import_uuid.v4)();
1156
- clone.slug = `${block.slug}Copy`;
1283
+ clone.id = uuidv4();
1284
+ clone.slug = `${block.slug}-copy`;
1157
1285
  clone.interfaceName = block.interfaceName ? `${block.interfaceName}Copy` : void 0;
1158
- clone.fields = clone.fields.map((f) => ({ ...f, id: (0, import_uuid.v4)() }));
1286
+ clone.fields = regenerateFieldIds(clone.fields);
1159
1287
  const idx = state.blocks.findIndex((b) => b.id === blockId);
1160
1288
  state.blocks.splice(idx + 1, 0, clone);
1161
1289
  state.activeBlockId = clone.id;
@@ -1164,36 +1292,47 @@ var useBuilderStore = (0, import_zustand.create)()(
1164
1292
  addField: (blockId, type) => set((state) => {
1165
1293
  const block = state.blocks.find((b) => b.id === blockId);
1166
1294
  if (!block) return;
1295
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1296
+ if (!targetFields) return;
1167
1297
  const field = createDefaultField(type);
1168
- block.fields.push(field);
1298
+ targetFields.push(field);
1169
1299
  state.activeFieldId = field.id;
1170
1300
  state.isDirty = true;
1171
1301
  }),
1172
1302
  removeField: (blockId, fieldId) => set((state) => {
1173
1303
  const block = state.blocks.find((b) => b.id === blockId);
1174
1304
  if (!block) return;
1175
- block.fields = block.fields.filter((f) => f.id !== fieldId);
1305
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1306
+ if (!targetFields) return;
1307
+ const index = targetFields.findIndex((f) => f.id === fieldId);
1308
+ if (index !== -1) {
1309
+ targetFields.splice(index, 1);
1310
+ }
1176
1311
  if (state.activeFieldId === fieldId) state.activeFieldId = null;
1177
1312
  state.isDirty = true;
1178
1313
  }),
1179
1314
  updateField: (blockId, fieldId, updates) => set((state) => {
1180
1315
  const block = state.blocks.find((b) => b.id === blockId);
1181
1316
  if (!block) return;
1182
- const field = block.fields.find((f) => f.id === fieldId);
1317
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1318
+ if (!targetFields) return;
1319
+ const field = targetFields.find((f) => f.id === fieldId);
1183
1320
  if (field) Object.assign(field, updates);
1184
1321
  state.isDirty = true;
1185
1322
  }),
1186
1323
  reorderFields: (blockId, fromIndex, toIndex) => set((state) => {
1187
1324
  const block = state.blocks.find((b) => b.id === blockId);
1188
1325
  if (!block) return;
1189
- const [moved] = block.fields.splice(fromIndex, 1);
1190
- block.fields.splice(toIndex, 0, moved);
1326
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1327
+ if (!targetFields) return;
1328
+ const [moved] = targetFields.splice(fromIndex, 1);
1329
+ targetFields.splice(toIndex, 0, moved);
1191
1330
  state.isDirty = true;
1192
1331
  }),
1193
1332
  setActiveField: (fieldId) => set((state) => {
1194
1333
  state.activeFieldId = fieldId;
1195
1334
  }),
1196
- reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null })),
1335
+ reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null, activeParentPath: [] })),
1197
1336
  markClean: () => set((state) => {
1198
1337
  state.isDirty = false;
1199
1338
  }),
@@ -1201,6 +1340,7 @@ var useBuilderStore = (0, import_zustand.create)()(
1201
1340
  state.blocks = [block];
1202
1341
  state.activeBlockId = block.id;
1203
1342
  state.activeFieldId = null;
1343
+ state.activeParentPath = [];
1204
1344
  state.isDirty = false;
1205
1345
  }),
1206
1346
  setVersionMeta: (versionId, isReadOnly) => set((state) => {
@@ -1209,10 +1349,36 @@ var useBuilderStore = (0, import_zustand.create)()(
1209
1349
  }),
1210
1350
  setBlockSlug: (slug) => set((state) => {
1211
1351
  state.blockSlug = slug;
1352
+ }),
1353
+ pushParentPath: (fieldId) => set((state) => {
1354
+ state.activeParentPath.push(fieldId);
1355
+ state.activeFieldId = null;
1356
+ }),
1357
+ popParentPath: () => set((state) => {
1358
+ state.activeParentPath.pop();
1359
+ state.activeFieldId = null;
1360
+ }),
1361
+ // Jump directly to an ancestor level -- `depth` is the number of
1362
+ // segments to keep, so breadcrumb index `i` maps to `i + 1`.
1363
+ truncateParentPath: (depth) => set((state) => {
1364
+ if (depth < 0 || depth >= state.activeParentPath.length) return;
1365
+ state.activeParentPath = state.activeParentPath.slice(0, depth);
1366
+ state.activeFieldId = null;
1367
+ }),
1368
+ resetParentPath: () => set((state) => {
1369
+ state.activeParentPath = [];
1370
+ state.activeFieldId = null;
1212
1371
  })
1213
1372
  })),
1214
1373
  {
1215
- name: "@nextbridgehq/payload-block-builder",
1374
+ name: BUILDER_PERSIST_KEY,
1375
+ version: 1,
1376
+ migrate: (persistedState, version) => {
1377
+ if (version === 0) {
1378
+ return { blocks: [], activeBlockId: null };
1379
+ }
1380
+ return persistedState;
1381
+ },
1216
1382
  partialize: (state) => ({
1217
1383
  blocks: state.blocks,
1218
1384
  activeBlockId: state.activeBlockId
@@ -1222,163 +1388,191 @@ var useBuilderStore = (0, import_zustand.create)()(
1222
1388
  )
1223
1389
  );
1224
1390
 
1225
- // src/block-builder/components/canvas/TopBar.tsx
1226
- var import_react7 = __toESM(require("react"), 1);
1227
- var import_lucide_react = require("lucide-react");
1228
-
1229
- // src/block-builder/lib/mapToSaveRequest.ts
1230
- var TYPE_MAP = {
1231
- richText: "richtext",
1232
- upload: "image",
1233
- radio: "select"
1234
- };
1235
- var UNSUPPORTED = /* @__PURE__ */ new Set(["code", "point", "ui", "tabs", "collapsible", "row"]);
1236
- function normalizeSlug(slug) {
1237
- return slug.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1238
- }
1239
- function mapToSaveRequest(block) {
1240
- const fields = block.fields.filter((f) => {
1241
- if (UNSUPPORTED.has(f.type)) {
1242
- console.warn(`[block-builder] Field type "${f.type}" is not supported in this project \xE2\u20AC" skipping field "${f.name}"`);
1243
- return false;
1244
- }
1245
- return true;
1246
- }).map(({ id: _id, relationTo, ...f }) => ({
1247
- ...f,
1248
- type: TYPE_MAP[f.type] ?? f.type,
1249
- // normalizer reads `collection`, block-builder stores `relationTo`
1250
- ...relationTo ? { collection: relationTo } : {}
1251
- }));
1252
- return {
1253
- blockSlug: normalizeSlug(block.slug),
1254
- name: block.labels?.singular ?? block.slug,
1255
- schema: { fields },
1256
- changelog: "Created via block builder"
1257
- };
1258
- }
1259
-
1260
- // src/block-builder/lib/codegen.ts
1261
- function indent(n) {
1262
- return " ".repeat(n);
1263
- }
1264
- function escStr(s) {
1265
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1266
- }
1267
- function fieldToCode(field, depth = 1) {
1268
- const pad = indent(depth);
1269
- const innerPad = indent(depth + 1);
1270
- const lines = [];
1271
- lines.push(`${pad}name: '${escStr(field.name)}'`);
1272
- lines.push(`${pad}type: '${field.type}'`);
1273
- if (field.label) lines.push(`${pad}label: '${escStr(field.label)}'`);
1274
- if (field.required) lines.push(`${pad}required: true`);
1275
- if (field.unique) lines.push(`${pad}unique: true`);
1276
- if (field.localized) lines.push(`${pad}localized: true`);
1277
- if (field.defaultValue !== void 0) {
1278
- const val = typeof field.defaultValue === "string" ? `'${escStr(String(field.defaultValue))}'` : field.defaultValue;
1279
- lines.push(`${pad}defaultValue: ${val}`);
1391
+ // src/block-builder/components/ErrorBoundary.tsx
1392
+ var ErrorBoundary = class extends import_react6.Component {
1393
+ constructor() {
1394
+ super(...arguments);
1395
+ this.state = {
1396
+ hasError: false,
1397
+ error: null
1398
+ };
1399
+ /**
1400
+ * Clears persisted builder state before reloading. "Try again" alone only
1401
+ * resets the boundary, so if the cause is corrupt persisted state the next
1402
+ * render throws immediately -- this gives the user a way out of that loop.
1403
+ */
1404
+ this.handleResetState = () => {
1405
+ try {
1406
+ window.localStorage.removeItem(BUILDER_PERSIST_KEY);
1407
+ } catch (err) {
1408
+ console.error("[Block Builder] Could not clear persisted state:", err);
1409
+ }
1410
+ window.location.reload();
1411
+ };
1280
1412
  }
1281
- if (field.type === "richText") {
1282
- lines.push(`${pad}editor: lexicalEditor({})`);
1413
+ static getDerivedStateFromError(error) {
1414
+ return { hasError: true, error };
1283
1415
  }
1284
- if (field.options && field.options.length > 0) {
1285
- const opts = field.options.map((o) => `{ label: '${escStr(o.label)}', value: '${escStr(o.value)}' }`).join(`, `);
1286
- lines.push(`${pad}options: [${opts}]`);
1287
- }
1288
- if (field.relationTo) {
1289
- lines.push(`${pad}relationTo: '${escStr(field.relationTo)}'`);
1290
- }
1291
- if (field.hasMany !== void 0) {
1292
- lines.push(`${pad}hasMany: ${field.hasMany}`);
1293
- }
1294
- if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
1295
- if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
1296
- if (field.fields && field.fields.length > 0) {
1297
- const nested = field.fields.map((f) => `${innerPad}{
1298
- ${fieldToCode(f, depth + 2)}
1299
- ${innerPad}}`).join(",\n");
1300
- lines.push(`${pad}fields: [
1301
- ${nested}
1302
- ${innerPad}]`);
1416
+ componentDidCatch(error, errorInfo) {
1417
+ console.error("[Block Builder] Uncaught error:", error, errorInfo);
1303
1418
  }
1304
- const adminParts = [];
1305
- if (field.admin?.description)
1306
- adminParts.push(`description: '${escStr(field.admin.description)}'`);
1307
- if (field.admin?.placeholder)
1308
- adminParts.push(`placeholder: '${escStr(field.admin.placeholder)}'`);
1309
- if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
1310
- if (field.admin?.hidden) adminParts.push(`hidden: true`);
1311
- if (adminParts.length > 0) {
1312
- lines.push(`${pad}admin: { ${adminParts.join(", ")} }`);
1419
+ render() {
1420
+ if (this.state.hasError) {
1421
+ return /* @__PURE__ */ import_react6.default.createElement("div", { className: "bb-error-boundary", role: "alert" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "bb-error-boundary__title" }, "Something went wrong in the Block Builder."), /* @__PURE__ */ import_react6.default.createElement("details", { className: "bb-error-boundary__details" }, /* @__PURE__ */ import_react6.default.createElement("summary", null, "Error details"), /* @__PURE__ */ import_react6.default.createElement("pre", { className: "bb-error-boundary__trace" }, this.state.error?.toString())), /* @__PURE__ */ import_react6.default.createElement("div", { className: "bb-error-boundary__actions" }, /* @__PURE__ */ import_react6.default.createElement(
1422
+ "button",
1423
+ {
1424
+ type: "button",
1425
+ onClick: () => this.setState({ hasError: false, error: null }),
1426
+ className: "bb-error-boundary__btn bb-error-boundary__btn--primary"
1427
+ },
1428
+ "Try again"
1429
+ ), /* @__PURE__ */ import_react6.default.createElement(
1430
+ "button",
1431
+ {
1432
+ type: "button",
1433
+ onClick: this.handleResetState,
1434
+ className: "bb-error-boundary__btn"
1435
+ },
1436
+ "Reset builder state"
1437
+ )), /* @__PURE__ */ import_react6.default.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."));
1438
+ }
1439
+ return this.props.children;
1313
1440
  }
1314
- return lines.join(",\n");
1315
- }
1316
- function generateBlockCode(block) {
1317
- const hasRichText = containsRichText(block.fields);
1318
- const imports = [`import type { Block } from 'payload'`];
1319
- if (hasRichText) {
1320
- imports.push(`import { lexicalEditor } from '@payloadcms/richtext-lexical'`);
1441
+ };
1442
+
1443
+ // src/components/SchemaBuilderField/index.tsx
1444
+ function parseSchema(raw) {
1445
+ try {
1446
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
1447
+ if (!parsed) return [];
1448
+ if (Array.isArray(parsed)) return parsed;
1449
+ if (parsed?.fields && Array.isArray(parsed.fields)) return parsed.fields;
1450
+ } catch {
1321
1451
  }
1322
- const fieldsCode = block.fields.map((f) => ` {
1323
- ${fieldToCode(f, 2)}
1324
- }`).join(",\n");
1325
- const labelsCode = block.labels ? `
1326
- labels: {
1327
- singular: '${escStr(block.labels.singular ?? block.slug)}',
1328
- plural: '${escStr(block.labels.plural ?? block.slug + "s")}',
1329
- },` : "";
1330
- const interfaceLine = block.interfaceName ? `
1331
- interfaceName: '${escStr(block.interfaceName)}',` : "";
1332
- const exportName = block.interfaceName ?? toCamelCase(block.slug);
1333
- return [
1334
- imports.join("\n"),
1335
- "",
1336
- `export const ${exportName}: Block = {`,
1337
- ` slug: '${escStr(block.slug)}',${interfaceLine}${labelsCode}`,
1338
- ` fields: [`,
1339
- fieldsCode,
1340
- ` ],`,
1341
- `}`,
1342
- ""
1343
- ].join("\n");
1344
- }
1345
- function containsRichText(fields) {
1346
- return fields.some(
1347
- (f) => f.type === "richText" || (f.fields ? containsRichText(f.fields) : false)
1348
- );
1349
- }
1350
- function toCamelCase(slug) {
1351
- return slug.split(/[-_]/).map(
1352
- (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
1353
- ).join("");
1354
- }
1355
- function generateBlockOutput(block) {
1356
- return {
1357
- filename: `${block.slug}.ts`,
1358
- code: generateBlockCode(block),
1359
- language: "typescript"
1360
- };
1452
+ return [];
1361
1453
  }
1362
- function generateAllBlocks(blocks) {
1363
- return blocks.map(generateBlockOutput);
1454
+ function SchemaBuilderField({ path, readOnly }) {
1455
+ const { value, setValue } = (0, import_ui3.useField)({ path });
1456
+ const setValueRef = (0, import_react7.useRef)(setValue);
1457
+ (0, import_react7.useEffect)(() => {
1458
+ setValueRef.current = setValue;
1459
+ });
1460
+ const [fields, setFields] = (0, import_react7.useState)(() => parseSchema(value));
1461
+ const hasExistingValue = value !== void 0 && value !== null;
1462
+ const [hydrated, setHydrated] = (0, import_react7.useState)(!hasExistingValue);
1463
+ (0, import_react7.useEffect)(() => {
1464
+ if (hydrated) return;
1465
+ if (value !== void 0 && value !== null) {
1466
+ setFields(parseSchema(value));
1467
+ setHydrated(true);
1468
+ }
1469
+ }, [value, hydrated]);
1470
+ (0, import_react7.useEffect)(() => {
1471
+ if (!hydrated) return;
1472
+ setValueRef.current({ fields });
1473
+ }, [fields, hydrated]);
1474
+ const addField = (0, import_react7.useCallback)(() => {
1475
+ setFields((prev) => [
1476
+ ...prev,
1477
+ { name: "", type: "text", label: "", required: false }
1478
+ ]);
1479
+ }, []);
1480
+ const updateField = (0, import_react7.useCallback)((index, updated) => {
1481
+ setFields((prev) => prev.map((f, i) => i === index ? updated : f));
1482
+ }, []);
1483
+ const removeField = (0, import_react7.useCallback)((index) => {
1484
+ setFields((prev) => prev.filter((_, i) => i !== index));
1485
+ }, []);
1486
+ const moveField = (0, import_react7.useCallback)((index, dir) => {
1487
+ setFields((prev) => {
1488
+ const next = [...prev];
1489
+ const target = index + dir;
1490
+ if (target < 0 || target >= next.length) return prev;
1491
+ [next[index], next[target]] = [next[target], next[index]];
1492
+ return next;
1493
+ });
1494
+ }, []);
1495
+ return /* @__PURE__ */ import_react7.default.createElement(ErrorBoundary, null, /* @__PURE__ */ import_react7.default.createElement("div", null, /* @__PURE__ */ import_react7.default.createElement("div", { className: "sbf-section-heading" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "sbf-section-label" }, "Fields", fields.length > 0 && /* @__PURE__ */ import_react7.default.createElement(
1496
+ "span",
1497
+ {
1498
+ style: {
1499
+ marginLeft: 8,
1500
+ fontSize: 11,
1501
+ color: "var(--theme-elevation-400)",
1502
+ fontWeight: 400
1503
+ }
1504
+ },
1505
+ "(",
1506
+ fields.length,
1507
+ ")"
1508
+ ))), fields.length === 0 ? /* @__PURE__ */ import_react7.default.createElement("div", { className: "sbf-empty" }, "No fields defined. Click \u201CAdd Field\u201D below to add the first field to this block schema.") : /* @__PURE__ */ import_react7.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react7.default.createElement(
1509
+ FieldRow,
1510
+ {
1511
+ key: i,
1512
+ index: i,
1513
+ total: fields.length,
1514
+ field,
1515
+ onChange: (updated) => updateField(i, updated),
1516
+ onRemove: () => removeField(i),
1517
+ onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
1518
+ onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
1519
+ readOnly
1520
+ }
1521
+ ))), !readOnly && /* @__PURE__ */ import_react7.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)", marginBottom: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react7.default.createElement("button", { type: "button", className: "sbf-add-btn", onClick: addField }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"), "Add Field"))));
1364
1522
  }
1365
- function generateIndexFile(blocks) {
1366
- const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
1367
- const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
1368
- const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
1369
- const code = [
1370
- imports,
1371
- "",
1372
- `export const blocks = [`,
1373
- exportList,
1374
- `] as const`,
1375
- ""
1376
- ].join("\n");
1377
- return { filename: "index.ts", code, language: "typescript" };
1523
+
1524
+ // src/components/EditInBuilderButton/index.tsx
1525
+ var import_react8 = __toESM(require("react"), 1);
1526
+ var import_ui4 = require("@payloadcms/ui");
1527
+ var import_ui5 = require("@payloadcms/ui");
1528
+ function EditInBuilderButton() {
1529
+ const { id } = (0, import_ui4.useDocumentInfo)();
1530
+ const { value: slug } = (0, import_ui5.useField)({ path: "slug" });
1531
+ if (!id || !slug) return null;
1532
+ return /* @__PURE__ */ import_react8.default.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ import_react8.default.createElement(
1533
+ "a",
1534
+ {
1535
+ href: `/block-builder?load=${slug}`,
1536
+ target: "_blank",
1537
+ rel: "noopener noreferrer",
1538
+ style: {
1539
+ display: "inline-flex",
1540
+ alignItems: "center",
1541
+ gap: "6px",
1542
+ padding: "8px 16px",
1543
+ borderRadius: "6px",
1544
+ border: "1px solid #16a34a",
1545
+ color: "#16a34a",
1546
+ textDecoration: "none",
1547
+ fontSize: "13px",
1548
+ fontWeight: 500,
1549
+ background: "transparent",
1550
+ cursor: "pointer"
1551
+ }
1552
+ },
1553
+ "Edit in Block Builder"
1554
+ ));
1378
1555
  }
1379
1556
 
1557
+ // src/block-builder/components/canvas/BuilderShell.tsx
1558
+ var import_react19 = __toESM(require("react"), 1);
1559
+
1380
1560
  // src/block-builder/components/canvas/TopBar.tsx
1381
- function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish }) {
1561
+ var import_react9 = __toESM(require("react"), 1);
1562
+ var import_lucide_react = require("lucide-react");
1563
+ function ensureFieldIds(fields) {
1564
+ if (!Array.isArray(fields)) return [];
1565
+ return fields.map((field) => {
1566
+ const f = { ...field };
1567
+ if (!f.id) f.id = uuidv4();
1568
+ if (Array.isArray(f.fields)) f.fields = ensureFieldIds(f.fields);
1569
+ if (Array.isArray(f.tabs)) {
1570
+ f.tabs = f.tabs.map((tab) => ({ ...tab, fields: ensureFieldIds(tab.fields) }));
1571
+ }
1572
+ return f;
1573
+ });
1574
+ }
1575
+ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish, notification, onSetNotification, previewOpen, onTogglePreview }) {
1382
1576
  const blocks = useBuilderStore((s) => s.blocks);
1383
1577
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1384
1578
  const activeBlock = blocks.find((b) => b.id === activeBlockId);
@@ -1386,21 +1580,23 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1386
1580
  const isDirty = useBuilderStore((s) => s.isDirty);
1387
1581
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1388
1582
  const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
1389
- const [notification, setNotification] = (0, import_react7.useState)(null);
1390
- const [versionDropdownOpen, setVersionDropdownOpen] = (0, import_react7.useState)(false);
1391
- const [blockPickerOpen, setBlockPickerOpen] = (0, import_react7.useState)(false);
1392
- const dropdownRef = (0, import_react7.useRef)(null);
1393
- const blockPickerRef = (0, import_react7.useRef)(null);
1583
+ const loadBlock = useBuilderStore((s) => s.loadBlock);
1584
+ const setNotification = onSetNotification;
1585
+ const [versionDropdownOpen, setVersionDropdownOpen] = (0, import_react9.useState)(false);
1586
+ const [blockPickerOpen, setBlockPickerOpen] = (0, import_react9.useState)(false);
1587
+ const dropdownRef = (0, import_react9.useRef)(null);
1588
+ const blockPickerRef = (0, import_react9.useRef)(null);
1589
+ const fileInputRef = (0, import_react9.useRef)(null);
1394
1590
  const selectedVersion = versions.find((v) => v.id === selectedVersionId);
1395
1591
  const currentVersion = versions.find((v) => v.isCurrent);
1396
1592
  const activeBlockDef = blockDefs.find((b) => b.slug === activeSlug);
1397
- (0, import_react7.useEffect)(() => {
1593
+ (0, import_react9.useEffect)(() => {
1398
1594
  if (notification?.status === "success") {
1399
1595
  const t = setTimeout(() => setNotification(null), 3e3);
1400
1596
  return () => clearTimeout(t);
1401
1597
  }
1402
1598
  }, [notification]);
1403
- (0, import_react7.useEffect)(() => {
1599
+ (0, import_react9.useEffect)(() => {
1404
1600
  function handleClick(e) {
1405
1601
  if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
1406
1602
  setVersionDropdownOpen(false);
@@ -1416,10 +1612,17 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1416
1612
  if (!activeBlock || isReadOnly) return false;
1417
1613
  setNotification({ status: "publishing" });
1418
1614
  try {
1419
- const req = mapToSaveRequest(activeBlock);
1615
+ const normalizeSlug = (s) => s.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1616
+ const publishedSlug = normalizeSlug(activeBlock.slug);
1617
+ const req = {
1618
+ blockSlug: publishedSlug,
1619
+ name: activeBlock.labels?.singular ?? activeBlock.slug,
1620
+ schema: { fields: activeBlock.fields },
1621
+ changelog: "Created via block builder"
1622
+ };
1420
1623
  const res = await fetch("/api/blocks/save", {
1421
1624
  method: "POST",
1422
- headers: { "Content-Type": "application/json" },
1625
+ headers: { "Content-Type": "application/json", "X-Block-Builder": "1" },
1423
1626
  body: JSON.stringify(req)
1424
1627
  });
1425
1628
  const json = await res.json();
@@ -1430,7 +1633,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1430
1633
  status: "success",
1431
1634
  msg: `v${json.versionNumber ?? "?"} published successfully!`
1432
1635
  });
1433
- onAfterPublish();
1636
+ onAfterPublish(publishedSlug);
1434
1637
  return true;
1435
1638
  } else {
1436
1639
  setNotification({
@@ -1449,35 +1652,54 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1449
1652
  return false;
1450
1653
  }
1451
1654
  }
1452
- function handleExport() {
1453
- if (blocks.length === 0) return;
1454
- const outputs = [...generateAllBlocks(blocks), generateIndexFile(blocks)];
1455
- for (const out of outputs) {
1456
- const blob = new Blob([out.code], { type: "text/plain" });
1457
- const url = URL.createObjectURL(blob);
1458
- const a = document.createElement("a");
1459
- a.href = url;
1460
- a.download = out.filename;
1461
- document.body.appendChild(a);
1462
- a.click();
1463
- document.body.removeChild(a);
1464
- URL.revokeObjectURL(url);
1465
- }
1655
+ function handleExportJson() {
1656
+ if (!activeBlock) return;
1657
+ const blob = new Blob([JSON.stringify(activeBlock, null, 2)], { type: "application/json" });
1658
+ const url = URL.createObjectURL(blob);
1659
+ const a = document.createElement("a");
1660
+ a.href = url;
1661
+ a.download = `${activeBlock.slug}-schema.json`;
1662
+ document.body.appendChild(a);
1663
+ a.click();
1664
+ document.body.removeChild(a);
1665
+ URL.revokeObjectURL(url);
1666
+ }
1667
+ function handleImportJson(e) {
1668
+ const file = e.target.files?.[0];
1669
+ if (!file) return;
1670
+ const reader = new FileReader();
1671
+ reader.onload = (event) => {
1672
+ try {
1673
+ const json = JSON.parse(event.target?.result);
1674
+ if (json && json.slug && Array.isArray(json.fields)) {
1675
+ if (!json.id) json.id = uuidv4();
1676
+ json.fields = ensureFieldIds(json.fields);
1677
+ loadBlock(json);
1678
+ setNotification({ status: "success", msg: "Block imported successfully!" });
1679
+ } else {
1680
+ setNotification({ status: "error", title: "Invalid JSON", errors: ["The file does not contain a valid block schema."] });
1681
+ }
1682
+ } catch (err) {
1683
+ setNotification({ status: "error", title: "Parse Error", errors: ["Could not parse JSON file."] });
1684
+ }
1685
+ };
1686
+ reader.readAsText(file);
1687
+ if (fileInputRef.current) fileInputRef.current.value = "";
1466
1688
  }
1467
1689
  function formatDate(iso) {
1468
1690
  return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
1469
1691
  }
1470
- return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar__brand" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-topbar__title" }, "Block Builder"), isDirty && !isReadOnly && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-topbar__dirty" }, "* unsaved")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar__selectors" }, blockDefs.length > 0 && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-block-picker", ref: blockPickerRef }, /* @__PURE__ */ import_react7.default.createElement(
1692
+ return /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-topbar" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-topbar__brand" }, /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-topbar__title" }, "Block Builder"), isDirty && !isReadOnly && /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-topbar__dirty" }, "* unsaved")), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-topbar__selectors" }, blockDefs.length > 0 && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-picker", ref: blockPickerRef }, /* @__PURE__ */ import_react9.default.createElement(
1471
1693
  "button",
1472
1694
  {
1473
1695
  type: "button",
1474
1696
  className: "bb-block-picker__trigger",
1475
1697
  onClick: () => setBlockPickerOpen((o) => !o)
1476
1698
  },
1477
- /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
1478
- /* @__PURE__ */ import_react7.default.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
1479
- /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1480
- ), blockPickerOpen && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ import_react7.default.createElement(
1699
+ /* @__PURE__ */ import_react9.default.createElement(import_lucide_react.Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
1700
+ /* @__PURE__ */ import_react9.default.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
1701
+ /* @__PURE__ */ import_react9.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1702
+ ), blockPickerOpen && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ import_react9.default.createElement(
1481
1703
  "button",
1482
1704
  {
1483
1705
  key: b.id,
@@ -1488,20 +1710,20 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1488
1710
  onBlockSelect(b.slug);
1489
1711
  }
1490
1712
  },
1491
- /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-block-picker__item-name" }, b.name),
1492
- /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-dropdown__meta" }, b.slug)
1493
- )))), versions.length > 0 && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-selector", ref: dropdownRef }, /* @__PURE__ */ import_react7.default.createElement(
1713
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-block-picker__item-name" }, b.name),
1714
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-version-dropdown__meta" }, b.slug)
1715
+ )))), versions.length > 0 && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-version-selector", ref: dropdownRef }, /* @__PURE__ */ import_react9.default.createElement(
1494
1716
  "button",
1495
1717
  {
1496
1718
  type: "button",
1497
1719
  className: `bb-version-selector__trigger${isReadOnly ? " bb-version-selector__trigger--readonly" : ""}`,
1498
1720
  onClick: () => setVersionDropdownOpen((o) => !o)
1499
1721
  },
1500
- /* @__PURE__ */ import_react7.default.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1501
- /* @__PURE__ */ import_react7.default.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
1502
- selectedVersion?.isCurrent && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__badge" }, "current"),
1503
- /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1504
- ), versionDropdownOpen && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ import_react7.default.createElement(
1722
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1723
+ /* @__PURE__ */ import_react9.default.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
1724
+ selectedVersion?.isCurrent && /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-version-selector__badge" }, "current"),
1725
+ /* @__PURE__ */ import_react9.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1726
+ ), versionDropdownOpen && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ import_react9.default.createElement(
1505
1727
  "button",
1506
1728
  {
1507
1729
  key: v.id,
@@ -1512,20 +1734,45 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1512
1734
  onVersionSelect(v.id);
1513
1735
  }
1514
1736
  },
1515
- /* @__PURE__ */ import_react7.default.createElement("span", { className: `bb-version-selector__dot${v.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1516
- /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-dropdown__label" }, v.label, v.isCurrent && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__badge" }, "current")),
1517
- /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-dropdown__meta" }, v.changelog ? `${v.changelog.slice(0, 32)}${v.changelog.length > 32 ? "..." : ""}` : formatDate(v.createdAt))
1518
- ))))), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar__actions" }, /* @__PURE__ */ import_react7.default.createElement(
1737
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: `bb-version-selector__dot${v.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1738
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-version-dropdown__label" }, v.label, v.isCurrent && /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-version-selector__badge" }, "current")),
1739
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-version-dropdown__meta" }, v.changelog ? `${v.changelog.slice(0, 32)}${v.changelog.length > 32 ? "..." : ""}` : formatDate(v.createdAt))
1740
+ ))))), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-topbar__actions" }, /* @__PURE__ */ import_react9.default.createElement(
1741
+ "input",
1742
+ {
1743
+ type: "file",
1744
+ accept: ".json",
1745
+ ref: fileInputRef,
1746
+ style: { display: "none" },
1747
+ onChange: handleImportJson
1748
+ }
1749
+ ), /* @__PURE__ */ import_react9.default.createElement(
1519
1750
  "button",
1520
1751
  {
1521
1752
  type: "button",
1522
- onClick: handleExport,
1523
- disabled: blocks.length === 0,
1524
- className: "bb-btn bb-btn--secondary",
1525
- style: { display: "none" }
1753
+ onClick: () => fileInputRef.current?.click(),
1754
+ className: "bb-btn bb-btn--secondary"
1526
1755
  },
1527
- "Export .ts"
1528
- ), isReadOnly ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
1756
+ "Import JSON"
1757
+ ), /* @__PURE__ */ import_react9.default.createElement(
1758
+ "button",
1759
+ {
1760
+ type: "button",
1761
+ onClick: handleExportJson,
1762
+ disabled: !activeBlock,
1763
+ className: "bb-btn bb-btn--secondary"
1764
+ },
1765
+ "Export JSON"
1766
+ ), /* @__PURE__ */ import_react9.default.createElement("div", { style: { width: 1, height: 24, background: "var(--bb-border)", margin: "0 4px" } }), /* @__PURE__ */ import_react9.default.createElement(
1767
+ "button",
1768
+ {
1769
+ type: "button",
1770
+ onClick: onTogglePreview,
1771
+ disabled: !activeBlock,
1772
+ className: `bb-btn ${previewOpen ? "bb-btn--primary" : "bb-btn--secondary"}`
1773
+ },
1774
+ previewOpen ? "Close Preview" : "Live Preview"
1775
+ ), isReadOnly ? /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
1529
1776
  "button",
1530
1777
  {
1531
1778
  type: "button",
@@ -1536,7 +1783,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1536
1783
  disabled: !currentVersion
1537
1784
  },
1538
1785
  "Back to current"
1539
- ), /* @__PURE__ */ import_react7.default.createElement(
1786
+ ), /* @__PURE__ */ import_react9.default.createElement(
1540
1787
  "button",
1541
1788
  {
1542
1789
  type: "button",
@@ -1548,7 +1795,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1548
1795
  className: "bb-btn bb-btn--warning"
1549
1796
  },
1550
1797
  notification?.status === "publishing" ? "Restoring..." : "Restore as new version"
1551
- )) : /* @__PURE__ */ import_react7.default.createElement(
1798
+ )) : /* @__PURE__ */ import_react9.default.createElement(
1552
1799
  "button",
1553
1800
  {
1554
1801
  type: "button",
@@ -1557,37 +1804,55 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1557
1804
  className: "bb-btn bb-btn--primary"
1558
1805
  },
1559
1806
  notification?.status === "publishing" ? "Publishing..." : "Publish to Payload"
1560
- ))), notification?.status === "publishing" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ import_react7.default.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ import_react7.default.createElement("li", { key: i }, e)))), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react7.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))));
1807
+ ))), notification?.status === "publishing" && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react9.default.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ import_react9.default.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react9.default.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ import_react9.default.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ import_react9.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react9.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react9.default.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ import_react9.default.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ import_react9.default.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ import_react9.default.createElement("li", { key: i }, e)))), /* @__PURE__ */ import_react9.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react9.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))));
1561
1808
  }
1562
1809
 
1563
1810
  // src/block-builder/components/canvas/BlockList.tsx
1564
- var import_react8 = __toESM(require("react"), 1);
1811
+ var import_react10 = __toESM(require("react"), 1);
1565
1812
  var import_lucide_react2 = require("lucide-react");
1566
- function BlockList() {
1813
+ function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
1567
1814
  const blocks = useBuilderStore((s) => s.blocks);
1568
1815
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1569
1816
  const addBlock = useBuilderStore((s) => s.addBlock);
1570
1817
  const removeBlock = useBuilderStore((s) => s.removeBlock);
1571
1818
  const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
1572
1819
  const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
1573
- return /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ import_react8.default.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ import_react8.default.createElement(import_lucide_react2.Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__body" }, blocks.length === 0 && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ import_react8.default.createElement("br", null), "Click + to create one."), blocks.map((block) => {
1820
+ const useApiNav = blockDefs.length > 0;
1821
+ const loadedBlock = useApiNav ? blocks.find((b) => b.slug === activeSlug) : null;
1822
+ const localOnlyBlocks = blocks.filter(
1823
+ (b) => !blockDefs.some((d) => d.slug === b.slug)
1824
+ );
1825
+ return /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ import_react10.default.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ import_react10.default.createElement(import_lucide_react2.Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-sidebar__body" }, useApiNav && blockDefs.map((def) => {
1826
+ const isActive = def.slug === activeSlug;
1827
+ const fieldCount = isActive && loadedBlock ? loadedBlock.fields.length : null;
1828
+ return /* @__PURE__ */ import_react10.default.createElement(
1829
+ "div",
1830
+ {
1831
+ key: def.id,
1832
+ onClick: () => onBlockSelect?.(def.slug),
1833
+ className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1834
+ },
1835
+ /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-block-item__slug" }, def.slug),
1836
+ /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-block-item__meta" }, fieldCount !== null ? `${fieldCount} field${fieldCount !== 1 ? "s" : ""}` : def.name)
1837
+ );
1838
+ }), localOnlyBlocks.map((block) => {
1574
1839
  const isActive = block.id === activeBlockId;
1575
- return /* @__PURE__ */ import_react8.default.createElement(
1840
+ return /* @__PURE__ */ import_react10.default.createElement(
1576
1841
  "div",
1577
1842
  {
1578
1843
  key: block.id,
1579
1844
  onClick: () => setActiveBlock(block.id),
1580
1845
  className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1581
1846
  },
1582
- /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-item__slug" }, block.slug),
1583
- /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-item__meta" }, block.fields.length, " field", block.fields.length !== 1 ? "s" : ""),
1584
- /* @__PURE__ */ import_react8.default.createElement(
1847
+ /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-block-item__slug" }, block.slug),
1848
+ /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-block-item__meta" }, block.fields.length, " field", block.fields.length !== 1 ? "s" : ""),
1849
+ /* @__PURE__ */ import_react10.default.createElement(
1585
1850
  "div",
1586
1851
  {
1587
1852
  className: "bb-block-item__actions",
1588
1853
  onClick: (e) => e.stopPropagation()
1589
1854
  },
1590
- /* @__PURE__ */ import_react8.default.createElement(
1855
+ /* @__PURE__ */ import_react10.default.createElement(
1591
1856
  "button",
1592
1857
  {
1593
1858
  type: "button",
@@ -1595,9 +1860,9 @@ function BlockList() {
1595
1860
  className: "bb-block-action",
1596
1861
  title: "Duplicate"
1597
1862
  },
1598
- /* @__PURE__ */ import_react8.default.createElement(import_lucide_react2.Copy, { size: 12, strokeWidth: 1.75 })
1863
+ /* @__PURE__ */ import_react10.default.createElement(import_lucide_react2.Copy, { size: 12, strokeWidth: 1.75 })
1599
1864
  ),
1600
- /* @__PURE__ */ import_react8.default.createElement(
1865
+ /* @__PURE__ */ import_react10.default.createElement(
1601
1866
  "button",
1602
1867
  {
1603
1868
  type: "button",
@@ -1605,69 +1870,103 @@ function BlockList() {
1605
1870
  className: "bb-block-action bb-block-action--danger",
1606
1871
  title: "Delete"
1607
1872
  },
1608
- /* @__PURE__ */ import_react8.default.createElement(import_lucide_react2.Trash2, { size: 12, strokeWidth: 1.75 })
1873
+ /* @__PURE__ */ import_react10.default.createElement(import_lucide_react2.Trash2, { size: 12, strokeWidth: 1.75 })
1609
1874
  )
1610
1875
  )
1611
1876
  );
1612
- })));
1877
+ }), !useApiNav && blocks.length === 0 && /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ import_react10.default.createElement("br", null), "Click + to create one.")));
1613
1878
  }
1614
1879
 
1615
1880
  // src/block-builder/components/canvas/BuilderCanvas.tsx
1616
- var import_react10 = __toESM(require("react"), 1);
1881
+ var import_react12 = __toESM(require("react"), 1);
1617
1882
  var import_core = require("@dnd-kit/core");
1618
1883
  var import_sortable2 = require("@dnd-kit/sortable");
1619
1884
  var import_modifiers = require("@dnd-kit/modifiers");
1620
1885
 
1621
1886
  // src/block-builder/components/canvas/SortableFieldCard.tsx
1622
- var import_react9 = __toESM(require("react"), 1);
1887
+ var import_react11 = __toESM(require("react"), 1);
1623
1888
  var import_sortable = require("@dnd-kit/sortable");
1624
1889
  var import_utilities = require("@dnd-kit/utilities");
1625
1890
  var import_lucide_react3 = require("lucide-react");
1626
1891
  var ICON_MAP = {
1627
1892
  text: import_lucide_react3.Type,
1628
1893
  textarea: import_lucide_react3.AlignLeft,
1894
+ richtext: import_lucide_react3.AlignJustify,
1629
1895
  number: import_lucide_react3.Hash,
1630
1896
  email: import_lucide_react3.Mail,
1631
1897
  date: import_lucide_react3.Calendar,
1632
1898
  checkbox: import_lucide_react3.CheckSquare,
1633
1899
  select: import_lucide_react3.ChevronDown,
1634
- radio: import_lucide_react3.Circle,
1635
- upload: import_lucide_react3.Upload,
1900
+ image: import_lucide_react3.Image,
1901
+ file: import_lucide_react3.Upload,
1636
1902
  relationship: import_lucide_react3.Link,
1637
- json: import_lucide_react3.Braces
1903
+ json: import_lucide_react3.Braces,
1904
+ array: import_lucide_react3.List,
1905
+ group: import_lucide_react3.Box,
1906
+ row: import_lucide_react3.Columns,
1907
+ tabs: import_lucide_react3.Folder,
1908
+ collapsible: import_lucide_react3.ChevronDown
1638
1909
  };
1639
- function SortableFieldCard({ field, blockId, index }) {
1910
+ var SortableFieldCard = import_react11.default.memo(function SortableFieldCard2({
1911
+ field,
1912
+ blockId,
1913
+ index,
1914
+ isActive,
1915
+ isReadOnly,
1916
+ onDrillDown
1917
+ }) {
1640
1918
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = (0, import_sortable.useSortable)({
1641
1919
  id: field.id
1642
1920
  });
1643
- const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1644
1921
  const setActiveField = useBuilderStore((s) => s.setActiveField);
1645
1922
  const removeField = useBuilderStore((s) => s.removeField);
1646
- const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1647
- const isActive = activeFieldId === field.id;
1648
1923
  const wrapStyle = {
1649
1924
  transform: import_utilities.CSS.Transform.toString(transform),
1650
1925
  transition
1651
1926
  };
1652
- return /* @__PURE__ */ import_react9.default.createElement(
1927
+ return /* @__PURE__ */ import_react11.default.createElement(
1653
1928
  "div",
1654
1929
  {
1655
1930
  ref: setNodeRef,
1656
1931
  style: wrapStyle,
1657
1932
  className: `bb-field-card-wrap${isDragging ? " bb-field-card-wrap--dragging" : ""}`,
1658
1933
  ...attributes,
1659
- ...listeners
1934
+ ...listeners,
1935
+ "aria-label": `Draggable field card for ${field.name || "unnamed"}`
1660
1936
  },
1661
- /* @__PURE__ */ import_react9.default.createElement(
1937
+ /* @__PURE__ */ import_react11.default.createElement(
1662
1938
  "div",
1663
1939
  {
1664
1940
  className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
1665
1941
  onClick: () => setActiveField(isActive ? null : field.id)
1666
1942
  },
1667
- /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ import_react9.default.createElement(FieldIcon, { type: field.type })),
1668
- /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__required" }, "*"))),
1669
- /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
1670
- !isReadOnly && /* @__PURE__ */ import_react9.default.createElement(
1943
+ /* @__PURE__ */ import_react11.default.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ import_react11.default.createElement(FieldIcon, { type: field.type })),
1944
+ /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ import_react11.default.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ import_react11.default.createElement("span", { className: "bb-field-card__required" }, "*"))),
1945
+ /* @__PURE__ */ import_react11.default.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
1946
+ onDrillDown && /* @__PURE__ */ import_react11.default.createElement(
1947
+ "button",
1948
+ {
1949
+ type: "button",
1950
+ onPointerDown: (e) => e.stopPropagation(),
1951
+ onClick: (e) => {
1952
+ e.stopPropagation();
1953
+ onDrillDown();
1954
+ },
1955
+ className: "bb-field-card__drill",
1956
+ title: "Edit Inner Fields",
1957
+ style: {
1958
+ marginLeft: "auto",
1959
+ fontSize: 12,
1960
+ padding: "2px 8px",
1961
+ borderRadius: 4,
1962
+ border: "1px solid var(--theme-border)",
1963
+ background: "var(--theme-elevation-100)",
1964
+ cursor: "pointer"
1965
+ }
1966
+ },
1967
+ "Edit Fields"
1968
+ ),
1969
+ !isReadOnly && /* @__PURE__ */ import_react11.default.createElement(
1671
1970
  "button",
1672
1971
  {
1673
1972
  type: "button",
@@ -1679,21 +1978,46 @@ function SortableFieldCard({ field, blockId, index }) {
1679
1978
  className: "bb-field-card__delete",
1680
1979
  title: "Remove field"
1681
1980
  },
1682
- /* @__PURE__ */ import_react9.default.createElement(import_lucide_react3.X, { size: 12, strokeWidth: 2 })
1981
+ /* @__PURE__ */ import_react11.default.createElement(import_lucide_react3.X, { size: 12, strokeWidth: 2 })
1683
1982
  )
1684
1983
  )
1685
1984
  );
1686
- }
1985
+ });
1687
1986
  function FieldIcon({ type }) {
1688
1987
  const Icon = ICON_MAP[type];
1689
- return Icon ? /* @__PURE__ */ import_react9.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
1988
+ return Icon ? /* @__PURE__ */ import_react11.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
1690
1989
  }
1691
1990
 
1692
1991
  // src/block-builder/components/canvas/BuilderCanvas.tsx
1992
+ function resolvePathLabels(block, parentPath) {
1993
+ const labels = [];
1994
+ let currentFields = block.fields;
1995
+ for (const segment of parentPath) {
1996
+ const sepIndex = segment.indexOf("::");
1997
+ if (sepIndex !== -1) {
1998
+ const fieldId = segment.slice(0, sepIndex);
1999
+ const tabIndex = Number(segment.slice(sepIndex + 2));
2000
+ const tabsField = currentFields?.find((f) => f.id === fieldId);
2001
+ const tab = tabsField?.tabs?.[tabIndex];
2002
+ labels.push(tab?.label || tabsField?.name || "Tab");
2003
+ currentFields = tab?.fields;
2004
+ continue;
2005
+ }
2006
+ const field = currentFields?.find((f) => f.id === segment);
2007
+ labels.push(field?.name || field?.label || "Nested Field");
2008
+ currentFields = field?.fields;
2009
+ }
2010
+ return labels;
2011
+ }
1693
2012
  function BuilderCanvas() {
1694
2013
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1695
2014
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1696
2015
  const reorderFields = useBuilderStore((s) => s.reorderFields);
2016
+ const activeFieldId = useBuilderStore((s) => s.activeFieldId);
2017
+ const activeParentPath = useBuilderStore((s) => s.activeParentPath);
2018
+ const truncateParentPath = useBuilderStore((s) => s.truncateParentPath);
2019
+ const resetParentPath = useBuilderStore((s) => s.resetParentPath);
2020
+ const pushParentPath = useBuilderStore((s) => s.pushParentPath);
1697
2021
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1698
2022
  const sensors = (0, import_core.useSensors)(
1699
2023
  (0, import_core.useSensor)(import_core.PointerSensor, { activationConstraint: { distance: 4 } }),
@@ -1703,16 +2027,41 @@ function BuilderCanvas() {
1703
2027
  if (isReadOnly) return;
1704
2028
  const { active, over } = event;
1705
2029
  if (!over || active.id === over.id || !block) return;
1706
- const fromIndex = block.fields.findIndex((f) => f.id === active.id);
1707
- const toIndex = block.fields.findIndex((f) => f.id === over.id);
2030
+ const targetFields2 = getTargetFields(block, activeParentPath);
2031
+ if (!targetFields2) return;
2032
+ const fromIndex = targetFields2.findIndex((f) => f.id === active.id);
2033
+ const toIndex = targetFields2.findIndex((f) => f.id === over.id);
1708
2034
  if (fromIndex !== -1 && toIndex !== -1) {
1709
2035
  reorderFields(block.id, fromIndex, toIndex);
1710
2036
  }
1711
2037
  }
1712
2038
  if (!block) {
1713
- return /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-canvas__no-block" }, "Select or create a block from the left panel."));
2039
+ return /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-canvas", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ import_react12.default.createElement("span", { className: "bb-canvas__no-block" }, "Select or create a block from the left panel."));
1714
2040
  }
1715
- return /* @__PURE__ */ import_react10.default.createElement("div", { className: `bb-canvas${isReadOnly ? " bb-canvas--readonly" : ""}` }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__inner" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__header" }, block.slug, " - ", block.fields.length, " field", block.fields.length !== 1 ? "s" : ""), block.fields.length === 0 ? /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__empty" }, "Add fields from the palette on the left") : /* @__PURE__ */ import_react10.default.createElement(
2041
+ const targetFields = getTargetFields(block, activeParentPath) || [];
2042
+ const pathLabels = resolvePathLabels(block, activeParentPath);
2043
+ return /* @__PURE__ */ import_react12.default.createElement("div", { className: `bb-canvas${isReadOnly ? " bb-canvas--readonly" : ""}` }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-canvas__inner" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-canvas__header" }, activeParentPath.length === 0 ? /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, block.slug, " - ", targetFields.length, " field", targetFields.length !== 1 ? "s" : "") : /* @__PURE__ */ import_react12.default.createElement("nav", { className: "bb-breadcrumb", "aria-label": "Field path" }, /* @__PURE__ */ import_react12.default.createElement(
2044
+ "button",
2045
+ {
2046
+ type: "button",
2047
+ onClick: () => resetParentPath(),
2048
+ className: "bb-breadcrumb__item"
2049
+ },
2050
+ block.slug
2051
+ ), /* @__PURE__ */ import_react12.default.createElement("span", { className: "bb-breadcrumb__sep" }, "/"), activeParentPath.map((segment, index) => {
2052
+ const isLast = index === activeParentPath.length - 1;
2053
+ return /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, { key: segment }, /* @__PURE__ */ import_react12.default.createElement(
2054
+ "button",
2055
+ {
2056
+ type: "button",
2057
+ onClick: isLast ? void 0 : () => truncateParentPath(index + 1),
2058
+ className: "bb-breadcrumb__item",
2059
+ "aria-current": isLast ? "location" : void 0,
2060
+ "data-current": isLast ? "true" : void 0
2061
+ },
2062
+ pathLabels[index]
2063
+ ), !isLast && /* @__PURE__ */ import_react12.default.createElement("span", { className: "bb-breadcrumb__sep" }, "/"));
2064
+ }))), targetFields.length === 0 ? /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-canvas__empty" }, "Add fields from the palette on the left") : /* @__PURE__ */ import_react12.default.createElement(
1716
2065
  import_core.DndContext,
1717
2066
  {
1718
2067
  sensors,
@@ -1720,19 +2069,22 @@ function BuilderCanvas() {
1720
2069
  modifiers: [import_modifiers.restrictToVerticalAxis, import_modifiers.restrictToParentElement],
1721
2070
  onDragEnd: handleDragEnd
1722
2071
  },
1723
- /* @__PURE__ */ import_react10.default.createElement(
2072
+ /* @__PURE__ */ import_react12.default.createElement(
1724
2073
  import_sortable2.SortableContext,
1725
2074
  {
1726
- items: block.fields.map((f) => f.id),
2075
+ items: targetFields.map((f) => f.id),
1727
2076
  strategy: import_sortable2.verticalListSortingStrategy
1728
2077
  },
1729
- /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__field-list" }, block.fields.map((field, i) => /* @__PURE__ */ import_react10.default.createElement(
2078
+ /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-canvas__field-list" }, targetFields.map((field, i) => /* @__PURE__ */ import_react12.default.createElement(
1730
2079
  SortableFieldCard,
1731
2080
  {
1732
2081
  key: field.id,
1733
2082
  field,
1734
2083
  blockId: block.id,
1735
- index: i
2084
+ index: i,
2085
+ isActive: activeFieldId === field.id,
2086
+ isReadOnly,
2087
+ onDrillDown: ["group", "row", "array", "collapsible"].includes(field.type) ? () => pushParentPath(field.id) : void 0
1736
2088
  }
1737
2089
  )))
1738
2090
  )
@@ -1740,18 +2092,18 @@ function BuilderCanvas() {
1740
2092
  }
1741
2093
 
1742
2094
  // src/block-builder/components/canvas/ConfigPanel.tsx
1743
- var import_react13 = __toESM(require("react"), 1);
2095
+ var import_react15 = __toESM(require("react"), 1);
1744
2096
 
1745
2097
  // src/block-builder/components/config/BlockConfig.tsx
1746
- var import_react11 = __toESM(require("react"), 1);
2098
+ var import_react13 = __toESM(require("react"), 1);
1747
2099
  function BlockConfig() {
1748
2100
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1749
2101
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1750
2102
  const updateBlock = useBuilderStore((s) => s.updateBlock);
1751
2103
  if (!block) {
1752
- return /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__empty" }, "No block selected.");
2104
+ return /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__empty" }, "No block selected.");
1753
2105
  }
1754
- return /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Slug *"), /* @__PURE__ */ import_react11.default.createElement(
2106
+ return /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Slug *"), /* @__PURE__ */ import_react13.default.createElement(
1755
2107
  "input",
1756
2108
  {
1757
2109
  type: "text",
@@ -1760,7 +2112,7 @@ function BlockConfig() {
1760
2112
  placeholder: "myBlock",
1761
2113
  className: "bb-input"
1762
2114
  }
1763
- ), /* @__PURE__ */ import_react11.default.createElement("span", { className: "bb-form__hint" }, "Unique identifier used in code and database")), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Interface Name"), /* @__PURE__ */ import_react11.default.createElement(
2115
+ ), /* @__PURE__ */ import_react13.default.createElement("span", { className: "bb-form__hint" }, "Unique identifier used in code and database")), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Interface Name"), /* @__PURE__ */ import_react13.default.createElement(
1764
2116
  "input",
1765
2117
  {
1766
2118
  type: "text",
@@ -1769,7 +2121,7 @@ function BlockConfig() {
1769
2121
  placeholder: "MyBlock",
1770
2122
  className: "bb-input"
1771
2123
  }
1772
- )), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Singular Label"), /* @__PURE__ */ import_react11.default.createElement(
2124
+ )), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Singular Label"), /* @__PURE__ */ import_react13.default.createElement(
1773
2125
  "input",
1774
2126
  {
1775
2127
  type: "text",
@@ -1778,7 +2130,7 @@ function BlockConfig() {
1778
2130
  placeholder: "My Block",
1779
2131
  className: "bb-input"
1780
2132
  }
1781
- )), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Plural Label"), /* @__PURE__ */ import_react11.default.createElement(
2133
+ )), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Plural Label"), /* @__PURE__ */ import_react13.default.createElement(
1782
2134
  "input",
1783
2135
  {
1784
2136
  type: "text",
@@ -1787,7 +2139,7 @@ function BlockConfig() {
1787
2139
  placeholder: "My Blocks",
1788
2140
  className: "bb-input"
1789
2141
  }
1790
- ))), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Image URL"), /* @__PURE__ */ import_react11.default.createElement(
2142
+ ))), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Image URL"), /* @__PURE__ */ import_react13.default.createElement(
1791
2143
  "input",
1792
2144
  {
1793
2145
  type: "text",
@@ -1796,38 +2148,62 @@ function BlockConfig() {
1796
2148
  placeholder: "https://...",
1797
2149
  className: "bb-input"
1798
2150
  }
1799
- )), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-stat-box" }, /* @__PURE__ */ import_react11.default.createElement("strong", null, block.fields.length), " field", block.fields.length !== 1 ? "s" : "", " defined"));
2151
+ )), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-stat-box" }, /* @__PURE__ */ import_react13.default.createElement("strong", null, block.fields.length), " field", block.fields.length !== 1 ? "s" : "", " defined"));
1800
2152
  }
1801
2153
 
1802
2154
  // src/block-builder/components/config/FieldConfig.tsx
1803
- var import_react12 = __toESM(require("react"), 1);
2155
+ var import_react14 = __toESM(require("react"), 1);
1804
2156
  var ALL_TYPES = [
1805
2157
  "text",
1806
2158
  "textarea",
2159
+ "richtext",
1807
2160
  "number",
1808
2161
  "email",
2162
+ "url",
2163
+ "color",
1809
2164
  "date",
1810
2165
  "checkbox",
1811
2166
  "select",
1812
- "radio",
1813
- "upload",
2167
+ "multiselect",
2168
+ "image",
2169
+ "file",
1814
2170
  "relationship",
1815
- "json"
2171
+ "json",
2172
+ "array",
2173
+ "group",
2174
+ "blocks",
2175
+ "row",
2176
+ "tabs",
2177
+ "collapsible"
1816
2178
  ];
2179
+ var NO_DEFAULT_VALUE_TYPES = /* @__PURE__ */ new Set([
2180
+ "array",
2181
+ "group",
2182
+ "blocks",
2183
+ "row",
2184
+ "tabs",
2185
+ "collapsible",
2186
+ "image",
2187
+ "file",
2188
+ "relationship",
2189
+ "json"
2190
+ ]);
1817
2191
  function FieldConfig() {
1818
2192
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1819
2193
  const activeFieldId = useBuilderStore((s) => s.activeFieldId);
2194
+ const activeParentPath = useBuilderStore((s) => s.activeParentPath);
1820
2195
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1821
- const field = block?.fields.find((f) => f.id === activeFieldId);
2196
+ const field = block ? getTargetFields(block, activeParentPath)?.find((f) => f.id === activeFieldId) : void 0;
1822
2197
  const updateField = useBuilderStore((s) => s.updateField);
2198
+ const pushParentPath = useBuilderStore((s) => s.pushParentPath);
1823
2199
  if (!activeBlockId || !activeFieldId || !field) {
1824
- return /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__empty" }, "Select a field to configure it.");
2200
+ return /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__empty" }, "Select a field to configure it.");
1825
2201
  }
1826
2202
  function upd(updates) {
1827
2203
  updateField(activeBlockId, activeFieldId, updates);
1828
2204
  }
1829
- const needsOptions = field.type === "select" || field.type === "radio";
1830
- return /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Field Name *"), /* @__PURE__ */ import_react12.default.createElement(
2205
+ const needsOptions = field.type === "select" || field.type === "multiselect";
2206
+ return /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Field Name *"), /* @__PURE__ */ import_react14.default.createElement(
1831
2207
  "input",
1832
2208
  {
1833
2209
  type: "text",
@@ -1836,15 +2212,15 @@ function FieldConfig() {
1836
2212
  placeholder: "fieldName",
1837
2213
  className: "bb-input"
1838
2214
  }
1839
- )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Type *"), /* @__PURE__ */ import_react12.default.createElement(
2215
+ )), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Type *"), /* @__PURE__ */ import_react14.default.createElement(
1840
2216
  "select",
1841
2217
  {
1842
2218
  value: field.type,
1843
2219
  onChange: (e) => upd({ type: e.target.value }),
1844
2220
  className: "bb-input bb-select"
1845
2221
  },
1846
- ALL_TYPES.map((t) => /* @__PURE__ */ import_react12.default.createElement("option", { key: t, value: t }, t))
1847
- ))), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Label"), /* @__PURE__ */ import_react12.default.createElement(
2222
+ ALL_TYPES.map((t) => /* @__PURE__ */ import_react14.default.createElement("option", { key: t, value: t }, t))
2223
+ ))), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Label"), /* @__PURE__ */ import_react14.default.createElement(
1848
2224
  "input",
1849
2225
  {
1850
2226
  type: "text",
@@ -1853,28 +2229,44 @@ function FieldConfig() {
1853
2229
  placeholder: "Human-readable label",
1854
2230
  className: "bb-input"
1855
2231
  }
1856
- )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-flags-row" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
2232
+ )), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-flags-row" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
1857
2233
  "input",
1858
2234
  {
1859
2235
  type: "checkbox",
1860
2236
  checked: field.required ?? false,
1861
2237
  onChange: (e) => upd({ required: e.target.checked })
1862
2238
  }
1863
- ), "Required"), /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
2239
+ ), "Required"), /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
1864
2240
  "input",
1865
2241
  {
1866
2242
  type: "checkbox",
1867
2243
  checked: field.unique ?? false,
1868
2244
  onChange: (e) => upd({ unique: e.target.checked })
1869
2245
  }
1870
- ), "Unique"), /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
2246
+ ), "Unique"), /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
1871
2247
  "input",
1872
2248
  {
1873
2249
  type: "checkbox",
1874
2250
  checked: field.localized ?? false,
1875
2251
  onChange: (e) => upd({ localized: e.target.checked })
1876
2252
  }
1877
- ), "Localized")), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Default Value"), /* @__PURE__ */ import_react12.default.createElement(
2253
+ ), "Localized")), !NO_DEFAULT_VALUE_TYPES.has(field.type) && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Default Value"), field.type === "checkbox" ? /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
2254
+ "input",
2255
+ {
2256
+ type: "checkbox",
2257
+ checked: field.defaultValue === true,
2258
+ onChange: (e) => upd({ defaultValue: e.target.checked })
2259
+ }
2260
+ ), "Checked by default") : field.type === "number" ? /* @__PURE__ */ import_react14.default.createElement(
2261
+ "input",
2262
+ {
2263
+ type: "number",
2264
+ value: typeof field.defaultValue === "number" ? field.defaultValue : "",
2265
+ onChange: (e) => upd({ defaultValue: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
2266
+ placeholder: "0",
2267
+ className: "bb-input"
2268
+ }
2269
+ ) : /* @__PURE__ */ import_react14.default.createElement(
1878
2270
  "input",
1879
2271
  {
1880
2272
  type: "text",
@@ -1883,23 +2275,23 @@ function FieldConfig() {
1883
2275
  placeholder: "Default value",
1884
2276
  className: "bb-input"
1885
2277
  }
1886
- )), field.type === "relationship" && /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Relation To (collection slug)"), /* @__PURE__ */ import_react12.default.createElement(
2278
+ )), field.type === "relationship" && /* @__PURE__ */ import_react14.default.createElement(import_react14.default.Fragment, null, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Relation To (collection slug)"), /* @__PURE__ */ import_react14.default.createElement(
1887
2279
  "input",
1888
2280
  {
1889
2281
  type: "text",
1890
- value: field.relationTo ?? "",
1891
- onChange: (e) => upd({ relationTo: e.target.value }),
2282
+ value: field.collection ?? "",
2283
+ onChange: (e) => upd({ collection: e.target.value }),
1892
2284
  placeholder: "pages",
1893
2285
  className: "bb-input"
1894
2286
  }
1895
- )), /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
2287
+ )), /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
1896
2288
  "input",
1897
2289
  {
1898
2290
  type: "checkbox",
1899
2291
  checked: field.hasMany ?? false,
1900
2292
  onChange: (e) => upd({ hasMany: e.target.checked })
1901
2293
  }
1902
- ), "Has Many")), field.type === "array" && /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Min Rows"), /* @__PURE__ */ import_react12.default.createElement(
2294
+ ), "Has Many")), field.type === "array" && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Min Rows"), /* @__PURE__ */ import_react14.default.createElement(
1903
2295
  "input",
1904
2296
  {
1905
2297
  type: "number",
@@ -1907,7 +2299,7 @@ function FieldConfig() {
1907
2299
  onChange: (e) => upd({ minRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
1908
2300
  className: "bb-input"
1909
2301
  }
1910
- )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Max Rows"), /* @__PURE__ */ import_react12.default.createElement(
2302
+ )), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-form__label" }, "Max Rows"), /* @__PURE__ */ import_react14.default.createElement(
1911
2303
  "input",
1912
2304
  {
1913
2305
  type: "number",
@@ -1915,7 +2307,54 @@ function FieldConfig() {
1915
2307
  onChange: (e) => upd({ maxRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
1916
2308
  className: "bb-input"
1917
2309
  }
1918
- ))), needsOptions && /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-options-label" }, "Options"), (field.options ?? []).map((opt, i) => /* @__PURE__ */ import_react12.default.createElement("div", { key: i, className: "bb-option-row" }, /* @__PURE__ */ import_react12.default.createElement(
2310
+ ))), field.type === "tabs" && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-options-label" }, "Tabs"), (field.tabs ?? []).map((tab, i) => /* @__PURE__ */ import_react14.default.createElement("div", { key: tab.id ?? i, className: "bb-option-row" }, /* @__PURE__ */ import_react14.default.createElement(
2311
+ "input",
2312
+ {
2313
+ type: "text",
2314
+ value: tab.label,
2315
+ placeholder: "Tab label",
2316
+ onChange: (e) => {
2317
+ const next = [...field.tabs ?? []];
2318
+ next[i] = { ...next[i], label: e.target.value };
2319
+ upd({ tabs: next });
2320
+ },
2321
+ className: "bb-input"
2322
+ }
2323
+ ), /* @__PURE__ */ import_react14.default.createElement(
2324
+ "button",
2325
+ {
2326
+ type: "button",
2327
+ onClick: () => pushParentPath(encodeTabPath(field.id, i)),
2328
+ className: "bb-add-option",
2329
+ title: "Edit this tab's fields"
2330
+ },
2331
+ "Edit Fields"
2332
+ ), /* @__PURE__ */ import_react14.default.createElement(
2333
+ "button",
2334
+ {
2335
+ type: "button",
2336
+ onClick: () => {
2337
+ const next = (field.tabs ?? []).filter((_, k) => k !== i);
2338
+ upd({ tabs: next });
2339
+ },
2340
+ className: "bb-option-delete",
2341
+ title: "Remove tab"
2342
+ },
2343
+ "x"
2344
+ ))), /* @__PURE__ */ import_react14.default.createElement(
2345
+ "button",
2346
+ {
2347
+ type: "button",
2348
+ onClick: () => upd({
2349
+ tabs: [
2350
+ ...field.tabs ?? [],
2351
+ { id: uuidv4(), label: `Tab ${(field.tabs?.length ?? 0) + 1}`, fields: [] }
2352
+ ]
2353
+ }),
2354
+ className: "bb-add-option"
2355
+ },
2356
+ "+ Add Tab"
2357
+ )), needsOptions && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-options-label" }, "Options"), (field.options ?? []).map((opt, i) => /* @__PURE__ */ import_react14.default.createElement("div", { key: i, className: "bb-option-row" }, /* @__PURE__ */ import_react14.default.createElement(
1919
2358
  "input",
1920
2359
  {
1921
2360
  type: "text",
@@ -1928,7 +2367,7 @@ function FieldConfig() {
1928
2367
  },
1929
2368
  className: "bb-input"
1930
2369
  }
1931
- ), /* @__PURE__ */ import_react12.default.createElement(
2370
+ ), /* @__PURE__ */ import_react14.default.createElement(
1932
2371
  "input",
1933
2372
  {
1934
2373
  type: "text",
@@ -1941,7 +2380,7 @@ function FieldConfig() {
1941
2380
  },
1942
2381
  className: "bb-input"
1943
2382
  }
1944
- ), /* @__PURE__ */ import_react12.default.createElement(
2383
+ ), /* @__PURE__ */ import_react14.default.createElement(
1945
2384
  "button",
1946
2385
  {
1947
2386
  type: "button",
@@ -1953,7 +2392,7 @@ function FieldConfig() {
1953
2392
  title: "Remove option"
1954
2393
  },
1955
2394
  "x"
1956
- ))), /* @__PURE__ */ import_react12.default.createElement(
2395
+ ))), /* @__PURE__ */ import_react14.default.createElement(
1957
2396
  "button",
1958
2397
  {
1959
2398
  type: "button",
@@ -1966,11 +2405,11 @@ function FieldConfig() {
1966
2405
 
1967
2406
  // src/block-builder/components/canvas/ConfigPanel.tsx
1968
2407
  function ConfigPanel() {
1969
- const [tab, setTab] = (0, import_react13.useState)("block");
2408
+ const [tab, setTab] = (0, import_react15.useState)("block");
1970
2409
  const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1971
2410
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1972
2411
  const activeTab = activeFieldId ? "field" : tab;
1973
- return /* @__PURE__ */ import_react13.default.createElement("div", { className: `bb-config${isReadOnly ? " bb-config--readonly" : ""}` }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-config__tabs" }, ["block", "field"].map((t) => /* @__PURE__ */ import_react13.default.createElement(
2412
+ return /* @__PURE__ */ import_react15.default.createElement("div", { className: `bb-config${isReadOnly ? " bb-config--readonly" : ""}` }, /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-config__tabs" }, ["block", "field"].map((t) => /* @__PURE__ */ import_react15.default.createElement(
1974
2413
  "button",
1975
2414
  {
1976
2415
  key: t,
@@ -1979,11 +2418,11 @@ function ConfigPanel() {
1979
2418
  className: `bb-config__tab${activeTab === t ? " bb-config__tab--active" : ""}`
1980
2419
  },
1981
2420
  t
1982
- ))), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-config__body" }, activeTab === "block" ? /* @__PURE__ */ import_react13.default.createElement(BlockConfig, null) : /* @__PURE__ */ import_react13.default.createElement(FieldConfig, null)), isReadOnly && /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-config__readonly-overlay" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "bb-config__readonly-label" }, "Read only")));
2421
+ ))), /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-config__body" }, activeTab === "block" ? /* @__PURE__ */ import_react15.default.createElement(BlockConfig, null) : /* @__PURE__ */ import_react15.default.createElement(FieldConfig, null)), isReadOnly && /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-config__readonly-overlay" }, /* @__PURE__ */ import_react15.default.createElement("span", { className: "bb-config__readonly-label" }, "Read only")));
1983
2422
  }
1984
2423
 
1985
2424
  // src/block-builder/components/sidebar/FieldPalette.tsx
1986
- var import_react14 = __toESM(require("react"), 1);
2425
+ var import_react16 = __toESM(require("react"), 1);
1987
2426
  var import_lucide_react4 = require("lucide-react");
1988
2427
 
1989
2428
  // src/block-builder/lib/field-palette.ts
@@ -1997,11 +2436,17 @@ var FIELD_PALETTE = [
1997
2436
  { type: "checkbox", label: "Checkbox", description: "Boolean toggle", icon: "CheckSquare", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1998
2437
  // Choice
1999
2438
  { type: "select", label: "Select", description: "Dropdown selection", icon: "ChevronDown", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
2000
- { type: "radio", label: "Radio", description: "Radio button group", icon: "Circle", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
2001
2439
  // Media
2002
- { 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" },
2440
+ { type: "image", label: "Image", description: "Image upload", icon: "Image", category: "media", color: "bg-green-500/20 text-green-400 border-green-500/30" },
2441
+ { type: "file", label: "File", description: "File upload", icon: "Upload", category: "media", color: "bg-green-500/20 text-green-400 border-green-500/30" },
2003
2442
  // Relational
2004
2443
  { 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" },
2444
+ // Layout
2445
+ { 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" },
2446
+ { type: "group", label: "Group", description: "Grouped fields object", icon: "Box", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2447
+ { type: "row", label: "Row", description: "Horizontal layout row", icon: "Columns", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2448
+ { type: "tabs", label: "Tabs", description: "Tabbed layout container", icon: "Folder", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2449
+ { type: "collapsible", label: "Collapsible", description: "Expandable field group", icon: "ChevronDown", category: "layout", color: "bg-pink-500/20 text-pink-400 border-pink-500/30" },
2005
2450
  // Advanced
2006
2451
  { 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" }
2007
2452
  ];
@@ -2010,6 +2455,7 @@ var FIELD_CATEGORIES = [
2010
2455
  { id: "choice", label: "Choice" },
2011
2456
  { id: "media", label: "Media" },
2012
2457
  { id: "relational", label: "Relational" },
2458
+ { id: "layout", label: "Layout" },
2013
2459
  { id: "advanced", label: "Advanced" }
2014
2460
  ];
2015
2461
  function getFieldMeta(type) {
@@ -2031,10 +2477,13 @@ var ICON_MAP2 = {
2031
2477
  Link: import_lucide_react4.Link,
2032
2478
  List: import_lucide_react4.List,
2033
2479
  Folder: import_lucide_react4.Folder,
2034
- Braces: import_lucide_react4.Braces
2480
+ Braces: import_lucide_react4.Braces,
2481
+ Image: import_lucide_react4.Image,
2482
+ Box: import_lucide_react4.Box,
2483
+ Columns: import_lucide_react4.Columns
2035
2484
  };
2036
2485
  function FieldPalette() {
2037
- const [search, setSearch] = (0, import_react14.useState)("");
2486
+ const [search, setSearch] = (0, import_react16.useState)("");
2038
2487
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
2039
2488
  const addField = useBuilderStore((s) => s.addField);
2040
2489
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
@@ -2045,7 +2494,7 @@ function FieldPalette() {
2045
2494
  if (!activeBlockId || isReadOnly) return;
2046
2495
  addField(activeBlockId, type);
2047
2496
  }
2048
- return /* @__PURE__ */ import_react14.default.createElement("div", { className: `bb-sidebar bb-sidebar--200 bb-sidebar--palette${isReadOnly ? " bb-sidebar--readonly" : ""}` }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-sidebar__title" }, "Fields")), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__search-wrap" }, /* @__PURE__ */ import_react14.default.createElement(
2497
+ return /* @__PURE__ */ import_react16.default.createElement("div", { className: `bb-sidebar bb-sidebar--200 bb-sidebar--palette${isReadOnly ? " bb-sidebar--readonly" : ""}` }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-sidebar__title" }, "Fields")), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-palette__search-wrap" }, /* @__PURE__ */ import_react16.default.createElement(
2049
2498
  "input",
2050
2499
  {
2051
2500
  type: "text",
@@ -2054,7 +2503,7 @@ function FieldPalette() {
2054
2503
  placeholder: "Search...",
2055
2504
  className: "bb-palette__search"
2056
2505
  }
2057
- )), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-sidebar__body" }, search.trim() ? /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__items" }, filtered.map((item) => /* @__PURE__ */ import_react14.default.createElement(
2506
+ )), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-sidebar__body" }, search.trim() ? /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-palette__items" }, filtered.map((item) => /* @__PURE__ */ import_react16.default.createElement(
2058
2507
  FieldButton,
2059
2508
  {
2060
2509
  key: item.type,
@@ -2066,7 +2515,7 @@ function FieldPalette() {
2066
2515
  }
2067
2516
  ))) : FIELD_CATEGORIES.map((cat) => {
2068
2517
  const items = FIELD_PALETTE.filter((f) => f.category === cat.id);
2069
- return /* @__PURE__ */ import_react14.default.createElement("div", { key: cat.id, style: { marginTop: 6 } }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__cat-label" }, cat.label), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__items" }, items.map((item) => /* @__PURE__ */ import_react14.default.createElement(
2518
+ return /* @__PURE__ */ import_react16.default.createElement("div", { key: cat.id, style: { marginTop: 6 } }, /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-palette__cat-label" }, cat.label), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-palette__items" }, items.map((item) => /* @__PURE__ */ import_react16.default.createElement(
2070
2519
  FieldButton,
2071
2520
  {
2072
2521
  key: item.type,
@@ -2077,7 +2526,7 @@ function FieldPalette() {
2077
2526
  disabled: !activeBlockId || isReadOnly
2078
2527
  }
2079
2528
  ))));
2080
- })), !activeBlockId && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-sidebar__footer" }, "Select a block first"));
2529
+ })), !activeBlockId && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-sidebar__footer" }, "Select a block first"));
2081
2530
  }
2082
2531
  function FieldButton({
2083
2532
  type,
@@ -2088,7 +2537,7 @@ function FieldButton({
2088
2537
  }) {
2089
2538
  const meta = getFieldMeta(type);
2090
2539
  const Icon = ICON_MAP2[icon];
2091
- return /* @__PURE__ */ import_react14.default.createElement(
2540
+ return /* @__PURE__ */ import_react16.default.createElement(
2092
2541
  "button",
2093
2542
  {
2094
2543
  type: "button",
@@ -2097,14 +2546,291 @@ function FieldButton({
2097
2546
  title: meta?.description,
2098
2547
  className: "bb-palette__item"
2099
2548
  },
2100
- /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-palette__item__icon" }, Icon ? /* @__PURE__ */ import_react14.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null),
2101
- /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-palette__item__label" }, label),
2102
- /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-palette__item__type" }, type)
2549
+ /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-palette__item__icon" }, Icon ? /* @__PURE__ */ import_react16.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null),
2550
+ /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-palette__item__label" }, label),
2551
+ /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-palette__item__type" }, type)
2103
2552
  );
2104
2553
  }
2105
2554
 
2106
2555
  // src/block-builder/components/canvas/CodePreview.tsx
2107
- var import_react15 = __toESM(require("react"), 1);
2556
+ var import_react17 = __toESM(require("react"), 1);
2557
+
2558
+ // src/block-builder/lib/codegen.ts
2559
+ function indent(n) {
2560
+ return " ".repeat(n);
2561
+ }
2562
+ function safeStr(s) {
2563
+ return JSON.stringify(s);
2564
+ }
2565
+ function listToCode(items, body, depth) {
2566
+ if (items.length === 0) return "[]";
2567
+ const pad = indent(depth);
2568
+ const innerPad = indent(depth + 1);
2569
+ const entries = items.map((item) => `${innerPad}{
2570
+ ${body(item)}
2571
+ ${innerPad}}`).join(",\n");
2572
+ return `[
2573
+ ${entries}
2574
+ ${pad}]`;
2575
+ }
2576
+ var UNNAMED_TYPES = /* @__PURE__ */ new Set(["row", "tabs", "collapsible"]);
2577
+ var NO_LABEL_TYPES = /* @__PURE__ */ new Set(["row"]);
2578
+ var NO_LOCALIZED_TYPES = /* @__PURE__ */ new Set(["row", "tabs", "collapsible"]);
2579
+ var NO_ADMIN_DESCRIPTION_TYPES = /* @__PURE__ */ new Set(["row", "tabs"]);
2580
+ var FIELDS_CONTAINER_TYPES = /* @__PURE__ */ new Set([
2581
+ "array",
2582
+ "group",
2583
+ "row",
2584
+ "collapsible"
2585
+ ]);
2586
+ var PAYLOAD_TYPE = {
2587
+ richtext: "richText",
2588
+ image: "upload",
2589
+ file: "upload",
2590
+ multiselect: "select",
2591
+ url: "text",
2592
+ color: "text"
2593
+ };
2594
+ var UPLOAD_TYPES = /* @__PURE__ */ new Set(["image", "file"]);
2595
+ var DEFAULT_UPLOAD_COLLECTION = "media";
2596
+ function payloadType(type) {
2597
+ return PAYLOAD_TYPE[type] ?? type;
2598
+ }
2599
+ function fieldToCode(field, depth = 1) {
2600
+ const pad = indent(depth);
2601
+ const innerPad = indent(depth + 1);
2602
+ const lines = [];
2603
+ if (!UNNAMED_TYPES.has(field.type)) {
2604
+ lines.push(`${pad}name: ${safeStr(field.name)}`);
2605
+ }
2606
+ lines.push(`${pad}type: '${payloadType(field.type)}'`);
2607
+ if (field.label && !NO_LABEL_TYPES.has(field.type)) {
2608
+ lines.push(`${pad}label: ${safeStr(field.label)}`);
2609
+ }
2610
+ if (field.required) lines.push(`${pad}required: true`);
2611
+ if (field.unique) lines.push(`${pad}unique: true`);
2612
+ if (field.localized && !NO_LOCALIZED_TYPES.has(field.type)) {
2613
+ lines.push(`${pad}localized: true`);
2614
+ }
2615
+ if (field.defaultValue !== void 0) {
2616
+ const val = typeof field.defaultValue === "string" ? safeStr(String(field.defaultValue)) : field.defaultValue;
2617
+ lines.push(`${pad}defaultValue: ${val}`);
2618
+ }
2619
+ if (field.type === "richtext") {
2620
+ lines.push(`${pad}editor: lexicalEditor({})`);
2621
+ }
2622
+ if (field.options && field.options.length > 0) {
2623
+ const opts = field.options.map((o) => `{ label: ${safeStr(o.label)}, value: ${safeStr(o.value)} }`).join(`, `);
2624
+ lines.push(`${pad}options: [${opts}]`);
2625
+ }
2626
+ if (UPLOAD_TYPES.has(field.type)) {
2627
+ lines.push(
2628
+ `${pad}relationTo: ${safeStr(field.collection || DEFAULT_UPLOAD_COLLECTION)}`
2629
+ );
2630
+ } else if (field.collection) {
2631
+ lines.push(`${pad}relationTo: ${safeStr(field.collection)}`);
2632
+ }
2633
+ if (field.type === "multiselect") {
2634
+ lines.push(`${pad}hasMany: true`);
2635
+ } else if (field.hasMany !== void 0) {
2636
+ lines.push(`${pad}hasMany: ${field.hasMany}`);
2637
+ }
2638
+ if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
2639
+ if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
2640
+ const children = field.fields ?? [];
2641
+ if (children.length > 0 || FIELDS_CONTAINER_TYPES.has(field.type)) {
2642
+ lines.push(
2643
+ `${pad}fields: ${listToCode(children, (f) => fieldToCode(f, depth + 2), depth)}`
2644
+ );
2645
+ }
2646
+ if (field.type === "blocks") {
2647
+ lines.push(`${pad}blocks: []`);
2648
+ }
2649
+ if (field.type === "tabs") {
2650
+ const tabsCode = (field.tabs ?? []).map((tab) => {
2651
+ const tabPad = indent(depth + 2);
2652
+ const tabLines = [];
2653
+ if (tab.name) tabLines.push(`${tabPad}name: ${safeStr(tab.name)}`);
2654
+ tabLines.push(`${tabPad}label: ${safeStr(tab.label)}`);
2655
+ tabLines.push(
2656
+ `${tabPad}fields: ${listToCode(tab.fields ?? [], (f) => fieldToCode(f, depth + 4), depth + 2)}`
2657
+ );
2658
+ return `${innerPad}{
2659
+ ${tabLines.join(",\n")}
2660
+ ${innerPad}}`;
2661
+ }).join(",\n");
2662
+ lines.push(
2663
+ `${pad}tabs: ${(field.tabs ?? []).length > 0 ? `[
2664
+ ${tabsCode}
2665
+ ${pad}]` : "[]"}`
2666
+ );
2667
+ }
2668
+ const adminParts = [];
2669
+ if (field.admin?.description && !NO_ADMIN_DESCRIPTION_TYPES.has(field.type))
2670
+ adminParts.push(`description: ${safeStr(field.admin.description)}`);
2671
+ if (field.admin?.placeholder)
2672
+ adminParts.push(`placeholder: ${safeStr(field.admin.placeholder)}`);
2673
+ if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
2674
+ if (field.admin?.hidden) adminParts.push(`hidden: true`);
2675
+ if (adminParts.length > 0) {
2676
+ lines.push(`${pad}admin: { ${adminParts.join(", ")} }`);
2677
+ }
2678
+ return lines.join(",\n");
2679
+ }
2680
+ function generateBlockCode(block) {
2681
+ const hasRichText = containsRichText(block.fields);
2682
+ const imports = [`import type { Block } from 'payload'`];
2683
+ if (hasRichText) {
2684
+ imports.push(`import { lexicalEditor } from '@payloadcms/richtext-lexical'`);
2685
+ }
2686
+ const fieldsCode = block.fields.map((f) => ` {
2687
+ ${fieldToCode(f, 3)}
2688
+ }`).join(",\n");
2689
+ const labelsCode = block.labels ? `
2690
+ labels: {
2691
+ singular: ${safeStr(block.labels.singular ?? block.slug)},
2692
+ plural: ${safeStr(block.labels.plural ?? block.slug + "s")},
2693
+ },` : "";
2694
+ const interfaceLine = block.interfaceName ? `
2695
+ interfaceName: ${safeStr(block.interfaceName)},` : "";
2696
+ const exportName = block.interfaceName ?? toCamelCase(block.slug);
2697
+ return [
2698
+ imports.join("\n"),
2699
+ "",
2700
+ `export const ${exportName}: Block = {`,
2701
+ ` slug: ${safeStr(block.slug)},${interfaceLine}${labelsCode}`,
2702
+ ` fields: [`,
2703
+ fieldsCode,
2704
+ ` ],`,
2705
+ `}`,
2706
+ ""
2707
+ ].join("\n");
2708
+ }
2709
+ function containsRichText(fields) {
2710
+ return fields.some((f) => {
2711
+ if (f.type === "richtext") return true;
2712
+ if (f.fields && containsRichText(f.fields)) return true;
2713
+ if (f.tabs?.some((tab) => containsRichText(tab.fields ?? []))) return true;
2714
+ return false;
2715
+ });
2716
+ }
2717
+ function toCamelCase(slug) {
2718
+ return slug.split(/[-_]/).map(
2719
+ (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
2720
+ ).join("");
2721
+ }
2722
+ function toPascalCase(slug) {
2723
+ return slug.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2724
+ }
2725
+ function getTsType(field) {
2726
+ switch (field.type) {
2727
+ case "number":
2728
+ return "number";
2729
+ case "checkbox":
2730
+ return "boolean";
2731
+ case "text":
2732
+ case "textarea":
2733
+ case "email":
2734
+ case "url":
2735
+ case "color":
2736
+ case "select":
2737
+ case "date":
2738
+ return "string";
2739
+ case "multiselect":
2740
+ return "string[]";
2741
+ case "group":
2742
+ return field.fields ? objectType(flattenProps(field.fields)) : "any";
2743
+ case "array":
2744
+ if (field.fields) {
2745
+ const inner = flattenProps(field.fields).map((p) => `${p.name}: ${p.type}`).join("; ");
2746
+ return `Array<{ id: string; ${inner} }>`;
2747
+ }
2748
+ return "any[]";
2749
+ default:
2750
+ return "any";
2751
+ }
2752
+ }
2753
+ function objectType(props) {
2754
+ return props.length > 0 ? `{ ${props.map((p) => `${p.name}: ${p.type}`).join("; ")} }` : "Record<string, unknown>";
2755
+ }
2756
+ function flattenProps(fields) {
2757
+ const out = [];
2758
+ for (const f of fields) {
2759
+ if (f.type === "row" || f.type === "collapsible") {
2760
+ out.push(...flattenProps(f.fields ?? []));
2761
+ } else if (f.type === "tabs") {
2762
+ for (const tab of f.tabs ?? []) {
2763
+ if (tab.name) {
2764
+ out.push({ name: tab.name, type: objectType(flattenProps(tab.fields ?? [])) });
2765
+ } else {
2766
+ out.push(...flattenProps(tab.fields ?? []));
2767
+ }
2768
+ }
2769
+ } else {
2770
+ out.push({ name: f.name, type: getTsType(f) });
2771
+ }
2772
+ }
2773
+ return out;
2774
+ }
2775
+ function generateReactComponent(block) {
2776
+ const componentName = block.interfaceName ?? toPascalCase(block.slug);
2777
+ const propsName = `${componentName}Props`;
2778
+ const propList = flattenProps(block.fields);
2779
+ const propsCode = propList.map((p) => ` ${p.name}: ${p.type}`).join("\n");
2780
+ const fieldsJsx = propList.map((p) => ` <div className="field-${p.name}">
2781
+ {/* ${p.name} */}
2782
+ {String(props.${p.name})}
2783
+ </div>`).join("\n");
2784
+ const code = [
2785
+ `import React from 'react'`,
2786
+ ``,
2787
+ `export type ${propsName} = {`,
2788
+ propsCode,
2789
+ `}`,
2790
+ ``,
2791
+ `export function ${componentName}(props: ${propsName}) {`,
2792
+ ` return (`,
2793
+ ` <div className="${block.slug}">`,
2794
+ fieldsJsx,
2795
+ ` </div>`,
2796
+ ` )`,
2797
+ `}`,
2798
+ ``
2799
+ ].join("\n");
2800
+ return {
2801
+ filename: `${componentName}.tsx`,
2802
+ code,
2803
+ language: "typescript"
2804
+ };
2805
+ }
2806
+ function generateBlockOutput(block) {
2807
+ return {
2808
+ filename: `${block.slug}.ts`,
2809
+ code: generateBlockCode(block),
2810
+ language: "typescript"
2811
+ };
2812
+ }
2813
+ function generateAllBlocks(blocks, options = {}) {
2814
+ return blocks.flatMap(
2815
+ (block) => options.react ? [generateBlockOutput(block), generateReactComponent(block)] : [generateBlockOutput(block)]
2816
+ );
2817
+ }
2818
+ function generateIndexFile(blocks) {
2819
+ const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
2820
+ const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
2821
+ const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
2822
+ const code = [
2823
+ imports,
2824
+ "",
2825
+ `export const blocks = [`,
2826
+ exportList,
2827
+ `] as const`,
2828
+ ""
2829
+ ].join("\n");
2830
+ return { filename: "index.ts", code, language: "typescript" };
2831
+ }
2832
+
2833
+ // src/block-builder/components/canvas/CodePreview.tsx
2108
2834
  var KEYWORDS = /* @__PURE__ */ new Set([
2109
2835
  "import",
2110
2836
  "export",
@@ -2192,22 +2918,22 @@ var TOKEN_COLORS = {
2192
2918
  };
2193
2919
  function HighlightedCode({ code }) {
2194
2920
  const tokens = tokenise(code);
2195
- return /* @__PURE__ */ import_react15.default.createElement("code", { style: { fontFamily: "inherit" } }, tokens.map((tok, i) => /* @__PURE__ */ import_react15.default.createElement("span", { key: i, style: { color: TOKEN_COLORS[tok.kind] } }, tok.text)));
2921
+ return /* @__PURE__ */ import_react17.default.createElement("code", { style: { fontFamily: "inherit" } }, tokens.map((tok, i) => /* @__PURE__ */ import_react17.default.createElement("span", { key: i, style: { color: TOKEN_COLORS[tok.kind] } }, tok.text)));
2196
2922
  }
2197
2923
  function CodePreview() {
2198
2924
  const blocks = useBuilderStore((s) => s.blocks);
2199
- const [fileMap, setFileMap] = (0, import_react15.useState)({});
2200
- const [activeFile, setActiveFile] = (0, import_react15.useState)(null);
2201
- const [copied, setCopied] = (0, import_react15.useState)(false);
2202
- const timerRef = (0, import_react15.useRef)(null);
2203
- const copyTimerRef = (0, import_react15.useRef)(null);
2204
- const regenerate = (0, import_react15.useCallback)(() => {
2925
+ const [fileMap, setFileMap] = (0, import_react17.useState)({});
2926
+ const [activeFile, setActiveFile] = (0, import_react17.useState)(null);
2927
+ const [copied, setCopied] = (0, import_react17.useState)(false);
2928
+ const timerRef = (0, import_react17.useRef)(null);
2929
+ const copyTimerRef = (0, import_react17.useRef)(null);
2930
+ const regenerate = (0, import_react17.useCallback)(() => {
2205
2931
  if (blocks.length === 0) {
2206
2932
  setFileMap({});
2207
2933
  setActiveFile(null);
2208
2934
  return;
2209
2935
  }
2210
- const blockOutputs = generateAllBlocks(blocks);
2936
+ const blockOutputs = generateAllBlocks(blocks, { react: true });
2211
2937
  const indexOutput = generateIndexFile(blocks);
2212
2938
  const next = {};
2213
2939
  for (const out of blockOutputs) next[out.filename] = out.code;
@@ -2218,7 +2944,7 @@ function CodePreview() {
2218
2944
  return prev && keys.includes(prev) ? prev : keys[0] ?? null;
2219
2945
  });
2220
2946
  }, [blocks]);
2221
- (0, import_react15.useEffect)(() => {
2947
+ (0, import_react17.useEffect)(() => {
2222
2948
  if (timerRef.current) clearTimeout(timerRef.current);
2223
2949
  timerRef.current = setTimeout(regenerate, 300);
2224
2950
  return () => {
@@ -2237,7 +2963,7 @@ function CodePreview() {
2237
2963
  const activeCode = activeFile ? fileMap[activeFile] ?? "" : "";
2238
2964
  const lineCount = activeCode ? activeCode.split("\n").length : 0;
2239
2965
  if (fileNames.length === 0) {
2240
- return /* @__PURE__ */ import_react15.default.createElement(
2966
+ return /* @__PURE__ */ import_react17.default.createElement(
2241
2967
  "div",
2242
2968
  {
2243
2969
  className: "code-preview",
@@ -2254,7 +2980,7 @@ function CodePreview() {
2254
2980
  "Add a block to see generated TypeScript code"
2255
2981
  );
2256
2982
  }
2257
- return /* @__PURE__ */ import_react15.default.createElement("div", { className: "code-preview", style: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }, /* @__PURE__ */ import_react15.default.createElement(
2983
+ return /* @__PURE__ */ import_react17.default.createElement("div", { className: "code-preview", style: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }, /* @__PURE__ */ import_react17.default.createElement(
2258
2984
  "div",
2259
2985
  {
2260
2986
  style: {
@@ -2268,7 +2994,7 @@ function CodePreview() {
2268
2994
  },
2269
2995
  fileNames.map((name) => {
2270
2996
  const isActive = activeFile === name;
2271
- return /* @__PURE__ */ import_react15.default.createElement(
2997
+ return /* @__PURE__ */ import_react17.default.createElement(
2272
2998
  "button",
2273
2999
  {
2274
3000
  key: name,
@@ -2291,8 +3017,8 @@ function CodePreview() {
2291
3017
  name
2292
3018
  );
2293
3019
  }),
2294
- /* @__PURE__ */ import_react15.default.createElement("div", { style: { flex: 1 } }),
2295
- lineCount > 0 && /* @__PURE__ */ import_react15.default.createElement(
3020
+ /* @__PURE__ */ import_react17.default.createElement("div", { style: { flex: 1 } }),
3021
+ lineCount > 0 && /* @__PURE__ */ import_react17.default.createElement(
2296
3022
  "span",
2297
3023
  {
2298
3024
  style: {
@@ -2305,7 +3031,7 @@ function CodePreview() {
2305
3031
  lineCount,
2306
3032
  " lines"
2307
3033
  ),
2308
- /* @__PURE__ */ import_react15.default.createElement(
3034
+ /* @__PURE__ */ import_react17.default.createElement(
2309
3035
  "button",
2310
3036
  {
2311
3037
  type: "button",
@@ -2328,7 +3054,7 @@ function CodePreview() {
2328
3054
  },
2329
3055
  copied ? "Copied" : "Copy"
2330
3056
  )
2331
- ), /* @__PURE__ */ import_react15.default.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ import_react15.default.createElement(
3057
+ ), /* @__PURE__ */ import_react17.default.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ import_react17.default.createElement(
2332
3058
  "div",
2333
3059
  {
2334
3060
  style: {
@@ -2345,8 +3071,8 @@ function CodePreview() {
2345
3071
  },
2346
3072
  "aria-hidden": "true"
2347
3073
  },
2348
- activeCode.split("\n").map((_, i) => /* @__PURE__ */ import_react15.default.createElement("div", { key: i }, i + 1))
2349
- ), /* @__PURE__ */ import_react15.default.createElement(
3074
+ activeCode.split("\n").map((_, i) => /* @__PURE__ */ import_react17.default.createElement("div", { key: i }, i + 1))
3075
+ ), /* @__PURE__ */ import_react17.default.createElement(
2350
3076
  "pre",
2351
3077
  {
2352
3078
  style: {
@@ -2361,69 +3087,214 @@ function CodePreview() {
2361
3087
  whiteSpace: "pre"
2362
3088
  }
2363
3089
  },
2364
- /* @__PURE__ */ import_react15.default.createElement(HighlightedCode, { code: activeCode })
3090
+ /* @__PURE__ */ import_react17.default.createElement(HighlightedCode, { code: activeCode })
2365
3091
  )));
2366
3092
  }
2367
3093
 
3094
+ // src/block-builder/components/canvas/LivePreview.tsx
3095
+ var import_react18 = __toESM(require("react"), 1);
3096
+ var import_lucide_react5 = require("lucide-react");
3097
+ var TYPE_ICON = {
3098
+ text: import_lucide_react5.Type,
3099
+ textarea: import_lucide_react5.AlignLeft,
3100
+ richtext: import_lucide_react5.AlignJustify,
3101
+ number: import_lucide_react5.Hash,
3102
+ email: import_lucide_react5.Mail,
3103
+ url: import_lucide_react5.Link,
3104
+ date: import_lucide_react5.Calendar,
3105
+ checkbox: import_lucide_react5.CheckSquare,
3106
+ select: import_lucide_react5.ChevronDown,
3107
+ image: import_lucide_react5.Image,
3108
+ file: import_lucide_react5.Upload,
3109
+ relationship: import_lucide_react5.Link,
3110
+ json: import_lucide_react5.Braces,
3111
+ array: import_lucide_react5.List,
3112
+ group: import_lucide_react5.Box,
3113
+ row: import_lucide_react5.Columns,
3114
+ tabs: import_lucide_react5.Folder,
3115
+ collapsible: import_lucide_react5.ChevronDown
3116
+ };
3117
+ function TypeIcon({ type }) {
3118
+ const Icon = TYPE_ICON[type] ?? import_lucide_react5.Type;
3119
+ return /* @__PURE__ */ import_react18.default.createElement(Icon, { size: 13, strokeWidth: 1.75 });
3120
+ }
3121
+ function fieldDisplayLabel(field) {
3122
+ return field.label || field.name || "Untitled field";
3123
+ }
3124
+ function fullFieldPath(field, prefix) {
3125
+ const name = field.name || "\u2014";
3126
+ return prefix ? `${prefix}.${name}` : name;
3127
+ }
3128
+ function fieldPlaceholder(field) {
3129
+ if (field.admin?.placeholder) return field.admin.placeholder;
3130
+ switch (field.type) {
3131
+ case "email":
3132
+ return "name@example.com";
3133
+ case "url":
3134
+ return "https://example.com";
3135
+ case "textarea":
3136
+ case "richtext":
3137
+ return `Write ${fieldDisplayLabel(field).toLowerCase()} here\u2026`;
3138
+ case "text":
3139
+ default:
3140
+ return `Enter ${fieldDisplayLabel(field).toLowerCase()}`;
3141
+ }
3142
+ }
3143
+ function pluralize(count, word) {
3144
+ return `${count} ${word}${count === 1 ? "" : "s"}`;
3145
+ }
3146
+ function FieldMeta({ field, path }) {
3147
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field__meta" }, path !== null && /* @__PURE__ */ import_react18.default.createElement("code", { className: "bb-preview-field__slug" }, path), /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__badge" }, field.type));
3148
+ }
3149
+ function FieldHead({ field, prefix }) {
3150
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field__head" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ import_react18.default.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field), field.required && /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__required" }, "*")), /* @__PURE__ */ import_react18.default.createElement(FieldMeta, { field, path: fullFieldPath(field, prefix) })));
3151
+ }
3152
+ function PreviewField({ field, prefix = "" }) {
3153
+ switch (field.type) {
3154
+ case "text":
3155
+ case "url":
3156
+ case "email":
3157
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("input", { className: "bb-preview-input", disabled: true, placeholder: fieldPlaceholder(field) }));
3158
+ case "textarea":
3159
+ case "richtext":
3160
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("textarea", { className: "bb-preview-textarea", disabled: true, rows: 3, placeholder: fieldPlaceholder(field) }));
3161
+ case "number":
3162
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("input", { className: "bb-preview-input", type: "number", disabled: true, placeholder: field.admin?.placeholder ?? "0" }));
3163
+ case "date":
3164
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("input", { className: "bb-preview-input", type: "date", disabled: true }));
3165
+ case "checkbox":
3166
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement("label", { className: "bb-preview-checkbox" }, /* @__PURE__ */ import_react18.default.createElement("input", { type: "checkbox", className: "bb-preview-checkbox__input", disabled: true }), /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ import_react18.default.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field), field.required && /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__required" }, "*")), /* @__PURE__ */ import_react18.default.createElement(FieldMeta, { field, path: fullFieldPath(field, prefix) }))));
3167
+ case "select":
3168
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("select", { className: "bb-preview-select", disabled: true }, /* @__PURE__ */ import_react18.default.createElement("option", null, field.options?.[0]?.label ?? "Select an option")));
3169
+ case "json":
3170
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-placeholder" }, "Raw JSON data"));
3171
+ case "image":
3172
+ case "file":
3173
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-placeholder bb-preview-placeholder--upload" }, /* @__PURE__ */ import_react18.default.createElement(TypeIcon, { type: field.type }), field.type === "image" ? "Image upload" : "File upload"));
3174
+ case "relationship":
3175
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-placeholder bb-preview-placeholder--upload" }, /* @__PURE__ */ import_react18.default.createElement(TypeIcon, { type: field.type }), field.collection ? `Linked to \u201C${field.collection}\u201D` : "No collection set", field.hasMany ? " \xB7 multiple" : ""));
3176
+ case "array": {
3177
+ const ownPath = fullFieldPath(field, prefix);
3178
+ return /* @__PURE__ */ import_react18.default.createElement(PreviewGroup, { field, ownPath, sublabel: `repeating \xB7 ${pluralize(field.fields?.length ?? 0, "field")} per row` }, (field.fields ?? []).map((f) => /* @__PURE__ */ import_react18.default.createElement(PreviewField, { key: f.id, field: f, prefix: `${ownPath}[]` })));
3179
+ }
3180
+ case "group": {
3181
+ const ownPath = fullFieldPath(field, prefix);
3182
+ return /* @__PURE__ */ import_react18.default.createElement(PreviewGroup, { field, ownPath }, (field.fields ?? []).map((f) => /* @__PURE__ */ import_react18.default.createElement(PreviewField, { key: f.id, field: f, prefix: ownPath })));
3183
+ }
3184
+ case "collapsible":
3185
+ return /* @__PURE__ */ import_react18.default.createElement(PreviewGroup, { field, ownPath: null }, (field.fields ?? []).map((f) => /* @__PURE__ */ import_react18.default.createElement(PreviewField, { key: f.id, field: f, prefix })));
3186
+ case "row":
3187
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-row" }, (field.fields ?? []).map((f) => /* @__PURE__ */ import_react18.default.createElement(PreviewField, { key: f.id, field: f, prefix })));
3188
+ case "tabs":
3189
+ return /* @__PURE__ */ import_react18.default.createElement(PreviewTabs, { field, prefix });
3190
+ default:
3191
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field" }, /* @__PURE__ */ import_react18.default.createElement(FieldHead, { field, prefix }), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-placeholder" }, "Unsupported preview for \u201C", field.type, "\u201D"));
3192
+ }
3193
+ }
3194
+ function PreviewGroup({ field, ownPath, sublabel, children }) {
3195
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-group" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-group__header" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ import_react18.default.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field), sublabel && /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-group__sublabel" }, sublabel)), /* @__PURE__ */ import_react18.default.createElement(FieldMeta, { field, path: ownPath }))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-group__body" }, import_react18.default.Children.count(children) > 0 ? children : /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-placeholder" }, "No fields inside yet")));
3196
+ }
3197
+ function PreviewTabs({ field, prefix }) {
3198
+ const [active, setActive] = (0, import_react18.useState)(0);
3199
+ const tabs = field.tabs ?? [];
3200
+ const tab = tabs[active];
3201
+ const tabPrefix = tab?.name ? prefix ? `${prefix}.${tab.name}` : tab.name : prefix;
3202
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-group" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-group__header" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__icon" }, /* @__PURE__ */ import_react18.default.createElement(TypeIcon, { type: field.type })), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-field__headtext" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-field__label" }, fieldDisplayLabel(field)), /* @__PURE__ */ import_react18.default.createElement(FieldMeta, { field, path: null }))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-tabs__list" }, tabs.length === 0 && /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-preview-placeholder", style: { margin: 8 } }, "No tabs yet"), tabs.map((t, i) => /* @__PURE__ */ import_react18.default.createElement(
3203
+ "button",
3204
+ {
3205
+ key: t.id ?? i,
3206
+ type: "button",
3207
+ className: `bb-preview-tabs__tab${i === active ? " bb-preview-tabs__tab--active" : ""}`,
3208
+ onClick: () => setActive(i)
3209
+ },
3210
+ t.label
3211
+ ))), tab && /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-group__body" }, tab.name && /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-tabs__path" }, "Nested under ", /* @__PURE__ */ import_react18.default.createElement("code", { className: "bb-preview-field__slug" }, tabPrefix)), (tab.fields ?? []).length > 0 ? (tab.fields ?? []).map((f) => /* @__PURE__ */ import_react18.default.createElement(PreviewField, { key: f.id, field: f, prefix: tabPrefix })) : /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-preview-placeholder" }, "No fields inside yet")));
3212
+ }
3213
+ function LivePreview() {
3214
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
3215
+ const blocks = useBuilderStore((s) => s.blocks);
3216
+ const block = blocks.find((b) => b.id === activeBlockId);
3217
+ if (!block || block.fields.length === 0) {
3218
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-live-preview bb-live-preview--empty" }, /* @__PURE__ */ import_react18.default.createElement(import_lucide_react5.LayoutTemplate, { size: 32, strokeWidth: 1.5, color: "var(--bb-text-subtle)", style: { marginBottom: 16 } }), /* @__PURE__ */ import_react18.default.createElement("p", { style: { fontWeight: 600, marginBottom: 8, color: "var(--bb-text)" } }, "Nothing to preview yet"), /* @__PURE__ */ import_react18.default.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."));
3219
+ }
3220
+ return /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-live-preview" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-live-preview__header" }, /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-live-preview__title" }, "Live Preview"), /* @__PURE__ */ import_react18.default.createElement("span", { className: "bb-live-preview__meta" }, block.labels?.singular ?? block.slug, " \xB7 ", pluralize(block.fields.length, "field"))), /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-live-preview__body" }, /* @__PURE__ */ import_react18.default.createElement("div", { className: "bb-live-preview__page" }, block.fields.map((field) => /* @__PURE__ */ import_react18.default.createElement(PreviewField, { key: field.id, field })))));
3221
+ }
3222
+
2368
3223
  // src/block-builder/components/canvas/BuilderShell.tsx
2369
3224
  function BuilderShell({ loadSlug }) {
2370
3225
  const loadBlock = useBuilderStore((s) => s.loadBlock);
2371
3226
  const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
2372
3227
  const setBlockSlug = useBuilderStore((s) => s.setBlockSlug);
2373
3228
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
2374
- const [activeSlug, setActiveSlug] = (0, import_react16.useState)(loadSlug ?? null);
2375
- const [loading, setLoading] = (0, import_react16.useState)(!!loadSlug);
2376
- const [loadError, setLoadError] = (0, import_react16.useState)(null);
2377
- const [showCodePreview, setShowCodePreview] = (0, import_react16.useState)(false);
2378
- const [versions, setVersions] = (0, import_react16.useState)([]);
2379
- const [selectedVersionId, setSelectedVersionId] = (0, import_react16.useState)(null);
2380
- const [blockDefs, setBlockDefs] = (0, import_react16.useState)([]);
2381
- const [mobilePanelTab, setMobilePanelTab] = (0, import_react16.useState)("blocks");
2382
- (0, import_react16.useEffect)(() => {
2383
- fetch("/api/block-definitions?limit=200&depth=0").then((r) => r.json()).then((json) => {
3229
+ const [activeSlug, setActiveSlug] = (0, import_react19.useState)(loadSlug ?? null);
3230
+ const [loading, setLoading] = (0, import_react19.useState)(!!loadSlug);
3231
+ const [notification, setNotification] = (0, import_react19.useState)(null);
3232
+ const [showCodePreview, setShowCodePreview] = (0, import_react19.useState)(false);
3233
+ const [versions, setVersions] = (0, import_react19.useState)([]);
3234
+ const [selectedVersionId, setSelectedVersionId] = (0, import_react19.useState)(null);
3235
+ const [blockDefs, setBlockDefs] = (0, import_react19.useState)([]);
3236
+ const [mobilePanelTab, setMobilePanelTab] = (0, import_react19.useState)("blocks");
3237
+ const [isMounted, setIsMounted] = (0, import_react19.useState)(false);
3238
+ const [previewOpen, setPreviewOpen] = (0, import_react19.useState)(false);
3239
+ (0, import_react19.useEffect)(() => {
3240
+ setIsMounted(true);
3241
+ }, []);
3242
+ const refreshBlockDefs = (0, import_react19.useCallback)(async (reportErrors = false) => {
3243
+ try {
3244
+ const res = await fetch("/api/block-definitions?limit=200&depth=0");
3245
+ const json = await res.json();
2384
3246
  setBlockDefs(
2385
3247
  (json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
2386
3248
  );
2387
- }).catch((err) => {
3249
+ } catch (err) {
2388
3250
  console.error("[block-builder] Failed to load block definitions:", err);
2389
- setLoadError("Could not load block definitions. Please refresh the page.");
2390
- });
3251
+ if (reportErrors) {
3252
+ setNotification({ status: "error", title: "Failed to load block definitions", errors: ["Could not load block definitions. Please refresh the page."] });
3253
+ }
3254
+ }
2391
3255
  }, []);
2392
- const loadVersionsForSlug = (0, import_react16.useCallback)(async (slug) => {
3256
+ (0, import_react19.useEffect)(() => {
3257
+ refreshBlockDefs(true);
3258
+ }, [refreshBlockDefs]);
3259
+ const loadVersionsForSlug = (0, import_react19.useCallback)(async (slug) => {
2393
3260
  try {
2394
- const res = await fetch(`/api/block-builder/versions/${encodeURIComponent(slug)}`);
3261
+ const res = await fetch(`/api/block-builder/versions/${encodeURIComponent(slug)}`, {
3262
+ headers: { "X-Block-Builder": "1" }
3263
+ });
2395
3264
  const json = await res.json();
2396
3265
  return json.versions ?? [];
2397
3266
  } catch {
2398
3267
  return [];
2399
3268
  }
2400
3269
  }, []);
2401
- const loadVersion = (0, import_react16.useCallback)(async (slug, versionId) => {
3270
+ const loadVersion = (0, import_react19.useCallback)(async (slug, versionId) => {
2402
3271
  setLoading(true);
2403
- setLoadError(null);
3272
+ setNotification(null);
2404
3273
  const url = versionId ? `/api/block-builder/load/${encodeURIComponent(slug)}?versionId=${encodeURIComponent(versionId)}` : `/api/block-builder/load/${encodeURIComponent(slug)}`;
2405
3274
  try {
2406
- const res = await fetch(url);
3275
+ const res = await fetch(url, {
3276
+ headers: { "X-Block-Builder": "1" }
3277
+ });
2407
3278
  const json = await res.json();
2408
3279
  if (json.block) {
2409
3280
  loadBlock(json.block);
2410
3281
  setVersionMeta(json.versionId ?? null, !(json.isCurrent ?? true));
2411
3282
  setSelectedVersionId(json.versionId ?? null);
2412
3283
  } else {
2413
- setLoadError(json.error ?? "Failed to load block");
3284
+ setNotification({ status: "error", title: "Failed to load block", errors: [json.error ?? "Unknown error"] });
2414
3285
  }
2415
3286
  } catch (err) {
2416
- setLoadError(err instanceof Error ? err.message : "Network error");
3287
+ setNotification({ status: "error", title: "Network error", errors: [err instanceof Error ? err.message : "Could not reach the server."] });
2417
3288
  } finally {
2418
3289
  setLoading(false);
2419
3290
  }
2420
3291
  }, [loadBlock, setVersionMeta]);
2421
- const loadBlockBySlug = (0, import_react16.useCallback)(async (slug) => {
3292
+ const loadBlockBySlug = (0, import_react19.useCallback)(async (slug) => {
2422
3293
  setActiveSlug(slug);
2423
3294
  setBlockSlug(slug);
2424
3295
  setVersions([]);
2425
3296
  setSelectedVersionId(null);
2426
- setLoadError(null);
3297
+ setNotification(null);
2427
3298
  setMobilePanelTab("canvas");
2428
3299
  await loadVersion(slug);
2429
3300
  const list = await loadVersionsForSlug(slug);
@@ -2431,7 +3302,7 @@ function BuilderShell({ loadSlug }) {
2431
3302
  const current = list.find((v) => v.isCurrent) ?? list[0];
2432
3303
  if (current) setSelectedVersionId(current.id);
2433
3304
  }, [loadVersion, loadVersionsForSlug, setBlockSlug]);
2434
- (0, import_react16.useEffect)(() => {
3305
+ (0, import_react19.useEffect)(() => {
2435
3306
  if (!loadSlug) return;
2436
3307
  loadBlockBySlug(loadSlug);
2437
3308
  }, [loadSlug]);
@@ -2447,14 +3318,22 @@ function BuilderShell({ loadSlug }) {
2447
3318
  const current = list.find((v) => v.isCurrent) ?? list[0];
2448
3319
  if (current) setSelectedVersionId(current.id);
2449
3320
  }
2450
- async function handleAfterPublish() {
2451
- if (!activeSlug) return;
2452
- const list = await loadVersionsForSlug(activeSlug);
3321
+ async function handleAfterPublish(publishedSlug) {
3322
+ setActiveSlug(publishedSlug);
3323
+ setBlockSlug(publishedSlug);
3324
+ const [list] = await Promise.all([
3325
+ loadVersionsForSlug(publishedSlug),
3326
+ // A first publish creates a definition the picker has never seen.
3327
+ refreshBlockDefs()
3328
+ ]);
2453
3329
  setVersions(list);
2454
3330
  const current = list.find((v) => v.isCurrent) ?? list[0];
2455
- if (current) setSelectedVersionId(current.id);
3331
+ setSelectedVersionId(current ? current.id : null);
2456
3332
  }
2457
- return /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ import_react16.default.createElement(
3333
+ if (!isMounted) {
3334
+ return /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-loading-bar" }, "Initializing Builder..."));
3335
+ }
3336
+ return /* @__PURE__ */ import_react19.default.createElement(ErrorBoundary, null, /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ import_react19.default.createElement(
2458
3337
  TopBar,
2459
3338
  {
2460
3339
  blockDefs,
@@ -2464,9 +3343,13 @@ function BuilderShell({ loadSlug }) {
2464
3343
  selectedVersionId,
2465
3344
  onVersionSelect: handleVersionSelect,
2466
3345
  onRestoreVersion: handleRestoreVersion,
2467
- onAfterPublish: handleAfterPublish
3346
+ onAfterPublish: handleAfterPublish,
3347
+ notification,
3348
+ onSetNotification: setNotification,
3349
+ previewOpen,
3350
+ onTogglePreview: () => setPreviewOpen((p) => !p)
2468
3351
  }
2469
- ), loading && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-loading-bar" }, "Loading..."), loadError && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-error-bar" }, "Error: ", loadError), isReadOnly && !loading && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-readonly-banner__icon" }, "[i]"), /* @__PURE__ */ import_react16.default.createElement("span", null, "You are viewing a previous version - read only.", /* @__PURE__ */ import_react16.default.createElement(
3352
+ ), loading && /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-loading-bar" }, "Loading..."), isReadOnly && !loading && /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ import_react19.default.createElement("span", { className: "bb-readonly-banner__icon" }, "[i]"), /* @__PURE__ */ import_react19.default.createElement("span", null, "You are viewing a previous version - read only.", /* @__PURE__ */ import_react19.default.createElement(
2470
3353
  "button",
2471
3354
  {
2472
3355
  type: "button",
@@ -2474,7 +3357,7 @@ function BuilderShell({ loadSlug }) {
2474
3357
  onClick: handleRestoreVersion
2475
3358
  },
2476
3359
  "Switch to latest"
2477
- ))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ import_react16.default.createElement(BlockList, null), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-main__center" }, /* @__PURE__ */ import_react16.default.createElement(FieldPalette, null), /* @__PURE__ */ import_react16.default.createElement(BuilderCanvas, null)), /* @__PURE__ */ import_react16.default.createElement(ConfigPanel, null)), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ import_react16.default.createElement(
3360
+ ))), /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ import_react19.default.createElement(BlockList, { blockDefs, activeSlug, onBlockSelect: loadBlockBySlug }), /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-main__center", style: { display: "flex", flex: 1 } }, /* @__PURE__ */ import_react19.default.createElement(FieldPalette, null), /* @__PURE__ */ import_react19.default.createElement(BuilderCanvas, null), previewOpen && /* @__PURE__ */ import_react19.default.createElement(LivePreview, null)), /* @__PURE__ */ import_react19.default.createElement(ConfigPanel, null)), /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ import_react19.default.createElement(
2478
3361
  "button",
2479
3362
  {
2480
3363
  type: "button",
@@ -2483,12 +3366,12 @@ function BuilderShell({ loadSlug }) {
2483
3366
  },
2484
3367
  showCodePreview ? "v" : ">",
2485
3368
  " Code Preview"
2486
- ), showCodePreview && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-footer__content" }, /* @__PURE__ */ import_react16.default.createElement(CodePreview, null))), /* @__PURE__ */ import_react16.default.createElement("nav", { className: "bb-mobile-nav", "aria-label": "Panel navigation" }, [
3369
+ ), showCodePreview && /* @__PURE__ */ import_react19.default.createElement("div", { className: "bb-footer__content" }, /* @__PURE__ */ import_react19.default.createElement(CodePreview, null))), /* @__PURE__ */ import_react19.default.createElement("nav", { className: "bb-mobile-nav", "aria-label": "Panel navigation" }, [
2487
3370
  { id: "blocks", icon: "B", label: "Blocks" },
2488
3371
  { id: "canvas", icon: "[]", label: "Canvas" },
2489
3372
  { id: "palette", icon: "+", label: "Fields" },
2490
3373
  { id: "config", icon: "*", label: "Config" }
2491
- ].map(({ id, icon, label }) => /* @__PURE__ */ import_react16.default.createElement(
3374
+ ].map(({ id, icon, label }) => /* @__PURE__ */ import_react19.default.createElement(
2492
3375
  "button",
2493
3376
  {
2494
3377
  key: id,
@@ -2496,16 +3379,16 @@ function BuilderShell({ loadSlug }) {
2496
3379
  className: `bb-mobile-nav__tab${mobilePanelTab === id ? " bb-mobile-nav__tab--active" : ""}`,
2497
3380
  onClick: () => setMobilePanelTab(id)
2498
3381
  },
2499
- /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
3382
+ /* @__PURE__ */ import_react19.default.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
2500
3383
  label
2501
- ))));
3384
+ )))));
2502
3385
  }
2503
3386
 
2504
3387
  // src/components/BlockBuilderNavLink/index.tsx
2505
- var import_react17 = __toESM(require("react"), 1);
3388
+ var import_react20 = __toESM(require("react"), 1);
2506
3389
  var import_link = __toESM(require("next/link"), 1);
2507
3390
  function BlockBuilderNavLink() {
2508
- return /* @__PURE__ */ import_react17.default.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ import_react17.default.createElement(
3391
+ return /* @__PURE__ */ import_react20.default.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ import_react20.default.createElement(
2509
3392
  import_link.default,
2510
3393
  {
2511
3394
  href: "/block-builder",
@@ -2532,6 +3415,7 @@ function BlockBuilderNavLink() {
2532
3415
  0 && (module.exports = {
2533
3416
  BlockBuilderNavLink,
2534
3417
  BlockDataField,
3418
+ BlockVersionSync,
2535
3419
  BuilderShell,
2536
3420
  EditInBuilderButton,
2537
3421
  SchemaBuilderField