@xiwebdesign/xicms-core 4.17.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/dist/XiCMSProvider.d.ts +7 -0
  2. package/dist/XiCMSProvider.js +32 -7
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/blockEditors/GenericEditor.d.ts +22 -0
  5. package/dist/admin/blockEditors/GenericEditor.js +201 -0
  6. package/dist/admin/blockEditors/GenericEditor.js.map +1 -0
  7. package/dist/admin/blockEditors/SymbolRefEditor.d.ts +2 -0
  8. package/dist/admin/blockEditors/SymbolRefEditor.js +55 -0
  9. package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -0
  10. package/dist/admin/blockEditors/index.d.ts +11 -6
  11. package/dist/admin/blockEditors/index.js +49 -620
  12. package/dist/admin/blockEditors/index.js.map +1 -1
  13. package/dist/admin/customBlocks/CustomBlocksPanel.d.ts +3 -0
  14. package/dist/admin/customBlocks/CustomBlocksPanel.js +145 -0
  15. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -0
  16. package/dist/admin/lib/compressImage.d.ts +4 -2
  17. package/dist/admin/lib/compressImage.js +7 -5
  18. package/dist/admin/lib/compressImage.js.map +1 -1
  19. package/dist/admin/lib/mediaUsage.d.ts +4 -3
  20. package/dist/admin/lib/mediaUsage.js +21 -10
  21. package/dist/admin/lib/mediaUsage.js.map +1 -1
  22. package/dist/admin/lib/symbolsClient.d.ts +13 -0
  23. package/dist/admin/lib/symbolsClient.js +51 -0
  24. package/dist/admin/lib/symbolsClient.js.map +1 -0
  25. package/dist/admin/lib/upload.d.ts +4 -0
  26. package/dist/admin/lib/upload.js +26 -6
  27. package/dist/admin/lib/upload.js.map +1 -1
  28. package/dist/admin/lib/zodFields.d.ts +18 -0
  29. package/dist/admin/lib/zodFields.js +65 -2
  30. package/dist/admin/lib/zodFields.js.map +1 -1
  31. package/dist/admin/media/MediaManager.js +23 -7
  32. package/dist/admin/media/MediaManager.js.map +1 -1
  33. package/dist/admin/media/MediaPicker.js +12 -2
  34. package/dist/admin/media/MediaPicker.js.map +1 -1
  35. package/dist/admin/panelRegistry.js +3 -0
  36. package/dist/admin/panelRegistry.js.map +1 -1
  37. package/dist/admin/settings/SettingsPanel.js +6 -1
  38. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  39. package/dist/admin/settings/SnapshotsCard.d.ts +3 -0
  40. package/dist/admin/settings/SnapshotsCard.js +98 -0
  41. package/dist/admin/settings/SnapshotsCard.js.map +1 -0
  42. package/dist/admin/visual/TranslationPanel.d.ts +13 -0
  43. package/dist/admin/visual/TranslationPanel.js +73 -0
  44. package/dist/admin/visual/TranslationPanel.js.map +1 -0
  45. package/dist/admin/visual/VisualEditor.js +146 -18
  46. package/dist/admin/visual/VisualEditor.js.map +1 -1
  47. package/dist/admin.css +1 -1
  48. package/dist/agentFiles/catalog.d.ts +21 -0
  49. package/dist/agentFiles/catalog.js +78 -0
  50. package/dist/agentFiles/catalog.js.map +1 -0
  51. package/dist/agentFiles/files.d.ts +59 -0
  52. package/dist/agentFiles/files.js +109 -0
  53. package/dist/agentFiles/files.js.map +1 -0
  54. package/dist/blocks/builtins/Agenda.d.ts +6 -0
  55. package/dist/blocks/builtins/Agenda.js +14 -6
  56. package/dist/blocks/builtins/Agenda.js.map +1 -1
  57. package/dist/blocks/builtins/BeforeAfter.js +1 -0
  58. package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
  59. package/dist/blocks/builtins/Breadcrumbs.js +5 -1
  60. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  61. package/dist/blocks/builtins/Cards.js +4 -2
  62. package/dist/blocks/builtins/Cards.js.map +1 -1
  63. package/dist/blocks/builtins/Contact.js +2 -1
  64. package/dist/blocks/builtins/Contact.js.map +1 -1
  65. package/dist/blocks/builtins/Cta.js +1 -0
  66. package/dist/blocks/builtins/Cta.js.map +1 -1
  67. package/dist/blocks/builtins/Elements.js +1 -0
  68. package/dist/blocks/builtins/Elements.js.map +1 -1
  69. package/dist/blocks/builtins/Faq.js +5 -4
  70. package/dist/blocks/builtins/Faq.js.map +1 -1
  71. package/dist/blocks/builtins/Gallery.js +2 -2
  72. package/dist/blocks/builtins/Gallery.js.map +1 -1
  73. package/dist/blocks/builtins/Hero.js +2 -2
  74. package/dist/blocks/builtins/Hero.js.map +1 -1
  75. package/dist/blocks/builtins/Jobs.d.ts +8 -1
  76. package/dist/blocks/builtins/Jobs.js +38 -21
  77. package/dist/blocks/builtins/Jobs.js.map +1 -1
  78. package/dist/blocks/builtins/MapBlock.js +31 -4
  79. package/dist/blocks/builtins/MapBlock.js.map +1 -1
  80. package/dist/blocks/builtins/PdfBlock.js +2 -3
  81. package/dist/blocks/builtins/PdfBlock.js.map +1 -1
  82. package/dist/blocks/builtins/Portfolio.js +2 -2
  83. package/dist/blocks/builtins/Portfolio.js.map +1 -1
  84. package/dist/blocks/builtins/Pricing.d.ts +2 -0
  85. package/dist/blocks/builtins/Pricing.js +18 -10
  86. package/dist/blocks/builtins/Pricing.js.map +1 -1
  87. package/dist/blocks/builtins/Sections.js +4 -2
  88. package/dist/blocks/builtins/Sections.js.map +1 -1
  89. package/dist/blocks/builtins/Slider.js +21 -3
  90. package/dist/blocks/builtins/Slider.js.map +1 -1
  91. package/dist/blocks/builtins/SymbolRef.d.ts +2 -0
  92. package/dist/blocks/builtins/SymbolRef.js +61 -0
  93. package/dist/blocks/builtins/SymbolRef.js.map +1 -0
  94. package/dist/blocks/builtins/Testimonials.js +21 -18
  95. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  96. package/dist/blocks/builtins/WhatsappChat.js +1 -1
  97. package/dist/blocks/builtins/WhatsappChat.js.map +1 -1
  98. package/dist/blocks/builtins/Widgets.js +2 -3
  99. package/dist/blocks/builtins/Widgets.js.map +1 -1
  100. package/dist/blocks/builtins/lib/ics.js +1 -1
  101. package/dist/blocks/builtins/lib/ics.js.map +1 -1
  102. package/dist/blocks/builtins/lib/openingHours.d.ts +4 -1
  103. package/dist/blocks/builtins/lib/openingHours.js +11 -5
  104. package/dist/blocks/builtins/lib/openingHours.js.map +1 -1
  105. package/dist/blocks/customTemplate/definition.d.ts +7 -0
  106. package/dist/blocks/customTemplate/definition.js +34 -0
  107. package/dist/blocks/customTemplate/definition.js.map +1 -0
  108. package/dist/blocks/customTemplate/interpret.d.ts +10 -0
  109. package/dist/blocks/customTemplate/interpret.js +136 -0
  110. package/dist/blocks/customTemplate/interpret.js.map +1 -0
  111. package/dist/blocks/registry.d.ts +8 -0
  112. package/dist/blocks/registry.js.map +1 -1
  113. package/dist/demo/demoStore.d.ts +11 -1
  114. package/dist/demo/demoStore.js +50 -0
  115. package/dist/demo/demoStore.js.map +1 -1
  116. package/dist/firebase/client.js +2 -1
  117. package/dist/firebase/client.js.map +1 -1
  118. package/dist/i18n/overlay.d.ts +61 -0
  119. package/dist/i18n/overlay.js +90 -0
  120. package/dist/i18n/overlay.js.map +1 -0
  121. package/dist/index.d.ts +11 -9
  122. package/dist/index.js +34 -9
  123. package/dist/index.js.map +1 -1
  124. package/dist/modules/builtins.js +3 -2
  125. package/dist/modules/builtins.js.map +1 -1
  126. package/dist/modules/consent/useConsent.js +15 -3
  127. package/dist/modules/consent/useConsent.js.map +1 -1
  128. package/dist/schema/blocks.d.ts +183 -3
  129. package/dist/schema/blocks.js +12 -1
  130. package/dist/schema/blocks.js.map +1 -1
  131. package/dist/schema/customBlocks.d.ts +47 -0
  132. package/dist/schema/customBlocks.js +32 -0
  133. package/dist/schema/customBlocks.js.map +1 -0
  134. package/dist/schema/fields.d.ts +122 -0
  135. package/dist/schema/fields.js +120 -0
  136. package/dist/schema/fields.js.map +1 -0
  137. package/dist/schema/forms.d.ts +6 -6
  138. package/dist/schema/pages.d.ts +15 -1
  139. package/dist/schema/pages.js +6 -2
  140. package/dist/schema/pages.js.map +1 -1
  141. package/dist/schema/siteConfig.d.ts +14 -8
  142. package/dist/schema/siteConfig.js +2 -1
  143. package/dist/schema/siteConfig.js.map +1 -1
  144. package/dist/schema/symbols.d.ts +205 -0
  145. package/dist/schema/symbols.js +18 -0
  146. package/dist/schema/symbols.js.map +1 -0
  147. package/dist/server/index.d.ts +2 -1
  148. package/dist/server/index.js +24 -0
  149. package/dist/server/index.js.map +1 -1
  150. package/dist/server/pages.d.ts +21 -0
  151. package/dist/server/pages.js +77 -0
  152. package/dist/server/pages.js.map +1 -1
  153. package/dist/server/seo.d.ts +12 -3
  154. package/dist/server/seo.js +23 -11
  155. package/dist/server/seo.js.map +1 -1
  156. package/dist/styles.css +62 -6
  157. package/package.json +1 -1
  158. package/dist/components/EditableImage.d.ts +0 -18
  159. package/dist/components/EditableImage.js +0 -62
  160. package/dist/components/EditableImage.js.map +0 -1
  161. package/dist/components/EditableRichText.d.ts +0 -14
  162. package/dist/components/EditableRichText.js +0 -26
  163. package/dist/components/EditableRichText.js.map +0 -1
  164. package/dist/components/EditableText.d.ts +0 -21
  165. package/dist/components/EditableText.js +0 -25
  166. package/dist/components/EditableText.js.map +0 -1
  167. package/dist/content/ContentProvider.d.ts +0 -23
  168. package/dist/content/ContentProvider.js +0 -86
  169. package/dist/content/ContentProvider.js.map +0 -1
@@ -0,0 +1,109 @@
1
+ import { z } from "zod";
2
+ import { PageSchema } from "../schema/pages";
3
+ import { SymbolSchema } from "../schema/symbols";
4
+ import { CustomBlockSchema } from "../schema/customBlocks";
5
+ function stableStringify(value) {
6
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
7
+ if (value && typeof value === "object") {
8
+ const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : 1);
9
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
10
+ }
11
+ return JSON.stringify(value) ?? "null";
12
+ }
13
+ function contentHash(value) {
14
+ const s = stableStringify(value);
15
+ let h = 0xcbf29ce484222325n;
16
+ const prime = 0x100000001b3n;
17
+ for (let i = 0; i < s.length; i++) {
18
+ h ^= BigInt(s.charCodeAt(i));
19
+ h = h * prime & 0xffffffffffffffffn;
20
+ }
21
+ return h.toString(16).padStart(16, "0");
22
+ }
23
+ function emptyManifest(siteId, pulledAt) {
24
+ return { format: "xicms-files", version: 1, siteId, pulledAt, entities: {} };
25
+ }
26
+ function decideSync(base, local, remote) {
27
+ if (base === void 0) return remote === void 0 ? "create" : remote === local ? "skip" : "conflict";
28
+ if (remote === void 0) return "remote-deleted";
29
+ if (remote === local) return "skip";
30
+ if (local === base) return "skip";
31
+ if (remote === base) return "push";
32
+ return "conflict";
33
+ }
34
+ function pagePathToFile(path) {
35
+ const clean = path.replace(/^\/+|\/+$/g, "");
36
+ return clean === "" ? "pages/index.yaml" : `pages/${clean}.yaml`;
37
+ }
38
+ function idToFilename(id) {
39
+ return id.replace(/[^a-zA-Z0-9_-]/g, "_");
40
+ }
41
+ function serializePage(page) {
42
+ return {
43
+ id: page.id,
44
+ title: page.title,
45
+ slug: page.slug,
46
+ path: page.path,
47
+ parentId: page.parentId ?? null,
48
+ order: page.order ?? 0,
49
+ ...page.seo && Object.keys(page.seo).length > 0 ? { seo: page.seo } : {},
50
+ blocks: page.blocks ?? [],
51
+ ...page.translations && Object.keys(page.translations).length > 0 ? { translations: page.translations } : {}
52
+ };
53
+ }
54
+ const PageFileSchema = PageSchema.extend({ id: z.string().min(1).max(64) });
55
+ function parsePageFile(obj) {
56
+ const res = PageFileSchema.safeParse(obj);
57
+ if (!res.success) {
58
+ const i = res.error.issues[0];
59
+ throw new Error(`ongeldige pagina: ${i?.path.join(".") || "(root)"}: ${i?.message}`);
60
+ }
61
+ return res.data;
62
+ }
63
+ function serializeSymbol(symbol) {
64
+ return {
65
+ id: symbol.id,
66
+ name: symbol.name,
67
+ blocks: symbol.blocks ?? [],
68
+ ...symbol.translations && Object.keys(symbol.translations).length > 0 ? { translations: symbol.translations } : {}
69
+ };
70
+ }
71
+ const SymbolFileSchema = SymbolSchema.extend({ id: z.string().min(1).max(120) });
72
+ function parseSymbolFile(obj) {
73
+ const res = SymbolFileSchema.safeParse(obj);
74
+ if (!res.success) {
75
+ const i = res.error.issues[0];
76
+ throw new Error(`ongeldig symbool: ${i?.path.join(".") || "(root)"}: ${i?.message}`);
77
+ }
78
+ return res.data;
79
+ }
80
+ function serializeCustomBlock(def) {
81
+ return {
82
+ meta: { type: def.type, label: def.label, icon: def.icon, version: def.version, fields: def.fields },
83
+ template: def.template,
84
+ css: def.css
85
+ };
86
+ }
87
+ function parseCustomBlockFiles(meta, template, css) {
88
+ const res = CustomBlockSchema.safeParse({ ...meta, template, css });
89
+ if (!res.success) {
90
+ const i = res.error.issues[0];
91
+ throw new Error(`ongeldige bouwsteen: ${i?.path.join(".") || "(root)"}: ${i?.message}`);
92
+ }
93
+ return res.data;
94
+ }
95
+ export {
96
+ contentHash,
97
+ decideSync,
98
+ emptyManifest,
99
+ idToFilename,
100
+ pagePathToFile,
101
+ parseCustomBlockFiles,
102
+ parsePageFile,
103
+ parseSymbolFile,
104
+ serializeCustomBlock,
105
+ serializePage,
106
+ serializeSymbol,
107
+ stableStringify
108
+ };
109
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agentFiles/files.ts"],"sourcesContent":["/**\n * Agent-bestandslaag (5.0, primo-concept \"DB ⇄ files\"): pure serialisatie-, hash- en\n * sync-logica voor de `pull-site`/`push-site`-scripts. Een site wordt een map met YAML/HTML-\n * bestanden die een coding-agent direct kan bewerken; `.xicms/manifest.json` onthoudt per\n * entiteit een inhouds-hash van het pull-moment zodat push conflicten kan detecteren\n * (drie-wegen: basis/lokaal/remote) in plaats van stil werk te overschrijven.\n *\n * Alleen pure functies — geen I/O, geen Firebase — zodat dit unit-testbaar is en zowel de\n * scripts als toekomstige tooling (bv. een cloud-agent) er op kunnen bouwen.\n */\nimport { z } from \"zod\";\nimport { PageSchema, type Page } from \"../schema/pages\";\nimport { SymbolSchema, type Symbol } from \"../schema/symbols\";\nimport { CustomBlockSchema, type CustomBlock } from \"../schema/customBlocks\";\n\n/* ---------------- hashing ---------------- */\n\n/** Stabiele JSON-representatie (gesorteerde sleutels, zonder undefined) voor hashing. */\nexport function stableStringify(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(stableStringify).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : 1));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/** FNV-1a 64-bit (hex) — snel, dependency-vrij en client-safe (geen node:crypto). Dit is een\n * wijzigingsdetector, geen beveiligingshash. */\nexport function contentHash(value: unknown): string {\n const s = stableStringify(value);\n let h = 0xcbf29ce484222325n;\n const prime = 0x100000001b3n;\n for (let i = 0; i < s.length; i++) {\n h ^= BigInt(s.charCodeAt(i));\n h = (h * prime) & 0xffffffffffffffffn;\n }\n return h.toString(16).padStart(16, \"0\");\n}\n\n/* ---------------- manifest + sync-beslissing ---------------- */\n\nexport interface ManifestEntry {\n /** Relatief bestandspad binnen de site-map (bv. \"pages/diensten/web.yaml\"). */\n file: string;\n /** contentHash van de geserialiseerde entiteit op pull-moment (de \"basis\"). */\n hash: string;\n}\n\nexport interface Manifest {\n format: \"xicms-files\";\n version: 1;\n siteId: string;\n pulledAt: string;\n /** Sleutels: \"<soort>:<id>\", bv. \"page:abc123\", \"symbol:cta\", \"customBlock:custom-usps\". */\n entities: Record<string, ManifestEntry>;\n}\n\nexport function emptyManifest(siteId: string, pulledAt: string): Manifest {\n return { format: \"xicms-files\", version: 1, siteId, pulledAt, entities: {} };\n}\n\nexport type SyncAction = \"push\" | \"skip\" | \"conflict\" | \"create\" | \"remote-deleted\";\n\n/**\n * Drie-wegen-beslissing per entiteit:\n * - basis (manifest-hash van pull), lokaal (bestand nu), remote (database nu).\n * - lokaal ongewijzigd → skip (niets te pushen), ook als remote intussen veranderde.\n * - lokaal gewijzigd + remote ongewijzigd → push.\n * - beide gewijzigd → conflict (push weigert zonder --force).\n * - geen basis (nieuw bestand) → create; bestaat remote al ook → conflict.\n * - basis wel, remote weg → remote-deleted (conflict-achtig: iemand verwijderde de entiteit).\n */\nexport function decideSync(base: string | undefined, local: string, remote: string | undefined): SyncAction {\n if (base === undefined) return remote === undefined ? \"create\" : remote === local ? \"skip\" : \"conflict\";\n if (remote === undefined) return \"remote-deleted\";\n if (remote === local) return \"skip\"; // al identiek — niets te doen\n if (local === base) return \"skip\"; // lokaal niets gewijzigd — remote-wijziging laten staan\n if (remote === base) return \"push\"; // alleen lokaal gewijzigd\n return \"conflict\"; // beide kanten gewijzigd\n}\n\n/* ---------------- bestandsnamen ---------------- */\n\n/** Pagina-pad → relatief YAML-pad: \"/\" → \"pages/index.yaml\", \"/diensten/web\" → \"pages/diensten/web.yaml\". */\nexport function pagePathToFile(path: string): string {\n const clean = path.replace(/^\\/+|\\/+$/g, \"\");\n return clean === \"\" ? \"pages/index.yaml\" : `pages/${clean}.yaml`;\n}\n\n/** Veilige bestandsnaam voor een id (symbolen kunnen elk Firestore-id hebben). */\nexport function idToFilename(id: string): string {\n return id.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n}\n\n/* ---------------- serialisatie (entiteit ⇄ plain object) ---------------- */\n\n/** Pagina-doc → bestandsobject met vaste, leesbare sleutelvolgorde. */\nexport function serializePage(page: Page & { id: string }): Record<string, unknown> {\n return {\n id: page.id,\n title: page.title,\n slug: page.slug,\n path: page.path,\n parentId: page.parentId ?? null,\n order: page.order ?? 0,\n ...(page.seo && Object.keys(page.seo).length > 0 ? { seo: page.seo } : {}),\n blocks: page.blocks ?? [],\n ...(page.translations && Object.keys(page.translations).length > 0 ? { translations: page.translations } : {}),\n };\n}\n\nconst PageFileSchema = PageSchema.extend({ id: z.string().min(1).max(64) });\n\n/** Bestandsobject → gevalideerde pagina. Gooit met een leesbare melding bij ongeldige inhoud. */\nexport function parsePageFile(obj: unknown): Page & { id: string } {\n const res = PageFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldige pagina: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\nexport function serializeSymbol(symbol: Symbol & { id: string }): Record<string, unknown> {\n return {\n id: symbol.id,\n name: symbol.name,\n blocks: symbol.blocks ?? [],\n ...(symbol.translations && Object.keys(symbol.translations).length > 0 ? { translations: symbol.translations } : {}),\n };\n}\n\nconst SymbolFileSchema = SymbolSchema.extend({ id: z.string().min(1).max(120) });\n\nexport function parseSymbolFile(obj: unknown): Symbol & { id: string } {\n const res = SymbolFileSchema.safeParse(obj);\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldig symbool: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n\n/** Custom block → drie bestanden: block.yaml (meta + Field-IR), template.html, style.css. */\nexport function serializeCustomBlock(def: CustomBlock): { meta: Record<string, unknown>; template: string; css: string } {\n return {\n meta: { type: def.type, label: def.label, icon: def.icon, version: def.version, fields: def.fields },\n template: def.template,\n css: def.css,\n };\n}\n\nexport function parseCustomBlockFiles(meta: unknown, template: string, css: string): CustomBlock {\n const res = CustomBlockSchema.safeParse({ ...(meta as Record<string, unknown>), template, css });\n if (!res.success) {\n const i = res.error.issues[0];\n throw new Error(`ongeldige bouwsteen: ${i?.path.join(\".\") || \"(root)\"}: ${i?.message}`);\n }\n return res.data;\n}\n"],"mappings":"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","names":[]}
@@ -8,6 +8,12 @@ export interface AgendaEvent {
8
8
  description?: string;
9
9
  url?: string;
10
10
  }
11
+ /**
12
+ * Parseert een event-datum als LOKALE tijd. Cruciaal voor datum-zonder-tijd: `new Date("2026-08-01")`
13
+ * is per spec UTC-middernacht, waardoor west-van-UTC-bezoekers "31 juli" te zien kregen (en de
14
+ * server/client bij hydration konden verschillen). "2026-08-01" is hier gewoon 1 augustus, overal.
15
+ */
16
+ export declare function parseEventDate(value: string): Date;
11
17
  /** Toont een datum leesbaar (nl-NL). Valt terug op de ruwe string bij ongeldige invoer. */
12
18
  export declare function formatEventDate(value: string): string;
13
19
  /**
@@ -3,9 +3,14 @@ import { AgendaBlockSchema } from "../../schema/blocks";
3
3
  import { defineBlock } from "../registry";
4
4
  import { Heading } from "../ui";
5
5
  import { icsDataUrl } from "./lib/ics";
6
+ function parseEventDate(value) {
7
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
8
+ if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
9
+ return new Date(value);
10
+ }
6
11
  function formatEventDate(value) {
7
12
  if (!value) return "";
8
- const d = new Date(value);
13
+ const d = parseEventDate(value);
9
14
  if (Number.isNaN(d.getTime())) return value;
10
15
  const hasTime = value.includes("T");
11
16
  return d.toLocaleDateString("nl-NL", {
@@ -22,13 +27,13 @@ function selectAgendaEvents(events, opts = {}) {
22
27
  const today = /* @__PURE__ */ new Date();
23
28
  today.setHours(0, 0, 0, 0);
24
29
  list = list.filter((e) => {
25
- const d = new Date(e.end || e.start);
30
+ const d = parseEventDate(e.end || e.start);
26
31
  return Number.isNaN(d.getTime()) || d >= today;
27
32
  });
28
33
  }
29
34
  list = [...list].sort((a, b) => {
30
- const ta = new Date(a.start).getTime();
31
- const tb = new Date(b.start).getTime();
35
+ const ta = parseEventDate(a.start).getTime();
36
+ const tb = parseEventDate(b.start).getTime();
32
37
  if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
33
38
  if (Number.isNaN(ta)) return 1;
34
39
  if (Number.isNaN(tb)) return -1;
@@ -40,7 +45,9 @@ function eventJsonLd(events) {
40
45
  return {
41
46
  "@context": "https://schema.org",
42
47
  "@type": "ItemList",
43
- itemListElement: events.filter((e) => e.title && e.start).map((e, i) => ({
48
+ // Google vereist `location` voor Event-rich-results: events zonder locatie renderen wel op
49
+ // de pagina, maar blijven uit de JSON-LD (voorkomt gegarandeerde Search-Console-errors).
50
+ itemListElement: events.filter((e) => e.title && e.start && e.location).map((e, i) => ({
44
51
  "@type": "ListItem",
45
52
  position: i + 1,
46
53
  item: {
@@ -48,7 +55,7 @@ function eventJsonLd(events) {
48
55
  name: e.title,
49
56
  startDate: e.start,
50
57
  ...e.end ? { endDate: e.end } : {},
51
- ...e.location ? { location: { "@type": "Place", name: e.location } } : {},
58
+ location: { "@type": "Place", name: e.location },
52
59
  ...e.description ? { description: e.description } : {},
53
60
  ...e.url ? { url: e.url } : {}
54
61
  }
@@ -97,6 +104,7 @@ const agendaBlock = defineBlock({
97
104
  export {
98
105
  agendaBlock,
99
106
  formatEventDate,
107
+ parseEventDate,
100
108
  selectAgendaEvents
101
109
  };
102
110
  //# sourceMappingURL=Agenda.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Agenda.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { AgendaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { icsDataUrl } from \"./lib/ics\";\n\ntype Agenda = z.infer<typeof AgendaBlockSchema>;\n\n/** Eén event zoals zowel het Agenda-blok als de Agenda-widget het kennen. */\nexport interface AgendaEvent {\n title: string;\n start: string;\n end?: string;\n location?: string;\n description?: string;\n url?: string;\n}\n\n/** Toont een datum leesbaar (nl-NL). Valt terug op de ruwe string bij ongeldige invoer. */\nexport function formatEventDate(value: string): string {\n if (!value) return \"\";\n const d = new Date(value);\n if (Number.isNaN(d.getTime())) return value;\n const hasTime = value.includes(\"T\");\n return d.toLocaleDateString(\"nl-NL\", {\n weekday: \"short\",\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n ...(hasTime ? { hour: \"2-digit\", minute: \"2-digit\" } : {}),\n });\n}\n\n/**\n * Selecteert de te tonen events: filtert lege titels, optioneel verlopen events, sorteert\n * chronologisch (events zonder geldige datum achteraan, in invoervolgorde) en limiteert. Gedeeld\n * door het volwaardige Agenda-blok en de compacte Agenda-widget — zo \"haalt\" de widget alles\n * uit dezelfde logica als de volledige versie.\n */\nexport function selectAgendaEvents<T extends AgendaEvent>(\n events: T[] | undefined,\n opts: { hidePast?: boolean; limit?: number } = {},\n): T[] {\n let list = (events ?? []).filter((e) => e.title);\n if (opts.hidePast) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n list = list.filter((e) => {\n const d = new Date(e.end || e.start);\n return Number.isNaN(d.getTime()) || d >= today;\n });\n }\n list = [...list].sort((a, b) => {\n const ta = new Date(a.start).getTime();\n const tb = new Date(b.start).getTime();\n if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;\n if (Number.isNaN(ta)) return 1;\n if (Number.isNaN(tb)) return -1;\n return ta - tb;\n });\n return typeof opts.limit === \"number\" ? list.slice(0, opts.limit) : list;\n}\n\nfunction eventJsonLd(events: Agenda[\"events\"]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n itemListElement: events\n .filter((e) => e.title && e.start)\n .map((e, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n item: {\n \"@type\": \"Event\",\n name: e.title,\n startDate: e.start,\n ...(e.end ? { endDate: e.end } : {}),\n ...(e.location ? { location: { \"@type\": \"Place\", name: e.location } } : {}),\n ...(e.description ? { description: e.description } : {}),\n ...(e.url ? { url: e.url } : {}),\n },\n })),\n };\n}\n\n/** Agenda/evenementen. Server-safe (geen client-state); `hidePast` filtert verlopen events. */\nfunction AgendaRender({ block }: BlockRenderProps<Agenda>) {\n const events = selectAgendaEvents(block.events, { hidePast: block.hidePast });\n if (events.length === 0) return null;\n\n return (\n <section className=\"xicms-agenda\">\n {/* Alleen de kop is inline bewerkbaar: de items worden gefilterd/gesorteerd, dus hun\n render-index wijst niet betrouwbaar naar block.events[i]. */}\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-agenda__list\">\n {events.map((e, i) => (\n <li key={i} className=\"xicms-agenda__item\">\n <time className=\"xicms-agenda__date\" dateTime={e.start}>\n {formatEventDate(e.start)}\n </time>\n <div className=\"xicms-agenda__body\">\n <strong className=\"xicms-agenda__title\">\n {e.url ? <a href={e.url}>{e.title}</a> : e.title}\n </strong>\n {e.location && <span className=\"xicms-agenda__location\">{e.location}</span>}\n {e.description && <p className=\"xicms-agenda__desc\">{e.description}</p>}\n {(() => {\n const ics = icsDataUrl(e);\n return ics ? (\n <a className=\"xicms-agenda__ics\" href={ics} download={`${(e.title || \"evenement\").toLowerCase().replace(/\\s+/g, \"-\").slice(0, 40)}.ics`}>\n 📅 Zet in agenda\n </a>\n ) : null;\n })()}\n </div>\n </li>\n ))}\n </ul>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(eventJsonLd(events)) }}\n />\n )}\n </section>\n );\n}\n\nexport const agendaBlock = defineBlock({\n type: \"agenda\",\n schema: AgendaBlockSchema,\n Render: AgendaRender,\n label: \"Agenda\",\n icon: \"📅\",\n defaults: () => ({\n heading: \"Agenda\",\n events: [{ title: \"Evenement\", start: \"\" }],\n hidePast: true,\n jsonLd: true,\n }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Agenda.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { AgendaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\nimport { icsDataUrl } from \"./lib/ics\";\n\ntype Agenda = z.infer<typeof AgendaBlockSchema>;\n\n/** Eén event zoals zowel het Agenda-blok als de Agenda-widget het kennen. */\nexport interface AgendaEvent {\n title: string;\n start: string;\n end?: string;\n location?: string;\n description?: string;\n url?: string;\n}\n\n/**\n * Parseert een event-datum als LOKALE tijd. Cruciaal voor datum-zonder-tijd: `new Date(\"2026-08-01\")`\n * is per spec UTC-middernacht, waardoor west-van-UTC-bezoekers \"31 juli\" te zien kregen (en de\n * server/client bij hydration konden verschillen). \"2026-08-01\" is hier gewoon 1 augustus, overal.\n */\nexport function parseEventDate(value: string): Date {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value.trim());\n if (m) return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n return new Date(value);\n}\n\n/** Toont een datum leesbaar (nl-NL). Valt terug op de ruwe string bij ongeldige invoer. */\nexport function formatEventDate(value: string): string {\n if (!value) return \"\";\n const d = parseEventDate(value);\n if (Number.isNaN(d.getTime())) return value;\n const hasTime = value.includes(\"T\");\n return d.toLocaleDateString(\"nl-NL\", {\n weekday: \"short\",\n day: \"numeric\",\n month: \"long\",\n year: \"numeric\",\n ...(hasTime ? { hour: \"2-digit\", minute: \"2-digit\" } : {}),\n });\n}\n\n/**\n * Selecteert de te tonen events: filtert lege titels, optioneel verlopen events, sorteert\n * chronologisch (events zonder geldige datum achteraan, in invoervolgorde) en limiteert. Gedeeld\n * door het volwaardige Agenda-blok en de compacte Agenda-widget — zo \"haalt\" de widget alles\n * uit dezelfde logica als de volledige versie.\n */\nexport function selectAgendaEvents<T extends AgendaEvent>(\n events: T[] | undefined,\n opts: { hidePast?: boolean; limit?: number } = {},\n): T[] {\n let list = (events ?? []).filter((e) => e.title);\n if (opts.hidePast) {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n list = list.filter((e) => {\n const d = parseEventDate(e.end || e.start);\n return Number.isNaN(d.getTime()) || d >= today;\n });\n }\n list = [...list].sort((a, b) => {\n const ta = parseEventDate(a.start).getTime();\n const tb = parseEventDate(b.start).getTime();\n if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;\n if (Number.isNaN(ta)) return 1;\n if (Number.isNaN(tb)) return -1;\n return ta - tb;\n });\n return typeof opts.limit === \"number\" ? list.slice(0, opts.limit) : list;\n}\n\nfunction eventJsonLd(events: Agenda[\"events\"]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n // Google vereist `location` voor Event-rich-results: events zonder locatie renderen wel op\n // de pagina, maar blijven uit de JSON-LD (voorkomt gegarandeerde Search-Console-errors).\n itemListElement: events\n .filter((e) => e.title && e.start && e.location)\n .map((e, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n item: {\n \"@type\": \"Event\",\n name: e.title,\n startDate: e.start,\n ...(e.end ? { endDate: e.end } : {}),\n location: { \"@type\": \"Place\", name: e.location },\n ...(e.description ? { description: e.description } : {}),\n ...(e.url ? { url: e.url } : {}),\n },\n })),\n };\n}\n\n/** Agenda/evenementen. Server-safe (geen client-state); `hidePast` filtert verlopen events. */\nfunction AgendaRender({ block }: BlockRenderProps<Agenda>) {\n const events = selectAgendaEvents(block.events, { hidePast: block.hidePast });\n if (events.length === 0) return null;\n\n return (\n <section className=\"xicms-agenda\">\n {/* Alleen de kop is inline bewerkbaar: de items worden gefilterd/gesorteerd, dus hun\n render-index wijst niet betrouwbaar naar block.events[i]. */}\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-agenda__list\">\n {events.map((e, i) => (\n <li key={i} className=\"xicms-agenda__item\">\n <time className=\"xicms-agenda__date\" dateTime={e.start}>\n {formatEventDate(e.start)}\n </time>\n <div className=\"xicms-agenda__body\">\n <strong className=\"xicms-agenda__title\">\n {e.url ? <a href={e.url}>{e.title}</a> : e.title}\n </strong>\n {e.location && <span className=\"xicms-agenda__location\">{e.location}</span>}\n {e.description && <p className=\"xicms-agenda__desc\">{e.description}</p>}\n {(() => {\n const ics = icsDataUrl(e);\n return ics ? (\n <a className=\"xicms-agenda__ics\" href={ics} download={`${(e.title || \"evenement\").toLowerCase().replace(/\\s+/g, \"-\").slice(0, 40)}.ics`}>\n 📅 Zet in agenda\n </a>\n ) : null;\n })()}\n </div>\n </li>\n ))}\n </ul>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(eventJsonLd(events)) }}\n />\n )}\n </section>\n );\n}\n\nexport const agendaBlock = defineBlock({\n type: \"agenda\",\n schema: AgendaBlockSchema,\n Render: AgendaRender,\n label: \"Agenda\",\n icon: \"📅\",\n defaults: () => ({\n heading: \"Agenda\",\n events: [{ title: \"Evenement\", start: \"\" }],\n hidePast: true,\n jsonLd: true,\n }),\n});\n"],"mappings":"AA2GwB,cAOZ,YAPY;AA1GxB,SAAS,yBAAyB;AAClC,SAAS,mBAA0C;AACnD,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAmBpB,SAAS,eAAe,OAAqB;AAClD,QAAM,IAAI,4BAA4B,KAAK,MAAM,KAAK,CAAC;AACvD,MAAI,EAAG,QAAO,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;AACnE,SAAO,IAAI,KAAK,KAAK;AACvB;AAGO,SAAS,gBAAgB,OAAuB;AACrD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,eAAe,KAAK;AAC9B,MAAI,OAAO,MAAM,EAAE,QAAQ,CAAC,EAAG,QAAO;AACtC,QAAM,UAAU,MAAM,SAAS,GAAG;AAClC,SAAO,EAAE,mBAAmB,SAAS;AAAA,IACnC,SAAS;AAAA,IACT,KAAK;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,IACN,GAAI,UAAU,EAAE,MAAM,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,EAC1D,CAAC;AACH;AAQO,SAAS,mBACd,QACA,OAA+C,CAAC,GAC3C;AACL,MAAI,QAAQ,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK;AAC/C,MAAI,KAAK,UAAU;AACjB,UAAM,QAAQ,oBAAI,KAAK;AACvB,UAAM,SAAS,GAAG,GAAG,GAAG,CAAC;AACzB,WAAO,KAAK,OAAO,CAAC,MAAM;AACxB,YAAM,IAAI,eAAe,EAAE,OAAO,EAAE,KAAK;AACzC,aAAO,OAAO,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK;AAAA,IAC3C,CAAC;AAAA,EACH;AACA,SAAO,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM;AAC9B,UAAM,KAAK,eAAe,EAAE,KAAK,EAAE,QAAQ;AAC3C,UAAM,KAAK,eAAe,EAAE,KAAK,EAAE,QAAQ;AAC3C,QAAI,OAAO,MAAM,EAAE,KAAK,OAAO,MAAM,EAAE,EAAG,QAAO;AACjD,QAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,QAAI,OAAO,MAAM,EAAE,EAAG,QAAO;AAC7B,WAAO,KAAK;AAAA,EACd,CAAC;AACD,SAAO,OAAO,KAAK,UAAU,WAAW,KAAK,MAAM,GAAG,KAAK,KAAK,IAAI;AACtE;AAEA,SAAS,YAAY,QAAmD;AACtE,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA;AAAA;AAAA,IAGT,iBAAiB,OACd,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAC9C,IAAI,CAAC,GAAG,OAAO;AAAA,MACd,SAAS;AAAA,MACT,UAAU,IAAI;AAAA,MACd,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,MAAM,EAAE;AAAA,QACR,WAAW,EAAE;AAAA,QACb,GAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;AAAA,QAClC,UAAU,EAAE,SAAS,SAAS,MAAM,EAAE,SAAS;AAAA,QAC/C,GAAI,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,IAAI,CAAC;AAAA,QACtD,GAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;AAAA,MAChC;AAAA,IACF,EAAE;AAAA,EACN;AACF;AAGA,SAAS,aAAa,EAAE,MAAM,GAA6B;AACzD,QAAM,SAAS,mBAAmB,MAAM,QAAQ,EAAE,UAAU,MAAM,SAAS,CAAC;AAC5E,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,SACE,qBAAC,aAAQ,WAAU,gBAGhB;AAAA,UAAM,WAAW,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9E,oBAAC,QAAG,WAAU,sBACX,iBAAO,IAAI,CAAC,GAAG,MACd,qBAAC,QAAW,WAAU,sBACpB;AAAA,0BAAC,UAAK,WAAU,sBAAqB,UAAU,EAAE,OAC9C,0BAAgB,EAAE,KAAK,GAC1B;AAAA,MACA,qBAAC,SAAI,WAAU,sBACb;AAAA,4BAAC,YAAO,WAAU,uBACf,YAAE,MAAM,oBAAC,OAAE,MAAM,EAAE,KAAM,YAAE,OAAM,IAAO,EAAE,OAC7C;AAAA,QACC,EAAE,YAAY,oBAAC,UAAK,WAAU,0BAA0B,YAAE,UAAS;AAAA,QACnE,EAAE,eAAe,oBAAC,OAAE,WAAU,sBAAsB,YAAE,aAAY;AAAA,SACjE,MAAM;AACN,gBAAM,MAAM,WAAW,CAAC;AACxB,iBAAO,MACL,oBAAC,OAAE,WAAU,qBAAoB,MAAM,KAAK,UAAU,IAAI,EAAE,SAAS,aAAa,YAAY,EAAE,QAAQ,QAAQ,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,qCAEzI,IACE;AAAA,QACN,GAAG;AAAA,SACL;AAAA,SAlBO,CAmBT,CACD,GACH;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB,EAAE,QAAQ,KAAK,UAAU,YAAY,MAAM,CAAC,EAAE;AAAA;AAAA,IACzE;AAAA,KAEJ;AAEJ;AAEO,MAAM,cAAc,YAAY;AAAA,EACrC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO;AAAA,IACf,SAAS;AAAA,IACT,QAAQ,CAAC,EAAE,OAAO,aAAa,OAAO,GAAG,CAAC;AAAA,IAC1C,UAAU;AAAA,IACV,QAAQ;AAAA,EACV;AACF,CAAC;","names":[]}
@@ -32,6 +32,7 @@ function BeforeAfterRender({ block }) {
32
32
  }
33
33
  const beforeAfterBlock = defineBlock({
34
34
  type: "beforeAfter",
35
+ fields: { beforeUrl: { type: "image" }, afterUrl: { type: "image" } },
35
36
  schema: BeforeAfterBlockSchema,
36
37
  Render: BeforeAfterRender,
37
38
  label: "Voor / na",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/BeforeAfter.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { z } from \"zod\";\nimport { BeforeAfterBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\n\ntype BeforeAfter = z.infer<typeof BeforeAfterBlockSchema>;\n\n/**\n * Voor/na-vergelijker: het \"na\"-beeld wordt geclipt op de sleppositie. De greep is een échte\n * range-input (over de hele breedte, transparant) — slepen, klikken én pijltjestoetsen werken\n * daarmee gratis en toegankelijk (aria-label + waarde in %).\n */\nfunction BeforeAfterRender({ block }: BlockRenderProps<BeforeAfter>) {\n const [pos, setPos] = useState(50);\n if (!block.beforeUrl || !block.afterUrl) return null;\n return (\n <figure className=\"xicms-beforeafter\" style={{ aspectRatio: block.aspectRatio.replace(\"/\", \" / \") }}>\n {block.heading && <figcaption className=\"xicms-beforeafter__heading\" data-xicms-edit=\"heading\">{block.heading}</figcaption>}\n <div className=\"xicms-beforeafter__stage\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img className=\"xicms-beforeafter__img\" src={block.beforeUrl} alt={block.alt || block.beforeLabel} loading=\"lazy\" />\n <div className=\"xicms-beforeafter__clip\" style={{ clipPath: `inset(0 0 0 ${pos}%)` }} aria-hidden>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img className=\"xicms-beforeafter__img\" src={block.afterUrl} alt=\"\" loading=\"lazy\" />\n </div>\n <div className=\"xicms-beforeafter__divider\" style={{ left: `${pos}%` }} aria-hidden>\n <span className=\"xicms-beforeafter__handle\">⟨⟩</span>\n </div>\n <span className=\"xicms-beforeafter__badge xicms-beforeafter__badge--before\">{block.beforeLabel}</span>\n <span className=\"xicms-beforeafter__badge xicms-beforeafter__badge--after\">{block.afterLabel}</span>\n <input\n className=\"xicms-beforeafter__range\"\n type=\"range\"\n min={0}\n max={100}\n value={pos}\n onChange={(e) => setPos(Number(e.target.value))}\n aria-label={`Vergelijk ${block.beforeLabel} en ${block.afterLabel}`}\n aria-valuetext={`${pos}% ${block.afterLabel}`}\n />\n </div>\n </figure>\n );\n}\n\nexport const beforeAfterBlock = defineBlock({\n type: \"beforeAfter\",\n schema: BeforeAfterBlockSchema,\n Render: BeforeAfterRender,\n label: \"Voor / na\",\n icon: \"🔀\",\n defaults: () => ({ beforeUrl: \"\", afterUrl: \"\", beforeLabel: \"Voor\", afterLabel: \"Na\", aspectRatio: \"4/3\" }),\n});\n"],"mappings":";AAmBwB,cAClB,YADkB;AAjBxB,SAAS,gBAAgB;AAEzB,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AASnD,SAAS,kBAAkB,EAAE,MAAM,GAAkC;AACnE,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,EAAE;AACjC,MAAI,CAAC,MAAM,aAAa,CAAC,MAAM,SAAU,QAAO;AAChD,SACE,qBAAC,YAAO,WAAU,qBAAoB,OAAO,EAAE,aAAa,MAAM,YAAY,QAAQ,KAAK,KAAK,EAAE,GAC/F;AAAA,UAAM,WAAW,oBAAC,gBAAW,WAAU,8BAA6B,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9G,qBAAC,SAAI,WAAU,4BAEb;AAAA,0BAAC,SAAI,WAAU,0BAAyB,KAAK,MAAM,WAAW,KAAK,MAAM,OAAO,MAAM,aAAa,SAAQ,QAAO;AAAA,MAClH,oBAAC,SAAI,WAAU,2BAA0B,OAAO,EAAE,UAAU,eAAe,GAAG,KAAK,GAAG,eAAW,MAE/F,8BAAC,SAAI,WAAU,0BAAyB,KAAK,MAAM,UAAU,KAAI,IAAG,SAAQ,QAAO,GACrF;AAAA,MACA,oBAAC,SAAI,WAAU,8BAA6B,OAAO,EAAE,MAAM,GAAG,GAAG,IAAI,GAAG,eAAW,MACjF,8BAAC,UAAK,WAAU,6BAA4B,0BAAE,GAChD;AAAA,MACA,oBAAC,UAAK,WAAU,6DAA6D,gBAAM,aAAY;AAAA,MAC/F,oBAAC,UAAK,WAAU,4DAA4D,gBAAM,YAAW;AAAA,MAC7F;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,OAAO,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,UAC9C,cAAY,aAAa,MAAM,WAAW,OAAO,MAAM,UAAU;AAAA,UACjE,kBAAgB,GAAG,GAAG,KAAK,MAAM,UAAU;AAAA;AAAA,MAC7C;AAAA,OACF;AAAA,KACF;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,WAAW,IAAI,UAAU,IAAI,aAAa,QAAQ,YAAY,MAAM,aAAa,MAAM;AAC5G,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/BeforeAfter.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { z } from \"zod\";\nimport { BeforeAfterBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\n\ntype BeforeAfter = z.infer<typeof BeforeAfterBlockSchema>;\n\n/**\n * Voor/na-vergelijker: het \"na\"-beeld wordt geclipt op de sleppositie. De greep is een échte\n * range-input (over de hele breedte, transparant) — slepen, klikken én pijltjestoetsen werken\n * daarmee gratis en toegankelijk (aria-label + waarde in %).\n */\nfunction BeforeAfterRender({ block }: BlockRenderProps<BeforeAfter>) {\n const [pos, setPos] = useState(50);\n if (!block.beforeUrl || !block.afterUrl) return null;\n return (\n <figure className=\"xicms-beforeafter\" style={{ aspectRatio: block.aspectRatio.replace(\"/\", \" / \") }}>\n {block.heading && <figcaption className=\"xicms-beforeafter__heading\" data-xicms-edit=\"heading\">{block.heading}</figcaption>}\n <div className=\"xicms-beforeafter__stage\">\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img className=\"xicms-beforeafter__img\" src={block.beforeUrl} alt={block.alt || block.beforeLabel} loading=\"lazy\" />\n <div className=\"xicms-beforeafter__clip\" style={{ clipPath: `inset(0 0 0 ${pos}%)` }} aria-hidden>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img className=\"xicms-beforeafter__img\" src={block.afterUrl} alt=\"\" loading=\"lazy\" />\n </div>\n <div className=\"xicms-beforeafter__divider\" style={{ left: `${pos}%` }} aria-hidden>\n <span className=\"xicms-beforeafter__handle\">⟨⟩</span>\n </div>\n <span className=\"xicms-beforeafter__badge xicms-beforeafter__badge--before\">{block.beforeLabel}</span>\n <span className=\"xicms-beforeafter__badge xicms-beforeafter__badge--after\">{block.afterLabel}</span>\n <input\n className=\"xicms-beforeafter__range\"\n type=\"range\"\n min={0}\n max={100}\n value={pos}\n onChange={(e) => setPos(Number(e.target.value))}\n aria-label={`Vergelijk ${block.beforeLabel} en ${block.afterLabel}`}\n aria-valuetext={`${pos}% ${block.afterLabel}`}\n />\n </div>\n </figure>\n );\n}\n\nexport const beforeAfterBlock = defineBlock({\n type: \"beforeAfter\",\n fields: { beforeUrl: { type: \"image\" }, afterUrl: { type: \"image\" } },\n schema: BeforeAfterBlockSchema,\n Render: BeforeAfterRender,\n label: \"Voor / na\",\n icon: \"🔀\",\n defaults: () => ({ beforeUrl: \"\", afterUrl: \"\", beforeLabel: \"Voor\", afterLabel: \"Na\", aspectRatio: \"4/3\" }),\n});\n"],"mappings":";AAmBwB,cAClB,YADkB;AAjBxB,SAAS,gBAAgB;AAEzB,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AASnD,SAAS,kBAAkB,EAAE,MAAM,GAAkC;AACnE,QAAM,CAAC,KAAK,MAAM,IAAI,SAAS,EAAE;AACjC,MAAI,CAAC,MAAM,aAAa,CAAC,MAAM,SAAU,QAAO;AAChD,SACE,qBAAC,YAAO,WAAU,qBAAoB,OAAO,EAAE,aAAa,MAAM,YAAY,QAAQ,KAAK,KAAK,EAAE,GAC/F;AAAA,UAAM,WAAW,oBAAC,gBAAW,WAAU,8BAA6B,mBAAgB,WAAW,gBAAM,SAAQ;AAAA,IAC9G,qBAAC,SAAI,WAAU,4BAEb;AAAA,0BAAC,SAAI,WAAU,0BAAyB,KAAK,MAAM,WAAW,KAAK,MAAM,OAAO,MAAM,aAAa,SAAQ,QAAO;AAAA,MAClH,oBAAC,SAAI,WAAU,2BAA0B,OAAO,EAAE,UAAU,eAAe,GAAG,KAAK,GAAG,eAAW,MAE/F,8BAAC,SAAI,WAAU,0BAAyB,KAAK,MAAM,UAAU,KAAI,IAAG,SAAQ,QAAO,GACrF;AAAA,MACA,oBAAC,SAAI,WAAU,8BAA6B,OAAO,EAAE,MAAM,GAAG,GAAG,IAAI,GAAG,eAAW,MACjF,8BAAC,UAAK,WAAU,6BAA4B,0BAAE,GAChD;AAAA,MACA,oBAAC,UAAK,WAAU,6DAA6D,gBAAM,aAAY;AAAA,MAC/F,oBAAC,UAAK,WAAU,4DAA4D,gBAAM,YAAW;AAAA,MAC7F;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAK;AAAA,UACL,KAAK;AAAA,UACL,KAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,OAAO,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,UAC9C,cAAY,aAAa,MAAM,WAAW,OAAO,MAAM,UAAU;AAAA,UACjE,kBAAgB,GAAG,GAAG,KAAK,MAAM,UAAU;AAAA;AAAA,MAC7C;AAAA,OACF;AAAA,KACF;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ,EAAE,WAAW,EAAE,MAAM,QAAQ,GAAG,UAAU,EAAE,MAAM,QAAQ,EAAE;AAAA,EACpE,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,WAAW,IAAI,UAAU,IAAI,aAAa,QAAQ,YAAY,MAAM,aAAa,MAAM;AAC5G,CAAC;","names":[]}
@@ -52,7 +52,11 @@ function BreadcrumbsRender({ block, mode }) {
52
52
  "script",
53
53
  {
54
54
  type: "application/ld+json",
55
- dangerouslySetInnerHTML: { __html: JSON.stringify(breadcrumbsJsonLd(crumbs)) }
55
+ dangerouslySetInnerHTML: {
56
+ __html: JSON.stringify(
57
+ breadcrumbsJsonLd(crumbs.map((c) => ({ ...c, href: `${window.location.origin}${c.href}` })))
58
+ )
59
+ }
56
60
  }
57
61
  )
58
62
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Breadcrumbs.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { BreadcrumbsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\n\ntype Breadcrumbs = z.infer<typeof BreadcrumbsBlockSchema>;\n\nexport interface Crumb {\n label: string;\n href: string;\n}\n\n/** Kruimels uit een pad: \"/diensten/tuinaanleg\" → Home / Diensten / Tuinaanleg. Puur en getest. */\nexport function crumbsFromPath(path: string, homeLabel = \"Home\"): Crumb[] {\n const segments = path.split(\"/\").filter(Boolean);\n const crumbs: Crumb[] = [{ label: homeLabel, href: \"/\" }];\n let href = \"\";\n for (const seg of segments) {\n href += `/${seg}`;\n // decodeURIComponent gooit op misvormde percent-encoding (\"%E0%A4%A\") — dan het rauwe segment.\n let decoded = seg;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n /* rauw segment als label */\n }\n const label = decoded.replace(/[-_]+/g, \" \").replace(/^./, (c) => c.toUpperCase());\n crumbs.push({ label, href });\n }\n return crumbs;\n}\n\n/** BreadcrumbList-JSON-LD. Puur en getest. */\nexport function breadcrumbsJsonLd(crumbs: Crumb[]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: crumbs.map((c, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n name: c.label,\n item: c.href,\n })),\n };\n}\n\n/**\n * Kruimelpad, afgeleid van de URL (client, ná mount — hydration-veilig). Op de homepage\n * (alleen \"Home\") wordt niets getoond.\n */\nfunction BreadcrumbsRender({ block, mode }: BlockRenderProps<Breadcrumbs>) {\n const LinkC = useLinkComponent();\n const [path, setPath] = useState<string | null>(null);\n useEffect(() => {\n setPath(window.location.pathname);\n }, []);\n\n if (mode === \"preview\" && (path === null || path === \"/\")) {\n return <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">{block.homeLabel} / Voorbeeldpagina</nav>;\n }\n if (path === null || path === \"/\") return null;\n const crumbs = crumbsFromPath(path, block.homeLabel);\n\n return (\n <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">\n <ol className=\"xicms-breadcrumbs__list\">\n {crumbs.map((c, i) => (\n <li key={c.href} className=\"xicms-breadcrumbs__item\">\n {i < crumbs.length - 1 ? (\n <LinkC href={c.href}>{c.label}</LinkC>\n ) : (\n <span aria-current=\"page\">{c.label}</span>\n )}\n </li>\n ))}\n </ol>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbsJsonLd(crumbs)) }}\n />\n )}\n </nav>\n );\n}\n\nexport const breadcrumbsBlock = defineBlock({\n type: \"breadcrumbs\",\n schema: BreadcrumbsBlockSchema,\n Render: BreadcrumbsRender,\n label: \"Kruimelpad\",\n icon: \"🧭\",\n defaults: () => ({ homeLabel: \"Home\", jsonLd: true }),\n});\n"],"mappings":";AA6DW,SAWG,KAXH;AA3DX,SAAS,WAAW,gBAAgB;AAEpC,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AACnD,SAAS,wBAAwB;AAU1B,SAAS,eAAe,MAAc,YAAY,QAAiB;AACxE,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/C,QAAM,SAAkB,CAAC,EAAE,OAAO,WAAW,MAAM,IAAI,CAAC;AACxD,MAAI,OAAO;AACX,aAAW,OAAO,UAAU;AAC1B,YAAQ,IAAI,GAAG;AAEf,QAAI,UAAU;AACd,QAAI;AACF,gBAAU,mBAAmB,GAAG;AAAA,IAClC,QAAQ;AAAA,IAER;AACA,UAAM,QAAQ,QAAQ,QAAQ,UAAU,GAAG,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;AACjF,WAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,EAC7B;AACA,SAAO;AACT;AAGO,SAAS,kBAAkB,QAA0C;AAC1E,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,iBAAiB,OAAO,IAAI,CAAC,GAAG,OAAO;AAAA,MACrC,SAAS;AAAA,MACT,UAAU,IAAI;AAAA,MACd,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AACF;AAMA,SAAS,kBAAkB,EAAE,OAAO,KAAK,GAAkC;AACzE,QAAM,QAAQ,iBAAiB;AAC/B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AACpD,YAAU,MAAM;AACd,YAAQ,OAAO,SAAS,QAAQ;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,MAAI,SAAS,cAAc,SAAS,QAAQ,SAAS,MAAM;AACzD,WAAO,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAAc;AAAA,YAAM;AAAA,MAAU;AAAA,OAAkB;AAAA,EACvG;AACA,MAAI,SAAS,QAAQ,SAAS,IAAK,QAAO;AAC1C,QAAM,SAAS,eAAe,MAAM,MAAM,SAAS;AAEnD,SACE,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAC5C;AAAA,wBAAC,QAAG,WAAU,2BACX,iBAAO,IAAI,CAAC,GAAG,MACd,oBAAC,QAAgB,WAAU,2BACxB,cAAI,OAAO,SAAS,IACnB,oBAAC,SAAM,MAAM,EAAE,MAAO,YAAE,OAAM,IAE9B,oBAAC,UAAK,gBAAa,QAAQ,YAAE,OAAM,KAJ9B,EAAE,IAMX,CACD,GACH;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAEL,yBAAyB,EAAE,QAAQ,KAAK,UAAU,kBAAkB,MAAM,CAAC,EAAE;AAAA;AAAA,IAC/E;AAAA,KAEJ;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,WAAW,QAAQ,QAAQ,KAAK;AACrD,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Breadcrumbs.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { BreadcrumbsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\n\ntype Breadcrumbs = z.infer<typeof BreadcrumbsBlockSchema>;\n\nexport interface Crumb {\n label: string;\n href: string;\n}\n\n/** Kruimels uit een pad: \"/diensten/tuinaanleg\" → Home / Diensten / Tuinaanleg. Puur en getest. */\nexport function crumbsFromPath(path: string, homeLabel = \"Home\"): Crumb[] {\n const segments = path.split(\"/\").filter(Boolean);\n const crumbs: Crumb[] = [{ label: homeLabel, href: \"/\" }];\n let href = \"\";\n for (const seg of segments) {\n href += `/${seg}`;\n // decodeURIComponent gooit op misvormde percent-encoding (\"%E0%A4%A\") — dan het rauwe segment.\n let decoded = seg;\n try {\n decoded = decodeURIComponent(seg);\n } catch {\n /* rauw segment als label */\n }\n const label = decoded.replace(/[-_]+/g, \" \").replace(/^./, (c) => c.toUpperCase());\n crumbs.push({ label, href });\n }\n return crumbs;\n}\n\n/** BreadcrumbList-JSON-LD. Puur en getest. */\nexport function breadcrumbsJsonLd(crumbs: Crumb[]): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: crumbs.map((c, i) => ({\n \"@type\": \"ListItem\",\n position: i + 1,\n name: c.label,\n item: c.href,\n })),\n };\n}\n\n/**\n * Kruimelpad, afgeleid van de URL (client, ná mount — hydration-veilig). Op de homepage\n * (alleen \"Home\") wordt niets getoond.\n */\nfunction BreadcrumbsRender({ block, mode }: BlockRenderProps<Breadcrumbs>) {\n const LinkC = useLinkComponent();\n const [path, setPath] = useState<string | null>(null);\n useEffect(() => {\n setPath(window.location.pathname);\n }, []);\n\n if (mode === \"preview\" && (path === null || path === \"/\")) {\n return <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">{block.homeLabel} / Voorbeeldpagina</nav>;\n }\n if (path === null || path === \"/\") return null;\n const crumbs = crumbsFromPath(path, block.homeLabel);\n\n return (\n <nav className=\"xicms-breadcrumbs\" aria-label=\"Kruimelpad\">\n <ol className=\"xicms-breadcrumbs__list\">\n {crumbs.map((c, i) => (\n <li key={c.href} className=\"xicms-breadcrumbs__item\">\n {i < crumbs.length - 1 ? (\n <LinkC href={c.href}>{c.label}</LinkC>\n ) : (\n <span aria-current=\"page\">{c.label}</span>\n )}\n </li>\n ))}\n </ol>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // Absolute URL's: Google wil volledige item-URL's in BreadcrumbList (dit rendert\n // client-side ná mount, dus window.location.origin is beschikbaar).\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(\n breadcrumbsJsonLd(crumbs.map((c) => ({ ...c, href: `${window.location.origin}${c.href}` }))),\n ),\n }}\n />\n )}\n </nav>\n );\n}\n\nexport const breadcrumbsBlock = defineBlock({\n type: \"breadcrumbs\",\n schema: BreadcrumbsBlockSchema,\n Render: BreadcrumbsRender,\n label: \"Kruimelpad\",\n icon: \"🧭\",\n defaults: () => ({ homeLabel: \"Home\", jsonLd: true }),\n});\n"],"mappings":";AA6DW,SAWG,KAXH;AA3DX,SAAS,WAAW,gBAAgB;AAEpC,SAAS,8BAA8B;AACvC,SAAS,mBAA0C;AACnD,SAAS,wBAAwB;AAU1B,SAAS,eAAe,MAAc,YAAY,QAAiB;AACxE,QAAM,WAAW,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAC/C,QAAM,SAAkB,CAAC,EAAE,OAAO,WAAW,MAAM,IAAI,CAAC;AACxD,MAAI,OAAO;AACX,aAAW,OAAO,UAAU;AAC1B,YAAQ,IAAI,GAAG;AAEf,QAAI,UAAU;AACd,QAAI;AACF,gBAAU,mBAAmB,GAAG;AAAA,IAClC,QAAQ;AAAA,IAER;AACA,UAAM,QAAQ,QAAQ,QAAQ,UAAU,GAAG,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;AACjF,WAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,EAC7B;AACA,SAAO;AACT;AAGO,SAAS,kBAAkB,QAA0C;AAC1E,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,iBAAiB,OAAO,IAAI,CAAC,GAAG,OAAO;AAAA,MACrC,SAAS;AAAA,MACT,UAAU,IAAI;AAAA,MACd,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,IACV,EAAE;AAAA,EACJ;AACF;AAMA,SAAS,kBAAkB,EAAE,OAAO,KAAK,GAAkC;AACzE,QAAM,QAAQ,iBAAiB;AAC/B,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,IAAI;AACpD,YAAU,MAAM;AACd,YAAQ,OAAO,SAAS,QAAQ;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,MAAI,SAAS,cAAc,SAAS,QAAQ,SAAS,MAAM;AACzD,WAAO,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAAc;AAAA,YAAM;AAAA,MAAU;AAAA,OAAkB;AAAA,EACvG;AACA,MAAI,SAAS,QAAQ,SAAS,IAAK,QAAO;AAC1C,QAAM,SAAS,eAAe,MAAM,MAAM,SAAS;AAEnD,SACE,qBAAC,SAAI,WAAU,qBAAoB,cAAW,cAC5C;AAAA,wBAAC,QAAG,WAAU,2BACX,iBAAO,IAAI,CAAC,GAAG,MACd,oBAAC,QAAgB,WAAU,2BACxB,cAAI,OAAO,SAAS,IACnB,oBAAC,SAAM,MAAM,EAAE,MAAO,YAAE,OAAM,IAE9B,oBAAC,UAAK,gBAAa,QAAQ,YAAE,OAAM,KAJ9B,EAAE,IAMX,CACD,GACH;AAAA,IACC,MAAM,UACL;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QAIL,yBAAyB;AAAA,UACvB,QAAQ,KAAK;AAAA,YACX,kBAAkB,OAAO,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,SAAS,MAAM,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC;AAAA,UAC7F;AAAA,QACF;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;AAEO,MAAM,mBAAmB,YAAY;AAAA,EAC1C,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,WAAW,QAAQ,QAAQ,KAAK;AACrD,CAAC;","names":[]}
@@ -7,14 +7,15 @@ import { useLinkComponent } from "@astryxdesign/core/Link";
7
7
  import { Img } from "../../media/ImageContext";
8
8
  function CardsRender({ block }) {
9
9
  const LinkC = useLinkComponent();
10
- const items = (block.items ?? []).filter((c) => c.title || c.text || c.imageUrl);
10
+ const items = (block.items ?? []).map((c, i) => ({ ...c, _i: i })).filter((c) => c.title || c.text || c.imageUrl);
11
11
  if (items.length === 0 && !block.heading) return null;
12
12
  return /* @__PURE__ */ jsxs(VStack, { gap: 4, as: "section", className: "xicms-cards", children: [
13
13
  (block.heading || block.intro) && /* @__PURE__ */ jsxs(VStack, { gap: 1, hAlign: "center", style: { textAlign: "center" }, children: [
14
14
  block.heading && /* @__PURE__ */ jsx(Heading, { level: 2, "data-xicms-edit": "heading", children: block.heading }),
15
15
  block.intro && /* @__PURE__ */ jsx(Text, { type: "large", color: "secondary", children: block.intro })
16
16
  ] }),
17
- /* @__PURE__ */ jsx(Grid, { columns: { minWidth: 240, max: block.columns }, gap: 4, children: items.map((c, i) => {
17
+ /* @__PURE__ */ jsx(Grid, { columns: { minWidth: 240, max: block.columns }, gap: 4, children: items.map((c) => {
18
+ const i = c._i;
18
19
  const cover = block.imageStyle === "cover" && Boolean(c.imageUrl);
19
20
  const body = /* @__PURE__ */ jsxs(VStack, { gap: 2, style: cover ? { padding: "1.1rem 1.25rem 1.25rem" } : void 0, children: [
20
21
  c.title && /* @__PURE__ */ jsx(Heading, { level: 3, className: "xicms-cards__title", "data-xicms-edit": `items.${i}.title`, children: c.title }),
@@ -74,6 +75,7 @@ function CardsRender({ block }) {
74
75
  }
75
76
  const cardsBlock = defineBlock({
76
77
  type: "cards",
78
+ fields: { "items.icon": { type: "icon" } },
77
79
  schema: CardsBlockSchema,
78
80
  Render: CardsRender,
79
81
  label: "Diensten / kaarten",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Cards.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CardsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Card, Heading, Text } from \"../ui\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Cards = z.infer<typeof CardsBlockSchema>;\n\n/** Diensten/uitgelichte kaarten in een responsive grid (Astryx). */\nfunction CardsRender({ block }: BlockRenderProps<Cards>) {\n const LinkC = useLinkComponent();\n const items = (block.items ?? []).filter((c) => c.title || c.text || c.imageUrl);\n if (items.length === 0 && !block.heading) return null;\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-cards\">\n {(block.heading || block.intro) && (\n <VStack gap={1} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text type=\"large\" color=\"secondary\">{block.intro}</Text>}\n </VStack>\n )}\n <Grid columns={{ minWidth: 240, max: block.columns }} gap={4}>\n {items.map((c, i) => {\n const cover = block.imageStyle === \"cover\" && Boolean(c.imageUrl);\n const body = (\n <VStack gap={2} style={cover ? { padding: \"1.1rem 1.25rem 1.25rem\" } : undefined}>\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const inner = cover ? (\n <VStack gap={0} style={{ height: \"100%\" }}>\n {/* Beeld edge-to-edge bovenaan (postkaart-stijl); tekst hieronder gepad. */}\n <Img src={c.imageUrl as string} alt={c.title ?? \"\"} width={600} height={360}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", aspectRatio: \"16 / 10\", objectFit: \"cover\", display: \"block\" }} />\n {body}\n </VStack>\n ) : (\n <VStack gap={2}>\n {c.imageUrl ? (\n <Img src={c.imageUrl} alt={c.title ?? \"\"} width={480} height={300}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", height: 160, objectFit: \"cover\", borderRadius: \"var(--radius-element)\" }} />\n ) : c.icon ? (\n <div aria-hidden=\"true\" style={{ fontSize: \"2rem\", lineHeight: 1 }}>{c.icon}</div>\n ) : null}\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const card = (\n <Card\n padding={cover ? 0 : 4}\n height=\"100%\"\n className={`xicms-cards__card${c.featured ? \" xicms-cards__card--featured\" : \"\"}`}\n style={cover ? { overflow: \"hidden\" } : undefined}\n >\n {inner}\n </Card>\n );\n return c.href ? (\n <LinkC key={i} href={c.href} style={{ textDecoration: \"none\", color: \"inherit\", display: \"block\" }}>\n {card}\n </LinkC>\n ) : (\n <div key={i}>{card}</div>\n );\n })}\n </Grid>\n </VStack>\n );\n}\n\nexport const cardsBlock = defineBlock({\n type: \"cards\",\n schema: CardsBlockSchema,\n Render: CardsRender,\n label: \"Diensten / kaarten\",\n icon: \"🧩\",\n defaults: () => ({\n heading: \"Onze diensten\",\n columns: 3,\n items: [\n { icon: \"⭐\", title: \"Dienst één\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"⚡\", title: \"Dienst twee\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"💬\", title: \"Dienst drie\", text: \"Korte omschrijving van wat je biedt.\" },\n ],\n }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Cards.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CardsBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, Grid, Card, Heading, Text } from \"../ui\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\nimport { Img } from \"../../media/ImageContext\";\n\ntype Cards = z.infer<typeof CardsBlockSchema>;\n\n/** Diensten/uitgelichte kaarten in een responsive grid (Astryx). */\nfunction CardsRender({ block }: BlockRenderProps<Cards>) {\n const LinkC = useLinkComponent();\n // _i = index in de OORSPRONKELIJKE array — inline-edit-paden moeten naar de ongefilterde\n // items wijzen, anders schrijft bewerken naar het verkeerde (weggefilterde) item.\n const items = (block.items ?? []).map((c, i) => ({ ...c, _i: i })).filter((c) => c.title || c.text || c.imageUrl);\n if (items.length === 0 && !block.heading) return null;\n return (\n <VStack gap={4} as=\"section\" className=\"xicms-cards\">\n {(block.heading || block.intro) && (\n <VStack gap={1} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n {block.intro && <Text type=\"large\" color=\"secondary\">{block.intro}</Text>}\n </VStack>\n )}\n <Grid columns={{ minWidth: 240, max: block.columns }} gap={4}>\n {items.map((c) => {\n const i = c._i;\n const cover = block.imageStyle === \"cover\" && Boolean(c.imageUrl);\n const body = (\n <VStack gap={2} style={cover ? { padding: \"1.1rem 1.25rem 1.25rem\" } : undefined}>\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const inner = cover ? (\n <VStack gap={0} style={{ height: \"100%\" }}>\n {/* Beeld edge-to-edge bovenaan (postkaart-stijl); tekst hieronder gepad. */}\n <Img src={c.imageUrl as string} alt={c.title ?? \"\"} width={600} height={360}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", aspectRatio: \"16 / 10\", objectFit: \"cover\", display: \"block\" }} />\n {body}\n </VStack>\n ) : (\n <VStack gap={2}>\n {c.imageUrl ? (\n <Img src={c.imageUrl} alt={c.title ?? \"\"} width={480} height={300}\n sizes=\"(max-width: 720px) 100vw, 33vw\" editField={`items.${i}.imageUrl`}\n style={{ width: \"100%\", height: 160, objectFit: \"cover\", borderRadius: \"var(--radius-element)\" }} />\n ) : c.icon ? (\n <div aria-hidden=\"true\" style={{ fontSize: \"2rem\", lineHeight: 1 }}>{c.icon}</div>\n ) : null}\n {c.title && <Heading level={3} className=\"xicms-cards__title\" data-xicms-edit={`items.${i}.title`}>{c.title}</Heading>}\n {c.text && <Text color=\"secondary\" className=\"xicms-cards__text\" data-xicms-edit={`items.${i}.text`}>{c.text}</Text>}\n {c.href && c.linkLabel && (\n <Text type=\"label\" style={{ color: \"var(--color-accent)\" }}>{c.linkLabel} →</Text>\n )}\n </VStack>\n );\n const card = (\n <Card\n padding={cover ? 0 : 4}\n height=\"100%\"\n className={`xicms-cards__card${c.featured ? \" xicms-cards__card--featured\" : \"\"}`}\n style={cover ? { overflow: \"hidden\" } : undefined}\n >\n {inner}\n </Card>\n );\n return c.href ? (\n <LinkC key={i} href={c.href} style={{ textDecoration: \"none\", color: \"inherit\", display: \"block\" }}>\n {card}\n </LinkC>\n ) : (\n <div key={i}>{card}</div>\n );\n })}\n </Grid>\n </VStack>\n );\n}\n\nexport const cardsBlock = defineBlock({\n type: \"cards\",\n fields: { \"items.icon\": { type: \"icon\" } },\n schema: CardsBlockSchema,\n Render: CardsRender,\n label: \"Diensten / kaarten\",\n icon: \"🧩\",\n defaults: () => ({\n heading: \"Onze diensten\",\n columns: 3,\n items: [\n { icon: \"⭐\", title: \"Dienst één\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"⚡\", title: \"Dienst twee\", text: \"Korte omschrijving van wat je biedt.\" },\n { icon: \"💬\", title: \"Dienst drie\", text: \"Korte omschrijving van wat je biedt.\" },\n ],\n }),\n});\n"],"mappings":"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","names":[]}
@@ -9,7 +9,8 @@ function contactJsonLd(b) {
9
9
  // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde
10
10
  // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.
11
11
  "@id": "#business",
12
- ...b.heading ? { name: b.heading } : {},
12
+ // Bewust géén `name: heading` — de blokkop is meestal "Contact" en zou als bedrijfsnaam in de
13
+ // samengevoegde #business-entiteit belanden; de naam komt uit de layout-brede businessJsonLd.
13
14
  ...b.address ? { address: b.address } : {},
14
15
  ...b.phone ? { telephone: b.phone } : {},
15
16
  ...b.email ? { email: b.email } : {},
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Contact.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { ContactBlockSchema, SocialBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\n/* ── Contact ─────────────────────────────────────────────────────────────── */\ntype Contact = z.infer<typeof ContactBlockSchema>;\nexport function contactJsonLd(b: Contact): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"LocalBusiness\",\n // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde\n // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.\n \"@id\": \"#business\",\n ...(b.heading ? { name: b.heading } : {}),\n ...(b.address ? { address: b.address } : {}),\n ...(b.phone ? { telephone: b.phone } : {}),\n ...(b.email ? { email: b.email } : {}),\n ...(b.geo ? { geo: { \"@type\": \"GeoCoordinates\", latitude: b.geo.lat, longitude: b.geo.lng } } : {}),\n };\n}\nfunction ContactRender({ block }: BlockRenderProps<Contact>) {\n return (\n <section className=\"xicms-contact\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <dl className=\"xicms-contact__list\">\n {block.address && (\n <div className=\"xicms-contact__row\">\n <dt>Adres</dt>\n <dd data-xicms-edit=\"address\">{block.address}</dd>\n </div>\n )}\n {block.phone && (\n <div className=\"xicms-contact__row\">\n <dt>Telefoon</dt>\n <dd><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`}>{block.phone}</a></dd>\n </div>\n )}\n {block.email && (\n <div className=\"xicms-contact__row\">\n <dt>E-mail</dt>\n <dd><a href={`mailto:${block.email}`}>{block.email}</a></dd>\n </div>\n )}\n {block.hours && (\n <div className=\"xicms-contact__row\">\n <dt>Openingstijden</dt>\n <dd data-xicms-edit=\"hours\">{block.hours}</dd>\n </div>\n )}\n </dl>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(contactJsonLd(block)) }}\n />\n )}\n </section>\n );\n}\nexport const contactBlock = defineBlock({\n type: \"contact\",\n schema: ContactBlockSchema,\n Render: ContactRender,\n label: \"Contactgegevens\",\n icon: \"📇\",\n defaults: () => ({ heading: \"Contact\", jsonLd: true }),\n});\n\n/* ── Social ──────────────────────────────────────────────────────────────── */\ntype Social = z.infer<typeof SocialBlockSchema>;\n\n/**\n * Minimalistische, monochrome merk-iconen (currentColor) — geen icon-dependency nodig.\n * Onbekende platformen vallen terug op de tekstweergave, dus niets breekt.\n */\nfunction SocialIcon({ platform }: { platform: string }) {\n const p = platform.toLowerCase();\n const common = { width: 22, height: 22, viewBox: \"0 0 24 24\", \"aria-hidden\": true } as const;\n if (p.includes(\"instagram\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"4.5\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.6\" />\n <circle cx=\"16.7\" cy=\"7.3\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n );\n }\n if (p.includes(\"facebook\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M13.5 20v-6.2h2.1l.4-2.6h-2.5V9.6c0-.8.3-1.3 1.3-1.3h1.3V6c-.3 0-1-.1-1.9-.1-2 0-3.3 1.2-3.3 3.4v1.9H8.7v2.6h2.2V20h2.6z\" />\n </svg>\n );\n }\n if (p.includes(\"linkedin\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <circle cx=\"6\" cy=\"6.4\" r=\"1.8\" />\n <rect x=\"4.6\" y=\"9.6\" width=\"2.8\" height=\"9.9\" />\n <path d=\"M10.4 9.6h2.7v1.4c.5-.9 1.6-1.7 3.1-1.7 2.4 0 3.7 1.5 3.7 4.3v5.9h-2.8v-5.4c0-1.5-.6-2.3-1.7-2.3-1.2 0-2.2.8-2.2 2.4v5.3h-2.8V9.6z\" />\n </svg>\n );\n }\n if (p.includes(\"twitter\") || p === \"x\" || p.startsWith(\"x \") || p.includes(\"(x)\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M4.5 4.5h3.6l4.1 5.6 4.7-5.6h2.6l-6.1 7.2 6.4 8.4h-3.6l-4.4-6-5 6H4.2l6.5-7.7-6.2-7.9z\" />\n </svg>\n );\n }\n if (p.includes(\"youtube\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <rect x=\"3.2\" y=\"6.2\" width=\"17.6\" height=\"11.6\" rx=\"3.2\" />\n <path d=\"M10.4 9.4v5.2l4.8-2.6z\" fill=\"var(--xicms-surface, #fff)\" />\n </svg>\n );\n }\n if (p.includes(\"whatsapp\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <path d=\"M12 4.2a7.8 7.8 0 0 0-6.7 11.8L4.4 19.6l3.7-.9A7.8 7.8 0 1 0 12 4.2z\" />\n <path d=\"M9.3 8.9c.7 2.4 2.6 4.4 5 5l1-1.2 2 1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n }\n if (p.includes(\"tiktok\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M14.5 4h2.6c.2 1.7 1.3 3 3 3.3v2.6a6.6 6.6 0 0 1-3.2-1v5.7a5.4 5.4 0 1 1-5.4-5.4c.2 0 .5 0 .7.1v2.8a2.6 2.6 0 1 0 2.3 2.6V4z\" />\n </svg>\n );\n }\n return null;\n}\n\nfunction SocialRender({ block }: BlockRenderProps<Social>) {\n const items = (block.items ?? []).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-social\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-social__list\">\n {items.map((it, i) => {\n // Als functie aangeroepen (geen hooks) zodat `null` echt null is — een JSX-element\n // zou altijd truthy zijn en de tekst-fallback nooit tonen.\n const icon = SocialIcon({ platform: it.platform });\n return (\n <li key={i}>\n <a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" aria-label={it.platform} title={it.platform} className={icon ? \"xicms-social__iconlink\" : undefined}>\n {icon ?? it.platform}\n </a>\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\nexport const socialBlock = defineBlock({\n type: \"social\",\n schema: SocialBlockSchema,\n Render: SocialRender,\n label: \"Social-links\",\n icon: \"🔗\",\n defaults: () => ({ items: [{ platform: \"Instagram\", href: \"\" }] }),\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Contact.tsx"],"sourcesContent":["import type { z } from \"zod\";\nimport { ContactBlockSchema, SocialBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { Heading } from \"../ui\";\n\n/* ── Contact ─────────────────────────────────────────────────────────────── */\ntype Contact = z.infer<typeof ContactBlockSchema>;\nexport function contactJsonLd(b: Contact): Record<string, unknown> {\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"LocalBusiness\",\n // Gedeeld relatief @id: contact-, openingstijden- en layout-JSON-LD beschrijven hetzelfde\n // bedrijf; zonder @id ziet Google drie conflicterende LocalBusiness-entiteiten per pagina.\n \"@id\": \"#business\",\n // Bewust géén `name: heading` — de blokkop is meestal \"Contact\" en zou als bedrijfsnaam in de\n // samengevoegde #business-entiteit belanden; de naam komt uit de layout-brede businessJsonLd.\n ...(b.address ? { address: b.address } : {}),\n ...(b.phone ? { telephone: b.phone } : {}),\n ...(b.email ? { email: b.email } : {}),\n ...(b.geo ? { geo: { \"@type\": \"GeoCoordinates\", latitude: b.geo.lat, longitude: b.geo.lng } } : {}),\n };\n}\nfunction ContactRender({ block }: BlockRenderProps<Contact>) {\n return (\n <section className=\"xicms-contact\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <dl className=\"xicms-contact__list\">\n {block.address && (\n <div className=\"xicms-contact__row\">\n <dt>Adres</dt>\n <dd data-xicms-edit=\"address\">{block.address}</dd>\n </div>\n )}\n {block.phone && (\n <div className=\"xicms-contact__row\">\n <dt>Telefoon</dt>\n <dd><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`}>{block.phone}</a></dd>\n </div>\n )}\n {block.email && (\n <div className=\"xicms-contact__row\">\n <dt>E-mail</dt>\n <dd><a href={`mailto:${block.email}`}>{block.email}</a></dd>\n </div>\n )}\n {block.hours && (\n <div className=\"xicms-contact__row\">\n <dt>Openingstijden</dt>\n <dd data-xicms-edit=\"hours\">{block.hours}</dd>\n </div>\n )}\n </dl>\n {block.jsonLd && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n dangerouslySetInnerHTML={{ __html: JSON.stringify(contactJsonLd(block)) }}\n />\n )}\n </section>\n );\n}\nexport const contactBlock = defineBlock({\n type: \"contact\",\n schema: ContactBlockSchema,\n Render: ContactRender,\n label: \"Contactgegevens\",\n icon: \"📇\",\n defaults: () => ({ heading: \"Contact\", jsonLd: true }),\n});\n\n/* ── Social ──────────────────────────────────────────────────────────────── */\ntype Social = z.infer<typeof SocialBlockSchema>;\n\n/**\n * Minimalistische, monochrome merk-iconen (currentColor) — geen icon-dependency nodig.\n * Onbekende platformen vallen terug op de tekstweergave, dus niets breekt.\n */\nfunction SocialIcon({ platform }: { platform: string }) {\n const p = platform.toLowerCase();\n const common = { width: 22, height: 22, viewBox: \"0 0 24 24\", \"aria-hidden\": true } as const;\n if (p.includes(\"instagram\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"4.5\" />\n <circle cx=\"12\" cy=\"12\" r=\"3.6\" />\n <circle cx=\"16.7\" cy=\"7.3\" r=\"1.1\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n );\n }\n if (p.includes(\"facebook\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M13.5 20v-6.2h2.1l.4-2.6h-2.5V9.6c0-.8.3-1.3 1.3-1.3h1.3V6c-.3 0-1-.1-1.9-.1-2 0-3.3 1.2-3.3 3.4v1.9H8.7v2.6h2.2V20h2.6z\" />\n </svg>\n );\n }\n if (p.includes(\"linkedin\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <circle cx=\"6\" cy=\"6.4\" r=\"1.8\" />\n <rect x=\"4.6\" y=\"9.6\" width=\"2.8\" height=\"9.9\" />\n <path d=\"M10.4 9.6h2.7v1.4c.5-.9 1.6-1.7 3.1-1.7 2.4 0 3.7 1.5 3.7 4.3v5.9h-2.8v-5.4c0-1.5-.6-2.3-1.7-2.3-1.2 0-2.2.8-2.2 2.4v5.3h-2.8V9.6z\" />\n </svg>\n );\n }\n if (p.includes(\"twitter\") || p === \"x\" || p.startsWith(\"x \") || p.includes(\"(x)\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M4.5 4.5h3.6l4.1 5.6 4.7-5.6h2.6l-6.1 7.2 6.4 8.4h-3.6l-4.4-6-5 6H4.2l6.5-7.7-6.2-7.9z\" />\n </svg>\n );\n }\n if (p.includes(\"youtube\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <rect x=\"3.2\" y=\"6.2\" width=\"17.6\" height=\"11.6\" rx=\"3.2\" />\n <path d=\"M10.4 9.4v5.2l4.8-2.6z\" fill=\"var(--xicms-surface, #fff)\" />\n </svg>\n );\n }\n if (p.includes(\"whatsapp\")) {\n return (\n <svg {...common} fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\">\n <path d=\"M12 4.2a7.8 7.8 0 0 0-6.7 11.8L4.4 19.6l3.7-.9A7.8 7.8 0 1 0 12 4.2z\" />\n <path d=\"M9.3 8.9c.7 2.4 2.6 4.4 5 5l1-1.2 2 1.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n );\n }\n if (p.includes(\"tiktok\")) {\n return (\n <svg {...common} fill=\"currentColor\">\n <path d=\"M14.5 4h2.6c.2 1.7 1.3 3 3 3.3v2.6a6.6 6.6 0 0 1-3.2-1v5.7a5.4 5.4 0 1 1-5.4-5.4c.2 0 .5 0 .7.1v2.8a2.6 2.6 0 1 0 2.3 2.6V4z\" />\n </svg>\n );\n }\n return null;\n}\n\nfunction SocialRender({ block }: BlockRenderProps<Social>) {\n const items = (block.items ?? []).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <section className=\"xicms-social\">\n {block.heading && <Heading level={2} data-xicms-edit=\"heading\">{block.heading}</Heading>}\n <ul className=\"xicms-social__list\">\n {items.map((it, i) => {\n // Als functie aangeroepen (geen hooks) zodat `null` echt null is — een JSX-element\n // zou altijd truthy zijn en de tekst-fallback nooit tonen.\n const icon = SocialIcon({ platform: it.platform });\n return (\n <li key={i}>\n <a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" aria-label={it.platform} title={it.platform} className={icon ? \"xicms-social__iconlink\" : undefined}>\n {icon ?? it.platform}\n </a>\n </li>\n );\n })}\n </ul>\n </section>\n );\n}\nexport const socialBlock = defineBlock({\n type: \"social\",\n schema: SocialBlockSchema,\n Render: SocialRender,\n label: \"Social-links\",\n icon: \"🔗\",\n defaults: () => ({ items: [{ platform: \"Instagram\", href: \"\" }] }),\n});\n"],"mappings":"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","names":[]}
@@ -40,6 +40,7 @@ function CtaRender({ block }) {
40
40
  }
41
41
  const ctaBlock = defineBlock({
42
42
  type: "cta",
43
+ fields: { buttonVariant: { type: "select", options: [{ value: "primary", label: "Primair" }, { value: "secondary", label: "Secundair" }, { value: "outline", label: "Outline" }] } },
43
44
  schema: CtaBlockSchema,
44
45
  Render: CtaRender,
45
46
  label: "Call-to-action",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Cta.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CtaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, HStack, Card, Heading, Text, Btn } from \"../ui\";\n\ntype Cta = z.infer<typeof CtaBlockSchema>;\n\nfunction CtaRender({ block }: BlockRenderProps<Cta>) {\n if (block.sticky) {\n return (\n <HStack\n gap={3}\n vAlign=\"center\"\n hAlign=\"between\"\n role=\"region\"\n aria-label={block.title || \"Call-to-action\"}\n style={{ position: \"sticky\", bottom: 0, zIndex: 30, padding: \"0.75rem 1rem\", background: \"var(--color-background-card)\", borderTop: \"1px solid var(--color-border)\", flexWrap: \"wrap\" }}\n >\n <Text weight=\"bold\">{block.title || block.text}</Text>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} size=\"md\" />\n </HStack>\n );\n }\n return (\n <Card\n padding={6}\n variant={block.backgroundColor ? \"default\" : \"muted\"}\n style={block.backgroundColor ? { background: block.backgroundColor } : undefined}\n >\n <VStack gap={3} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.title && <Heading level={2} data-xicms-edit=\"title\">{block.title}</Heading>}\n {block.text && <Text type=\"large\" color=\"secondary\" data-xicms-edit=\"text\">{block.text}</Text>}\n <HStack gap={2} hAlign=\"center\" style={{ flexWrap: \"wrap\" }}>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} />\n {block.button2Label && (\n <Btn label={block.button2Label} href={block.button2Href ?? \"#\"} variant=\"outline\" />\n )}\n </HStack>\n </VStack>\n </Card>\n );\n}\n\nexport const ctaBlock = defineBlock({\n type: \"cta\",\n schema: CtaBlockSchema,\n Render: CtaRender,\n label: \"Call-to-action\",\n icon: \"📣\",\n defaults: () => ({ title: \"Klaar om te starten?\", buttonLabel: \"Neem contact op\", buttonHref: \"#contact\", buttonVariant: \"primary\" }),\n});\n"],"mappings":";AAYM,SAQE,KARF;AATN,SAAS,sBAAsB;AAC/B,SAAS,mBAA0C;AACnD,SAAS,QAAQ,QAAQ,MAAM,SAAS,MAAM,WAAW;AAIzD,SAAS,UAAU,EAAE,MAAM,GAA0B;AACnD,MAAI,MAAM,QAAQ;AAChB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,QAAO;AAAA,QACP,QAAO;AAAA,QACP,MAAK;AAAA,QACL,cAAY,MAAM,SAAS;AAAA,QAC3B,OAAO,EAAE,UAAU,UAAU,QAAQ,GAAG,QAAQ,IAAI,SAAS,gBAAgB,YAAY,gCAAgC,WAAW,iCAAiC,UAAU,OAAO;AAAA,QAEtL;AAAA,8BAAC,QAAK,QAAO,QAAQ,gBAAM,SAAS,MAAM,MAAK;AAAA,UAC/C,oBAAC,OAAI,OAAO,MAAM,aAAa,MAAM,MAAM,YAAY,SAAS,MAAM,eAAe,MAAK,MAAK;AAAA;AAAA;AAAA,IACjG;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT,SAAS,MAAM,kBAAkB,YAAY;AAAA,MAC7C,OAAO,MAAM,kBAAkB,EAAE,YAAY,MAAM,gBAAgB,IAAI;AAAA,MAEvE,+BAAC,UAAO,KAAK,GAAG,QAAO,UAAS,OAAO,EAAE,WAAW,SAAS,GAC1D;AAAA,cAAM,SAAS,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,SAAS,gBAAM,OAAM;AAAA,QACvE,MAAM,QAAQ,oBAAC,QAAK,MAAK,SAAQ,OAAM,aAAY,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,QACvF,qBAAC,UAAO,KAAK,GAAG,QAAO,UAAS,OAAO,EAAE,UAAU,OAAO,GACxD;AAAA,8BAAC,OAAI,OAAO,MAAM,aAAa,MAAM,MAAM,YAAY,SAAS,MAAM,eAAe;AAAA,UACpF,MAAM,gBACL,oBAAC,OAAI,OAAO,MAAM,cAAc,MAAM,MAAM,eAAe,KAAK,SAAQ,WAAU;AAAA,WAEtF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;AAEO,MAAM,WAAW,YAAY;AAAA,EAClC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,wBAAwB,aAAa,mBAAmB,YAAY,YAAY,eAAe,UAAU;AACrI,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Cta.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { CtaBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { VStack, HStack, Card, Heading, Text, Btn } from \"../ui\";\n\ntype Cta = z.infer<typeof CtaBlockSchema>;\n\nfunction CtaRender({ block }: BlockRenderProps<Cta>) {\n if (block.sticky) {\n return (\n <HStack\n gap={3}\n vAlign=\"center\"\n hAlign=\"between\"\n role=\"region\"\n aria-label={block.title || \"Call-to-action\"}\n style={{ position: \"sticky\", bottom: 0, zIndex: 30, padding: \"0.75rem 1rem\", background: \"var(--color-background-card)\", borderTop: \"1px solid var(--color-border)\", flexWrap: \"wrap\" }}\n >\n <Text weight=\"bold\">{block.title || block.text}</Text>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} size=\"md\" />\n </HStack>\n );\n }\n return (\n <Card\n padding={6}\n variant={block.backgroundColor ? \"default\" : \"muted\"}\n style={block.backgroundColor ? { background: block.backgroundColor } : undefined}\n >\n <VStack gap={3} hAlign=\"center\" style={{ textAlign: \"center\" }}>\n {block.title && <Heading level={2} data-xicms-edit=\"title\">{block.title}</Heading>}\n {block.text && <Text type=\"large\" color=\"secondary\" data-xicms-edit=\"text\">{block.text}</Text>}\n <HStack gap={2} hAlign=\"center\" style={{ flexWrap: \"wrap\" }}>\n <Btn label={block.buttonLabel} href={block.buttonHref} variant={block.buttonVariant} />\n {block.button2Label && (\n <Btn label={block.button2Label} href={block.button2Href ?? \"#\"} variant=\"outline\" />\n )}\n </HStack>\n </VStack>\n </Card>\n );\n}\n\nexport const ctaBlock = defineBlock({\n type: \"cta\",\n fields: { buttonVariant: { type: \"select\", options: [{ value: \"primary\", label: \"Primair\" }, { value: \"secondary\", label: \"Secundair\" }, { value: \"outline\", label: \"Outline\" }] } },\n schema: CtaBlockSchema,\n Render: CtaRender,\n label: \"Call-to-action\",\n icon: \"📣\",\n defaults: () => ({ title: \"Klaar om te starten?\", buttonLabel: \"Neem contact op\", buttonHref: \"#contact\", buttonVariant: \"primary\" }),\n});\n"],"mappings":";AAYM,SAQE,KARF;AATN,SAAS,sBAAsB;AAC/B,SAAS,mBAA0C;AACnD,SAAS,QAAQ,QAAQ,MAAM,SAAS,MAAM,WAAW;AAIzD,SAAS,UAAU,EAAE,MAAM,GAA0B;AACnD,MAAI,MAAM,QAAQ;AAChB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,QAAO;AAAA,QACP,QAAO;AAAA,QACP,MAAK;AAAA,QACL,cAAY,MAAM,SAAS;AAAA,QAC3B,OAAO,EAAE,UAAU,UAAU,QAAQ,GAAG,QAAQ,IAAI,SAAS,gBAAgB,YAAY,gCAAgC,WAAW,iCAAiC,UAAU,OAAO;AAAA,QAEtL;AAAA,8BAAC,QAAK,QAAO,QAAQ,gBAAM,SAAS,MAAM,MAAK;AAAA,UAC/C,oBAAC,OAAI,OAAO,MAAM,aAAa,MAAM,MAAM,YAAY,SAAS,MAAM,eAAe,MAAK,MAAK;AAAA;AAAA;AAAA,IACjG;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAS;AAAA,MACT,SAAS,MAAM,kBAAkB,YAAY;AAAA,MAC7C,OAAO,MAAM,kBAAkB,EAAE,YAAY,MAAM,gBAAgB,IAAI;AAAA,MAEvE,+BAAC,UAAO,KAAK,GAAG,QAAO,UAAS,OAAO,EAAE,WAAW,SAAS,GAC1D;AAAA,cAAM,SAAS,oBAAC,WAAQ,OAAO,GAAG,mBAAgB,SAAS,gBAAM,OAAM;AAAA,QACvE,MAAM,QAAQ,oBAAC,QAAK,MAAK,SAAQ,OAAM,aAAY,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,QACvF,qBAAC,UAAO,KAAK,GAAG,QAAO,UAAS,OAAO,EAAE,UAAU,OAAO,GACxD;AAAA,8BAAC,OAAI,OAAO,MAAM,aAAa,MAAM,MAAM,YAAY,SAAS,MAAM,eAAe;AAAA,UACpF,MAAM,gBACL,oBAAC,OAAI,OAAO,MAAM,cAAc,MAAM,MAAM,eAAe,KAAK,SAAQ,WAAU;AAAA,WAEtF;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;AAEO,MAAM,WAAW,YAAY;AAAA,EAClC,MAAM;AAAA,EACN,QAAQ,EAAE,eAAe,EAAE,MAAM,UAAU,SAAS,CAAC,EAAE,OAAO,WAAW,OAAO,UAAU,GAAG,EAAE,OAAO,aAAa,OAAO,YAAY,GAAG,EAAE,OAAO,WAAW,OAAO,UAAU,CAAC,EAAE,EAAE;AAAA,EACnL,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,wBAAwB,aAAa,mBAAmB,YAAY,YAAY,eAAe,UAAU;AACrI,CAAC;","names":[]}
@@ -11,6 +11,7 @@ function ButtonsRender({ block }) {
11
11
  }
12
12
  const buttonsBlock = defineBlock({
13
13
  type: "buttons",
14
+ fields: { "items.variant": { type: "select", options: [{ value: "primary", label: "Primair" }, { value: "secondary", label: "Secundair" }, { value: "outline", label: "Outline" }] } },
14
15
  schema: ButtonsBlockSchema,
15
16
  Render: ButtonsRender,
16
17
  label: "Knoppen",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/blocks/builtins/Elements.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { ButtonsBlockSchema, QuoteBlockSchema, DividerBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { HStack, VStack, Card, Text, Btn, Divider } from \"../ui\";\n\n/* ── Buttons (knopgroep) ─────────────────────────────────────────────────── */\ntype Buttons = z.infer<typeof ButtonsBlockSchema>;\nfunction ButtonsRender({ block }: BlockRenderProps<Buttons>) {\n const items = (block.items ?? []).filter((b) => b.label);\n if (items.length === 0) return null;\n const hAlign = block.align === \"center\" ? \"center\" : block.align === \"right\" ? \"end\" : \"start\";\n return (\n <HStack gap={2} hAlign={hAlign} style={{ flexWrap: \"wrap\" }}>\n {items.map((b, i) => (\n <Btn key={i} label={b.label} href={b.href} variant={b.variant} />\n ))}\n </HStack>\n );\n}\nexport const buttonsBlock = defineBlock({\n type: \"buttons\",\n schema: ButtonsBlockSchema,\n Render: ButtonsRender,\n label: \"Knoppen\",\n icon: \"🔘\",\n defaults: () => ({ align: \"left\", items: [{ label: \"Knop\", href: \"#\", variant: \"primary\" }] }),\n});\n\n/* ── Quote (uitspraak) ───────────────────────────────────────────────────── */\ntype Quote = z.infer<typeof QuoteBlockSchema>;\nfunction QuoteRender({ block }: BlockRenderProps<Quote>) {\n if (!block.text) return null;\n return (\n <Card padding={5} variant=\"muted\">\n <VStack gap={2} as=\"figure\" style={{ margin: 0 }}>\n <Text type=\"large\" as=\"p\" style={{ fontStyle: \"italic\" }}>“<span data-xicms-edit=\"text\">{block.text}</span>”</Text>\n {(block.author || block.role) && (\n <Text as=\"label\" color=\"secondary\">\n — {block.author}\n {block.role ? ` · ${block.role}` : \"\"}\n </Text>\n )}\n </VStack>\n </Card>\n );\n}\nexport const quoteBlock = defineBlock({\n type: \"quote\",\n schema: QuoteBlockSchema,\n Render: QuoteRender,\n label: \"Uitspraak\",\n icon: \"❝\",\n defaults: () => ({ text: \"Een krachtige uitspraak.\", author: \"\" }),\n});\n\n/* ── Divider (scheiding) ─────────────────────────────────────────────────── */\ntype DividerBlk = z.infer<typeof DividerBlockSchema>;\nfunction DividerRender({ block }: BlockRenderProps<DividerBlk>) {\n const pad = block.spacing === \"sm\" ? \"0.75rem\" : block.spacing === \"lg\" ? \"3rem\" : \"1.5rem\";\n if (block.variant === \"space\") return <div style={{ height: pad }} aria-hidden=\"true\" />;\n if (block.variant === \"dots\") {\n return (\n <div style={{ textAlign: \"center\", padding: `${pad} 0`, color: \"var(--color-text-secondary)\", letterSpacing: \"0.5em\" }} aria-hidden=\"true\">\n • • •\n </div>\n );\n }\n return <div style={{ padding: `${pad} 0` }}><Divider variant=\"subtle\" /></div>;\n}\nexport const dividerBlock = defineBlock({\n type: \"divider\",\n schema: DividerBlockSchema,\n Render: DividerRender,\n label: \"Scheiding\",\n icon: \"➖\",\n defaults: () => ({ variant: \"line\", spacing: \"md\" }),\n});\n"],"mappings":";AAgBQ,cAqBA,YArBA;AAbR,SAAS,oBAAoB,kBAAkB,0BAA0B;AACzE,SAAS,mBAA0C;AACnD,SAAS,QAAQ,QAAQ,MAAM,MAAM,KAAK,eAAe;AAIzD,SAAS,cAAc,EAAE,MAAM,GAA8B;AAC3D,QAAM,SAAS,MAAM,SAAS,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,SAAS,MAAM,UAAU,WAAW,WAAW,MAAM,UAAU,UAAU,QAAQ;AACvF,SACE,oBAAC,UAAO,KAAK,GAAG,QAAgB,OAAO,EAAE,UAAU,OAAO,GACvD,gBAAM,IAAI,CAAC,GAAG,MACb,oBAAC,OAAY,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,SAAS,EAAE,WAA5C,CAAqD,CAChE,GACH;AAEJ;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,QAAQ,OAAO,CAAC,EAAE,OAAO,QAAQ,MAAM,KAAK,SAAS,UAAU,CAAC,EAAE;AAC9F,CAAC;AAID,SAAS,YAAY,EAAE,MAAM,GAA4B;AACvD,MAAI,CAAC,MAAM,KAAM,QAAO;AACxB,SACE,oBAAC,QAAK,SAAS,GAAG,SAAQ,SACxB,+BAAC,UAAO,KAAK,GAAG,IAAG,UAAS,OAAO,EAAE,QAAQ,EAAE,GAC7C;AAAA,yBAAC,QAAK,MAAK,SAAQ,IAAG,KAAI,OAAO,EAAE,WAAW,SAAS,GAAG;AAAA;AAAA,MAAC,oBAAC,UAAK,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,MAAO;AAAA,OAAC;AAAA,KAC1G,MAAM,UAAU,MAAM,SACtB,qBAAC,QAAK,IAAG,SAAQ,OAAM,aAAY;AAAA;AAAA,MAC9B,MAAM;AAAA,MACR,MAAM,OAAO,SAAM,MAAM,IAAI,KAAK;AAAA,OACrC;AAAA,KAEJ,GACF;AAEJ;AACO,MAAM,aAAa,YAAY;AAAA,EACpC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,MAAM,4BAA4B,QAAQ,GAAG;AAClE,CAAC;AAID,SAAS,cAAc,EAAE,MAAM,GAAiC;AAC9D,QAAM,MAAM,MAAM,YAAY,OAAO,YAAY,MAAM,YAAY,OAAO,SAAS;AACnF,MAAI,MAAM,YAAY,QAAS,QAAO,oBAAC,SAAI,OAAO,EAAE,QAAQ,IAAI,GAAG,eAAY,QAAO;AACtF,MAAI,MAAM,YAAY,QAAQ;AAC5B,WACE,oBAAC,SAAI,OAAO,EAAE,WAAW,UAAU,SAAS,GAAG,GAAG,MAAM,OAAO,+BAA+B,eAAe,QAAQ,GAAG,eAAY,QAAO,kCAE3I;AAAA,EAEJ;AACA,SAAO,oBAAC,SAAI,OAAO,EAAE,SAAS,GAAG,GAAG,KAAK,GAAG,8BAAC,WAAQ,SAAQ,UAAS,GAAE;AAC1E;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,QAAQ,SAAS,KAAK;AACpD,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/blocks/builtins/Elements.tsx"],"sourcesContent":["\"use client\";\n\nimport type { z } from \"zod\";\nimport { ButtonsBlockSchema, QuoteBlockSchema, DividerBlockSchema } from \"../../schema/blocks\";\nimport { defineBlock, type BlockRenderProps } from \"../registry\";\nimport { HStack, VStack, Card, Text, Btn, Divider } from \"../ui\";\n\n/* ── Buttons (knopgroep) ─────────────────────────────────────────────────── */\ntype Buttons = z.infer<typeof ButtonsBlockSchema>;\nfunction ButtonsRender({ block }: BlockRenderProps<Buttons>) {\n const items = (block.items ?? []).filter((b) => b.label);\n if (items.length === 0) return null;\n const hAlign = block.align === \"center\" ? \"center\" : block.align === \"right\" ? \"end\" : \"start\";\n return (\n <HStack gap={2} hAlign={hAlign} style={{ flexWrap: \"wrap\" }}>\n {items.map((b, i) => (\n <Btn key={i} label={b.label} href={b.href} variant={b.variant} />\n ))}\n </HStack>\n );\n}\nexport const buttonsBlock = defineBlock({\n type: \"buttons\",\n fields: { \"items.variant\": { type: \"select\", options: [{ value: \"primary\", label: \"Primair\" }, { value: \"secondary\", label: \"Secundair\" }, { value: \"outline\", label: \"Outline\" }] } },\n schema: ButtonsBlockSchema,\n Render: ButtonsRender,\n label: \"Knoppen\",\n icon: \"🔘\",\n defaults: () => ({ align: \"left\", items: [{ label: \"Knop\", href: \"#\", variant: \"primary\" }] }),\n});\n\n/* ── Quote (uitspraak) ───────────────────────────────────────────────────── */\ntype Quote = z.infer<typeof QuoteBlockSchema>;\nfunction QuoteRender({ block }: BlockRenderProps<Quote>) {\n if (!block.text) return null;\n return (\n <Card padding={5} variant=\"muted\">\n <VStack gap={2} as=\"figure\" style={{ margin: 0 }}>\n <Text type=\"large\" as=\"p\" style={{ fontStyle: \"italic\" }}>“<span data-xicms-edit=\"text\">{block.text}</span>”</Text>\n {(block.author || block.role) && (\n <Text as=\"label\" color=\"secondary\">\n — {block.author}\n {block.role ? ` · ${block.role}` : \"\"}\n </Text>\n )}\n </VStack>\n </Card>\n );\n}\nexport const quoteBlock = defineBlock({\n type: \"quote\",\n schema: QuoteBlockSchema,\n Render: QuoteRender,\n label: \"Uitspraak\",\n icon: \"❝\",\n defaults: () => ({ text: \"Een krachtige uitspraak.\", author: \"\" }),\n});\n\n/* ── Divider (scheiding) ─────────────────────────────────────────────────── */\ntype DividerBlk = z.infer<typeof DividerBlockSchema>;\nfunction DividerRender({ block }: BlockRenderProps<DividerBlk>) {\n const pad = block.spacing === \"sm\" ? \"0.75rem\" : block.spacing === \"lg\" ? \"3rem\" : \"1.5rem\";\n if (block.variant === \"space\") return <div style={{ height: pad }} aria-hidden=\"true\" />;\n if (block.variant === \"dots\") {\n return (\n <div style={{ textAlign: \"center\", padding: `${pad} 0`, color: \"var(--color-text-secondary)\", letterSpacing: \"0.5em\" }} aria-hidden=\"true\">\n • • •\n </div>\n );\n }\n return <div style={{ padding: `${pad} 0` }}><Divider variant=\"subtle\" /></div>;\n}\nexport const dividerBlock = defineBlock({\n type: \"divider\",\n schema: DividerBlockSchema,\n Render: DividerRender,\n label: \"Scheiding\",\n icon: \"➖\",\n defaults: () => ({ variant: \"line\", spacing: \"md\" }),\n});\n"],"mappings":";AAgBQ,cAsBA,YAtBA;AAbR,SAAS,oBAAoB,kBAAkB,0BAA0B;AACzE,SAAS,mBAA0C;AACnD,SAAS,QAAQ,QAAQ,MAAM,MAAM,KAAK,eAAe;AAIzD,SAAS,cAAc,EAAE,MAAM,GAA8B;AAC3D,QAAM,SAAS,MAAM,SAAS,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,SAAS,MAAM,UAAU,WAAW,WAAW,MAAM,UAAU,UAAU,QAAQ;AACvF,SACE,oBAAC,UAAO,KAAK,GAAG,QAAgB,OAAO,EAAE,UAAU,OAAO,GACvD,gBAAM,IAAI,CAAC,GAAG,MACb,oBAAC,OAAY,OAAO,EAAE,OAAO,MAAM,EAAE,MAAM,SAAS,EAAE,WAA5C,CAAqD,CAChE,GACH;AAEJ;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ,EAAE,iBAAiB,EAAE,MAAM,UAAU,SAAS,CAAC,EAAE,OAAO,WAAW,OAAO,UAAU,GAAG,EAAE,OAAO,aAAa,OAAO,YAAY,GAAG,EAAE,OAAO,WAAW,OAAO,UAAU,CAAC,EAAE,EAAE;AAAA,EACrL,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,OAAO,QAAQ,OAAO,CAAC,EAAE,OAAO,QAAQ,MAAM,KAAK,SAAS,UAAU,CAAC,EAAE;AAC9F,CAAC;AAID,SAAS,YAAY,EAAE,MAAM,GAA4B;AACvD,MAAI,CAAC,MAAM,KAAM,QAAO;AACxB,SACE,oBAAC,QAAK,SAAS,GAAG,SAAQ,SACxB,+BAAC,UAAO,KAAK,GAAG,IAAG,UAAS,OAAO,EAAE,QAAQ,EAAE,GAC7C;AAAA,yBAAC,QAAK,MAAK,SAAQ,IAAG,KAAI,OAAO,EAAE,WAAW,SAAS,GAAG;AAAA;AAAA,MAAC,oBAAC,UAAK,mBAAgB,QAAQ,gBAAM,MAAK;AAAA,MAAO;AAAA,OAAC;AAAA,KAC1G,MAAM,UAAU,MAAM,SACtB,qBAAC,QAAK,IAAG,SAAQ,OAAM,aAAY;AAAA;AAAA,MAC9B,MAAM;AAAA,MACR,MAAM,OAAO,SAAM,MAAM,IAAI,KAAK;AAAA,OACrC;AAAA,KAEJ,GACF;AAEJ;AACO,MAAM,aAAa,YAAY;AAAA,EACpC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,MAAM,4BAA4B,QAAQ,GAAG;AAClE,CAAC;AAID,SAAS,cAAc,EAAE,MAAM,GAAiC;AAC9D,QAAM,MAAM,MAAM,YAAY,OAAO,YAAY,MAAM,YAAY,OAAO,SAAS;AACnF,MAAI,MAAM,YAAY,QAAS,QAAO,oBAAC,SAAI,OAAO,EAAE,QAAQ,IAAI,GAAG,eAAY,QAAO;AACtF,MAAI,MAAM,YAAY,QAAQ;AAC5B,WACE,oBAAC,SAAI,OAAO,EAAE,WAAW,UAAU,SAAS,GAAG,GAAG,MAAM,OAAO,+BAA+B,eAAe,QAAQ,GAAG,eAAY,QAAO,kCAE3I;AAAA,EAEJ;AACA,SAAO,oBAAC,SAAI,OAAO,EAAE,SAAS,GAAG,GAAG,KAAK,GAAG,8BAAC,WAAQ,SAAQ,UAAS,GAAE;AAC1E;AACO,MAAM,eAAe,YAAY;AAAA,EACtC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,OAAO,EAAE,SAAS,QAAQ,SAAS,KAAK;AACpD,CAAC;","names":[]}
@@ -35,7 +35,7 @@ function FaqRender({ block }) {
35
35
  const [q, setQ] = useState("");
36
36
  const withSlugs = useMemo(() => {
37
37
  const slugs = questionSlugs(items.map((it) => it.q));
38
- return items.map((it, i) => ({ it, slug: slugs[i] }));
38
+ return items.map((it, i) => ({ it, slug: slugs[i], idx: i }));
39
39
  }, [items]);
40
40
  const [openSlug, setOpenSlug] = useState(null);
41
41
  useEffect(() => {
@@ -63,17 +63,18 @@ function FaqRender({ block }) {
63
63
  }
64
64
  ) }),
65
65
  /* @__PURE__ */ jsxs(VStack, { gap: 1.5, children: [
66
- filtered.map(({ it, slug }, i) => {
66
+ filtered.map(({ it, slug, idx }, i) => {
67
67
  return /* @__PURE__ */ jsx("div", { id: slug || void 0, children: /* @__PURE__ */ jsx(
68
68
  Collapsible,
69
69
  {
70
- trigger: /* @__PURE__ */ jsx(Text, { weight: "bold", className: "xicms-faq__question", children: it.q }),
70
+ trigger: /* @__PURE__ */ jsx(Text, { weight: "bold", className: "xicms-faq__question", "data-xicms-edit": `items.${idx}.q`, children: it.q }),
71
71
  defaultIsOpen: block.defaultOpen || slug !== "" && slug === openSlug,
72
72
  children: it.aHtml ? (
73
73
  // Server-side gesanitized bij publiceren (velden op *Html) — zie functions/lib/sanitize.
74
+ // Rich-text-antwoord: bewerk je via het zijpaneel (de platte-tekst-haak dekt geen html-veld).
74
75
  // eslint-disable-next-line react/no-danger
75
76
  /* @__PURE__ */ jsx("div", { className: "xicms-richtext xicms-faq__answer", dangerouslySetInnerHTML: { __html: it.aHtml } })
76
- ) : /* @__PURE__ */ jsx(Text, { color: "secondary", className: "xicms-faq__answer", children: it.a })
77
+ ) : /* @__PURE__ */ jsx(Text, { color: "secondary", className: "xicms-faq__answer", "data-xicms-edit": `items.${idx}.a`, children: it.a })
77
78
  },
78
79
  `${i}-${slug === openSlug ? "open" : "dicht"}`
79
80
  ) }, i);