@xiwebdesign/xicms-core 7.14.0 → 7.16.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 (263) hide show
  1. package/dist/admin/AdminApp.js +4 -3
  2. package/dist/admin/AdminApp.js.map +1 -1
  3. package/dist/admin/dashboard/DashboardPanel.d.ts +3 -0
  4. package/dist/admin/dashboard/DashboardPanel.js +316 -0
  5. package/dist/admin/dashboard/DashboardPanel.js.map +1 -0
  6. package/dist/admin/navContext.d.ts +14 -0
  7. package/dist/admin/navContext.js +12 -0
  8. package/dist/admin/navContext.js.map +1 -0
  9. package/dist/admin/pages/PagesPanel.js +38 -12
  10. package/dist/admin/pages/PagesPanel.js.map +1 -1
  11. package/dist/admin/panelRegistry.js +4 -0
  12. package/dist/admin/panelRegistry.js.map +1 -1
  13. package/dist/admin/ui/PanelIcon.js +5 -0
  14. package/dist/admin/ui/PanelIcon.js.map +1 -1
  15. package/dist/admin/visual/VisualEditor.js +56 -20
  16. package/dist/admin/visual/VisualEditor.js.map +1 -1
  17. package/dist/admin.css +1 -1
  18. package/dist/auth/AuthProvider.js +8 -1
  19. package/dist/auth/AuthProvider.js.map +1 -1
  20. package/dist/blocks/builtins/Accordion.d.ts +1 -2
  21. package/dist/blocks/builtins/Accordion.js +2 -39
  22. package/dist/blocks/builtins/Accordion.js.map +1 -1
  23. package/dist/blocks/builtins/AccordionRender.d.ts +7 -0
  24. package/dist/blocks/builtins/AccordionRender.js +42 -0
  25. package/dist/blocks/builtins/AccordionRender.js.map +1 -0
  26. package/dist/blocks/builtins/Agenda.d.ts +1 -29
  27. package/dist/blocks/builtins/Agenda.js +3 -94
  28. package/dist/blocks/builtins/Agenda.js.map +1 -1
  29. package/dist/blocks/builtins/AgendaRender.d.ts +34 -0
  30. package/dist/blocks/builtins/AgendaRender.js +96 -0
  31. package/dist/blocks/builtins/AgendaRender.js.map +1 -0
  32. package/dist/blocks/builtins/Banner.d.ts +1 -2
  33. package/dist/blocks/builtins/Banner.js +2 -32
  34. package/dist/blocks/builtins/Banner.js.map +1 -1
  35. package/dist/blocks/builtins/BannerRender.d.ts +11 -0
  36. package/dist/blocks/builtins/BannerRender.js +35 -0
  37. package/dist/blocks/builtins/BannerRender.js.map +1 -0
  38. package/dist/blocks/builtins/BeforeAfter.d.ts +1 -2
  39. package/dist/blocks/builtins/BeforeAfter.js +2 -31
  40. package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
  41. package/dist/blocks/builtins/BeforeAfterRender.d.ts +11 -0
  42. package/dist/blocks/builtins/BeforeAfterRender.js +34 -0
  43. package/dist/blocks/builtins/BeforeAfterRender.js.map +1 -0
  44. package/dist/blocks/builtins/Booking.d.ts +1 -2
  45. package/dist/blocks/builtins/Booking.js +2 -14
  46. package/dist/blocks/builtins/Booking.js.map +1 -1
  47. package/dist/blocks/builtins/BookingRender.d.ts +8 -0
  48. package/dist/blocks/builtins/BookingRender.js +17 -0
  49. package/dist/blocks/builtins/BookingRender.js.map +1 -0
  50. package/dist/blocks/builtins/Breadcrumbs.d.ts +1 -10
  51. package/dist/blocks/builtins/Breadcrumbs.js +3 -69
  52. package/dist/blocks/builtins/Breadcrumbs.js.map +1 -1
  53. package/dist/blocks/builtins/BreadcrumbsRender.d.ts +18 -0
  54. package/dist/blocks/builtins/BreadcrumbsRender.js +71 -0
  55. package/dist/blocks/builtins/BreadcrumbsRender.js.map +1 -0
  56. package/dist/blocks/builtins/Cards.d.ts +1 -2
  57. package/dist/blocks/builtins/Cards.js +2 -74
  58. package/dist/blocks/builtins/Cards.js.map +1 -1
  59. package/dist/blocks/builtins/CardsRender.d.ts +7 -0
  60. package/dist/blocks/builtins/CardsRender.js +77 -0
  61. package/dist/blocks/builtins/CardsRender.js.map +1 -0
  62. package/dist/blocks/builtins/Columns.d.ts +1 -2
  63. package/dist/blocks/builtins/Columns.js +2 -9
  64. package/dist/blocks/builtins/Columns.js.map +1 -1
  65. package/dist/blocks/builtins/ColumnsRender.d.ts +6 -0
  66. package/dist/blocks/builtins/ColumnsRender.js +12 -0
  67. package/dist/blocks/builtins/ColumnsRender.js.map +1 -0
  68. package/dist/blocks/builtins/Contact.d.ts +2 -7
  69. package/dist/blocks/builtins/Contact.js +3 -102
  70. package/dist/blocks/builtins/Contact.js.map +1 -1
  71. package/dist/blocks/builtins/ContactRender.d.ts +9 -0
  72. package/dist/blocks/builtins/ContactRender.js +105 -0
  73. package/dist/blocks/builtins/ContactRender.js.map +1 -0
  74. package/dist/blocks/builtins/Countdown.d.ts +1 -18
  75. package/dist/blocks/builtins/Countdown.js +3 -53
  76. package/dist/blocks/builtins/Countdown.js.map +1 -1
  77. package/dist/blocks/builtins/CountdownRender.d.ts +22 -0
  78. package/dist/blocks/builtins/CountdownRender.js +61 -0
  79. package/dist/blocks/builtins/CountdownRender.js.map +1 -0
  80. package/dist/blocks/builtins/Cta.d.ts +1 -2
  81. package/dist/blocks/builtins/Cta.js +2 -39
  82. package/dist/blocks/builtins/Cta.js.map +1 -1
  83. package/dist/blocks/builtins/CtaRender.d.ts +6 -0
  84. package/dist/blocks/builtins/CtaRender.js +42 -0
  85. package/dist/blocks/builtins/CtaRender.js.map +1 -0
  86. package/dist/blocks/builtins/Downloads.d.ts +1 -2
  87. package/dist/blocks/builtins/Downloads.js +2 -16
  88. package/dist/blocks/builtins/Downloads.js.map +1 -1
  89. package/dist/blocks/builtins/DownloadsRender.d.ts +7 -0
  90. package/dist/blocks/builtins/DownloadsRender.js +19 -0
  91. package/dist/blocks/builtins/DownloadsRender.js.map +1 -0
  92. package/dist/blocks/builtins/Elements.d.ts +3 -4
  93. package/dist/blocks/builtins/Elements.js +4 -38
  94. package/dist/blocks/builtins/Elements.js.map +1 -1
  95. package/dist/blocks/builtins/ElementsRender.d.ts +10 -0
  96. package/dist/blocks/builtins/ElementsRender.js +41 -0
  97. package/dist/blocks/builtins/ElementsRender.js.map +1 -0
  98. package/dist/blocks/builtins/Embed.d.ts +1 -2
  99. package/dist/blocks/builtins/Embed.js +2 -19
  100. package/dist/blocks/builtins/Embed.js.map +1 -1
  101. package/dist/blocks/builtins/EmbedRender.d.ts +7 -0
  102. package/dist/blocks/builtins/EmbedRender.js +22 -0
  103. package/dist/blocks/builtins/EmbedRender.js.map +1 -0
  104. package/dist/blocks/builtins/Faq.d.ts +1 -6
  105. package/dist/blocks/builtins/Faq.js +3 -103
  106. package/dist/blocks/builtins/Faq.js.map +1 -1
  107. package/dist/blocks/builtins/FaqRender.d.ts +10 -0
  108. package/dist/blocks/builtins/FaqRender.js +105 -0
  109. package/dist/blocks/builtins/FaqRender.js.map +1 -0
  110. package/dist/blocks/builtins/FormBlock.d.ts +1 -2
  111. package/dist/blocks/builtins/FormBlock.js +2 -222
  112. package/dist/blocks/builtins/FormBlock.js.map +1 -1
  113. package/dist/blocks/builtins/FormBlockRender.d.ts +6 -0
  114. package/dist/blocks/builtins/FormBlockRender.js +225 -0
  115. package/dist/blocks/builtins/FormBlockRender.js.map +1 -0
  116. package/dist/blocks/builtins/Gallery.d.ts +1 -23
  117. package/dist/blocks/builtins/Gallery.js +2 -164
  118. package/dist/blocks/builtins/Gallery.js.map +1 -1
  119. package/dist/blocks/builtins/GalleryRender.d.ts +25 -0
  120. package/dist/blocks/builtins/GalleryRender.js +167 -0
  121. package/dist/blocks/builtins/GalleryRender.js.map +1 -0
  122. package/dist/blocks/builtins/Hero.d.ts +1 -2
  123. package/dist/blocks/builtins/Hero.js +2 -73
  124. package/dist/blocks/builtins/Hero.js.map +1 -1
  125. package/dist/blocks/builtins/HeroRender.d.ts +6 -0
  126. package/dist/blocks/builtins/HeroRender.js +76 -0
  127. package/dist/blocks/builtins/HeroRender.js.map +1 -0
  128. package/dist/blocks/builtins/ImageBlock.d.ts +1 -2
  129. package/dist/blocks/builtins/ImageBlock.js +2 -22
  130. package/dist/blocks/builtins/ImageBlock.js.map +1 -1
  131. package/dist/blocks/builtins/ImageBlockRender.d.ts +6 -0
  132. package/dist/blocks/builtins/ImageBlockRender.js +25 -0
  133. package/dist/blocks/builtins/ImageBlockRender.js.map +1 -0
  134. package/dist/blocks/builtins/Jobs.d.ts +1 -15
  135. package/dist/blocks/builtins/Jobs.js +3 -69
  136. package/dist/blocks/builtins/Jobs.js.map +1 -1
  137. package/dist/blocks/builtins/JobsRender.d.ts +17 -0
  138. package/dist/blocks/builtins/JobsRender.js +71 -0
  139. package/dist/blocks/builtins/JobsRender.js.map +1 -0
  140. package/dist/blocks/builtins/Layout.d.ts +2 -3
  141. package/dist/blocks/builtins/Layout.js +3 -39
  142. package/dist/blocks/builtins/Layout.js.map +1 -1
  143. package/dist/blocks/builtins/LayoutRender.d.ts +8 -0
  144. package/dist/blocks/builtins/LayoutRender.js +42 -0
  145. package/dist/blocks/builtins/LayoutRender.js.map +1 -0
  146. package/dist/blocks/builtins/MapBlock.d.ts +1 -2
  147. package/dist/blocks/builtins/MapBlock.js +2 -45
  148. package/dist/blocks/builtins/MapBlock.js.map +1 -1
  149. package/dist/blocks/builtins/MapBlockRender.d.ts +6 -0
  150. package/dist/blocks/builtins/MapBlockRender.js +48 -0
  151. package/dist/blocks/builtins/MapBlockRender.js.map +1 -0
  152. package/dist/blocks/builtins/NewsListBlock.d.ts +1 -2
  153. package/dist/blocks/builtins/NewsListBlock.js +2 -11
  154. package/dist/blocks/builtins/NewsListBlock.js.map +1 -1
  155. package/dist/blocks/builtins/NewsListBlockRender.d.ts +6 -0
  156. package/dist/blocks/builtins/NewsListBlockRender.js +14 -0
  157. package/dist/blocks/builtins/NewsListBlockRender.js.map +1 -0
  158. package/dist/blocks/builtins/Newsletter.d.ts +1 -2
  159. package/dist/blocks/builtins/Newsletter.js +2 -89
  160. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  161. package/dist/blocks/builtins/NewsletterRender.d.ts +13 -0
  162. package/dist/blocks/builtins/NewsletterRender.js +92 -0
  163. package/dist/blocks/builtins/NewsletterRender.js.map +1 -0
  164. package/dist/blocks/builtins/OpeningHours.d.ts +1 -2
  165. package/dist/blocks/builtins/OpeningHours.js +2 -28
  166. package/dist/blocks/builtins/OpeningHours.js.map +1 -1
  167. package/dist/blocks/builtins/OpeningHoursRender.d.ts +7 -0
  168. package/dist/blocks/builtins/OpeningHoursRender.js +31 -0
  169. package/dist/blocks/builtins/OpeningHoursRender.js.map +1 -0
  170. package/dist/blocks/builtins/PdfBlock.d.ts +1 -2
  171. package/dist/blocks/builtins/PdfBlock.js +2 -76
  172. package/dist/blocks/builtins/PdfBlock.js.map +1 -1
  173. package/dist/blocks/builtins/PdfBlockRender.d.ts +11 -0
  174. package/dist/blocks/builtins/PdfBlockRender.js +79 -0
  175. package/dist/blocks/builtins/PdfBlockRender.js.map +1 -0
  176. package/dist/blocks/builtins/Portfolio.d.ts +1 -2
  177. package/dist/blocks/builtins/Portfolio.js +2 -75
  178. package/dist/blocks/builtins/Portfolio.js.map +1 -1
  179. package/dist/blocks/builtins/PortfolioRender.d.ts +7 -0
  180. package/dist/blocks/builtins/PortfolioRender.js +78 -0
  181. package/dist/blocks/builtins/PortfolioRender.js.map +1 -0
  182. package/dist/blocks/builtins/Pricing.d.ts +1 -8
  183. package/dist/blocks/builtins/Pricing.js +3 -73
  184. package/dist/blocks/builtins/Pricing.js.map +1 -1
  185. package/dist/blocks/builtins/PricingRender.d.ts +10 -0
  186. package/dist/blocks/builtins/PricingRender.js +75 -0
  187. package/dist/blocks/builtins/PricingRender.js.map +1 -0
  188. package/dist/blocks/builtins/RichTextBlock.d.ts +1 -2
  189. package/dist/blocks/builtins/RichTextBlock.js +2 -5
  190. package/dist/blocks/builtins/RichTextBlock.js.map +1 -1
  191. package/dist/blocks/builtins/RichTextBlockRender.d.ts +6 -0
  192. package/dist/blocks/builtins/RichTextBlockRender.js +8 -0
  193. package/dist/blocks/builtins/RichTextBlockRender.js.map +1 -0
  194. package/dist/blocks/builtins/Search.d.ts +1 -2
  195. package/dist/blocks/builtins/Search.js +2 -80
  196. package/dist/blocks/builtins/Search.js.map +1 -1
  197. package/dist/blocks/builtins/SearchRender.d.ts +7 -0
  198. package/dist/blocks/builtins/SearchRender.js +83 -0
  199. package/dist/blocks/builtins/SearchRender.js.map +1 -0
  200. package/dist/blocks/builtins/Sections.d.ts +4 -5
  201. package/dist/blocks/builtins/Sections.js +5 -142
  202. package/dist/blocks/builtins/Sections.js.map +1 -1
  203. package/dist/blocks/builtins/SectionsRender.d.ts +12 -0
  204. package/dist/blocks/builtins/SectionsRender.js +145 -0
  205. package/dist/blocks/builtins/SectionsRender.js.map +1 -0
  206. package/dist/blocks/builtins/Slider.d.ts +1 -2
  207. package/dist/blocks/builtins/Slider.js +2 -134
  208. package/dist/blocks/builtins/Slider.js.map +1 -1
  209. package/dist/blocks/builtins/SliderRender.d.ts +7 -0
  210. package/dist/blocks/builtins/SliderRender.js +137 -0
  211. package/dist/blocks/builtins/SliderRender.js.map +1 -0
  212. package/dist/blocks/builtins/Spacer.d.ts +1 -2
  213. package/dist/blocks/builtins/Spacer.js +2 -6
  214. package/dist/blocks/builtins/Spacer.js.map +1 -1
  215. package/dist/blocks/builtins/SpacerRender.d.ts +6 -0
  216. package/dist/blocks/builtins/SpacerRender.js +9 -0
  217. package/dist/blocks/builtins/SpacerRender.js.map +1 -0
  218. package/dist/blocks/builtins/SymbolRef.d.ts +1 -2
  219. package/dist/blocks/builtins/SymbolRef.js +2 -76
  220. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  221. package/dist/blocks/builtins/SymbolRefRender.d.ts +13 -0
  222. package/dist/blocks/builtins/SymbolRefRender.js +79 -0
  223. package/dist/blocks/builtins/SymbolRefRender.js.map +1 -0
  224. package/dist/blocks/builtins/Team.d.ts +1 -2
  225. package/dist/blocks/builtins/Team.js +2 -55
  226. package/dist/blocks/builtins/Team.js.map +1 -1
  227. package/dist/blocks/builtins/TeamRender.d.ts +7 -0
  228. package/dist/blocks/builtins/TeamRender.js +58 -0
  229. package/dist/blocks/builtins/TeamRender.js.map +1 -0
  230. package/dist/blocks/builtins/Testimonials.d.ts +1 -8
  231. package/dist/blocks/builtins/Testimonials.js +2 -69
  232. package/dist/blocks/builtins/Testimonials.js.map +1 -1
  233. package/dist/blocks/builtins/TestimonialsRender.d.ts +10 -0
  234. package/dist/blocks/builtins/TestimonialsRender.js +72 -0
  235. package/dist/blocks/builtins/TestimonialsRender.js.map +1 -0
  236. package/dist/blocks/builtins/Video.d.ts +1 -9
  237. package/dist/blocks/builtins/Video.js +2 -64
  238. package/dist/blocks/builtins/Video.js.map +1 -1
  239. package/dist/blocks/builtins/VideoRender.d.ts +10 -0
  240. package/dist/blocks/builtins/VideoRender.js +67 -0
  241. package/dist/blocks/builtins/VideoRender.js.map +1 -0
  242. package/dist/blocks/builtins/WhatsappChat.d.ts +1 -8
  243. package/dist/blocks/builtins/WhatsappChat.js +2 -37
  244. package/dist/blocks/builtins/WhatsappChat.js.map +1 -1
  245. package/dist/blocks/builtins/WhatsappChatRender.d.ts +17 -0
  246. package/dist/blocks/builtins/WhatsappChatRender.js +40 -0
  247. package/dist/blocks/builtins/WhatsappChatRender.js.map +1 -0
  248. package/dist/blocks/builtins/Widgets.d.ts +12 -13
  249. package/dist/blocks/builtins/Widgets.js +13 -175
  250. package/dist/blocks/builtins/Widgets.js.map +1 -1
  251. package/dist/blocks/builtins/WidgetsRender.d.ts +34 -0
  252. package/dist/blocks/builtins/WidgetsRender.js +178 -0
  253. package/dist/blocks/builtins/WidgetsRender.js.map +1 -0
  254. package/dist/components/barOffset.js +1 -1
  255. package/dist/components/barOffset.js.map +1 -1
  256. package/dist/modules/contract.js +2 -1
  257. package/dist/modules/contract.js.map +1 -1
  258. package/dist/schema/settings.d.ts +4 -0
  259. package/dist/schema/settings.js +10 -1
  260. package/dist/schema/settings.js.map +1 -1
  261. package/dist/styles.css +93 -19
  262. package/dist/version.js +1 -1
  263. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/blocks/builtins/WidgetsRender.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { z } from \"zod\";\nimport { Onvoltooid } from \"./lib/Onvoltooid\";\nimport type {\n NewsWidgetSchema,\n AgendaWidgetSchema,\n OpeningHoursWidgetSchema,\n ContactWidgetSchema,\n SocialWidgetSchema,\n NewsletterWidgetSchema,\n CtaWidgetSchema,\n GalleryWidgetSchema,\n TestimonialsWidgetSchema,\n CardsWidgetSchema,\n DownloadsWidgetSchema,\n CountdownWidgetSchema,\n} from \"../../schema/blocks\";\nimport type { BlockRenderProps } from \"../registry\";\nimport { Btn } from \"../ui\";\nimport { useNews } from \"../../components/News\";\nimport { selectAgendaEvents, formatEventDate } from \"./AgendaRender\";\nimport { parseDays } from \"./lib/openingHours\";\nimport { useCountdown, CountdownDigits } from \"./CountdownRender\";\nimport { TextInput } from \"@astryxdesign/core/TextInput\";\nimport { Button } from \"@astryxdesign/core/Button\";\n\n/**\n * Widgets = compacte zijbalk-varianten van de modules. Elke widget hergebruikt de gegevens en\n * logica van de volwaardige versie (zelfde nieuws-collectie, zelfde agenda-selectie, zelfde\n * countdown-hook) en rendert die alleen compacter. Plaats ze in het \"Met zijbalk\"-blok of in een\n * smalle kolom naast je hoofdinhoud.\n */\n\n/* ── Laatste nieuws ────────────────────────────────────────────────────────── */\ntype NewsW = z.infer<typeof NewsWidgetSchema>;\nexport function NewsWidgetRender({ block }: BlockRenderProps<NewsW>) {\n const { articles, loading } = useNews({ max: block.limit });\n const shown = articles.slice(0, block.limit);\n return (\n <div className=\"xicms-widget xicms-widget--news\">\n <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading ?? \"Laatste nieuws\"}</h3>\n {loading ? (\n <p className=\"xicms-widget__empty\">Nieuws laden…</p>\n ) : shown.length === 0 ? (\n <p className=\"xicms-widget__empty\">Nog geen nieuws.</p>\n ) : (\n <ul className=\"xicms-widget__list\">\n {shown.map((a) => (\n <li key={a.id} className=\"xicms-widget__item\">\n {block.showImages && a.coverUrl && (\n // eslint-disable-next-line @next/next/no-img-element\n <img className=\"xicms-widget__thumb\" src={a.coverUrl} alt=\"\" loading=\"lazy\" />\n )}\n <div>\n <a className=\"xicms-widget__link\" href={`${block.basePath}/${a.slug}`}>{a.title}</a>\n <span className=\"xicms-widget__meta\">{a.date}</span>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n/* ── Komende agenda ────────────────────────────────────────────────────────── */\ntype AgendaW = z.infer<typeof AgendaWidgetSchema>;\nexport function AgendaWidgetRender({ block }: BlockRenderProps<AgendaW>) {\n const events = selectAgendaEvents((block.events ?? []).map((e, i) => ({ ...e, _i: i })), { hidePast: block.hidePast, limit: block.limit });\n return (\n <div className=\"xicms-widget xicms-widget--agenda\">\n <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading ?? \"Komende evenementen\"}</h3>\n {events.length === 0 ? (\n <p className=\"xicms-widget__empty\">Geen geplande evenementen.</p>\n ) : (\n <ul className=\"xicms-widget__list\">\n {events.map((e) => (\n <li key={e._i} className=\"xicms-widget__item xicms-widget__item--event\">\n <time className=\"xicms-widget__meta\" dateTime={e.start}>{formatEventDate(e.start)}</time>\n <span className=\"xicms-widget__link\">\n {e.url\n ? <a href={e.url} data-xicms-edit={`events.${e._i}.title`}>{e.title}</a>\n : <span data-xicms-edit={`events.${e._i}.title`}>{e.title}</span>}\n </span>\n {e.location && <span className=\"xicms-widget__sub\" data-xicms-edit={`events.${e._i}.location`}>{e.location}</span>}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n/* ── Openingstijden (vandaag gemarkeerd) ───────────────────────────────────── */\ntype OpeningW = z.infer<typeof OpeningHoursWidgetSchema>;\nexport function OpeningHoursWidgetRender({ block }: BlockRenderProps<OpeningW>) {\n // \"Vandaag\" client-side bepalen (voorkomt hydration-mismatch t.o.v. de servertijd).\n const [todayIdx, setTodayIdx] = useState<number | null>(null);\n useEffect(() => setTodayIdx(new Date().getDay()), []);\n const rows = block.rows ?? [];\n return (\n <div className=\"xicms-widget xicms-widget--hours\">\n <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading ?? \"Openingstijden\"}</h3>\n <table className=\"xicms-widget__hours\">\n <tbody>\n {rows.map((r, i) => {\n // parseDays begrijpt ook bereiken (\"Ma-vr\", \"Maandag t/m vrijdag\") - een woensdag\n // hoort dan net zo goed gemarkeerd als de letterlijk genoemde dagen.\n const isToday =\n block.highlightToday && todayIdx !== null && parseDays(r.day).includes(todayIdx);\n return (\n <tr key={i} className={isToday ? \"xicms-widget__hours-today\" : undefined}>\n <th scope=\"row\" data-xicms-edit={`rows.${i}.day`}>{r.day}</th>\n <td data-xicms-edit={`rows.${i}.hours`}>{r.hours}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n/* ── Contact (compact) ─────────────────────────────────────────────────────── */\ntype ContactW = z.infer<typeof ContactWidgetSchema>;\nexport function ContactWidgetRender({ block }: BlockRenderProps<ContactW>) {\n if (!block.address && !block.phone && !block.email) return null;\n return (\n <div className=\"xicms-widget xicms-widget--contact\">\n <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading ?? \"Contact\"}</h3>\n <ul className=\"xicms-widget__list\">\n {block.address && <li className=\"xicms-widget__item\" data-xicms-edit=\"address\">{block.address}</li>}\n {block.phone && (\n <li className=\"xicms-widget__item\"><a href={`tel:${block.phone.replace(/\\s+/g, \"\")}`} data-xicms-edit=\"phone\">{block.phone}</a></li>\n )}\n {block.email && (\n <li className=\"xicms-widget__item\"><a href={`mailto:${block.email}`} data-xicms-edit=\"email\">{block.email}</a></li>\n )}\n </ul>\n </div>\n );\n}\n/* ── Social (compact) ──────────────────────────────────────────────────────── */\ntype SocialW = z.infer<typeof SocialWidgetSchema>;\nexport function SocialWidgetRender({ block }: BlockRenderProps<SocialW>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op href && platform.\n const items = (block.items ?? []).map((it, i) => ({ ...it, _i: i })).filter((i) => i.href && i.platform);\n if (items.length === 0) return null;\n return (\n <div className=\"xicms-widget xicms-widget--social\">\n {block.heading && <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading}</h3>}\n <ul className=\"xicms-widget__social\">\n {items.map((it) => (\n <li key={it._i}><a href={it.href} rel=\"noopener noreferrer\" target=\"_blank\" data-xicms-edit={`items.${it._i}.platform`}>{it.platform}</a></li>\n ))}\n </ul>\n </div>\n );\n}\n/* ── Nieuwsbrief (compact) ─────────────────────────────────────────────────── */\ntype NewsletterW = z.infer<typeof NewsletterWidgetSchema>;\nexport function NewsletterWidgetRender({ block }: BlockRenderProps<NewsletterW>) {\n const [email, setEmail] = useState(\"\");\n return (\n <div className=\"xicms-widget xicms-widget--newsletter\">\n <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading ?? \"Nieuwsbrief\"}</h3>\n {block.text && <p className=\"xicms-widget__sub\" data-xicms-edit=\"text\">{block.text}</p>}\n {block.actionUrl ? (\n <form className=\"xicms-widget__form\" action={block.actionUrl} method=\"post\" target=\"_blank\" style={{ display: \"grid\", gap: \"0.6rem\" }}>\n <TextInput label=\"E-mailadres\" isLabelHidden type=\"email\" htmlName=\"EMAIL\" isRequired placeholder=\"je@email.nl\" value={email} onChange={setEmail} />\n <Button label={block.buttonLabel} variant=\"primary\" type=\"submit\">\n <span data-xicms-edit=\"buttonLabel\">{block.buttonLabel}</span>\n </Button>\n </form>\n ) : (\n <Onvoltooid>Deze aanmeldwidget mist nog een aanmeld-URL (tandwiel). Bezoekers zien dit blok niet.</Onvoltooid>\n )}\n </div>\n );\n}\n/* ── CTA (compact) ─────────────────────────────────────────────────────────── */\ntype CtaW = z.infer<typeof CtaWidgetSchema>;\nexport function CtaWidgetRender({ block }: BlockRenderProps<CtaW>) {\n if (!block.title && !block.text) return null;\n return (\n <div className=\"xicms-widget xicms-widget--cta\" style={block.backgroundColor ? { background: block.backgroundColor } : undefined}>\n {block.title && <h3 className=\"xicms-widget__title\" data-xicms-edit=\"title\">{block.title}</h3>}\n {block.text && <p className=\"xicms-widget__sub\" data-xicms-edit=\"text\">{block.text}</p>}\n {block.buttonLabel && (\n <Btn label={block.buttonLabel} href={block.buttonHref} variant=\"primary\" size=\"md\" editField=\"buttonLabel\" />\n )}\n </div>\n );\n}\n/* ── Galerij (compacte strip) ──────────────────────────────────────────────── */\ntype GalleryW = z.infer<typeof GalleryWidgetSchema>;\nexport function GalleryWidgetRender({ block }: BlockRenderProps<GalleryW>) {\n const items = (block.items ?? []).filter((i) => i.url);\n if (items.length === 0) return null;\n return (\n <div className=\"xicms-widget xicms-widget--gallery\">\n {block.heading && <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading}</h3>}\n <div className=\"xicms-widget__grid\" data-cols={block.columns}>\n {items.map((it, i) => (\n // eslint-disable-next-line @next/next/no-img-element\n <img key={i} className=\"xicms-widget__photo\" src={it.url} alt={it.alt ?? \"\"} loading=\"lazy\" />\n ))}\n </div>\n </div>\n );\n}\n/* ── Review (compact) ──────────────────────────────────────────────────────── */\ntype TestimonialsW = z.infer<typeof TestimonialsWidgetSchema>;\nexport function TestimonialsWidgetRender({ block }: BlockRenderProps<TestimonialsW>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd op quote.\n const items = (block.items ?? []).map((t, i) => ({ ...t, _i: i })).filter((t) => t.quote);\n if (items.length === 0) return null;\n return (\n <div className=\"xicms-widget xicms-widget--testimonial\">\n {block.heading && <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading}</h3>}\n <ul className=\"xicms-widget__list\">\n {items.map((t) => (\n <li key={t._i} className=\"xicms-widget__quote\">\n {typeof t.rating === \"number\" && (\n <span className=\"xicms-widget__stars\" aria-label={`${t.rating} van 5`}>{\"★\".repeat(t.rating)}{\"☆\".repeat(5 - t.rating)}</span>\n )}\n <p>“<span data-xicms-edit={`items.${t._i}.quote`}>{t.quote}</span>”</p>\n {t.author && <span className=\"xicms-widget__meta\">— <span data-xicms-edit={`items.${t._i}.author`}>{t.author}</span></span>}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n/* ── Diensten (compact) ────────────────────────────────────────────────────── */\ntype CardsW = z.infer<typeof CardsWidgetSchema>;\nexport function CardsWidgetRender({ block }: BlockRenderProps<CardsW>) {\n const items = (block.items ?? []).filter((c) => c.title);\n if (items.length === 0) return null;\n return (\n <div className=\"xicms-widget xicms-widget--cards\">\n {block.heading && <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading}</h3>}\n <ul className=\"xicms-widget__list\">\n {items.map((c, i) => {\n const inner = (\n <>\n {c.icon && <span className=\"xicms-widget__cardicon\" aria-hidden=\"true\">{c.icon}</span>}\n <span>\n <span className=\"xicms-widget__link\">{c.title}</span>\n {c.text && <span className=\"xicms-widget__sub\">{c.text}</span>}\n </span>\n </>\n );\n return (\n <li key={i} className=\"xicms-widget__item xicms-widget__item--card\">\n {c.href ? <a href={c.href} className=\"xicms-widget__cardlink\">{inner}</a> : inner}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n/* ── Downloads (compact) ───────────────────────────────────────────────────── */\ntype DownloadsW = z.infer<typeof DownloadsWidgetSchema>;\nexport function DownloadsWidgetRender({ block }: BlockRenderProps<DownloadsW>) {\n // _i = index in de OORSPRONKELIJKE array — de lijst wordt gefilterd én afgekapt op `limit`.\n const items = (block.items ?? []).map((d, i) => ({ ...d, _i: i })).filter((d) => d.label && d.href).slice(0, block.limit);\n if (items.length === 0) return null;\n return (\n <div className=\"xicms-widget xicms-widget--downloads\">\n <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading ?? \"Downloads\"}</h3>\n <ul className=\"xicms-widget__list\">\n {items.map((d) => (\n <li key={d._i} className=\"xicms-widget__item\">\n <a className=\"xicms-widget__link\" href={d.href} download>\n <span aria-hidden=\"true\">📄</span> <span data-xicms-edit={`items.${d._i}.label`}>{d.label}</span>\n </a>\n {d.meta && <span className=\"xicms-widget__meta\" data-xicms-edit={`items.${d._i}.meta`}>{d.meta}</span>}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n/* ── Aftelteller (compact) ─────────────────────────────────────────────────── */\ntype CountdownW = z.infer<typeof CountdownWidgetSchema>;\nexport function CountdownWidgetRender({ block }: BlockRenderProps<CountdownW>) {\n const r = useCountdown(block.target);\n if (!block.target) return null;\n return (\n <div className=\"xicms-widget xicms-widget--countdown\">\n {block.heading && <h3 className=\"xicms-widget__title\" data-xicms-edit=\"heading\">{block.heading}</h3>}\n {r === null ? (\n <p className=\"xicms-widget__empty\" aria-hidden=\"true\">…</p>\n ) : r.done ? (\n <p className=\"xicms-countdown__expired\" data-xicms-edit=\"expiredText\">{block.expiredText}</p>\n ) : (\n <CountdownDigits r={r} showLabels />\n )}\n </div>\n );\n}\n// Let op: exporteer GEEN aggregaat-array vanuit dit \"use client\"-bestand. Een array die hier\n// wordt opgebouwd is niet server-importeerbaar (de individuele block-exports wél). De volledige\n// lijst staat in blocks/builtins/index.ts.\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { useEffect } from "react";
3
- function useBalkOffset(actief, hoogte = 40) {
3
+ function useBalkOffset(actief, hoogte = 56) {
4
4
  useEffect(() => {
5
5
  if (!actief || typeof document === "undefined") return;
6
6
  const aangepast = [];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/barOffset.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\n/**\n * Schuif de plakkende elementen van de SITE onder onze balk vandaan.\n *\n * ── WAAROM PADDING NIET GENOEG IS ─────────────────────────────────────────────────────────\n *\n * De beheerbalk staat `fixed` bovenaan en de pagina wordt omlaag geduwd met padding op `:root`.\n * Dat werkt voor alles wat gewoon meestroomt — maar niet voor een header met\n * `position: sticky; top: 0`. Die plakt aan de bovenkant van het VENSTER, niet aan de bovenkant\n * van de pagina, en schuift dus vrolijk onder onze balk door. Op groenhof was het logo en het hele\n * menu daardoor half afgedekt zodra je inlogde.\n *\n * Het is geen randgeval: een plakkende header is zo'n beetje de standaard, en elke klantsite kiest\n * daar zijn eigen klassenaam en eigen CSS voor. Wij kunnen die CSS niet kennen en willen hem al\n * helemaal niet overschrijven met een gok van een selector.\n *\n * Dus meten we het gewoon: welke elementen plakken of hangen vast aan de bovenrand, en zet daar\n * zolang de balk er staat een `top` van de balkhoogte op. Bij het opruimen gaat de oorspronkelijke\n * waarde terug — de site blijft van de klant.\n *\n * Bewust GEEN elementen aanraken die al verder naar beneden hangen (een plakkende zijbalk, een\n * knop rechtsonder): die zitten onze balk niet in de weg en verplaatsen zou ze juist verschuiven.\n */\nexport function useBalkOffset(actief: boolean, hoogte = 40): void {\n useEffect(() => {\n if (!actief || typeof document === \"undefined\") return;\n\n const aangepast: { el: HTMLElement; top: string }[] = [];\n\n const verschuif = () => {\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"body *\"))) {\n // Onze eigen gereedschapslaag overslaan: die staat er met opzet bovenop.\n if (el.closest(\"[data-xicms-tooling]\")) continue;\n if (aangepast.some((a) => a.el === el)) continue;\n const cs = getComputedStyle(el);\n if (cs.position !== \"sticky\" && cs.position !== \"fixed\") continue;\n // `top: auto` betekent \"waar je toevallig staat\" — daar valt niets te corrigeren.\n const top = parseFloat(cs.top);\n if (!Number.isFinite(top) || top > 4) continue;\n aangepast.push({ el, top: el.style.top });\n el.style.top = `${top + hoogte}px`;\n }\n };\n\n verschuif();\n // Een site kan later nog iets plakkends tonen (een aankondigingsbalk die pas na consent komt,\n // een menu dat bij het scrollen van gedrag verandert). Eén meting bij het openen zou dat missen.\n //\n // Wel gebundeld per frame: de observer kijkt naar class/style op de HELE boom, en tijdens het\n // typen in een contentEditable-veld komen mutaties in salvo's. Elke mutatie een volledige\n // DOM-scan geven zou het bewerken zelf traag maken — precies het scherm waar deze hook voor\n // bestaat. En: onze eigen `el.style.top` hieronder is zélf een mutatie; zonder bundeling\n // triggert de correctie de observer opnieuw (onschuldig maar zinloos rondpompen).\n let frame = 0;\n const gepland = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(verschuif);\n };\n const obs = new MutationObserver(gepland);\n obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [\"class\", \"style\"] });\n\n return () => {\n obs.disconnect();\n cancelAnimationFrame(frame);\n for (const { el, top } of aangepast) el.style.top = top;\n };\n }, [actief, hoogte]);\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAwBnB,SAAS,cAAc,QAAiB,SAAS,IAAU;AAChE,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,aAAa,YAAa;AAEhD,UAAM,YAAgD,CAAC;AAEvD,UAAM,YAAY,MAAM;AACtB,iBAAW,MAAM,MAAM,KAAK,SAAS,iBAA8B,QAAQ,CAAC,GAAG;AAE7E,YAAI,GAAG,QAAQ,sBAAsB,EAAG;AACxC,YAAI,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAG;AACxC,cAAM,KAAK,iBAAiB,EAAE;AAC9B,YAAI,GAAG,aAAa,YAAY,GAAG,aAAa,QAAS;AAEzD,cAAM,MAAM,WAAW,GAAG,GAAG;AAC7B,YAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,EAAG;AACtC,kBAAU,KAAK,EAAE,IAAI,KAAK,GAAG,MAAM,IAAI,CAAC;AACxC,WAAG,MAAM,MAAM,GAAG,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AAEA,cAAU;AASV,QAAI,QAAQ;AACZ,UAAM,UAAU,MAAM;AACpB,2BAAqB,KAAK;AAC1B,cAAQ,sBAAsB,SAAS;AAAA,IACzC;AACA,UAAM,MAAM,IAAI,iBAAiB,OAAO;AACxC,QAAI,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,MAAM,YAAY,MAAM,iBAAiB,CAAC,SAAS,OAAO,EAAE,CAAC;AAEpH,WAAO,MAAM;AACX,UAAI,WAAW;AACf,2BAAqB,KAAK;AAC1B,iBAAW,EAAE,IAAI,IAAI,KAAK,UAAW,IAAG,MAAM,MAAM;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,CAAC;AACrB;","names":[]}
1
+ {"version":3,"sources":["../../src/components/barOffset.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect } from \"react\";\n\n/**\n * Schuif de plakkende elementen van de SITE onder onze balk vandaan.\n *\n * ── WAAROM PADDING NIET GENOEG IS ─────────────────────────────────────────────────────────\n *\n * De beheerbalk staat `fixed` bovenaan en de pagina wordt omlaag geduwd met padding op `:root`.\n * Dat werkt voor alles wat gewoon meestroomt — maar niet voor een header met\n * `position: sticky; top: 0`. Die plakt aan de bovenkant van het VENSTER, niet aan de bovenkant\n * van de pagina, en schuift dus vrolijk onder onze balk door. Op groenhof was het logo en het hele\n * menu daardoor half afgedekt zodra je inlogde.\n *\n * Het is geen randgeval: een plakkende header is zo'n beetje de standaard, en elke klantsite kiest\n * daar zijn eigen klassenaam en eigen CSS voor. Wij kunnen die CSS niet kennen en willen hem al\n * helemaal niet overschrijven met een gok van een selector.\n *\n * Dus meten we het gewoon: welke elementen plakken of hangen vast aan de bovenrand, en zet daar\n * zolang de balk er staat een `top` van de balkhoogte op. Bij het opruimen gaat de oorspronkelijke\n * waarde terug — de site blijft van de klant.\n *\n * Bewust GEEN elementen aanraken die al verder naar beneden hangen (een plakkende zijbalk, een\n * knop rechtsonder): die zitten onze balk niet in de weg en verplaatsen zou ze juist verschuiven.\n */\nexport function useBalkOffset(actief: boolean, hoogte = 56): void {\n useEffect(() => {\n if (!actief || typeof document === \"undefined\") return;\n\n const aangepast: { el: HTMLElement; top: string }[] = [];\n\n const verschuif = () => {\n for (const el of Array.from(document.querySelectorAll<HTMLElement>(\"body *\"))) {\n // Onze eigen gereedschapslaag overslaan: die staat er met opzet bovenop.\n if (el.closest(\"[data-xicms-tooling]\")) continue;\n if (aangepast.some((a) => a.el === el)) continue;\n const cs = getComputedStyle(el);\n if (cs.position !== \"sticky\" && cs.position !== \"fixed\") continue;\n // `top: auto` betekent \"waar je toevallig staat\" — daar valt niets te corrigeren.\n const top = parseFloat(cs.top);\n if (!Number.isFinite(top) || top > 4) continue;\n aangepast.push({ el, top: el.style.top });\n el.style.top = `${top + hoogte}px`;\n }\n };\n\n verschuif();\n // Een site kan later nog iets plakkends tonen (een aankondigingsbalk die pas na consent komt,\n // een menu dat bij het scrollen van gedrag verandert). Eén meting bij het openen zou dat missen.\n //\n // Wel gebundeld per frame: de observer kijkt naar class/style op de HELE boom, en tijdens het\n // typen in een contentEditable-veld komen mutaties in salvo's. Elke mutatie een volledige\n // DOM-scan geven zou het bewerken zelf traag maken — precies het scherm waar deze hook voor\n // bestaat. En: onze eigen `el.style.top` hieronder is zélf een mutatie; zonder bundeling\n // triggert de correctie de observer opnieuw (onschuldig maar zinloos rondpompen).\n let frame = 0;\n const gepland = () => {\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(verschuif);\n };\n const obs = new MutationObserver(gepland);\n obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [\"class\", \"style\"] });\n\n return () => {\n obs.disconnect();\n cancelAnimationFrame(frame);\n for (const { el, top } of aangepast) el.style.top = top;\n };\n }, [actief, hoogte]);\n}\n"],"mappings":";AAEA,SAAS,iBAAiB;AAwBnB,SAAS,cAAc,QAAiB,SAAS,IAAU;AAChE,YAAU,MAAM;AACd,QAAI,CAAC,UAAU,OAAO,aAAa,YAAa;AAEhD,UAAM,YAAgD,CAAC;AAEvD,UAAM,YAAY,MAAM;AACtB,iBAAW,MAAM,MAAM,KAAK,SAAS,iBAA8B,QAAQ,CAAC,GAAG;AAE7E,YAAI,GAAG,QAAQ,sBAAsB,EAAG;AACxC,YAAI,UAAU,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,EAAG;AACxC,cAAM,KAAK,iBAAiB,EAAE;AAC9B,YAAI,GAAG,aAAa,YAAY,GAAG,aAAa,QAAS;AAEzD,cAAM,MAAM,WAAW,GAAG,GAAG;AAC7B,YAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,EAAG;AACtC,kBAAU,KAAK,EAAE,IAAI,KAAK,GAAG,MAAM,IAAI,CAAC;AACxC,WAAG,MAAM,MAAM,GAAG,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AAEA,cAAU;AASV,QAAI,QAAQ;AACZ,UAAM,UAAU,MAAM;AACpB,2BAAqB,KAAK;AAC1B,cAAQ,sBAAsB,SAAS;AAAA,IACzC;AACA,UAAM,MAAM,IAAI,iBAAiB,OAAO;AACxC,QAAI,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,MAAM,YAAY,MAAM,iBAAiB,CAAC,SAAS,OAAO,EAAE,CAAC;AAEpH,WAAO,MAAM;AACX,UAAI,WAAW;AACf,2BAAqB,KAAK;AAC1B,iBAAW,EAAE,IAAI,IAAI,KAAK,UAAW,IAAG,MAAM,MAAM;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,QAAQ,MAAM,CAAC;AACrB;","names":[]}
@@ -18,7 +18,8 @@ function validateModuleContract(defs) {
18
18
  for (const b of d.blocks ?? []) {
19
19
  if (blockTypes.has(b.type)) problems.push(`block-type "${b.type}" wordt door meerdere modules geleverd.`);
20
20
  blockTypes.add(b.type);
21
- if (typeof b.Render !== "function") problems.push(`block "${b.type}" (module "${d.id}") mist een Render-component.`);
21
+ const isComponent = (c) => typeof c === "function" || typeof c === "object" && c !== null && "$$typeof" in c;
22
+ if (!isComponent(b.Render)) problems.push(`block "${b.type}" (module "${d.id}") mist een Render-component.`);
22
23
  if (!b.schema) problems.push(`block "${b.type}" (module "${d.id}") mist een schema.`);
23
24
  }
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/contract.ts"],"sourcesContent":["/**\n * Module-contract — controleert dat een set modules consistent is. Bruikbaar in de core-tests én\n * door een klantsite om zijn eigen modules te valideren (`assertModuleContract([mijnModule])`).\n * Geeft een lijst problemen terug (leeg = ok).\n */\nimport type { ModuleDefinition } from \"./types\";\n\nconst SEMVER = /^\\d+\\.\\d+\\.\\d+(?:[-+].*)?$/;\n// kebab- of camelCase, startend met een kleine letter (sluit aan op block-`_type`-conventie).\nconst ID = /^[a-z][a-zA-Z0-9-]*$/;\n\nexport function validateModuleContract(defs: ModuleDefinition[]): string[] {\n const problems: string[] = [];\n const ids = new Set<string>();\n const featureKeys = new Set<string>();\n const blockTypes = new Set<string>();\n\n for (const d of defs) {\n if (!ID.test(d.id)) problems.push(`module-id \"${d.id}\" is geen geldige kebab-id.`);\n if (ids.has(d.id)) problems.push(`dubbele module-id \"${d.id}\".`);\n ids.add(d.id);\n\n if (!SEMVER.test(d.version)) problems.push(`module \"${d.id}\" heeft geen geldige semver (${d.version}).`);\n if (!d.label) problems.push(`module \"${d.id}\" mist een label.`);\n\n if (d.featureKey) {\n if (featureKeys.has(d.featureKey)) problems.push(`featureKey \"${d.featureKey}\" wordt door meerdere modules gebruikt.`);\n featureKeys.add(d.featureKey);\n }\n\n for (const b of d.blocks ?? []) {\n if (blockTypes.has(b.type)) problems.push(`block-type \"${b.type}\" wordt door meerdere modules geleverd.`);\n blockTypes.add(b.type);\n if (typeof b.Render !== \"function\") problems.push(`block \"${b.type}\" (module \"${d.id}\") mist een Render-component.`);\n if (!b.schema) problems.push(`block \"${b.type}\" (module \"${d.id}\") mist een schema.`);\n }\n }\n return problems;\n}\n\n/** Gooit als het contract niet klopt (handig in een test of bij opstart). */\nexport function assertModuleContract(defs: ModuleDefinition[]): void {\n const problems = validateModuleContract(defs);\n if (problems.length) {\n throw new Error(`Module-contract geschonden:\\n- ${problems.join(\"\\n- \")}`);\n }\n}\n"],"mappings":"AAOA,MAAM,SAAS;AAEf,MAAM,KAAK;AAEJ,SAAS,uBAAuB,MAAoC;AACzE,QAAM,WAAqB,CAAC;AAC5B,QAAM,MAAM,oBAAI,IAAY;AAC5B,QAAM,cAAc,oBAAI,IAAY;AACpC,QAAM,aAAa,oBAAI,IAAY;AAEnC,aAAW,KAAK,MAAM;AACpB,QAAI,CAAC,GAAG,KAAK,EAAE,EAAE,EAAG,UAAS,KAAK,cAAc,EAAE,EAAE,6BAA6B;AACjF,QAAI,IAAI,IAAI,EAAE,EAAE,EAAG,UAAS,KAAK,sBAAsB,EAAE,EAAE,IAAI;AAC/D,QAAI,IAAI,EAAE,EAAE;AAEZ,QAAI,CAAC,OAAO,KAAK,EAAE,OAAO,EAAG,UAAS,KAAK,WAAW,EAAE,EAAE,gCAAgC,EAAE,OAAO,IAAI;AACvG,QAAI,CAAC,EAAE,MAAO,UAAS,KAAK,WAAW,EAAE,EAAE,mBAAmB;AAE9D,QAAI,EAAE,YAAY;AAChB,UAAI,YAAY,IAAI,EAAE,UAAU,EAAG,UAAS,KAAK,eAAe,EAAE,UAAU,yCAAyC;AACrH,kBAAY,IAAI,EAAE,UAAU;AAAA,IAC9B;AAEA,eAAW,KAAK,EAAE,UAAU,CAAC,GAAG;AAC9B,UAAI,WAAW,IAAI,EAAE,IAAI,EAAG,UAAS,KAAK,eAAe,EAAE,IAAI,yCAAyC;AACxG,iBAAW,IAAI,EAAE,IAAI;AACrB,UAAI,OAAO,EAAE,WAAW,WAAY,UAAS,KAAK,UAAU,EAAE,IAAI,cAAc,EAAE,EAAE,+BAA+B;AACnH,UAAI,CAAC,EAAE,OAAQ,UAAS,KAAK,UAAU,EAAE,IAAI,cAAc,EAAE,EAAE,qBAAqB;AAAA,IACtF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,MAAgC;AACnE,QAAM,WAAW,uBAAuB,IAAI;AAC5C,MAAI,SAAS,QAAQ;AACnB,UAAM,IAAI,MAAM;AAAA,IAAkC,SAAS,KAAK,MAAM,CAAC,EAAE;AAAA,EAC3E;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/contract.ts"],"sourcesContent":["/**\n * Module-contract — controleert dat een set modules consistent is. Bruikbaar in de core-tests én\n * door een klantsite om zijn eigen modules te valideren (`assertModuleContract([mijnModule])`).\n * Geeft een lijst problemen terug (leeg = ok).\n */\nimport type { ModuleDefinition } from \"./types\";\n\nconst SEMVER = /^\\d+\\.\\d+\\.\\d+(?:[-+].*)?$/;\n// kebab- of camelCase, startend met een kleine letter (sluit aan op block-`_type`-conventie).\nconst ID = /^[a-z][a-zA-Z0-9-]*$/;\n\nexport function validateModuleContract(defs: ModuleDefinition[]): string[] {\n const problems: string[] = [];\n const ids = new Set<string>();\n const featureKeys = new Set<string>();\n const blockTypes = new Set<string>();\n\n for (const d of defs) {\n if (!ID.test(d.id)) problems.push(`module-id \"${d.id}\" is geen geldige kebab-id.`);\n if (ids.has(d.id)) problems.push(`dubbele module-id \"${d.id}\".`);\n ids.add(d.id);\n\n if (!SEMVER.test(d.version)) problems.push(`module \"${d.id}\" heeft geen geldige semver (${d.version}).`);\n if (!d.label) problems.push(`module \"${d.id}\" mist een label.`);\n\n if (d.featureKey) {\n if (featureKeys.has(d.featureKey)) problems.push(`featureKey \"${d.featureKey}\" wordt door meerdere modules gebruikt.`);\n featureKeys.add(d.featureKey);\n }\n\n for (const b of d.blocks ?? []) {\n if (blockTypes.has(b.type)) problems.push(`block-type \"${b.type}\" wordt door meerdere modules geleverd.`);\n blockTypes.add(b.type);\n // Een component is een functie óf een React-exotic (lazy/memo/forwardRef, met $$typeof) —\n // sinds de per-blok-codesplitsing zijn de builtin-Renders next/dynamic-componenten.\n const isComponent = (c: unknown): boolean =>\n typeof c === \"function\" || (typeof c === \"object\" && c !== null && \"$$typeof\" in c);\n if (!isComponent(b.Render)) problems.push(`block \"${b.type}\" (module \"${d.id}\") mist een Render-component.`);\n if (!b.schema) problems.push(`block \"${b.type}\" (module \"${d.id}\") mist een schema.`);\n }\n }\n return problems;\n}\n\n/** Gooit als het contract niet klopt (handig in een test of bij opstart). */\nexport function assertModuleContract(defs: ModuleDefinition[]): void {\n const problems = validateModuleContract(defs);\n if (problems.length) {\n throw new Error(`Module-contract geschonden:\\n- ${problems.join(\"\\n- \")}`);\n }\n}\n"],"mappings":"AAOA,MAAM,SAAS;AAEf,MAAM,KAAK;AAEJ,SAAS,uBAAuB,MAAoC;AACzE,QAAM,WAAqB,CAAC;AAC5B,QAAM,MAAM,oBAAI,IAAY;AAC5B,QAAM,cAAc,oBAAI,IAAY;AACpC,QAAM,aAAa,oBAAI,IAAY;AAEnC,aAAW,KAAK,MAAM;AACpB,QAAI,CAAC,GAAG,KAAK,EAAE,EAAE,EAAG,UAAS,KAAK,cAAc,EAAE,EAAE,6BAA6B;AACjF,QAAI,IAAI,IAAI,EAAE,EAAE,EAAG,UAAS,KAAK,sBAAsB,EAAE,EAAE,IAAI;AAC/D,QAAI,IAAI,EAAE,EAAE;AAEZ,QAAI,CAAC,OAAO,KAAK,EAAE,OAAO,EAAG,UAAS,KAAK,WAAW,EAAE,EAAE,gCAAgC,EAAE,OAAO,IAAI;AACvG,QAAI,CAAC,EAAE,MAAO,UAAS,KAAK,WAAW,EAAE,EAAE,mBAAmB;AAE9D,QAAI,EAAE,YAAY;AAChB,UAAI,YAAY,IAAI,EAAE,UAAU,EAAG,UAAS,KAAK,eAAe,EAAE,UAAU,yCAAyC;AACrH,kBAAY,IAAI,EAAE,UAAU;AAAA,IAC9B;AAEA,eAAW,KAAK,EAAE,UAAU,CAAC,GAAG;AAC9B,UAAI,WAAW,IAAI,EAAE,IAAI,EAAG,UAAS,KAAK,eAAe,EAAE,IAAI,yCAAyC;AACxG,iBAAW,IAAI,EAAE,IAAI;AAGrB,YAAM,cAAc,CAAC,MACnB,OAAO,MAAM,cAAe,OAAO,MAAM,YAAY,MAAM,QAAQ,cAAc;AACnF,UAAI,CAAC,YAAY,EAAE,MAAM,EAAG,UAAS,KAAK,UAAU,EAAE,IAAI,cAAc,EAAE,EAAE,+BAA+B;AAC3G,UAAI,CAAC,EAAE,OAAQ,UAAS,KAAK,UAAU,EAAE,IAAI,cAAc,EAAE,EAAE,qBAAqB;AAAA,IACtF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,qBAAqB,MAAgC;AACnE,QAAM,WAAW,uBAAuB,IAAI;AAC5C,MAAI,SAAS,QAAQ;AACnB,UAAM,IAAI,MAAM;AAAA,IAAkC,SAAS,KAAK,MAAM,CAAC,EAAE;AAAA,EAC3E;AACF;","names":[]}
@@ -236,6 +236,10 @@ export declare const SiteSettingsSchema: z.ZodMiniObject<{
236
236
  enabled: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
237
237
  config: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniUnknown>>;
238
238
  }, z.core.$strip>]>>>;
239
+ dashboardLinks: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
240
+ label: z.ZodMiniString<string>;
241
+ href: z.ZodMiniString<string>;
242
+ }, z.core.$strip>>>;
239
243
  }, z.core.$strip>;
240
244
  export type SiteSettings = z.infer<typeof SiteSettingsSchema>;
241
245
  /**
@@ -121,7 +121,16 @@ const SiteSettingsSchema = z.object({
121
121
  seoDefaults: z.optional(SeoSchema),
122
122
  forms: z.optional(FormsSenderSchema),
123
123
  footer: z.optional(FooterSettingsSchema),
124
- modules: z.optional(ModulesSettingsSchema)
124
+ modules: z.optional(ModulesSettingsSchema),
125
+ /**
126
+ * Snelkoppelingen op de beheer-startpagina (7.16). Beheer-intern: bewust NIET in de publieke
127
+ * subset. Opslaan loopt via de bestaande saveSiteSettings-callable — die is passthrough met
128
+ * merge, dus dit veld vergt geen functions-wijziging.
129
+ */
130
+ dashboardLinks: z.optional(z.array(z.object({
131
+ label: z.string().check(z.minLength(1)).check(z.maxLength(80)),
132
+ href: z.string().check(z.minLength(1)).check(z.maxLength(2e3))
133
+ })).check(z.maxLength(12)))
125
134
  });
126
135
  const PublicSiteSettingsSchema = z.object({
127
136
  name: z.optional(z.string().check(z.maxLength(160))),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/schema/settings.ts"],"sourcesContent":["import * as z from \"zod/mini\";\nimport { SeoSchema } from \"./seo\";\n\n/**\n * Eén navigatie-item.\n *\n * `target` is OPTIONEEL en dat is opzet: elk bestaand menu in Git en in Firestore mist dit veld, en\n * moet blijven valideren. Ontbreekt het, dan opent de link in hetzelfde venster — precies wat een\n * gewone `<a href>` altijd al deed.\n *\n * Er staat bewust geen `type: \"page\" | \"extern\"` in. Dat onderscheid is uit de href zelf af te\n * lezen (begint hij met `/` of met `http`), en een apart veld zou een tweede waarheid introduceren\n * die uit de pas kan lopen met de href. Het beheerscherm mag de gebruiker wél die keuze voorleggen;\n * wat er wordt opgeslagen is één ondubbelzinnig adres.\n */\nexport const NavItemSchema = z.object({\n label: z.string().check(z.minLength(1)).check(z.maxLength(80)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n /**\n * `_embed` toont een externe pagina in een kader BINNEN de site, onder de logo/menubalk, alsof\n * hij erbij hoort. Alleen zinvol bij een externe href.\n */\n target: z.optional(z.enum([\"_self\", \"_blank\", \"_embed\"])),\n});\nexport type NavItem = z.infer<typeof NavItemSchema>;\n\n/** Verwijst dit item naar buiten? Afgeleid uit de href, zodat er geen tweede waarheid ontstaat. */\nexport function isExterneLink(href: string): boolean {\n return /^https?:\\/\\//i.test(href);\n}\n\n/** Het pad waarop een ingekaderde externe pagina wordt getoond. */\nexport const EMBED_PAD = \"/extern\";\n\n/**\n * De href waar dit menu-item feitelijk heen linkt.\n *\n * Bij `_embed` is dat niet de externe URL zelf maar de eigen `/extern`-route, die hem in een kader\n * binnen de site toont. De externe URL gaat als parameter mee; de route controleert vervolgens of\n * dat adres écht in het menu staat vóór hij het inlaadt. Zonder die controle zou iedereen een\n * willekeurige pagina in jouw huisstijl kunnen laten verschijnen door de URL te bewerken.\n */\nexport function navHref(item: NavItem): string {\n if (item.target === \"_embed\" && isExterneLink(item.href)) {\n return `${EMBED_PAD}?url=${encodeURIComponent(item.href)}`;\n }\n return item.href;\n}\n\n/** Staat dit adres als ingekaderd menu-item in de navigatie? Zo niet, dan niet tonen. */\nexport function isToegestaneEmbed(url: string, nav: NavItem[] | undefined): boolean {\n return (nav ?? []).some((i) => i.target === \"_embed\" && i.href === url);\n}\n\n/** Huisstijl-tokens (worden CSS custom properties). Alle uitbreidingen zijn optioneel/update-veilig. */\nexport const ThemeSchema = z.object({\n primary: z.string().check(z.maxLength(40)),\n accent: z.optional(z.string().check(z.maxLength(40))),\n /** Logo-URL (absoluut, https) — gebruikt in de mail-header en op de factuur-PDF. */\n logo: z.optional(z.string().check(z.maxLength(500))),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n fontFamily: z.optional(z.string().check(z.maxLength(200))),\n /** Optioneel apart lettertype voor koppen (display/serif), voor een elegantere typografie. */\n headingFont: z.optional(z.string().check(z.maxLength(200))),\n /** Optionele stylesheet-URL voor het lettertype (bv. een Google Fonts CSS-link). */\n fontUrl: z.optional(z.string().check(z.maxLength(500))),\n /** Afrondingsschaal (sm/md/lg). */\n radius: z.optional(z.object({ sm: z.optional(z.string().check(z.maxLength(20))), md: z.optional(z.string().check(z.maxLength(20))), lg: z.optional(z.string().check(z.maxLength(20))) })),\n /** Ruimteschaal voor block-padding (xs..xl). */\n spacing: z.optional(z.object({\n xs: z.optional(z.string().check(z.maxLength(20))),\n sm: z.optional(z.string().check(z.maxLength(20))),\n md: z.optional(z.string().check(z.maxLength(20))),\n lg: z.optional(z.string().check(z.maxLength(20))),\n xl: z.optional(z.string().check(z.maxLength(20))),\n })),\n /** Maximale container-breedte + horizontale padding. */\n container: z.optional(z.object({ maxWidth: z.optional(z.string().check(z.maxLength(20))), padding: z.optional(z.string().check(z.maxLength(20))) })),\n /** Donkere modus: uit, altijd aan, of meebewegen met het systeem van de bezoeker. */\n dark: z.optional(z.object({\n /**\n * \"auto\" — volgt het besturingssysteem van de bezoeker (light-dark()).\n * \"dark\" — de site is altijd donker, ongeacht de bezoeker.\n * \"light\" — de site is altijd licht (gelijk aan geen donkere modus).\n *\n * Vervangt de oude `auto`-boolean, die alleen \"volg het systeem\" of \"niets\" kon zeggen —\n * \"deze site is gewoon donker\" was daarmee niet uit te drukken. `auto` blijft gelezen worden\n * zodat bestaande site-configs blijven werken (zie themeMode in XiCMSProvider).\n */\n mode: z.optional(z.enum([\"light\", \"dark\", \"auto\"])),\n /**\n * @deprecated Gebruik `mode`. Blijft werken: true → \"auto\", afwezig/false → \"light\".\n * OPTIONEEL (niet `_default(false)`): met een verplichte default draagt elk gemerged\n * dark-object een `auto`, en dan botst een partiële update uit /beheer op het type.\n */\n auto: z.optional(z.boolean()),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n primary: z.optional(z.string().check(z.maxLength(40))),\n })),\n});\nexport type ThemeTokens = z.infer<typeof ThemeSchema>;\n\n/**\n * Voegt live thema-instellingen (uit `settings/public.theme`, /beheer) per token samen met het\n * statische SiteConfig-thema. Firestore wint per veld waar gezet; geneste schalen (radius/spacing/\n * container/dark) mergen per subveld, zodat een klant die alleen kleuren wijzigt de fonts/radius\n * uit de repo behoudt. Puur (geen React) — gedeeld door provider en `useMergedSiteConfig`.\n */\nexport function mergeThemeTokens(base: ThemeTokens, override?: ThemeTokens): ThemeTokens {\n if (!override) return base;\n const merged: ThemeTokens = { ...base, ...override };\n for (const key of [\"radius\", \"spacing\", \"container\", \"dark\"] as const) {\n if (base[key] && override[key]) {\n merged[key] = { ...base[key], ...override[key] } as never;\n } else if (base[key] && !override[key]) {\n merged[key] = base[key] as never;\n }\n }\n return merged;\n}\n\n/**\n * Afzender-instelling voor formulier-e-mail. Een provider mag alleen versturen vanaf een\n * geverifieerd domein, dus dit is per site instelbaar:\n * - mode \"xicms\": from = verifiedSender (XiCMS-domein), replyTo = inzender/klant. Werkt direct.\n * - mode \"client\": from = het geverifieerde eigen domein van de klant.\n */\nexport const FormsSenderSchema = z.object({\n mode: z._default(z.enum([\"xicms\", \"client\"]), \"xicms\"),\n /** Geverifieerd from-adres (verplicht ongeacht mode; bij \"client\" het klant-domein). */\n fromEmail: z.optional(z.email()),\n fromName: z.optional(z.string().check(z.maxLength(120))),\n /** Waar notificaties heen gaan (meestal het klant-mailadres). */\n notifyTo: z.optional(z.email()),\n});\nexport type FormsSender = z.infer<typeof FormsSenderSchema>;\n\n/** Eén social-link (platform + url). */\nexport const SocialLinkSchema = z.object({\n platform: z.string().check(z.minLength(1)).check(z.maxLength(40)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n});\nexport type SocialLink = z.infer<typeof SocialLinkSchema>;\n\n/** Eén footer-kolom: kop + links. */\nexport const FooterColumnSchema = z.object({\n heading: z.optional(z.string().check(z.maxLength(120))),\n links: z._default(z.array(NavItemSchema).check(z.maxLength(20)), []),\n});\n\n/** Bewerkbare footer (klant past dit aan in /beheer; core levert de unstyled `SiteFooter`). */\nexport const FooterSettingsSchema = z.object({\n columns: z.optional(z.array(FooterColumnSchema).check(z.maxLength(6))),\n social: z.optional(z.array(SocialLinkSchema).check(z.maxLength(12))),\n copyright: z.optional(z.string().check(z.maxLength(300))),\n /** Toon de \"Beheer\"-link in de footer (default: aan). */\n showAdminLink: z.optional(z.boolean()),\n});\nexport type FooterSettings = z.infer<typeof FooterSettingsSchema>;\n\n/** Per-module activatie/instellingen (spiegelt ModulesSiteConfig; permissief gehouden). */\nexport const ModulesSettingsSchema = z.record(\n z.string(),\n z.union([\n z.boolean(),\n z.object({ enabled: z.optional(z.boolean()), config: z.optional(z.record(z.string(), z.unknown())) }),\n ]),\n);\n\n/** Het `sites/{siteId}/settings/site`-document: alle bewerkbare site-instellingen. */\nexport const SiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.minLength(1)).check(z.maxLength(160))),\n // Zelfde hostname-regel als SiteConfigSchema.domain (geen protocol/pad).\n domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\"))), z.literal(\"\")]),\n contactEmail: z.optional(z.email()),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n forms: z.optional(FormsSenderSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type SiteSettings = z.infer<typeof SiteSettingsSchema>;\n\n/**\n * Publiek-veilige presentatie-instellingen (`sites/{siteId}/settings/public`): wordt door de live\n * site gelezen (footer, thema, nav, modules) zónder de gevoelige velden (e-mailafzender, contact)\n * uit `settings/site` bloot te geven.\n */\nexport const PublicSiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.maxLength(160))),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type PublicSiteSettings = z.infer<typeof PublicSiteSettingsSchema>;\n\n/** Haalt de publiek-veilige subset uit de volledige settings (voor het `settings/public`-doc). */\nexport function toPublicSettings(s: SiteSettings): PublicSiteSettings {\n return {\n name: s.name,\n nav: s.nav,\n theme: s.theme,\n seoDefaults: s.seoDefaults,\n footer: s.footer,\n modules: s.modules,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/schema/settings.ts"],"sourcesContent":["import * as z from \"zod/mini\";\nimport { SeoSchema } from \"./seo\";\n\n/**\n * Eén navigatie-item.\n *\n * `target` is OPTIONEEL en dat is opzet: elk bestaand menu in Git en in Firestore mist dit veld, en\n * moet blijven valideren. Ontbreekt het, dan opent de link in hetzelfde venster — precies wat een\n * gewone `<a href>` altijd al deed.\n *\n * Er staat bewust geen `type: \"page\" | \"extern\"` in. Dat onderscheid is uit de href zelf af te\n * lezen (begint hij met `/` of met `http`), en een apart veld zou een tweede waarheid introduceren\n * die uit de pas kan lopen met de href. Het beheerscherm mag de gebruiker wél die keuze voorleggen;\n * wat er wordt opgeslagen is één ondubbelzinnig adres.\n */\nexport const NavItemSchema = z.object({\n label: z.string().check(z.minLength(1)).check(z.maxLength(80)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n /**\n * `_embed` toont een externe pagina in een kader BINNEN de site, onder de logo/menubalk, alsof\n * hij erbij hoort. Alleen zinvol bij een externe href.\n */\n target: z.optional(z.enum([\"_self\", \"_blank\", \"_embed\"])),\n});\nexport type NavItem = z.infer<typeof NavItemSchema>;\n\n/** Verwijst dit item naar buiten? Afgeleid uit de href, zodat er geen tweede waarheid ontstaat. */\nexport function isExterneLink(href: string): boolean {\n return /^https?:\\/\\//i.test(href);\n}\n\n/** Het pad waarop een ingekaderde externe pagina wordt getoond. */\nexport const EMBED_PAD = \"/extern\";\n\n/**\n * De href waar dit menu-item feitelijk heen linkt.\n *\n * Bij `_embed` is dat niet de externe URL zelf maar de eigen `/extern`-route, die hem in een kader\n * binnen de site toont. De externe URL gaat als parameter mee; de route controleert vervolgens of\n * dat adres écht in het menu staat vóór hij het inlaadt. Zonder die controle zou iedereen een\n * willekeurige pagina in jouw huisstijl kunnen laten verschijnen door de URL te bewerken.\n */\nexport function navHref(item: NavItem): string {\n if (item.target === \"_embed\" && isExterneLink(item.href)) {\n return `${EMBED_PAD}?url=${encodeURIComponent(item.href)}`;\n }\n return item.href;\n}\n\n/** Staat dit adres als ingekaderd menu-item in de navigatie? Zo niet, dan niet tonen. */\nexport function isToegestaneEmbed(url: string, nav: NavItem[] | undefined): boolean {\n return (nav ?? []).some((i) => i.target === \"_embed\" && i.href === url);\n}\n\n/** Huisstijl-tokens (worden CSS custom properties). Alle uitbreidingen zijn optioneel/update-veilig. */\nexport const ThemeSchema = z.object({\n primary: z.string().check(z.maxLength(40)),\n accent: z.optional(z.string().check(z.maxLength(40))),\n /** Logo-URL (absoluut, https) — gebruikt in de mail-header en op de factuur-PDF. */\n logo: z.optional(z.string().check(z.maxLength(500))),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n fontFamily: z.optional(z.string().check(z.maxLength(200))),\n /** Optioneel apart lettertype voor koppen (display/serif), voor een elegantere typografie. */\n headingFont: z.optional(z.string().check(z.maxLength(200))),\n /** Optionele stylesheet-URL voor het lettertype (bv. een Google Fonts CSS-link). */\n fontUrl: z.optional(z.string().check(z.maxLength(500))),\n /** Afrondingsschaal (sm/md/lg). */\n radius: z.optional(z.object({ sm: z.optional(z.string().check(z.maxLength(20))), md: z.optional(z.string().check(z.maxLength(20))), lg: z.optional(z.string().check(z.maxLength(20))) })),\n /** Ruimteschaal voor block-padding (xs..xl). */\n spacing: z.optional(z.object({\n xs: z.optional(z.string().check(z.maxLength(20))),\n sm: z.optional(z.string().check(z.maxLength(20))),\n md: z.optional(z.string().check(z.maxLength(20))),\n lg: z.optional(z.string().check(z.maxLength(20))),\n xl: z.optional(z.string().check(z.maxLength(20))),\n })),\n /** Maximale container-breedte + horizontale padding. */\n container: z.optional(z.object({ maxWidth: z.optional(z.string().check(z.maxLength(20))), padding: z.optional(z.string().check(z.maxLength(20))) })),\n /** Donkere modus: uit, altijd aan, of meebewegen met het systeem van de bezoeker. */\n dark: z.optional(z.object({\n /**\n * \"auto\" — volgt het besturingssysteem van de bezoeker (light-dark()).\n * \"dark\" — de site is altijd donker, ongeacht de bezoeker.\n * \"light\" — de site is altijd licht (gelijk aan geen donkere modus).\n *\n * Vervangt de oude `auto`-boolean, die alleen \"volg het systeem\" of \"niets\" kon zeggen —\n * \"deze site is gewoon donker\" was daarmee niet uit te drukken. `auto` blijft gelezen worden\n * zodat bestaande site-configs blijven werken (zie themeMode in XiCMSProvider).\n */\n mode: z.optional(z.enum([\"light\", \"dark\", \"auto\"])),\n /**\n * @deprecated Gebruik `mode`. Blijft werken: true → \"auto\", afwezig/false → \"light\".\n * OPTIONEEL (niet `_default(false)`): met een verplichte default draagt elk gemerged\n * dark-object een `auto`, en dan botst een partiële update uit /beheer op het type.\n */\n auto: z.optional(z.boolean()),\n background: z.optional(z.string().check(z.maxLength(40))),\n foreground: z.optional(z.string().check(z.maxLength(40))),\n primary: z.optional(z.string().check(z.maxLength(40))),\n })),\n});\nexport type ThemeTokens = z.infer<typeof ThemeSchema>;\n\n/**\n * Voegt live thema-instellingen (uit `settings/public.theme`, /beheer) per token samen met het\n * statische SiteConfig-thema. Firestore wint per veld waar gezet; geneste schalen (radius/spacing/\n * container/dark) mergen per subveld, zodat een klant die alleen kleuren wijzigt de fonts/radius\n * uit de repo behoudt. Puur (geen React) — gedeeld door provider en `useMergedSiteConfig`.\n */\nexport function mergeThemeTokens(base: ThemeTokens, override?: ThemeTokens): ThemeTokens {\n if (!override) return base;\n const merged: ThemeTokens = { ...base, ...override };\n for (const key of [\"radius\", \"spacing\", \"container\", \"dark\"] as const) {\n if (base[key] && override[key]) {\n merged[key] = { ...base[key], ...override[key] } as never;\n } else if (base[key] && !override[key]) {\n merged[key] = base[key] as never;\n }\n }\n return merged;\n}\n\n/**\n * Afzender-instelling voor formulier-e-mail. Een provider mag alleen versturen vanaf een\n * geverifieerd domein, dus dit is per site instelbaar:\n * - mode \"xicms\": from = verifiedSender (XiCMS-domein), replyTo = inzender/klant. Werkt direct.\n * - mode \"client\": from = het geverifieerde eigen domein van de klant.\n */\nexport const FormsSenderSchema = z.object({\n mode: z._default(z.enum([\"xicms\", \"client\"]), \"xicms\"),\n /** Geverifieerd from-adres (verplicht ongeacht mode; bij \"client\" het klant-domein). */\n fromEmail: z.optional(z.email()),\n fromName: z.optional(z.string().check(z.maxLength(120))),\n /** Waar notificaties heen gaan (meestal het klant-mailadres). */\n notifyTo: z.optional(z.email()),\n});\nexport type FormsSender = z.infer<typeof FormsSenderSchema>;\n\n/** Eén social-link (platform + url). */\nexport const SocialLinkSchema = z.object({\n platform: z.string().check(z.minLength(1)).check(z.maxLength(40)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n});\nexport type SocialLink = z.infer<typeof SocialLinkSchema>;\n\n/** Eén footer-kolom: kop + links. */\nexport const FooterColumnSchema = z.object({\n heading: z.optional(z.string().check(z.maxLength(120))),\n links: z._default(z.array(NavItemSchema).check(z.maxLength(20)), []),\n});\n\n/** Bewerkbare footer (klant past dit aan in /beheer; core levert de unstyled `SiteFooter`). */\nexport const FooterSettingsSchema = z.object({\n columns: z.optional(z.array(FooterColumnSchema).check(z.maxLength(6))),\n social: z.optional(z.array(SocialLinkSchema).check(z.maxLength(12))),\n copyright: z.optional(z.string().check(z.maxLength(300))),\n /** Toon de \"Beheer\"-link in de footer (default: aan). */\n showAdminLink: z.optional(z.boolean()),\n});\nexport type FooterSettings = z.infer<typeof FooterSettingsSchema>;\n\n/** Per-module activatie/instellingen (spiegelt ModulesSiteConfig; permissief gehouden). */\nexport const ModulesSettingsSchema = z.record(\n z.string(),\n z.union([\n z.boolean(),\n z.object({ enabled: z.optional(z.boolean()), config: z.optional(z.record(z.string(), z.unknown())) }),\n ]),\n);\n\n/** Het `sites/{siteId}/settings/site`-document: alle bewerkbare site-instellingen. */\nexport const SiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.minLength(1)).check(z.maxLength(160))),\n // Zelfde hostname-regel als SiteConfigSchema.domain (geen protocol/pad).\n domain: z.union([z.optional(z.string().check(z.maxLength(255)).check(z.regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i, \"alleen een hostname, bv. 'www.klant.nl' (zonder https:// of pad)\"))), z.literal(\"\")]),\n contactEmail: z.optional(z.email()),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n forms: z.optional(FormsSenderSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n /**\n * Snelkoppelingen op de beheer-startpagina (7.16). Beheer-intern: bewust NIET in de publieke\n * subset. Opslaan loopt via de bestaande saveSiteSettings-callable — die is passthrough met\n * merge, dus dit veld vergt geen functions-wijziging.\n */\n dashboardLinks: z.optional(z.array(z.object({\n label: z.string().check(z.minLength(1)).check(z.maxLength(80)),\n href: z.string().check(z.minLength(1)).check(z.maxLength(2000)),\n })).check(z.maxLength(12))),\n});\nexport type SiteSettings = z.infer<typeof SiteSettingsSchema>;\n\n/**\n * Publiek-veilige presentatie-instellingen (`sites/{siteId}/settings/public`): wordt door de live\n * site gelezen (footer, thema, nav, modules) zónder de gevoelige velden (e-mailafzender, contact)\n * uit `settings/site` bloot te geven.\n */\nexport const PublicSiteSettingsSchema = z.object({\n name: z.optional(z.string().check(z.maxLength(160))),\n nav: z.optional(z.array(NavItemSchema).check(z.maxLength(50))),\n theme: z.optional(ThemeSchema),\n seoDefaults: z.optional(SeoSchema),\n footer: z.optional(FooterSettingsSchema),\n modules: z.optional(ModulesSettingsSchema),\n});\nexport type PublicSiteSettings = z.infer<typeof PublicSiteSettingsSchema>;\n\n/** Haalt de publiek-veilige subset uit de volledige settings (voor het `settings/public`-doc). */\nexport function toPublicSettings(s: SiteSettings): PublicSiteSettings {\n return {\n name: s.name,\n nav: s.nav,\n theme: s.theme,\n seoDefaults: s.seoDefaults,\n footer: s.footer,\n modules: s.modules,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/styles.css CHANGED
@@ -346,7 +346,7 @@
346
346
  productformulier vraagt — de pagina blijft de hoofdzaak. z-index boven de publiceerknop
347
347
  zou de knop verstoppen; erónder blijven (10001) en niet de volle breedte pakken is genoeg. */
348
348
  position: fixed;
349
- top: 2.6rem;
349
+ top: 3.5rem;
350
350
  right: 0;
351
351
  bottom: var(--xicms-bottombar, 0px);
352
352
  width: clamp(360px, 28vw, 480px);
@@ -415,7 +415,8 @@
415
415
  /* Zolang de onder-lade open is heeft de publiceerknop geen plek: hij lag er qua z-index
416
416
  ónder (onklikbaar) en erboven zou hij het formulier afdekken. De lade heeft zijn eigen
417
417
  ✓ Opgeslagen/✕ Sluiten; publiceren doe je daarna — de knop komt terug zodra de lade sluit. */
418
- :root:has(.xicms-ve__panel) .xicms-ve-publiceer {
418
+ :root:has(.xicms-ve__panel) .xicms-ve-publiceer,
419
+ :root:has(.xicms-ve__panel) .xicms-ve-verwerp {
419
420
  display: none;
420
421
  }
421
422
  /* Telefoon: de lade komt van onderen (een zijlade van 360px zou het hele scherm zijn). */
@@ -546,12 +547,25 @@
546
547
  .xicms-ve-pencil:hover,
547
548
  .xicms-ve-pencil:focus-visible {
548
549
  opacity: 1;
549
- background: var(--xicms-primary, #4f46e5);
550
+ /* Donker blijven, niet de merkkleur: `--xicms-primary` is van de klant en kan licht zijn
551
+ (goud, lime) — dan werd dit wit-op-licht en "verdween" de knop precies bij het aanwijzen.
552
+ Gereedschap houdt zijn eigen kleuren (zie het EditBar-commentaar). */
553
+ background: rgba(15, 23, 42, 0.92);
550
554
  }
551
555
  .xicms-ve-pencil:focus-visible {
552
- outline: 2px solid var(--xicms-primary, #4f46e5);
556
+ outline: 2px solid #a5b4fc;
553
557
  outline-offset: 1px;
554
558
  }
559
+ /* Het potlood van het veld dat je NU bewerkt wordt een groen vinkje: "het is goed, klaar met dit
560
+ blok". Dezelfde plek als het potlood waarmee je begon, dus de afronding zit waar je hand al is. */
561
+ .xicms-ve-pencil.is-klaar {
562
+ opacity: 1;
563
+ background: rgba(22, 163, 74, 0.9);
564
+ }
565
+ .xicms-ve-pencil.is-klaar:hover,
566
+ .xicms-ve-pencil.is-klaar:focus-visible {
567
+ background: rgba(21, 128, 61, 1);
568
+ }
555
569
  /* Zwevende opmaak-toolbar boven een gefocust inline rich-text-veld (B/I/U/link/koppen/lijst). */
556
570
  .xicms-ve-fmtbar {
557
571
  position: fixed;
@@ -609,7 +623,8 @@
609
623
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
610
624
  transition: background 0.12s, transform 0.12s;
611
625
  }
612
- .xicms-ve-img__btn:hover { background: var(--color-accent, #4f46e5); transform: translateY(-1px); }
626
+ /* Donker blijven op hover — een lichte merkkleur (`--color-accent`) maakte dit wit-op-licht. */
627
+ .xicms-ve-img__btn:hover { background: rgba(17, 24, 39, 1); border-color: rgba(255, 255, 255, 0.5); transform: translateY(-1px); }
613
628
  /* Vullende variant (hero-achtergrond): knop absoluut t.o.v. het blok-element. */
614
629
  .xicms-ve-img__btn--fill {
615
630
  position: absolute;
@@ -754,35 +769,38 @@
754
769
 
755
770
  /* Zwevende werkbalk voor editors */
756
771
  /*
757
- * De beheerbalk staat BOVENAAN, en in beide toestanden op precies dezelfde plek.
772
+ * De beheerbalk ZWEEFT bovenaan, gecentreerd — en in beide toestanden op precies dezelfde plek.
758
773
  *
759
- * Hij zweefde als pilletje onderaan, terwijl de editor zijn eigen balk bovenaan meebracht. Het
760
- * overschakelen tussen "live site" en "bewerken" verplaatste de knoppen daarmee van onder naar
761
- * boven — je moest je houvast opnieuw zoeken op het moment dat je van modus wisselde, en dat is
762
- * precies het moment waarop je het niet kunt missen. Nu is het één balk die blijft staan en
763
- * waarin alleen een andere knop oplicht.
774
+ * Geschiedenis in twee stappen. Hij zweefde ooit als pilletje onderaan, terwijl de editor zijn
775
+ * eigen balk bovenaan meebracht: overschakelen verplaatste je houvast. 7.8 loste dat op met één
776
+ * vaste balk van rand tot rand — maar die las als een deel van de site in plaats van als
777
+ * gereedschap dat erover heen ligt. 7.16 combineert beide lessen: wéér een zwevend pilletje
778
+ * (duidelijk gereedschap, de site blijft zichtbaar), maar bovenaan gecentreerd en in beide
779
+ * modi identiek gepositioneerd, zodat het houvast van 7.8 blijft.
764
780
  *
765
- * Onderaan was ook niet meer vrij: daar staat sinds 7.8 de publiceerknop, en die hoort bij je werk
766
- * op de pagina — niet naast de navigatie.
781
+ * Onderaan blijft van de publiceerknop (sinds 7.8): die hoort bij je werk, niet bij de navigatie.
767
782
  */
768
783
  .xicms-editbar,
769
784
  .xicms-ve__bar {
770
785
  position: fixed;
771
- top: 0;
772
- left: 0;
773
- right: 0;
786
+ top: 10px;
787
+ left: 50%;
788
+ transform: translateX(-50%);
789
+ max-width: calc(100vw - 16px);
774
790
  z-index: 10001;
775
791
  display: flex;
776
792
  align-items: center;
777
793
  gap: 0.3rem;
778
794
  padding: 0.4rem 0.9rem;
795
+ border-radius: 999px;
779
796
  /* Eigen kleuren, los van de site eronder: een donkere staaf leest op een witte én op een
780
797
  donkere pagina, en blijft op élke klantsite hetzelfde stukje gereedschap. */
781
798
  background: rgba(15, 23, 42, 0.95);
782
799
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
783
800
  backdrop-filter: saturate(1.4) blur(10px);
784
801
  color: #fff;
785
- border-bottom: 1px solid rgba(255, 255, 255, 0.12);
802
+ border: 1px solid rgba(255, 255, 255, 0.12);
803
+ box-shadow: 0 12px 32px rgba(2, 6, 23, 0.35);
786
804
  font-size: 0.8rem;
787
805
  line-height: 1;
788
806
  }
@@ -809,7 +827,9 @@
809
827
 
810
828
  :root:has(.xicms-editbar),
811
829
  :root:has(.xicms-ve__bar) {
812
- padding-top: 2.6rem;
830
+ /* 10px lucht + ±34px pilletje + 12px lucht ≈ 3.5rem. Houd dit gelijk aan de standaard-`hoogte`
831
+ van useBalkOffset (barOffset.ts) en aan de top van .xicms-ve__panel/.xicms-ve-starthint. */
832
+ padding-top: 3.5rem;
813
833
  }
814
834
  /* Smalle schermen: de balk hield alles en werd twee regels hoog — en de pagina-offset (2.6rem,
815
835
  vast) rekent op één regel, dus de site schoof onder de balk. Het pad en de statustekst zijn op
@@ -822,6 +842,12 @@
822
842
  }
823
843
  .xicms-editbar,
824
844
  .xicms-ve__bar {
845
+ /* Op een telefoon is centreren met transform vragen om een pilletje dat half buiten beeld
846
+ hangt zodra de inhoud breder is dan het scherm: volle breedte minus marge, zonder anker. */
847
+ left: 8px;
848
+ right: 8px;
849
+ transform: none;
850
+ max-width: none;
825
851
  gap: 0.15rem;
826
852
  padding: 0.4rem 0.5rem;
827
853
  overflow-x: auto;
@@ -2642,6 +2668,15 @@
2642
2668
  .xicms-countdown__value { display: block; font-size: 1.8rem; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
2643
2669
  .xicms-countdown__label { display: block; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.6; margin-top: 0.25rem; }
2644
2670
  .xicms-countdown__expired { font-size: 1.4rem; font-weight: 700; }
2671
+ /* Editor-preview zonder einddatum: gestippeld kader zodat het blok zichtbaar en aanklikbaar blijft. */
2672
+ .xicms-countdown__placeholder {
2673
+ margin: 0;
2674
+ padding: 1rem 1.5rem;
2675
+ border: 2px dashed var(--xicms-muted, rgba(0, 0, 0, 0.2));
2676
+ border-radius: 0.5rem;
2677
+ display: inline-block;
2678
+ opacity: 0.7;
2679
+ }
2645
2680
  .xicms-widget--countdown .xicms-countdown__digits { gap: 0.4rem; }
2646
2681
  .xicms-widget--countdown .xicms-countdown__value { font-size: 1.3rem; }
2647
2682
  .xicms-widget--countdown .xicms-countdown__unit { min-width: 2.6rem; }
@@ -3187,6 +3222,12 @@
3187
3222
  .xicms-ve-blokknoppen.is-open .xicms-ve-blokknoppen__meer-in {
3188
3223
  display: flex;
3189
3224
  gap: 2px;
3225
+ /* VÓÓR potlood en tandwiel. Het cluster hangt met `right` aan de rechterkant van het blok, dus
3226
+ de doos groeit bij het uitklappen naar LINKS. Stonden de extra knoppen achteraan (DOM-volgorde),
3227
+ dan schoof het potlood precies hun breedte naar links — weg onder de cursor die erheen op weg
3228
+ was, en één misklik verwijderd van "dupliceren". Met de uitklapgroep vooraan blijven potlood en
3229
+ tandwiel op hun plek staan. DOM-volgorde blijft potlood-eerst (tab-volgorde, screenreaders). */
3230
+ order: -1;
3190
3231
  }
3191
3232
  .xicms-ve-blokknop:disabled {
3192
3233
  opacity: 0.35;
@@ -3243,7 +3284,7 @@
3243
3284
  /* Eénmalige starthint van de bewerkmodus: vast onder de balk, midden, weg te klikken. */
3244
3285
  .xicms-ve-starthint {
3245
3286
  position: fixed;
3246
- top: calc(2.6rem + 10px);
3287
+ top: calc(3.5rem + 6px);
3247
3288
  left: 50%;
3248
3289
  transform: translateX(-50%);
3249
3290
  z-index: 10006;
@@ -3336,6 +3377,39 @@
3336
3377
  font-size: 0.72rem;
3337
3378
  opacity: 0.8;
3338
3379
  }
3380
+ /* Verwerpen: het kleine broertje bóven Publiceren. Bestond niet — er was wel een ↺ per blok en
3381
+ undo, maar geen "gooi álles weg en terug naar live". Bewust klein en donker naast de grote
3382
+ blauwe: verwerpen is de uitzondering, publiceren het doel. Zelfde meeschuif-/verberggedrag
3383
+ als de publiceerknop (zie de :has-regels daar). */
3384
+ .xicms-ve-verwerp {
3385
+ position: fixed;
3386
+ right: 20px;
3387
+ bottom: calc(94px + var(--xicms-bottombar, 0px));
3388
+ z-index: 10006;
3389
+ padding: 0.45rem 0.9rem;
3390
+ border: 1px solid rgba(255, 255, 255, 0.14);
3391
+ border-radius: 999px;
3392
+ background: rgba(15, 23, 42, 0.75);
3393
+ color: rgba(255, 255, 255, 0.85);
3394
+ font: inherit;
3395
+ font-size: 0.78rem;
3396
+ cursor: pointer;
3397
+ box-shadow: 0 10px 30px rgba(2, 6, 23, 0.3);
3398
+ transition: background 0.15s ease, right 0.2s ease;
3399
+ }
3400
+ .xicms-ve-verwerp:hover {
3401
+ background: rgba(185, 28, 28, 0.9);
3402
+ color: #fff;
3403
+ }
3404
+ .xicms-ve-verwerp:focus-visible {
3405
+ outline: 2px solid #a5b4fc;
3406
+ outline-offset: 2px;
3407
+ }
3408
+ @media (min-width: 721px) {
3409
+ :root:has(.xicms-ve__panel) .xicms-ve-verwerp {
3410
+ right: calc(clamp(360px, 28vw, 480px) + 20px);
3411
+ }
3412
+ }
3339
3413
 
3340
3414
  /* Melding in de editorbalk (nog geen werkversie). Hoort bij het gereedschap, niet bij de pagina. */
3341
3415
  .xicms-ve__balkmelding {
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const CORE_VERSION = true ? "7.14.0" : "0.0.0-dev";
1
+ const CORE_VERSION = true ? "7.16.0" : "0.0.0-dev";
2
2
  const CORE_PACKAGE = "@xiwebdesign/xicms-core";
3
3
  function isNieuwer(beschikbaar, huidig) {
4
4
  const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "7.14.0",
3
+ "version": "7.16.0",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {