@riverbankcms/sdk 0.2.0 → 0.2.1

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.
Files changed (176) hide show
  1. package/dist/cli/index.js +4840 -9
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/client/bookings.d.mts +82 -2
  4. package/dist/client/bookings.d.ts +82 -2
  5. package/dist/client/bookings.js +1623 -3
  6. package/dist/client/bookings.js.map +1 -1
  7. package/dist/client/bookings.mjs +1610 -5
  8. package/dist/client/bookings.mjs.map +1 -1
  9. package/dist/client/client.d.mts +8 -5
  10. package/dist/client/client.d.ts +8 -5
  11. package/dist/client/client.js +16856 -322
  12. package/dist/client/client.js.map +1 -1
  13. package/dist/client/client.mjs +16838 -307
  14. package/dist/client/client.mjs.map +1 -1
  15. package/dist/client/hooks.d.mts +10 -7
  16. package/dist/client/hooks.d.ts +10 -7
  17. package/dist/client/hooks.js +5074 -4
  18. package/dist/client/hooks.js.map +1 -1
  19. package/dist/client/hooks.mjs +5074 -4
  20. package/dist/client/hooks.mjs.map +1 -1
  21. package/dist/client/rendering/client.d.mts +7 -1
  22. package/dist/client/rendering/client.d.ts +7 -1
  23. package/dist/client/rendering/client.js +17388 -2
  24. package/dist/client/rendering/client.js.map +1 -1
  25. package/dist/client/rendering/client.mjs +17382 -2
  26. package/dist/client/rendering/client.mjs.map +1 -1
  27. package/dist/client/resolver-BhueZVxZ.d.mts +61 -0
  28. package/dist/client/resolver-BhueZVxZ.d.ts +61 -0
  29. package/dist/client/usePage-BBcFCxOU.d.ts +6297 -0
  30. package/dist/client/usePage-BydHcMYB.d.mts +6297 -0
  31. package/dist/server/Layout-CLg8oH_S.d.ts +44 -0
  32. package/dist/server/Layout-DK_9OOgb.d.mts +44 -0
  33. package/dist/server/chunk-3J46ILMJ.mjs +2111 -0
  34. package/dist/server/chunk-3J46ILMJ.mjs.map +1 -0
  35. package/dist/server/{chunk-JB4LIEFS.js → chunk-5R4NMVXA.js} +15 -8
  36. package/dist/server/chunk-5R4NMVXA.js.map +1 -0
  37. package/dist/server/{chunk-ADREPXFU.js → chunk-62ZJI564.js} +3 -3
  38. package/dist/server/{chunk-ADREPXFU.js.map → chunk-62ZJI564.js.map} +1 -1
  39. package/dist/server/chunk-7DS4Q3GA.mjs +333 -0
  40. package/dist/server/chunk-7DS4Q3GA.mjs.map +1 -0
  41. package/dist/server/chunk-BJTO5JO5.mjs +11 -0
  42. package/dist/server/{chunk-4Z5FBFRL.mjs → chunk-BPKYRPCQ.mjs} +7 -3
  43. package/dist/server/{chunk-4Z5FBFRL.mjs.map → chunk-BPKYRPCQ.mjs.map} +1 -1
  44. package/dist/server/chunk-DGUM43GV.js +11 -0
  45. package/dist/server/chunk-DGUM43GV.js.map +1 -0
  46. package/dist/server/chunk-EGTDJ4PL.js +5461 -0
  47. package/dist/server/chunk-EGTDJ4PL.js.map +1 -0
  48. package/dist/server/chunk-FK64TZBT.mjs +831 -0
  49. package/dist/server/chunk-FK64TZBT.mjs.map +1 -0
  50. package/dist/server/chunk-GKYNDDJS.js +2111 -0
  51. package/dist/server/chunk-GKYNDDJS.js.map +1 -0
  52. package/dist/server/chunk-HOY77YBF.js +333 -0
  53. package/dist/server/chunk-HOY77YBF.js.map +1 -0
  54. package/dist/server/chunk-INWKF3IC.js +831 -0
  55. package/dist/server/chunk-INWKF3IC.js.map +1 -0
  56. package/dist/server/{chunk-2RW5HAQQ.mjs → chunk-JTAERCX2.mjs} +2 -2
  57. package/dist/server/chunk-O5DC7MYW.mjs +9606 -0
  58. package/dist/server/chunk-O5DC7MYW.mjs.map +1 -0
  59. package/dist/server/{chunk-PEAXKTDU.mjs → chunk-OP2GHK27.mjs} +2 -2
  60. package/dist/server/{chunk-WKG57P2H.mjs → chunk-PN3CHDVX.mjs} +10 -3
  61. package/dist/server/{chunk-WKG57P2H.mjs.map → chunk-PN3CHDVX.mjs.map} +1 -1
  62. package/dist/server/chunk-SF63XAX7.js +9606 -0
  63. package/dist/server/chunk-SF63XAX7.js.map +1 -0
  64. package/dist/server/{chunk-F472SMKX.js → chunk-TO7FD6TQ.js} +4 -4
  65. package/dist/server/{chunk-F472SMKX.js.map → chunk-TO7FD6TQ.js.map} +1 -1
  66. package/dist/server/chunk-USQF2XTU.mjs +5461 -0
  67. package/dist/server/chunk-USQF2XTU.mjs.map +1 -0
  68. package/dist/server/{chunk-SW7LE4M3.js → chunk-XLVL5WPH.js} +12 -8
  69. package/dist/server/chunk-XLVL5WPH.js.map +1 -0
  70. package/dist/server/components-BzdA6NAc.d.mts +305 -0
  71. package/dist/server/components-DhIcstww.d.ts +305 -0
  72. package/dist/server/components.d.mts +13 -49
  73. package/dist/server/components.d.ts +13 -49
  74. package/dist/server/components.js +7 -4
  75. package/dist/server/components.js.map +1 -1
  76. package/dist/server/components.mjs +9 -6
  77. package/dist/server/components.mjs.map +1 -1
  78. package/dist/server/config-validation.d.mts +2 -2
  79. package/dist/server/config-validation.d.ts +2 -2
  80. package/dist/server/config-validation.js +6 -3
  81. package/dist/server/config-validation.js.map +1 -1
  82. package/dist/server/config-validation.mjs +5 -2
  83. package/dist/server/config.d.mts +3 -3
  84. package/dist/server/config.d.ts +3 -3
  85. package/dist/server/config.js +6 -3
  86. package/dist/server/config.js.map +1 -1
  87. package/dist/server/config.mjs +5 -2
  88. package/dist/server/config.mjs.map +1 -1
  89. package/dist/server/data.d.mts +9 -8
  90. package/dist/server/data.d.ts +9 -8
  91. package/dist/server/data.js +4 -2
  92. package/dist/server/data.js.map +1 -1
  93. package/dist/server/data.mjs +3 -1
  94. package/dist/server/{index-C6M0Wfjq.d.ts → index-BB28KAui.d.ts} +1 -1
  95. package/dist/server/{index-B0yI_V6Z.d.mts → index-C_FVup_o.d.mts} +1 -1
  96. package/dist/server/index.d.mts +1554 -5
  97. package/dist/server/index.d.ts +1554 -5
  98. package/dist/server/index.js +4 -4
  99. package/dist/server/index.js.map +1 -1
  100. package/dist/server/index.mjs +4 -4
  101. package/dist/server/index.mjs.map +1 -1
  102. package/dist/server/{loadContent-CJcbYF3J.d.ts → loadContent-AQOBf_gP.d.ts} +4 -4
  103. package/dist/server/{loadContent-zhlL4YSE.d.mts → loadContent-DBmprsB4.d.mts} +4 -4
  104. package/dist/server/loadPage-3ECPF426.js +11 -0
  105. package/dist/server/loadPage-3ECPF426.js.map +1 -0
  106. package/dist/server/{loadPage-CCf15nt8.d.mts → loadPage-BMg8PJxJ.d.ts} +146 -5
  107. package/dist/server/loadPage-LW273NYO.mjs +11 -0
  108. package/dist/server/loadPage-LW273NYO.mjs.map +1 -0
  109. package/dist/server/{loadPage-BYmVMk0V.d.ts → loadPage-pg4HimlK.d.mts} +146 -5
  110. package/dist/server/metadata.d.mts +9 -6
  111. package/dist/server/metadata.d.ts +9 -6
  112. package/dist/server/metadata.js +3 -1
  113. package/dist/server/metadata.js.map +1 -1
  114. package/dist/server/metadata.mjs +2 -0
  115. package/dist/server/metadata.mjs.map +1 -1
  116. package/dist/server/rendering/server.d.mts +9 -7
  117. package/dist/server/rendering/server.d.ts +9 -7
  118. package/dist/server/rendering/server.js +7 -4
  119. package/dist/server/rendering/server.js.map +1 -1
  120. package/dist/server/rendering/server.mjs +6 -3
  121. package/dist/server/rendering.d.mts +172 -9
  122. package/dist/server/rendering.d.ts +172 -9
  123. package/dist/server/rendering.js +12 -9
  124. package/dist/server/rendering.js.map +1 -1
  125. package/dist/server/rendering.mjs +14 -11
  126. package/dist/server/rendering.mjs.map +1 -1
  127. package/dist/server/routing.d.mts +9 -6
  128. package/dist/server/routing.d.ts +9 -6
  129. package/dist/server/routing.js +4 -2
  130. package/dist/server/routing.js.map +1 -1
  131. package/dist/server/routing.mjs +3 -1
  132. package/dist/server/routing.mjs.map +1 -1
  133. package/dist/server/schema-Bpy9N5ZI.d.mts +1870 -0
  134. package/dist/server/schema-Bpy9N5ZI.d.ts +1870 -0
  135. package/dist/server/server.d.mts +11 -8
  136. package/dist/server/server.d.ts +11 -8
  137. package/dist/server/server.js +7 -5
  138. package/dist/server/server.js.map +1 -1
  139. package/dist/server/server.mjs +6 -4
  140. package/dist/server/theme-bridge.js +13 -10
  141. package/dist/server/theme-bridge.js.map +1 -1
  142. package/dist/server/theme-bridge.mjs +10 -7
  143. package/dist/server/theme-bridge.mjs.map +1 -1
  144. package/dist/server/theme.js +3 -1
  145. package/dist/server/theme.js.map +1 -1
  146. package/dist/server/theme.mjs +2 -0
  147. package/dist/server/theme.mjs.map +1 -1
  148. package/dist/server/{types-BCeqWtI2.d.ts → types--u4GLCAY.d.ts} +1 -1
  149. package/dist/server/types-BprgZt-t.d.ts +4149 -0
  150. package/dist/server/types-C0G9IxWO.d.mts +4149 -0
  151. package/dist/server/{types-Bbo01M7P.d.mts → types-_nDnPHpv.d.mts} +27 -1
  152. package/dist/server/{types-Bbo01M7P.d.ts → types-_nDnPHpv.d.ts} +27 -1
  153. package/dist/server/{types-BCeqWtI2.d.mts → types-_zWJTgv0.d.mts} +1 -1
  154. package/package.json +6 -6
  155. package/dist/server/chunk-3KKZVGH4.mjs +0 -179
  156. package/dist/server/chunk-3KKZVGH4.mjs.map +0 -1
  157. package/dist/server/chunk-4Z3GPTCS.js +0 -179
  158. package/dist/server/chunk-4Z3GPTCS.js.map +0 -1
  159. package/dist/server/chunk-JB4LIEFS.js.map +0 -1
  160. package/dist/server/chunk-QQ6U4QX6.js +0 -120
  161. package/dist/server/chunk-QQ6U4QX6.js.map +0 -1
  162. package/dist/server/chunk-R5YGLRUG.mjs +0 -122
  163. package/dist/server/chunk-R5YGLRUG.mjs.map +0 -1
  164. package/dist/server/chunk-SW7LE4M3.js.map +0 -1
  165. package/dist/server/chunk-W3K7LVPS.mjs +0 -120
  166. package/dist/server/chunk-W3K7LVPS.mjs.map +0 -1
  167. package/dist/server/chunk-YHEZMVTS.js +0 -122
  168. package/dist/server/chunk-YHEZMVTS.js.map +0 -1
  169. package/dist/server/loadPage-DVH3DW6E.js +0 -9
  170. package/dist/server/loadPage-DVH3DW6E.js.map +0 -1
  171. package/dist/server/loadPage-PHQZ6XQZ.mjs +0 -9
  172. package/dist/server/types-C6gmRHLe.d.mts +0 -150
  173. package/dist/server/types-C6gmRHLe.d.ts +0 -150
  174. /package/dist/server/{loadPage-PHQZ6XQZ.mjs.map → chunk-BJTO5JO5.mjs.map} +0 -0
  175. /package/dist/server/{chunk-2RW5HAQQ.mjs.map → chunk-JTAERCX2.mjs.map} +0 -0
  176. /package/dist/server/{chunk-PEAXKTDU.mjs.map → chunk-OP2GHK27.mjs.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -3,13 +3,4844 @@
3
3
 
4
4
  var commander = require('commander');
5
5
  var zod = require('zod');
6
- var blocks = require('@riverbankcms/blocks');
7
- require('@riverbankcms/blocks/system/data');
8
6
  var jiti = require('jiti');
9
7
  var path = require('path');
10
8
  var fs = require('fs');
11
9
 
12
10
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
+ // ../blocks/src/system/manifest/augmentManifest.ts
12
+ function augmentManifest(manifest) {
13
+ let augmentedFields = manifest.fields ?? [];
14
+ const variantField = createVariantField(manifest);
15
+ if (variantField) {
16
+ augmentedFields = [variantField, ...augmentedFields];
17
+ }
18
+ return {
19
+ ...manifest,
20
+ fields: augmentedFields
21
+ };
22
+ }
23
+ function createVariantField(manifest) {
24
+ if (!manifest.variants || Object.keys(manifest.variants).length <= 1) {
25
+ return null;
26
+ }
27
+ const variantKeys = Object.keys(manifest.variants);
28
+ const field = {
29
+ id: "variant",
30
+ type: "select",
31
+ label: "Variant",
32
+ description: "Choose a layout variant for this block",
33
+ required: false,
34
+ defaultValue: manifest.defaultVariant ?? variantKeys[0],
35
+ options: variantKeys.map((key) => ({
36
+ value: key,
37
+ label: formatVariantLabel(key)
38
+ })),
39
+ multiple: false
40
+ };
41
+ return field;
42
+ }
43
+ function formatVariantLabel(variantKey) {
44
+ return variantKey.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
45
+ }
46
+
47
+ // ../blocks/src/system/manifest/registry.ts
48
+ var REGISTRY_SYMBOL = Symbol.for("@riverbankcms/blocks/manifest-registry");
49
+ var globalScope = globalThis;
50
+ if (!globalScope[REGISTRY_SYMBOL]) {
51
+ globalScope[REGISTRY_SYMBOL] = /* @__PURE__ */ new Map();
52
+ }
53
+ var manifestStore = globalScope[REGISTRY_SYMBOL];
54
+ function registerManifest(manifest) {
55
+ manifestStore.set(manifest.name, manifest);
56
+ return manifest;
57
+ }
58
+ var transformStepSchema = zod.z.object({
59
+ id: zod.z.string().min(1, "Transform requires an identifier"),
60
+ options: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
61
+ });
62
+ var bindingPathSchema = zod.z.string().min(1, "Binding path is required").regex(/[A-Za-z0-9_\.$\[\]/-]+/, "Binding path contains invalid characters");
63
+ var bindingSchema = zod.z.object({
64
+ from: bindingPathSchema,
65
+ fallback: zod.z.unknown().optional(),
66
+ transforms: transformStepSchema.array().default([]),
67
+ pick: zod.z.enum(["value", "collection", "context"]).default("value")
68
+ });
69
+ var repeatSchema = zod.z.object({
70
+ collection: bindingSchema,
71
+ itemName: zod.z.string().min(1).default("item"),
72
+ indexName: zod.z.string().min(1).default("index"),
73
+ limit: zod.z.number().int().positive().optional(),
74
+ sortBy: zod.z.object({
75
+ path: bindingPathSchema,
76
+ direction: zod.z.enum(["asc", "desc"]).default("asc")
77
+ }).optional()
78
+ });
79
+ var conditionSchema = zod.z.object({
80
+ when: bindingSchema,
81
+ equals: zod.z.unknown().optional(),
82
+ not: zod.z.boolean().default(false)
83
+ });
84
+ var dataScopeSchema = zod.z.object({
85
+ name: zod.z.string().min(1),
86
+ from: bindingSchema
87
+ });
88
+ var nodePropsSchema = zod.z.record(zod.z.string(), zod.z.unknown()).default({});
89
+ var nodeSchema = zod.z.object({
90
+ type: zod.z.string().min(1, "Node type is required"),
91
+ key: zod.z.string().optional(),
92
+ props: nodePropsSchema.optional(),
93
+ children: zod.z.lazy(() => nodeSchema.array().default([])).optional(),
94
+ $bind: bindingSchema.optional(),
95
+ $repeat: repeatSchema.optional(),
96
+ $when: conditionSchema.optional(),
97
+ $scopes: dataScopeSchema.array().optional()
98
+ });
99
+ nodeSchema.array().or(nodeSchema);
100
+ var NodeSchema = nodeSchema;
101
+
102
+ // ../blocks/src/system/node/typeBasedLayout.ts
103
+ function typeBasedLayout(itemTypesMap, options) {
104
+ const itemName = options?.itemName ?? "item";
105
+ const result = Object.entries(itemTypesMap).map(([typeId, layout]) => {
106
+ const node = Array.isArray(layout) && layout.length === 1 ? layout[0] : layout;
107
+ return {
108
+ ...node,
109
+ $when: {
110
+ when: { from: `${itemName}._type` },
111
+ equals: typeId
112
+ }
113
+ };
114
+ });
115
+ return result;
116
+ }
117
+
118
+ // ../blocks/src/system/manifest/schema.ts
119
+ var visibilityLevels = ["admin", "designer", "author"];
120
+ var uiSchema = zod.z.object({
121
+ widget: zod.z.string().optional(),
122
+ flattenInRepeater: zod.z.boolean().optional(),
123
+ hidden: zod.z.boolean().optional(),
124
+ hideLabel: zod.z.boolean().optional(),
125
+ hideDescription: zod.z.boolean().optional(),
126
+ showCharCount: zod.z.union([zod.z.number().int().positive(), zod.z.object({ max: zod.z.number().int().positive() })]).optional(),
127
+ showSlugPreview: zod.z.boolean().optional(),
128
+ variant: zod.z.enum(["full", "inline", "limited", "media"]).optional(),
129
+ richTextVariant: zod.z.enum(["full", "inline", "limited", "media"]).optional(),
130
+ // Optional input hints for validators/widgets
131
+ inputType: zod.z.enum(["text", "email", "tel", "number"]).optional(),
132
+ min: zod.z.number().optional(),
133
+ max: zod.z.number().optional(),
134
+ step: zod.z.number().optional(),
135
+ pattern: zod.z.string().optional(),
136
+ placeholder: zod.z.string().optional(),
137
+ visibleWhen: zod.z.object({
138
+ field: zod.z.string().min(1),
139
+ equals: zod.z.any().optional(),
140
+ notEquals: zod.z.any().optional(),
141
+ oneOf: zod.z.array(zod.z.any()).optional(),
142
+ notIn: zod.z.array(zod.z.any()).optional()
143
+ }).optional(),
144
+ // Modal configuration for modal and group fields
145
+ modalConfig: zod.z.object({
146
+ buttonLabel: zod.z.string().optional(),
147
+ description: zod.z.string().optional(),
148
+ buttonVariant: zod.z.enum(["default", "outline"]).optional(),
149
+ showCustomizedIndicator: zod.z.boolean().optional(),
150
+ maxWidth: zod.z.string().optional()
151
+ }).optional(),
152
+ // Background field configuration
153
+ allowedTypes: zod.z.array(zod.z.enum(["color", "gradient", "image"])).optional(),
154
+ // Tab group configuration
155
+ fullWidth: zod.z.boolean().optional(),
156
+ // Field layout configuration
157
+ row: zod.z.string().optional(),
158
+ colSpan: zod.z.number().int().min(1).max(4).optional(),
159
+ // Group layout configuration
160
+ layout: zod.z.enum(["stack", "grid"]).optional(),
161
+ columns: zod.z.number().int().min(2).max(4).optional(),
162
+ // Entry picker configuration
163
+ contentTypeField: zod.z.string().optional()
164
+ }).partial();
165
+ var baseFieldSchema = zod.z.object({
166
+ id: zod.z.string().min(1, "Field id is required"),
167
+ label: zod.z.string().min(1, "Field label is required"),
168
+ description: zod.z.string().optional(),
169
+ visibleRoles: zod.z.array(zod.z.enum(visibilityLevels)).optional(),
170
+ defaultValue: zod.z.any().optional(),
171
+ required: zod.z.boolean().default(false),
172
+ ui: uiSchema.optional()
173
+ });
174
+ var textFieldSchema = baseFieldSchema.extend({
175
+ type: zod.z.literal("text"),
176
+ multiline: zod.z.boolean().default(false),
177
+ maxLength: zod.z.number().int().positive().optional()
178
+ });
179
+ var richTextFieldSchema = baseFieldSchema.extend({
180
+ type: zod.z.literal("richText"),
181
+ format: zod.z.enum(["markdown", "html"]).default("markdown")
182
+ });
183
+ var mediaFieldSchema = baseFieldSchema.extend({
184
+ type: zod.z.literal("media"),
185
+ mediaKinds: zod.z.array(zod.z.enum(["image", "video"])).default(["image"]),
186
+ aspectRatio: zod.z.string().optional()
187
+ });
188
+ var booleanFieldSchema = baseFieldSchema.extend({
189
+ type: zod.z.literal("boolean")
190
+ });
191
+ var dateFieldSchema = baseFieldSchema.extend({
192
+ type: zod.z.literal("date")
193
+ });
194
+ var timeFieldSchema = baseFieldSchema.extend({
195
+ type: zod.z.literal("time")
196
+ });
197
+ var dateTimeFieldSchema = baseFieldSchema.extend({
198
+ type: zod.z.literal("datetime")
199
+ });
200
+ var slugFieldSchema = baseFieldSchema.extend({
201
+ type: zod.z.literal("slug"),
202
+ sourceFieldId: zod.z.string().min(1).optional(),
203
+ statusFieldId: zod.z.string().min(1).optional(),
204
+ maxLength: zod.z.number().int().positive().optional()
205
+ });
206
+ var urlFieldSchema = baseFieldSchema.extend({
207
+ type: zod.z.literal("url"),
208
+ allowRelative: zod.z.boolean().default(false)
209
+ });
210
+ var linkFieldSchema = baseFieldSchema.extend({
211
+ type: zod.z.literal("link")
212
+ });
213
+ var selectFieldSchema = baseFieldSchema.extend({
214
+ type: zod.z.literal("select"),
215
+ options: zod.z.array(zod.z.object({
216
+ value: zod.z.string(),
217
+ label: zod.z.string()
218
+ })).min(1),
219
+ multiple: zod.z.boolean().default(false)
220
+ });
221
+ var referenceFieldSchema = baseFieldSchema.extend({
222
+ type: zod.z.literal("reference"),
223
+ referenceKind: zod.z.string().min(1),
224
+ allowManualEntry: zod.z.boolean().default(false)
225
+ });
226
+ var itemTypeSchema = zod.z.object({
227
+ label: zod.z.string().min(1),
228
+ icon: zod.z.string().optional(),
229
+ fields: zod.z.lazy(() => getFieldSchemaInternal().array().min(1, "Item type requires at least one field"))
230
+ });
231
+ var repeaterFieldSchema = baseFieldSchema.extend({
232
+ type: zod.z.literal("repeater"),
233
+ itemLabel: zod.z.string().default("Item"),
234
+ itemLabelSource: zod.z.string().optional(),
235
+ minItems: zod.z.number().int().min(0).default(0),
236
+ maxItems: zod.z.number().int().positive().optional(),
237
+ // Monomorphic mode
238
+ schema: zod.z.object({
239
+ fields: zod.z.lazy(() => getFieldSchemaInternal().array().min(1, "Repeater requires at least one field"))
240
+ }).optional(),
241
+ // Polymorphic mode
242
+ polymorphic: zod.z.boolean().optional(),
243
+ itemTypes: zod.z.record(zod.z.string(), itemTypeSchema).optional(),
244
+ allowConversion: zod.z.boolean().default(true)
245
+ }).refine(
246
+ (data) => {
247
+ const hasSchema = data.schema !== void 0;
248
+ const hasPolymorphic = data.polymorphic === true && data.itemTypes !== void 0;
249
+ return hasSchema !== hasPolymorphic;
250
+ },
251
+ {
252
+ message: "Repeater must have either 'schema' (monomorphic) or 'polymorphic: true' with 'itemTypes' (polymorphic)"
253
+ }
254
+ );
255
+ var groupFieldSchema = baseFieldSchema.extend({
256
+ type: zod.z.literal("group"),
257
+ schema: zod.z.object({
258
+ fields: zod.z.lazy(() => getFieldSchemaInternal().array().min(1, "Group requires at least one field"))
259
+ })
260
+ });
261
+ var modalFieldSchema = baseFieldSchema.extend({
262
+ type: zod.z.literal("modal"),
263
+ schema: zod.z.object({
264
+ fields: zod.z.lazy(() => getFieldSchemaInternal().array().min(1, "Modal requires at least one field"))
265
+ })
266
+ });
267
+ var numberFieldSchema = baseFieldSchema.extend({
268
+ type: zod.z.literal("number"),
269
+ min: zod.z.number().optional(),
270
+ max: zod.z.number().optional(),
271
+ step: zod.z.number().optional()
272
+ });
273
+ var tabDefinitionSchema = zod.z.object({
274
+ id: zod.z.string().min(1),
275
+ label: zod.z.string().min(1),
276
+ icon: zod.z.string().optional(),
277
+ description: zod.z.string().optional(),
278
+ fields: zod.z.lazy(() => getFieldSchemaInternal().array()),
279
+ /** SDK section option that controls tab visibility based on site config */
280
+ sdkSectionOption: zod.z.enum(["backgroundColor", "backgroundGradient", "backgroundImage"]).optional()
281
+ });
282
+ var tabGroupFieldSchema = baseFieldSchema.extend({
283
+ type: zod.z.literal("tabGroup"),
284
+ tabs: zod.z.array(tabDefinitionSchema).min(1, "TabGroup requires at least one tab"),
285
+ activeTabField: zod.z.string().optional()
286
+ });
287
+ var presetOptionSchema = zod.z.object({
288
+ value: zod.z.string(),
289
+ label: zod.z.string()
290
+ });
291
+ var presetOrCustomFieldSchema = baseFieldSchema.extend({
292
+ type: zod.z.literal("presetOrCustom"),
293
+ presets: zod.z.array(presetOptionSchema).min(1, "PresetOrCustom requires at least one preset"),
294
+ customInput: zod.z.object({
295
+ placeholder: zod.z.string().optional(),
296
+ pattern: zod.z.string().optional(),
297
+ helpText: zod.z.string().optional()
298
+ }).optional()
299
+ });
300
+ var contentTypeSelectFieldSchema = baseFieldSchema.extend({
301
+ type: zod.z.literal("contentTypeSelect"),
302
+ /** Filter: all, routable (hasPages=true), nonRoutable (hasPages=false) */
303
+ filter: zod.z.enum(["all", "routable", "nonRoutable"]).default("all")
304
+ });
305
+ var entryPickerFieldSchema = baseFieldSchema.extend({
306
+ type: zod.z.literal("entryPicker")
307
+ });
308
+ var _fieldSchemaInternal = null;
309
+ function getFieldSchemaInternal() {
310
+ if (_fieldSchemaInternal) {
311
+ return _fieldSchemaInternal;
312
+ }
313
+ _fieldSchemaInternal = zod.z.discriminatedUnion("type", [
314
+ textFieldSchema,
315
+ richTextFieldSchema,
316
+ mediaFieldSchema,
317
+ booleanFieldSchema,
318
+ numberFieldSchema,
319
+ dateFieldSchema,
320
+ timeFieldSchema,
321
+ dateTimeFieldSchema,
322
+ slugFieldSchema,
323
+ urlFieldSchema,
324
+ linkFieldSchema,
325
+ selectFieldSchema,
326
+ referenceFieldSchema,
327
+ repeaterFieldSchema,
328
+ groupFieldSchema,
329
+ modalFieldSchema,
330
+ tabGroupFieldSchema,
331
+ presetOrCustomFieldSchema,
332
+ contentTypeSelectFieldSchema,
333
+ entryPickerFieldSchema
334
+ ]);
335
+ return _fieldSchemaInternal;
336
+ }
337
+ var fieldSchema = new Proxy({}, {
338
+ get(_, prop) {
339
+ const schema = getFieldSchemaInternal();
340
+ const value = schema[prop];
341
+ return typeof value === "function" ? value.bind(schema) : value;
342
+ },
343
+ // Forward has checks to the real schema
344
+ has(_, prop) {
345
+ return prop in getFieldSchemaInternal();
346
+ }
347
+ });
348
+ var slotSchema = zod.z.object({
349
+ id: zod.z.string().min(1),
350
+ label: zod.z.string().min(1),
351
+ allowedKinds: zod.z.array(zod.z.string()).default([]),
352
+ min: zod.z.number().int().min(0).default(0),
353
+ max: zod.z.number().int().positive().optional(),
354
+ help: zod.z.string().optional(),
355
+ visibleRoles: zod.z.array(zod.z.enum(visibilityLevels)).optional()
356
+ });
357
+ var typographyTokens = ["display", "heading", "subheading", "body", "caption"];
358
+ var colorTokens = ["background", "surface", "foreground", "accent", "muted"];
359
+ var spacingTokens = ["none", "xs", "sm", "md", "lg", "xl"];
360
+ var radiusTokens = ["none", "sm", "md", "lg", "full"];
361
+ var styleTokenSchema = zod.z.object({
362
+ background: zod.z.enum(colorTokens).optional(),
363
+ foreground: zod.z.enum(colorTokens).optional(),
364
+ border: zod.z.enum(colorTokens).optional(),
365
+ typography: zod.z.enum(typographyTokens).optional(),
366
+ spacing: zod.z.enum(spacingTokens).optional(),
367
+ radius: zod.z.enum(radiusTokens).optional()
368
+ });
369
+ var behaviourSchema = zod.z.object({
370
+ supportsThemeSwitching: zod.z.boolean().default(true),
371
+ inlineEditing: zod.z.boolean().default(false),
372
+ animation: zod.z.boolean().default(false),
373
+ // Hide from block picker palettes (e.g., header/footer system blocks)
374
+ paletteHidden: zod.z.boolean().default(false)
375
+ });
376
+ var blockCategoryEnum = zod.z.enum(["marketing", "content", "blog", "media", "layout", "interactive"]);
377
+ zod.z.object({
378
+ name: zod.z.string().min(1),
379
+ version: zod.z.string().min(1),
380
+ title: zod.z.string().min(1),
381
+ titleSource: zod.z.string().optional(),
382
+ description: zod.z.string().optional(),
383
+ component: zod.z.string().min(1),
384
+ fields: fieldSchema.array().default([]),
385
+ slots: slotSchema.array().default([]),
386
+ styleTokens: styleTokenSchema.optional(),
387
+ behaviours: behaviourSchema.optional(),
388
+ layout: NodeSchema.optional(),
389
+ // Block variants system
390
+ variants: zod.z.record(zod.z.string(), NodeSchema).optional(),
391
+ defaultVariant: zod.z.string().optional(),
392
+ // Discovery metadata
393
+ category: blockCategoryEnum.optional(),
394
+ contentTypes: zod.z.array(zod.z.string()).optional(),
395
+ tags: zod.z.array(zod.z.string()).optional(),
396
+ icon: zod.z.string().optional()
397
+ });
398
+
399
+ // ../blocks/src/utils/env.ts
400
+ function isDevEnvironment() {
401
+ try {
402
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== void 0) {
403
+ return process.env.NODE_ENV !== "production";
404
+ }
405
+ return true;
406
+ } catch {
407
+ return true;
408
+ }
409
+ }
410
+
411
+ // ../blocks/src/system/node/types.ts
412
+ var RESERVED_KEYS = [
413
+ "children",
414
+ "key",
415
+ "$bind",
416
+ "$when",
417
+ "$repeat",
418
+ "$scopes"
419
+ ];
420
+ function findReservedKeysInProps(props2) {
421
+ if (!props2) return [];
422
+ return RESERVED_KEYS.filter((key) => key in props2);
423
+ }
424
+ function validateProps(props2, context = "node builder") {
425
+ const reserved = findReservedKeysInProps(props2);
426
+ if (reserved.length === 0) return;
427
+ const message = `[blocks:${context}] Reserved keys found in props: ${reserved.join(", ")}. These should be passed as separate parameters or modifiers, not in props. For example, 'children' should be the 3rd parameter to el(), not in the props object.`;
428
+ if (isDevEnvironment()) {
429
+ throw new TypeError(message);
430
+ } else {
431
+ console.error(message);
432
+ }
433
+ }
434
+
435
+ // ../blocks/src/system/node/builder.ts
436
+ function normalizeChildren(children) {
437
+ if (!children) return void 0;
438
+ const flat = children.flat().filter(Boolean);
439
+ return flat.length > 0 ? flat : void 0;
440
+ }
441
+ function el(type, props2, children, ...mods) {
442
+ validateProps(props2, `el('${type}', ...)`);
443
+ const node = {
444
+ type,
445
+ ...props2 && Object.keys(props2).length > 0 ? { props: props2 } : {},
446
+ ...children && children.length ? { children: normalizeChildren(children) } : {}
447
+ };
448
+ if (mods && mods.length > 0) {
449
+ const validMods = mods.filter((mod) => typeof mod === "function");
450
+ if (validMods.length > 0) {
451
+ const modified = validMods.reduce((acc, fn) => fn(acc), node);
452
+ return devValidate(modified);
453
+ }
454
+ }
455
+ return devValidate(node);
456
+ }
457
+ var section = (props2, children, ...mods) => el("section", props2, children, ...mods);
458
+ var headerSection = (props2, children, ...mods) => el("headerSection", props2, children, ...mods);
459
+ var stack = (props2, children, ...mods) => el("stack", props2, children, ...mods);
460
+ var inline = (props2, children, ...mods) => el("inline", props2, children, ...mods);
461
+ var accordion = (props2, children, ...mods) => el("accordion", props2, children, ...mods);
462
+ var accordionItem = (props2, children, ...mods) => el("accordionItem", props2, children, ...mods);
463
+ var carousel = (props2, children, ...mods) => el("carousel", props2, children, ...mods);
464
+ var text = (props2, ...mods) => el("text", props2, void 0, ...mods);
465
+ var richText = (props2, ...mods) => el("richText", props2, void 0, ...mods);
466
+ var media = (props2, ...mods) => el("media", props2, void 0, ...mods);
467
+ var button = (props2, children, ...mods) => el("button", props2, children, ...mods);
468
+ var link = (props2, children, ...mods) => el("link", props2, children, ...mods);
469
+ var form = (props2, children, ...mods) => el("form", props2, children, ...mods);
470
+ var bookingForm = (props2, children, ...mods) => el("booking-form", props2, children, ...mods);
471
+ var eventRegistration = (props2, children, ...mods) => el("event-registration", props2, children, ...mods);
472
+ function bind(from, options) {
473
+ return (node) => ({
474
+ ...node,
475
+ $bind: {
476
+ from,
477
+ ...options?.fallback !== void 0 ? { fallback: options.fallback } : {},
478
+ ...options?.transforms ? { transforms: options.transforms } : {},
479
+ ...options?.pick ? { pick: options.pick } : {}
480
+ }
481
+ });
482
+ }
483
+ function when(path, options) {
484
+ return (node) => ({
485
+ ...node,
486
+ $when: {
487
+ when: { from: path },
488
+ ...options?.equals !== void 0 ? { equals: options.equals } : {},
489
+ ...options?.not ? { not: true } : {}
490
+ }
491
+ });
492
+ }
493
+ function repeat(collectionPath, itemName = "item", options) {
494
+ return (node) => ({
495
+ ...node,
496
+ $repeat: {
497
+ collection: { from: collectionPath, pick: "collection" },
498
+ itemName,
499
+ ...options?.indexName ? { indexName: options.indexName } : {},
500
+ ...options?.limit ? { limit: options.limit } : {},
501
+ ...options?.sortBy ? { sortBy: { path: options.sortBy.path, direction: options.sortBy.direction ?? "asc" } } : {}
502
+ }
503
+ });
504
+ }
505
+ function props(extra) {
506
+ validateProps(extra, "props() modifier");
507
+ return (node) => ({
508
+ ...node,
509
+ props: { ...node.props ?? {}, ...extra }
510
+ });
511
+ }
512
+ function accordionList({
513
+ collection,
514
+ itemName = "item",
515
+ indexName,
516
+ accordionProps = null,
517
+ itemProps = null,
518
+ triggerFrom,
519
+ contentFrom,
520
+ valueFrom
521
+ }, ...mods) {
522
+ const resolvedIndexName = indexName;
523
+ const resolvedValuePath = valueFrom === null ? null : valueFrom ?? resolvedIndexName;
524
+ const baseItemProps = {
525
+ ...itemProps ?? {},
526
+ trigger: { $bind: { from: triggerFrom } }
527
+ };
528
+ {
529
+ baseItemProps.content = { $bind: { from: contentFrom } };
530
+ }
531
+ if (resolvedValuePath) {
532
+ baseItemProps.value = { $bind: { from: resolvedValuePath } };
533
+ }
534
+ return accordion(
535
+ accordionProps ?? void 0,
536
+ [
537
+ accordionItem(
538
+ baseItemProps,
539
+ void 0,
540
+ repeat(collection, itemName, { indexName: resolvedIndexName })
541
+ )
542
+ ],
543
+ ...mods
544
+ );
545
+ }
546
+ function devValidate(node) {
547
+ try {
548
+ if (isDevEnvironment()) {
549
+ const res = NodeSchema.safeParse(node);
550
+ if (!res.success) {
551
+ console.warn("[blocks:builder] Invalid node produced by builder", res.error.format());
552
+ }
553
+ }
554
+ } catch {
555
+ }
556
+ return node;
557
+ }
558
+
559
+ // ../blocks/src/system/node/fragments/backgroundLayer.ts
560
+ function backgroundLayer(path, options = {}) {
561
+ const {
562
+ styleClassName = "absolute inset-0 -z-10 h-full w-full pointer-events-none",
563
+ imageClassName
564
+ } = options;
565
+ const styleLayer = el("div", {
566
+ className: { $bind: { from: path, transforms: [{ id: "background.resolveClass", options: { baseClass: styleClassName } }] } },
567
+ style: { $bind: { from: path, transforms: [{ id: "background.resolveStyle" }] } }
568
+ });
569
+ const imageLayer = createBackgroundImageNode(path, imageClassName);
570
+ return [styleLayer, imageLayer];
571
+ }
572
+ function createBackgroundImageNode(path, baseClassName = "absolute -z-10") {
573
+ const imagePath = `${path}.image`;
574
+ return media(
575
+ {
576
+ className: {
577
+ $bind: {
578
+ from: path,
579
+ transforms: [{
580
+ id: "background.resolveImageClassName",
581
+ options: { baseClass: `background-image ${baseClassName}` }
582
+ }]
583
+ }
584
+ },
585
+ style: {
586
+ $bind: {
587
+ from: path,
588
+ transforms: [{ id: "background.resolveImageStyle" }]
589
+ }
590
+ }
591
+ },
592
+ when(imagePath),
593
+ bind(imagePath)
594
+ );
595
+ }
596
+
597
+ // ../blocks/src/theme/utils/colorStyles.ts
598
+ var COLOR_VAR_PREFIX = "--tb-";
599
+ function parseToken(source) {
600
+ if (source.startsWith("raw:")) {
601
+ return { token: source, raw: source.slice(4) };
602
+ }
603
+ if (source.includes("/")) {
604
+ const [token, opacity] = source.split("/");
605
+ const alpha = Number(opacity) / 100;
606
+ if (!Number.isNaN(alpha)) {
607
+ return { token, alpha };
608
+ }
609
+ return { token: source };
610
+ }
611
+ return { token: source };
612
+ }
613
+ function rgbColorValue(token) {
614
+ const { token: baseToken, alpha, raw } = parseToken(token);
615
+ if (raw) {
616
+ return raw;
617
+ }
618
+ const cssVar = `${COLOR_VAR_PREFIX}${baseToken}`;
619
+ if (alpha === void 0) {
620
+ return `rgb(var(${cssVar}))`;
621
+ }
622
+ return `rgba(var(${cssVar}), ${alpha})`;
623
+ }
624
+ function backgroundColorStyle(token) {
625
+ return { backgroundColor: rgbColorValue(token) };
626
+ }
627
+ function textColorStyle(token) {
628
+ return { color: rgbColorValue(token) };
629
+ }
630
+ function borderColorStyle(token) {
631
+ return { borderColor: rgbColorValue(token) };
632
+ }
633
+ function mergeStyles(...styles) {
634
+ const merged = styles.filter(Boolean).reduce((acc, style) => Object.assign(acc, style), {});
635
+ return Object.keys(merged).length ? merged : void 0;
636
+ }
637
+
638
+ // ../blocks/src/system/node/fragments/headingGroup.ts
639
+ function headingGroup(opts) {
640
+ const {
641
+ eyebrowPath,
642
+ titlePath,
643
+ containerClass = "text-center",
644
+ className,
645
+ eyebrowClass = "heading-eyebrow text-sm font-semibold tracking-wide",
646
+ titleClass = "heading-title text-3xl font-semibold sm:text-4xl",
647
+ eyebrowStyle = textColorStyle("neutral-500"),
648
+ titleStyle = textColorStyle("neutral-900")
649
+ } = opts;
650
+ const finalContainerClass = className ? `${containerClass} ${className}` : containerClass;
651
+ return stack({ gap: "sm", className: finalContainerClass }, [
652
+ eyebrowPath ? text({ as: "p", className: eyebrowClass, style: eyebrowStyle }, when(eyebrowPath), bind(eyebrowPath)) : null,
653
+ text({ as: "h2", className: titleClass, style: titleStyle }, bind(titlePath))
654
+ ]);
655
+ }
656
+
657
+ // ../blocks/src/system/node/fragments/sectionContainer.ts
658
+ function sectionContainer(children, opts) {
659
+ const gap = opts?.gap ?? "md";
660
+ const align = opts?.align;
661
+ const bindFrom = opts?.bindFrom ?? "_containerStyles";
662
+ const additionalClasses = opts?.className ?? "";
663
+ return stack(
664
+ {
665
+ gap,
666
+ className: {
667
+ $bind: {
668
+ from: bindFrom,
669
+ transforms: [
670
+ {
671
+ id: "containerStyles.resolveClassName",
672
+ options: { baseClass: additionalClasses }
673
+ }
674
+ ],
675
+ fallback: additionalClasses ? `container mx-auto ${additionalClasses}` : "container mx-auto"
676
+ }
677
+ },
678
+ ...align ? { align } : {}
679
+ },
680
+ children
681
+ );
682
+ }
683
+
684
+ // ../blocks/src/system/spacing.ts
685
+ var DEFAULT_SECTION_SPACING = "medium";
686
+
687
+ // ../blocks/src/system/node/fragments/styledSection.ts
688
+ function styledSection(config) {
689
+ const {
690
+ children,
691
+ baseClass = "px-6",
692
+ spacing = DEFAULT_SECTION_SPACING,
693
+ background = "background/base",
694
+ bindFrom = "_sectionStyles",
695
+ imageClassName = "absolute -z-10"
696
+ } = config;
697
+ const backgroundNodes = backgroundLayer(`${bindFrom}.background`, {
698
+ imageClassName
699
+ });
700
+ const childrenArray = Array.isArray(children) ? children : [children];
701
+ return section(
702
+ {
703
+ background,
704
+ className: {
705
+ $bind: {
706
+ from: bindFrom,
707
+ transforms: [
708
+ {
709
+ id: "sectionStyles.resolveClassName",
710
+ options: { baseClass, defaultSpacing: spacing }
711
+ }
712
+ ]
713
+ // No fallback needed - transform handles all cases via defaultSpacing
714
+ }
715
+ },
716
+ allowOverflow: {
717
+ $bind: {
718
+ from: `${bindFrom}.background.overflow`,
719
+ fallback: false
720
+ }
721
+ }
722
+ },
723
+ [
724
+ ...backgroundNodes,
725
+ ...childrenArray
726
+ ]
727
+ );
728
+ }
729
+
730
+ // ../blocks/src/system/node/fragments/ctaButton.ts
731
+ function ctaButton(opts) {
732
+ const base = opts?.basePath ?? "cta";
733
+ const linkPath = opts?.linkPath ?? `${base}.link`;
734
+ const labelPath = opts?.labelPath ?? `${base}.label`;
735
+ const variantPath = opts?.variantPath ?? `${base}.variant`;
736
+ const iconLeftPath = opts?.iconLeftPath ?? `${base}.iconLeft`;
737
+ const iconRightPath = opts?.iconRightPath ?? `${base}.iconRight`;
738
+ const whenPath = opts?.whenPath ?? labelPath;
739
+ const leftIcon = media({ className: "mr-2 h-4 w-4 inline-block" }, when(iconLeftPath), bind(iconLeftPath));
740
+ const rightIcon = media({ className: "ml-2 h-4 w-4 inline-block" }, when(iconRightPath), bind(iconRightPath));
741
+ const label = text({ as: "span" }, bind(labelPath));
742
+ const classNameObj = {
743
+ $bind: {
744
+ from: variantPath,
745
+ fallback: "primary"
746
+ },
747
+ $prepend: "button-"
748
+ };
749
+ if (opts?.className) {
750
+ classNameObj.$append = ` ${opts.className}`;
751
+ }
752
+ const node = button(
753
+ {
754
+ className: classNameObj,
755
+ href: { $bind: { from: linkPath, transforms: [{ id: "links.resolve" }], fallback: "#" } }
756
+ },
757
+ [leftIcon, label, rightIcon]
758
+ );
759
+ if (opts?.repeatFrom) {
760
+ return button(
761
+ node.props ?? {},
762
+ node.children,
763
+ repeat(opts.repeatFrom.collectionPath, opts.repeatFrom.itemName ?? base)
764
+ );
765
+ }
766
+ return button(node.props ?? {}, node.children, when(whenPath));
767
+ }
768
+
769
+ // ../blocks/src/system/node/fragments/ctaRow.ts
770
+ function ctaRow(opts) {
771
+ const collectionPath = opts?.collectionPath ?? "content.ctas";
772
+ const item = opts?.itemName ?? "cta";
773
+ const gap = opts?.gap ?? "sm";
774
+ const justify = opts?.justify ?? "center";
775
+ const containerClassName = ["cta-row flex-wrap", opts?.containerClassName].filter(Boolean).join(" ");
776
+ return inline(
777
+ { gap, justify, className: containerClassName },
778
+ [
779
+ ctaButton({ basePath: item, repeatFrom: { collectionPath, itemName: item } })
780
+ ],
781
+ when(collectionPath)
782
+ );
783
+ }
784
+
785
+ // ../blocks/src/system/node/fragments/navRow.ts
786
+ function navRow(opts) {
787
+ const collectionPath = opts?.collectionPath ?? "menu.items";
788
+ const itemName = opts?.itemName ?? "navItem";
789
+ const gap = opts?.gap ?? "md";
790
+ const align = opts?.align ?? "end";
791
+ const className = ["items-center flex-wrap", opts?.className].filter(Boolean).join(" ").trim();
792
+ const linkClassName = opts?.linkClassName ?? "header-nav-link inline-flex items-center px-4 py-2 text-sm font-medium transition-theme-standard";
793
+ const links = link(
794
+ {
795
+ className: linkClassName,
796
+ href: { $bind: { from: `${itemName}.link`, transforms: [{ id: "links.resolve" }], fallback: "#" } },
797
+ target: { $bind: { from: `${itemName}.target` } },
798
+ rel: { $bind: { from: `${itemName}.rel` } },
799
+ "data-active": { $bind: { from: `${itemName}.active` } }
800
+ },
801
+ [text({ as: "span" }, bind(`${itemName}.label`))],
802
+ repeat(collectionPath, itemName)
803
+ );
804
+ return inline(
805
+ { gap, align, className },
806
+ [links],
807
+ when(collectionPath)
808
+ );
809
+ }
810
+ var FRAGMENT_ID_PATTERN = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/i;
811
+ var FIELD_ID_PATTERN = /^[a-z][a-zA-Z0-9_-]*$/;
812
+ var dataLoaderSchema = zod.z.object({
813
+ endpoint: zod.z.string().min(1, "Fragment data loader requires an endpoint"),
814
+ params: zod.z.record(zod.z.string(), zod.z.unknown()).default({}),
815
+ mode: zod.z.enum(["server", "client"]).default("server")
816
+ });
817
+ var fragmentDataSchema = zod.z.object({
818
+ key: zod.z.string().min(1, "Fragment data key is required"),
819
+ loader: dataLoaderSchema.optional()
820
+ });
821
+ var fragmentConfigSchema = zod.z.object({
822
+ id: zod.z.string().min(1, "Fragment id is required").regex(FRAGMENT_ID_PATTERN, "Fragment id must be alphanumeric with optional . _ - separators"),
823
+ title: zod.z.string().optional(),
824
+ description: zod.z.string().optional(),
825
+ category: zod.z.enum(["content", "media", "interactive", "layout"]).optional(),
826
+ icon: zod.z.string().optional(),
827
+ fields: fieldSchema.array().default([]),
828
+ layout: zod.z.union([NodeSchema, NodeSchema.array()]).transform((value) => Array.isArray(value) ? value : [value]),
829
+ data: fragmentDataSchema.optional()
830
+ });
831
+ var FragmentConfigError = class extends Error {
832
+ constructor(message) {
833
+ super(message);
834
+ this.name = "FragmentConfigError";
835
+ }
836
+ };
837
+ function defineFragment(config) {
838
+ const parsed = fragmentConfigSchema.parse(config);
839
+ validateFieldDefinitions(parsed.fields, parsed.id);
840
+ return {
841
+ ...parsed,
842
+ fields: parsed.fields.map(cloneFieldDefinition),
843
+ layout: parsed.layout.map((node) => ({ ...node }))
844
+ };
845
+ }
846
+ function scopeFragmentFields(fragment, scope) {
847
+ const normalizedScope = scope.trim();
848
+ if (!normalizedScope) {
849
+ return fragment.fields.map(cloneFieldDefinition);
850
+ }
851
+ return fragment.fields.map((field) => prefixFieldId(field, normalizedScope));
852
+ }
853
+ function scopeFragmentLayout(fragment, scope) {
854
+ const normalizedScope = scope.trim();
855
+ return fragment.layout.map((node) => cloneAndScopeNode(node, normalizedScope));
856
+ }
857
+ function validateFieldDefinitions(fields4, fragmentId) {
858
+ const seen = /* @__PURE__ */ new Set();
859
+ for (const field of fields4) {
860
+ if (field.id.includes(".")) {
861
+ throw new FragmentConfigError(
862
+ `Field "${field.id}" in fragment "${fragmentId}" should not include dot notation; the builder scopes fields automatically.`
863
+ );
864
+ }
865
+ if (!FIELD_ID_PATTERN.test(field.id)) {
866
+ throw new FragmentConfigError(
867
+ `Field "${field.id}" in fragment "${fragmentId}" must start with a letter and contain only alphanumeric, "_", or "-".`
868
+ );
869
+ }
870
+ if (seen.has(field.id)) {
871
+ throw new FragmentConfigError(
872
+ `Duplicate field id "${field.id}" found in fragment "${fragmentId}". Field ids must be unique per fragment.`
873
+ );
874
+ }
875
+ seen.add(field.id);
876
+ }
877
+ }
878
+ function prefixFieldId(field, scope) {
879
+ const cloned = cloneFieldDefinition(field);
880
+ cloned.id = `${scope}.${field.id}`;
881
+ cloned.defaultValue = cloneUnknown(cloned.defaultValue);
882
+ cloned.visibleRoles = cloned.visibleRoles ? [...cloned.visibleRoles] : void 0;
883
+ cloned.ui = cloneUnknown(cloned.ui);
884
+ return cloned;
885
+ }
886
+ function cloneFieldDefinition(field) {
887
+ switch (field.type) {
888
+ case "repeater":
889
+ if (field.polymorphic && field.itemTypes) {
890
+ return {
891
+ ...field,
892
+ polymorphic: true,
893
+ itemTypes: Object.fromEntries(
894
+ Object.entries(field.itemTypes).map(([key, itemType]) => [
895
+ key,
896
+ {
897
+ ...itemType,
898
+ fields: itemType.fields.map(cloneFieldDefinition)
899
+ }
900
+ ])
901
+ )
902
+ };
903
+ } else if (field.schema) {
904
+ return {
905
+ ...field,
906
+ schema: {
907
+ fields: field.schema.fields.map(cloneFieldDefinition)
908
+ }
909
+ };
910
+ }
911
+ return { ...field };
912
+ case "group":
913
+ return {
914
+ ...field,
915
+ schema: {
916
+ fields: field.schema.fields.map(cloneFieldDefinition)
917
+ }
918
+ };
919
+ default:
920
+ return { ...field };
921
+ }
922
+ }
923
+ function cloneUnknown(value) {
924
+ if (value == null || typeof value !== "object") {
925
+ return value;
926
+ }
927
+ if (Array.isArray(value)) {
928
+ return value.map((entry) => cloneUnknown(entry));
929
+ }
930
+ return { ...value };
931
+ }
932
+ function cloneAndScopeNode(node, scope) {
933
+ const cloned = {
934
+ ...node
935
+ };
936
+ if (node.children) {
937
+ cloned.children = node.children.map((child) => cloneAndScopeNode(child, scope));
938
+ }
939
+ if (node.props) {
940
+ cloned.props = scopePropBindings(node.props, scope);
941
+ }
942
+ if (node.$bind) {
943
+ cloned.$bind = scopeBinding(node.$bind, scope);
944
+ }
945
+ if (node.$repeat) {
946
+ cloned.$repeat = scopeRepeat(node.$repeat, scope);
947
+ }
948
+ if (node.$when) {
949
+ cloned.$when = scopeCondition(node.$when, scope);
950
+ }
951
+ if (node.$scopes) {
952
+ cloned.$scopes = node.$scopes.map((entry) => ({
953
+ name: entry.name,
954
+ from: scopeBinding(entry.from, scope)
955
+ }));
956
+ }
957
+ return cloned;
958
+ }
959
+ function scopeBinding(binding, scope) {
960
+ const cloned = {
961
+ ...binding,
962
+ transforms: binding.transforms?.map(
963
+ (step) => ({ ...step })
964
+ ) ?? []
965
+ };
966
+ cloned.from = scopeContentPath(binding.from, scope);
967
+ return cloned;
968
+ }
969
+ function scopeRepeat(repeat2, scope) {
970
+ const cloned = {
971
+ ...repeat2,
972
+ collection: scopeBinding(repeat2.collection, scope)
973
+ };
974
+ if (repeat2.sortBy) {
975
+ cloned.sortBy = {
976
+ ...repeat2.sortBy,
977
+ path: scopeContentPath(repeat2.sortBy.path, scope)
978
+ };
979
+ }
980
+ return cloned;
981
+ }
982
+ function scopeCondition(condition, scope) {
983
+ return {
984
+ ...condition,
985
+ when: scopeBinding(condition.when, scope)
986
+ };
987
+ }
988
+ function scopePropBindings(input, scope) {
989
+ const result = {};
990
+ for (const [key, value] of Object.entries(input)) {
991
+ result[key] = scopePropValue(value, scope);
992
+ }
993
+ return result;
994
+ }
995
+ function scopePropValue(value, scope) {
996
+ if (Array.isArray(value)) {
997
+ return value.map((entry) => scopePropValue(entry, scope));
998
+ }
999
+ if (value && typeof value === "object") {
1000
+ if ("$bind" in value) {
1001
+ const bindingConfig = value.$bind;
1002
+ return {
1003
+ $bind: scopeBinding(bindingConfig, scope)
1004
+ };
1005
+ }
1006
+ const result = {};
1007
+ for (const [key, entry] of Object.entries(value)) {
1008
+ result[key] = scopePropValue(entry, scope);
1009
+ }
1010
+ return result;
1011
+ }
1012
+ return value;
1013
+ }
1014
+ function scopeContentPath(path, scope) {
1015
+ if (!scope || scope.length === 0) {
1016
+ return path;
1017
+ }
1018
+ if (path === "content") {
1019
+ return `content.${scope}`;
1020
+ }
1021
+ if (path.startsWith("content.")) {
1022
+ const remainder = path.slice("content.".length);
1023
+ return remainder.length > 0 ? `content.${scope}.${remainder}` : `content.${scope}`;
1024
+ }
1025
+ if (path.startsWith("content[")) {
1026
+ return path.replace(/^content/, `content.${scope}`);
1027
+ }
1028
+ if (path.startsWith("$root.")) {
1029
+ return path;
1030
+ }
1031
+ if (path.includes(".")) {
1032
+ return path;
1033
+ }
1034
+ return `content.${scope}.${path}`;
1035
+ }
1036
+
1037
+ // ../blocks/src/system/fragments/builder.ts
1038
+ var FragmentCompositionError = class extends Error {
1039
+ constructor(message) {
1040
+ super(message);
1041
+ this.name = "FragmentCompositionError";
1042
+ }
1043
+ };
1044
+ var DOT_SCOPE_REGEX = /^[A-Za-z0-9_.-]*$/;
1045
+ function materializeFragment(config) {
1046
+ const scope = normalizeScope(config.scope);
1047
+ const fields4 = scopeFragmentFields(config.fragment, scope);
1048
+ const layout = scopeFragmentLayout(config.fragment, scope);
1049
+ const fieldPriority = config.fieldPriority ?? 0;
1050
+ let data;
1051
+ if (config.fragment.data) {
1052
+ const key = config.dataKey ?? config.fragment.data.key;
1053
+ if (!key || typeof key !== "string") {
1054
+ throw new FragmentCompositionError(
1055
+ `Fragment "${config.fragment.id}" requires a data key to compose.`
1056
+ );
1057
+ }
1058
+ data = {
1059
+ key,
1060
+ loader: config.fragment.data.loader ? { ...config.fragment.data.loader } : void 0
1061
+ };
1062
+ }
1063
+ return {
1064
+ fragment: config.fragment,
1065
+ scope,
1066
+ fields: fields4,
1067
+ layout,
1068
+ data,
1069
+ fieldPriority
1070
+ };
1071
+ }
1072
+ function composeFragments(instances) {
1073
+ const fieldEntries = [];
1074
+ const layouts = [];
1075
+ const dataEntries = [];
1076
+ const scopes = [];
1077
+ const seenFieldIds = /* @__PURE__ */ new Set();
1078
+ let fieldOrder = 0;
1079
+ for (const instance of instances) {
1080
+ const materialized = materializeFragment(instance);
1081
+ for (const field of materialized.fields) {
1082
+ if (seenFieldIds.has(field.id)) {
1083
+ throw new FragmentCompositionError(
1084
+ `Duplicate field id "${field.id}" when composing fragments.`
1085
+ );
1086
+ }
1087
+ seenFieldIds.add(field.id);
1088
+ fieldEntries.push({
1089
+ id: field.id,
1090
+ field,
1091
+ priority: materialized.fieldPriority,
1092
+ order: fieldOrder++
1093
+ });
1094
+ }
1095
+ scopes.push(materialized.scope);
1096
+ layouts.push(...materialized.layout);
1097
+ if (materialized.data) {
1098
+ if (dataEntries.find((entry) => entry.key === materialized.data?.key)) {
1099
+ throw new FragmentCompositionError(
1100
+ `Duplicate fragment data key "${materialized.data.key}" detected.`
1101
+ );
1102
+ }
1103
+ dataEntries.push(materialized.data);
1104
+ }
1105
+ }
1106
+ return {
1107
+ fields: fieldEntries.sort((a, b) => {
1108
+ if (a.priority !== b.priority) {
1109
+ return a.priority - b.priority;
1110
+ }
1111
+ return a.order - b.order;
1112
+ }).map((entry) => entry.field),
1113
+ layout: layouts,
1114
+ data: dataEntries,
1115
+ scopes
1116
+ };
1117
+ }
1118
+ function normalizeScope(scope) {
1119
+ if (!scope) return "";
1120
+ const trimmed = scope.trim();
1121
+ if (!DOT_SCOPE_REGEX.test(trimmed)) {
1122
+ throw new FragmentCompositionError(
1123
+ `Fragment scope "${scope}" contains invalid characters. Use alphanumeric, ".", "-", or "_".`
1124
+ );
1125
+ }
1126
+ return trimmed;
1127
+ }
1128
+ function buildFragmentDataLoaders(composition2) {
1129
+ const loaders = {};
1130
+ for (const entry of composition2.data) {
1131
+ if (!entry?.loader) continue;
1132
+ loaders[entry.key] = {
1133
+ endpoint: entry.loader.endpoint,
1134
+ params: { ...entry.loader.params ?? {} },
1135
+ mode: entry.loader.mode ?? "server"
1136
+ };
1137
+ }
1138
+ return loaders;
1139
+ }
1140
+
1141
+ // ../blocks/src/system/fragments/library/bodyCopy.ts
1142
+ var bodyCopyFragment = defineFragment({
1143
+ id: "bodyCopy",
1144
+ title: "Body Copy",
1145
+ description: "Heading, rich text body, and alignment controls.",
1146
+ fields: [
1147
+ {
1148
+ id: "heading",
1149
+ type: "text",
1150
+ label: "Heading",
1151
+ description: "Optional heading displayed above the body copy."
1152
+ },
1153
+ {
1154
+ id: "body",
1155
+ type: "richText",
1156
+ label: "Body",
1157
+ required: true
1158
+ },
1159
+ {
1160
+ id: "alignment",
1161
+ type: "select",
1162
+ label: "Text alignment",
1163
+ required: false,
1164
+ defaultValue: "left",
1165
+ options: [
1166
+ { value: "left", label: "Left" },
1167
+ { value: "center", label: "Center" }
1168
+ ]
1169
+ }
1170
+ ],
1171
+ layout: stack(
1172
+ {
1173
+ gap: "md",
1174
+ className: "mx-auto max-w-3xl",
1175
+ align: {
1176
+ $bind: {
1177
+ from: "content.alignment",
1178
+ transforms: [{ id: "ui.stackAlignFromAlignment" }],
1179
+ fallback: "start"
1180
+ }
1181
+ }
1182
+ },
1183
+ [
1184
+ text(
1185
+ {
1186
+ as: "h2",
1187
+ className: {
1188
+ $bind: {
1189
+ from: "content.alignment",
1190
+ transforms: [
1191
+ {
1192
+ id: "ui.headingClassFromAlignment",
1193
+ options: {
1194
+ base: "text-3xl font-semibold sm:text-4xl"
1195
+ }
1196
+ }
1197
+ ],
1198
+ fallback: "text-3xl font-semibold sm:text-4xl"
1199
+ }
1200
+ },
1201
+ style: textColorStyle("neutral-900")
1202
+ },
1203
+ when("content.heading"),
1204
+ bind("content.heading")
1205
+ ),
1206
+ richText(
1207
+ {
1208
+ className: {
1209
+ $bind: {
1210
+ from: "content.alignment",
1211
+ transforms: [
1212
+ {
1213
+ id: "ui.bodyClassFromAlignment",
1214
+ options: {
1215
+ base: "prose prose-lg max-w-none sm:prose-xl"
1216
+ }
1217
+ }
1218
+ ],
1219
+ fallback: "prose prose-lg max-w-none sm:prose-xl"
1220
+ },
1221
+ style: textColorStyle("neutral-600")
1222
+ }
1223
+ },
1224
+ bind("content.body")
1225
+ )
1226
+ ]
1227
+ )
1228
+ });
1229
+
1230
+ // ../blocks/src/system/fragments/library/heroCopy.ts
1231
+ var heroCopyFragment = defineFragment({
1232
+ id: "heroCopy",
1233
+ title: "Hero Copy",
1234
+ description: "Eyebrow, headline, and subheadline copy for hero layouts.",
1235
+ fields: [
1236
+ {
1237
+ id: "eyebrow",
1238
+ type: "text",
1239
+ label: "Eyebrow",
1240
+ description: "Optional short line above the headline",
1241
+ visibleRoles: ["designer", "admin"]
1242
+ },
1243
+ {
1244
+ id: "headline",
1245
+ type: "text",
1246
+ label: "Headline",
1247
+ required: true,
1248
+ maxLength: 120
1249
+ },
1250
+ {
1251
+ id: "subheadline",
1252
+ type: "text",
1253
+ label: "Subheadline",
1254
+ multiline: true,
1255
+ maxLength: 280
1256
+ }
1257
+ ],
1258
+ layout: [
1259
+ text(
1260
+ {
1261
+ as: "p",
1262
+ className: "hero-eyebrow text-sm font-semibold tracking-wide",
1263
+ style: textColorStyle("neutral-500")
1264
+ },
1265
+ when("content.eyebrow"),
1266
+ bind("content.eyebrow")
1267
+ ),
1268
+ text(
1269
+ {
1270
+ as: "h1",
1271
+ className: "hero-headline text-4xl font-semibold sm:text-5xl md:text-6xl",
1272
+ style: textColorStyle("neutral-900")
1273
+ },
1274
+ bind("content.headline")
1275
+ ),
1276
+ text(
1277
+ {
1278
+ as: "p",
1279
+ className: "hero-subheadline text-lg sm:text-xl",
1280
+ style: textColorStyle("neutral-600")
1281
+ },
1282
+ when("content.subheadline"),
1283
+ bind("content.subheadline")
1284
+ )
1285
+ ]
1286
+ });
1287
+
1288
+ // ../blocks/src/system/fields/button.ts
1289
+ function createButtonGroup(options = {}) {
1290
+ const {
1291
+ variants = [
1292
+ { value: "primary", label: "Primary" },
1293
+ { value: "secondary", label: "Secondary" },
1294
+ { value: "outline", label: "Outline" }
1295
+ ],
1296
+ showGroupLabel = false,
1297
+ groupId = "button",
1298
+ groupLabel = "Button",
1299
+ flattenInRepeater = true
1300
+ } = options;
1301
+ const mainFields = [
1302
+ {
1303
+ id: "label",
1304
+ type: "text",
1305
+ label: "Label",
1306
+ required: true,
1307
+ multiline: false,
1308
+ maxLength: 40,
1309
+ ui: { row: "primary" }
1310
+ },
1311
+ {
1312
+ id: "variant",
1313
+ type: "select",
1314
+ label: "Style",
1315
+ options: variants,
1316
+ required: false,
1317
+ multiple: false,
1318
+ ui: { row: "primary" }
1319
+ },
1320
+ {
1321
+ id: "link",
1322
+ type: "link",
1323
+ label: "Link",
1324
+ required: true,
1325
+ ui: { colSpan: 2 }
1326
+ }
1327
+ ];
1328
+ const iconsGroup = {
1329
+ id: "icons",
1330
+ type: "group",
1331
+ label: "Icons",
1332
+ required: false,
1333
+ ui: { preset: "disclosure", colSpan: 2 },
1334
+ schema: {
1335
+ fields: [
1336
+ { id: "iconLeft", type: "media", label: "Left icon", required: false, mediaKinds: ["image"] },
1337
+ { id: "iconRight", type: "media", label: "Right icon", required: false, mediaKinds: ["image"] }
1338
+ ]
1339
+ }
1340
+ };
1341
+ return {
1342
+ id: groupId,
1343
+ type: "group",
1344
+ label: groupLabel,
1345
+ ui: { layout: "grid", columns: 2, flattenInRepeater, hideLabel: !showGroupLabel },
1346
+ schema: { fields: [...mainFields, iconsGroup] },
1347
+ required: false
1348
+ };
1349
+ }
1350
+
1351
+ // ../blocks/src/system/fields/ctas.ts
1352
+ function createCtasRepeater(options = {}) {
1353
+ const {
1354
+ id = "ctas",
1355
+ label = "Calls to action",
1356
+ itemLabel = "CTA",
1357
+ minItems = 0,
1358
+ maxItems
1359
+ } = options;
1360
+ return {
1361
+ id,
1362
+ type: "repeater",
1363
+ label,
1364
+ itemLabel,
1365
+ itemLabelSource: "label",
1366
+ minItems,
1367
+ maxItems,
1368
+ schema: {
1369
+ fields: [createButtonGroup({ showGroupLabel: false, flattenInRepeater: true })]
1370
+ }
1371
+ };
1372
+ }
1373
+
1374
+ // ../blocks/src/system/fragments/library/ctaRow.ts
1375
+ var ctaRowFragment = defineFragment({
1376
+ id: "ctaRow",
1377
+ title: "CTA Row",
1378
+ description: "Repeatable calls to action rendered inline.",
1379
+ fields: [createCtasRepeater({ label: "Calls to action", itemLabel: "CTA", maxItems: 3 })],
1380
+ layout: [
1381
+ ctaRow({ collectionPath: "content.ctas", itemName: "cta", justify: "center" })
1382
+ ]
1383
+ });
1384
+
1385
+ // ../blocks/src/system/fragments/library/ctaCopy.ts
1386
+ var ctaCopyFragment = defineFragment({
1387
+ id: "ctaCopy",
1388
+ title: "CTA Copy",
1389
+ description: "Eyebrow, title, and supporting rich text for CTA layouts.",
1390
+ fields: [
1391
+ {
1392
+ id: "eyebrow",
1393
+ type: "text",
1394
+ label: "Eyebrow",
1395
+ description: "Optional short line above the title",
1396
+ visibleRoles: ["designer", "admin"]
1397
+ },
1398
+ {
1399
+ id: "title",
1400
+ type: "text",
1401
+ label: "Title",
1402
+ required: true,
1403
+ maxLength: 120
1404
+ },
1405
+ {
1406
+ id: "content",
1407
+ type: "richText",
1408
+ label: "Copy",
1409
+ description: "Short paragraph of supporting copy."
1410
+ }
1411
+ ],
1412
+ layout: [
1413
+ headingGroup({ eyebrowPath: "content.eyebrow", titlePath: "content.title", className: "cta-heading" }),
1414
+ richText(
1415
+ {
1416
+ className: "cta-content prose prose-lg mx-auto max-w-none sm:prose-xl",
1417
+ style: textColorStyle("neutral-700")
1418
+ },
1419
+ when("content.content"),
1420
+ bind("content.content")
1421
+ )
1422
+ ]
1423
+ });
1424
+
1425
+ // ../blocks/src/system/fragments/library/testimonialsHeading.ts
1426
+ var testimonialsHeadingFragment = defineFragment({
1427
+ id: "testimonialsHeading",
1428
+ title: "Testimonials Heading",
1429
+ description: "Heading and subheading copy for testimonials section.",
1430
+ fields: [
1431
+ {
1432
+ id: "heading",
1433
+ type: "text",
1434
+ label: "Heading",
1435
+ description: "Optional block heading displayed above the testimonials."
1436
+ },
1437
+ {
1438
+ id: "subheading",
1439
+ type: "text",
1440
+ label: "Description",
1441
+ multiline: true,
1442
+ description: "Optional supporting copy shown below the heading."
1443
+ }
1444
+ ],
1445
+ layout: [
1446
+ stack(
1447
+ { gap: "md", className: "mx-auto max-w-2xl text-center" },
1448
+ [
1449
+ text(
1450
+ { as: "h2", className: "text-3xl font-semibold sm:text-4xl", style: textColorStyle("neutral-900") },
1451
+ when("content.heading"),
1452
+ bind("content.heading")
1453
+ ),
1454
+ text(
1455
+ { as: "p", className: "text-lg sm:text-xl", style: textColorStyle("neutral-600") },
1456
+ when("content.subheading"),
1457
+ bind("content.subheading")
1458
+ )
1459
+ ]
1460
+ )
1461
+ ]
1462
+ });
1463
+
1464
+ // ../blocks/src/system/fragments/library/testimonialsCarousel.ts
1465
+ var testimonialsCarouselFragment = defineFragment({
1466
+ id: "testimonialsCarousel",
1467
+ title: "Testimonials Carousel",
1468
+ description: "Carousel layout for testimonial cards with data loader configuration.",
1469
+ fields: [
1470
+ {
1471
+ id: "slidesToShow",
1472
+ type: "select",
1473
+ label: "Testimonials per view",
1474
+ defaultValue: "1",
1475
+ options: [
1476
+ { value: "1", label: "One at a time" },
1477
+ { value: "2", label: "Two at a time" },
1478
+ { value: "3", label: "Three at a time" }
1479
+ ]
1480
+ },
1481
+ {
1482
+ id: "transition",
1483
+ type: "select",
1484
+ label: "Transition",
1485
+ defaultValue: "slide",
1486
+ options: [
1487
+ { value: "slide", label: "Slide" },
1488
+ { value: "fade", label: "Fade" }
1489
+ ]
1490
+ },
1491
+ {
1492
+ id: "maxEntries",
1493
+ type: "select",
1494
+ label: "Testimonials to load",
1495
+ defaultValue: "6",
1496
+ options: [
1497
+ { value: "3", label: "3 testimonials" },
1498
+ { value: "6", label: "6 testimonials" },
1499
+ { value: "9", label: "9 testimonials" }
1500
+ ]
1501
+ }
1502
+ ],
1503
+ layout: [
1504
+ carousel(
1505
+ {
1506
+ className: "mt-12",
1507
+ slidesToShow: { $bind: { from: "slidesToShow", fallback: 1 } },
1508
+ transition: { $bind: { from: "transition", fallback: "slide" } },
1509
+ showControls: true
1510
+ },
1511
+ [
1512
+ stack(
1513
+ {
1514
+ gap: "lg",
1515
+ className: "h-full justify-between rounded-2xl border p-8 shadow-sm",
1516
+ style: mergeStyles(
1517
+ borderColorStyle("neutral-200"),
1518
+ backgroundColorStyle("surface")
1519
+ )
1520
+ },
1521
+ [
1522
+ richText(
1523
+ {
1524
+ className: "prose prose-lg max-w-none sm:prose-xl",
1525
+ style: textColorStyle("neutral-700")
1526
+ },
1527
+ bind("testimonial.content.body"),
1528
+ when("testimonial.content.body")
1529
+ ),
1530
+ inline(
1531
+ { gap: "md", className: "mt-6 items-center" },
1532
+ [
1533
+ media(
1534
+ { className: "h-12 w-12 shrink-0 rounded-full object-cover" },
1535
+ bind("testimonial.content.headshot", { transforms: [{ id: "media.fromUrl" }] }),
1536
+ when("testimonial.content.headshot")
1537
+ ),
1538
+ stack(
1539
+ { gap: "xs" },
1540
+ [
1541
+ text(
1542
+ { as: "p", className: "font-semibold", style: textColorStyle("neutral-900") },
1543
+ bind("testimonial.content.name", { fallback: "Anonymous" })
1544
+ ),
1545
+ text(
1546
+ { as: "p", className: "text-sm", style: textColorStyle("neutral-500") },
1547
+ when("testimonial.content.jobTitle"),
1548
+ bind("testimonial.content.jobTitle")
1549
+ ),
1550
+ text(
1551
+ { as: "p", className: "text-sm", style: textColorStyle("neutral-500") },
1552
+ when("testimonial.content.company"),
1553
+ bind("testimonial.content.company")
1554
+ )
1555
+ ]
1556
+ )
1557
+ ]
1558
+ )
1559
+ ],
1560
+ repeat("data.entries", "testimonial")
1561
+ )
1562
+ ]
1563
+ )
1564
+ ],
1565
+ data: {
1566
+ key: "entries",
1567
+ loader: {
1568
+ endpoint: "listPublishedEntries",
1569
+ params: {
1570
+ type: "testimonial",
1571
+ siteId: { $bind: { from: "$root.siteId" } },
1572
+ limit: { $bind: { from: "maxEntries", fallback: "6" } },
1573
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
1574
+ },
1575
+ mode: "server"
1576
+ }
1577
+ }
1578
+ });
1579
+
1580
+ // ../blocks/src/system/fragments/library/formCopy.ts
1581
+ var formCopyFragment = defineFragment({
1582
+ id: "formCopy",
1583
+ title: "Form Copy",
1584
+ description: "Optional title and introduction content for form blocks.",
1585
+ fields: [
1586
+ {
1587
+ id: "title",
1588
+ type: "text",
1589
+ label: "Title"
1590
+ },
1591
+ {
1592
+ id: "intro",
1593
+ type: "richText",
1594
+ label: "Intro",
1595
+ description: "Optional introduction above the form."
1596
+ }
1597
+ ],
1598
+ layout: [
1599
+ text(
1600
+ {
1601
+ as: "h2",
1602
+ className: "text-2xl font-semibold",
1603
+ style: textColorStyle("text")
1604
+ },
1605
+ when("content.title"),
1606
+ bind("content.title")
1607
+ ),
1608
+ richText(
1609
+ {
1610
+ className: "text-base",
1611
+ style: textColorStyle("text")
1612
+ },
1613
+ when("content.intro"),
1614
+ bind("content.intro")
1615
+ )
1616
+ ]
1617
+ });
1618
+
1619
+ // ../blocks/src/system/fragments/library/formEmbed.ts
1620
+ var formEmbedFragment = defineFragment({
1621
+ id: "formEmbed",
1622
+ title: "Form Embed",
1623
+ description: "Embeds a saved form with configurable submit button copy.",
1624
+ fields: [
1625
+ {
1626
+ id: "formId",
1627
+ type: "reference",
1628
+ label: "Form",
1629
+ description: "Pick a saved form to render.",
1630
+ required: true,
1631
+ referenceKind: "form",
1632
+ allowManualEntry: false
1633
+ },
1634
+ {
1635
+ id: "submitLabel",
1636
+ type: "text",
1637
+ label: "Submit button label",
1638
+ defaultValue: "Submit"
1639
+ },
1640
+ {
1641
+ id: "successMessage",
1642
+ type: "text",
1643
+ label: "Success message",
1644
+ description: "Shown after successful submit (handled by page or block)."
1645
+ }
1646
+ ],
1647
+ layout: [
1648
+ form(
1649
+ {
1650
+ submitLabel: { $bind: { from: "submitLabel" } },
1651
+ successMessage: { $bind: { from: "successMessage" } }
1652
+ },
1653
+ [],
1654
+ bind("data.form")
1655
+ )
1656
+ ],
1657
+ data: {
1658
+ key: "form",
1659
+ loader: {
1660
+ endpoint: "getPublicFormById",
1661
+ params: {
1662
+ formId: { $bind: { from: "formId" } }
1663
+ },
1664
+ mode: "server"
1665
+ }
1666
+ }
1667
+ });
1668
+
1669
+ // ../blocks/src/system/fragments/library/footerBottomText.ts
1670
+ var footerBottomTextFragment = defineFragment({
1671
+ id: "footerBottomText",
1672
+ title: "Footer Bottom Text",
1673
+ description: "Rich text content displayed at the bottom of the footer.",
1674
+ fields: [
1675
+ {
1676
+ id: "bottomText",
1677
+ type: "richText",
1678
+ label: "Bottom text",
1679
+ description: "Appears at the very bottom of the footer.",
1680
+ ui: { variant: "limited" }
1681
+ }
1682
+ ],
1683
+ layout: [
1684
+ richText(
1685
+ {
1686
+ className: "prose prose-sm mx-auto max-w-3xl text-center",
1687
+ style: textColorStyle("text/80")
1688
+ },
1689
+ when("bottomText"),
1690
+ bind("bottomText")
1691
+ )
1692
+ ]
1693
+ });
1694
+
1695
+ // ../blocks/src/system/fragments/library/footerLinkGroups.ts
1696
+ var footerLinkGroupsFragment = defineFragment({
1697
+ id: "footerLinkGroups",
1698
+ title: "Footer Link Groups",
1699
+ description: "Repeating columns of grouped footer links.",
1700
+ fields: [
1701
+ {
1702
+ id: "linkGroups",
1703
+ type: "repeater",
1704
+ label: "Link groups",
1705
+ description: "Organize footer links into columns.",
1706
+ schema: {
1707
+ fields: [
1708
+ {
1709
+ id: "title",
1710
+ type: "text",
1711
+ label: "Group title"
1712
+ },
1713
+ {
1714
+ id: "links",
1715
+ type: "repeater",
1716
+ label: "Links",
1717
+ schema: {
1718
+ fields: [
1719
+ {
1720
+ id: "label",
1721
+ type: "text",
1722
+ label: "Label",
1723
+ required: true
1724
+ },
1725
+ {
1726
+ id: "link",
1727
+ type: "link",
1728
+ label: "Link",
1729
+ required: true
1730
+ }
1731
+ ]
1732
+ }
1733
+ }
1734
+ ]
1735
+ }
1736
+ }
1737
+ ],
1738
+ layout: [
1739
+ stack(
1740
+ {
1741
+ gap: "lg",
1742
+ align: "start",
1743
+ className: "grid gap-8 md:grid-cols-3"
1744
+ },
1745
+ [
1746
+ stack(
1747
+ { gap: "md", align: "start", className: "text-left" },
1748
+ [
1749
+ text(
1750
+ {
1751
+ as: "h3",
1752
+ className: "text-xs font-semibold uppercase tracking-wide",
1753
+ style: textColorStyle("text/70")
1754
+ },
1755
+ when("group.title"),
1756
+ bind("group.title")
1757
+ ),
1758
+ stack(
1759
+ { gap: "sm", align: "start", className: "space-y-2" },
1760
+ [
1761
+ link(
1762
+ {
1763
+ className: "block text-sm transition-theme hover:opacity-80",
1764
+ style: textColorStyle("text"),
1765
+ href: {
1766
+ $bind: {
1767
+ from: "entry.link",
1768
+ transforms: [{ id: "links.resolve" }],
1769
+ fallback: "#"
1770
+ }
1771
+ },
1772
+ target: { $bind: { from: "entry.target" } },
1773
+ rel: { $bind: { from: "entry.rel" } }
1774
+ },
1775
+ [text({ as: "span" }, bind("entry.label"))],
1776
+ repeat("group.links", "entry")
1777
+ )
1778
+ ]
1779
+ )
1780
+ ],
1781
+ repeat("linkGroups", "group")
1782
+ // Relative path
1783
+ )
1784
+ ],
1785
+ when("linkGroups")
1786
+ // Relative path
1787
+ )
1788
+ ]
1789
+ });
1790
+
1791
+ // ../blocks/src/system/fragments/library/blogFeaturedPost.ts
1792
+ var blogFeaturedPostFragment = defineFragment({
1793
+ id: "blogFeaturedPost",
1794
+ title: "Featured Blog Post",
1795
+ description: "Featured blog post card with title and preview content.",
1796
+ fields: [
1797
+ {
1798
+ id: "title",
1799
+ type: "text",
1800
+ label: "Block title",
1801
+ description: "Displayed above the featured blog post.",
1802
+ defaultValue: "Latest post",
1803
+ maxLength: 120
1804
+ },
1805
+ {
1806
+ id: "postSlug",
1807
+ type: "reference",
1808
+ label: "Blog post",
1809
+ description: "Pick the blog post to feature in this block.",
1810
+ required: true,
1811
+ referenceKind: "post",
1812
+ allowManualEntry: false
1813
+ }
1814
+ ],
1815
+ layout: [
1816
+ sectionContainer(
1817
+ [
1818
+ stack({ gap: "sm", className: "md:w-1/3" }, [
1819
+ text(
1820
+ {
1821
+ as: "h2",
1822
+ className: "text-2xl font-semibold md:text-3xl",
1823
+ style: textColorStyle("neutral-900")
1824
+ },
1825
+ bind("title", { fallback: "Latest post" })
1826
+ // Relative path
1827
+ )
1828
+ ]),
1829
+ stack({ gap: "md", className: "flex-1" }, [
1830
+ stack({
1831
+ gap: "md",
1832
+ className: "rounded-2xl border bg-white p-6 shadow-sm",
1833
+ style: borderColorStyle("neutral-200")
1834
+ }, [
1835
+ link(
1836
+ {
1837
+ className: "block overflow-hidden rounded-xl border",
1838
+ style: borderColorStyle("neutral-100"),
1839
+ href: { $bind: { from: "post.path" } }
1840
+ },
1841
+ [
1842
+ media(
1843
+ { className: "h-56 w-full object-cover" },
1844
+ bind("post.image", { transforms: [{ id: "media.fromUrl" }] })
1845
+ )
1846
+ ],
1847
+ when("post.image")
1848
+ ),
1849
+ stack({ gap: "sm" }, [
1850
+ link(
1851
+ {
1852
+ className: "block text-2xl font-semibold hover:opacity-80",
1853
+ style: textColorStyle("neutral-900"),
1854
+ href: { $bind: { from: "post.path" } }
1855
+ },
1856
+ [text({ as: "span" }, bind("post.title"))]
1857
+ ),
1858
+ text(
1859
+ { as: "p", className: "text-sm", style: textColorStyle("neutral-500") },
1860
+ when("post.publishedAt"),
1861
+ bind("post.publishedAt", { transforms: [{ id: "date.formatShort" }] })
1862
+ ),
1863
+ text(
1864
+ { as: "p", className: "text-base", style: textColorStyle("neutral-600") },
1865
+ bind("post.excerpt", { fallback: "Select a blog post to display its preview." })
1866
+ ),
1867
+ link(
1868
+ {
1869
+ className: "inline-flex items-center text-sm font-semibold hover:opacity-80",
1870
+ style: textColorStyle("primary"),
1871
+ href: { $bind: { from: "post.path" } }
1872
+ },
1873
+ [text({ as: "span" }, props({ value: "Read more \u2192" }))]
1874
+ )
1875
+ ])
1876
+ ], when("post")),
1877
+ stack(
1878
+ {
1879
+ gap: "sm",
1880
+ className: "rounded-xl border border-dashed p-6 text-sm",
1881
+ style: mergeStyles(
1882
+ borderColorStyle("neutral-300"),
1883
+ backgroundColorStyle("neutral-50"),
1884
+ textColorStyle("neutral-500")
1885
+ )
1886
+ },
1887
+ [text({ as: "p" }, props({ value: "Select a blog post to display its preview." }))],
1888
+ when("post", { not: true })
1889
+ )
1890
+ ])
1891
+ ],
1892
+ { gap: "lg", className: "w-full md:flex-row md:items-start md:gap-10" }
1893
+ )
1894
+ ],
1895
+ data: {
1896
+ key: "post",
1897
+ loader: {
1898
+ endpoint: "getPublishedEntryPreview",
1899
+ params: {
1900
+ type: "post",
1901
+ slug: { $bind: { from: "postSlug" } },
1902
+ siteId: { $bind: { from: "$root.siteId" } }
1903
+ },
1904
+ mode: "server"
1905
+ }
1906
+ }
1907
+ });
1908
+
1909
+ // ../blocks/src/system/fragments/library/blogListGrid.ts
1910
+ var grid = (props2, children, ...mods) => el("grid", props2 ?? void 0, children ?? void 0, ...mods);
1911
+ var blogListGridFragment = defineFragment({
1912
+ id: "blogListGrid",
1913
+ title: "Blog List Grid",
1914
+ description: "Grid layout for displaying blog posts as cards with images.",
1915
+ fields: [
1916
+ {
1917
+ id: "columns",
1918
+ type: "select",
1919
+ label: "Grid columns",
1920
+ description: "Number of columns in the grid layout.",
1921
+ defaultValue: "3",
1922
+ options: [
1923
+ { value: "2", label: "2 columns" },
1924
+ { value: "3", label: "3 columns" },
1925
+ { value: "4", label: "4 columns" }
1926
+ ]
1927
+ },
1928
+ {
1929
+ id: "showImages",
1930
+ type: "boolean",
1931
+ label: "Show images",
1932
+ description: "Display featured images for each post.",
1933
+ defaultValue: true
1934
+ },
1935
+ {
1936
+ id: "showExcerpts",
1937
+ type: "boolean",
1938
+ label: "Show excerpts",
1939
+ description: "Display post preview text.",
1940
+ defaultValue: true
1941
+ },
1942
+ {
1943
+ id: "showDates",
1944
+ type: "boolean",
1945
+ label: "Show dates",
1946
+ description: "Display publish dates.",
1947
+ defaultValue: true
1948
+ }
1949
+ ],
1950
+ layout: [
1951
+ // Grid container
1952
+ grid(
1953
+ {
1954
+ className: "grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3"
1955
+ },
1956
+ [
1957
+ // Individual post card
1958
+ stack(
1959
+ {
1960
+ gap: "md",
1961
+ className: "flex flex-col overflow-hidden rounded-xl border shadow-sm transition-shadow hover:shadow-md",
1962
+ style: mergeStyles(
1963
+ borderColorStyle("neutral-200"),
1964
+ backgroundColorStyle("surface")
1965
+ )
1966
+ },
1967
+ [
1968
+ // Featured image
1969
+ link(
1970
+ {
1971
+ className: "block overflow-hidden",
1972
+ href: { $bind: { from: "post.path" } }
1973
+ },
1974
+ [
1975
+ media(
1976
+ {
1977
+ className: "h-56 w-full object-cover transition-transform hover:scale-105"
1978
+ },
1979
+ bind("post.image", { transforms: [{ id: "media.fromUrl" }] })
1980
+ )
1981
+ ],
1982
+ when("post.image"),
1983
+ when("showImages")
1984
+ ),
1985
+ // Post content
1986
+ stack({ gap: "sm", className: "flex-1 p-6" }, [
1987
+ // Title
1988
+ link(
1989
+ {
1990
+ href: { $bind: { from: "post.path" } },
1991
+ className: "block text-xl font-semibold transition-colors hover:opacity-80",
1992
+ style: textColorStyle("neutral-900")
1993
+ },
1994
+ [text({ as: "h3" }, bind("post.title"))]
1995
+ ),
1996
+ // Date
1997
+ text(
1998
+ {
1999
+ as: "time",
2000
+ className: "text-sm",
2001
+ style: textColorStyle("neutral-500")
2002
+ },
2003
+ bind("post.publishedAt", { transforms: [{ id: "date.formatShort" }] }),
2004
+ when("post.publishedAt"),
2005
+ when("showDates")
2006
+ ),
2007
+ // Excerpt
2008
+ text(
2009
+ {
2010
+ as: "p",
2011
+ className: "mt-2 text-base line-clamp-3",
2012
+ style: textColorStyle("neutral-600")
2013
+ },
2014
+ bind("post.excerpt"),
2015
+ when("post.excerpt"),
2016
+ when("showExcerpts")
2017
+ ),
2018
+ // Read more button
2019
+ button(
2020
+ {
2021
+ href: { $bind: { from: "post.path" } },
2022
+ className: {
2023
+ $bind: { from: "readMoreVariant", fallback: "primary" },
2024
+ $prepend: "button-"
2025
+ }
2026
+ },
2027
+ [text({ as: "span" }, bind("readMoreText", { fallback: "Read more \u2192" }))]
2028
+ )
2029
+ ])
2030
+ ],
2031
+ repeat("data.posts", "post")
2032
+ )
2033
+ ]
2034
+ )
2035
+ ],
2036
+ data: {
2037
+ key: "posts",
2038
+ loader: {
2039
+ endpoint: "listPublishedEntries",
2040
+ params: {
2041
+ type: "post",
2042
+ siteId: { $bind: { from: "$root.siteId" } },
2043
+ limit: { $bind: { from: "postsPerPage", fallback: "12" } },
2044
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
2045
+ },
2046
+ mode: "server"
2047
+ }
2048
+ }
2049
+ });
2050
+
2051
+ // ../blocks/src/system/fragments/library/blogListStack.ts
2052
+ var blogListStackFragment = defineFragment({
2053
+ id: "blogListStack",
2054
+ title: "Blog List Stack",
2055
+ description: "Vertical list layout for displaying blog posts with optional thumbnails.",
2056
+ fields: [
2057
+ {
2058
+ id: "showImages",
2059
+ type: "boolean",
2060
+ label: "Show images",
2061
+ description: "Display thumbnail images for each post.",
2062
+ defaultValue: true
2063
+ },
2064
+ {
2065
+ id: "showExcerpts",
2066
+ type: "boolean",
2067
+ label: "Show excerpts",
2068
+ description: "Display post preview text.",
2069
+ defaultValue: true
2070
+ },
2071
+ {
2072
+ id: "showDates",
2073
+ type: "boolean",
2074
+ label: "Show dates",
2075
+ description: "Display publish dates.",
2076
+ defaultValue: true
2077
+ }
2078
+ ],
2079
+ layout: [
2080
+ // Vertical stack container
2081
+ stack({ gap: "lg", className: "mx-auto max-w-3xl" }, [
2082
+ // Individual post row
2083
+ inline(
2084
+ {
2085
+ gap: "md",
2086
+ className: "items-start border-b pb-6 last:border-b-0",
2087
+ style: borderColorStyle("neutral-200")
2088
+ },
2089
+ [
2090
+ // Optional thumbnail (left-aligned)
2091
+ link(
2092
+ {
2093
+ href: { $bind: { from: "post.path" } },
2094
+ className: "flex-shrink-0"
2095
+ },
2096
+ [
2097
+ media(
2098
+ {
2099
+ className: "h-24 w-24 rounded-lg object-cover transition-transform hover:scale-105"
2100
+ },
2101
+ bind("post.image", { transforms: [{ id: "media.fromUrl" }] })
2102
+ )
2103
+ ],
2104
+ when("post.image"),
2105
+ when("showImages")
2106
+ ),
2107
+ // Post content (grows to fill space)
2108
+ stack({ gap: "sm", className: "flex-1 min-w-0" }, [
2109
+ // Title
2110
+ link(
2111
+ {
2112
+ href: { $bind: { from: "post.path" } },
2113
+ className: "block text-xl font-semibold transition-colors hover:opacity-80",
2114
+ style: textColorStyle("neutral-900")
2115
+ },
2116
+ [text({ as: "h3" }, bind("post.title"))]
2117
+ ),
2118
+ // Date
2119
+ text(
2120
+ {
2121
+ as: "time",
2122
+ className: "text-sm",
2123
+ style: textColorStyle("neutral-500")
2124
+ },
2125
+ bind("post.publishedAt", { transforms: [{ id: "date.formatShort" }] }),
2126
+ when("post.publishedAt"),
2127
+ when("showDates")
2128
+ ),
2129
+ // Excerpt
2130
+ text(
2131
+ {
2132
+ as: "p",
2133
+ className: "text-sm line-clamp-2",
2134
+ style: textColorStyle("neutral-600")
2135
+ },
2136
+ bind("post.excerpt"),
2137
+ when("post.excerpt"),
2138
+ when("showExcerpts")
2139
+ ),
2140
+ // Read more button
2141
+ button(
2142
+ {
2143
+ href: { $bind: { from: "post.path" } },
2144
+ className: {
2145
+ $bind: { from: "readMoreVariant", fallback: "primary" },
2146
+ $prepend: "button-"
2147
+ }
2148
+ },
2149
+ [text({ as: "span" }, bind("readMoreText", { fallback: "Read more \u2192" }))]
2150
+ )
2151
+ ])
2152
+ ],
2153
+ repeat("data.posts", "post")
2154
+ )
2155
+ ])
2156
+ ],
2157
+ data: {
2158
+ key: "posts",
2159
+ loader: {
2160
+ endpoint: "listPublishedEntries",
2161
+ params: {
2162
+ type: "post",
2163
+ siteId: { $bind: { from: "$root.siteId" } },
2164
+ limit: { $bind: { from: "postsPerPage", fallback: "12" } },
2165
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
2166
+ },
2167
+ mode: "server"
2168
+ }
2169
+ }
2170
+ });
2171
+
2172
+ // ../blocks/src/system/fragments/library/faqHeading.ts
2173
+ var faqHeadingFragment = defineFragment({
2174
+ id: "faqHeading",
2175
+ title: "FAQ Heading",
2176
+ description: "Optional eyebrow, title, and description for FAQ sections.",
2177
+ fields: [
2178
+ {
2179
+ id: "eyebrow",
2180
+ type: "text",
2181
+ label: "Eyebrow",
2182
+ description: "Short label displayed above the title.",
2183
+ visibleRoles: ["designer", "admin"],
2184
+ maxLength: 60
2185
+ },
2186
+ {
2187
+ id: "title",
2188
+ type: "text",
2189
+ label: "Title",
2190
+ required: false,
2191
+ maxLength: 120
2192
+ },
2193
+ {
2194
+ id: "description",
2195
+ type: "richText",
2196
+ label: "Description",
2197
+ required: false,
2198
+ ui: { variant: "limited" }
2199
+ }
2200
+ ],
2201
+ layout: stack(
2202
+ {
2203
+ gap: "sm",
2204
+ className: "mx-auto max-w-3xl text-center mb-4"
2205
+ },
2206
+ [
2207
+ text(
2208
+ {
2209
+ as: "p",
2210
+ className: "faq-eyebrow text-sm font-semibold uppercase tracking-wide",
2211
+ style: textColorStyle("primary")
2212
+ },
2213
+ when("content.eyebrow"),
2214
+ bind("content.eyebrow")
2215
+ ),
2216
+ text(
2217
+ {
2218
+ as: "h2",
2219
+ className: "faq-title text-3xl font-semibold sm:text-4xl",
2220
+ style: textColorStyle("neutral-900")
2221
+ },
2222
+ when("content.title"),
2223
+ bind("content.title")
2224
+ ),
2225
+ richText(
2226
+ {
2227
+ className: "faq-description prose mx-auto max-w-none text-base sm:text-lg",
2228
+ style: textColorStyle("neutral-600")
2229
+ },
2230
+ when("content.description"),
2231
+ bind("content.description")
2232
+ )
2233
+ ]
2234
+ )
2235
+ });
2236
+
2237
+ // ../blocks/src/system/fragments/library/faqAccordion.ts
2238
+ var faqAccordionFragment = defineFragment({
2239
+ id: "faqAccordion",
2240
+ title: "FAQ Accordion",
2241
+ description: "Expandable list of question and answer pairs.",
2242
+ fields: [
2243
+ {
2244
+ id: "items",
2245
+ type: "repeater",
2246
+ label: "FAQ items",
2247
+ itemLabel: "FAQ item",
2248
+ itemLabelSource: "question",
2249
+ minItems: 1,
2250
+ maxItems: 30,
2251
+ schema: {
2252
+ fields: [
2253
+ {
2254
+ id: "question",
2255
+ type: "text",
2256
+ label: "Question",
2257
+ required: true,
2258
+ maxLength: 160
2259
+ },
2260
+ {
2261
+ id: "answer",
2262
+ type: "richText",
2263
+ label: "Answer",
2264
+ required: true,
2265
+ ui: { variant: "limited" }
2266
+ }
2267
+ ]
2268
+ }
2269
+ }
2270
+ ],
2271
+ layout: accordionList({
2272
+ collection: "content.items",
2273
+ itemName: "faqItem",
2274
+ indexName: "faqIndex",
2275
+ accordionProps: {
2276
+ className: "accordion-root",
2277
+ // CSS-first: all styling handled by theme CSS
2278
+ type: "single",
2279
+ collapsible: true
2280
+ },
2281
+ itemProps: {
2282
+ className: "accordion-item",
2283
+ iconStyle: { $bind: { from: "theme.accordions.icon.style" } }
2284
+ // Pass icon style from theme
2285
+ },
2286
+ triggerFrom: "faqItem.question",
2287
+ contentFrom: "faqItem.answer"
2288
+ })
2289
+ });
2290
+
2291
+ // ../blocks/src/system/fragments/library/card.ts
2292
+ var cardFragment = defineFragment({
2293
+ id: "card",
2294
+ title: "Card",
2295
+ description: "Content card with optional media, title, body, and action buttons",
2296
+ category: "content",
2297
+ icon: "LayoutCard",
2298
+ fields: [
2299
+ {
2300
+ id: "media",
2301
+ type: "media",
2302
+ label: "Featured media",
2303
+ description: "Image or video displayed at top of card",
2304
+ required: false,
2305
+ mediaKinds: ["image", "video"]
2306
+ },
2307
+ {
2308
+ id: "aspectRatio",
2309
+ type: "select",
2310
+ label: "Media aspect ratio",
2311
+ defaultValue: "auto",
2312
+ options: [
2313
+ { value: "auto", label: "Auto" },
2314
+ { value: "16/9", label: "16:9 (Landscape)" },
2315
+ { value: "4/3", label: "4:3 (Standard)" },
2316
+ { value: "1/1", label: "1:1 (Square)" },
2317
+ { value: "3/4", label: "3:4 (Portrait)" }
2318
+ ],
2319
+ condition: {
2320
+ field: "media",
2321
+ operator: "isNotEmpty"
2322
+ }
2323
+ },
2324
+ {
2325
+ id: "title",
2326
+ type: "text",
2327
+ label: "Title",
2328
+ required: true,
2329
+ maxLength: 120
2330
+ },
2331
+ {
2332
+ id: "body",
2333
+ type: "richText",
2334
+ label: "Body",
2335
+ description: "Main card content",
2336
+ format: "markdown"
2337
+ },
2338
+ createCtasRepeater({
2339
+ id: "ctas",
2340
+ label: "Action buttons",
2341
+ itemLabel: "Button",
2342
+ minItems: 0,
2343
+ maxItems: 3
2344
+ }),
2345
+ {
2346
+ id: "variant",
2347
+ type: "select",
2348
+ label: "Card variant",
2349
+ description: "Choose a card style from your theme",
2350
+ required: false,
2351
+ defaultValue: "default",
2352
+ options: [
2353
+ { value: "default", label: "Default" },
2354
+ { value: "variant1", label: "Variant 1" },
2355
+ { value: "variant2", label: "Variant 2" }
2356
+ ]
2357
+ }
2358
+ ],
2359
+ layout: stack(
2360
+ {
2361
+ gap: "none",
2362
+ className: {
2363
+ $bind: {
2364
+ from: "variant",
2365
+ fallback: "default"
2366
+ },
2367
+ $prepend: "card-"
2368
+ }
2369
+ },
2370
+ [
2371
+ // Featured media at top (conditional) - full width, no padding
2372
+ // CSS handles corner radius via .card-{variant} .card-media selector
2373
+ media(
2374
+ {
2375
+ className: "card-media w-full h-auto object-cover",
2376
+ style: {
2377
+ aspectRatio: { $bind: { from: "aspectRatio" } }
2378
+ }
2379
+ },
2380
+ when("media"),
2381
+ bind("media")
2382
+ ),
2383
+ // Content wrapper with padding based on variant
2384
+ // CSS handles padding via .card-{variant} .card-content selector
2385
+ stack(
2386
+ {
2387
+ gap: "sm",
2388
+ className: "card-content"
2389
+ },
2390
+ [
2391
+ text(
2392
+ {
2393
+ as: "h3",
2394
+ className: "card-title text-xl font-semibold"
2395
+ },
2396
+ bind("title")
2397
+ ),
2398
+ richText(
2399
+ {
2400
+ className: "card-body text-base",
2401
+ style: textColorStyle("mutedText")
2402
+ },
2403
+ when("body"),
2404
+ bind("body")
2405
+ ),
2406
+ // Action buttons at bottom (conditional)
2407
+ ctaRow({
2408
+ collectionPath: "ctas",
2409
+ itemName: "cta",
2410
+ gap: "sm",
2411
+ justify: "start",
2412
+ containerClassName: "mt-2"
2413
+ })
2414
+ ]
2415
+ )
2416
+ ]
2417
+ )
2418
+ });
2419
+
2420
+ // ../blocks/src/system/fragments/library/heading.ts
2421
+ var headingFragment = defineFragment({
2422
+ id: "heading",
2423
+ title: "Heading",
2424
+ description: "Text heading",
2425
+ category: "content",
2426
+ icon: "Heading",
2427
+ fields: [
2428
+ {
2429
+ id: "text",
2430
+ type: "text",
2431
+ label: "Heading text",
2432
+ required: true,
2433
+ maxLength: 200
2434
+ },
2435
+ {
2436
+ id: "level",
2437
+ type: "select",
2438
+ label: "Heading level",
2439
+ defaultValue: "h2",
2440
+ options: [
2441
+ { value: "h1", label: "H1 (Large)" },
2442
+ { value: "h2", label: "H2 (Medium)" },
2443
+ { value: "h3", label: "H3 (Small)" }
2444
+ ]
2445
+ }
2446
+ ],
2447
+ layout: text(
2448
+ {
2449
+ as: { $bind: { from: "level" } },
2450
+ // Relative path - resolves to current scope
2451
+ className: "fragment-heading font-bold"
2452
+ },
2453
+ bind("text")
2454
+ // Relative path - resolves to current scope
2455
+ )
2456
+ });
2457
+
2458
+ // ../blocks/src/system/fragments/library/richText.ts
2459
+ var richTextFragment = defineFragment({
2460
+ id: "richText",
2461
+ title: "Rich Text",
2462
+ description: "Formatted text with markdown",
2463
+ category: "content",
2464
+ icon: "FileText",
2465
+ fields: [
2466
+ {
2467
+ id: "content",
2468
+ type: "richText",
2469
+ label: "Content",
2470
+ required: true,
2471
+ format: "markdown"
2472
+ }
2473
+ ],
2474
+ layout: richText(
2475
+ {
2476
+ className: "fragment-richtext prose prose-neutral"
2477
+ },
2478
+ bind("content")
2479
+ // Relative path - resolves to current scope
2480
+ )
2481
+ });
2482
+
2483
+ // ../blocks/src/system/fragments/utils/toRepeaterSchema.ts
2484
+ function fragmentsToRepeaterField(id, label, fragments, options = {}) {
2485
+ return {
2486
+ id,
2487
+ type: "repeater",
2488
+ label,
2489
+ description: options.description,
2490
+ polymorphic: true,
2491
+ itemLabel: options.itemLabel ?? "Item",
2492
+ itemLabelSource: "_type",
2493
+ minItems: options.minItems ?? 0,
2494
+ maxItems: options.maxItems,
2495
+ allowConversion: options.allowConversion ?? true,
2496
+ required: false,
2497
+ itemTypes: Object.fromEntries(
2498
+ Object.entries(fragments).map(([typeId, fragment]) => [
2499
+ typeId,
2500
+ {
2501
+ label: fragment.title ?? typeId,
2502
+ icon: fragment.icon,
2503
+ fields: fragment.fields
2504
+ }
2505
+ ])
2506
+ )
2507
+ };
2508
+ }
2509
+
2510
+ // ../blocks/src/system/fragments/library/image.ts
2511
+ var imageFragment = defineFragment({
2512
+ id: "image",
2513
+ title: "Image",
2514
+ description: "Image with optional caption",
2515
+ category: "content",
2516
+ icon: "Image",
2517
+ fields: [
2518
+ {
2519
+ id: "image",
2520
+ type: "media",
2521
+ label: "Image",
2522
+ required: true,
2523
+ mediaKinds: ["image"]
2524
+ },
2525
+ {
2526
+ id: "caption",
2527
+ type: "text",
2528
+ label: "Caption",
2529
+ required: false,
2530
+ maxLength: 200
2531
+ },
2532
+ {
2533
+ id: "aspectRatio",
2534
+ type: "select",
2535
+ label: "Aspect ratio",
2536
+ defaultValue: "auto",
2537
+ options: [
2538
+ { value: "auto", label: "Auto" },
2539
+ { value: "16/9", label: "16:9 (Landscape)" },
2540
+ { value: "4/3", label: "4:3 (Standard)" },
2541
+ { value: "1/1", label: "1:1 (Square)" },
2542
+ { value: "3/4", label: "3:4 (Portrait)" }
2543
+ ]
2544
+ }
2545
+ ],
2546
+ layout: stack({ gap: "sm" }, [
2547
+ media(
2548
+ {
2549
+ className: "fragment-image w-full h-auto object-cover",
2550
+ style: {
2551
+ aspectRatio: { $bind: { from: "aspectRatio" } }
2552
+ }
2553
+ },
2554
+ bind("image")
2555
+ ),
2556
+ text(
2557
+ {
2558
+ as: "p",
2559
+ className: "image-caption text-sm text-center",
2560
+ style: textColorStyle("mutedText")
2561
+ },
2562
+ when("caption"),
2563
+ bind("caption")
2564
+ )
2565
+ ])
2566
+ });
2567
+
2568
+ // ../blocks/src/system/fragments/library/columnContent.ts
2569
+ var columnContentFragment = defineFragment({
2570
+ id: "columnContent",
2571
+ title: "Column",
2572
+ description: "A single column with customizable content",
2573
+ category: "layout",
2574
+ icon: "RectangleVertical",
2575
+ fields: [
2576
+ fragmentsToRepeaterField(
2577
+ "items",
2578
+ "Column items",
2579
+ {
2580
+ image: imageFragment,
2581
+ heading: headingFragment,
2582
+ richText: richTextFragment
2583
+ },
2584
+ {
2585
+ minItems: 0,
2586
+ maxItems: 20,
2587
+ itemLabel: "Item",
2588
+ description: "Add content to this column"
2589
+ }
2590
+ )
2591
+ ],
2592
+ layout: stack(
2593
+ { gap: "md", className: "h-full" },
2594
+ [
2595
+ {
2596
+ type: "stack",
2597
+ gap: "md",
2598
+ children: typeBasedLayout(
2599
+ {
2600
+ image: imageFragment.layout,
2601
+ heading: headingFragment.layout,
2602
+ richText: richTextFragment.layout
2603
+ },
2604
+ { itemName: "item" }
2605
+ ),
2606
+ $repeat: {
2607
+ collection: { from: "items" },
2608
+ itemName: "item"
2609
+ }
2610
+ }
2611
+ ]
2612
+ )
2613
+ });
2614
+
2615
+ // ../blocks/src/system/constants/background.ts
2616
+ var BACKGROUND_POSITION_PRESETS = [
2617
+ { value: "center", label: "Center" },
2618
+ { value: "top", label: "Top" },
2619
+ { value: "top left", label: "Top Left" },
2620
+ { value: "top right", label: "Top Right" },
2621
+ { value: "bottom", label: "Bottom" },
2622
+ { value: "bottom left", label: "Bottom Left" },
2623
+ { value: "bottom right", label: "Bottom Right" },
2624
+ { value: "left", label: "Left" },
2625
+ { value: "right", label: "Right" },
2626
+ // The following are valid keywords but are not included in the UI to reduce clutter.
2627
+ // They are included here to ensure the transform logic can correctly identify them as presets.
2628
+ { value: "top center", label: "Top Center" },
2629
+ { value: "bottom center", label: "Bottom Center" },
2630
+ { value: "left center", label: "Left Center" },
2631
+ { value: "right center", label: "Right Center" },
2632
+ { value: "left top", label: "Left Top" },
2633
+ { value: "right top", label: "Right Top" },
2634
+ { value: "left bottom", label: "Left Bottom" },
2635
+ { value: "right bottom", label: "Right Bottom" }
2636
+ ];
2637
+ BACKGROUND_POSITION_PRESETS.map((p) => p.value);
2638
+
2639
+ // ../blocks/src/system/fields/background.ts
2640
+ function createBackgroundField(options = {}) {
2641
+ const {
2642
+ id = "background",
2643
+ label = "Background",
2644
+ allowColor = true,
2645
+ allowGradient = true,
2646
+ allowImage = true,
2647
+ includeAdvanced = false
2648
+ } = options;
2649
+ const tabs = [];
2650
+ if (allowColor) {
2651
+ tabs.push({
2652
+ id: "color",
2653
+ label: "Color",
2654
+ description: "Solid color background using theme tokens or custom colors",
2655
+ sdkSectionOption: "backgroundColor",
2656
+ fields: [
2657
+ {
2658
+ id: "color",
2659
+ type: "text",
2660
+ label: "Color",
2661
+ description: "Select a background color from the available options.",
2662
+ required: false,
2663
+ multiline: false,
2664
+ ui: {
2665
+ // Use BackgroundColorWidget via widget override
2666
+ widget: "backgroundColor"
2667
+ }
2668
+ }
2669
+ ]
2670
+ });
2671
+ }
2672
+ if (allowGradient) {
2673
+ tabs.push({
2674
+ id: "gradient",
2675
+ label: "Gradient",
2676
+ description: "CSS gradient background",
2677
+ sdkSectionOption: "backgroundGradient",
2678
+ fields: [
2679
+ {
2680
+ id: "gradient",
2681
+ type: "text",
2682
+ label: "Gradient",
2683
+ description: "CSS gradient value (e.g., linear-gradient(to right, #ff0000, #00ff00)).",
2684
+ required: false,
2685
+ multiline: true,
2686
+ ui: {
2687
+ placeholder: "linear-gradient(to right, #ff0000, #00ff00)"
2688
+ }
2689
+ }
2690
+ ]
2691
+ });
2692
+ }
2693
+ if (allowImage) {
2694
+ const imageFields = [
2695
+ {
2696
+ id: "image",
2697
+ type: "media",
2698
+ label: "Image",
2699
+ description: "Background image file",
2700
+ mediaKinds: ["image"],
2701
+ required: false
2702
+ }
2703
+ ];
2704
+ if (includeAdvanced) {
2705
+ const objectFitField = {
2706
+ id: "objectFit",
2707
+ type: "select",
2708
+ label: "Image Sizing",
2709
+ description: "How the image should be sized and positioned.",
2710
+ required: false,
2711
+ multiple: false,
2712
+ options: [
2713
+ { value: "fill", label: "Fill (cover)" },
2714
+ { value: "fit", label: "Fit (contain)" },
2715
+ { value: "original", label: "Original size" },
2716
+ { value: "custom", label: "Scale up" }
2717
+ ]
2718
+ };
2719
+ const scaleField = {
2720
+ id: "scale",
2721
+ type: "presetOrCustom",
2722
+ label: "Scale",
2723
+ description: "Image scale amount.",
2724
+ required: false,
2725
+ ui: {
2726
+ visibleWhen: {
2727
+ field: "objectFit",
2728
+ equals: "custom"
2729
+ }
2730
+ },
2731
+ presets: [
2732
+ { value: "125", label: "125%" },
2733
+ { value: "150", label: "150%" },
2734
+ { value: "175", label: "175%" },
2735
+ { value: "200", label: "200%" }
2736
+ ],
2737
+ customInput: {
2738
+ placeholder: "e.g., 800px, 50vw, 175%",
2739
+ helpText: "Enter a custom size using %, px, vh, vw, rem, or em units"
2740
+ }
2741
+ };
2742
+ const positionField = {
2743
+ id: "position",
2744
+ type: "presetOrCustom",
2745
+ label: "Position",
2746
+ description: 'Anchor point for the image. Relevant for "Fill" and "Custom size" options.',
2747
+ required: false,
2748
+ presets: [...BACKGROUND_POSITION_PRESETS],
2749
+ customInput: {
2750
+ placeholder: "e.g., 25% 75%, top 20px left 50px",
2751
+ helpText: "Enter a custom CSS background-position value"
2752
+ }
2753
+ };
2754
+ const opacityField = {
2755
+ id: "opacity",
2756
+ type: "number",
2757
+ label: "Opacity",
2758
+ description: "Image opacity (0-100).",
2759
+ required: false,
2760
+ ui: {
2761
+ min: 0,
2762
+ max: 100,
2763
+ step: 5
2764
+ }
2765
+ };
2766
+ imageFields.push(
2767
+ objectFitField,
2768
+ scaleField,
2769
+ positionField,
2770
+ opacityField
2771
+ );
2772
+ }
2773
+ tabs.push({
2774
+ id: "image",
2775
+ label: "Image",
2776
+ description: includeAdvanced ? "Background image with advanced controls" : "Background image",
2777
+ sdkSectionOption: "backgroundImage",
2778
+ fields: imageFields
2779
+ });
2780
+ }
2781
+ return {
2782
+ id,
2783
+ type: "tabGroup",
2784
+ label,
2785
+ required: false,
2786
+ tabs,
2787
+ activeTabField: "type",
2788
+ ui: {
2789
+ hideLabel: false
2790
+ }
2791
+ };
2792
+ }
2793
+
2794
+ // ../blocks/src/system/fields/boxStyles.ts
2795
+ function sectionStylesField(options = {}) {
2796
+ const {
2797
+ id = "_sectionStyles",
2798
+ label = "Section styles",
2799
+ includeBackground = true
2800
+ } = options;
2801
+ const fields4 = [];
2802
+ if (includeBackground) {
2803
+ fields4.push(
2804
+ createBackgroundField({
2805
+ id: "background",
2806
+ label: "Background",
2807
+ allowColor: true,
2808
+ allowGradient: true,
2809
+ allowImage: true,
2810
+ includeAdvanced: true
2811
+ })
2812
+ );
2813
+ }
2814
+ fields4.push({
2815
+ id: "spacing",
2816
+ type: "select",
2817
+ label: "Inner spacing",
2818
+ description: "Vertical padding for the section.",
2819
+ required: false,
2820
+ multiple: false,
2821
+ options: [
2822
+ { value: "none", label: "None" },
2823
+ { value: "compact", label: "Compact" },
2824
+ { value: "cozy", label: "Cozy" },
2825
+ { value: "medium", label: "Medium" },
2826
+ { value: "comfortable", label: "Comfortable" },
2827
+ { value: "spacious", label: "Spacious" }
2828
+ ]
2829
+ });
2830
+ return {
2831
+ id,
2832
+ type: "modal",
2833
+ label,
2834
+ required: false,
2835
+ schema: { fields: fields4 },
2836
+ ui: {
2837
+ modalConfig: {
2838
+ buttonLabel: label,
2839
+ description: "Configure background and spacing for this section.",
2840
+ buttonVariant: "outline",
2841
+ showCustomizedIndicator: true
2842
+ }
2843
+ }
2844
+ };
2845
+ }
2846
+
2847
+ // ../blocks/src/system/defineBlock.ts
2848
+ function createBlockManifest(config) {
2849
+ const composition2 = config.fragments ? composeFragments(config.fragments) : { fields: []};
2850
+ const allFields = [
2851
+ ...composition2.fields,
2852
+ ...config.additionalFields ?? []
2853
+ ];
2854
+ if (!config.skipSectionStyles) {
2855
+ allFields.push(
2856
+ sectionStylesField({
2857
+ id: "_sectionStyles",
2858
+ label: "Section styles"
2859
+ })
2860
+ );
2861
+ }
2862
+ const fields4 = fieldSchema.array().parse(allFields);
2863
+ const layout = config.layout;
2864
+ const variants = config.variants;
2865
+ let behaviours = config.behaviours;
2866
+ if (!behaviours && config.paletteHidden !== void 0) {
2867
+ behaviours = {
2868
+ supportsThemeSwitching: true,
2869
+ inlineEditing: true,
2870
+ animation: true,
2871
+ paletteHidden: config.paletteHidden
2872
+ };
2873
+ }
2874
+ const manifest = {
2875
+ name: config.id,
2876
+ version: "0.1.0",
2877
+ title: config.title,
2878
+ titleSource: config.titleSource,
2879
+ description: config.description ?? "",
2880
+ component: config.component ?? deriveComponentName(config.id),
2881
+ fields: fields4,
2882
+ slots: [],
2883
+ // Always empty
2884
+ styleTokens: config.styleTokens,
2885
+ behaviours,
2886
+ category: config.category,
2887
+ contentTypes: config.contentTypes,
2888
+ tags: config.tags ?? [],
2889
+ icon: config.icon ?? "Box",
2890
+ layout,
2891
+ variants,
2892
+ defaultVariant: config.defaultVariant
2893
+ };
2894
+ return augmentManifest(manifest);
2895
+ }
2896
+ function deriveComponentName(id) {
2897
+ const base = id.replace(/^block\./, "");
2898
+ const dotSeparated = base.replace(/([a-z])([A-Z])/g, "$1.$2").toLowerCase();
2899
+ return `${dotSeparated}.default`;
2900
+ }
2901
+
2902
+ // ../blocks/src/system/blocks/hero.ts
2903
+ var heroCopyAndCta = composeFragments([
2904
+ { fragment: heroCopyFragment },
2905
+ { fragment: ctaRowFragment }
2906
+ ]);
2907
+ var heroContentNodes = heroCopyAndCta.layout;
2908
+ var classicLayout = styledSection({
2909
+ children: sectionContainer(heroContentNodes, {
2910
+ gap: "md",
2911
+ className: "text-center"
2912
+ })
2913
+ });
2914
+ var microLayout = styledSection({
2915
+ children: sectionContainer(heroContentNodes, {
2916
+ gap: "sm",
2917
+ className: "text-center"
2918
+ }),
2919
+ spacing: "compact"
2920
+ });
2921
+ var splitLayoutBase = styledSection({
2922
+ children: {
2923
+ type: "grid",
2924
+ cols: 1,
2925
+ colsSm: 2,
2926
+ gap: "lg",
2927
+ className: "mx-auto max-w-7xl items-center",
2928
+ children: [
2929
+ {
2930
+ type: "stack",
2931
+ gap: "md",
2932
+ className: "text-left",
2933
+ children: heroContentNodes
2934
+ }
2935
+ ]
2936
+ }
2937
+ });
2938
+ var splitLayout = splitLayoutBase;
2939
+ var splitReverseLayout = splitLayoutBase;
2940
+ var heroManifest = createBlockManifest({
2941
+ id: "block.hero",
2942
+ title: "Hero",
2943
+ titleSource: "headline",
2944
+ category: "layout",
2945
+ fragments: [
2946
+ { fragment: heroCopyFragment, fieldPriority: 0 },
2947
+ { fragment: ctaRowFragment, fieldPriority: 2 }
2948
+ ],
2949
+ layout: classicLayout,
2950
+ variants: {
2951
+ classic: classicLayout,
2952
+ micro: microLayout,
2953
+ split: splitLayout,
2954
+ splitReverse: splitReverseLayout
2955
+ },
2956
+ defaultVariant: "classic",
2957
+ description: "Hero section with headline, subtitle, and repeatable CTAs.",
2958
+ tags: ["header", "banner", "landing", "introduction", "welcome", "splash", "headline"],
2959
+ icon: "Sparkles",
2960
+ styleTokens: {
2961
+ typography: "display",
2962
+ spacing: "xl"
2963
+ }
2964
+ });
2965
+ var heroBlockDefinition = {
2966
+ manifest: heroManifest
2967
+ };
2968
+
2969
+ // ../blocks/src/system/blocks/body-text.ts
2970
+ var bodyCopyComposition = composeFragments([{ fragment: bodyCopyFragment }]);
2971
+ var bodyTextManifest = createBlockManifest({
2972
+ id: "block.bodyText",
2973
+ title: "Body Text",
2974
+ titleSource: "heading",
2975
+ category: "content",
2976
+ component: "body.text.basic",
2977
+ fragments: [
2978
+ { fragment: bodyCopyFragment, fieldPriority: 0 }
2979
+ ],
2980
+ layout: styledSection({
2981
+ children: bodyCopyComposition.layout,
2982
+ spacing: "medium"
2983
+ }),
2984
+ description: "Simple text block with optional heading and alignment controls.",
2985
+ tags: ["text", "paragraph", "content", "copy", "article", "writing", "rich-text"],
2986
+ icon: "Type",
2987
+ styleTokens: {
2988
+ spacing: "md"
2989
+ }
2990
+ });
2991
+ var bodyTextBlockDefinition = {
2992
+ manifest: bodyTextManifest
2993
+ };
2994
+ var composition = composeFragments([{ fragment: blogFeaturedPostFragment }]);
2995
+ var fields = fieldSchema.array().parse(composition.fields);
2996
+ var blogPostLayout = section(
2997
+ { background: "background/base", className: "px-6 py-12" },
2998
+ [...composition.layout]
2999
+ );
3000
+ var blogPostManifest = {
3001
+ name: "block.blogPost",
3002
+ version: "0.1.0",
3003
+ title: "Blog post",
3004
+ description: "Highlights a single blog post with title, image, and excerpt.",
3005
+ component: "blog.post.highlight",
3006
+ fields,
3007
+ slots: [],
3008
+ styleTokens: {
3009
+ background: "surface",
3010
+ typography: "body",
3011
+ spacing: "md"
3012
+ },
3013
+ behaviours: {
3014
+ supportsThemeSwitching: true,
3015
+ inlineEditing: false,
3016
+ animation: false,
3017
+ paletteHidden: false
3018
+ },
3019
+ category: "blog",
3020
+ contentTypes: ["post"],
3021
+ tags: ["blog", "post", "featured", "highlight", "article", "single"],
3022
+ icon: "FileText",
3023
+ layout: blogPostLayout
3024
+ };
3025
+ var blogPostDataSchema = zod.z.object({
3026
+ id: zod.z.string(),
3027
+ title: zod.z.string(),
3028
+ slug: zod.z.string(),
3029
+ path: zod.z.string(),
3030
+ excerpt: zod.z.string().nullable().optional(),
3031
+ image: zod.z.object({
3032
+ url: zod.z.string().optional().nullable(),
3033
+ alt: zod.z.string().optional().nullable()
3034
+ }).nullable().optional(),
3035
+ publishedAt: zod.z.string().nullable().optional()
3036
+ });
3037
+ var blogPostBlockDefinition = {
3038
+ manifest: blogPostManifest,
3039
+ dataSchemas: { post: blogPostDataSchema.optional() },
3040
+ dataLoaders: buildFragmentDataLoaders(composition)
3041
+ };
3042
+
3043
+ // ../blocks/src/system/blocks/blog-placeholder.ts
3044
+ var grid2 = (props2, children, ...mods) => el("grid", props2 ?? void 0, children ?? void 0, ...mods);
3045
+ var blogPlaceholderLayout = section(
3046
+ { background: "surface", className: "px-6 py-16 sm:py-20 md:py-24" },
3047
+ // Semantic: comfortable + extra md padding
3048
+ [
3049
+ stack(
3050
+ { gap: "sm", align: "center", className: "mx-auto max-w-3xl text-center" },
3051
+ [
3052
+ text(
3053
+ { as: "p", className: "text-sm font-semibold uppercase tracking-wide", style: textColorStyle("mutedText") },
3054
+ bind("content.eyebrow")
3055
+ ),
3056
+ text(
3057
+ { as: "h2", className: "text-3xl font-semibold tracking-tight sm:text-4xl", style: textColorStyle("text") },
3058
+ bind("content.heading")
3059
+ ),
3060
+ text(
3061
+ { className: "text-base leading-relaxed", style: textColorStyle("mutedText") },
3062
+ bind("content.description")
3063
+ )
3064
+ ]
3065
+ ),
3066
+ grid2(
3067
+ { className: "mt-10 gap-6 sm:grid-cols-2 lg:grid-cols-3" },
3068
+ [
3069
+ stack(
3070
+ {
3071
+ gap: "sm",
3072
+ className: "rounded-xl border p-6 text-left shadow-sm",
3073
+ style: mergeStyles(borderColorStyle("border/60"), backgroundColorStyle("background"))
3074
+ },
3075
+ [
3076
+ text(
3077
+ { as: "h3", className: "text-lg font-semibold", style: textColorStyle("text") },
3078
+ bind("item.title")
3079
+ ),
3080
+ text(
3081
+ { className: "text-sm leading-relaxed", style: textColorStyle("mutedText") },
3082
+ bind("item.summary")
3083
+ )
3084
+ ],
3085
+ repeat("content.cards", "item", { limit: 6 })
3086
+ )
3087
+ ]
3088
+ ),
3089
+ stack(
3090
+ { gap: "xs", align: "center", className: "mt-10 text-center" },
3091
+ [
3092
+ text({ className: "text-sm", style: textColorStyle("mutedText") }, bind("content.note"))
3093
+ ]
3094
+ )
3095
+ ]
3096
+ );
3097
+ var blogPlaceholderManifest = createBlockManifest({
3098
+ id: "block.blogPlaceholder",
3099
+ title: "Blog placeholder",
3100
+ category: "blog",
3101
+ component: "blog.placeholder",
3102
+ // Skip section styles - this block has custom layout with fixed styling
3103
+ skipSectionStyles: true,
3104
+ // Custom fields for blog placeholder content
3105
+ additionalFields: [
3106
+ fieldSchema.parse({
3107
+ id: "eyebrow",
3108
+ type: "text",
3109
+ label: "Eyebrow",
3110
+ description: "Short label displayed above the heading."
3111
+ }),
3112
+ fieldSchema.parse({
3113
+ id: "heading",
3114
+ type: "text",
3115
+ label: "Heading",
3116
+ required: true
3117
+ }),
3118
+ fieldSchema.parse({
3119
+ id: "description",
3120
+ type: "text",
3121
+ label: "Description",
3122
+ multiline: true
3123
+ }),
3124
+ fieldSchema.parse({
3125
+ id: "cards",
3126
+ type: "repeater",
3127
+ label: "Placeholder cards",
3128
+ itemLabel: "Placeholder",
3129
+ schema: {
3130
+ fields: [
3131
+ { id: "title", type: "text", label: "Title", required: true },
3132
+ { id: "summary", type: "text", label: "Summary", multiline: true, required: true }
3133
+ ]
3134
+ }
3135
+ }),
3136
+ fieldSchema.parse({
3137
+ id: "note",
3138
+ type: "text",
3139
+ label: "Footer note",
3140
+ description: "Optional helper text displayed below the cards."
3141
+ })
3142
+ ],
3143
+ layout: blogPlaceholderLayout,
3144
+ description: "Starter layout shown until real blog content replaces it.",
3145
+ styleTokens: {
3146
+ background: "surface",
3147
+ spacing: "lg"
3148
+ },
3149
+ paletteHidden: true
3150
+ });
3151
+ var blogPlaceholderBlockDefinition = {
3152
+ manifest: blogPlaceholderManifest
3153
+ };
3154
+ var gridComposition = composeFragments([{ fragment: blogListGridFragment }]);
3155
+ var stackComposition = composeFragments([{ fragment: blogListStackFragment }]);
3156
+ var fragmentFields = [...gridComposition.fields, ...stackComposition.fields];
3157
+ var uniqueFields = fragmentFields.filter(
3158
+ (field, index, self) => index === self.findIndex((f) => f.id === field.id)
3159
+ );
3160
+ var blogListingLayout = section(
3161
+ { background: "background/base", className: "px-6 py-12 sm:py-16 md:py-20" },
3162
+ // Semantic: medium + extra md padding
3163
+ [
3164
+ // Grid layout (conditionally shown)
3165
+ ...gridComposition.layout.map((node) => ({
3166
+ ...node,
3167
+ $when: {
3168
+ when: { from: "content.layout" },
3169
+ equals: "grid"
3170
+ }
3171
+ })),
3172
+ // Stack layout (conditionally shown)
3173
+ ...stackComposition.layout.map((node) => ({
3174
+ ...node,
3175
+ $when: {
3176
+ when: { from: "content.layout" },
3177
+ equals: "stack"
3178
+ }
3179
+ })),
3180
+ // Empty state (shown when no posts)
3181
+ stack(
3182
+ {
3183
+ gap: "sm",
3184
+ className: "mx-auto max-w-3xl rounded-xl border border-dashed p-12 text-center",
3185
+ style: mergeStyles(
3186
+ borderColorStyle("neutral-300"),
3187
+ backgroundColorStyle("neutral-50")
3188
+ )
3189
+ },
3190
+ [
3191
+ text(
3192
+ { as: "p", className: "text-base", style: textColorStyle("neutral-500") },
3193
+ bind("content.emptyMessage", { fallback: "No posts published yet." })
3194
+ )
3195
+ ],
3196
+ when("data.posts", { not: true })
3197
+ )
3198
+ ]
3199
+ );
3200
+ var blogListingManifest = createBlockManifest({
3201
+ id: "block.blogListing",
3202
+ title: "Blog listing",
3203
+ category: "blog",
3204
+ component: "blog.listing",
3205
+ // Skip section styles - this block has layout-specific fields instead
3206
+ skipSectionStyles: true,
3207
+ // Custom fields for blog listing configuration
3208
+ additionalFields: [
3209
+ fieldSchema.parse({
3210
+ id: "layout",
3211
+ type: "select",
3212
+ label: "Layout",
3213
+ description: "Choose how blog posts are displayed.",
3214
+ defaultValue: "grid",
3215
+ options: [
3216
+ { value: "grid", label: "Grid (cards)" },
3217
+ { value: "stack", label: "Stack (vertical list)" }
3218
+ ]
3219
+ }),
3220
+ // Grid-specific column field (only visible when layout is grid)
3221
+ ...gridComposition.fields.filter((f) => f.id === "columns").map((f) => ({
3222
+ ...f,
3223
+ ui: {
3224
+ ...f.ui,
3225
+ visibleWhen: { field: "layout", equals: "grid" }
3226
+ }
3227
+ })),
3228
+ fieldSchema.parse({
3229
+ id: "postsPerPage",
3230
+ type: "select",
3231
+ label: "Posts to display",
3232
+ description: "Number of posts to show on this page.",
3233
+ defaultValue: "12",
3234
+ options: [
3235
+ { value: "6", label: "6 posts" },
3236
+ { value: "12", label: "12 posts" },
3237
+ { value: "24", label: "24 posts" },
3238
+ { value: "48", label: "48 posts" }
3239
+ ]
3240
+ }),
3241
+ // Shared toggle fields from fragments (now unscoped)
3242
+ ...uniqueFields.filter((f) => ["showImages", "showExcerpts", "showDates"].includes(f.id)),
3243
+ fieldSchema.parse({
3244
+ id: "readMoreText",
3245
+ type: "text",
3246
+ label: "Read more text",
3247
+ description: "Text for the read more button/link.",
3248
+ defaultValue: "Read more \u2192",
3249
+ maxLength: 40
3250
+ }),
3251
+ fieldSchema.parse({
3252
+ id: "readMoreVariant",
3253
+ type: "select",
3254
+ label: "Read more style",
3255
+ description: "Visual style for the read more button.",
3256
+ defaultValue: "link",
3257
+ options: [
3258
+ { value: "link", label: "Link (minimal)" },
3259
+ { value: "primary", label: "Primary button" },
3260
+ { value: "secondary", label: "Secondary button" },
3261
+ { value: "outline", label: "Outline button" }
3262
+ ]
3263
+ }),
3264
+ fieldSchema.parse({
3265
+ id: "emptyMessage",
3266
+ type: "text",
3267
+ label: "Empty state message",
3268
+ description: "Message shown when no posts are available.",
3269
+ defaultValue: "No posts published yet.",
3270
+ maxLength: 200
3271
+ })
3272
+ ],
3273
+ layout: blogListingLayout,
3274
+ description: "Display a collection of blog posts in grid or stack layout.",
3275
+ contentTypes: ["post"],
3276
+ tags: ["blog", "posts", "articles", "news", "archive", "listing", "feed", "index"],
3277
+ icon: "Newspaper",
3278
+ styleTokens: {
3279
+ background: "surface",
3280
+ spacing: "lg"
3281
+ }
3282
+ });
3283
+ var blogPostListEntrySchema = zod.z.object({
3284
+ id: zod.z.string(),
3285
+ slug: zod.z.string(),
3286
+ path: zod.z.string(),
3287
+ title: zod.z.string(),
3288
+ excerpt: zod.z.string().nullable().optional(),
3289
+ publishedAt: zod.z.string().nullable().optional(),
3290
+ updatedAt: zod.z.string(),
3291
+ status: zod.z.string(),
3292
+ image: zod.z.object({
3293
+ url: zod.z.string(),
3294
+ alt: zod.z.string().optional()
3295
+ }).nullable().optional()
3296
+ });
3297
+ var blogListingBlockDefinition = {
3298
+ manifest: blogListingManifest,
3299
+ dataSchemas: {
3300
+ posts: zod.z.array(blogPostListEntrySchema).optional()
3301
+ },
3302
+ dataLoaders: buildFragmentDataLoaders(gridComposition)
3303
+ // Use grid composition for loader config (same for both)
3304
+ };
3305
+
3306
+ // ../blocks/src/system/blocks/cta-full.ts
3307
+ var ctaComposition = composeFragments([
3308
+ { fragment: ctaCopyFragment },
3309
+ { fragment: ctaRowFragment }
3310
+ ]);
3311
+ var ctaFullManifest = createBlockManifest({
3312
+ id: "block.ctaFull",
3313
+ title: "Full-width CTA",
3314
+ category: "marketing",
3315
+ fragments: [
3316
+ { fragment: ctaCopyFragment, fieldPriority: 0 },
3317
+ { fragment: ctaRowFragment, fieldPriority: 2 }
3318
+ ],
3319
+ layout: styledSection({
3320
+ children: sectionContainer(ctaComposition.layout, {
3321
+ gap: "md",
3322
+ className: "relative text-center"
3323
+ })
3324
+ }),
3325
+ description: "Centered call to action with optional background image.",
3326
+ tags: ["cta", "call-to-action", "button", "conversion", "action", "sign-up", "get-started"],
3327
+ icon: "Target"
3328
+ });
3329
+ var ctaFullBlockDefinition = {
3330
+ manifest: ctaFullManifest
3331
+ };
3332
+ var formComposition = composeFragments([
3333
+ { fragment: formCopyFragment },
3334
+ { fragment: formEmbedFragment }
3335
+ ]);
3336
+ var formLayout = section(
3337
+ { background: "surface", className: "px-6 py-12" },
3338
+ [sectionContainer(formComposition.layout, { gap: "lg" })]
3339
+ );
3340
+ var fields2 = fieldSchema.array().parse(formComposition.fields);
3341
+ var dataLoaders = buildFragmentDataLoaders(formComposition);
3342
+ var formManifest = {
3343
+ name: "block.form",
3344
+ version: "0.1.0",
3345
+ title: "Form",
3346
+ titleSource: "title",
3347
+ description: "Renders a saved form definition with server-side submit.",
3348
+ component: "form.block",
3349
+ fields: fields2,
3350
+ slots: [],
3351
+ styleTokens: { background: "surface", typography: "body", spacing: "md" },
3352
+ behaviours: { supportsThemeSwitching: true, inlineEditing: false, animation: false, paletteHidden: false },
3353
+ category: "interactive",
3354
+ tags: ["form", "contact", "input", "submit", "fields", "signup", "lead-capture"],
3355
+ icon: "FormInput",
3356
+ layout: formLayout
3357
+ };
3358
+ var formDataSchema = zod.z.object({
3359
+ id: zod.z.string(),
3360
+ siteId: zod.z.string(),
3361
+ userId: zod.z.string(),
3362
+ name: zod.z.string(),
3363
+ slug: zod.z.string(),
3364
+ schemaJson: zod.z.any(),
3365
+ settingsJson: zod.z.any().optional(),
3366
+ createdAt: zod.z.string(),
3367
+ updatedAt: zod.z.string()
3368
+ });
3369
+ var formBlockDefinition = {
3370
+ manifest: formManifest,
3371
+ dataSchemas: { form: formDataSchema.optional() },
3372
+ dataLoaders
3373
+ };
3374
+
3375
+ // ../blocks/src/system/blocks/faq.ts
3376
+ var faqComposition = composeFragments([
3377
+ { fragment: faqHeadingFragment, fieldPriority: 0 },
3378
+ { fragment: faqAccordionFragment, fieldPriority: 1 }
3379
+ ]);
3380
+ var faqManifest = createBlockManifest({
3381
+ id: "block.faq",
3382
+ title: "FAQ",
3383
+ titleSource: "title",
3384
+ category: "content",
3385
+ fragments: [
3386
+ { fragment: faqHeadingFragment, fieldPriority: 0 },
3387
+ { fragment: faqAccordionFragment, fieldPriority: 1 }
3388
+ ],
3389
+ layout: styledSection({
3390
+ children: sectionContainer(faqComposition.layout, {
3391
+ gap: "xl",
3392
+ className: "w-full"
3393
+ })
3394
+ }),
3395
+ description: "Accordion of frequently asked questions and answers.",
3396
+ tags: ["faq", "questions", "answers", "help", "support", "accordion", "q&a"],
3397
+ icon: "HelpCircle"
3398
+ });
3399
+ var faqBlockDefinition = {
3400
+ manifest: faqManifest
3401
+ };
3402
+
3403
+ // ../blocks/src/system/transforms/typed.ts
3404
+ function tx(id, options) {
3405
+ return { id, options };
3406
+ }
3407
+ function pipe(...steps) {
3408
+ return steps;
3409
+ }
3410
+ function bindProp(from, opts) {
3411
+ return {
3412
+ $bind: {
3413
+ from,
3414
+ ...opts?.transforms ? { transforms: [...opts.transforms] } : {},
3415
+ ...opts?.fallback !== void 0 ? { fallback: opts.fallback } : {},
3416
+ ...opts?.pick ? { pick: opts.pick } : {}
3417
+ }
3418
+ };
3419
+ }
3420
+
3421
+ // ../blocks/src/system/blocks/site-header.ts
3422
+ var logoRow = link(
3423
+ { href: "/", className: "header-logo flex min-w-0 items-center gap-3 no-underline transition-opacity hover:opacity-80" },
3424
+ [
3425
+ media({ className: "h-10 w-auto transition-all duration-300 [.header-scrolled_&]:h-8" }, when("content.logo"), bind("content.logo")),
3426
+ text({ as: "span", className: "header-logo-text truncate text-lg font-semibold" }, bind("site.title", { fallback: "Your Site" }))
3427
+ ]
3428
+ );
3429
+ var centeredLogoRow = link(
3430
+ { href: "/", className: "header-logo flex items-center justify-center gap-3 text-center no-underline transition-opacity hover:opacity-80" },
3431
+ [
3432
+ media({ className: "h-12 w-auto transition-all duration-300 [.header-scrolled_&]:h-10" }, when("content.logo"), bind("content.logo")),
3433
+ text({ as: "span", className: "header-logo-text text-xl font-semibold" }, bind("site.title", { fallback: "Your Site" }))
3434
+ ]
3435
+ );
3436
+ var createNavRow = (className, align = "end") => navRow({
3437
+ className: `${className} header-nav-row`,
3438
+ align,
3439
+ linkClassName: "header-nav-link inline-flex items-center px-4 py-2 text-sm font-medium transition-theme-standard"
3440
+ });
3441
+ var headerCta = ctaButton({
3442
+ basePath: "menu.ctaItem",
3443
+ whenPath: "menu.ctaItem.label",
3444
+ variantPath: "menu.ctaItem.variant",
3445
+ linkPath: "menu.ctaItem.link",
3446
+ className: "header-cta btn-sm hidden md:inline-flex ml-6"
3447
+ });
3448
+ var classicLayout2 = inline(
3449
+ {
3450
+ className: bindProp("$root.theme.header.maxWidth", {
3451
+ transforms: pipe(tx("layout.maxWidthClass", { base: "flex w-full items-center gap-6 py-4" })),
3452
+ fallback: "container mx-auto flex w-full items-center gap-6 px-6 py-4"
3453
+ }),
3454
+ align: "center"
3455
+ },
3456
+ [
3457
+ logoRow,
3458
+ createNavRow("ml-auto hidden md:flex gap-6"),
3459
+ headerCta
3460
+ ],
3461
+ when("$root.theme.header.variant", { equals: "classic" })
3462
+ );
3463
+ var centeredLayout = stack(
3464
+ {
3465
+ gap: "md",
3466
+ align: "center",
3467
+ className: bindProp("$root.theme.header.maxWidth", {
3468
+ transforms: pipe(tx("layout.maxWidthClass", { base: "flex w-full flex-col items-center gap-5 py-6 text-center" })),
3469
+ fallback: "container mx-auto flex w-full flex-col items-center gap-5 px-6 py-6 text-center"
3470
+ })
3471
+ },
3472
+ [
3473
+ centeredLogoRow,
3474
+ createNavRow("flex flex-wrap justify-center gap-x-6 gap-y-3", "center")
3475
+ ],
3476
+ when("$root.theme.header.variant", { equals: "centered" })
3477
+ );
3478
+ var transparentLayout = inline(
3479
+ {
3480
+ className: bindProp("$root.theme.header.maxWidth", {
3481
+ transforms: pipe(tx("layout.maxWidthClass", { base: "flex w-full items-center gap-6 py-4" })),
3482
+ fallback: "container mx-auto flex w-full items-center gap-6 px-6 py-4"
3483
+ }),
3484
+ align: "center"
3485
+ },
3486
+ [
3487
+ logoRow,
3488
+ createNavRow("ml-auto hidden md:flex gap-6"),
3489
+ headerCta
3490
+ ],
3491
+ when("$root.theme.header.variant", { equals: "transparent" })
3492
+ );
3493
+ var floatingLayout = inline(
3494
+ {
3495
+ className: bindProp("$root.theme.header.maxWidth", {
3496
+ transforms: pipe(tx("layout.maxWidthClass", { base: "header-floating-container absolute left-1/2 top-4 flex w-[calc(100%-2rem)] max-w-7xl -translate-x-1/2 items-center gap-6 px-6 py-3" })),
3497
+ fallback: "header-floating-container absolute left-1/2 top-4 flex w-[calc(100%-2rem)] max-w-7xl -translate-x-1/2 items-center gap-6 px-6 py-3"
3498
+ }),
3499
+ align: "center"
3500
+ },
3501
+ [
3502
+ logoRow,
3503
+ createNavRow("ml-auto hidden md:flex gap-6"),
3504
+ headerCta
3505
+ ],
3506
+ when("$root.theme.header.variant", { equals: "floating" })
3507
+ );
3508
+ var editorialLayout = stack(
3509
+ {
3510
+ gap: "md",
3511
+ align: "center",
3512
+ className: bindProp("$root.theme.header.maxWidth", {
3513
+ transforms: pipe(tx("layout.maxWidthClass", { base: "flex w-full flex-col items-center gap-6 py-6 text-center" })),
3514
+ fallback: "container mx-auto flex w-full flex-col items-center gap-6 px-6 py-6 text-center"
3515
+ })
3516
+ },
3517
+ [
3518
+ centeredLogoRow,
3519
+ createNavRow("flex flex-wrap justify-center gap-x-8 gap-y-3", "center")
3520
+ ],
3521
+ when("$root.theme.header.variant", { equals: "editorial" })
3522
+ );
3523
+ var headerLayout = headerSection(
3524
+ {
3525
+ background: "background/base",
3526
+ className: bindProp("$root.theme.header", {
3527
+ transforms: pipe(tx("layout.headerRootClass")),
3528
+ fallback: "header-root z-40 w-full border-b transition-theme backdrop-blur"
3529
+ }),
3530
+ style: bindProp("$root.theme.header", {
3531
+ transforms: pipe(tx("layout.headerRootStyle")),
3532
+ fallback: mergeStyles(
3533
+ backgroundColorStyle("surface"),
3534
+ textColorStyle("text"),
3535
+ borderColorStyle("border")
3536
+ )
3537
+ })
3538
+ },
3539
+ [
3540
+ classicLayout2,
3541
+ centeredLayout,
3542
+ transparentLayout,
3543
+ floatingLayout,
3544
+ editorialLayout
3545
+ ],
3546
+ props({
3547
+ "data-site-header": "true"
3548
+ })
3549
+ );
3550
+ var siteHeaderManifest = createBlockManifest({
3551
+ id: "block.siteHeader",
3552
+ title: "Site Header",
3553
+ category: "layout",
3554
+ component: "site.header.default",
3555
+ // Skip section styles - this block uses theme-based styling
3556
+ skipSectionStyles: true,
3557
+ layout: headerLayout,
3558
+ description: "Site-wide header with logo, navigation, and optional CTA.",
3559
+ styleTokens: {
3560
+ spacing: "sm"
3561
+ },
3562
+ paletteHidden: true
3563
+ });
3564
+ var siteHeaderBlockDefinition = {
3565
+ manifest: siteHeaderManifest
3566
+ };
3567
+
3568
+ // ../blocks/src/system/blocks/site-footer.ts
3569
+ composeFragments([
3570
+ { fragment: footerLinkGroupsFragment, fieldPriority: 0 },
3571
+ { fragment: footerBottomTextFragment, fieldPriority: 1 }
3572
+ ]);
3573
+ var linkGroupsLayout = () => materializeFragment({ fragment: footerLinkGroupsFragment }).layout;
3574
+ var bottomTextLayout = () => materializeFragment({ fragment: footerBottomTextFragment }).layout;
3575
+ var simpleFooterLayout = stack(
3576
+ {
3577
+ gap: "md",
3578
+ align: "center",
3579
+ className: {
3580
+ $bind: {
3581
+ from: "$root.theme.footer.maxWidth",
3582
+ transforms: [
3583
+ {
3584
+ id: "layout.maxWidthClass",
3585
+ options: { base: "flex w-full flex-col items-center gap-4 py-10 text-center" }
3586
+ }
3587
+ ],
3588
+ fallback: "container mx-auto flex w-full flex-col items-center gap-4 px-6 py-10 text-center"
3589
+ }
3590
+ }
3591
+ },
3592
+ [
3593
+ navRow({ align: "center", className: "flex flex-wrap justify-center gap-x-6 gap-y-3" }),
3594
+ ...bottomTextLayout()
3595
+ ],
3596
+ when("$root.theme.footer.variant", { equals: "simple" })
3597
+ );
3598
+ var columnsFooterLayout = stack(
3599
+ {
3600
+ gap: "xl",
3601
+ align: "start",
3602
+ className: {
3603
+ $bind: {
3604
+ from: "$root.theme.footer.maxWidth",
3605
+ transforms: [
3606
+ {
3607
+ id: "layout.maxWidthClass",
3608
+ options: { base: "flex w-full flex-col gap-10 py-12" }
3609
+ }
3610
+ ],
3611
+ fallback: "container mx-auto flex w-full flex-col gap-10 px-6 py-12"
3612
+ }
3613
+ }
3614
+ },
3615
+ [
3616
+ ...linkGroupsLayout(),
3617
+ inline(
3618
+ { className: "flex w-full flex-wrap items-center justify-between gap-4" },
3619
+ [
3620
+ text({ as: "span", className: "text-sm font-semibold", style: textColorStyle("text") }, bind("site.title", { fallback: "Your Site" })),
3621
+ navRow({ className: "flex flex-wrap justify-end gap-x-6 gap-y-3", align: "end" })
3622
+ ]
3623
+ ),
3624
+ ...bottomTextLayout()
3625
+ ],
3626
+ when("$root.theme.footer.variant", { equals: "columns" })
3627
+ );
3628
+ var footerLayout = section(
3629
+ {
3630
+ background: "background/base",
3631
+ className: {
3632
+ $bind: {
3633
+ from: "$root.theme.footer",
3634
+ transforms: [
3635
+ {
3636
+ id: "layout.footerRootClass"
3637
+ }
3638
+ ],
3639
+ fallback: "w-full border-t transition-theme"
3640
+ }
3641
+ },
3642
+ style: {
3643
+ $bind: {
3644
+ from: "$root.theme.footer",
3645
+ transforms: [
3646
+ {
3647
+ id: "layout.footerRootStyle"
3648
+ }
3649
+ ],
3650
+ fallback: mergeStyles(
3651
+ backgroundColorStyle("surface"),
3652
+ textColorStyle("text"),
3653
+ borderColorStyle("border")
3654
+ )
3655
+ }
3656
+ }
3657
+ },
3658
+ [
3659
+ simpleFooterLayout,
3660
+ columnsFooterLayout
3661
+ ]
3662
+ );
3663
+ var siteFooterManifest = createBlockManifest({
3664
+ id: "block.siteFooter",
3665
+ title: "Site Footer",
3666
+ category: "layout",
3667
+ component: "site.footer.default",
3668
+ // Use fragments for link groups and bottom text
3669
+ fragments: [
3670
+ { fragment: footerLinkGroupsFragment, fieldPriority: 0 },
3671
+ { fragment: footerBottomTextFragment, fieldPriority: 1 }
3672
+ ],
3673
+ // Skip section styles - this block uses theme-based styling
3674
+ skipSectionStyles: true,
3675
+ layout: footerLayout,
3676
+ description: "Site-wide footer with navigation links and optional columns.",
3677
+ styleTokens: {
3678
+ spacing: "md"
3679
+ },
3680
+ paletteHidden: true
3681
+ });
3682
+ var siteFooterBlockDefinition = {
3683
+ manifest: siteFooterManifest
3684
+ };
3685
+
3686
+ // ../blocks/src/system/blocks/testimonials.tsx
3687
+ var testimonialsBackgroundNodes = backgroundLayer("_sectionStyles.background", {
3688
+ imageClassName: "absolute inset-0 -z-10 h-full w-full object-cover opacity-50"
3689
+ });
3690
+ var testimonialsContent = composeFragments([
3691
+ { fragment: testimonialsHeadingFragment },
3692
+ { fragment: testimonialsCarouselFragment }
3693
+ ]);
3694
+ var testimonialsLayout = section(
3695
+ { background: "background/base", className: "px-6 py-16 sm:py-20 md:py-24" },
3696
+ // Semantic: comfortable + extra md padding
3697
+ [
3698
+ ...testimonialsBackgroundNodes,
3699
+ sectionContainer(
3700
+ [...testimonialsContent.layout],
3701
+ { gap: "xl", className: "relative" }
3702
+ )
3703
+ ]
3704
+ );
3705
+ var fields3 = fieldSchema.array().parse([
3706
+ ...testimonialsContent.fields.filter((field) => field.id === "heading" || field.id === "subheading"),
3707
+ ...testimonialsContent.fields.filter(
3708
+ (field) => field.id === "slidesToShow" || field.id === "transition" || field.id === "maxEntries"
3709
+ ),
3710
+ sectionStylesField({
3711
+ id: "_sectionStyles",
3712
+ label: "Section styles"
3713
+ })
3714
+ ]);
3715
+ var dataLoaders2 = buildFragmentDataLoaders(testimonialsContent);
3716
+ var testimonialsManifest = {
3717
+ name: "block.testimonials",
3718
+ version: "0.1.0",
3719
+ title: "Testimonials",
3720
+ description: "Carousel of customer testimonials with optional background styling.",
3721
+ component: "testimonials.carousel",
3722
+ fields: fields3,
3723
+ slots: [],
3724
+ styleTokens: {
3725
+ background: "surface",
3726
+ typography: "body",
3727
+ spacing: "lg"
3728
+ },
3729
+ behaviours: {
3730
+ supportsThemeSwitching: true,
3731
+ inlineEditing: false,
3732
+ animation: true,
3733
+ paletteHidden: false
3734
+ },
3735
+ category: "marketing",
3736
+ tags: ["testimonials", "reviews", "quotes", "feedback", "social-proof", "customers", "carousel"],
3737
+ icon: "MessageSquareQuote",
3738
+ layout: testimonialsLayout
3739
+ };
3740
+ var testimonialsBlockDefinition = {
3741
+ manifest: testimonialsManifest,
3742
+ dataLoaders: dataLoaders2
3743
+ };
3744
+
3745
+ // ../blocks/src/system/blocks/columns.ts
3746
+ var columnsLayout = styledSection({
3747
+ children: sectionContainer(
3748
+ [
3749
+ el(
3750
+ "grid",
3751
+ {
3752
+ cols: {
3753
+ $bind: {
3754
+ from: "content.columns",
3755
+ transforms: [{ id: "array.length" }]
3756
+ }
3757
+ },
3758
+ gap: { $bind: { from: "content.gap" } }
3759
+ },
3760
+ [
3761
+ {
3762
+ type: "stack",
3763
+ props: { gap: "md", className: "h-full" },
3764
+ children: typeBasedLayout(
3765
+ {
3766
+ card: cardFragment.layout,
3767
+ columnContent: columnContentFragment.layout
3768
+ },
3769
+ { itemName: "column" }
3770
+ ),
3771
+ $repeat: {
3772
+ collection: { from: "content.columns" },
3773
+ itemName: "column"
3774
+ }
3775
+ }
3776
+ ]
3777
+ )
3778
+ ],
3779
+ { gap: "md" }
3780
+ ),
3781
+ spacing: "medium"
3782
+ });
3783
+ var columnsManifest = createBlockManifest({
3784
+ id: "block.columns",
3785
+ title: "Columns",
3786
+ category: "layout",
3787
+ component: "columns",
3788
+ // Custom fields for columns configuration
3789
+ additionalFields: [
3790
+ fieldSchema.parse({
3791
+ id: "gap",
3792
+ type: "select",
3793
+ label: "Gap between columns",
3794
+ defaultValue: "lg",
3795
+ options: [
3796
+ { value: "sm", label: "Small" },
3797
+ { value: "md", label: "Medium" },
3798
+ { value: "lg", label: "Large" }
3799
+ ]
3800
+ }),
3801
+ fragmentsToRepeaterField(
3802
+ "columns",
3803
+ "Columns",
3804
+ {
3805
+ card: cardFragment,
3806
+ columnContent: columnContentFragment
3807
+ },
3808
+ {
3809
+ minItems: 0,
3810
+ maxItems: 4,
3811
+ itemLabel: "Column",
3812
+ description: "Add or remove columns (up to 4)"
3813
+ }
3814
+ )
3815
+ ],
3816
+ layout: columnsLayout,
3817
+ description: "Multi-column layout with customizable content per column",
3818
+ tags: ["columns", "grid", "layout", "flexible", "multi-column"],
3819
+ icon: "Columns",
3820
+ styleTokens: {
3821
+ spacing: "lg"
3822
+ }
3823
+ });
3824
+ var columnsBlockDefinition = {
3825
+ manifest: columnsManifest
3826
+ };
3827
+ var appointmentBookingManifest = {
3828
+ name: "block.appointment-booking",
3829
+ version: "2.0.0",
3830
+ // Major version bump - breaking change from v1
3831
+ title: "Appointment Booking",
3832
+ titleSource: "heading",
3833
+ description: "Multi-step appointment booking with customizable form fields",
3834
+ component: "appointment-booking.block",
3835
+ fields: [
3836
+ {
3837
+ id: "formId",
3838
+ type: "reference",
3839
+ label: "Booking Form",
3840
+ description: "Select which booking form to use for collecting customer information",
3841
+ required: true,
3842
+ referenceKind: "bookingForm",
3843
+ allowManualEntry: false
3844
+ },
3845
+ {
3846
+ id: "heading",
3847
+ type: "text",
3848
+ label: "Heading",
3849
+ description: "Main heading shown at the top of the booking flow",
3850
+ required: false,
3851
+ multiline: false,
3852
+ defaultValue: "Book an Appointment"
3853
+ },
3854
+ {
3855
+ id: "description",
3856
+ type: "richText",
3857
+ label: "Description",
3858
+ description: "Optional description or instructions for users",
3859
+ required: false,
3860
+ format: "markdown"
3861
+ }
3862
+ ],
3863
+ slots: [],
3864
+ styleTokens: { background: "surface", typography: "body", spacing: "md" },
3865
+ behaviours: { supportsThemeSwitching: true, inlineEditing: false, animation: false, paletteHidden: false },
3866
+ category: "interactive",
3867
+ tags: ["booking", "appointment", "calendar", "scheduling", "reservation"],
3868
+ icon: "Calendar",
3869
+ layout: [
3870
+ styledSection({
3871
+ children: sectionContainer([
3872
+ // Optional heading
3873
+ text(
3874
+ { as: "h2", size: "2xl", weight: "bold" },
3875
+ bind("content.heading"),
3876
+ when("content.heading")
3877
+ ),
3878
+ // Optional description
3879
+ richText(
3880
+ {},
3881
+ bind("content.description"),
3882
+ when("content.description")
3883
+ ),
3884
+ // The booking form component - loads booking form by ID and renders multi-step flow
3885
+ bookingForm({
3886
+ siteId: { $bind: { from: "$root.siteId" } },
3887
+ formId: { $bind: { from: "content.formId" } },
3888
+ form: { $bind: { from: "data.form" } },
3889
+ services: { $bind: { from: "data.services" } }
3890
+ })
3891
+ ], {
3892
+ gap: "lg"
3893
+ }),
3894
+ spacing: "spacious"
3895
+ })
3896
+ ]
3897
+ };
3898
+ var availabilityDataSchema = zod.z.object({
3899
+ slots: zod.z.array(zod.z.object({
3900
+ startAt: zod.z.string(),
3901
+ endAt: zod.z.string(),
3902
+ resourceId: zod.z.string()
3903
+ }))
3904
+ });
3905
+ var appointmentBookingBlockDefinition = {
3906
+ manifest: appointmentBookingManifest,
3907
+ dataSchemas: { availability: availabilityDataSchema.optional() },
3908
+ dataLoaders: {
3909
+ form: {
3910
+ endpoint: "getPublicFormById",
3911
+ params: {
3912
+ formId: { $bind: { from: "content.formId" } }
3913
+ },
3914
+ mode: "server"
3915
+ },
3916
+ services: {
3917
+ endpoint: "getPublicBookingServices",
3918
+ params: {
3919
+ siteId: { $bind: { from: "$root.siteId" } }
3920
+ },
3921
+ mode: "server"
3922
+ }
3923
+ }
3924
+ };
3925
+ var eventVenueSchema = zod.z.object({
3926
+ id: zod.z.string(),
3927
+ name: zod.z.string(),
3928
+ address: zod.z.string().nullable()
3929
+ });
3930
+ var publicEventSchema = zod.z.object({
3931
+ id: zod.z.string(),
3932
+ seriesId: zod.z.string(),
3933
+ title: zod.z.string(),
3934
+ description: zod.z.string().nullable(),
3935
+ slug: zod.z.string(),
3936
+ startsAt: zod.z.string(),
3937
+ endsAt: zod.z.string(),
3938
+ capacity: zod.z.number().nullable(),
3939
+ registeredCount: zod.z.number(),
3940
+ availableSpots: zod.z.number().nullable(),
3941
+ venue: eventVenueSchema.nullable()
3942
+ });
3943
+ var publicEventsArraySchema = zod.z.array(publicEventSchema);
3944
+
3945
+ // ../blocks/src/system/blocks/events/shared/fields.ts
3946
+ var cardStylingFields = [
3947
+ {
3948
+ id: "cardVariant",
3949
+ type: "select",
3950
+ label: "Card style",
3951
+ description: "Choose a card style from your theme",
3952
+ required: false,
3953
+ multiple: false,
3954
+ defaultValue: "default",
3955
+ options: [
3956
+ { value: "default", label: "Default" },
3957
+ { value: "variant1", label: "Variant 1" },
3958
+ { value: "variant2", label: "Variant 2" }
3959
+ ]
3960
+ },
3961
+ {
3962
+ id: "buttonVariant",
3963
+ type: "select",
3964
+ label: "Button style",
3965
+ description: "Choose a button style from your theme",
3966
+ required: false,
3967
+ multiple: false,
3968
+ defaultValue: "primary",
3969
+ options: [
3970
+ { value: "primary", label: "Primary" },
3971
+ { value: "secondary", label: "Secondary" },
3972
+ { value: "outline", label: "Outline" },
3973
+ { value: "link", label: "Link" }
3974
+ ]
3975
+ },
3976
+ {
3977
+ id: "buttonText",
3978
+ type: "text",
3979
+ label: "Button text",
3980
+ description: "Text for the event action button",
3981
+ required: false,
3982
+ multiline: false,
3983
+ defaultValue: "View event",
3984
+ maxLength: 40
3985
+ }
3986
+ ];
3987
+ var eventDisplayFields = [
3988
+ {
3989
+ id: "showVenue",
3990
+ type: "boolean",
3991
+ label: "Show venue",
3992
+ description: "Display venue name and address",
3993
+ required: false,
3994
+ defaultValue: true
3995
+ },
3996
+ {
3997
+ id: "showCapacity",
3998
+ type: "boolean",
3999
+ label: "Show available spots",
4000
+ description: "Display remaining capacity",
4001
+ required: false,
4002
+ defaultValue: true
4003
+ }
4004
+ ];
4005
+ var emptyStateField = {
4006
+ id: "emptyMessage",
4007
+ type: "text",
4008
+ label: "Empty state message",
4009
+ description: "Message when no upcoming events",
4010
+ required: false,
4011
+ multiline: false,
4012
+ defaultValue: "No upcoming events scheduled.",
4013
+ maxLength: 200
4014
+ };
4015
+ var sectionHeaderFields = (defaultHeading) => [
4016
+ {
4017
+ id: "heading",
4018
+ type: "text",
4019
+ label: "Heading",
4020
+ description: "Main heading for the events section",
4021
+ required: false,
4022
+ multiline: false,
4023
+ defaultValue: defaultHeading
4024
+ },
4025
+ {
4026
+ id: "description",
4027
+ type: "richText",
4028
+ label: "Description",
4029
+ description: "Optional introductory text",
4030
+ required: false,
4031
+ format: "markdown"
4032
+ }
4033
+ ];
4034
+ var layoutField = {
4035
+ id: "layout",
4036
+ type: "select",
4037
+ label: "Layout",
4038
+ description: "Choose how events are displayed",
4039
+ required: false,
4040
+ multiple: false,
4041
+ defaultValue: "grid",
4042
+ options: [
4043
+ { value: "grid", label: "Grid (cards)" },
4044
+ { value: "stack", label: "Stack (vertical list)" }
4045
+ ]
4046
+ };
4047
+ var columnsField = (options = ["2", "3", "4"]) => ({
4048
+ id: "columns",
4049
+ type: "select",
4050
+ label: "Columns",
4051
+ description: "Number of columns in grid layout",
4052
+ required: false,
4053
+ multiple: false,
4054
+ defaultValue: "3",
4055
+ ui: {
4056
+ visibleWhen: { field: "layout", equals: "grid" }
4057
+ },
4058
+ options: options.map((n) => ({ value: n, label: `${n} columns` }))
4059
+ });
4060
+
4061
+ // ../blocks/src/system/blocks/event-registration.ts
4062
+ var eventRegistrationManifest = {
4063
+ name: "block.event-registration",
4064
+ version: "1.0.0",
4065
+ title: "Event Registration",
4066
+ titleSource: "heading",
4067
+ description: "Multi-step event registration form",
4068
+ component: "event-registration.block",
4069
+ fields: [
4070
+ {
4071
+ id: "heading",
4072
+ type: "text",
4073
+ label: "Heading",
4074
+ description: "Main heading for the registration form",
4075
+ required: false,
4076
+ multiline: false,
4077
+ defaultValue: "Register for this event"
4078
+ },
4079
+ {
4080
+ id: "description",
4081
+ type: "richText",
4082
+ label: "Description",
4083
+ description: "Optional introductory text",
4084
+ required: false,
4085
+ format: "markdown"
4086
+ },
4087
+ {
4088
+ id: "maxTickets",
4089
+ type: "select",
4090
+ label: "Max tickets per registration",
4091
+ description: "Maximum tickets a person can register for",
4092
+ required: false,
4093
+ multiple: false,
4094
+ defaultValue: "5",
4095
+ options: [
4096
+ { value: "1", label: "1 ticket" },
4097
+ { value: "2", label: "2 tickets" },
4098
+ { value: "3", label: "3 tickets" },
4099
+ { value: "5", label: "5 tickets" },
4100
+ { value: "10", label: "10 tickets" }
4101
+ ]
4102
+ },
4103
+ {
4104
+ id: "showVenue",
4105
+ type: "boolean",
4106
+ label: "Show venue",
4107
+ description: "Display venue information",
4108
+ required: false,
4109
+ defaultValue: true
4110
+ },
4111
+ {
4112
+ id: "showCapacity",
4113
+ type: "boolean",
4114
+ label: "Show available spots",
4115
+ description: "Display remaining capacity",
4116
+ required: false,
4117
+ defaultValue: true
4118
+ },
4119
+ {
4120
+ id: "successMessage",
4121
+ type: "text",
4122
+ label: "Success message",
4123
+ description: "Message shown after successful registration",
4124
+ required: false,
4125
+ multiline: true,
4126
+ defaultValue: "Thank you for registering! Check your email for confirmation details.",
4127
+ maxLength: 500
4128
+ },
4129
+ {
4130
+ id: "waitlistMessage",
4131
+ type: "text",
4132
+ label: "Waitlist message",
4133
+ description: "Message when added to waitlist",
4134
+ required: false,
4135
+ multiline: true,
4136
+ defaultValue: "You've been added to the waitlist. We'll notify you if a spot opens up.",
4137
+ maxLength: 500
4138
+ },
4139
+ {
4140
+ id: "buttonText",
4141
+ type: "text",
4142
+ label: "Submit button text",
4143
+ description: "Text for the registration submit button",
4144
+ required: false,
4145
+ multiline: false,
4146
+ defaultValue: "Complete Registration",
4147
+ maxLength: 40
4148
+ },
4149
+ {
4150
+ id: "buttonVariant",
4151
+ type: "select",
4152
+ label: "Button style",
4153
+ description: "Choose a button style from your theme",
4154
+ required: false,
4155
+ multiple: false,
4156
+ defaultValue: "primary",
4157
+ options: [
4158
+ { value: "primary", label: "Primary" },
4159
+ { value: "secondary", label: "Secondary" },
4160
+ { value: "outline", label: "Outline" }
4161
+ ]
4162
+ }
4163
+ ],
4164
+ slots: [],
4165
+ styleTokens: { background: "surface", typography: "body", spacing: "md" },
4166
+ behaviours: { supportsThemeSwitching: true, inlineEditing: false, animation: false, paletteHidden: false },
4167
+ category: "interactive",
4168
+ tags: ["events", "registration", "booking", "form", "signup"],
4169
+ icon: "ClipboardList",
4170
+ layout: [
4171
+ styledSection({
4172
+ spacing: "spacious",
4173
+ children: sectionContainer([
4174
+ // Optional heading
4175
+ text(
4176
+ { as: "h2", size: "2xl", weight: "bold" },
4177
+ bind("content.heading"),
4178
+ when("content.heading")
4179
+ ),
4180
+ // Optional description
4181
+ richText(
4182
+ {},
4183
+ bind("content.description"),
4184
+ when("content.description")
4185
+ ),
4186
+ // Event registration form component
4187
+ eventRegistration({
4188
+ // Site context for API calls
4189
+ siteId: { $bind: { from: "$root.siteId" } },
4190
+ // Pre-selected occurrence from route context (if available)
4191
+ occurrenceContext: { $bind: { from: "$root.occurrenceContext" } },
4192
+ // Event data from content entry context (if on event template)
4193
+ contentEntry: { $bind: { from: "$root.contentEntry" } },
4194
+ // Content configuration
4195
+ maxTickets: { $bind: { from: "content.maxTickets", fallback: "5" } },
4196
+ showVenue: { $bind: { from: "content.showVenue" } },
4197
+ showCapacity: { $bind: { from: "content.showCapacity" } },
4198
+ successMessage: { $bind: { from: "content.successMessage" } },
4199
+ waitlistMessage: { $bind: { from: "content.waitlistMessage" } },
4200
+ buttonText: { $bind: { from: "content.buttonText", fallback: "Complete Registration" } },
4201
+ buttonVariant: { $bind: { from: "content.buttonVariant", fallback: "primary" } },
4202
+ // Event data from loader
4203
+ events: { $bind: { from: "data.events" } }
4204
+ })
4205
+ ], {
4206
+ gap: "lg"
4207
+ })
4208
+ })
4209
+ ]
4210
+ };
4211
+ var occurrenceContextSchema = zod.z.object({
4212
+ /** Unique identifier for this occurrence */
4213
+ id: zod.z.string(),
4214
+ /** The event series this occurrence belongs to */
4215
+ seriesId: zod.z.string(),
4216
+ /** ISO 8601 datetime when the occurrence starts */
4217
+ startsAt: zod.z.string(),
4218
+ /** ISO 8601 datetime when the occurrence ends */
4219
+ endsAt: zod.z.string(),
4220
+ /** Override capacity for this specific occurrence (null = use series default) */
4221
+ capacityOverride: zod.z.number().nullable().optional(),
4222
+ /** Field-level overrides: { title?, description?, venueId?, etc. } */
4223
+ overrides: zod.z.record(zod.z.string(), zod.z.unknown()).nullable().optional()
4224
+ }).nullable();
4225
+ var eventRegistrationBlockDefinition = {
4226
+ manifest: eventRegistrationManifest,
4227
+ dataSchemas: {
4228
+ events: zod.z.array(publicEventSchema).optional(),
4229
+ occurrenceContext: occurrenceContextSchema.optional()
4230
+ },
4231
+ dataLoaders: {
4232
+ // Load events for occurrence selection
4233
+ // This is needed when block is on a standalone page or event template without occurrence in URL
4234
+ events: {
4235
+ endpoint: "listPublicEvents",
4236
+ params: {
4237
+ siteId: { $bind: { from: "$root.siteId" } },
4238
+ limit: "50",
4239
+ // Get more events for selection
4240
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
4241
+ },
4242
+ mode: "server"
4243
+ }
4244
+ }
4245
+ };
4246
+
4247
+ // ../blocks/src/system/blocks/events/event-spotlight.ts
4248
+ var eventSpotlightManifest = {
4249
+ name: "block.event-spotlight",
4250
+ version: "1.0.0",
4251
+ title: "Event Spotlight",
4252
+ titleSource: "heading",
4253
+ description: "Feature a few upcoming events on your homepage",
4254
+ component: "event-spotlight.block",
4255
+ fields: [
4256
+ ...sectionHeaderFields("Upcoming Events"),
4257
+ {
4258
+ id: "maxEvents",
4259
+ type: "select",
4260
+ label: "Events to display",
4261
+ description: "Number of events to show",
4262
+ required: false,
4263
+ multiple: false,
4264
+ defaultValue: "3",
4265
+ options: [
4266
+ { value: "1", label: "1 event" },
4267
+ { value: "2", label: "2 events" },
4268
+ { value: "3", label: "3 events" },
4269
+ { value: "4", label: "4 events" },
4270
+ { value: "5", label: "5 events" },
4271
+ { value: "6", label: "6 events" }
4272
+ ]
4273
+ },
4274
+ layoutField,
4275
+ columnsField(["2", "3"]),
4276
+ ...cardStylingFields,
4277
+ ...eventDisplayFields,
4278
+ emptyStateField
4279
+ ],
4280
+ slots: [],
4281
+ styleTokens: { background: "surface", typography: "body", spacing: "md" },
4282
+ behaviours: { supportsThemeSwitching: true, inlineEditing: false, animation: false, paletteHidden: false },
4283
+ category: "interactive",
4284
+ tags: ["events", "featured", "homepage", "spotlight", "upcoming"],
4285
+ icon: "Star",
4286
+ layout: [
4287
+ styledSection({
4288
+ children: sectionContainer([
4289
+ // Optional heading
4290
+ text(
4291
+ { as: "h2", size: "2xl", weight: "bold" },
4292
+ bind("content.heading"),
4293
+ when("content.heading")
4294
+ ),
4295
+ // Optional description
4296
+ richText(
4297
+ {},
4298
+ bind("content.description"),
4299
+ when("content.description")
4300
+ ),
4301
+ // Event spotlight component
4302
+ el("event-spotlight", {
4303
+ events: { $bind: { from: "data.events" } },
4304
+ layout: { $bind: { from: "content.layout", fallback: "grid" } },
4305
+ columns: { $bind: { from: "content.columns", fallback: "3" } },
4306
+ cardVariant: { $bind: { from: "content.cardVariant", fallback: "default" } },
4307
+ buttonVariant: { $bind: { from: "content.buttonVariant", fallback: "primary" } },
4308
+ buttonText: { $bind: { from: "content.buttonText", fallback: "View event" } },
4309
+ showVenue: { $bind: { from: "content.showVenue" } },
4310
+ showCapacity: { $bind: { from: "content.showCapacity" } },
4311
+ emptyMessage: { $bind: { from: "content.emptyMessage" } }
4312
+ })
4313
+ ], {
4314
+ gap: "lg"
4315
+ }),
4316
+ spacing: "spacious"
4317
+ })
4318
+ ]
4319
+ };
4320
+ var eventSpotlightBlockDefinition = {
4321
+ manifest: eventSpotlightManifest,
4322
+ dataSchemas: {
4323
+ events: publicEventsArraySchema.optional()
4324
+ },
4325
+ dataLoaders: {
4326
+ events: {
4327
+ endpoint: "listPublicEvents",
4328
+ params: {
4329
+ siteId: { $bind: { from: "$root.siteId" } },
4330
+ limit: { $bind: { from: "content.maxEvents", fallback: "3" } },
4331
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
4332
+ },
4333
+ mode: "server"
4334
+ }
4335
+ }
4336
+ };
4337
+
4338
+ // ../blocks/src/system/blocks/events/event-listing.ts
4339
+ var eventListingManifest = {
4340
+ name: "block.event-listing",
4341
+ version: "1.0.0",
4342
+ title: "Event Listing",
4343
+ titleSource: "heading",
4344
+ description: "Paginated list of all upcoming events",
4345
+ component: "event-listing.block",
4346
+ fields: [
4347
+ ...sectionHeaderFields("All Events"),
4348
+ layoutField,
4349
+ columnsField(["2", "3", "4"]),
4350
+ {
4351
+ id: "eventsPerPage",
4352
+ type: "select",
4353
+ label: "Events per page",
4354
+ description: "Number of events to load at a time",
4355
+ required: false,
4356
+ multiple: false,
4357
+ defaultValue: "12",
4358
+ options: [
4359
+ { value: "6", label: "6 events" },
4360
+ { value: "12", label: "12 events" },
4361
+ { value: "24", label: "24 events" }
4362
+ ]
4363
+ },
4364
+ {
4365
+ id: "loadMoreText",
4366
+ type: "text",
4367
+ label: "Load more button text",
4368
+ description: "Text for the pagination button",
4369
+ required: false,
4370
+ multiline: false,
4371
+ defaultValue: "Load more events",
4372
+ maxLength: 40
4373
+ },
4374
+ ...cardStylingFields,
4375
+ ...eventDisplayFields,
4376
+ emptyStateField
4377
+ ],
4378
+ slots: [],
4379
+ styleTokens: { background: "surface", typography: "body", spacing: "md" },
4380
+ behaviours: { supportsThemeSwitching: true, inlineEditing: false, animation: false, paletteHidden: false },
4381
+ category: "interactive",
4382
+ tags: ["events", "listing", "paginated", "archive", "all-events"],
4383
+ icon: "List",
4384
+ layout: [
4385
+ styledSection({
4386
+ children: sectionContainer([
4387
+ // Optional heading
4388
+ text(
4389
+ { as: "h2", size: "2xl", weight: "bold" },
4390
+ bind("content.heading"),
4391
+ when("content.heading")
4392
+ ),
4393
+ // Optional description
4394
+ richText(
4395
+ {},
4396
+ bind("content.description"),
4397
+ when("content.description")
4398
+ ),
4399
+ // Event listing component with pagination
4400
+ el("event-listing", {
4401
+ events: { $bind: { from: "data.events" } },
4402
+ siteId: { $bind: { from: "$root.siteId" } },
4403
+ layout: { $bind: { from: "content.layout", fallback: "grid" } },
4404
+ columns: { $bind: { from: "content.columns", fallback: "3" } },
4405
+ eventsPerPage: { $bind: { from: "content.eventsPerPage", fallback: "12" } },
4406
+ loadMoreText: { $bind: { from: "content.loadMoreText", fallback: "Load more events" } },
4407
+ cardVariant: { $bind: { from: "content.cardVariant", fallback: "default" } },
4408
+ buttonVariant: { $bind: { from: "content.buttonVariant", fallback: "primary" } },
4409
+ buttonText: { $bind: { from: "content.buttonText", fallback: "View event" } },
4410
+ showVenue: { $bind: { from: "content.showVenue" } },
4411
+ showCapacity: { $bind: { from: "content.showCapacity" } },
4412
+ emptyMessage: { $bind: { from: "content.emptyMessage" } }
4413
+ })
4414
+ ], {
4415
+ gap: "lg"
4416
+ }),
4417
+ spacing: "spacious"
4418
+ })
4419
+ ]
4420
+ };
4421
+ var eventListingBlockDefinition = {
4422
+ manifest: eventListingManifest,
4423
+ dataSchemas: {
4424
+ events: publicEventsArraySchema.optional()
4425
+ },
4426
+ dataLoaders: {
4427
+ events: {
4428
+ endpoint: "listPublicEvents",
4429
+ params: {
4430
+ siteId: { $bind: { from: "$root.siteId" } },
4431
+ limit: { $bind: { from: "content.eventsPerPage", fallback: "12" } },
4432
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
4433
+ },
4434
+ mode: "server"
4435
+ }
4436
+ }
4437
+ };
4438
+
4439
+ // ../blocks/src/system/blocks/events/event-calendar.ts
4440
+ var eventCalendarManifest = {
4441
+ name: "block.event-calendar",
4442
+ version: "2.0.0",
4443
+ title: "Event Calendar",
4444
+ titleSource: "heading",
4445
+ description: "Interactive calendar showing events by month or week",
4446
+ component: "event-calendar.block",
4447
+ fields: [
4448
+ ...sectionHeaderFields("Event Calendar"),
4449
+ {
4450
+ id: "calendarView",
4451
+ type: "select",
4452
+ label: "Default view",
4453
+ description: "Initial calendar view",
4454
+ required: false,
4455
+ multiple: false,
4456
+ defaultValue: "month",
4457
+ options: [
4458
+ { value: "month", label: "Month" },
4459
+ { value: "week", label: "Week" }
4460
+ ]
4461
+ },
4462
+ {
4463
+ id: "startOfWeek",
4464
+ type: "select",
4465
+ label: "Week starts on",
4466
+ description: "First day of the week",
4467
+ required: false,
4468
+ multiple: false,
4469
+ defaultValue: "monday",
4470
+ options: [
4471
+ { value: "sunday", label: "Sunday" },
4472
+ { value: "monday", label: "Monday" }
4473
+ ]
4474
+ },
4475
+ ...cardStylingFields.filter((f) => f.id === "buttonVariant"),
4476
+ // Only button variant for calendar
4477
+ emptyStateField
4478
+ ],
4479
+ slots: [],
4480
+ styleTokens: { background: "surface", typography: "body", spacing: "md" },
4481
+ behaviours: { supportsThemeSwitching: true, inlineEditing: false, animation: false, paletteHidden: false },
4482
+ category: "interactive",
4483
+ tags: ["events", "calendar", "month", "week", "schedule", "interactive"],
4484
+ icon: "CalendarDays",
4485
+ layout: [
4486
+ styledSection({
4487
+ children: sectionContainer([
4488
+ // Optional heading
4489
+ text(
4490
+ { as: "h2", size: "2xl", weight: "bold" },
4491
+ bind("content.heading"),
4492
+ when("content.heading")
4493
+ ),
4494
+ // Optional description
4495
+ richText(
4496
+ {},
4497
+ bind("content.description"),
4498
+ when("content.description")
4499
+ ),
4500
+ // Event calendar grid component
4501
+ el("event-calendar", {
4502
+ events: { $bind: { from: "data.events" } },
4503
+ siteId: { $bind: { from: "$root.siteId" } },
4504
+ calendarView: { $bind: { from: "content.calendarView", fallback: "month" } },
4505
+ startOfWeek: { $bind: { from: "content.startOfWeek", fallback: "monday" } },
4506
+ buttonVariant: { $bind: { from: "content.buttonVariant", fallback: "primary" } },
4507
+ emptyMessage: { $bind: { from: "content.emptyMessage" } }
4508
+ })
4509
+ ], {
4510
+ gap: "lg"
4511
+ }),
4512
+ spacing: "spacious"
4513
+ })
4514
+ ]
4515
+ };
4516
+ var eventCalendarBlockDefinition = {
4517
+ manifest: eventCalendarManifest,
4518
+ dataSchemas: {
4519
+ events: publicEventsArraySchema.optional()
4520
+ },
4521
+ dataLoaders: {
4522
+ events: {
4523
+ endpoint: "listPublicEvents",
4524
+ params: {
4525
+ siteId: { $bind: { from: "$root.siteId" } },
4526
+ // Pre-fetch 3 months of events (server-side)
4527
+ // The client will use the same API to fetch more as user navigates
4528
+ limit: 100,
4529
+ // High limit for calendar view
4530
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } }
4531
+ },
4532
+ mode: "server"
4533
+ }
4534
+ }
4535
+ };
4536
+ var embedFields = [
4537
+ // Section heading
4538
+ fieldSchema.parse({
4539
+ id: "heading",
4540
+ type: "text",
4541
+ label: "Heading",
4542
+ description: "Optional section title displayed above the content.",
4543
+ required: false,
4544
+ maxLength: 120
4545
+ }),
4546
+ fieldSchema.parse({
4547
+ id: "subheading",
4548
+ type: "richText",
4549
+ label: "Subheading",
4550
+ description: "Optional section description below the heading.",
4551
+ required: false,
4552
+ format: "markdown",
4553
+ ui: { variant: "limited" }
4554
+ }),
4555
+ // Content source
4556
+ fieldSchema.parse({
4557
+ id: "contentType",
4558
+ type: "contentTypeSelect",
4559
+ label: "Content type",
4560
+ description: "Select the content type to embed.",
4561
+ required: true,
4562
+ filter: "all"
4563
+ }),
4564
+ fieldSchema.parse({
4565
+ id: "mode",
4566
+ type: "select",
4567
+ label: "Selection mode",
4568
+ description: "How to select which entries to display.",
4569
+ required: true,
4570
+ defaultValue: "query",
4571
+ options: [
4572
+ { value: "query", label: "Query (automatic)" },
4573
+ { value: "manual", label: "Manual (pick entries)" }
4574
+ ]
4575
+ }),
4576
+ // Query mode options
4577
+ fieldSchema.parse({
4578
+ id: "limit",
4579
+ type: "select",
4580
+ label: "Limit",
4581
+ description: "Maximum number of entries to display.",
4582
+ defaultValue: "10",
4583
+ options: [
4584
+ { value: "3", label: "3 entries" },
4585
+ { value: "6", label: "6 entries" },
4586
+ { value: "10", label: "10 entries" },
4587
+ { value: "20", label: "20 entries" },
4588
+ { value: "50", label: "50 entries" }
4589
+ ],
4590
+ ui: {
4591
+ visibleWhen: { field: "mode", equals: "query" }
4592
+ }
4593
+ }),
4594
+ fieldSchema.parse({
4595
+ id: "orderBy",
4596
+ type: "select",
4597
+ label: "Order by",
4598
+ description: "How to sort the entries.",
4599
+ defaultValue: "order",
4600
+ options: [
4601
+ { value: "order", label: "Custom order field" },
4602
+ { value: "newest", label: "Newest first" },
4603
+ { value: "oldest", label: "Oldest first" },
4604
+ { value: "title", label: "Title (A-Z)" }
4605
+ ],
4606
+ ui: {
4607
+ visibleWhen: { field: "mode", equals: "query" }
4608
+ }
4609
+ }),
4610
+ // Manual mode options - entry references
4611
+ fieldSchema.parse({
4612
+ id: "entries",
4613
+ type: "repeater",
4614
+ label: "Entries",
4615
+ description: "Select specific entries to display.",
4616
+ required: false,
4617
+ itemLabel: "Entry",
4618
+ minItems: 1,
4619
+ maxItems: 20,
4620
+ schema: {
4621
+ fields: [
4622
+ {
4623
+ id: "entryId",
4624
+ type: "entryPicker",
4625
+ label: "Entry",
4626
+ description: "Select a content entry to embed.",
4627
+ required: true,
4628
+ ui: {
4629
+ contentTypeField: "contentType"
4630
+ }
4631
+ }
4632
+ ]
4633
+ },
4634
+ ui: {
4635
+ visibleWhen: { field: "mode", equals: "manual" }
4636
+ }
4637
+ }),
4638
+ // Layout selection - SDK sites can provide custom options via blockFieldOptions
4639
+ fieldSchema.parse({
4640
+ id: "layout",
4641
+ type: "select",
4642
+ label: "Layout",
4643
+ description: "Select a layout style for displaying the embedded content.",
4644
+ defaultValue: "list",
4645
+ options: [
4646
+ { value: "list", label: "List" },
4647
+ { value: "grid", label: "Grid" },
4648
+ { value: "carousel", label: "Carousel" }
4649
+ ],
4650
+ ui: {
4651
+ widget: "sdkSelect"
4652
+ // Use SDK-aware widget for site-specific options
4653
+ }
4654
+ }),
4655
+ // Empty state
4656
+ fieldSchema.parse({
4657
+ id: "emptyMessage",
4658
+ type: "text",
4659
+ label: "Empty state message",
4660
+ description: "Message shown when no entries are available.",
4661
+ defaultValue: "No entries found.",
4662
+ maxLength: 200
4663
+ })
4664
+ ];
4665
+ var embedLayout = styledSection({
4666
+ children: sectionContainer([
4667
+ // Heading
4668
+ text(
4669
+ {
4670
+ as: "h2",
4671
+ className: "text-3xl font-bold",
4672
+ style: textColorStyle("neutral-900")
4673
+ },
4674
+ bind("content.heading"),
4675
+ when("content.heading")
4676
+ ),
4677
+ // Subheading
4678
+ el(
4679
+ "richText",
4680
+ {
4681
+ className: "mt-2 text-lg",
4682
+ style: textColorStyle("neutral-600")
4683
+ },
4684
+ void 0,
4685
+ bind("content.subheading"),
4686
+ when("content.subheading")
4687
+ ),
4688
+ // Default entry rendering - a simple list
4689
+ // Sites should override this with custom layouts via blockOverrides
4690
+ // Outer stack provides gap between repeated entry cards
4691
+ stack(
4692
+ { gap: "md", className: "mt-8" },
4693
+ [
4694
+ // Entry card (repeated for each entry)
4695
+ stack(
4696
+ {
4697
+ gap: "sm",
4698
+ className: "rounded-lg border border-neutral-200 bg-white p-4 shadow-sm"
4699
+ },
4700
+ [
4701
+ text(
4702
+ {
4703
+ as: "h3",
4704
+ className: "text-lg font-semibold",
4705
+ style: textColorStyle("neutral-900")
4706
+ },
4707
+ bind("entry.title")
4708
+ ),
4709
+ text(
4710
+ {
4711
+ as: "p",
4712
+ className: "text-sm",
4713
+ style: textColorStyle("neutral-500")
4714
+ },
4715
+ bind("entry.slug"),
4716
+ when("entry.slug")
4717
+ )
4718
+ ],
4719
+ repeat("data.entries", "entry")
4720
+ )
4721
+ ],
4722
+ when("data.entries")
4723
+ ),
4724
+ // Empty state
4725
+ stack(
4726
+ {
4727
+ gap: "sm",
4728
+ className: "py-12 text-center"
4729
+ },
4730
+ [
4731
+ text(
4732
+ {
4733
+ as: "p",
4734
+ className: "text-base",
4735
+ style: textColorStyle("neutral-400")
4736
+ },
4737
+ bind("content.emptyMessage", { fallback: "No entries found." })
4738
+ )
4739
+ ],
4740
+ when("data.entries", { not: true })
4741
+ )
4742
+ ]),
4743
+ spacing: "comfortable"
4744
+ });
4745
+ var embedManifest = createBlockManifest({
4746
+ id: "block.embed",
4747
+ title: "Embed Content",
4748
+ category: "content",
4749
+ titleSource: "heading",
4750
+ additionalFields: embedFields,
4751
+ layout: embedLayout,
4752
+ description: "Embed content entries from any content type. Sites provide custom layouts via block overrides.",
4753
+ tags: ["embed", "content", "dynamic", "collection", "entries", "listing"],
4754
+ icon: "LayoutList",
4755
+ styleTokens: {
4756
+ spacing: "lg"
4757
+ }
4758
+ });
4759
+ var embedEntrySchema = zod.z.object({
4760
+ id: zod.z.string(),
4761
+ title: zod.z.string(),
4762
+ slug: zod.z.string().nullable().optional(),
4763
+ content: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
4764
+ publishedAt: zod.z.string().nullable().optional()
4765
+ });
4766
+ var embedBlockDefinition = {
4767
+ manifest: embedManifest,
4768
+ dataSchemas: {
4769
+ entries: zod.z.array(embedEntrySchema).optional()
4770
+ },
4771
+ dataLoaders: {
4772
+ entries: {
4773
+ endpoint: "listPublishedEntries",
4774
+ params: {
4775
+ type: { $bind: { from: "contentType" } },
4776
+ siteId: { $bind: { from: "$root.siteId" } },
4777
+ limit: { $bind: { from: "limit", fallback: "10" } },
4778
+ orderBy: { $bind: { from: "orderBy", fallback: "order" } },
4779
+ stage: { $bind: { from: "$root.previewStage", fallback: "published" } },
4780
+ // Manual mode entry IDs - loader should handle this
4781
+ entryIds: { $bind: { from: "entries" } },
4782
+ mode: { $bind: { from: "mode" } }
4783
+ },
4784
+ mode: "server"
4785
+ }
4786
+ }
4787
+ };
4788
+
4789
+ // ../blocks/src/system/blocks/index.ts
4790
+ var systemBlockDefinitions = [
4791
+ heroBlockDefinition,
4792
+ bodyTextBlockDefinition,
4793
+ blogPostBlockDefinition,
4794
+ blogPlaceholderBlockDefinition,
4795
+ blogListingBlockDefinition,
4796
+ ctaFullBlockDefinition,
4797
+ formBlockDefinition,
4798
+ faqBlockDefinition,
4799
+ siteHeaderBlockDefinition,
4800
+ siteFooterBlockDefinition,
4801
+ testimonialsBlockDefinition,
4802
+ columnsBlockDefinition,
4803
+ appointmentBookingBlockDefinition,
4804
+ eventRegistrationBlockDefinition,
4805
+ // Event display blocks (3 specialized blocks)
4806
+ eventSpotlightBlockDefinition,
4807
+ eventListingBlockDefinition,
4808
+ eventCalendarBlockDefinition,
4809
+ // Content embedding
4810
+ embedBlockDefinition
4811
+ ];
4812
+ var defaultsRegistered = false;
4813
+ function ensureSystemBlockDefinitionsRegistered() {
4814
+ if (defaultsRegistered) {
4815
+ return;
4816
+ }
4817
+ defaultsRegistered = true;
4818
+ for (const definition of systemBlockDefinitions) {
4819
+ registerBlockDefinition(definition);
4820
+ }
4821
+ }
4822
+
4823
+ // ../blocks/src/system/registry.ts
4824
+ var REGISTRY_SYMBOL2 = Symbol.for("@riverbankcms/blocks/system/definitions");
4825
+ var globalScope2 = globalThis;
4826
+ if (!globalScope2[REGISTRY_SYMBOL2]) {
4827
+ globalScope2[REGISTRY_SYMBOL2] = /* @__PURE__ */ new Map();
4828
+ }
4829
+ var blockStore = globalScope2[REGISTRY_SYMBOL2];
4830
+ function ensureDefaults() {
4831
+ ensureSystemBlockDefinitionsRegistered();
4832
+ }
4833
+ function registerBlockDefinition(definition) {
4834
+ registerManifest(definition.manifest);
4835
+ blockStore.set(definition.manifest.name, definition);
4836
+ return definition;
4837
+ }
4838
+ function getBlockDefinition(name) {
4839
+ ensureDefaults();
4840
+ return blockStore.get(name);
4841
+ }
4842
+
4843
+ // src/data/prefetchBlockData.ts
13
4844
  var SUPPORTED_LOADER_ENDPOINTS = [
14
4845
  "listPublishedEntries",
15
4846
  "getPublishedEntryPreview",
@@ -96,7 +4927,7 @@ var blockFieldOptionsSchema = zod.z.record(
96
4927
  )
97
4928
  ).optional();
98
4929
  var blockFieldExtensionSchema = zod.z.object({
99
- fields: blocks.fieldSchema.array().min(1, "At least one field is required")
4930
+ fields: fieldSchema.array().min(1, "At least one field is required")
100
4931
  }).refine(
101
4932
  (data) => {
102
4933
  return data.fields.every((field) => {
@@ -118,7 +4949,7 @@ function validateFieldIdConflicts(blockFieldExtensions) {
118
4949
  if (!blockFieldExtensions) return [];
119
4950
  const conflicts = [];
120
4951
  for (const [blockId, extension] of Object.entries(blockFieldExtensions)) {
121
- const definition = blocks.getBlockDefinition(blockId);
4952
+ const definition = getBlockDefinition(blockId);
122
4953
  if (!definition) {
123
4954
  conflicts.push({
124
4955
  blockId,
@@ -128,9 +4959,9 @@ function validateFieldIdConflicts(blockFieldExtensions) {
128
4959
  continue;
129
4960
  }
130
4961
  const existingFieldIds = /* @__PURE__ */ new Set();
131
- const collectFieldIds = (fields) => {
132
- if (!fields) return;
133
- for (const field of fields) {
4962
+ const collectFieldIds = (fields4) => {
4963
+ if (!fields4) return;
4964
+ for (const field of fields4) {
134
4965
  existingFieldIds.add(field.id);
135
4966
  if (field.type === "group" || field.type === "modal") {
136
4967
  collectFieldIds(field.schema?.fields);
@@ -164,11 +4995,11 @@ var sdkCustomBlockSchema = zod.z.object({
164
4995
  title: zod.z.string().min(1, "Title is required"),
165
4996
  titleSource: zod.z.string().optional(),
166
4997
  description: zod.z.string().optional(),
167
- category: blocks.blockCategoryEnum,
4998
+ category: blockCategoryEnum,
168
4999
  icon: zod.z.string().optional(),
169
5000
  tags: zod.z.array(zod.z.string()).optional(),
170
5001
  // Reuse the exact field schema from @riverbankcms/blocks - all field types supported
171
- fields: blocks.fieldSchema.array().min(1, "Custom blocks must have at least one field"),
5002
+ fields: fieldSchema.array().min(1, "Custom blocks must have at least one field"),
172
5003
  // Data loaders for CMS endpoints
173
5004
  dataLoaders: sdkDataLoadersSchema
174
5005
  }).refine(