@nextbridgehq/payload-block-builder 0.1.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.
@@ -0,0 +1,2454 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/client.ts
30
+ var client_exports = {};
31
+ __export(client_exports, {
32
+ BlockDataField: () => BlockDataField,
33
+ BuilderShell: () => BuilderShell,
34
+ EditInBuilderButton: () => EditInBuilderButton,
35
+ SchemaBuilderField: () => SchemaBuilderField
36
+ });
37
+ module.exports = __toCommonJS(client_exports);
38
+
39
+ // src/components/BlockDataField/index.tsx
40
+ var import_react = __toESM(require("react"), 1);
41
+ var import_ui = require("@payloadcms/ui");
42
+ function MediaPicker({ label, required, value, onChange }) {
43
+ const changeRef = (0, import_react.useRef)(onChange);
44
+ const closeRef = (0, import_react.useRef)(() => {
45
+ });
46
+ (0, import_react.useEffect)(() => {
47
+ changeRef.current = onChange;
48
+ });
49
+ const handleSelect = (0, import_react.useCallback)(
50
+ ({ docID, doc }) => {
51
+ changeRef.current({
52
+ id: docID,
53
+ filename: doc?.filename ?? null,
54
+ url: doc?.url ?? null,
55
+ alt: doc?.alt ?? null
56
+ });
57
+ closeRef.current();
58
+ },
59
+ []
60
+ );
61
+ const [ListDrawer, ListDrawerToggler, { closeDrawer }] = (0, import_ui.useListDrawer)({
62
+ collectionSlugs: ["media"]
63
+ });
64
+ closeRef.current = closeDrawer;
65
+ const media = value && typeof value === "object" ? value : null;
66
+ const mediaId = media?.id ?? (typeof value === "string" || typeof value === "number" ? value : null);
67
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-area" }, mediaId ? /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-selected" }, media?.url ? /* @__PURE__ */ import_react.default.createElement("img", { src: media.url, alt: media.alt ?? "", className: "bdf-thumb" }) : /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-thumb-placeholder" }, "\xF0\u0178\u2013\xBC"), /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-upload-name" }, media?.filename ? String(media.filename) : `ID: ${String(mediaId)}`), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-upload-actions" }, /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Change"), /* @__PURE__ */ import_react.default.createElement(
68
+ "button",
69
+ {
70
+ type: "button",
71
+ className: "bdf-icon-btn bdf-icon-btn--danger",
72
+ title: "Remove media",
73
+ onClick: () => onChange(null)
74
+ },
75
+ "\xE2\u0153\u2022"
76
+ ))) : /* @__PURE__ */ import_react.default.createElement(ListDrawerToggler, { className: "bdf-upload-btn" }, "Choose from Media Library")), /* @__PURE__ */ import_react.default.createElement(ListDrawer, { onSelect: handleSelect }));
77
+ }
78
+ function SchemaForm({ schema, value, onChange }) {
79
+ const set = (0, import_react.useCallback)(
80
+ (key, val) => onChange({ ...value, [key]: val }),
81
+ [value, onChange]
82
+ );
83
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, schema.map((field) => /* @__PURE__ */ import_react.default.createElement(
84
+ FieldInput,
85
+ {
86
+ key: field.name,
87
+ field,
88
+ value: value[field.name],
89
+ onChange: (v) => set(field.name, v)
90
+ }
91
+ )));
92
+ }
93
+ function FieldInput({ field, value, onChange }) {
94
+ const label = field.label ?? field.name;
95
+ switch (field.type) {
96
+ case "text":
97
+ case "url":
98
+ case "email":
99
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement(
100
+ "input",
101
+ {
102
+ className: "bdf-input",
103
+ type: field.type === "email" ? "email" : "text",
104
+ value: value ?? "",
105
+ onChange: (e) => onChange(e.target.value)
106
+ }
107
+ ));
108
+ case "color":
109
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ import_react.default.createElement(
110
+ "input",
111
+ {
112
+ className: "bdf-input",
113
+ type: "text",
114
+ value: value ?? "",
115
+ onChange: (e) => onChange(e.target.value),
116
+ placeholder: "#ffffff",
117
+ style: { flex: 1 }
118
+ }
119
+ ), /* @__PURE__ */ import_react.default.createElement(
120
+ "input",
121
+ {
122
+ type: "color",
123
+ value: value || "#ffffff",
124
+ onChange: (e) => onChange(e.target.value),
125
+ style: { width: 40, height: 40, padding: 2, border: "1px solid var(--theme-elevation-150)", borderRadius: 4, cursor: "pointer", flexShrink: 0 }
126
+ }
127
+ )));
128
+ case "textarea":
129
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement(
130
+ "textarea",
131
+ {
132
+ className: "bdf-input bdf-textarea",
133
+ value: value ?? "",
134
+ onChange: (e) => onChange(e.target.value),
135
+ rows: 4
136
+ }
137
+ ));
138
+ case "richtext":
139
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(plain text)")), /* @__PURE__ */ import_react.default.createElement(
140
+ "textarea",
141
+ {
142
+ className: "bdf-input bdf-textarea",
143
+ value: value ?? "",
144
+ onChange: (e) => onChange(e.target.value),
145
+ rows: 6
146
+ }
147
+ ));
148
+ case "number": {
149
+ const numVal = typeof value === "number" && !Number.isNaN(value) ? value : "";
150
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement(
151
+ "input",
152
+ {
153
+ className: "bdf-input",
154
+ type: "number",
155
+ value: numVal,
156
+ onChange: (e) => onChange(e.target.value === "" ? void 0 : e.target.valueAsNumber)
157
+ }
158
+ ));
159
+ }
160
+ case "checkbox":
161
+ return /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-checkbox-row" }, /* @__PURE__ */ import_react.default.createElement(
162
+ "input",
163
+ {
164
+ type: "checkbox",
165
+ checked: Boolean(value),
166
+ onChange: (e) => onChange(e.target.checked)
167
+ }
168
+ ), /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-checkbox-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, " *")));
169
+ case "date":
170
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement(
171
+ "input",
172
+ {
173
+ className: "bdf-input",
174
+ type: "date",
175
+ value: value ?? "",
176
+ onChange: (e) => onChange(e.target.value)
177
+ }
178
+ ));
179
+ case "select":
180
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*")), /* @__PURE__ */ import_react.default.createElement(
181
+ "select",
182
+ {
183
+ className: "bdf-input bdf-select",
184
+ value: value ?? "",
185
+ onChange: (e) => onChange(e.target.value)
186
+ },
187
+ /* @__PURE__ */ import_react.default.createElement("option", { value: "" }, "\xE2\u20AC\u201D select \xE2\u20AC\u201D"),
188
+ (field.options ?? []).map((opt) => /* @__PURE__ */ import_react.default.createElement("option", { key: opt.value, value: opt.value }, opt.label))
189
+ ));
190
+ case "multiselect": {
191
+ const current = Array.isArray(value) ? value : [];
192
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__header" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required", style: { marginLeft: 3 } }, "*")), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__body" }, (field.options ?? []).map((opt) => /* @__PURE__ */ import_react.default.createElement("label", { key: opt.value, className: "bdf-multiselect-opt" }, /* @__PURE__ */ import_react.default.createElement(
193
+ "input",
194
+ {
195
+ type: "checkbox",
196
+ checked: current.includes(opt.value),
197
+ onChange: (e) => {
198
+ const next = e.target.checked ? [...current, opt.value] : current.filter((v) => v !== opt.value);
199
+ onChange(next);
200
+ }
201
+ }
202
+ ), opt.label)))));
203
+ }
204
+ case "image":
205
+ case "file":
206
+ return /* @__PURE__ */ import_react.default.createElement(
207
+ MediaPicker,
208
+ {
209
+ label,
210
+ required: field.required,
211
+ value,
212
+ onChange
213
+ }
214
+ );
215
+ case "relationship":
216
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), field.collection && /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", field.collection, ")")), /* @__PURE__ */ import_react.default.createElement(
217
+ "input",
218
+ {
219
+ className: "bdf-input",
220
+ type: "text",
221
+ value: value ?? "",
222
+ onChange: (e) => onChange(e.target.value),
223
+ placeholder: "Enter document ID"
224
+ }
225
+ ));
226
+ case "json":
227
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("label", { className: "bdf-label" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required" }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(JSON)")), /* @__PURE__ */ import_react.default.createElement(
228
+ "textarea",
229
+ {
230
+ className: "bdf-input bdf-textarea bdf-mono",
231
+ value: value !== void 0 ? JSON.stringify(value, null, 2) : "",
232
+ rows: 4,
233
+ onChange: (e) => {
234
+ try {
235
+ onChange(JSON.parse(e.target.value));
236
+ } catch {
237
+ }
238
+ }
239
+ }
240
+ ));
241
+ case "array": {
242
+ const rows = Array.isArray(value) ? value : [];
243
+ const subFields = field.fields ?? [];
244
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__header" }, label, field.required && /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-required", style: { marginLeft: 3 } }, "*"), /* @__PURE__ */ import_react.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", rows.length, " ", rows.length === 1 ? "row" : "rows", ")")), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__body" }, rows.map((row, i) => /* @__PURE__ */ import_react.default.createElement("div", { key: i, className: "bdf-array-row" }, /* @__PURE__ */ import_react.default.createElement(
245
+ SchemaForm,
246
+ {
247
+ schema: subFields,
248
+ value: row,
249
+ onChange: (updated) => {
250
+ const next = [...rows];
251
+ next[i] = updated;
252
+ onChange(next);
253
+ }
254
+ }
255
+ ), /* @__PURE__ */ import_react.default.createElement(
256
+ "button",
257
+ {
258
+ type: "button",
259
+ className: "bdf-remove-btn",
260
+ onClick: () => onChange(rows.filter((_, j) => j !== i))
261
+ },
262
+ "\xE2\u0153\u2022 Remove row"
263
+ ))), /* @__PURE__ */ import_react.default.createElement(
264
+ "button",
265
+ {
266
+ type: "button",
267
+ className: "bdf-add-btn",
268
+ onClick: () => onChange([...rows, {}])
269
+ },
270
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "bdf-add-btn__icon" }, "+"),
271
+ "Add row"
272
+ ))));
273
+ }
274
+ case "group": {
275
+ const groupVal = value ?? {};
276
+ const subFields = field.fields ?? [];
277
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-field" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__header" }, label), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-fieldset__body" }, /* @__PURE__ */ import_react.default.createElement(SchemaForm, { schema: subFields, value: groupVal, onChange }))));
278
+ }
279
+ default:
280
+ return /* @__PURE__ */ import_react.default.createElement("div", { style: { fontSize: 12, color: "var(--theme-elevation-400)", fontFamily: "var(--font-body)", padding: "4px 0" } }, "Unsupported field type: ", /* @__PURE__ */ import_react.default.createElement("strong", null, field.type), " (", label, ")");
281
+ }
282
+ }
283
+ function BlockDataField({ path }) {
284
+ const { value, setValue } = (0, import_ui.useField)({ path });
285
+ const rowPrefix = path.replace(/\.data$/, "");
286
+ const blockVersionPath = `${rowPrefix}.blockVersion`;
287
+ const blockVersionValue = (0, import_ui.useFormFields)(([fields]) => fields[blockVersionPath]?.value);
288
+ const versionId = blockVersionValue && typeof blockVersionValue === "object" ? blockVersionValue.id ?? null : typeof blockVersionValue === "string" || typeof blockVersionValue === "number" ? blockVersionValue : null;
289
+ const [schema, setSchema] = (0, import_react.useState)(null);
290
+ const [loading, setLoading] = (0, import_react.useState)(false);
291
+ const [error, setError] = (0, import_react.useState)(null);
292
+ (0, import_react.useEffect)(() => {
293
+ if (!versionId) {
294
+ setSchema(null);
295
+ setError(null);
296
+ return;
297
+ }
298
+ setLoading(true);
299
+ setError(null);
300
+ fetch(`/api/block-definition-versions/${versionId}?depth=0`, { credentials: "same-origin" }).then((r) => {
301
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
302
+ return r.json();
303
+ }).then((doc) => {
304
+ if (!doc || typeof doc !== "object") throw new Error("Empty response from server");
305
+ const raw = doc.schema;
306
+ const fields = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.fields) ? raw.fields : [];
307
+ setSchema(fields.length > 0 ? fields : null);
308
+ setLoading(false);
309
+ }).catch((e) => {
310
+ setError(String(e));
311
+ setLoading(false);
312
+ });
313
+ }, [versionId]);
314
+ if (!versionId) {
315
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-empty" }, "Select a ", /* @__PURE__ */ import_react.default.createElement("strong", null, "Block Version"), " above to configure block data fields.");
316
+ }
317
+ if (loading) {
318
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-loading" }, "Loading schema\xE2\u20AC\xA6");
319
+ }
320
+ if (error) {
321
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-error" }, "Failed to load schema: ", error);
322
+ }
323
+ if (!schema || schema.length === 0) {
324
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-empty" }, "This block version has no fields defined in its schema.");
325
+ }
326
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-wrap" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-heading" }, "Block Data"), /* @__PURE__ */ import_react.default.createElement("div", { className: "bdf-body" }, /* @__PURE__ */ import_react.default.createElement(
327
+ SchemaForm,
328
+ {
329
+ schema,
330
+ value: value ?? {},
331
+ onChange: setValue
332
+ }
333
+ )));
334
+ }
335
+
336
+ // src/components/SchemaBuilderField/index.tsx
337
+ var import_react5 = __toESM(require("react"), 1);
338
+ var import_ui2 = require("@payloadcms/ui");
339
+
340
+ // src/components/SchemaBuilderField/FieldRow.tsx
341
+ var import_react4 = __toESM(require("react"), 1);
342
+
343
+ // src/components/SchemaBuilderField/OptionsEditor.tsx
344
+ var import_react2 = __toESM(require("react"), 1);
345
+ function OptionsEditor({ options, onChange, readOnly }) {
346
+ function addOption() {
347
+ onChange([...options, { label: "", value: "" }]);
348
+ }
349
+ function updateOption(index, key, val) {
350
+ onChange(options.map((o, i) => i === index ? { ...o, [key]: val } : o));
351
+ }
352
+ function removeOption(index) {
353
+ onChange(options.filter((_, i) => i !== index));
354
+ }
355
+ return /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, /* @__PURE__ */ import_react2.default.createElement(
356
+ "span",
357
+ {
358
+ style: {
359
+ display: "flex",
360
+ alignItems: "center",
361
+ color: "var(--theme-elevation-800)",
362
+ fontFamily: "var(--font-body)",
363
+ fontSize: 13,
364
+ lineHeight: "20px",
365
+ fontWeight: 400,
366
+ paddingBottom: 5
367
+ }
368
+ },
369
+ "Options",
370
+ options.length > 0 && /* @__PURE__ */ import_react2.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", options.length, ")")
371
+ ), options.length === 0 && /* @__PURE__ */ import_react2.default.createElement(
372
+ "p",
373
+ {
374
+ style: {
375
+ fontSize: 13,
376
+ color: "var(--theme-elevation-400)",
377
+ fontFamily: "var(--font-body)",
378
+ lineHeight: "20px",
379
+ margin: 0
380
+ }
381
+ },
382
+ "No options yet."
383
+ ), options.map((opt, i) => /* @__PURE__ */ import_react2.default.createElement("div", { key: i, className: "sbf-options-row" }, /* @__PURE__ */ import_react2.default.createElement(
384
+ "input",
385
+ {
386
+ className: "sbf-input",
387
+ type: "text",
388
+ value: opt.label,
389
+ placeholder: "Label",
390
+ disabled: readOnly,
391
+ onChange: (e) => updateOption(i, "label", e.target.value)
392
+ }
393
+ ), /* @__PURE__ */ import_react2.default.createElement(
394
+ "input",
395
+ {
396
+ className: "sbf-input",
397
+ type: "text",
398
+ value: opt.value,
399
+ placeholder: "Value",
400
+ disabled: readOnly,
401
+ onChange: (e) => updateOption(i, "value", e.target.value)
402
+ }
403
+ ), !readOnly && /* @__PURE__ */ import_react2.default.createElement(
404
+ "button",
405
+ {
406
+ type: "button",
407
+ className: "sbf-icon-btn sbf-icon-btn--danger",
408
+ title: "Remove option",
409
+ onClick: () => removeOption(i)
410
+ },
411
+ "\xE2\u0153\u2022"
412
+ ))), !readOnly && /* @__PURE__ */ import_react2.default.createElement(
413
+ "button",
414
+ {
415
+ type: "button",
416
+ className: "sbf-add-btn sbf-add-btn--small",
417
+ onClick: addOption
418
+ },
419
+ /* @__PURE__ */ import_react2.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
420
+ "Add Option"
421
+ ));
422
+ }
423
+
424
+ // src/components/SchemaBuilderField/NestedFieldsEditor.tsx
425
+ var import_react3 = __toESM(require("react"), 1);
426
+ function NestedFieldsEditor({ fields, onChange, readOnly, depth = 1 }) {
427
+ const addField = (0, import_react3.useCallback)(() => {
428
+ onChange([...fields, { name: "", type: "text", label: "", required: false }]);
429
+ }, [fields, onChange]);
430
+ const updateField = (0, import_react3.useCallback)(
431
+ (index, updated) => {
432
+ onChange(fields.map((f, i) => i === index ? updated : f));
433
+ },
434
+ [fields, onChange]
435
+ );
436
+ const removeField = (0, import_react3.useCallback)(
437
+ (index) => {
438
+ onChange(fields.filter((_, i) => i !== index));
439
+ },
440
+ [fields, onChange]
441
+ );
442
+ const moveField = (0, import_react3.useCallback)(
443
+ (index, dir) => {
444
+ const next = [...fields];
445
+ const target = index + dir;
446
+ if (target < 0 || target >= next.length) return;
447
+ [next[index], next[target]] = [next[target], next[index]];
448
+ onChange(next);
449
+ },
450
+ [fields, onChange]
451
+ );
452
+ return /* @__PURE__ */ import_react3.default.createElement("div", { className: "sbf-nested" }, /* @__PURE__ */ import_react3.default.createElement(
453
+ "span",
454
+ {
455
+ style: {
456
+ display: "flex",
457
+ alignItems: "center",
458
+ color: "var(--theme-elevation-800)",
459
+ fontFamily: "var(--font-body)",
460
+ fontSize: 13,
461
+ lineHeight: "20px",
462
+ fontWeight: 400,
463
+ paddingBottom: fields.length > 0 ? 6 : 0
464
+ }
465
+ },
466
+ "Nested fields",
467
+ fields.length > 0 && /* @__PURE__ */ import_react3.default.createElement("span", { style: { marginLeft: 6, fontSize: 11, color: "var(--theme-elevation-400)", fontWeight: 400 } }, "(", fields.length, ")")
468
+ ), fields.length === 0 && /* @__PURE__ */ import_react3.default.createElement(
469
+ "p",
470
+ {
471
+ style: {
472
+ fontSize: 13,
473
+ color: "var(--theme-elevation-400)",
474
+ fontFamily: "var(--font-body)",
475
+ lineHeight: "20px",
476
+ margin: "4px 0 8px"
477
+ }
478
+ },
479
+ "No nested fields yet."
480
+ ), fields.length > 0 && /* @__PURE__ */ import_react3.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react3.default.createElement(
481
+ FieldRow,
482
+ {
483
+ key: i,
484
+ index: i,
485
+ total: fields.length,
486
+ field,
487
+ onChange: (updated) => updateField(i, updated),
488
+ onRemove: () => removeField(i),
489
+ onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
490
+ onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
491
+ readOnly,
492
+ depth
493
+ }
494
+ ))), !readOnly && /* @__PURE__ */ import_react3.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react3.default.createElement(
495
+ "button",
496
+ {
497
+ type: "button",
498
+ className: "sbf-add-btn sbf-add-btn--small",
499
+ onClick: addField
500
+ },
501
+ /* @__PURE__ */ import_react3.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
502
+ "Add Nested Field"
503
+ )));
504
+ }
505
+
506
+ // src/components/SchemaBuilderField/FieldRow.tsx
507
+ var ALL_FIELD_TYPES = [
508
+ "text",
509
+ "textarea",
510
+ "richtext",
511
+ "number",
512
+ "checkbox",
513
+ "select",
514
+ "multiselect",
515
+ "date",
516
+ "image",
517
+ "file",
518
+ "url",
519
+ "email",
520
+ "color",
521
+ "array",
522
+ "group",
523
+ "relationship",
524
+ "json",
525
+ "blocks"
526
+ ];
527
+ var TYPE_BADGE_COLORS = {
528
+ text: "var(--theme-elevation-150)",
529
+ textarea: "var(--theme-elevation-150)",
530
+ richtext: "var(--theme-elevation-150)",
531
+ number: "var(--theme-elevation-150)",
532
+ email: "var(--theme-elevation-150)",
533
+ url: "var(--theme-elevation-150)",
534
+ color: "var(--theme-elevation-150)",
535
+ date: "var(--theme-elevation-150)",
536
+ checkbox: "var(--theme-elevation-150)",
537
+ select: "var(--theme-elevation-150)",
538
+ multiselect: "var(--theme-elevation-150)",
539
+ image: "var(--theme-elevation-150)",
540
+ file: "var(--theme-elevation-150)",
541
+ array: "var(--theme-elevation-150)",
542
+ group: "var(--theme-elevation-150)",
543
+ relationship: "var(--theme-elevation-150)",
544
+ json: "var(--theme-elevation-150)",
545
+ blocks: "var(--theme-elevation-150)"
546
+ };
547
+ function FieldLabel({ children, required }) {
548
+ return /* @__PURE__ */ import_react4.default.createElement("label", { className: `sbf-label${required ? " sbf-label--required" : ""}` }, children);
549
+ }
550
+ function FieldWrap({ children }) {
551
+ return /* @__PURE__ */ import_react4.default.createElement("div", { style: { display: "flex", flexDirection: "column" } }, children);
552
+ }
553
+ function FieldRow({
554
+ index,
555
+ field,
556
+ onChange,
557
+ onRemove,
558
+ onMoveUp,
559
+ onMoveDown,
560
+ readOnly,
561
+ depth = 0
562
+ }) {
563
+ const [expanded, setExpanded] = (0, import_react4.useState)(false);
564
+ const [showAdmin, setShowAdmin] = (0, import_react4.useState)(false);
565
+ const [showConditions, setShowConditions] = (0, import_react4.useState)(false);
566
+ const maxDepth = 3;
567
+ const canNest = depth < maxDepth;
568
+ const needsOptions = field.type === "select" || field.type === "multiselect";
569
+ const needsNested = field.type === "array" || field.type === "group";
570
+ function set(key, val) {
571
+ onChange({ ...field, [key]: val });
572
+ }
573
+ const badgeBg = TYPE_BADGE_COLORS[field.type] ?? "var(--theme-elevation-150)";
574
+ return /* @__PURE__ */ import_react4.default.createElement("div", { className: `sbf-row${expanded ? "" : " sbf-row--collapsed"}` }, /* @__PURE__ */ import_react4.default.createElement(
575
+ "div",
576
+ {
577
+ className: "sbf-row__header",
578
+ role: "button",
579
+ "aria-expanded": expanded,
580
+ onClick: () => setExpanded((p) => !p)
581
+ },
582
+ /* @__PURE__ */ import_react4.default.createElement(
583
+ "span",
584
+ {
585
+ style: {
586
+ fontSize: 10,
587
+ color: "var(--theme-elevation-400)",
588
+ flexShrink: 0,
589
+ width: 16,
590
+ display: "flex",
591
+ alignItems: "center",
592
+ justifyContent: "center",
593
+ transition: "transform 0.15s",
594
+ transform: expanded ? "rotate(90deg)" : "rotate(0deg)"
595
+ }
596
+ },
597
+ "\xE2\u2013\xB6"
598
+ ),
599
+ /* @__PURE__ */ import_react4.default.createElement("span", { className: `sbf-row__label${!field.name ? " sbf-row__label--empty" : ""}` }, field.name || "unnamed field"),
600
+ field.required && /* @__PURE__ */ import_react4.default.createElement("span", { style: { color: "var(--theme-error-500, #ef4444)", fontSize: 16, lineHeight: 1, flexShrink: 0 } }, "*"),
601
+ /* @__PURE__ */ import_react4.default.createElement(
602
+ "span",
603
+ {
604
+ className: "sbf-type-badge",
605
+ style: { background: badgeBg }
606
+ },
607
+ field.type
608
+ ),
609
+ !readOnly && /* @__PURE__ */ import_react4.default.createElement(
610
+ "div",
611
+ {
612
+ style: { display: "flex", gap: 2, flexShrink: 0, alignItems: "center" },
613
+ onClick: (e) => e.stopPropagation()
614
+ },
615
+ onMoveUp && /* @__PURE__ */ import_react4.default.createElement("button", { type: "button", className: "sbf-icon-btn", onClick: onMoveUp, title: "Move up" }, "\xE2\u2020\u2018"),
616
+ onMoveDown && /* @__PURE__ */ import_react4.default.createElement("button", { type: "button", className: "sbf-icon-btn", onClick: onMoveDown, title: "Move down" }, "\xE2\u2020\u201C"),
617
+ /* @__PURE__ */ import_react4.default.createElement(
618
+ "button",
619
+ {
620
+ type: "button",
621
+ className: "sbf-icon-btn sbf-icon-btn--danger",
622
+ onClick: onRemove,
623
+ title: "Remove field"
624
+ },
625
+ "\xE2\u0153\u2022"
626
+ )
627
+ )
628
+ ), expanded && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-row__content" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, { required: true }, "Field Name"), /* @__PURE__ */ import_react4.default.createElement(
629
+ "input",
630
+ {
631
+ className: "sbf-input",
632
+ type: "text",
633
+ value: field.name,
634
+ placeholder: "fieldName",
635
+ disabled: readOnly,
636
+ onChange: (e) => set("name", e.target.value)
637
+ }
638
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, { required: true }, "Type"), /* @__PURE__ */ import_react4.default.createElement(
639
+ "select",
640
+ {
641
+ className: "sbf-input sbf-select",
642
+ value: field.type,
643
+ disabled: readOnly,
644
+ onChange: (e) => set("type", e.target.value)
645
+ },
646
+ ALL_FIELD_TYPES.map((t) => /* @__PURE__ */ import_react4.default.createElement("option", { key: t, value: t }, t))
647
+ ))), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Label"), /* @__PURE__ */ import_react4.default.createElement(
648
+ "input",
649
+ {
650
+ className: "sbf-input",
651
+ type: "text",
652
+ value: field.label ?? "",
653
+ placeholder: "Human-readable label",
654
+ disabled: readOnly,
655
+ onChange: (e) => set("label", e.target.value)
656
+ }
657
+ )), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
658
+ "input",
659
+ {
660
+ type: "checkbox",
661
+ checked: field.required ?? false,
662
+ disabled: readOnly,
663
+ onChange: (e) => set("required", e.target.checked)
664
+ }
665
+ ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Required"))), field.type === "number" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Min"), /* @__PURE__ */ import_react4.default.createElement(
666
+ "input",
667
+ {
668
+ className: "sbf-input",
669
+ type: "number",
670
+ value: field.min ?? "",
671
+ disabled: readOnly,
672
+ onChange: (e) => set("min", e.target.value === "" ? void 0 : e.target.valueAsNumber)
673
+ }
674
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Max"), /* @__PURE__ */ import_react4.default.createElement(
675
+ "input",
676
+ {
677
+ className: "sbf-input",
678
+ type: "number",
679
+ value: field.max ?? "",
680
+ disabled: readOnly,
681
+ onChange: (e) => set("max", e.target.value === "" ? void 0 : e.target.valueAsNumber)
682
+ }
683
+ ))), (field.type === "text" || field.type === "textarea") && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Min Length"), /* @__PURE__ */ import_react4.default.createElement(
684
+ "input",
685
+ {
686
+ className: "sbf-input",
687
+ type: "number",
688
+ value: field.minLength ?? "",
689
+ disabled: readOnly,
690
+ onChange: (e) => set("minLength", e.target.value === "" ? void 0 : e.target.valueAsNumber)
691
+ }
692
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Max Length"), /* @__PURE__ */ import_react4.default.createElement(
693
+ "input",
694
+ {
695
+ className: "sbf-input",
696
+ type: "number",
697
+ value: field.maxLength ?? "",
698
+ disabled: readOnly,
699
+ onChange: (e) => set("maxLength", e.target.value === "" ? void 0 : e.target.valueAsNumber)
700
+ }
701
+ ))), field.type === "array" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-grid-2" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Min Rows"), /* @__PURE__ */ import_react4.default.createElement(
702
+ "input",
703
+ {
704
+ className: "sbf-input",
705
+ type: "number",
706
+ value: field.minRows ?? "",
707
+ disabled: readOnly,
708
+ onChange: (e) => set("minRows", e.target.value === "" ? void 0 : e.target.valueAsNumber)
709
+ }
710
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Max Rows"), /* @__PURE__ */ import_react4.default.createElement(
711
+ "input",
712
+ {
713
+ className: "sbf-input",
714
+ type: "number",
715
+ value: field.maxRows ?? "",
716
+ disabled: readOnly,
717
+ onChange: (e) => set("maxRows", e.target.value === "" ? void 0 : e.target.valueAsNumber)
718
+ }
719
+ ))), field.type === "relationship" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, { required: true }, "Collection slug"), /* @__PURE__ */ import_react4.default.createElement(
720
+ "input",
721
+ {
722
+ className: "sbf-input",
723
+ type: "text",
724
+ value: field.collection ?? "",
725
+ placeholder: "e.g. pages",
726
+ disabled: readOnly,
727
+ onChange: (e) => set("collection", e.target.value)
728
+ }
729
+ )), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
730
+ "input",
731
+ {
732
+ type: "checkbox",
733
+ checked: field.hasMany ?? false,
734
+ disabled: readOnly,
735
+ onChange: (e) => set("hasMany", e.target.checked)
736
+ }
737
+ ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Has Many")))), needsOptions && /* @__PURE__ */ import_react4.default.createElement(
738
+ OptionsEditor,
739
+ {
740
+ options: field.options ?? [],
741
+ onChange: (opts) => set("options", opts),
742
+ readOnly
743
+ }
744
+ ), needsNested && canNest && /* @__PURE__ */ import_react4.default.createElement(
745
+ NestedFieldsEditor,
746
+ {
747
+ fields: field.fields ?? [],
748
+ onChange: (nested) => set("fields", nested),
749
+ readOnly,
750
+ depth: depth + 1
751
+ }
752
+ ), needsNested && !canNest && /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-description" }, "Maximum nesting depth (", depth, ") reached."), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section" }, /* @__PURE__ */ import_react4.default.createElement(
753
+ "button",
754
+ {
755
+ type: "button",
756
+ className: `sbf-section__toggle${showAdmin ? " sbf-section__toggle--open" : ""}`,
757
+ onClick: () => setShowAdmin((p) => !p)
758
+ },
759
+ "Admin & UI settings",
760
+ /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-section__toggle-icon" }, "\xE2\u2013\xB6")
761
+ ), showAdmin && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section__body" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Description"), /* @__PURE__ */ import_react4.default.createElement(
762
+ "input",
763
+ {
764
+ className: "sbf-input",
765
+ type: "text",
766
+ value: field.admin?.description ?? "",
767
+ placeholder: "Helper text shown below the field",
768
+ disabled: readOnly,
769
+ onChange: (e) => set("admin", { ...field.admin, description: e.target.value })
770
+ }
771
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Placeholder"), /* @__PURE__ */ import_react4.default.createElement(
772
+ "input",
773
+ {
774
+ className: "sbf-input",
775
+ type: "text",
776
+ value: field.admin?.placeholder ?? "",
777
+ placeholder: "Input placeholder text",
778
+ disabled: readOnly,
779
+ onChange: (e) => set("admin", { ...field.admin, placeholder: e.target.value })
780
+ }
781
+ )), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-flags-row" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
782
+ "input",
783
+ {
784
+ type: "checkbox",
785
+ checked: field.admin?.readOnly ?? false,
786
+ disabled: readOnly,
787
+ onChange: (e) => set("admin", { ...field.admin, readOnly: e.target.checked })
788
+ }
789
+ ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Read Only")), /* @__PURE__ */ import_react4.default.createElement("label", { className: "sbf-checkbox-wrap" }, /* @__PURE__ */ import_react4.default.createElement(
790
+ "input",
791
+ {
792
+ type: "checkbox",
793
+ checked: field.admin?.hidden ?? false,
794
+ disabled: readOnly,
795
+ onChange: (e) => set("admin", { ...field.admin, hidden: e.target.checked })
796
+ }
797
+ ), /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-checkbox-label" }, "Hidden"))))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section" }, /* @__PURE__ */ import_react4.default.createElement(
798
+ "button",
799
+ {
800
+ type: "button",
801
+ className: `sbf-section__toggle${showConditions ? " sbf-section__toggle--open" : ""}`,
802
+ onClick: () => setShowConditions((p) => !p)
803
+ },
804
+ "Conditional logic",
805
+ (field.conditions?.length ?? 0) > 0 && /* @__PURE__ */ import_react4.default.createElement(
806
+ "span",
807
+ {
808
+ style: {
809
+ marginLeft: "auto",
810
+ fontSize: 11,
811
+ color: "var(--theme-elevation-400)",
812
+ fontWeight: 400
813
+ }
814
+ },
815
+ field.conditions.length,
816
+ " rule",
817
+ field.conditions.length !== 1 ? "s" : ""
818
+ ),
819
+ /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-section__toggle-icon" }, "\xE2\u2013\xB6")
820
+ ), showConditions && /* @__PURE__ */ import_react4.default.createElement("div", { className: "sbf-section__body" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Condition Mode"), /* @__PURE__ */ import_react4.default.createElement(
821
+ "select",
822
+ {
823
+ className: "sbf-input sbf-select",
824
+ value: field.conditionMode ?? "AND",
825
+ disabled: readOnly,
826
+ onChange: (e) => set("conditionMode", e.target.value),
827
+ style: { width: "auto", minWidth: 240 }
828
+ },
829
+ /* @__PURE__ */ import_react4.default.createElement("option", { value: "AND" }, "AND \xE2\u20AC\u201D all conditions must match"),
830
+ /* @__PURE__ */ import_react4.default.createElement("option", { value: "OR" }, "OR \xE2\u20AC\u201D any condition must match")
831
+ )), (field.conditions ?? []).map((cond, ci) => /* @__PURE__ */ import_react4.default.createElement("div", { key: ci, className: "sbf-conditions-grid" }, /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Field"), /* @__PURE__ */ import_react4.default.createElement(
832
+ "input",
833
+ {
834
+ className: "sbf-input",
835
+ type: "text",
836
+ value: cond.field,
837
+ placeholder: "fieldName",
838
+ disabled: readOnly,
839
+ onChange: (e) => {
840
+ const next = [...field.conditions ?? []];
841
+ next[ci] = { ...next[ci], field: e.target.value };
842
+ set("conditions", next);
843
+ }
844
+ }
845
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Operator"), /* @__PURE__ */ import_react4.default.createElement(
846
+ "select",
847
+ {
848
+ className: "sbf-input sbf-select",
849
+ value: cond.operator,
850
+ disabled: readOnly,
851
+ onChange: (e) => {
852
+ const next = [...field.conditions ?? []];
853
+ next[ci] = { ...next[ci], operator: e.target.value };
854
+ set("conditions", next);
855
+ }
856
+ },
857
+ ["equals", "not_equals", "contains", "not_contains", "greater_than", "less_than", "in", "not_in", "exists", "empty"].map((op) => /* @__PURE__ */ import_react4.default.createElement("option", { key: op, value: op }, op.replace(/_/g, " ")))
858
+ )), /* @__PURE__ */ import_react4.default.createElement(FieldWrap, null, /* @__PURE__ */ import_react4.default.createElement(FieldLabel, null, "Value"), /* @__PURE__ */ import_react4.default.createElement(
859
+ "input",
860
+ {
861
+ className: "sbf-input",
862
+ type: "text",
863
+ value: String(cond.value ?? ""),
864
+ disabled: readOnly,
865
+ onChange: (e) => {
866
+ const next = [...field.conditions ?? []];
867
+ next[ci] = { ...next[ci], value: e.target.value };
868
+ set("conditions", next);
869
+ }
870
+ }
871
+ )), !readOnly && /* @__PURE__ */ import_react4.default.createElement(
872
+ "button",
873
+ {
874
+ type: "button",
875
+ className: "sbf-icon-btn sbf-icon-btn--danger",
876
+ title: "Remove condition",
877
+ style: { marginTop: 25 },
878
+ onClick: () => {
879
+ set("conditions", (field.conditions ?? []).filter((_, k) => k !== ci));
880
+ }
881
+ },
882
+ "\xE2\u0153\u2022"
883
+ ))), !readOnly && /* @__PURE__ */ import_react4.default.createElement(
884
+ "button",
885
+ {
886
+ type: "button",
887
+ className: "sbf-add-btn sbf-add-btn--small",
888
+ onClick: () => {
889
+ set("conditions", [
890
+ ...field.conditions ?? [],
891
+ { field: "", operator: "equals", value: "" }
892
+ ]);
893
+ }
894
+ },
895
+ /* @__PURE__ */ import_react4.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"),
896
+ "Add Condition"
897
+ )))));
898
+ }
899
+
900
+ // src/components/SchemaBuilderField/index.tsx
901
+ function parseSchema(raw) {
902
+ try {
903
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
904
+ if (!parsed) return [];
905
+ if (Array.isArray(parsed)) return parsed;
906
+ if (parsed?.fields && Array.isArray(parsed.fields)) return parsed.fields;
907
+ } catch {
908
+ }
909
+ return [];
910
+ }
911
+ function SchemaBuilderField({ path, readOnly }) {
912
+ const { value, setValue } = (0, import_ui2.useField)({ path });
913
+ const setValueRef = (0, import_react5.useRef)(setValue);
914
+ (0, import_react5.useEffect)(() => {
915
+ setValueRef.current = setValue;
916
+ });
917
+ const [fields, setFields] = (0, import_react5.useState)(() => parseSchema(value));
918
+ const hasExistingValue = value !== void 0 && value !== null;
919
+ const [hydrated, setHydrated] = (0, import_react5.useState)(!hasExistingValue);
920
+ (0, import_react5.useEffect)(() => {
921
+ if (hydrated) return;
922
+ if (value !== void 0 && value !== null) {
923
+ setFields(parseSchema(value));
924
+ setHydrated(true);
925
+ }
926
+ }, [value, hydrated]);
927
+ (0, import_react5.useEffect)(() => {
928
+ if (!hydrated) return;
929
+ setValueRef.current({ fields });
930
+ }, [fields, hydrated]);
931
+ const addField = (0, import_react5.useCallback)(() => {
932
+ setFields((prev) => [
933
+ ...prev,
934
+ { name: "", type: "text", label: "", required: false }
935
+ ]);
936
+ }, []);
937
+ const updateField = (0, import_react5.useCallback)((index, updated) => {
938
+ setFields((prev) => prev.map((f, i) => i === index ? updated : f));
939
+ }, []);
940
+ const removeField = (0, import_react5.useCallback)((index) => {
941
+ setFields((prev) => prev.filter((_, i) => i !== index));
942
+ }, []);
943
+ const moveField = (0, import_react5.useCallback)((index, dir) => {
944
+ setFields((prev) => {
945
+ const next = [...prev];
946
+ const target = index + dir;
947
+ if (target < 0 || target >= next.length) return prev;
948
+ [next[index], next[target]] = [next[target], next[index]];
949
+ return next;
950
+ });
951
+ }, []);
952
+ return /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-section-heading" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-section-label" }, "Fields", fields.length > 0 && /* @__PURE__ */ import_react5.default.createElement(
953
+ "span",
954
+ {
955
+ style: {
956
+ marginLeft: 8,
957
+ fontSize: 11,
958
+ color: "var(--theme-elevation-400)",
959
+ fontWeight: 400
960
+ }
961
+ },
962
+ "(",
963
+ fields.length,
964
+ ")"
965
+ ))), fields.length === 0 ? /* @__PURE__ */ import_react5.default.createElement("div", { className: "sbf-empty" }, "No fields defined. Click \u201CAdd Field\u201D below to add the first field to this block schema.") : /* @__PURE__ */ import_react5.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "calc(var(--base, 16px) / 2)" } }, fields.map((field, i) => /* @__PURE__ */ import_react5.default.createElement(
966
+ FieldRow,
967
+ {
968
+ key: i,
969
+ index: i,
970
+ total: fields.length,
971
+ field,
972
+ onChange: (updated) => updateField(i, updated),
973
+ onRemove: () => removeField(i),
974
+ onMoveUp: i > 0 ? () => moveField(i, -1) : void 0,
975
+ onMoveDown: i < fields.length - 1 ? () => moveField(i, 1) : void 0,
976
+ readOnly
977
+ }
978
+ ))), !readOnly && /* @__PURE__ */ import_react5.default.createElement("div", { style: { marginTop: "calc(var(--base, 16px) / 2)", marginBottom: "calc(var(--base, 16px) / 2)" } }, /* @__PURE__ */ import_react5.default.createElement("button", { type: "button", className: "sbf-add-btn", onClick: addField }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "sbf-add-btn__icon" }, "+"), "Add Field")));
979
+ }
980
+
981
+ // src/components/EditInBuilderButton/index.tsx
982
+ var import_react6 = __toESM(require("react"), 1);
983
+ var import_ui3 = require("@payloadcms/ui");
984
+ var import_ui4 = require("@payloadcms/ui");
985
+ function EditInBuilderButton() {
986
+ const { id } = (0, import_ui3.useDocumentInfo)();
987
+ const { value: slug } = (0, import_ui4.useField)({ path: "slug" });
988
+ if (!id || !slug) return null;
989
+ return /* @__PURE__ */ import_react6.default.createElement("div", { style: { marginTop: "1rem" } }, /* @__PURE__ */ import_react6.default.createElement(
990
+ "a",
991
+ {
992
+ href: `/block-builder?load=${slug}`,
993
+ target: "_blank",
994
+ rel: "noopener noreferrer",
995
+ style: {
996
+ display: "inline-flex",
997
+ alignItems: "center",
998
+ gap: "6px",
999
+ padding: "8px 16px",
1000
+ borderRadius: "6px",
1001
+ border: "1px solid #16a34a",
1002
+ color: "#16a34a",
1003
+ textDecoration: "none",
1004
+ fontSize: "13px",
1005
+ fontWeight: 500,
1006
+ background: "transparent",
1007
+ cursor: "pointer"
1008
+ }
1009
+ },
1010
+ "Edit in Block Builder"
1011
+ ));
1012
+ }
1013
+
1014
+ // src/block-builder/components/canvas/BuilderShell.tsx
1015
+ var import_react16 = __toESM(require("react"), 1);
1016
+
1017
+ // src/block-builder/store/builder.store.ts
1018
+ var import_zustand = require("zustand");
1019
+ var import_middleware = require("zustand/middleware");
1020
+ var import_immer = require("zustand/middleware/immer");
1021
+ var import_uuid = require("uuid");
1022
+ function createDefaultField(type) {
1023
+ const base = {
1024
+ id: (0, import_uuid.v4)(),
1025
+ type,
1026
+ name: `${type}Field`,
1027
+ label: `${type.charAt(0).toUpperCase()}${type.slice(1)} Field`,
1028
+ required: false
1029
+ };
1030
+ switch (type) {
1031
+ case "select":
1032
+ case "radio":
1033
+ return {
1034
+ ...base,
1035
+ options: [
1036
+ { label: "Option 1", value: "option_1" },
1037
+ { label: "Option 2", value: "option_2" }
1038
+ ]
1039
+ };
1040
+ case "relationship":
1041
+ return { ...base, relationTo: "", hasMany: false };
1042
+ case "array":
1043
+ return { ...base, fields: [] };
1044
+ case "group":
1045
+ return { ...base, fields: [] };
1046
+ default:
1047
+ return base;
1048
+ }
1049
+ }
1050
+ function createDefaultBlock() {
1051
+ return {
1052
+ id: (0, import_uuid.v4)(),
1053
+ slug: "myBlock",
1054
+ interfaceName: "MyBlock",
1055
+ labels: { singular: "My Block", plural: "My Blocks" },
1056
+ fields: []
1057
+ };
1058
+ }
1059
+ var initialState = {
1060
+ blocks: [],
1061
+ activeBlockId: null,
1062
+ activeFieldId: null,
1063
+ isDirty: false
1064
+ };
1065
+ var useBuilderStore = (0, import_zustand.create)()(
1066
+ (0, import_middleware.devtools)(
1067
+ (0, import_middleware.persist)(
1068
+ (0, import_immer.immer)((set) => ({
1069
+ ...initialState,
1070
+ isReadOnly: false,
1071
+ loadedVersionId: null,
1072
+ blockSlug: null,
1073
+ addBlock: () => set((state) => {
1074
+ const block = createDefaultBlock();
1075
+ state.blocks.push(block);
1076
+ state.activeBlockId = block.id;
1077
+ state.activeFieldId = null;
1078
+ state.isDirty = true;
1079
+ }),
1080
+ removeBlock: (blockId) => set((state) => {
1081
+ state.blocks = state.blocks.filter((b) => b.id !== blockId);
1082
+ if (state.activeBlockId === blockId) {
1083
+ state.activeBlockId = state.blocks[0]?.id ?? null;
1084
+ state.activeFieldId = null;
1085
+ }
1086
+ state.isDirty = true;
1087
+ }),
1088
+ updateBlock: (blockId, updates) => set((state) => {
1089
+ const block = state.blocks.find((b) => b.id === blockId);
1090
+ if (block) Object.assign(block, updates);
1091
+ state.isDirty = true;
1092
+ }),
1093
+ setActiveBlock: (blockId) => set((state) => {
1094
+ state.activeBlockId = blockId;
1095
+ state.activeFieldId = null;
1096
+ }),
1097
+ duplicateBlock: (blockId) => set((state) => {
1098
+ const block = state.blocks.find((b) => b.id === blockId);
1099
+ if (!block) return;
1100
+ const clone = JSON.parse(JSON.stringify(block));
1101
+ clone.id = (0, import_uuid.v4)();
1102
+ clone.slug = `${block.slug}Copy`;
1103
+ clone.interfaceName = block.interfaceName ? `${block.interfaceName}Copy` : void 0;
1104
+ clone.fields = clone.fields.map((f) => ({ ...f, id: (0, import_uuid.v4)() }));
1105
+ const idx = state.blocks.findIndex((b) => b.id === blockId);
1106
+ state.blocks.splice(idx + 1, 0, clone);
1107
+ state.activeBlockId = clone.id;
1108
+ state.isDirty = true;
1109
+ }),
1110
+ addField: (blockId, type) => set((state) => {
1111
+ const block = state.blocks.find((b) => b.id === blockId);
1112
+ if (!block) return;
1113
+ const field = createDefaultField(type);
1114
+ block.fields.push(field);
1115
+ state.activeFieldId = field.id;
1116
+ state.isDirty = true;
1117
+ }),
1118
+ removeField: (blockId, fieldId) => set((state) => {
1119
+ const block = state.blocks.find((b) => b.id === blockId);
1120
+ if (!block) return;
1121
+ block.fields = block.fields.filter((f) => f.id !== fieldId);
1122
+ if (state.activeFieldId === fieldId) state.activeFieldId = null;
1123
+ state.isDirty = true;
1124
+ }),
1125
+ updateField: (blockId, fieldId, updates) => set((state) => {
1126
+ const block = state.blocks.find((b) => b.id === blockId);
1127
+ if (!block) return;
1128
+ const field = block.fields.find((f) => f.id === fieldId);
1129
+ if (field) Object.assign(field, updates);
1130
+ state.isDirty = true;
1131
+ }),
1132
+ reorderFields: (blockId, fromIndex, toIndex) => set((state) => {
1133
+ const block = state.blocks.find((b) => b.id === blockId);
1134
+ if (!block) return;
1135
+ const [moved] = block.fields.splice(fromIndex, 1);
1136
+ block.fields.splice(toIndex, 0, moved);
1137
+ state.isDirty = true;
1138
+ }),
1139
+ setActiveField: (fieldId) => set((state) => {
1140
+ state.activeFieldId = fieldId;
1141
+ }),
1142
+ reset: () => set(() => ({ ...initialState, isReadOnly: false, loadedVersionId: null, blockSlug: null })),
1143
+ markClean: () => set((state) => {
1144
+ state.isDirty = false;
1145
+ }),
1146
+ loadBlock: (block) => set((state) => {
1147
+ state.blocks = [block];
1148
+ state.activeBlockId = block.id;
1149
+ state.activeFieldId = null;
1150
+ state.isDirty = false;
1151
+ }),
1152
+ setVersionMeta: (versionId, isReadOnly) => set((state) => {
1153
+ state.loadedVersionId = versionId;
1154
+ state.isReadOnly = isReadOnly;
1155
+ }),
1156
+ setBlockSlug: (slug) => set((state) => {
1157
+ state.blockSlug = slug;
1158
+ })
1159
+ })),
1160
+ {
1161
+ name: "@nextbridgehq/payload-block-builder",
1162
+ partialize: (state) => ({
1163
+ blocks: state.blocks,
1164
+ activeBlockId: state.activeBlockId
1165
+ })
1166
+ }
1167
+ )
1168
+ )
1169
+ );
1170
+
1171
+ // src/block-builder/components/canvas/TopBar.tsx
1172
+ var import_react7 = __toESM(require("react"), 1);
1173
+
1174
+ // src/block-builder/lib/mapToSaveRequest.ts
1175
+ var TYPE_MAP = {
1176
+ richText: "richtext",
1177
+ upload: "image",
1178
+ radio: "select"
1179
+ };
1180
+ var UNSUPPORTED = /* @__PURE__ */ new Set(["code", "point", "ui", "tabs", "collapsible", "row"]);
1181
+ function normalizeSlug(slug) {
1182
+ return slug.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
1183
+ }
1184
+ function mapToSaveRequest(block) {
1185
+ const fields = block.fields.filter((f) => {
1186
+ if (UNSUPPORTED.has(f.type)) {
1187
+ console.warn(`[block-builder] Field type "${f.type}" is not supported in this project \xE2\u20AC\u201D skipping field "${f.name}"`);
1188
+ return false;
1189
+ }
1190
+ return true;
1191
+ }).map(({ id: _id, relationTo, ...f }) => ({
1192
+ ...f,
1193
+ type: TYPE_MAP[f.type] ?? f.type,
1194
+ // normalizer reads `collection`, block-builder stores `relationTo`
1195
+ ...relationTo ? { collection: relationTo } : {}
1196
+ }));
1197
+ return {
1198
+ blockSlug: normalizeSlug(block.slug),
1199
+ name: block.labels?.singular ?? block.slug,
1200
+ schema: { fields },
1201
+ changelog: "Created via block builder"
1202
+ };
1203
+ }
1204
+
1205
+ // src/block-builder/lib/codegen.ts
1206
+ function indent(n) {
1207
+ return " ".repeat(n);
1208
+ }
1209
+ function escStr(s) {
1210
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1211
+ }
1212
+ function fieldToCode(field, depth = 1) {
1213
+ const pad = indent(depth);
1214
+ const innerPad = indent(depth + 1);
1215
+ const lines = [];
1216
+ lines.push(`${pad}name: '${escStr(field.name)}'`);
1217
+ lines.push(`${pad}type: '${field.type}'`);
1218
+ if (field.label) lines.push(`${pad}label: '${escStr(field.label)}'`);
1219
+ if (field.required) lines.push(`${pad}required: true`);
1220
+ if (field.unique) lines.push(`${pad}unique: true`);
1221
+ if (field.localized) lines.push(`${pad}localized: true`);
1222
+ if (field.defaultValue !== void 0) {
1223
+ const val = typeof field.defaultValue === "string" ? `'${escStr(String(field.defaultValue))}'` : field.defaultValue;
1224
+ lines.push(`${pad}defaultValue: ${val}`);
1225
+ }
1226
+ if (field.type === "richText") {
1227
+ lines.push(`${pad}editor: lexicalEditor({})`);
1228
+ }
1229
+ if (field.options && field.options.length > 0) {
1230
+ const opts = field.options.map((o) => `{ label: '${escStr(o.label)}', value: '${escStr(o.value)}' }`).join(`, `);
1231
+ lines.push(`${pad}options: [${opts}]`);
1232
+ }
1233
+ if (field.relationTo) {
1234
+ lines.push(`${pad}relationTo: '${escStr(field.relationTo)}'`);
1235
+ }
1236
+ if (field.hasMany !== void 0) {
1237
+ lines.push(`${pad}hasMany: ${field.hasMany}`);
1238
+ }
1239
+ if (field.minRows !== void 0) lines.push(`${pad}minRows: ${field.minRows}`);
1240
+ if (field.maxRows !== void 0) lines.push(`${pad}maxRows: ${field.maxRows}`);
1241
+ if (field.fields && field.fields.length > 0) {
1242
+ const nested = field.fields.map((f) => `${innerPad}{
1243
+ ${fieldToCode(f, depth + 2)}
1244
+ ${innerPad}}`).join(",\n");
1245
+ lines.push(`${pad}fields: [
1246
+ ${nested}
1247
+ ${innerPad}]`);
1248
+ }
1249
+ const adminParts = [];
1250
+ if (field.admin?.description)
1251
+ adminParts.push(`description: '${escStr(field.admin.description)}'`);
1252
+ if (field.admin?.placeholder)
1253
+ adminParts.push(`placeholder: '${escStr(field.admin.placeholder)}'`);
1254
+ if (field.admin?.readOnly) adminParts.push(`readOnly: true`);
1255
+ if (field.admin?.hidden) adminParts.push(`hidden: true`);
1256
+ if (adminParts.length > 0) {
1257
+ lines.push(`${pad}admin: { ${adminParts.join(", ")} }`);
1258
+ }
1259
+ return lines.join(",\n");
1260
+ }
1261
+ function generateBlockCode(block) {
1262
+ const hasRichText = containsRichText(block.fields);
1263
+ const imports = [`import type { Block } from 'payload'`];
1264
+ if (hasRichText) {
1265
+ imports.push(`import { lexicalEditor } from '@payloadcms/richtext-lexical'`);
1266
+ }
1267
+ const fieldsCode = block.fields.map((f) => ` {
1268
+ ${fieldToCode(f, 2)}
1269
+ }`).join(",\n");
1270
+ const labelsCode = block.labels ? `
1271
+ labels: {
1272
+ singular: '${escStr(block.labels.singular ?? block.slug)}',
1273
+ plural: '${escStr(block.labels.plural ?? block.slug + "s")}',
1274
+ },` : "";
1275
+ const interfaceLine = block.interfaceName ? `
1276
+ interfaceName: '${escStr(block.interfaceName)}',` : "";
1277
+ const exportName = block.interfaceName ?? toCamelCase(block.slug);
1278
+ return [
1279
+ imports.join("\n"),
1280
+ "",
1281
+ `export const ${exportName}: Block = {`,
1282
+ ` slug: '${escStr(block.slug)}',${interfaceLine}${labelsCode}`,
1283
+ ` fields: [`,
1284
+ fieldsCode,
1285
+ ` ],`,
1286
+ `}`,
1287
+ ""
1288
+ ].join("\n");
1289
+ }
1290
+ function containsRichText(fields) {
1291
+ return fields.some(
1292
+ (f) => f.type === "richText" || (f.fields ? containsRichText(f.fields) : false)
1293
+ );
1294
+ }
1295
+ function toCamelCase(slug) {
1296
+ return slug.split(/[-_]/).map(
1297
+ (part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)
1298
+ ).join("");
1299
+ }
1300
+ function generateBlockOutput(block) {
1301
+ return {
1302
+ filename: `${block.slug}.ts`,
1303
+ code: generateBlockCode(block),
1304
+ language: "typescript"
1305
+ };
1306
+ }
1307
+ function generateAllBlocks(blocks) {
1308
+ return blocks.map(generateBlockOutput);
1309
+ }
1310
+ function generateIndexFile(blocks) {
1311
+ const exportName = (b) => b.interfaceName ?? toCamelCase(b.slug);
1312
+ const imports = blocks.map((b) => `import { ${exportName(b)} } from './${b.slug}'`).join("\n");
1313
+ const exportList = blocks.map((b) => ` ${exportName(b)}`).join(",\n");
1314
+ const code = [
1315
+ imports,
1316
+ "",
1317
+ `export const blocks = [`,
1318
+ exportList,
1319
+ `] as const`,
1320
+ ""
1321
+ ].join("\n");
1322
+ return { filename: "index.ts", code, language: "typescript" };
1323
+ }
1324
+
1325
+ // src/block-builder/components/canvas/TopBar.tsx
1326
+ function TopBar({ blockDefs, activeSlug, onBlockSelect, versions, selectedVersionId, onVersionSelect, onRestoreVersion, onAfterPublish }) {
1327
+ const blocks = useBuilderStore((s) => s.blocks);
1328
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1329
+ const activeBlock = blocks.find((b) => b.id === activeBlockId);
1330
+ const markClean = useBuilderStore((s) => s.markClean);
1331
+ const isDirty = useBuilderStore((s) => s.isDirty);
1332
+ const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1333
+ const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
1334
+ const [notification, setNotification] = (0, import_react7.useState)(null);
1335
+ const [versionDropdownOpen, setVersionDropdownOpen] = (0, import_react7.useState)(false);
1336
+ const [blockPickerOpen, setBlockPickerOpen] = (0, import_react7.useState)(false);
1337
+ const dropdownRef = (0, import_react7.useRef)(null);
1338
+ const blockPickerRef = (0, import_react7.useRef)(null);
1339
+ const selectedVersion = versions.find((v) => v.id === selectedVersionId);
1340
+ const currentVersion = versions.find((v) => v.isCurrent);
1341
+ const activeBlockDef = blockDefs.find((b) => b.slug === activeSlug);
1342
+ (0, import_react7.useEffect)(() => {
1343
+ if (notification?.status === "success") {
1344
+ const t = setTimeout(() => setNotification(null), 3e3);
1345
+ return () => clearTimeout(t);
1346
+ }
1347
+ }, [notification]);
1348
+ (0, import_react7.useEffect)(() => {
1349
+ function handleClick(e) {
1350
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
1351
+ setVersionDropdownOpen(false);
1352
+ }
1353
+ if (blockPickerRef.current && !blockPickerRef.current.contains(e.target)) {
1354
+ setBlockPickerOpen(false);
1355
+ }
1356
+ }
1357
+ document.addEventListener("mousedown", handleClick);
1358
+ return () => document.removeEventListener("mousedown", handleClick);
1359
+ }, []);
1360
+ async function handlePublish() {
1361
+ if (!activeBlock || isReadOnly) return;
1362
+ setNotification({ status: "publishing" });
1363
+ try {
1364
+ const req = mapToSaveRequest(activeBlock);
1365
+ const res = await fetch("/api/blocks/save", {
1366
+ method: "POST",
1367
+ headers: { "Content-Type": "application/json" },
1368
+ body: JSON.stringify(req)
1369
+ });
1370
+ const json = await res.json();
1371
+ if (json.success) {
1372
+ markClean();
1373
+ setVersionMeta(null, false);
1374
+ setNotification({
1375
+ status: "success",
1376
+ msg: `v${json.versionNumber ?? "?"} published successfully!`
1377
+ });
1378
+ await onAfterPublish();
1379
+ } else {
1380
+ setNotification({
1381
+ status: "error",
1382
+ title: "Failed to publish block",
1383
+ errors: json.errors ?? ["An unknown error occurred."]
1384
+ });
1385
+ }
1386
+ } catch (err) {
1387
+ setNotification({
1388
+ status: "error",
1389
+ title: "Network error",
1390
+ errors: [err instanceof Error ? err.message : "Could not reach the server."]
1391
+ });
1392
+ }
1393
+ }
1394
+ function handleExport() {
1395
+ if (blocks.length === 0) return;
1396
+ const outputs = [...generateAllBlocks(blocks), generateIndexFile(blocks)];
1397
+ for (const out of outputs) {
1398
+ const blob = new Blob([out.code], { type: "text/plain" });
1399
+ const url = URL.createObjectURL(blob);
1400
+ const a = document.createElement("a");
1401
+ a.href = url;
1402
+ a.download = out.filename;
1403
+ document.body.appendChild(a);
1404
+ a.click();
1405
+ document.body.removeChild(a);
1406
+ URL.revokeObjectURL(url);
1407
+ }
1408
+ }
1409
+ function formatDate(iso) {
1410
+ return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric" });
1411
+ }
1412
+ return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar__brand" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-topbar__title" }, "Block Builder"), isDirty && !isReadOnly && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-topbar__dirty" }, "\xE2\u2014\x8F unsaved")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar__selectors" }, blockDefs.length > 0 && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-block-picker", ref: blockPickerRef }, /* @__PURE__ */ import_react7.default.createElement(
1413
+ "button",
1414
+ {
1415
+ type: "button",
1416
+ className: "bb-block-picker__trigger",
1417
+ onClick: () => setBlockPickerOpen((o) => !o)
1418
+ },
1419
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-block-picker__icon" }, "\xE2\xAC\xA1"),
1420
+ /* @__PURE__ */ import_react7.default.createElement("span", null, activeBlockDef?.name ?? activeSlug ?? "Select a block"),
1421
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__chevron" }, "\xE2\u2013\xBE")
1422
+ ), blockPickerOpen && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-block-picker__dropdown" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown__header" }, "Block Definitions"), blockDefs.map((b) => /* @__PURE__ */ import_react7.default.createElement(
1423
+ "button",
1424
+ {
1425
+ key: b.id,
1426
+ type: "button",
1427
+ className: `bb-version-dropdown__item${b.slug === activeSlug ? " bb-version-dropdown__item--active" : ""}`,
1428
+ onClick: () => {
1429
+ setBlockPickerOpen(false);
1430
+ onBlockSelect(b.slug);
1431
+ }
1432
+ },
1433
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-block-picker__item-name" }, b.name),
1434
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-dropdown__meta" }, b.slug)
1435
+ )))), versions.length > 0 && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-selector", ref: dropdownRef }, /* @__PURE__ */ import_react7.default.createElement(
1436
+ "button",
1437
+ {
1438
+ type: "button",
1439
+ className: `bb-version-selector__trigger${isReadOnly ? " bb-version-selector__trigger--readonly" : ""}`,
1440
+ onClick: () => setVersionDropdownOpen((o) => !o)
1441
+ },
1442
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: `bb-version-selector__dot${selectedVersion?.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1443
+ /* @__PURE__ */ import_react7.default.createElement("span", null, selectedVersion?.label ?? `v${selectedVersion?.versionNumber ?? "?"}`),
1444
+ selectedVersion?.isCurrent && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__badge" }, "current"),
1445
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__chevron" }, "\xE2\u2013\xBE")
1446
+ ), versionDropdownOpen && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-version-dropdown__header" }, "Version History"), versions.map((v) => /* @__PURE__ */ import_react7.default.createElement(
1447
+ "button",
1448
+ {
1449
+ key: v.id,
1450
+ type: "button",
1451
+ className: `bb-version-dropdown__item${v.id === selectedVersionId ? " bb-version-dropdown__item--active" : ""}`,
1452
+ onClick: () => {
1453
+ setVersionDropdownOpen(false);
1454
+ onVersionSelect(v.id);
1455
+ }
1456
+ },
1457
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: `bb-version-selector__dot${v.isCurrent ? " bb-version-selector__dot--current" : " bb-version-selector__dot--old"}` }),
1458
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-dropdown__label" }, v.label, v.isCurrent && /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-selector__badge" }, "current")),
1459
+ /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-version-dropdown__meta" }, v.changelog ? `${v.changelog.slice(0, 32)}${v.changelog.length > 32 ? "\xE2\u20AC\xA6" : ""}` : formatDate(v.createdAt))
1460
+ ))))), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-topbar__actions" }, /* @__PURE__ */ import_react7.default.createElement(
1461
+ "button",
1462
+ {
1463
+ type: "button",
1464
+ onClick: handleExport,
1465
+ disabled: blocks.length === 0,
1466
+ className: "bb-btn bb-btn--secondary",
1467
+ style: { display: "none" }
1468
+ },
1469
+ "Export .ts"
1470
+ ), isReadOnly ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
1471
+ "button",
1472
+ {
1473
+ type: "button",
1474
+ onClick: () => {
1475
+ onVersionSelect(currentVersion?.id ?? "");
1476
+ },
1477
+ className: "bb-btn bb-btn--secondary",
1478
+ disabled: !currentVersion
1479
+ },
1480
+ "Back to current"
1481
+ ), /* @__PURE__ */ import_react7.default.createElement(
1482
+ "button",
1483
+ {
1484
+ type: "button",
1485
+ onClick: async () => {
1486
+ await handlePublish();
1487
+ setVersionMeta(null, false);
1488
+ onRestoreVersion();
1489
+ },
1490
+ disabled: notification?.status === "publishing" || !activeBlock,
1491
+ className: "bb-btn bb-btn--warning"
1492
+ },
1493
+ notification?.status === "publishing" ? "Restoring\xE2\u20AC\xA6" : "Restore as new version"
1494
+ )) : /* @__PURE__ */ import_react7.default.createElement(
1495
+ "button",
1496
+ {
1497
+ type: "button",
1498
+ onClick: handlePublish,
1499
+ disabled: notification?.status === "publishing" || !activeBlock,
1500
+ className: "bb-btn bb-btn--primary"
1501
+ },
1502
+ notification?.status === "publishing" ? "Publishing\xE2\u20AC\xA6" : "Publish to Payload"
1503
+ ))), notification?.status === "publishing" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--publishing" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__spinner" }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, isReadOnly ? "Restoring version\xE2\u20AC\xA6" : "Publishing to Payload\xE2\u20AC\xA6"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Validating schema and saving block definition.")))), notification?.status === "success" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--success" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "\xE2\u0153\u201C"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.msg), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "The block definition and version have been saved.")), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, "\xE2\u0153\u2022"))), notification?.status === "error" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify bb-notify--error" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__box" }, /* @__PURE__ */ import_react7.default.createElement("span", { className: "bb-notify__icon" }, "\xE2\u0153\u2022"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "bb-notify__body" }, /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__title" }, notification.title), /* @__PURE__ */ import_react7.default.createElement("p", { className: "bb-notify__sub" }, "Fix the following errors before publishing:"), /* @__PURE__ */ import_react7.default.createElement("ul", { className: "bb-notify__error-list" }, notification.errors.map((e, i) => /* @__PURE__ */ import_react7.default.createElement("li", { key: i }, e)))), /* @__PURE__ */ import_react7.default.createElement("button", { className: "bb-notify__close", onClick: () => setNotification(null) }, "\xE2\u0153\u2022"))));
1504
+ }
1505
+
1506
+ // src/block-builder/components/canvas/BlockList.tsx
1507
+ var import_react8 = __toESM(require("react"), 1);
1508
+ function BlockList() {
1509
+ const blocks = useBuilderStore((s) => s.blocks);
1510
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1511
+ const addBlock = useBuilderStore((s) => s.addBlock);
1512
+ const removeBlock = useBuilderStore((s) => s.removeBlock);
1513
+ const duplicateBlock = useBuilderStore((s) => s.duplicateBlock);
1514
+ const setActiveBlock = useBuilderStore((s) => s.setActiveBlock);
1515
+ return /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar bb-sidebar--200 bb-sidebar--blocks" }, /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react8.default.createElement("span", { className: "bb-sidebar__title" }, "Blocks"), /* @__PURE__ */ import_react8.default.createElement("button", { type: "button", onClick: addBlock, title: "Add block", className: "bb-sidebar__add" }, "+")), /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-sidebar__body" }, blocks.length === 0 && /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-empty" }, "No blocks yet.", /* @__PURE__ */ import_react8.default.createElement("br", null), "Click + to create one."), blocks.map((block) => {
1516
+ const isActive = block.id === activeBlockId;
1517
+ return /* @__PURE__ */ import_react8.default.createElement(
1518
+ "div",
1519
+ {
1520
+ key: block.id,
1521
+ onClick: () => setActiveBlock(block.id),
1522
+ className: `bb-block-item${isActive ? " bb-block-item--active" : ""}`
1523
+ },
1524
+ /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-item__slug" }, block.slug),
1525
+ /* @__PURE__ */ import_react8.default.createElement("div", { className: "bb-block-item__meta" }, block.fields.length, " field", block.fields.length !== 1 ? "s" : ""),
1526
+ /* @__PURE__ */ import_react8.default.createElement(
1527
+ "div",
1528
+ {
1529
+ className: "bb-block-item__actions",
1530
+ onClick: (e) => e.stopPropagation()
1531
+ },
1532
+ /* @__PURE__ */ import_react8.default.createElement(
1533
+ "button",
1534
+ {
1535
+ type: "button",
1536
+ onClick: () => duplicateBlock(block.id),
1537
+ className: "bb-block-action",
1538
+ title: "Duplicate"
1539
+ },
1540
+ "\xE2\xA7\u2030"
1541
+ ),
1542
+ /* @__PURE__ */ import_react8.default.createElement(
1543
+ "button",
1544
+ {
1545
+ type: "button",
1546
+ onClick: () => removeBlock(block.id),
1547
+ className: "bb-block-action bb-block-action--danger",
1548
+ title: "Delete"
1549
+ },
1550
+ "\xE2\u0153\u2022"
1551
+ )
1552
+ )
1553
+ );
1554
+ })));
1555
+ }
1556
+
1557
+ // src/block-builder/components/canvas/BuilderCanvas.tsx
1558
+ var import_react10 = __toESM(require("react"), 1);
1559
+ var import_core = require("@dnd-kit/core");
1560
+ var import_sortable2 = require("@dnd-kit/sortable");
1561
+ var import_modifiers = require("@dnd-kit/modifiers");
1562
+
1563
+ // src/block-builder/components/canvas/SortableFieldCard.tsx
1564
+ var import_react9 = __toESM(require("react"), 1);
1565
+ var import_sortable = require("@dnd-kit/sortable");
1566
+ var import_utilities = require("@dnd-kit/utilities");
1567
+ var ICON_MAP = {
1568
+ text: "\xF0\u0178\u201C\x9D",
1569
+ textarea: "\xF0\u0178\u201C\u201E",
1570
+ richText: "\xF0\u0178\u201C\xB0",
1571
+ number: "\xF0\u0178\u201D\xA2",
1572
+ checkbox: "\xE2\u02DC\u2018",
1573
+ select: "\xE2\u2013\xBE",
1574
+ radio: "\xE2\u2014\u2030",
1575
+ date: "\xF0\u0178\u201C\u2026",
1576
+ upload: "\xF0\u0178\u201C\u017D",
1577
+ email: "\xE2\u0153\u2030",
1578
+ code: "\xE2\u20AC\xB9\xE2\u20AC\xBA",
1579
+ point: "\xE2\u2014\u017D",
1580
+ relationship: "\xE2\u2021\u0152",
1581
+ array: "\xE2\u2013\xA4",
1582
+ group: "\xE2\u2013\xA3",
1583
+ json: "{}",
1584
+ ui: "\xE2\u2014\u02C6"
1585
+ };
1586
+ function SortableFieldCard({ field, blockId, index }) {
1587
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = (0, import_sortable.useSortable)({
1588
+ id: field.id
1589
+ });
1590
+ const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1591
+ const setActiveField = useBuilderStore((s) => s.setActiveField);
1592
+ const removeField = useBuilderStore((s) => s.removeField);
1593
+ const isActive = activeFieldId === field.id;
1594
+ const wrapStyle = {
1595
+ transform: import_utilities.CSS.Transform.toString(transform),
1596
+ transition
1597
+ };
1598
+ return /* @__PURE__ */ import_react9.default.createElement(
1599
+ "div",
1600
+ {
1601
+ ref: setNodeRef,
1602
+ style: wrapStyle,
1603
+ className: `bb-field-card-wrap${isDragging ? " bb-field-card-wrap--dragging" : ""}`,
1604
+ ...attributes,
1605
+ ...listeners
1606
+ },
1607
+ /* @__PURE__ */ import_react9.default.createElement(
1608
+ "div",
1609
+ {
1610
+ className: `bb-field-card${isActive ? " bb-field-card--active" : ""}`,
1611
+ onClick: () => setActiveField(isActive ? null : field.id)
1612
+ },
1613
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__icon" }, ICON_MAP[field.type] ?? "\xE2\u2014\xBB"),
1614
+ /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__body" }, /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__name" }, field.name || /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__name--empty" }, "unnamed")), /* @__PURE__ */ import_react9.default.createElement("div", { className: "bb-field-card__type" }, field.type, field.required && /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__required" }, "*"))),
1615
+ /* @__PURE__ */ import_react9.default.createElement("span", { className: "bb-field-card__index" }, "#", index + 1),
1616
+ /* @__PURE__ */ import_react9.default.createElement(
1617
+ "button",
1618
+ {
1619
+ type: "button",
1620
+ onPointerDown: (e) => e.stopPropagation(),
1621
+ onClick: (e) => {
1622
+ e.stopPropagation();
1623
+ removeField(blockId, field.id);
1624
+ },
1625
+ className: "bb-field-card__delete",
1626
+ title: "Remove field"
1627
+ },
1628
+ "\xE2\u0153\u2022"
1629
+ )
1630
+ )
1631
+ );
1632
+ }
1633
+
1634
+ // src/block-builder/components/canvas/BuilderCanvas.tsx
1635
+ function BuilderCanvas() {
1636
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1637
+ const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1638
+ const reorderFields = useBuilderStore((s) => s.reorderFields);
1639
+ const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1640
+ const sensors = (0, import_core.useSensors)(
1641
+ (0, import_core.useSensor)(import_core.PointerSensor, { activationConstraint: { distance: 4 } }),
1642
+ (0, import_core.useSensor)(import_core.KeyboardSensor, { coordinateGetter: import_sortable2.sortableKeyboardCoordinates })
1643
+ );
1644
+ function handleDragEnd(event) {
1645
+ if (isReadOnly) return;
1646
+ const { active, over } = event;
1647
+ if (!over || active.id === over.id || !block) return;
1648
+ const fromIndex = block.fields.findIndex((f) => f.id === active.id);
1649
+ const toIndex = block.fields.findIndex((f) => f.id === over.id);
1650
+ if (fromIndex !== -1 && toIndex !== -1) {
1651
+ reorderFields(block.id, fromIndex, toIndex);
1652
+ }
1653
+ }
1654
+ if (!block) {
1655
+ return /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas", style: { display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ import_react10.default.createElement("span", { className: "bb-canvas__no-block" }, "Select or create a block from the left panel."));
1656
+ }
1657
+ return /* @__PURE__ */ import_react10.default.createElement("div", { className: `bb-canvas${isReadOnly ? " bb-canvas--readonly" : ""}` }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__inner" }, /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__header" }, block.slug, " \xE2\u20AC\u201D ", block.fields.length, " field", block.fields.length !== 1 ? "s" : ""), block.fields.length === 0 ? /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__empty" }, "Add fields from the palette on the left") : /* @__PURE__ */ import_react10.default.createElement(
1658
+ import_core.DndContext,
1659
+ {
1660
+ sensors,
1661
+ collisionDetection: import_core.closestCenter,
1662
+ modifiers: [import_modifiers.restrictToVerticalAxis, import_modifiers.restrictToParentElement],
1663
+ onDragEnd: handleDragEnd
1664
+ },
1665
+ /* @__PURE__ */ import_react10.default.createElement(
1666
+ import_sortable2.SortableContext,
1667
+ {
1668
+ items: block.fields.map((f) => f.id),
1669
+ strategy: import_sortable2.verticalListSortingStrategy
1670
+ },
1671
+ /* @__PURE__ */ import_react10.default.createElement("div", { className: "bb-canvas__field-list" }, block.fields.map((field, i) => /* @__PURE__ */ import_react10.default.createElement(
1672
+ SortableFieldCard,
1673
+ {
1674
+ key: field.id,
1675
+ field,
1676
+ blockId: block.id,
1677
+ index: i
1678
+ }
1679
+ )))
1680
+ )
1681
+ )));
1682
+ }
1683
+
1684
+ // src/block-builder/components/canvas/ConfigPanel.tsx
1685
+ var import_react13 = __toESM(require("react"), 1);
1686
+
1687
+ // src/block-builder/components/config/BlockConfig.tsx
1688
+ var import_react11 = __toESM(require("react"), 1);
1689
+ function BlockConfig() {
1690
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1691
+ const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1692
+ const updateBlock = useBuilderStore((s) => s.updateBlock);
1693
+ if (!block) {
1694
+ return /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__empty" }, "No block selected.");
1695
+ }
1696
+ return /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Slug *"), /* @__PURE__ */ import_react11.default.createElement(
1697
+ "input",
1698
+ {
1699
+ type: "text",
1700
+ value: block.slug,
1701
+ onChange: (e) => updateBlock(block.id, { slug: e.target.value }),
1702
+ placeholder: "myBlock",
1703
+ className: "bb-input"
1704
+ }
1705
+ ), /* @__PURE__ */ import_react11.default.createElement("span", { className: "bb-form__hint" }, "Unique identifier used in code and database")), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Interface Name"), /* @__PURE__ */ import_react11.default.createElement(
1706
+ "input",
1707
+ {
1708
+ type: "text",
1709
+ value: block.interfaceName ?? "",
1710
+ onChange: (e) => updateBlock(block.id, { interfaceName: e.target.value || void 0 }),
1711
+ placeholder: "MyBlock",
1712
+ className: "bb-input"
1713
+ }
1714
+ )), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Singular Label"), /* @__PURE__ */ import_react11.default.createElement(
1715
+ "input",
1716
+ {
1717
+ type: "text",
1718
+ value: block.labels?.singular ?? "",
1719
+ onChange: (e) => updateBlock(block.id, { labels: { ...block.labels, singular: e.target.value } }),
1720
+ placeholder: "My Block",
1721
+ className: "bb-input"
1722
+ }
1723
+ )), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Plural Label"), /* @__PURE__ */ import_react11.default.createElement(
1724
+ "input",
1725
+ {
1726
+ type: "text",
1727
+ value: block.labels?.plural ?? "",
1728
+ onChange: (e) => updateBlock(block.id, { labels: { ...block.labels, plural: e.target.value } }),
1729
+ placeholder: "My Blocks",
1730
+ className: "bb-input"
1731
+ }
1732
+ ))), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react11.default.createElement("label", { className: "bb-form__label" }, "Image URL"), /* @__PURE__ */ import_react11.default.createElement(
1733
+ "input",
1734
+ {
1735
+ type: "text",
1736
+ value: block.imageURL ?? "",
1737
+ onChange: (e) => updateBlock(block.id, { imageURL: e.target.value || void 0 }),
1738
+ placeholder: "https://\xE2\u20AC\xA6",
1739
+ className: "bb-input"
1740
+ }
1741
+ )), /* @__PURE__ */ import_react11.default.createElement("div", { className: "bb-stat-box" }, /* @__PURE__ */ import_react11.default.createElement("strong", null, block.fields.length), " field", block.fields.length !== 1 ? "s" : "", " defined"));
1742
+ }
1743
+
1744
+ // src/block-builder/components/config/FieldConfig.tsx
1745
+ var import_react12 = __toESM(require("react"), 1);
1746
+ var ALL_TYPES = [
1747
+ "text",
1748
+ "textarea",
1749
+ "richText",
1750
+ "number",
1751
+ "checkbox",
1752
+ "select",
1753
+ "radio",
1754
+ "date",
1755
+ "upload",
1756
+ "email",
1757
+ "code",
1758
+ "point",
1759
+ "relationship",
1760
+ "array",
1761
+ "group",
1762
+ "json",
1763
+ "ui"
1764
+ ];
1765
+ function FieldConfig() {
1766
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1767
+ const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1768
+ const block = useBuilderStore((s) => s.blocks.find((b) => b.id === activeBlockId));
1769
+ const field = block?.fields.find((f) => f.id === activeFieldId);
1770
+ const updateField = useBuilderStore((s) => s.updateField);
1771
+ if (!activeBlockId || !activeFieldId || !field) {
1772
+ return /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__empty" }, "Select a field to configure it.");
1773
+ }
1774
+ function upd(updates) {
1775
+ updateField(activeBlockId, activeFieldId, updates);
1776
+ }
1777
+ const needsOptions = field.type === "select" || field.type === "radio";
1778
+ return /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Field Name *"), /* @__PURE__ */ import_react12.default.createElement(
1779
+ "input",
1780
+ {
1781
+ type: "text",
1782
+ value: field.name,
1783
+ onChange: (e) => upd({ name: e.target.value }),
1784
+ placeholder: "fieldName",
1785
+ className: "bb-input"
1786
+ }
1787
+ )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Type *"), /* @__PURE__ */ import_react12.default.createElement(
1788
+ "select",
1789
+ {
1790
+ value: field.type,
1791
+ onChange: (e) => upd({ type: e.target.value }),
1792
+ className: "bb-input bb-select"
1793
+ },
1794
+ ALL_TYPES.map((t) => /* @__PURE__ */ import_react12.default.createElement("option", { key: t, value: t }, t))
1795
+ ))), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Label"), /* @__PURE__ */ import_react12.default.createElement(
1796
+ "input",
1797
+ {
1798
+ type: "text",
1799
+ value: field.label ?? "",
1800
+ onChange: (e) => upd({ label: e.target.value || void 0 }),
1801
+ placeholder: "Human-readable label",
1802
+ className: "bb-input"
1803
+ }
1804
+ )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-flags-row" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
1805
+ "input",
1806
+ {
1807
+ type: "checkbox",
1808
+ checked: field.required ?? false,
1809
+ onChange: (e) => upd({ required: e.target.checked })
1810
+ }
1811
+ ), "Required"), /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
1812
+ "input",
1813
+ {
1814
+ type: "checkbox",
1815
+ checked: field.unique ?? false,
1816
+ onChange: (e) => upd({ unique: e.target.checked })
1817
+ }
1818
+ ), "Unique"), /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
1819
+ "input",
1820
+ {
1821
+ type: "checkbox",
1822
+ checked: field.localized ?? false,
1823
+ onChange: (e) => upd({ localized: e.target.checked })
1824
+ }
1825
+ ), "Localized")), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Default Value"), /* @__PURE__ */ import_react12.default.createElement(
1826
+ "input",
1827
+ {
1828
+ type: "text",
1829
+ value: field.defaultValue !== void 0 ? String(field.defaultValue) : "",
1830
+ onChange: (e) => upd({ defaultValue: e.target.value || void 0 }),
1831
+ placeholder: "Default value",
1832
+ className: "bb-input"
1833
+ }
1834
+ )), field.type === "relationship" && /* @__PURE__ */ import_react12.default.createElement(import_react12.default.Fragment, null, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Relation To (collection slug)"), /* @__PURE__ */ import_react12.default.createElement(
1835
+ "input",
1836
+ {
1837
+ type: "text",
1838
+ value: field.relationTo ?? "",
1839
+ onChange: (e) => upd({ relationTo: e.target.value }),
1840
+ placeholder: "pages",
1841
+ className: "bb-input"
1842
+ }
1843
+ )), /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-checkbox-row" }, /* @__PURE__ */ import_react12.default.createElement(
1844
+ "input",
1845
+ {
1846
+ type: "checkbox",
1847
+ checked: field.hasMany ?? false,
1848
+ onChange: (e) => upd({ hasMany: e.target.checked })
1849
+ }
1850
+ ), "Has Many")), field.type === "array" && /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-grid-2" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Min Rows"), /* @__PURE__ */ import_react12.default.createElement(
1851
+ "input",
1852
+ {
1853
+ type: "number",
1854
+ value: field.minRows ?? "",
1855
+ onChange: (e) => upd({ minRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
1856
+ className: "bb-input"
1857
+ }
1858
+ )), /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("label", { className: "bb-form__label" }, "Max Rows"), /* @__PURE__ */ import_react12.default.createElement(
1859
+ "input",
1860
+ {
1861
+ type: "number",
1862
+ value: field.maxRows ?? "",
1863
+ onChange: (e) => upd({ maxRows: e.target.value === "" ? void 0 : e.target.valueAsNumber }),
1864
+ className: "bb-input"
1865
+ }
1866
+ ))), needsOptions && /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-form__section" }, /* @__PURE__ */ import_react12.default.createElement("div", { className: "bb-options-label" }, "Options"), (field.options ?? []).map((opt, i) => /* @__PURE__ */ import_react12.default.createElement("div", { key: i, className: "bb-option-row" }, /* @__PURE__ */ import_react12.default.createElement(
1867
+ "input",
1868
+ {
1869
+ type: "text",
1870
+ value: opt.label,
1871
+ placeholder: "Label",
1872
+ onChange: (e) => {
1873
+ const next = [...field.options ?? []];
1874
+ next[i] = { ...next[i], label: e.target.value };
1875
+ upd({ options: next });
1876
+ },
1877
+ className: "bb-input"
1878
+ }
1879
+ ), /* @__PURE__ */ import_react12.default.createElement(
1880
+ "input",
1881
+ {
1882
+ type: "text",
1883
+ value: opt.value,
1884
+ placeholder: "Value",
1885
+ onChange: (e) => {
1886
+ const next = [...field.options ?? []];
1887
+ next[i] = { ...next[i], value: e.target.value };
1888
+ upd({ options: next });
1889
+ },
1890
+ className: "bb-input"
1891
+ }
1892
+ ), /* @__PURE__ */ import_react12.default.createElement(
1893
+ "button",
1894
+ {
1895
+ type: "button",
1896
+ onClick: () => {
1897
+ const next = (field.options ?? []).filter((_, k) => k !== i);
1898
+ upd({ options: next });
1899
+ },
1900
+ className: "bb-option-delete",
1901
+ title: "Remove option"
1902
+ },
1903
+ "\xE2\u0153\u2022"
1904
+ ))), /* @__PURE__ */ import_react12.default.createElement(
1905
+ "button",
1906
+ {
1907
+ type: "button",
1908
+ onClick: () => upd({ options: [...field.options ?? [], { label: "", value: "" }] }),
1909
+ className: "bb-add-option"
1910
+ },
1911
+ "+ Add Option"
1912
+ )));
1913
+ }
1914
+
1915
+ // src/block-builder/components/canvas/ConfigPanel.tsx
1916
+ function ConfigPanel() {
1917
+ const [tab, setTab] = (0, import_react13.useState)("block");
1918
+ const activeFieldId = useBuilderStore((s) => s.activeFieldId);
1919
+ const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1920
+ const activeTab = activeFieldId ? "field" : tab;
1921
+ return /* @__PURE__ */ import_react13.default.createElement("div", { className: `bb-config${isReadOnly ? " bb-config--readonly" : ""}` }, /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-config__tabs" }, ["block", "field"].map((t) => /* @__PURE__ */ import_react13.default.createElement(
1922
+ "button",
1923
+ {
1924
+ key: t,
1925
+ type: "button",
1926
+ onClick: () => setTab(t),
1927
+ className: `bb-config__tab${activeTab === t ? " bb-config__tab--active" : ""}`
1928
+ },
1929
+ t
1930
+ ))), /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-config__body" }, activeTab === "block" ? /* @__PURE__ */ import_react13.default.createElement(BlockConfig, null) : /* @__PURE__ */ import_react13.default.createElement(FieldConfig, null)), isReadOnly && /* @__PURE__ */ import_react13.default.createElement("div", { className: "bb-config__readonly-overlay" }, /* @__PURE__ */ import_react13.default.createElement("span", { className: "bb-config__readonly-label" }, "Read only")));
1931
+ }
1932
+
1933
+ // src/block-builder/components/sidebar/FieldPalette.tsx
1934
+ var import_react14 = __toESM(require("react"), 1);
1935
+ var import_lucide_react = require("lucide-react");
1936
+
1937
+ // src/block-builder/lib/field-palette.ts
1938
+ var FIELD_PALETTE = [
1939
+ // Basic
1940
+ { type: "text", label: "Text", description: "Single line text input", icon: "Type", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1941
+ { type: "textarea", label: "Textarea", description: "Multi-line text input", icon: "AlignLeft", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1942
+ { type: "number", label: "Number", description: "Numeric input field", icon: "Hash", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1943
+ { type: "email", label: "Email", description: "Email address field", icon: "Mail", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1944
+ { type: "date", label: "Date", description: "Date and time picker", icon: "Calendar", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1945
+ { type: "checkbox", label: "Checkbox", description: "Boolean toggle", icon: "CheckSquare", category: "basic", color: "bg-blue-500/20 text-blue-400 border-blue-500/30" },
1946
+ // Choice
1947
+ { type: "select", label: "Select", description: "Dropdown selection", icon: "ChevronDown", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
1948
+ { type: "radio", label: "Radio", description: "Radio button group", icon: "Circle", category: "choice", color: "bg-purple-500/20 text-purple-400 border-purple-500/30" },
1949
+ // Media
1950
+ { 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" },
1951
+ // Relational
1952
+ { 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" },
1953
+ // Advanced
1954
+ { 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" }
1955
+ ];
1956
+ var FIELD_CATEGORIES = [
1957
+ { id: "basic", label: "Basic" },
1958
+ { id: "choice", label: "Choice" },
1959
+ { id: "media", label: "Media" },
1960
+ { id: "relational", label: "Relational" },
1961
+ { id: "advanced", label: "Advanced" }
1962
+ ];
1963
+ function getFieldMeta(type) {
1964
+ return FIELD_PALETTE.find((f) => f.type === type);
1965
+ }
1966
+
1967
+ // src/block-builder/components/sidebar/FieldPalette.tsx
1968
+ var ICON_MAP2 = {
1969
+ Type: import_lucide_react.Type,
1970
+ AlignLeft: import_lucide_react.AlignLeft,
1971
+ Hash: import_lucide_react.Hash,
1972
+ Mail: import_lucide_react.Mail,
1973
+ Calendar: import_lucide_react.Calendar,
1974
+ CheckSquare: import_lucide_react.CheckSquare,
1975
+ ChevronDown: import_lucide_react.ChevronDown,
1976
+ Circle: import_lucide_react.Circle,
1977
+ Upload: import_lucide_react.Upload,
1978
+ Link: import_lucide_react.Link,
1979
+ List: import_lucide_react.List,
1980
+ Folder: import_lucide_react.Folder,
1981
+ Braces: import_lucide_react.Braces
1982
+ };
1983
+ function FieldPalette() {
1984
+ const [search, setSearch] = (0, import_react14.useState)("");
1985
+ const activeBlockId = useBuilderStore((s) => s.activeBlockId);
1986
+ const addField = useBuilderStore((s) => s.addField);
1987
+ const isReadOnly = useBuilderStore((s) => s.isReadOnly);
1988
+ const filtered = search.trim() ? FIELD_PALETTE.filter(
1989
+ (f) => f.label.toLowerCase().includes(search.toLowerCase()) || f.type.toLowerCase().includes(search.toLowerCase())
1990
+ ) : FIELD_PALETTE;
1991
+ function handleAdd(type) {
1992
+ if (!activeBlockId || isReadOnly) return;
1993
+ addField(activeBlockId, type);
1994
+ }
1995
+ return /* @__PURE__ */ import_react14.default.createElement("div", { className: `bb-sidebar bb-sidebar--200 bb-sidebar--palette${isReadOnly ? " bb-sidebar--readonly" : ""}` }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-sidebar__header" }, /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-sidebar__title" }, "Fields")), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__search-wrap" }, /* @__PURE__ */ import_react14.default.createElement(
1996
+ "input",
1997
+ {
1998
+ type: "text",
1999
+ value: search,
2000
+ onChange: (e) => setSearch(e.target.value),
2001
+ placeholder: "Search\xE2\u20AC\xA6",
2002
+ className: "bb-palette__search"
2003
+ }
2004
+ )), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-sidebar__body" }, search.trim() ? /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__items" }, filtered.map((item) => /* @__PURE__ */ import_react14.default.createElement(
2005
+ FieldButton,
2006
+ {
2007
+ key: item.type,
2008
+ type: item.type,
2009
+ label: item.label,
2010
+ icon: item.icon,
2011
+ onAdd: handleAdd,
2012
+ disabled: !activeBlockId || isReadOnly
2013
+ }
2014
+ ))) : FIELD_CATEGORIES.map((cat) => {
2015
+ const items = FIELD_PALETTE.filter((f) => f.category === cat.id);
2016
+ return /* @__PURE__ */ import_react14.default.createElement("div", { key: cat.id, style: { marginTop: 6 } }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__cat-label" }, cat.label), /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-palette__items" }, items.map((item) => /* @__PURE__ */ import_react14.default.createElement(
2017
+ FieldButton,
2018
+ {
2019
+ key: item.type,
2020
+ type: item.type,
2021
+ label: item.label,
2022
+ icon: item.icon,
2023
+ onAdd: handleAdd,
2024
+ disabled: !activeBlockId || isReadOnly
2025
+ }
2026
+ ))));
2027
+ })), !activeBlockId && /* @__PURE__ */ import_react14.default.createElement("div", { className: "bb-sidebar__footer" }, "Select a block first"));
2028
+ }
2029
+ function FieldButton({
2030
+ type,
2031
+ label,
2032
+ icon,
2033
+ onAdd,
2034
+ disabled
2035
+ }) {
2036
+ const meta = getFieldMeta(type);
2037
+ const Icon = ICON_MAP2[icon];
2038
+ return /* @__PURE__ */ import_react14.default.createElement(
2039
+ "button",
2040
+ {
2041
+ type: "button",
2042
+ disabled,
2043
+ onClick: () => onAdd(type),
2044
+ title: meta?.description,
2045
+ className: "bb-palette__item"
2046
+ },
2047
+ /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-palette__item__icon" }, Icon ? /* @__PURE__ */ import_react14.default.createElement(Icon, { size: 13, strokeWidth: 1.75 }) : null),
2048
+ /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-palette__item__label" }, label),
2049
+ /* @__PURE__ */ import_react14.default.createElement("span", { className: "bb-palette__item__type" }, type)
2050
+ );
2051
+ }
2052
+
2053
+ // src/block-builder/components/canvas/CodePreview.tsx
2054
+ var import_react15 = __toESM(require("react"), 1);
2055
+ var KEYWORDS = /* @__PURE__ */ new Set([
2056
+ "import",
2057
+ "export",
2058
+ "from",
2059
+ "const",
2060
+ "let",
2061
+ "var",
2062
+ "type",
2063
+ "interface",
2064
+ "as",
2065
+ "true",
2066
+ "false",
2067
+ "null",
2068
+ "undefined",
2069
+ "return",
2070
+ "async",
2071
+ "await"
2072
+ ]);
2073
+ var TYPE_RE = /^[A-Z][A-Za-z0-9_<>[\],\s|&]*$/;
2074
+ function tokenise(code) {
2075
+ const tokens = [];
2076
+ let i = 0;
2077
+ while (i < code.length) {
2078
+ if (code[i] === "/" && code[i + 1] === "/") {
2079
+ const end = code.indexOf("\n", i);
2080
+ const text = end === -1 ? code.slice(i) : code.slice(i, end);
2081
+ tokens.push({ kind: "comment", text });
2082
+ i += text.length;
2083
+ continue;
2084
+ }
2085
+ if (code[i] === "'" || code[i] === '"' || code[i] === "`") {
2086
+ const q = code[i];
2087
+ let j2 = i + 1;
2088
+ while (j2 < code.length) {
2089
+ if (code[j2] === "\\") {
2090
+ j2 += 2;
2091
+ continue;
2092
+ }
2093
+ if (code[j2] === q) {
2094
+ j2++;
2095
+ break;
2096
+ }
2097
+ j2++;
2098
+ }
2099
+ tokens.push({ kind: "string", text: code.slice(i, j2) });
2100
+ i = j2;
2101
+ continue;
2102
+ }
2103
+ if (/[0-9]/.test(code[i]) || code[i] === "-" && /[0-9]/.test(code[i + 1] ?? "")) {
2104
+ let j2 = i + 1;
2105
+ while (j2 < code.length && /[0-9._]/.test(code[j2])) j2++;
2106
+ tokens.push({ kind: "number", text: code.slice(i, j2) });
2107
+ i = j2;
2108
+ continue;
2109
+ }
2110
+ if (/[A-Za-z_$]/.test(code[i])) {
2111
+ let j2 = i + 1;
2112
+ while (j2 < code.length && /[A-Za-z0-9_$]/.test(code[j2])) j2++;
2113
+ const word = code.slice(i, j2);
2114
+ const kind = KEYWORDS.has(word) ? "keyword" : TYPE_RE.test(word) ? "type" : "plain";
2115
+ tokens.push({ kind, text: word });
2116
+ i = j2;
2117
+ continue;
2118
+ }
2119
+ if (/[{}[\]:,;=().<>|&!]/.test(code[i])) {
2120
+ tokens.push({ kind: "punct", text: code[i] });
2121
+ i++;
2122
+ continue;
2123
+ }
2124
+ let j = i + 1;
2125
+ while (j < code.length && !/[A-Za-z0-9_$'"`0-9{}[\]:,;=().<>|&!/-]/.test(code[j])) j++;
2126
+ tokens.push({ kind: "plain", text: code.slice(i, j) });
2127
+ i = j;
2128
+ }
2129
+ return tokens;
2130
+ }
2131
+ var TOKEN_COLORS = {
2132
+ keyword: "#c792ea",
2133
+ string: "#c3e88d",
2134
+ comment: "#546e7a",
2135
+ type: "#82aaff",
2136
+ number: "#f78c6c",
2137
+ punct: "#89ddff",
2138
+ plain: "#f9fafb"
2139
+ };
2140
+ function HighlightedCode({ code }) {
2141
+ const tokens = tokenise(code);
2142
+ return /* @__PURE__ */ import_react15.default.createElement("code", { style: { fontFamily: "inherit" } }, tokens.map((tok, i) => /* @__PURE__ */ import_react15.default.createElement("span", { key: i, style: { color: TOKEN_COLORS[tok.kind] } }, tok.text)));
2143
+ }
2144
+ function CodePreview() {
2145
+ const blocks = useBuilderStore((s) => s.blocks);
2146
+ const [fileMap, setFileMap] = (0, import_react15.useState)({});
2147
+ const [activeFile, setActiveFile] = (0, import_react15.useState)(null);
2148
+ const [copied, setCopied] = (0, import_react15.useState)(false);
2149
+ const timerRef = (0, import_react15.useRef)(null);
2150
+ const copyTimerRef = (0, import_react15.useRef)(null);
2151
+ const regenerate = (0, import_react15.useCallback)(() => {
2152
+ if (blocks.length === 0) {
2153
+ setFileMap({});
2154
+ setActiveFile(null);
2155
+ return;
2156
+ }
2157
+ const blockOutputs = generateAllBlocks(blocks);
2158
+ const indexOutput = generateIndexFile(blocks);
2159
+ const next = {};
2160
+ for (const out of blockOutputs) next[out.filename] = out.code;
2161
+ next[indexOutput.filename] = indexOutput.code;
2162
+ setFileMap(next);
2163
+ setActiveFile((prev) => {
2164
+ const keys = Object.keys(next);
2165
+ return prev && keys.includes(prev) ? prev : keys[0] ?? null;
2166
+ });
2167
+ }, [blocks]);
2168
+ (0, import_react15.useEffect)(() => {
2169
+ if (timerRef.current) clearTimeout(timerRef.current);
2170
+ timerRef.current = setTimeout(regenerate, 300);
2171
+ return () => {
2172
+ if (timerRef.current) clearTimeout(timerRef.current);
2173
+ };
2174
+ }, [regenerate]);
2175
+ function copyActive() {
2176
+ if (!activeFile || !fileMap[activeFile]) return;
2177
+ navigator.clipboard.writeText(fileMap[activeFile]).then(() => {
2178
+ setCopied(true);
2179
+ if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
2180
+ copyTimerRef.current = setTimeout(() => setCopied(false), 2e3);
2181
+ });
2182
+ }
2183
+ const fileNames = Object.keys(fileMap);
2184
+ const activeCode = activeFile ? fileMap[activeFile] ?? "" : "";
2185
+ const lineCount = activeCode ? activeCode.split("\n").length : 0;
2186
+ if (fileNames.length === 0) {
2187
+ return /* @__PURE__ */ import_react15.default.createElement(
2188
+ "div",
2189
+ {
2190
+ className: "code-preview",
2191
+ style: {
2192
+ height: "100%",
2193
+ display: "flex",
2194
+ alignItems: "center",
2195
+ justifyContent: "center",
2196
+ color: "var(--payload-muted, #9ca3af)",
2197
+ fontSize: "0.875rem",
2198
+ fontStyle: "italic"
2199
+ }
2200
+ },
2201
+ "Add a block to see generated TypeScript code"
2202
+ );
2203
+ }
2204
+ return /* @__PURE__ */ import_react15.default.createElement("div", { className: "code-preview", style: { height: "100%", display: "flex", flexDirection: "column", overflow: "hidden" } }, /* @__PURE__ */ import_react15.default.createElement(
2205
+ "div",
2206
+ {
2207
+ style: {
2208
+ display: "flex",
2209
+ alignItems: "center",
2210
+ borderBottom: "1px solid var(--payload-border, #1f2937)",
2211
+ flexShrink: 0,
2212
+ overflowX: "auto",
2213
+ gap: 0
2214
+ }
2215
+ },
2216
+ fileNames.map((name) => {
2217
+ const isActive = activeFile === name;
2218
+ return /* @__PURE__ */ import_react15.default.createElement(
2219
+ "button",
2220
+ {
2221
+ key: name,
2222
+ type: "button",
2223
+ onClick: () => setActiveFile(name),
2224
+ style: {
2225
+ padding: "0.375rem 0.875rem",
2226
+ background: isActive ? "var(--payload-surface, #111827)" : "transparent",
2227
+ border: "none",
2228
+ borderBottom: `2px solid ${isActive ? "#22c55e" : "transparent"}`,
2229
+ borderRight: "1px solid var(--payload-border, #1f2937)",
2230
+ color: isActive ? "#22c55e" : "var(--payload-muted, #9ca3af)",
2231
+ fontSize: "0.75rem",
2232
+ fontFamily: "ui-monospace, monospace",
2233
+ cursor: "pointer",
2234
+ whiteSpace: "nowrap",
2235
+ transition: "color 0.1s, background 0.1s"
2236
+ }
2237
+ },
2238
+ name
2239
+ );
2240
+ }),
2241
+ /* @__PURE__ */ import_react15.default.createElement("div", { style: { flex: 1 } }),
2242
+ lineCount > 0 && /* @__PURE__ */ import_react15.default.createElement(
2243
+ "span",
2244
+ {
2245
+ style: {
2246
+ fontSize: "0.6875rem",
2247
+ color: "var(--payload-muted, #9ca3af)",
2248
+ padding: "0 0.75rem",
2249
+ whiteSpace: "nowrap"
2250
+ }
2251
+ },
2252
+ lineCount,
2253
+ " lines"
2254
+ ),
2255
+ /* @__PURE__ */ import_react15.default.createElement(
2256
+ "button",
2257
+ {
2258
+ type: "button",
2259
+ onClick: copyActive,
2260
+ title: "Copy to clipboard",
2261
+ style: {
2262
+ padding: "0.375rem 0.875rem",
2263
+ background: "transparent",
2264
+ border: "none",
2265
+ borderLeft: "1px solid var(--payload-border, #1f2937)",
2266
+ color: copied ? "#22c55e" : "var(--payload-muted, #9ca3af)",
2267
+ fontSize: "0.75rem",
2268
+ cursor: "pointer",
2269
+ whiteSpace: "nowrap",
2270
+ transition: "color 0.2s",
2271
+ display: "flex",
2272
+ alignItems: "center",
2273
+ gap: "0.25rem"
2274
+ }
2275
+ },
2276
+ copied ? "\xE2\u0153\u201C Copied" : "\xE2\xA7\u2030 Copy"
2277
+ )
2278
+ ), /* @__PURE__ */ import_react15.default.createElement("div", { style: { flex: 1, overflow: "auto", display: "flex" } }, /* @__PURE__ */ import_react15.default.createElement(
2279
+ "div",
2280
+ {
2281
+ style: {
2282
+ flexShrink: 0,
2283
+ padding: "1rem 0.75rem 1rem 0.5rem",
2284
+ textAlign: "right",
2285
+ color: "var(--payload-muted, #4b5563)",
2286
+ fontSize: "0.75rem",
2287
+ lineHeight: 1.6,
2288
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
2289
+ userSelect: "none",
2290
+ borderRight: "1px solid var(--payload-border, #1f2937)",
2291
+ minWidth: "2.5rem"
2292
+ },
2293
+ "aria-hidden": "true"
2294
+ },
2295
+ activeCode.split("\n").map((_, i) => /* @__PURE__ */ import_react15.default.createElement("div", { key: i }, i + 1))
2296
+ ), /* @__PURE__ */ import_react15.default.createElement(
2297
+ "pre",
2298
+ {
2299
+ style: {
2300
+ flex: 1,
2301
+ margin: 0,
2302
+ padding: "1rem",
2303
+ fontSize: "0.75rem",
2304
+ lineHeight: 1.6,
2305
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
2306
+ background: "transparent",
2307
+ overflow: "visible",
2308
+ whiteSpace: "pre"
2309
+ }
2310
+ },
2311
+ /* @__PURE__ */ import_react15.default.createElement(HighlightedCode, { code: activeCode })
2312
+ )));
2313
+ }
2314
+
2315
+ // src/block-builder/components/canvas/BuilderShell.tsx
2316
+ function BuilderShell({ loadSlug }) {
2317
+ const loadBlock = useBuilderStore((s) => s.loadBlock);
2318
+ const setVersionMeta = useBuilderStore((s) => s.setVersionMeta);
2319
+ const setBlockSlug = useBuilderStore((s) => s.setBlockSlug);
2320
+ const isReadOnly = useBuilderStore((s) => s.isReadOnly);
2321
+ const [activeSlug, setActiveSlug] = (0, import_react16.useState)(loadSlug ?? null);
2322
+ const [loading, setLoading] = (0, import_react16.useState)(!!loadSlug);
2323
+ const [loadError, setLoadError] = (0, import_react16.useState)(null);
2324
+ const [showCodePreview, setShowCodePreview] = (0, import_react16.useState)(false);
2325
+ const [versions, setVersions] = (0, import_react16.useState)([]);
2326
+ const [selectedVersionId, setSelectedVersionId] = (0, import_react16.useState)(null);
2327
+ const [blockDefs, setBlockDefs] = (0, import_react16.useState)([]);
2328
+ const [mobilePanelTab, setMobilePanelTab] = (0, import_react16.useState)("blocks");
2329
+ (0, import_react16.useEffect)(() => {
2330
+ fetch("/api/block-definitions?limit=200&depth=0").then((r) => r.json()).then((json) => {
2331
+ setBlockDefs(
2332
+ (json.docs ?? []).map((d) => ({ id: String(d.id), slug: d.slug, name: d.name }))
2333
+ );
2334
+ }).catch(() => {
2335
+ });
2336
+ }, []);
2337
+ const loadVersionsForSlug = (0, import_react16.useCallback)(async (slug) => {
2338
+ try {
2339
+ const res = await fetch(`/api/block-builder/versions/${encodeURIComponent(slug)}`);
2340
+ const json = await res.json();
2341
+ return json.versions ?? [];
2342
+ } catch {
2343
+ return [];
2344
+ }
2345
+ }, []);
2346
+ const loadVersion = (0, import_react16.useCallback)(async (slug, versionId) => {
2347
+ setLoading(true);
2348
+ setLoadError(null);
2349
+ const url = versionId ? `/api/block-builder/load/${encodeURIComponent(slug)}?versionId=${encodeURIComponent(versionId)}` : `/api/block-builder/load/${encodeURIComponent(slug)}`;
2350
+ try {
2351
+ const res = await fetch(url);
2352
+ const json = await res.json();
2353
+ if (json.block) {
2354
+ loadBlock(json.block);
2355
+ setVersionMeta(json.versionId ?? null, !(json.isCurrent ?? true));
2356
+ setSelectedVersionId(json.versionId ?? null);
2357
+ } else {
2358
+ setLoadError(json.error ?? "Failed to load block");
2359
+ }
2360
+ } catch (err) {
2361
+ setLoadError(err instanceof Error ? err.message : "Network error");
2362
+ } finally {
2363
+ setLoading(false);
2364
+ }
2365
+ }, [loadBlock, setVersionMeta]);
2366
+ const loadBlockBySlug = (0, import_react16.useCallback)(async (slug) => {
2367
+ setActiveSlug(slug);
2368
+ setBlockSlug(slug);
2369
+ setVersions([]);
2370
+ setSelectedVersionId(null);
2371
+ setLoadError(null);
2372
+ setMobilePanelTab("canvas");
2373
+ await loadVersion(slug);
2374
+ const list = await loadVersionsForSlug(slug);
2375
+ setVersions(list);
2376
+ const current = list.find((v) => v.isCurrent) ?? list[0];
2377
+ if (current) setSelectedVersionId(current.id);
2378
+ }, [loadVersion, loadVersionsForSlug, setBlockSlug]);
2379
+ (0, import_react16.useEffect)(() => {
2380
+ if (!loadSlug) return;
2381
+ loadBlockBySlug(loadSlug);
2382
+ }, [loadSlug]);
2383
+ function handleVersionSelect(versionId) {
2384
+ if (!activeSlug) return;
2385
+ loadVersion(activeSlug, versionId);
2386
+ }
2387
+ async function handleRestoreVersion() {
2388
+ if (!activeSlug) return;
2389
+ await loadVersion(activeSlug);
2390
+ const list = await loadVersionsForSlug(activeSlug);
2391
+ setVersions(list);
2392
+ const current = list.find((v) => v.isCurrent) ?? list[0];
2393
+ if (current) setSelectedVersionId(current.id);
2394
+ }
2395
+ async function handleAfterPublish() {
2396
+ if (!activeSlug) return;
2397
+ const list = await loadVersionsForSlug(activeSlug);
2398
+ setVersions(list);
2399
+ const current = list.find((v) => v.isCurrent) ?? list[0];
2400
+ if (current) setSelectedVersionId(current.id);
2401
+ }
2402
+ return /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-shell" }, /* @__PURE__ */ import_react16.default.createElement(
2403
+ TopBar,
2404
+ {
2405
+ blockDefs,
2406
+ activeSlug,
2407
+ onBlockSelect: loadBlockBySlug,
2408
+ versions,
2409
+ selectedVersionId,
2410
+ onVersionSelect: handleVersionSelect,
2411
+ onRestoreVersion: handleRestoreVersion,
2412
+ onAfterPublish: handleAfterPublish
2413
+ }
2414
+ ), loading && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-loading-bar" }, "Loading\xE2\u20AC\xA6"), loadError && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-error-bar" }, "Error: ", loadError), isReadOnly && !loading && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-readonly-banner" }, /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-readonly-banner__icon" }, "\xF0\u0178\u2018\x81"), /* @__PURE__ */ import_react16.default.createElement("span", null, "You are viewing a previous version \xE2\u20AC\u201D read only.", /* @__PURE__ */ import_react16.default.createElement(
2415
+ "button",
2416
+ {
2417
+ type: "button",
2418
+ className: "bb-readonly-banner__btn",
2419
+ onClick: handleRestoreVersion
2420
+ },
2421
+ "Switch to latest"
2422
+ ))), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-main", "data-mobile-panel": mobilePanelTab }, /* @__PURE__ */ import_react16.default.createElement(BlockList, null), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-main__center" }, /* @__PURE__ */ import_react16.default.createElement(FieldPalette, null), /* @__PURE__ */ import_react16.default.createElement(BuilderCanvas, null)), /* @__PURE__ */ import_react16.default.createElement(ConfigPanel, null)), /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-footer" }, /* @__PURE__ */ import_react16.default.createElement(
2423
+ "button",
2424
+ {
2425
+ type: "button",
2426
+ onClick: () => setShowCodePreview((p) => !p),
2427
+ className: `bb-footer__toggle${showCodePreview ? " bb-footer__toggle--open" : ""}`
2428
+ },
2429
+ showCodePreview ? "\xE2\u2013\xBC" : "\xE2\u2013\xB6",
2430
+ " Code Preview"
2431
+ ), showCodePreview && /* @__PURE__ */ import_react16.default.createElement("div", { className: "bb-footer__content" }, /* @__PURE__ */ import_react16.default.createElement(CodePreview, null))), /* @__PURE__ */ import_react16.default.createElement("nav", { className: "bb-mobile-nav", "aria-label": "Panel navigation" }, [
2432
+ { id: "blocks", icon: "\xE2\xAC\xA1", label: "Blocks" },
2433
+ { id: "canvas", icon: "\xE2\u2013\xA6", label: "Canvas" },
2434
+ { id: "palette", icon: "\xEF\xBC\u2039", label: "Fields" },
2435
+ { id: "config", icon: "\xE2\u0161\u2122", label: "Config" }
2436
+ ].map(({ id, icon, label }) => /* @__PURE__ */ import_react16.default.createElement(
2437
+ "button",
2438
+ {
2439
+ key: id,
2440
+ type: "button",
2441
+ className: `bb-mobile-nav__tab${mobilePanelTab === id ? " bb-mobile-nav__tab--active" : ""}`,
2442
+ onClick: () => setMobilePanelTab(id)
2443
+ },
2444
+ /* @__PURE__ */ import_react16.default.createElement("span", { className: "bb-mobile-nav__icon" }, icon),
2445
+ label
2446
+ ))));
2447
+ }
2448
+ // Annotate the CommonJS export names for ESM import in node:
2449
+ 0 && (module.exports = {
2450
+ BlockDataField,
2451
+ BuilderShell,
2452
+ EditInBuilderButton,
2453
+ SchemaBuilderField
2454
+ });