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