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