@nextbridgehq/payload-block-builder 0.1.9 → 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;
@@ -136,7 +137,8 @@ function RelationshipPicker({ label, required, collection, hasMany = false, valu
136
137
  const t = doc.title ?? doc.name ?? doc.slug ?? null;
137
138
  if (t) setFetchedTitles((prev) => ({ ...prev, [idStr]: String(t) }));
138
139
  }
139
- }).catch(() => {
140
+ }).catch((err) => {
141
+ console.error("[Block Builder] Failed to load relation title:", err);
140
142
  fetchingRef.current.delete(idStr);
141
143
  });
142
144
  });
@@ -205,22 +207,79 @@ function JsonField({ label, required, value, onChange }) {
205
207
  }
206
208
  ), hasError && /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-error", style: { marginTop: 4 } }, "Invalid JSON \u2014 changes not saved until fixed."));
207
209
  }
208
- function SchemaForm({ schema, value, onChange }) {
210
+ function SchemaForm({ schema, value, onChange, depth = 0 }) {
209
211
  const set = (0, import_react.useCallback)(
210
212
  (key, val) => onChange({ ...value, [key]: val }),
211
213
  [value, onChange]
212
214
  );
213
- return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, schema.map((field) => /* @__PURE__ */ import_react.default.createElement(
214
- FieldInput,
215
- {
216
- key: field.name,
217
- field,
218
- value: value[field.name],
219
- onChange: (v) => set(field.name, v)
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 }));
220
221
  }
221
- )));
222
+ if (field.type === "collapsible") {
223
+ return /* @__PURE__ */ import_react.default.createElement(CollapsibleSection, { key: field.name, field, value, onChange, depth: depth + 1 });
224
+ }
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
236
+ }
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(
248
+ "button",
249
+ {
250
+ type: "button",
251
+ className: "bdf-collapsible__header",
252
+ onClick: () => setOpen((o) => !o)
253
+ },
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 })));
257
+ }
258
+ function TabsSection({
259
+ field,
260
+ value,
261
+ onChange,
262
+ depth
263
+ }) {
264
+ const [activeTab, setActiveTab] = (0, import_react.useState)(0);
265
+ const set = (0, import_react.useCallback)(
266
+ (key, val) => onChange({ ...value, [key]: val }),
267
+ [value, onChange]
268
+ );
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",
273
+ {
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 }))));
222
281
  }
223
- function FieldInput({ field, value, onChange }) {
282
+ function FieldInput({ field, value, onChange, depth }) {
224
283
  const label = field.label ?? field.name;
225
284
  switch (field.type) {
226
285
  case "text":
@@ -373,7 +432,8 @@ function FieldInput({ field, value, onChange }) {
373
432
  const next = [...rows];
374
433
  next[i] = updated;
375
434
  onChange(next);
376
- }
435
+ },
436
+ depth: depth + 1
377
437
  }
378
438
  ), /* @__PURE__ */ import_react.default.createElement(
379
439
  "button",
@@ -397,7 +457,7 @@ function FieldInput({ field, value, onChange }) {
397
457
  case "group": {
398
458
  const groupVal = value ?? {};
399
459
  const subFields = field.fields ?? [];
400
- 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 }))));
401
461
  }
402
462
  default:
403
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, ")");
@@ -418,6 +478,10 @@ function BlockDataField({ path }) {
418
478
  setError(null);
419
479
  return;
420
480
  }
481
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(versionId))) {
482
+ setError("Invalid version ID format");
483
+ return;
484
+ }
421
485
  setLoading(true);
422
486
  setError(null);
423
487
  fetch(`/api/block-definition-versions/${versionId}?depth=0`, { credentials: "same-origin" }).then((r) => {
@@ -472,20 +536,24 @@ function BlockVersionSync({ path }) {
472
536
  setVersion(null);
473
537
  return;
474
538
  }
539
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(defId))) {
540
+ return;
541
+ }
475
542
  fetch(`/api/block-definitions/${String(defId)}?depth=1`, { credentials: "same-origin" }).then((r) => r.ok ? r.json() : null).then((doc) => {
476
543
  if (!doc) return;
477
544
  const currentVersion = doc.currentVersion;
478
545
  if (!currentVersion) return;
479
546
  const versionId = typeof currentVersion === "object" ? currentVersion.id : currentVersion;
480
547
  if (versionId) setVersion(versionId);
481
- }).catch(() => {
548
+ }).catch((err) => {
549
+ console.error("[Block Builder] BlockVersionSync fetch error:", err);
482
550
  });
483
551
  }, [blockDefValue, setVersion]);
484
552
  return null;
485
553
  }
486
554
 
487
555
  // src/components/SchemaBuilderField/index.tsx
488
- var import_react6 = __toESM(require("react"), 1);
556
+ var import_react7 = __toESM(require("react"), 1);
489
557
  var import_ui3 = require("@payloadcms/ui");
490
558
 
491
559
  // src/components/SchemaBuilderField/FieldRow.tsx
@@ -1048,131 +1116,75 @@ function FieldRow({
1048
1116
  )))));
1049
1117
  }
1050
1118
 
1051
- // src/components/SchemaBuilderField/index.tsx
1052
- function parseSchema(raw) {
1053
- try {
1054
- const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
1055
- if (!parsed) return [];
1056
- if (Array.isArray(parsed)) return parsed;
1057
- if (parsed?.fields && Array.isArray(parsed.fields)) return parsed.fields;
1058
- } catch {
1119
+ // src/block-builder/components/ErrorBoundary.tsx
1120
+ var import_react6 = __toESM(require("react"), 1);
1121
+
1122
+ // src/block-builder/store/builder.store.ts
1123
+ var import_zustand = require("zustand");
1124
+ var import_middleware = require("zustand/middleware");
1125
+ var import_immer = require("zustand/middleware/immer");
1126
+
1127
+ // src/utils/uuid.ts
1128
+ function uuidv4() {
1129
+ const c = globalThis.crypto;
1130
+ if (typeof c?.randomUUID === "function") {
1131
+ return c.randomUUID();
1059
1132
  }
1060
- return [];
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
+ );
1061
1143
  }
1062
- function SchemaBuilderField({ path, readOnly }) {
1063
- const { value, setValue } = (0, import_ui3.useField)({ path });
1064
- const setValueRef = (0, import_react6.useRef)(setValue);
1065
- (0, import_react6.useEffect)(() => {
1066
- setValueRef.current = setValue;
1067
- });
1068
- const [fields, setFields] = (0, import_react6.useState)(() => parseSchema(value));
1069
- const hasExistingValue = value !== void 0 && value !== null;
1070
- const [hydrated, setHydrated] = (0, import_react6.useState)(!hasExistingValue);
1071
- (0, import_react6.useEffect)(() => {
1072
- if (hydrated) return;
1073
- if (value !== void 0 && value !== null) {
1074
- setFields(parseSchema(value));
1075
- setHydrated(true);
1076
- }
1077
- }, [value, hydrated]);
1078
- (0, import_react6.useEffect)(() => {
1079
- if (!hydrated) return;
1080
- setValueRef.current({ fields });
1081
- }, [fields, hydrated]);
1082
- const addField = (0, import_react6.useCallback)(() => {
1083
- setFields((prev) => [
1084
- ...prev,
1085
- { name: "", type: "text", label: "", required: false }
1086
- ]);
1087
- }, []);
1088
- const updateField = (0, import_react6.useCallback)((index, updated) => {
1089
- setFields((prev) => prev.map((f, i) => i === index ? updated : f));
1090
- }, []);
1091
- const removeField = (0, import_react6.useCallback)((index) => {
1092
- setFields((prev) => prev.filter((_, i) => i !== index));
1093
- }, []);
1094
- const moveField = (0, import_react6.useCallback)((index, dir) => {
1095
- setFields((prev) => {
1096
- const next = [...prev];
1097
- const target = index + dir;
1098
- if (target < 0 || target >= next.length) return prev;
1099
- [next[index], next[target]] = [next[target], next[index]];
1100
- return next;
1101
- });
1102
- }, []);
1103
- return /* @__PURE__ */ import_react6.default.createElement("div", null, /* @__PURE__ */ import_react6.default.createElement("div", { className: "sbf-section-heading" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "sbf-section-label" }, "Fields", fields.length > 0 && /* @__PURE__ */ import_react6.default.createElement(
1104
- "span",
1105
- {
1106
- style: {
1107
- marginLeft: 8,
1108
- fontSize: 11,
1109
- color: "var(--theme-elevation-400)",
1110
- fontWeight: 400
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 = [];
1111
1165
  }
1112
- },
1113
- "(",
1114
- fields.length,
1115
- ")"
1116
- ))), fields.length === 0 ? /* @__PURE__ */ import_react6.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_react6.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react6.default.createElement(
1117
- FieldRow,
1118
- {
1119
- key: i,
1120
- index: i,
1121
- total: fields.length,
1122
- field,
1123
- onChange: (updated) => updateField(i, updated),
1124
- onRemove: () => removeField(i),
1125
- onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
1126
- onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
1127
- readOnly
1166
+ currentFields = tab.fields;
1167
+ continue;
1128
1168
  }
1129
- ))), !readOnly && /* @__PURE__ */ import_react6.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)", marginBottom: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", className: "sbf-add-btn", onClick: addField }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"), "Add Field")));
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;
1130
1178
  }
1131
-
1132
- // src/components/EditInBuilderButton/index.tsx
1133
- var import_react7 = __toESM(require("react"), 1);
1134
- var import_ui4 = require("@payloadcms/ui");
1135
- var import_ui5 = require("@payloadcms/ui");
1136
- function EditInBuilderButton() {
1137
- const { id } = (0, import_ui4.useDocumentInfo)();
1138
- const { value: slug } = (0, import_ui5.useField)({ path: "slug" });
1139
- if (!id || !slug) return null;
1140
- return /* @__PURE__ */ import_react7.default.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ import_react7.default.createElement(
1141
- "a",
1142
- {
1143
- href: `/block-builder?load=${slug}`,
1144
- target: "_blank",
1145
- rel: "noopener noreferrer",
1146
- style: {
1147
- display: "inline-flex",
1148
- alignItems: "center",
1149
- gap: "6px",
1150
- padding: "8px 16px",
1151
- borderRadius: "6px",
1152
- border: "1px solid #16a34a",
1153
- color: "#16a34a",
1154
- textDecoration: "none",
1155
- fontSize: "13px",
1156
- fontWeight: 500,
1157
- background: "transparent",
1158
- cursor: "pointer"
1159
- }
1160
- },
1161
- "Edit in Block Builder"
1162
- ));
1179
+ function getTargetFields(block, parentPath) {
1180
+ return walkPath(block, parentPath, false);
1181
+ }
1182
+ function ensureTargetFields(block, parentPath) {
1183
+ return walkPath(block, parentPath, true);
1163
1184
  }
1164
-
1165
- // src/block-builder/components/canvas/BuilderShell.tsx
1166
- var import_react17 = __toESM(require("react"), 1);
1167
-
1168
- // src/block-builder/store/builder.store.ts
1169
- var import_zustand = require("zustand");
1170
- var import_middleware = require("zustand/middleware");
1171
- var import_immer = require("zustand/middleware/immer");
1172
- var import_uuid = require("uuid");
1173
1185
  function createDefaultField(type) {
1174
1186
  const base = {
1175
- id: (0, import_uuid.v4)(),
1187
+ id: uuidv4(),
1176
1188
  type,
1177
1189
  name: `${type}Field`,
1178
1190
  label: `${type.charAt(0).toUpperCase()}${type.slice(1)} Field`,
@@ -1180,7 +1192,6 @@ function createDefaultField(type) {
1180
1192
  };
1181
1193
  switch (type) {
1182
1194
  case "select":
1183
- case "radio":
1184
1195
  return {
1185
1196
  ...base,
1186
1197
  options: [
@@ -1189,18 +1200,34 @@ function createDefaultField(type) {
1189
1200
  ]
1190
1201
  };
1191
1202
  case "relationship":
1192
- return { ...base, relationTo: "", hasMany: false };
1203
+ return { ...base, collection: "", hasMany: false };
1193
1204
  case "array":
1194
1205
  return { ...base, fields: [] };
1195
1206
  case "group":
1196
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: [] }] };
1197
1214
  default:
1198
1215
  return base;
1199
1216
  }
1200
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
+ }
1201
1228
  function createDefaultBlock() {
1202
1229
  return {
1203
- id: (0, import_uuid.v4)(),
1230
+ id: uuidv4(),
1204
1231
  slug: "myBlock",
1205
1232
  interfaceName: "MyBlock",
1206
1233
  labels: { singular: "My Block", plural: "My Blocks" },
@@ -1221,11 +1248,13 @@ var useBuilderStore = (0, import_zustand.create)()(
1221
1248
  isReadOnly: false,
1222
1249
  loadedVersionId: null,
1223
1250
  blockSlug: null,
1251
+ activeParentPath: [],
1224
1252
  addBlock: () => set((state) => {
1225
1253
  const block = createDefaultBlock();
1226
1254
  state.blocks.push(block);
1227
1255
  state.activeBlockId = block.id;
1228
1256
  state.activeFieldId = null;
1257
+ state.activeParentPath = [];
1229
1258
  state.isDirty = true;
1230
1259
  }),
1231
1260
  removeBlock: (blockId) => set((state) => {
@@ -1233,6 +1262,7 @@ var useBuilderStore = (0, import_zustand.create)()(
1233
1262
  if (state.activeBlockId === blockId) {
1234
1263
  state.activeBlockId = state.blocks[0]?.id ?? null;
1235
1264
  state.activeFieldId = null;
1265
+ state.activeParentPath = [];
1236
1266
  }
1237
1267
  state.isDirty = true;
1238
1268
  }),
@@ -1244,15 +1274,16 @@ var useBuilderStore = (0, import_zustand.create)()(
1244
1274
  setActiveBlock: (blockId) => set((state) => {
1245
1275
  state.activeBlockId = blockId;
1246
1276
  state.activeFieldId = null;
1277
+ state.activeParentPath = [];
1247
1278
  }),
1248
1279
  duplicateBlock: (blockId) => set((state) => {
1249
1280
  const block = state.blocks.find((b) => b.id === blockId);
1250
1281
  if (!block) return;
1251
1282
  const clone = JSON.parse(JSON.stringify(block));
1252
- clone.id = (0, import_uuid.v4)();
1253
- clone.slug = `${block.slug}Copy`;
1283
+ clone.id = uuidv4();
1284
+ clone.slug = `${block.slug}-copy`;
1254
1285
  clone.interfaceName = block.interfaceName ? `${block.interfaceName}Copy` : void 0;
1255
- clone.fields = clone.fields.map((f) => ({ ...f, id: (0, import_uuid.v4)() }));
1286
+ clone.fields = regenerateFieldIds(clone.fields);
1256
1287
  const idx = state.blocks.findIndex((b) => b.id === blockId);
1257
1288
  state.blocks.splice(idx + 1, 0, clone);
1258
1289
  state.activeBlockId = clone.id;
@@ -1261,36 +1292,47 @@ var useBuilderStore = (0, import_zustand.create)()(
1261
1292
  addField: (blockId, type) => set((state) => {
1262
1293
  const block = state.blocks.find((b) => b.id === blockId);
1263
1294
  if (!block) return;
1295
+ const targetFields = ensureTargetFields(block, state.activeParentPath);
1296
+ if (!targetFields) return;
1264
1297
  const field = createDefaultField(type);
1265
- block.fields.push(field);
1298
+ targetFields.push(field);
1266
1299
  state.activeFieldId = field.id;
1267
1300
  state.isDirty = true;
1268
1301
  }),
1269
1302
  removeField: (blockId, fieldId) => set((state) => {
1270
1303
  const block = state.blocks.find((b) => b.id === blockId);
1271
1304
  if (!block) return;
1272
- 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
+ }
1273
1311
  if (state.activeFieldId === fieldId) state.activeFieldId = null;
1274
1312
  state.isDirty = true;
1275
1313
  }),
1276
1314
  updateField: (blockId, fieldId, updates) => set((state) => {
1277
1315
  const block = state.blocks.find((b) => b.id === blockId);
1278
1316
  if (!block) return;
1279
- 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);
1280
1320
  if (field) Object.assign(field, updates);
1281
1321
  state.isDirty = true;
1282
1322
  }),
1283
1323
  reorderFields: (blockId, fromIndex, toIndex) => set((state) => {
1284
1324
  const block = state.blocks.find((b) => b.id === blockId);
1285
1325
  if (!block) return;
1286
- const [moved] = block.fields.splice(fromIndex, 1);
1287
- 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);
1288
1330
  state.isDirty = true;
1289
1331
  }),
1290
1332
  setActiveField: (fieldId) => set((state) => {
1291
1333
  state.activeFieldId = fieldId;
1292
1334
  }),
1293
- reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null })),
1335
+ reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null, activeParentPath: [] })),
1294
1336
  markClean: () => set((state) => {
1295
1337
  state.isDirty = false;
1296
1338
  }),
@@ -1298,6 +1340,7 @@ var useBuilderStore = (0, import_zustand.create)()(
1298
1340
  state.blocks = [block];
1299
1341
  state.activeBlockId = block.id;
1300
1342
  state.activeFieldId = null;
1343
+ state.activeParentPath = [];
1301
1344
  state.isDirty = false;
1302
1345
  }),
1303
1346
  setVersionMeta: (versionId, isReadOnly) => set((state) => {
@@ -1306,10 +1349,36 @@ var useBuilderStore = (0, import_zustand.create)()(
1306
1349
  }),
1307
1350
  setBlockSlug: (slug) => set((state) => {
1308
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;
1309
1371
  })
1310
1372
  })),
1311
1373
  {
1312
- 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
+ },
1313
1382
  partialize: (state) => ({
1314
1383
  blocks: state.blocks,
1315
1384
  activeBlockId: state.activeBlockId
@@ -1319,163 +1388,191 @@ var useBuilderStore = (0, import_zustand.create)()(
1319
1388
  )
1320
1389
  );
1321
1390
 
1322
- // src/block-builder/components/canvas/TopBar.tsx
1323
- var import_react8 = __toESM(require("react"), 1);
1324
- var import_lucide_react = require("lucide-react");
1325
-
1326
- // src/block-builder/lib/mapToSaveRequest.ts
1327
- var TYPE_MAP = {
1328
- richText: "richtext",
1329
- upload: "image",
1330
- radio: "select"
1331
- };
1332
- var UNSUPPORTED = /* @__PURE__ */ new Set(["code", "point", "ui", "tabs", "collapsible", "row"]);
1333
- function normalizeSlug(slug) {
1334
- return slug.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1335
- }
1336
- function mapToSaveRequest(block) {
1337
- const fields = block.fields.filter((f) => {
1338
- if (UNSUPPORTED.has(f.type)) {
1339
- console.warn(`[block-builder] Field type "${f.type}" is not supported in this project \xE2\u20AC" skipping field "${f.name}"`);
1340
- return false;
1341
- }
1342
- return true;
1343
- }).map(({ id: _id, relationTo, ...f }) => ({
1344
- ...f,
1345
- type: TYPE_MAP[f.type] ?? f.type,
1346
- // normalizer reads `collection`, block-builder stores `relationTo`
1347
- ...relationTo ? { collection: relationTo } : {}
1348
- }));
1349
- return {
1350
- blockSlug: normalizeSlug(block.slug),
1351
- name: block.labels?.singular ?? block.slug,
1352
- schema: { fields },
1353
- changelog: "Created via block builder"
1354
- };
1355
- }
1356
-
1357
- // src/block-builder/lib/codegen.ts
1358
- function indent(n) {
1359
- return " ".repeat(n);
1360
- }
1361
- function escStr(s) {
1362
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1363
- }
1364
- function fieldToCode(field, depth = 1) {
1365
- const pad = indent(depth);
1366
- const innerPad = indent(depth + 1);
1367
- const lines = [];
1368
- lines.push(`${pad}name: '${escStr(field.name)}'`);
1369
- lines.push(`${pad}type: '${field.type}'`);
1370
- if (field.label) lines.push(`${pad}label: '${escStr(field.label)}'`);
1371
- if (field.required) lines.push(`${pad}required: true`);
1372
- if (field.unique) lines.push(`${pad}unique: true`);
1373
- if (field.localized) lines.push(`${pad}localized: true`);
1374
- if (field.defaultValue !== void 0) {
1375
- const val = typeof field.defaultValue === "string" ? `'${escStr(String(field.defaultValue))}'` : field.defaultValue;
1376
- lines.push(`${pad}defaultValue: ${val}`);
1377
- }
1378
- if (field.type === "richText") {
1379
- lines.push(`${pad}editor: lexicalEditor({})`);
1380
- }
1381
- if (field.options && field.options.length > 0) {
1382
- const opts = field.options.map((o) => `{ label: '${escStr(o.label)}', value: '${escStr(o.value)}' }`).join(`, `);
1383
- lines.push(`${pad}options: [${opts}]`);
1384
- }
1385
- if (field.relationTo) {
1386
- lines.push(`${pad}relationTo: '${escStr(field.relationTo)}'`);
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
+ };
1387
1412
  }
1388
- if (field.hasMany !== void 0) {
1389
- lines.push(`${pad}hasMany: ${field.hasMany}`);
1413
+ static getDerivedStateFromError(error) {
1414
+ return { hasError: true, error };
1390
1415
  }
1391
- if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
1392
- if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
1393
- if (field.fields && field.fields.length > 0) {
1394
- const nested = field.fields.map((f) => `${innerPad}{
1395
- ${fieldToCode(f, depth + 2)}
1396
- ${innerPad}}`).join(",\n");
1397
- lines.push(`${pad}fields: [
1398
- ${nested}
1399
- ${innerPad}]`);
1416
+ componentDidCatch(error, errorInfo) {
1417
+ console.error("[Block Builder] Uncaught error:", error, errorInfo);
1400
1418
  }
1401
- const adminParts = [];
1402
- if (field.admin?.description)
1403
- adminParts.push(`description: '${escStr(field.admin.description)}'`);
1404
- if (field.admin?.placeholder)
1405
- adminParts.push(`placeholder: '${escStr(field.admin.placeholder)}'`);
1406
- if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
1407
- if (field.admin?.hidden) adminParts.push(`hidden: true`);
1408
- if (adminParts.length > 0) {
1409
- 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;
1410
1440
  }
1411
- return lines.join(",\n");
1412
- }
1413
- function generateBlockCode(block) {
1414
- const hasRichText = containsRichText(block.fields);
1415
- const imports = [`import type { Block } from 'payload'`];
1416
- if (hasRichText) {
1417
- 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 {
1418
1451
  }
1419
- const fieldsCode = block.fields.map((f) => ` {
1420
- ${fieldToCode(f, 2)}
1421
- }`).join(",\n");
1422
- const labelsCode = block.labels ? `
1423
- labels: {
1424
- singular: '${escStr(block.labels.singular ?? block.slug)}',
1425
- plural: '${escStr(block.labels.plural ?? block.slug + "s")}',
1426
- },` : "";
1427
- const interfaceLine = block.interfaceName ? `
1428
- interfaceName: '${escStr(block.interfaceName)}',` : "";
1429
- const exportName = block.interfaceName ?? toCamelCase(block.slug);
1430
- return [
1431
- imports.join("\n"),
1432
- "",
1433
- `export const ${exportName}: Block = {`,
1434
- ` slug: '${escStr(block.slug)}',${interfaceLine}${labelsCode}`,
1435
- ` fields: [`,
1436
- fieldsCode,
1437
- ` ],`,
1438
- `}`,
1439
- ""
1440
- ].join("\n");
1452
+ return [];
1441
1453
  }
1442
- function containsRichText(fields) {
1443
- return fields.some(
1444
- (f) => f.type === "richText" || (f.fields ? containsRichText(f.fields) : false)
1445
- );
1446
- }
1447
- function toCamelCase(slug) {
1448
- return slug.split(/[-_]/).map(
1449
- (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
1450
- ).join("");
1451
- }
1452
- function generateBlockOutput(block) {
1453
- return {
1454
- filename: `${block.slug}.ts`,
1455
- code: generateBlockCode(block),
1456
- language: "typescript"
1457
- };
1458
- }
1459
- function generateAllBlocks(blocks) {
1460
- 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"))));
1461
1522
  }
1462
- function generateIndexFile(blocks) {
1463
- const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
1464
- const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
1465
- const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
1466
- const code = [
1467
- imports,
1468
- "",
1469
- `export const blocks = [`,
1470
- exportList,
1471
- `] as const`,
1472
- ""
1473
- ].join("\n");
1474
- 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
+ ));
1475
1555
  }
1476
1556
 
1557
+ // src/block-builder/components/canvas/BuilderShell.tsx
1558
+ var import_react19 = __toESM(require("react"), 1);
1559
+
1477
1560
  // src/block-builder/components/canvas/TopBar.tsx
1478
- function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish, notification, onSetNotification }) {
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 }) {
1479
1576
  const blocks = useBuilderStore((s) => s.blocks);
1480
1577
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1481
1578
  const activeBlock = blocks.find((b) => b.id === activeBlockId);
@@ -1483,21 +1580,23 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1483
1580
  const isDirty = useBuilderStore((s) => s.isDirty);
1484
1581
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1485
1582
  const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
1583
+ const loadBlock = useBuilderStore((s) => s.loadBlock);
1486
1584
  const setNotification = onSetNotification;
1487
- const [versionDropdownOpen, setVersionDropdownOpen] = (0, import_react8.useState)(false);
1488
- const [blockPickerOpen, setBlockPickerOpen] = (0, import_react8.useState)(false);
1489
- const dropdownRef = (0, import_react8.useRef)(null);
1490
- const blockPickerRef = (0, import_react8.useRef)(null);
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);
1491
1590
  const selectedVersion = versions.find((v) => v.id === selectedVersionId);
1492
1591
  const currentVersion = versions.find((v) => v.isCurrent);
1493
1592
  const activeBlockDef = blockDefs.find((b) => b.slug === activeSlug);
1494
- (0, import_react8.useEffect)(() => {
1593
+ (0, import_react9.useEffect)(() => {
1495
1594
  if (notification?.status === "success") {
1496
1595
  const t = setTimeout(() => setNotification(null), 3e3);
1497
1596
  return () => clearTimeout(t);
1498
1597
  }
1499
1598
  }, [notification]);
1500
- (0, import_react8.useEffect)(() => {
1599
+ (0, import_react9.useEffect)(() => {
1501
1600
  function handleClick(e) {
1502
1601
  if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
1503
1602
  setVersionDropdownOpen(false);
@@ -1513,10 +1612,17 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1513
1612
  if (!activeBlock || isReadOnly) return false;
1514
1613
  setNotification({ status: "publishing" });
1515
1614
  try {
1516
- 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
+ };
1517
1623
  const res = await fetch("/api/blocks/save", {
1518
1624
  method: "POST",
1519
- headers: { "Content-Type": "application/json" },
1625
+ headers: { "Content-Type": "application/json", "X-Block-Builder": "1" },
1520
1626
  body: JSON.stringify(req)
1521
1627
  });
1522
1628
  const json = await res.json();
@@ -1527,7 +1633,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1527
1633
  status: "success",
1528
1634
  msg: `v${json.versionNumber ?? "?"} published successfully!`
1529
1635
  });
1530
- onAfterPublish();
1636
+ onAfterPublish(publishedSlug);
1531
1637
  return true;
1532
1638
  } else {
1533
1639
  setNotification({
@@ -1546,35 +1652,54 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1546
1652
  return false;
1547
1653
  }
1548
1654
  }
1549
- function handleExport() {
1550
- if (blocks.length === 0) return;
1551
- const outputs = [...generateAllBlocks(blocks), generateIndexFile(blocks)];
1552
- for (const out of outputs) {
1553
- const blob = new Blob([out.code], { type: "text/plain" });
1554
- const url = URL.createObjectURL(blob);
1555
- const a = document.createElement("a");
1556
- a.href = url;
1557
- a.download = out.filename;
1558
- document.body.appendChild(a);
1559
- a.click();
1560
- document.body.removeChild(a);
1561
- URL.revokeObjectURL(url);
1562
- }
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 = "";
1563
1688
  }
1564
1689
  function formatDate(iso) {
1565
1690
  return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
1566
1691
  }
1567
- return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-topbar" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-topbar__brand" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-topbar__title" }, "Block Builder"), isDirty && !isReadOnly && /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-topbar__dirty" }, "* unsaved")), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-topbar__selectors" }, blockDefs.length > 0 && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-picker", ref: blockPickerRef }, /* @__PURE__ */ import_react8.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(
1568
1693
  "button",
1569
1694
  {
1570
1695
  type: "button",
1571
1696
  className: "bb-block-picker__trigger",
1572
1697
  onClick: () => setBlockPickerOpen((o) => !o)
1573
1698
  },
1574
- /* @__PURE__ */ import_react8.default.createElement(import_lucide_react.Blocks, { size: 14, strokeWidth: 1.75, className: "bb-block-picker__icon" }),
1575
- /* @__PURE__ */ import_react8.default.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
1576
- /* @__PURE__ */ import_react8.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1577
- ), blockPickerOpen && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ import_react8.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(
1578
1703
  "button",
1579
1704
  {
1580
1705
  key: b.id,
@@ -1585,20 +1710,20 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1585
1710
  onBlockSelect(b.slug);
1586
1711
  }
1587
1712
  },
1588
- /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-block-picker__item-name" }, b.name),
1589
- /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-version-dropdown__meta" }, b.slug)
1590
- )))), versions.length > 0 && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-version-selector", ref: dropdownRef }, /* @__PURE__ */ import_react8.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(
1591
1716
  "button",
1592
1717
  {
1593
1718
  type: "button",
1594
1719
  className: `bb-version-selector__trigger${isReadOnly ? " bb-version-selector__trigger--readonly" : ""}`,
1595
1720
  onClick: () => setVersionDropdownOpen((o) => !o)
1596
1721
  },
1597
- /* @__PURE__ */ import_react8.default.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1598
- /* @__PURE__ */ import_react8.default.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
1599
- selectedVersion?.isCurrent && /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-version-selector__badge" }, "current"),
1600
- /* @__PURE__ */ import_react8.default.createElement(import_lucide_react.ChevronDown, { size: 14, strokeWidth: 1.75, className: "bb-version-selector__chevron" })
1601
- ), versionDropdownOpen && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ import_react8.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(
1602
1727
  "button",
1603
1728
  {
1604
1729
  key: v.id,
@@ -1609,20 +1734,45 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1609
1734
  onVersionSelect(v.id);
1610
1735
  }
1611
1736
  },
1612
- /* @__PURE__ */ import_react8.default.createElement("span", { className: `bb-version-selector__dot${v.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1613
- /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-version-dropdown__label" }, v.label, v.isCurrent && /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-version-selector__badge" }, "current")),
1614
- /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-version-dropdown__meta" }, v.changelog ? `${v.changelog.slice(0, 32)}${v.changelog.length > 32 ? "..." : ""}` : formatDate(v.createdAt))
1615
- ))))), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-topbar__actions" }, /* @__PURE__ */ import_react8.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(
1616
1750
  "button",
1617
1751
  {
1618
1752
  type: "button",
1619
- onClick: handleExport,
1620
- disabled: blocks.length === 0,
1621
- className: "bb-btn bb-btn--secondary",
1622
- style: { display: "none" }
1753
+ onClick: () => fileInputRef.current?.click(),
1754
+ className: "bb-btn bb-btn--secondary"
1755
+ },
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"
1623
1764
  },
1624
- "Export .ts"
1625
- ), isReadOnly ? /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, null, /* @__PURE__ */ import_react8.default.createElement(
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(
1626
1776
  "button",
1627
1777
  {
1628
1778
  type: "button",
@@ -1633,7 +1783,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1633
1783
  disabled: !currentVersion
1634
1784
  },
1635
1785
  "Back to current"
1636
- ), /* @__PURE__ */ import_react8.default.createElement(
1786
+ ), /* @__PURE__ */ import_react9.default.createElement(
1637
1787
  "button",
1638
1788
  {
1639
1789
  type: "button",
@@ -1645,7 +1795,7 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1645
1795
  className: "bb-btn bb-btn--warning"
1646
1796
  },
1647
1797
  notification?.status === "publishing" ? "Restoring..." : "Restore as new version"
1648
- )) : /* @__PURE__ */ import_react8.default.createElement(
1798
+ )) : /* @__PURE__ */ import_react9.default.createElement(
1649
1799
  "button",
1650
1800
  {
1651
1801
  type: "button",
@@ -1654,11 +1804,11 @@ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersio
1654
1804
  className: "bb-btn bb-btn--primary"
1655
1805
  },
1656
1806
  notification?.status === "publishing" ? "Publishing..." : "Publish to Payload"
1657
- ))), notification?.status === "publishing" && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react8.default.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version..." : "Publishing to Payload..."), /* @__PURE__ */ import_react8.default.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-notify__icon" }, "OK"), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react8.default.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ import_react8.default.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ import_react8.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react8.default.createElement(import_lucide_react.X, { size: 12, strokeWidth: 2 })))), notification?.status === "error" && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-notify__icon" }, "!"), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react8.default.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ import_react8.default.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ import_react8.default.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ import_react8.default.createElement("li", { key: i }, e)))), /* @__PURE__ */ import_react8.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, /* @__PURE__ */ import_react8.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 })))));
1658
1808
  }
1659
1809
 
1660
1810
  // src/block-builder/components/canvas/BlockList.tsx
1661
- var import_react9 = __toESM(require("react"), 1);
1811
+ var import_react10 = __toESM(require("react"), 1);
1662
1812
  var import_lucide_react2 = require("lucide-react");
1663
1813
  function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
1664
1814
  const blocks = useBuilderStore((s) => s.blocks);
@@ -1672,37 +1822,37 @@ function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
1672
1822
  const localOnlyBlocks = blocks.filter(
1673
1823
  (b) => !blockDefs.some((d) => d.slug === b.slug)
1674
1824
  );
1675
- return /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ import_react9.default.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, /* @__PURE__ */ import_react9.default.createElement(import_lucide_react2.Plus, { size: 14, strokeWidth: 2 }))), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-sidebar__body" }, useApiNav && blockDefs.map((def) => {
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) => {
1676
1826
  const isActive = def.slug === activeSlug;
1677
1827
  const fieldCount = isActive && loadedBlock ? loadedBlock.fields.length : null;
1678
- return /* @__PURE__ */ import_react9.default.createElement(
1828
+ return /* @__PURE__ */ import_react10.default.createElement(
1679
1829
  "div",
1680
1830
  {
1681
1831
  key: def.id,
1682
1832
  onClick: () => onBlockSelect?.(def.slug),
1683
1833
  className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1684
1834
  },
1685
- /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-item__slug" }, def.slug),
1686
- /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-item__meta" }, fieldCount !== null ? `${fieldCount} field${fieldCount !== 1 ? "s" : ""}` : def.name)
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)
1687
1837
  );
1688
1838
  }), localOnlyBlocks.map((block) => {
1689
1839
  const isActive = block.id === activeBlockId;
1690
- return /* @__PURE__ */ import_react9.default.createElement(
1840
+ return /* @__PURE__ */ import_react10.default.createElement(
1691
1841
  "div",
1692
1842
  {
1693
1843
  key: block.id,
1694
1844
  onClick: () => setActiveBlock(block.id),
1695
1845
  className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1696
1846
  },
1697
- /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-item__slug" }, block.slug),
1698
- /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-item__meta" }, block.fields.length, " field", block.fields.length !== 1 ? "s" : ""),
1699
- /* @__PURE__ */ import_react9.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(
1700
1850
  "div",
1701
1851
  {
1702
1852
  className: "bb-block-item__actions",
1703
1853
  onClick: (e) => e.stopPropagation()
1704
1854
  },
1705
- /* @__PURE__ */ import_react9.default.createElement(
1855
+ /* @__PURE__ */ import_react10.default.createElement(
1706
1856
  "button",
1707
1857
  {
1708
1858
  type: "button",
@@ -1710,9 +1860,9 @@ function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
1710
1860
  className: "bb-block-action",
1711
1861
  title: "Duplicate"
1712
1862
  },
1713
- /* @__PURE__ */ import_react9.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 })
1714
1864
  ),
1715
- /* @__PURE__ */ import_react9.default.createElement(
1865
+ /* @__PURE__ */ import_react10.default.createElement(
1716
1866
  "button",
1717
1867
  {
1718
1868
  type: "button",
@@ -1720,69 +1870,103 @@ function BlockList({ blockDefs = [], activeSlug, onBlockSelect }) {
1720
1870
  className: "bb-block-action bb-block-action--danger",
1721
1871
  title: "Delete"
1722
1872
  },
1723
- /* @__PURE__ */ import_react9.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 })
1724
1874
  )
1725
1875
  )
1726
1876
  );
1727
- }), !useApiNav && blocks.length === 0 && /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ import_react9.default.createElement("br", null), "Click + to create one.")));
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.")));
1728
1878
  }
1729
1879
 
1730
1880
  // src/block-builder/components/canvas/BuilderCanvas.tsx
1731
- var import_react11 = __toESM(require("react"), 1);
1881
+ var import_react12 = __toESM(require("react"), 1);
1732
1882
  var import_core = require("@dnd-kit/core");
1733
1883
  var import_sortable2 = require("@dnd-kit/sortable");
1734
1884
  var import_modifiers = require("@dnd-kit/modifiers");
1735
1885
 
1736
1886
  // src/block-builder/components/canvas/SortableFieldCard.tsx
1737
- var import_react10 = __toESM(require("react"), 1);
1887
+ var import_react11 = __toESM(require("react"), 1);
1738
1888
  var import_sortable = require("@dnd-kit/sortable");
1739
1889
  var import_utilities = require("@dnd-kit/utilities");
1740
1890
  var import_lucide_react3 = require("lucide-react");
1741
1891
  var ICON_MAP = {
1742
1892
  text: import_lucide_react3.Type,
1743
1893
  textarea: import_lucide_react3.AlignLeft,
1894
+ richtext: import_lucide_react3.AlignJustify,
1744
1895
  number: import_lucide_react3.Hash,
1745
1896
  email: import_lucide_react3.Mail,
1746
1897
  date: import_lucide_react3.Calendar,
1747
1898
  checkbox: import_lucide_react3.CheckSquare,
1748
1899
  select: import_lucide_react3.ChevronDown,
1749
- radio: import_lucide_react3.Circle,
1750
- upload: import_lucide_react3.Upload,
1900
+ image: import_lucide_react3.Image,
1901
+ file: import_lucide_react3.Upload,
1751
1902
  relationship: import_lucide_react3.Link,
1752
- 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
1753
1909
  };
1754
- 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
+ }) {
1755
1918
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = (0, import_sortable.useSortable)({
1756
1919
  id: field.id
1757
1920
  });
1758
- const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1759
1921
  const setActiveField = useBuilderStore((s) => s.setActiveField);
1760
1922
  const removeField = useBuilderStore((s) => s.removeField);
1761
- const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1762
- const isActive = activeFieldId === field.id;
1763
1923
  const wrapStyle = {
1764
1924
  transform: import_utilities.CSS.Transform.toString(transform),
1765
1925
  transition
1766
1926
  };
1767
- return /* @__PURE__ */ import_react10.default.createElement(
1927
+ return /* @__PURE__ */ import_react11.default.createElement(
1768
1928
  "div",
1769
1929
  {
1770
1930
  ref: setNodeRef,
1771
1931
  style: wrapStyle,
1772
1932
  className: `bb-field-card-wrap${isDragging ? " bb-field-card-wrap--dragging" : ""}`,
1773
1933
  ...attributes,
1774
- ...listeners
1934
+ ...listeners,
1935
+ "aria-label": `Draggable field card for ${field.name || "unnamed"}`
1775
1936
  },
1776
- /* @__PURE__ */ import_react10.default.createElement(
1937
+ /* @__PURE__ */ import_react11.default.createElement(
1777
1938
  "div",
1778
1939
  {
1779
1940
  className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
1780
1941
  onClick: () => setActiveField(isActive ? null : field.id)
1781
1942
  },
1782
- /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-field-card__icon" }, /* @__PURE__ */ import_react10.default.createElement(FieldIcon, { type: field.type })),
1783
- /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-field-card__required" }, "*"))),
1784
- /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
1785
- !isReadOnly && /* @__PURE__ */ import_react10.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(
1786
1970
  "button",
1787
1971
  {
1788
1972
  type: "button",
@@ -1794,21 +1978,46 @@ function SortableFieldCard({ field, blockId, index }) {
1794
1978
  className: "bb-field-card__delete",
1795
1979
  title: "Remove field"
1796
1980
  },
1797
- /* @__PURE__ */ import_react10.default.createElement(import_lucide_react3.X, { size: 12, strokeWidth: 2 })
1981
+ /* @__PURE__ */ import_react11.default.createElement(import_lucide_react3.X, { size: 12, strokeWidth: 2 })
1798
1982
  )
1799
1983
  )
1800
1984
  );
1801
- }
1985
+ });
1802
1986
  function FieldIcon({ type }) {
1803
1987
  const Icon = ICON_MAP[type];
1804
- return Icon ? /* @__PURE__ */ import_react10.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
1988
+ return Icon ? /* @__PURE__ */ import_react11.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null;
1805
1989
  }
1806
1990
 
1807
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
+ }
1808
2012
  function BuilderCanvas() {
1809
2013
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1810
2014
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1811
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);
1812
2021
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1813
2022
  const sensors = (0, import_core.useSensors)(
1814
2023
  (0, import_core.useSensor)(import_core.PointerSensor, { activationConstraint: { distance: 4 } }),
@@ -1818,16 +2027,41 @@ function BuilderCanvas() {
1818
2027
  if (isReadOnly) return;
1819
2028
  const { active, over } = event;
1820
2029
  if (!over || active.id === over.id || !block) return;
1821
- const fromIndex = block.fields.findIndex((f) => f.id === active.id);
1822
- 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);
1823
2034
  if (fromIndex !== -1 && toIndex !== -1) {
1824
2035
  reorderFields(block.id, fromIndex, toIndex);
1825
2036
  }
1826
2037
  }
1827
2038
  if (!block) {
1828
- return /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-canvas", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ import_react11.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."));
1829
2040
  }
1830
- return /* @__PURE__ */ import_react11.default.createElement("div", { className: `bb-canvas${isReadOnly ? " bb-canvas--readonly" : ""}` }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-canvas__inner" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-canvas__header" }, block.slug, " - ", block.fields.length, " field", block.fields.length !== 1 ? "s" : ""), block.fields.length === 0 ? /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-canvas__empty" }, "Add fields from the palette on the left") : /* @__PURE__ */ import_react11.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(
1831
2065
  import_core.DndContext,
1832
2066
  {
1833
2067
  sensors,
@@ -1835,19 +2069,22 @@ function BuilderCanvas() {
1835
2069
  modifiers: [import_modifiers.restrictToVerticalAxis, import_modifiers.restrictToParentElement],
1836
2070
  onDragEnd: handleDragEnd
1837
2071
  },
1838
- /* @__PURE__ */ import_react11.default.createElement(
2072
+ /* @__PURE__ */ import_react12.default.createElement(
1839
2073
  import_sortable2.SortableContext,
1840
2074
  {
1841
- items: block.fields.map((f) => f.id),
2075
+ items: targetFields.map((f) => f.id),
1842
2076
  strategy: import_sortable2.verticalListSortingStrategy
1843
2077
  },
1844
- /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-canvas__field-list" }, block.fields.map((field, i) => /* @__PURE__ */ import_react11.default.createElement(
2078
+ /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-canvas__field-list" }, targetFields.map((field, i) => /* @__PURE__ */ import_react12.default.createElement(
1845
2079
  SortableFieldCard,
1846
2080
  {
1847
2081
  key: field.id,
1848
2082
  field,
1849
2083
  blockId: block.id,
1850
- 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
1851
2088
  }
1852
2089
  )))
1853
2090
  )
@@ -1855,18 +2092,18 @@ function BuilderCanvas() {
1855
2092
  }
1856
2093
 
1857
2094
  // src/block-builder/components/canvas/ConfigPanel.tsx
1858
- var import_react14 = __toESM(require("react"), 1);
2095
+ var import_react15 = __toESM(require("react"), 1);
1859
2096
 
1860
2097
  // src/block-builder/components/config/BlockConfig.tsx
1861
- var import_react12 = __toESM(require("react"), 1);
2098
+ var import_react13 = __toESM(require("react"), 1);
1862
2099
  function BlockConfig() {
1863
2100
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1864
2101
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1865
2102
  const updateBlock = useBuilderStore((s) => s.updateBlock);
1866
2103
  if (!block) {
1867
- return /* @__PURE__ */ import_react12.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.");
1868
2105
  }
1869
- return /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Slug *"), /* @__PURE__ */ import_react12.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(
1870
2107
  "input",
1871
2108
  {
1872
2109
  type: "text",
@@ -1875,7 +2112,7 @@ function BlockConfig() {
1875
2112
  placeholder: "myBlock",
1876
2113
  className: "bb-input"
1877
2114
  }
1878
- ), /* @__PURE__ */ import_react12.default.createElement("span", { className: "bb-form__hint" }, "Unique identifier used in code and database")), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Interface Name"), /* @__PURE__ */ import_react12.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(
1879
2116
  "input",
1880
2117
  {
1881
2118
  type: "text",
@@ -1884,7 +2121,7 @@ function BlockConfig() {
1884
2121
  placeholder: "MyBlock",
1885
2122
  className: "bb-input"
1886
2123
  }
1887
- )), /* @__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" }, "Singular Label"), /* @__PURE__ */ import_react12.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(
1888
2125
  "input",
1889
2126
  {
1890
2127
  type: "text",
@@ -1893,7 +2130,7 @@ function BlockConfig() {
1893
2130
  placeholder: "My Block",
1894
2131
  className: "bb-input"
1895
2132
  }
1896
- )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Plural Label"), /* @__PURE__ */ import_react12.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(
1897
2134
  "input",
1898
2135
  {
1899
2136
  type: "text",
@@ -1902,7 +2139,7 @@ function BlockConfig() {
1902
2139
  placeholder: "My Blocks",
1903
2140
  className: "bb-input"
1904
2141
  }
1905
- ))), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Image URL"), /* @__PURE__ */ import_react12.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(
1906
2143
  "input",
1907
2144
  {
1908
2145
  type: "text",
@@ -1911,38 +2148,62 @@ function BlockConfig() {
1911
2148
  placeholder: "https://...",
1912
2149
  className: "bb-input"
1913
2150
  }
1914
- )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-stat-box" }, /* @__PURE__ */ import_react12.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"));
1915
2152
  }
1916
2153
 
1917
2154
  // src/block-builder/components/config/FieldConfig.tsx
1918
- var import_react13 = __toESM(require("react"), 1);
2155
+ var import_react14 = __toESM(require("react"), 1);
1919
2156
  var ALL_TYPES = [
1920
2157
  "text",
1921
2158
  "textarea",
2159
+ "richtext",
1922
2160
  "number",
1923
2161
  "email",
2162
+ "url",
2163
+ "color",
1924
2164
  "date",
1925
2165
  "checkbox",
1926
2166
  "select",
1927
- "radio",
1928
- "upload",
2167
+ "multiselect",
2168
+ "image",
2169
+ "file",
1929
2170
  "relationship",
1930
- "json"
2171
+ "json",
2172
+ "array",
2173
+ "group",
2174
+ "blocks",
2175
+ "row",
2176
+ "tabs",
2177
+ "collapsible"
1931
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
+ ]);
1932
2191
  function FieldConfig() {
1933
2192
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1934
2193
  const activeFieldId = useBuilderStore((s) => s.activeFieldId);
2194
+ const activeParentPath = useBuilderStore((s) => s.activeParentPath);
1935
2195
  const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1936
- const field = block?.fields.find((f) => f.id === activeFieldId);
2196
+ const field = block ? getTargetFields(block, activeParentPath)?.find((f) => f.id === activeFieldId) : void 0;
1937
2197
  const updateField = useBuilderStore((s) => s.updateField);
2198
+ const pushParentPath = useBuilderStore((s) => s.pushParentPath);
1938
2199
  if (!activeBlockId || !activeFieldId || !field) {
1939
- return /* @__PURE__ */ import_react13.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.");
1940
2201
  }
1941
2202
  function upd(updates) {
1942
2203
  updateField(activeBlockId, activeFieldId, updates);
1943
2204
  }
1944
- const needsOptions = field.type === "select" || field.type === "radio";
1945
- return /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form" }, /* @__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" }, "Field Name *"), /* @__PURE__ */ import_react13.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(
1946
2207
  "input",
1947
2208
  {
1948
2209
  type: "text",
@@ -1951,15 +2212,15 @@ function FieldConfig() {
1951
2212
  placeholder: "fieldName",
1952
2213
  className: "bb-input"
1953
2214
  }
1954
- )), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Type *"), /* @__PURE__ */ import_react13.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(
1955
2216
  "select",
1956
2217
  {
1957
2218
  value: field.type,
1958
2219
  onChange: (e) => upd({ type: e.target.value }),
1959
2220
  className: "bb-input bb-select"
1960
2221
  },
1961
- ALL_TYPES.map((t) => /* @__PURE__ */ import_react13.default.createElement("option", { key: t, value: t }, t))
1962
- ))), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Label"), /* @__PURE__ */ import_react13.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(
1963
2224
  "input",
1964
2225
  {
1965
2226
  type: "text",
@@ -1968,28 +2229,44 @@ function FieldConfig() {
1968
2229
  placeholder: "Human-readable label",
1969
2230
  className: "bb-input"
1970
2231
  }
1971
- )), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-flags-row" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react13.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(
1972
2233
  "input",
1973
2234
  {
1974
2235
  type: "checkbox",
1975
2236
  checked: field.required ?? false,
1976
2237
  onChange: (e) => upd({ required: e.target.checked })
1977
2238
  }
1978
- ), "Required"), /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react13.default.createElement(
2239
+ ), "Required"), /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
1979
2240
  "input",
1980
2241
  {
1981
2242
  type: "checkbox",
1982
2243
  checked: field.unique ?? false,
1983
2244
  onChange: (e) => upd({ unique: e.target.checked })
1984
2245
  }
1985
- ), "Unique"), /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react13.default.createElement(
2246
+ ), "Unique"), /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
1986
2247
  "input",
1987
2248
  {
1988
2249
  type: "checkbox",
1989
2250
  checked: field.localized ?? false,
1990
2251
  onChange: (e) => upd({ localized: e.target.checked })
1991
2252
  }
1992
- ), "Localized")), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Default Value"), /* @__PURE__ */ import_react13.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(
1993
2270
  "input",
1994
2271
  {
1995
2272
  type: "text",
@@ -1998,23 +2275,23 @@ function FieldConfig() {
1998
2275
  placeholder: "Default value",
1999
2276
  className: "bb-input"
2000
2277
  }
2001
- )), field.type === "relationship" && /* @__PURE__ */ import_react13.default.createElement(import_react13.default.Fragment, null, /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Relation To (collection slug)"), /* @__PURE__ */ import_react13.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(
2002
2279
  "input",
2003
2280
  {
2004
2281
  type: "text",
2005
- value: field.relationTo ?? "",
2006
- onChange: (e) => upd({ relationTo: e.target.value }),
2282
+ value: field.collection ?? "",
2283
+ onChange: (e) => upd({ collection: e.target.value }),
2007
2284
  placeholder: "pages",
2008
2285
  className: "bb-input"
2009
2286
  }
2010
- )), /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react13.default.createElement(
2287
+ )), /* @__PURE__ */ import_react14.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react14.default.createElement(
2011
2288
  "input",
2012
2289
  {
2013
2290
  type: "checkbox",
2014
2291
  checked: field.hasMany ?? false,
2015
2292
  onChange: (e) => upd({ hasMany: e.target.checked })
2016
2293
  }
2017
- ), "Has Many")), field.type === "array" && /* @__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" }, "Min Rows"), /* @__PURE__ */ import_react13.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(
2018
2295
  "input",
2019
2296
  {
2020
2297
  type: "number",
@@ -2022,7 +2299,7 @@ function FieldConfig() {
2022
2299
  onChange: (e) => upd({ minRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
2023
2300
  className: "bb-input"
2024
2301
  }
2025
- )), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("label", { className: "bb-form__label" }, "Max Rows"), /* @__PURE__ */ import_react13.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(
2026
2303
  "input",
2027
2304
  {
2028
2305
  type: "number",
@@ -2030,7 +2307,54 @@ function FieldConfig() {
2030
2307
  onChange: (e) => upd({ maxRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
2031
2308
  className: "bb-input"
2032
2309
  }
2033
- ))), needsOptions && /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-options-label" }, "Options"), (field.options ?? []).map((opt, i) => /* @__PURE__ */ import_react13.default.createElement("div", { key: i, className: "bb-option-row" }, /* @__PURE__ */ import_react13.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(
2034
2358
  "input",
2035
2359
  {
2036
2360
  type: "text",
@@ -2043,7 +2367,7 @@ function FieldConfig() {
2043
2367
  },
2044
2368
  className: "bb-input"
2045
2369
  }
2046
- ), /* @__PURE__ */ import_react13.default.createElement(
2370
+ ), /* @__PURE__ */ import_react14.default.createElement(
2047
2371
  "input",
2048
2372
  {
2049
2373
  type: "text",
@@ -2056,7 +2380,7 @@ function FieldConfig() {
2056
2380
  },
2057
2381
  className: "bb-input"
2058
2382
  }
2059
- ), /* @__PURE__ */ import_react13.default.createElement(
2383
+ ), /* @__PURE__ */ import_react14.default.createElement(
2060
2384
  "button",
2061
2385
  {
2062
2386
  type: "button",
@@ -2068,7 +2392,7 @@ function FieldConfig() {
2068
2392
  title: "Remove option"
2069
2393
  },
2070
2394
  "x"
2071
- ))), /* @__PURE__ */ import_react13.default.createElement(
2395
+ ))), /* @__PURE__ */ import_react14.default.createElement(
2072
2396
  "button",
2073
2397
  {
2074
2398
  type: "button",
@@ -2081,11 +2405,11 @@ function FieldConfig() {
2081
2405
 
2082
2406
  // src/block-builder/components/canvas/ConfigPanel.tsx
2083
2407
  function ConfigPanel() {
2084
- const [tab, setTab] = (0, import_react14.useState)("block");
2408
+ const [tab, setTab] = (0, import_react15.useState)("block");
2085
2409
  const activeFieldId = useBuilderStore((s) => s.activeFieldId);
2086
2410
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
2087
2411
  const activeTab = activeFieldId ? "field" : tab;
2088
- return /* @__PURE__ */ import_react14.default.createElement("div", { className: `bb-config${isReadOnly ? " bb-config--readonly" : ""}` }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-config__tabs" }, ["block", "field"].map((t) => /* @__PURE__ */ import_react14.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(
2089
2413
  "button",
2090
2414
  {
2091
2415
  key: t,
@@ -2094,11 +2418,11 @@ function ConfigPanel() {
2094
2418
  className: `bb-config__tab${activeTab === t ? " bb-config__tab--active" : ""}`
2095
2419
  },
2096
2420
  t
2097
- ))), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-config__body" }, activeTab === "block" ? /* @__PURE__ */ import_react14.default.createElement(BlockConfig, null) : /* @__PURE__ */ import_react14.default.createElement(FieldConfig, null)), isReadOnly && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-config__readonly-overlay" }, /* @__PURE__ */ import_react14.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")));
2098
2422
  }
2099
2423
 
2100
2424
  // src/block-builder/components/sidebar/FieldPalette.tsx
2101
- var import_react15 = __toESM(require("react"), 1);
2425
+ var import_react16 = __toESM(require("react"), 1);
2102
2426
  var import_lucide_react4 = require("lucide-react");
2103
2427
 
2104
2428
  // src/block-builder/lib/field-palette.ts
@@ -2112,11 +2436,17 @@ var FIELD_PALETTE = [
2112
2436
  { type: "checkbox", label: "Checkbox", description: "Boolean toggle", icon: "CheckSquare", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
2113
2437
  // Choice
2114
2438
  { type: "select", label: "Select", description: "Dropdown selection", icon: "ChevronDown", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
2115
- { type: "radio", label: "Radio", description: "Radio button group", icon: "Circle", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
2116
2439
  // Media
2117
- { 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" },
2118
2442
  // Relational
2119
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" },
2120
2450
  // Advanced
2121
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" }
2122
2452
  ];
@@ -2125,6 +2455,7 @@ var FIELD_CATEGORIES = [
2125
2455
  { id: "choice", label: "Choice" },
2126
2456
  { id: "media", label: "Media" },
2127
2457
  { id: "relational", label: "Relational" },
2458
+ { id: "layout", label: "Layout" },
2128
2459
  { id: "advanced", label: "Advanced" }
2129
2460
  ];
2130
2461
  function getFieldMeta(type) {
@@ -2146,10 +2477,13 @@ var ICON_MAP2 = {
2146
2477
  Link: import_lucide_react4.Link,
2147
2478
  List: import_lucide_react4.List,
2148
2479
  Folder: import_lucide_react4.Folder,
2149
- 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
2150
2484
  };
2151
2485
  function FieldPalette() {
2152
- const [search, setSearch] = (0, import_react15.useState)("");
2486
+ const [search, setSearch] = (0, import_react16.useState)("");
2153
2487
  const activeBlockId = useBuilderStore((s) => s.activeBlockId);
2154
2488
  const addField = useBuilderStore((s) => s.addField);
2155
2489
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
@@ -2160,7 +2494,7 @@ function FieldPalette() {
2160
2494
  if (!activeBlockId || isReadOnly) return;
2161
2495
  addField(activeBlockId, type);
2162
2496
  }
2163
- return /* @__PURE__ */ import_react15.default.createElement("div", { className: `bb-sidebar bb-sidebar--200 bb-sidebar--palette${isReadOnly ? " bb-sidebar--readonly" : ""}` }, /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react15.default.createElement("span", { className: "bb-sidebar__title" }, "Fields")), /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-palette__search-wrap" }, /* @__PURE__ */ import_react15.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(
2164
2498
  "input",
2165
2499
  {
2166
2500
  type: "text",
@@ -2169,7 +2503,7 @@ function FieldPalette() {
2169
2503
  placeholder: "Search...",
2170
2504
  className: "bb-palette__search"
2171
2505
  }
2172
- )), /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-sidebar__body" }, search.trim() ? /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-palette__items" }, filtered.map((item) => /* @__PURE__ */ import_react15.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(
2173
2507
  FieldButton,
2174
2508
  {
2175
2509
  key: item.type,
@@ -2181,7 +2515,7 @@ function FieldPalette() {
2181
2515
  }
2182
2516
  ))) : FIELD_CATEGORIES.map((cat) => {
2183
2517
  const items = FIELD_PALETTE.filter((f) => f.category === cat.id);
2184
- return /* @__PURE__ */ import_react15.default.createElement("div", { key: cat.id, style: { marginTop: 6 } }, /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-palette__cat-label" }, cat.label), /* @__PURE__ */ import_react15.default.createElement("div", { className: "bb-palette__items" }, items.map((item) => /* @__PURE__ */ import_react15.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(
2185
2519
  FieldButton,
2186
2520
  {
2187
2521
  key: item.type,
@@ -2192,7 +2526,7 @@ function FieldPalette() {
2192
2526
  disabled: !activeBlockId || isReadOnly
2193
2527
  }
2194
2528
  ))));
2195
- })), !activeBlockId && /* @__PURE__ */ import_react15.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"));
2196
2530
  }
2197
2531
  function FieldButton({
2198
2532
  type,
@@ -2203,7 +2537,7 @@ function FieldButton({
2203
2537
  }) {
2204
2538
  const meta = getFieldMeta(type);
2205
2539
  const Icon = ICON_MAP2[icon];
2206
- return /* @__PURE__ */ import_react15.default.createElement(
2540
+ return /* @__PURE__ */ import_react16.default.createElement(
2207
2541
  "button",
2208
2542
  {
2209
2543
  type: "button",
@@ -2212,14 +2546,291 @@ function FieldButton({
2212
2546
  title: meta?.description,
2213
2547
  className: "bb-palette__item"
2214
2548
  },
2215
- /* @__PURE__ */ import_react15.default.createElement("span", { className: "bb-palette__item__icon" }, Icon ? /* @__PURE__ */ import_react15.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null),
2216
- /* @__PURE__ */ import_react15.default.createElement("span", { className: "bb-palette__item__label" }, label),
2217
- /* @__PURE__ */ import_react15.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)
2218
2552
  );
2219
2553
  }
2220
2554
 
2221
2555
  // src/block-builder/components/canvas/CodePreview.tsx
2222
- var import_react16 = __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
2223
2834
  var KEYWORDS = /* @__PURE__ */ new Set([
2224
2835
  "import",
2225
2836
  "export",
@@ -2307,22 +2918,22 @@ var TOKEN_COLORS = {
2307
2918
  };
2308
2919
  function HighlightedCode({ code }) {
2309
2920
  const tokens = tokenise(code);
2310
- return /* @__PURE__ */ import_react16.default.createElement("code", { style: { fontFamily: "inherit" } }, tokens.map((tok, i) => /* @__PURE__ */ import_react16.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)));
2311
2922
  }
2312
2923
  function CodePreview() {
2313
2924
  const blocks = useBuilderStore((s) => s.blocks);
2314
- const [fileMap, setFileMap] = (0, import_react16.useState)({});
2315
- const [activeFile, setActiveFile] = (0, import_react16.useState)(null);
2316
- const [copied, setCopied] = (0, import_react16.useState)(false);
2317
- const timerRef = (0, import_react16.useRef)(null);
2318
- const copyTimerRef = (0, import_react16.useRef)(null);
2319
- const regenerate = (0, import_react16.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)(() => {
2320
2931
  if (blocks.length === 0) {
2321
2932
  setFileMap({});
2322
2933
  setActiveFile(null);
2323
2934
  return;
2324
2935
  }
2325
- const blockOutputs = generateAllBlocks(blocks);
2936
+ const blockOutputs = generateAllBlocks(blocks, { react: true });
2326
2937
  const indexOutput = generateIndexFile(blocks);
2327
2938
  const next = {};
2328
2939
  for (const out of blockOutputs) next[out.filename] = out.code;
@@ -2333,7 +2944,7 @@ function CodePreview() {
2333
2944
  return prev && keys.includes(prev) ? prev : keys[0] ?? null;
2334
2945
  });
2335
2946
  }, [blocks]);
2336
- (0, import_react16.useEffect)(() => {
2947
+ (0, import_react17.useEffect)(() => {
2337
2948
  if (timerRef.current) clearTimeout(timerRef.current);
2338
2949
  timerRef.current = setTimeout(regenerate, 300);
2339
2950
  return () => {
@@ -2352,7 +2963,7 @@ function CodePreview() {
2352
2963
  const activeCode = activeFile ? fileMap[activeFile] ?? "" : "";
2353
2964
  const lineCount = activeCode ? activeCode.split("\n").length : 0;
2354
2965
  if (fileNames.length === 0) {
2355
- return /* @__PURE__ */ import_react16.default.createElement(
2966
+ return /* @__PURE__ */ import_react17.default.createElement(
2356
2967
  "div",
2357
2968
  {
2358
2969
  className: "code-preview",
@@ -2369,7 +2980,7 @@ function CodePreview() {
2369
2980
  "Add a block to see generated TypeScript code"
2370
2981
  );
2371
2982
  }
2372
- return /* @__PURE__ */ import_react16.default.createElement("div", { className: "code-preview", style: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }, /* @__PURE__ */ import_react16.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(
2373
2984
  "div",
2374
2985
  {
2375
2986
  style: {
@@ -2383,7 +2994,7 @@ function CodePreview() {
2383
2994
  },
2384
2995
  fileNames.map((name) => {
2385
2996
  const isActive = activeFile === name;
2386
- return /* @__PURE__ */ import_react16.default.createElement(
2997
+ return /* @__PURE__ */ import_react17.default.createElement(
2387
2998
  "button",
2388
2999
  {
2389
3000
  key: name,
@@ -2406,8 +3017,8 @@ function CodePreview() {
2406
3017
  name
2407
3018
  );
2408
3019
  }),
2409
- /* @__PURE__ */ import_react16.default.createElement("div", { style: { flex: 1 } }),
2410
- lineCount > 0 && /* @__PURE__ */ import_react16.default.createElement(
3020
+ /* @__PURE__ */ import_react17.default.createElement("div", { style: { flex: 1 } }),
3021
+ lineCount > 0 && /* @__PURE__ */ import_react17.default.createElement(
2411
3022
  "span",
2412
3023
  {
2413
3024
  style: {
@@ -2420,7 +3031,7 @@ function CodePreview() {
2420
3031
  lineCount,
2421
3032
  " lines"
2422
3033
  ),
2423
- /* @__PURE__ */ import_react16.default.createElement(
3034
+ /* @__PURE__ */ import_react17.default.createElement(
2424
3035
  "button",
2425
3036
  {
2426
3037
  type: "button",
@@ -2443,7 +3054,7 @@ function CodePreview() {
2443
3054
  },
2444
3055
  copied ? "Copied" : "Copy"
2445
3056
  )
2446
- ), /* @__PURE__ */ import_react16.default.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ import_react16.default.createElement(
3057
+ ), /* @__PURE__ */ import_react17.default.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ import_react17.default.createElement(
2447
3058
  "div",
2448
3059
  {
2449
3060
  style: {
@@ -2460,8 +3071,8 @@ function CodePreview() {
2460
3071
  },
2461
3072
  "aria-hidden": "true"
2462
3073
  },
2463
- activeCode.split("\n").map((_, i) => /* @__PURE__ */ import_react16.default.createElement("div", { key: i }, i + 1))
2464
- ), /* @__PURE__ */ import_react16.default.createElement(
3074
+ activeCode.split("\n").map((_, i) => /* @__PURE__ */ import_react17.default.createElement("div", { key: i }, i + 1))
3075
+ ), /* @__PURE__ */ import_react17.default.createElement(
2465
3076
  "pre",
2466
3077
  {
2467
3078
  style: {
@@ -2476,49 +3087,194 @@ function CodePreview() {
2476
3087
  whiteSpace: "pre"
2477
3088
  }
2478
3089
  },
2479
- /* @__PURE__ */ import_react16.default.createElement(HighlightedCode, { code: activeCode })
3090
+ /* @__PURE__ */ import_react17.default.createElement(HighlightedCode, { code: activeCode })
2480
3091
  )));
2481
3092
  }
2482
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
+
2483
3223
  // src/block-builder/components/canvas/BuilderShell.tsx
2484
3224
  function BuilderShell({ loadSlug }) {
2485
3225
  const loadBlock = useBuilderStore((s) => s.loadBlock);
2486
3226
  const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
2487
3227
  const setBlockSlug = useBuilderStore((s) => s.setBlockSlug);
2488
3228
  const isReadOnly = useBuilderStore((s) => s.isReadOnly);
2489
- const [activeSlug, setActiveSlug] = (0, import_react17.useState)(loadSlug ?? null);
2490
- const [loading, setLoading] = (0, import_react17.useState)(!!loadSlug);
2491
- const [notification, setNotification] = (0, import_react17.useState)(null);
2492
- const [showCodePreview, setShowCodePreview] = (0, import_react17.useState)(false);
2493
- const [versions, setVersions] = (0, import_react17.useState)([]);
2494
- const [selectedVersionId, setSelectedVersionId] = (0, import_react17.useState)(null);
2495
- const [blockDefs, setBlockDefs] = (0, import_react17.useState)([]);
2496
- const [mobilePanelTab, setMobilePanelTab] = (0, import_react17.useState)("blocks");
2497
- (0, import_react17.useEffect)(() => {
2498
- 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();
2499
3246
  setBlockDefs(
2500
3247
  (json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
2501
3248
  );
2502
- }).catch((err) => {
3249
+ } catch (err) {
2503
3250
  console.error("[block-builder] Failed to load block definitions:", err);
2504
- setNotification({ status: "error", title: "Failed to load block definitions", errors: ["Could not load block definitions. Please refresh the page."] });
2505
- });
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
+ }
2506
3255
  }, []);
2507
- const loadVersionsForSlug = (0, import_react17.useCallback)(async (slug) => {
3256
+ (0, import_react19.useEffect)(() => {
3257
+ refreshBlockDefs(true);
3258
+ }, [refreshBlockDefs]);
3259
+ const loadVersionsForSlug = (0, import_react19.useCallback)(async (slug) => {
2508
3260
  try {
2509
- 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
+ });
2510
3264
  const json = await res.json();
2511
3265
  return json.versions ?? [];
2512
3266
  } catch {
2513
3267
  return [];
2514
3268
  }
2515
3269
  }, []);
2516
- const loadVersion = (0, import_react17.useCallback)(async (slug, versionId) => {
3270
+ const loadVersion = (0, import_react19.useCallback)(async (slug, versionId) => {
2517
3271
  setLoading(true);
2518
3272
  setNotification(null);
2519
3273
  const url = versionId ? `/api/block-builder/load/${encodeURIComponent(slug)}?versionId=${encodeURIComponent(versionId)}` : `/api/block-builder/load/${encodeURIComponent(slug)}`;
2520
3274
  try {
2521
- const res = await fetch(url);
3275
+ const res = await fetch(url, {
3276
+ headers: { "X-Block-Builder": "1" }
3277
+ });
2522
3278
  const json = await res.json();
2523
3279
  if (json.block) {
2524
3280
  loadBlock(json.block);
@@ -2533,7 +3289,7 @@ function BuilderShell({ loadSlug }) {
2533
3289
  setLoading(false);
2534
3290
  }
2535
3291
  }, [loadBlock, setVersionMeta]);
2536
- const loadBlockBySlug = (0, import_react17.useCallback)(async (slug) => {
3292
+ const loadBlockBySlug = (0, import_react19.useCallback)(async (slug) => {
2537
3293
  setActiveSlug(slug);
2538
3294
  setBlockSlug(slug);
2539
3295
  setVersions([]);
@@ -2546,7 +3302,7 @@ function BuilderShell({ loadSlug }) {
2546
3302
  const current = list.find((v) => v.isCurrent) ?? list[0];
2547
3303
  if (current) setSelectedVersionId(current.id);
2548
3304
  }, [loadVersion, loadVersionsForSlug, setBlockSlug]);
2549
- (0, import_react17.useEffect)(() => {
3305
+ (0, import_react19.useEffect)(() => {
2550
3306
  if (!loadSlug) return;
2551
3307
  loadBlockBySlug(loadSlug);
2552
3308
  }, [loadSlug]);
@@ -2562,14 +3318,22 @@ function BuilderShell({ loadSlug }) {
2562
3318
  const current = list.find((v) => v.isCurrent) ?? list[0];
2563
3319
  if (current) setSelectedVersionId(current.id);
2564
3320
  }
2565
- async function handleAfterPublish() {
2566
- if (!activeSlug) return;
2567
- 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
+ ]);
2568
3329
  setVersions(list);
2569
3330
  const current = list.find((v) => v.isCurrent) ?? list[0];
2570
- if (current) setSelectedVersionId(current.id);
3331
+ setSelectedVersionId(current ? current.id : null);
2571
3332
  }
2572
- return /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ import_react17.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(
2573
3337
  TopBar,
2574
3338
  {
2575
3339
  blockDefs,
@@ -2581,9 +3345,11 @@ function BuilderShell({ loadSlug }) {
2581
3345
  onRestoreVersion: handleRestoreVersion,
2582
3346
  onAfterPublish: handleAfterPublish,
2583
3347
  notification,
2584
- onSetNotification: setNotification
3348
+ onSetNotification: setNotification,
3349
+ previewOpen,
3350
+ onTogglePreview: () => setPreviewOpen((p) => !p)
2585
3351
  }
2586
- ), loading && /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-loading-bar" }, "Loading..."), isReadOnly && !loading && /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ import_react17.default.createElement("span", { className: "bb-readonly-banner__icon" }, "[i]"), /* @__PURE__ */ import_react17.default.createElement("span", null, "You are viewing a previous version - read only.", /* @__PURE__ */ import_react17.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(
2587
3353
  "button",
2588
3354
  {
2589
3355
  type: "button",
@@ -2591,7 +3357,7 @@ function BuilderShell({ loadSlug }) {
2591
3357
  onClick: handleRestoreVersion
2592
3358
  },
2593
3359
  "Switch to latest"
2594
- ))), /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ import_react17.default.createElement(BlockList, { blockDefs, activeSlug, onBlockSelect: loadBlockBySlug }), /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-main__center" }, /* @__PURE__ */ import_react17.default.createElement(FieldPalette, null), /* @__PURE__ */ import_react17.default.createElement(BuilderCanvas, null)), /* @__PURE__ */ import_react17.default.createElement(ConfigPanel, null)), /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ import_react17.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(
2595
3361
  "button",
2596
3362
  {
2597
3363
  type: "button",
@@ -2600,12 +3366,12 @@ function BuilderShell({ loadSlug }) {
2600
3366
  },
2601
3367
  showCodePreview ? "v" : ">",
2602
3368
  " Code Preview"
2603
- ), showCodePreview && /* @__PURE__ */ import_react17.default.createElement("div", { className: "bb-footer__content" }, /* @__PURE__ */ import_react17.default.createElement(CodePreview, null))), /* @__PURE__ */ import_react17.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" }, [
2604
3370
  { id: "blocks", icon: "B", label: "Blocks" },
2605
3371
  { id: "canvas", icon: "[]", label: "Canvas" },
2606
3372
  { id: "palette", icon: "+", label: "Fields" },
2607
3373
  { id: "config", icon: "*", label: "Config" }
2608
- ].map(({ id, icon, label }) => /* @__PURE__ */ import_react17.default.createElement(
3374
+ ].map(({ id, icon, label }) => /* @__PURE__ */ import_react19.default.createElement(
2609
3375
  "button",
2610
3376
  {
2611
3377
  key: id,
@@ -2613,16 +3379,16 @@ function BuilderShell({ loadSlug }) {
2613
3379
  className: `bb-mobile-nav__tab${mobilePanelTab === id ? " bb-mobile-nav__tab--active" : ""}`,
2614
3380
  onClick: () => setMobilePanelTab(id)
2615
3381
  },
2616
- /* @__PURE__ */ import_react17.default.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
3382
+ /* @__PURE__ */ import_react19.default.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
2617
3383
  label
2618
- ))));
3384
+ )))));
2619
3385
  }
2620
3386
 
2621
3387
  // src/components/BlockBuilderNavLink/index.tsx
2622
- var import_react18 = __toESM(require("react"), 1);
3388
+ var import_react20 = __toESM(require("react"), 1);
2623
3389
  var import_link = __toESM(require("next/link"), 1);
2624
3390
  function BlockBuilderNavLink() {
2625
- return /* @__PURE__ */ import_react18.default.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ import_react18.default.createElement(
3391
+ return /* @__PURE__ */ import_react20.default.createElement("div", { style: { padding: "0 16px", marginTop: "8px" } }, /* @__PURE__ */ import_react20.default.createElement(
2626
3392
  import_link.default,
2627
3393
  {
2628
3394
  href: "/block-builder",