@xiwebdesign/xicms-core 5.2.0 → 5.4.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 (92) hide show
  1. package/dist/XiCMSProvider.d.ts +11 -2
  2. package/dist/XiCMSProvider.js +10 -5
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/blockEditors/GenericEditor.js +18 -8
  5. package/dist/admin/blockEditors/GenericEditor.js.map +1 -1
  6. package/dist/admin/blockEditors/index.js +2 -2
  7. package/dist/admin/blockEditors/index.js.map +1 -1
  8. package/dist/admin/customBlocks/CustomBlocksPanel.js +12 -2
  9. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -1
  10. package/dist/admin/lib/pagesClient.js +4 -1
  11. package/dist/admin/lib/pagesClient.js.map +1 -1
  12. package/dist/admin/lib/upload.js +14 -7
  13. package/dist/admin/lib/upload.js.map +1 -1
  14. package/dist/admin/media/MediaManager.js +60 -28
  15. package/dist/admin/media/MediaManager.js.map +1 -1
  16. package/dist/admin/media/MediaPicker.js +25 -15
  17. package/dist/admin/media/MediaPicker.js.map +1 -1
  18. package/dist/admin/pages/PageEditor.d.ts +2 -1
  19. package/dist/admin/pages/PageEditor.js +16 -2
  20. package/dist/admin/pages/PageEditor.js.map +1 -1
  21. package/dist/admin/pages/PagesPanel.js +9 -5
  22. package/dist/admin/pages/PagesPanel.js.map +1 -1
  23. package/dist/admin/visual/VisualEditor.js +11 -2
  24. package/dist/admin/visual/VisualEditor.js.map +1 -1
  25. package/dist/agentFiles/files.d.ts +15 -3
  26. package/dist/agentFiles/files.js +57 -0
  27. package/dist/agentFiles/files.js.map +1 -1
  28. package/dist/auth/AuthProvider.js +3 -3
  29. package/dist/auth/AuthProvider.js.map +1 -1
  30. package/dist/blocks/builtins/Booking.js +1 -1
  31. package/dist/blocks/builtins/Booking.js.map +1 -1
  32. package/dist/blocks/builtins/Columns.js +1 -1
  33. package/dist/blocks/builtins/Columns.js.map +1 -1
  34. package/dist/blocks/builtins/Embed.js +2 -0
  35. package/dist/blocks/builtins/Embed.js.map +1 -1
  36. package/dist/blocks/builtins/Gallery.js +2 -2
  37. package/dist/blocks/builtins/Gallery.js.map +1 -1
  38. package/dist/blocks/builtins/Hero.js +46 -7
  39. package/dist/blocks/builtins/Hero.js.map +1 -1
  40. package/dist/blocks/builtins/ImageBlock.js +14 -3
  41. package/dist/blocks/builtins/ImageBlock.js.map +1 -1
  42. package/dist/blocks/builtins/Jobs.d.ts +2 -2
  43. package/dist/blocks/builtins/Jobs.js +1 -1
  44. package/dist/blocks/builtins/Jobs.js.map +1 -1
  45. package/dist/blocks/builtins/Newsletter.js +18 -5
  46. package/dist/blocks/builtins/Newsletter.js.map +1 -1
  47. package/dist/blocks/builtins/Pricing.js +3 -1
  48. package/dist/blocks/builtins/Pricing.js.map +1 -1
  49. package/dist/blocks/builtins/Sections.js +4 -4
  50. package/dist/blocks/builtins/Sections.js.map +1 -1
  51. package/dist/blocks/builtins/Slider.js +9 -4
  52. package/dist/blocks/builtins/Slider.js.map +1 -1
  53. package/dist/blocks/builtins/SymbolRef.js.map +1 -1
  54. package/dist/blocks/builtins/Video.js +1 -1
  55. package/dist/blocks/builtins/Video.js.map +1 -1
  56. package/dist/blocks/builtins/Widgets.js +2 -1
  57. package/dist/blocks/builtins/Widgets.js.map +1 -1
  58. package/dist/components/PageView.js +4 -3
  59. package/dist/components/PageView.js.map +1 -1
  60. package/dist/demo/index.d.ts +7 -0
  61. package/dist/demo/index.js +23 -0
  62. package/dist/demo/index.js.map +1 -0
  63. package/dist/index.d.ts +2 -4
  64. package/dist/index.js +0 -20
  65. package/dist/index.js.map +1 -1
  66. package/dist/modules/registry.d.ts +3 -4
  67. package/dist/modules/registry.js +2 -3
  68. package/dist/modules/registry.js.map +1 -1
  69. package/dist/schema/blocks.d.ts +27 -90
  70. package/dist/schema/blocks.js +4 -6
  71. package/dist/schema/blocks.js.map +1 -1
  72. package/dist/schema/settings.d.ts +1 -34
  73. package/dist/schema/settings.js +2 -8
  74. package/dist/schema/settings.js.map +1 -1
  75. package/dist/schema/siteConfig.d.ts +3 -71
  76. package/dist/schema/siteConfig.js +3 -8
  77. package/dist/schema/siteConfig.js.map +1 -1
  78. package/dist/schema/symbols.d.ts +2 -2
  79. package/dist/schema/symbols.js.map +1 -1
  80. package/dist/server/index.d.ts +1 -1
  81. package/dist/server/index.js +2 -0
  82. package/dist/server/index.js.map +1 -1
  83. package/dist/server/pages.d.ts +23 -3
  84. package/dist/server/pages.js +37 -3
  85. package/dist/server/pages.js.map +1 -1
  86. package/dist/server/seo.js +5 -3
  87. package/dist/server/seo.js.map +1 -1
  88. package/dist/settings/useSiteSettings.d.ts +15 -3
  89. package/dist/settings/useSiteSettings.js +26 -5
  90. package/dist/settings/useSiteSettings.js.map +1 -1
  91. package/dist/styles.css +64 -9
  92. package/package.json +6 -2
@@ -3,6 +3,7 @@ import { type BlockDefinition } from "./blocks/registry";
3
3
  import { type CustomBlock as CustomBlockData } from "./schema/customBlocks";
4
4
  import { type BlockOverrides } from "./modules/overrides";
5
5
  import type { ModuleDefinition } from "./modules/types";
6
+ import { type PublicSiteSettings } from "./schema/settings";
6
7
  import { type DefinedTheme } from "@astryxdesign/core/theme";
7
8
  import { type LinkComponentType } from "@astryxdesign/core/Link";
8
9
  import { type ImageComponentType } from "./media/ImageContext";
@@ -13,7 +14,7 @@ interface ProviderProps {
13
14
  blocks?: BlockDefinition[];
14
15
  /**
15
16
  * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)
16
- * en respecteren `config.modules`/`config.features` voor activatie.
17
+ * en respecteren `config.modules` voor activatie.
17
18
  */
18
19
  modules?: ModuleDefinition[];
19
20
  /**
@@ -27,6 +28,14 @@ interface ProviderProps {
27
28
  * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).
28
29
  */
29
30
  customBlocksData?: CustomBlockData[];
31
+ /**
32
+ * SSR-startset van de publieke site-instellingen: geef het resultaat van
33
+ * `getPublicSiteSettings(siteId)` (server) door zodat klant-thema/nav/footer al in de
34
+ * server-HTML zitten (geen theme-flits bij eerste paint) én publiek geen client-read meer
35
+ * doet — de ISR-gecachte layout ververst de settings per revalidate. Zonder deze prop laadt
36
+ * de provider ze alsnog client-side (werkend, maar met mogelijke flits + 1 read per bezoeker).
37
+ */
38
+ settingsData?: PublicSiteSettings;
30
39
  /**
31
40
  * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.
32
41
  * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen
@@ -54,7 +63,7 @@ interface ProviderProps {
54
63
  * - de themavariabelen als CSS custom properties;
55
64
  * - App Check (alleen als geconfigureerd) en de EditBar/DemoLock.
56
65
  */
57
- export declare function XiCMSProvider({ config, blocks, modules, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, children }: ProviderProps): import("react").JSX.Element;
66
+ export declare function XiCMSProvider({ config, blocks, modules, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }: ProviderProps): import("react").JSX.Element;
58
67
  /**
59
68
  * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere
60
69
  * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding
@@ -18,7 +18,7 @@ import { applyBlockOverrides } from "./modules/overrides";
18
18
  import { builtinModules } from "./modules/builtins";
19
19
  import { ConsentBanner } from "./modules/consent/ConsentBanner";
20
20
  import { EditorProvider } from "./editor/EditorProvider";
21
- import { SiteSettingsProvider, useSiteSettings } from "./settings/useSiteSettings";
21
+ import { SiteSettingsProvider, useSiteSettings, useSiteSettingsLiveSync } from "./settings/useSiteSettings";
22
22
  import { mergeThemeTokens } from "./schema/settings";
23
23
  import { initAppCheck } from "./firebase/appcheck";
24
24
  import { DemoStudio } from "./demo/DemoStudio";
@@ -37,7 +37,7 @@ function warnOnce(message) {
37
37
  warned.add(message);
38
38
  console.warn(message);
39
39
  }
40
- function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, children }) {
40
+ function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }) {
41
41
  useEffect(() => {
42
42
  initAppCheck();
43
43
  }, []);
@@ -56,7 +56,7 @@ function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overr
56
56
  }
57
57
  }
58
58
  }, [config, modules]);
59
- return /* @__PURE__ */ jsx(ConfigProvider, { config, children: /* @__PURE__ */ jsx(SiteSettingsProvider, { siteId: config.siteId, children: /* @__PURE__ */ jsx(ModuleLayer, { config, blocks, modules, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, children }) }) });
59
+ return /* @__PURE__ */ jsx(ConfigProvider, { config, children: /* @__PURE__ */ jsx(SiteSettingsProvider, { siteId: config.siteId, initial: settingsData, children: /* @__PURE__ */ jsx(ModuleLayer, { config, blocks, modules, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, children }) }) });
60
60
  }
61
61
  function themeCssVars(theme) {
62
62
  const auto = Boolean(theme.dark?.auto);
@@ -139,10 +139,9 @@ function ModuleLayer({
139
139
  const resolved = useMemo(
140
140
  () => resolveModules([...builtinModules, ...modules], {
141
141
  modules: config.modules,
142
- features: config.features,
143
142
  firestoreModules: settings.modules
144
143
  }),
145
- [config.modules, config.features, modules, settings.modules]
144
+ [config.modules, modules, settings.modules]
146
145
  );
147
146
  const [customBlocks, setCustomBlocks] = useState(customBlocksData ?? []);
148
147
  const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);
@@ -152,6 +151,7 @@ function ModuleLayer({
152
151
  );
153
152
  return /* @__PURE__ */ jsx(ModulesProvider, { value: resolved, children: /* @__PURE__ */ jsx(RegistryProvider, { registry, children: /* @__PURE__ */ jsxs(AuthProvider, { siteId: config.siteId, demo: Boolean(config.demoEditing), children: [
154
153
  /* @__PURE__ */ jsx(CustomBlocksSync, { siteId: config.siteId, onData: setCustomBlocks }),
154
+ /* @__PURE__ */ jsx(SiteSettingsSync, { siteId: config.siteId }),
155
155
  /* @__PURE__ */ jsxs(EditorProvider, { children: [
156
156
  theme.fontUrl && /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: theme.fontUrl }),
157
157
  /* @__PURE__ */ jsx(Theme, { theme: astryxTheme ?? neutralTheme, mode: theme.dark?.auto ? "system" : isDarkColor(theme.background) ? "dark" : "light", children: /* @__PURE__ */ jsx(LinkProvider, { component: linkComponent ?? "a", children: /* @__PURE__ */ jsx(ImageProvider, { component: imageComponent, children: /* @__PURE__ */ jsxs(
@@ -172,6 +172,11 @@ function ModuleLayer({
172
172
  ] })
173
173
  ] }) }) });
174
174
  }
175
+ function SiteSettingsSync({ siteId }) {
176
+ const { role } = useAuth();
177
+ useSiteSettingsLiveSync(siteId, canEdit(role));
178
+ return null;
179
+ }
175
180
  function CustomBlocksSync({ siteId, onData }) {
176
181
  const { role } = useAuth();
177
182
  const editing = canEdit(role);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nimport { canEdit } from \"./auth/roles\";\nimport { EditBar } from \"./components/EditBar\";\nimport { DemoLock } from \"./lock/DemoLock\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { getDb } from \"./firebase/client\";\nimport { collection, onSnapshot } from \"firebase/firestore\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\nimport { CustomBlockSchema, type CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nimport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nimport { resolveModules } from \"./modules/registry\";\nimport { ModulesProvider } from \"./modules/context\";\nimport { applyBlockOverrides, type BlockOverrides } from \"./modules/overrides\";\nimport { builtinModules } from \"./modules/builtins\";\nimport type { ModuleDefinition, ModulesSiteConfig } from \"./modules/types\";\nimport { ConsentBanner, type ConsentSettings } from \"./modules/consent/ConsentBanner\";\nimport { EditorProvider } from \"./editor/EditorProvider\";\nimport { SiteSettingsProvider, useSiteSettings } from \"./settings/useSiteSettings\";\nimport { mergeThemeTokens, type ThemeTokens } from \"./schema/settings\";\nimport { initAppCheck } from \"./firebase/appcheck\";\nimport { DemoStudio } from \"./demo/DemoStudio\";\nimport { setDemoActive } from \"./demo/demoStore\";\nimport { Theme, type DefinedTheme } from \"@astryxdesign/core/theme\";\nimport { neutralTheme } from \"@astryxdesign/theme-neutral/built\";\nimport { LinkProvider, type LinkComponentType } from \"@astryxdesign/core/Link\";\nimport { ImageProvider, type ImageComponentType } from \"./media/ImageContext\";\nimport { validateSiteConfig, type SiteConfig } from \"./schema/siteConfig\";\nimport { validateModuleContract } from \"./modules/contract\";\n\ninterface ProviderProps {\n config: SiteConfig;\n /** Losse klant-blokken (via `defineBlock`) buiten een module om. Altijd actief. */\n blocks?: BlockDefinition[];\n /**\n * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)\n * en respecteren `config.modules`/`config.features` voor activatie.\n */\n modules?: ModuleDefinition[];\n /**\n * Per-site presentatie-overrides per block-type (`{ gallery: { Render: MijnGalerij } }`). Vervangt\n * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.\n */\n overrides?: BlockOverrides;\n /**\n * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`\n * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze\n * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).\n */\n customBlocksData?: CustomBlockData[];\n /**\n * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.\n * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen\n * `defineTheme(...)` doorgeven voor component-overrides — zónder core te forken.\n */\n astryxTheme?: DefinedTheme;\n /**\n * Framework-link-component (bv. Next `Link`) voor soepele client-navigatie + prefetch. Astryx-\n * componenten met een `href` (knoppen/links/tabs) gebruiken 'm dan i.p.v. een rauwe `<a>` (= volledige\n * page-reload). Core importeert `next/link` niet zelf (optionele peer) — geef 'm hier door vanuit de site.\n */\n linkComponent?: LinkComponentType;\n /**\n * Framework-beeldcomponent (bv. Next `Image`) voor automatische beeldoptimalisatie (responsive\n * srcset, AVIF/WebP, geen layout-shift). Core importeert `next/image` niet zelf — geef 'm hier door.\n */\n imageComponent?: ImageComponentType;\n children: ReactNode;\n}\n\n// Stabiele lege defaults: een `= []`-defaultparameter maakt bij ELKE render een nieuwe\n// array-referentie en laat memo's/effects met `modules`/`blocks` in de deps onnodig hervuren.\nconst NO_BLOCKS: BlockDefinition[] = [];\nconst NO_MODULES: ModuleDefinition[] = [];\n\n// Dev-waarschuwingen één keer per boodschap (React strict-mode dubbelt effects; zonder dedupe\n// verschijnt dezelfde config-waarschuwing telkens opnieuw in de console).\nconst warned = new Set<string>();\nfunction warnOnce(message: string) {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(message);\n}\n\n/**\n * Wikkel de hele site in deze provider (in `app/layout.tsx`). Hij zet:\n * - de SiteConfig-context (siteId, theme, mode) + live publieke settings (footer/nav/modules);\n * - auth + content (Firebase, env-gedreven; werkt ook zonder Firebase);\n * - de block-registry (ingebouwde + klant-blokken) voor de page-renderer;\n * - de themavariabelen als CSS custom properties;\n * - App Check (alleen als geconfigureerd) en de EditBar/DemoLock.\n */\nexport function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, children }: ProviderProps) {\n useEffect(() => {\n initAppCheck();\n }, []);\n\n // Dev-vangnet voor (agent-)geschreven configs: meld config- en module-contractfouten meteen in\n // de console i.p.v. een subtiel kapotte site. In productie geen werk — en er wordt nooit gegooid.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const configIssues = validateSiteConfig(config);\n if (configIssues.length > 0) {\n warnOnce(`[xicms] site.config.ts bevat problemen:\\n- ${configIssues.join(\"\\n- \")}`);\n }\n if (modules.length > 0) {\n const contractIssues = validateModuleContract(modules);\n if (contractIssues.length > 0) {\n warnOnce(`[xicms] klant-modules voldoen niet aan het module-contract:\\n- ${contractIssues.join(\"\\n- \")}`);\n }\n }\n }, [config, modules]);\n\n return (\n <ConfigProvider config={config}>\n <SiteSettingsProvider siteId={config.siteId}>\n <ModuleLayer config={config} blocks={blocks} modules={modules} overrides={overrides} astryxTheme={astryxTheme} linkComponent={linkComponent} imageComponent={imageComponent} customBlocksData={customBlocksData}>\n {children}\n </ModuleLayer>\n </SiteSettingsProvider>\n </ConfigProvider>\n );\n}\n\n/**\n * Theme-tokens → CSS custom properties. Puur; wordt in `ModuleLayer` op het LIVE-gemergde thema\n * toegepast (SiteConfig + `settings/public.theme`), zodat huisstijl-wijzigingen uit /beheer direct\n * op de site verschijnen — net als module-instellingen.\n */\nfunction themeCssVars(theme: ThemeTokens): Record<string, string> {\n // Dark-auto via light-dark(): deze variabelen staan INLINE op `.xicms-root`, en inline wint\n // altijd van het `@media (prefers-color-scheme: dark)`-blok in styles.css. Alleen dat blok kan\n // een inline lichte waarde dus nooit overrulen — de tokenwaarde zelf moet meeschakelen.\n // (styles.css zet `color-scheme: light dark` op `[data-dark=\"auto\"]`, vereist voor light-dark().)\n const auto = Boolean(theme.dark?.auto);\n const ld = (light: string, dark: string) => (auto ? `light-dark(${light}, ${dark})` : light);\n const cssVars: Record<string, string> = {\n \"--xicms-primary\": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,\n \"--xicms-accent\": theme.accent ?? theme.primary,\n \"--xicms-bg\": ld(theme.background ?? \"#ffffff\", theme.dark?.background ?? \"#0f172a\"),\n \"--xicms-fg\": ld(theme.foreground ?? \"#0f172a\", theme.dark?.foreground ?? \"#e2e8f0\"),\n };\n if (theme.fontFamily) cssVars[\"--xicms-font\"] = theme.fontFamily;\n // Optionele schalen → CSS-variabelen (alleen gezet als opgegeven; styles.css heeft defaults).\n for (const [k, v] of Object.entries(theme.spacing ?? {})) if (v) cssVars[`--xicms-spacing-${k}`] = v;\n if (theme.radius?.sm) cssVars[\"--xicms-radius-sm\"] = theme.radius.sm;\n if (theme.radius?.md) cssVars[\"--xicms-radius\"] = theme.radius.md;\n if (theme.radius?.lg) cssVars[\"--xicms-radius-lg\"] = theme.radius.lg;\n if (theme.container?.maxWidth) cssVars[\"--xicms-container-max\"] = theme.container.maxWidth;\n if (theme.container?.padding) cssVars[\"--xicms-container-pad\"] = theme.container.padding;\n if (theme.dark?.background) cssVars[\"--xicms-bg-dark\"] = theme.dark.background;\n if (theme.dark?.foreground) cssVars[\"--xicms-fg-dark\"] = theme.dark.foreground;\n if (theme.dark?.primary) cssVars[\"--xicms-primary-dark\"] = theme.dark.primary;\n\n // Astryx-tokens op XiCMS-waarden mappen, zodat de Astryx-componenten elke site z'n eigen merk\n // (kleur/typografie/radius) volgen. Inline op `.xicms-root` → wint over de theme.css-defaults.\n cssVars[\"--color-accent\"] = \"var(--xicms-primary)\";\n // Tekst óp de accentkleur: wit op een donkere merkkleur, donker op een lichte (geel/pastel) —\n // hardcoded wit maakte knop- en bannertekst onleesbaar op lichte primaire kleuren.\n const onAccent = isDarkColor(theme.primary) ? \"#ffffff\" : \"#0f172a\";\n cssVars[\"--color-on-accent\"] = onAccent;\n cssVars[\"--xicms-on-primary\"] = onAccent;\n cssVars[\"--color-text-accent\"] = \"var(--xicms-primary)\";\n cssVars[\"--radius-element\"] = \"var(--xicms-radius-sm, 8px)\";\n cssVars[\"--radius-container\"] = \"var(--xicms-radius, 14px)\";\n cssVars[\"--radius-page\"] = \"var(--xicms-radius-lg, 24px)\";\n // Volledige paletmapping — ALLEEN als de site een eigen achtergrond/tekst kiest. Zo verschilt niet\n // enkel het accent maar de hele sfeer (kaarten, borders, secundaire tekst) per site; een minimale\n // config blijft op de gepolijste neutrale basis (incl. automatische dark-mode via light-dark()).\n if (theme.background) {\n cssVars[\"--color-background-body\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 4%, var(--xicms-bg))\";\n cssVars[\"--color-background-card\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--color-background-popover\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 6%, var(--xicms-bg))\";\n // Ook de XiCMS-oppervlaktetokens meeschakelen: hun defaults in styles.css zijn licht (#fff/zwart-\n // rgba) en het dark-blok vuurt alleen bij `data-dark=\"auto\"`. Zonder deze mapping tonen presets\n // met een vaste donkere achtergrond (donker-modern, luxe-donker) witte kaarten en zwarte borders.\n cssVars[\"--xicms-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--xicms-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 5%, var(--xicms-bg))\";\n }\n if (theme.foreground) {\n cssVars[\"--color-text-primary\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-text-secondary\"] = \"color-mix(in srgb, var(--xicms-fg) 65%, transparent)\";\n cssVars[\"--color-neutral\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-border\"] = \"color-mix(in srgb, var(--xicms-fg) 14%, transparent)\";\n cssVars[\"--xicms-border\"] = \"color-mix(in srgb, var(--xicms-fg) 12%, transparent)\";\n cssVars[\"--xicms-border-strong\"] = \"color-mix(in srgb, var(--xicms-fg) 22%, transparent)\";\n }\n if (theme.headingFont) cssVars[\"--xicms-font-heading\"] = theme.headingFont;\n if (theme.fontFamily) {\n cssVars[\"--font-family-body\"] = \"var(--xicms-font)\";\n // Koppen volgen een apart display/serif-font als dat is opgegeven, anders het body-font.\n cssVars[\"--font-family-heading\"] = theme.headingFont ? \"var(--xicms-font-heading)\" : \"var(--xicms-font)\";\n } else if (theme.headingFont) {\n cssVars[\"--font-family-heading\"] = \"var(--xicms-font-heading)\";\n }\n\n return cssVars;\n}\n\n/**\n * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere\n * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding\n * bleef Astryx op \"light\" staan en tekenden zijn componenten lichte kaarten op een donkere pagina.\n * Puur en exporteerbaar voor tests; niet-hex waarden gelden als licht (veilige default).\n */\nexport function isDarkColor(color?: string): boolean {\n const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color?.trim() ?? \"\");\n if (!m) return false;\n let hex = m[1];\n if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);\n return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.45;\n}\n\n/**\n * Binnenlaag: leest de live publieke settings en resolvet daarmee de modules (settings/public.modules\n * legt zich over SiteConfig.modules) én het thema (settings/public.theme over SiteConfig.theme).\n * Zo werken module-aan/uit, -instellingen én huisstijl die de klant in /beheer zet direct door.\n */\nfunction ModuleLayer({\n config,\n blocks = NO_BLOCKS,\n modules = NO_MODULES,\n overrides,\n customBlocksData,\n astryxTheme,\n linkComponent,\n imageComponent,\n children,\n}: ProviderProps) {\n const { settings } = useSiteSettings();\n\n // LIVE thema: wijzigingen uit /beheer (settings/public.theme) winnen per token van SiteConfig.\n const theme = useMemo(() => mergeThemeTokens(config.theme, settings.theme), [config.theme, settings.theme]);\n const cssVars = useMemo(() => themeCssVars(theme), [theme]);\n\n // Demo-bewerkmodus globaal markeren zodat de (Firebase-)dataclients lokaal-only werken.\n // In een effect (niet tijdens render): render-pure + veilig onder concurrent rendering. De\n // demoStore-default is `false`, dus vóór het effect gedraagt alles zich als niet-demo.\n useEffect(() => {\n setDemoActive(Boolean(config.demoEditing));\n }, [config.demoEditing]);\n\n const resolved = useMemo(\n () =>\n resolveModules([...builtinModules, ...modules], {\n modules: config.modules,\n features: config.features,\n firestoreModules: settings.modules as ModulesSiteConfig | undefined,\n }),\n [config.modules, config.features, modules, settings.modules],\n );\n\n // Custom blocks (5.0): runtime-gedefinieerde bloktypes uit sites/{siteId}/customBlocks.\n // Publieke bezoekers krijgen UITSLUITEND de SSR-startset (customBlocksData, server-side gelezen) —\n // géén Firestore-listener per bezoeker (kosten/verbinding). Alleen ingelogde editors krijgen een\n // live sync (CustomBlocksSync, binnen AuthProvider) zodat een nieuw blok uit /beheer of een\n // agent-push zonder redeploy in het toevoeg-menu en op de pagina verschijnt.\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);\n\n const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),\n [resolved.blocks, customDefs, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <CustomBlocksSync siteId={config.siteId} onData={setCustomBlocks} />\n <EditorProvider>\n {/* React 19 hijst <link rel=\"stylesheet\"> automatisch naar <head> → lettertype laadt. */}\n {theme.fontUrl && <link rel=\"stylesheet\" href={theme.fontUrl} />}\n {/* Astryx-thema (Neutral basis) — de XiCMS-token-mapping op `.xicms-root` legt elke\n site z'n eigen merk eroverheen. */}\n <Theme theme={astryxTheme ?? neutralTheme} mode={theme.dark?.auto ? \"system\" : isDarkColor(theme.background) ? \"dark\" : \"light\"}>\n {/* Framework-router voor Astryx-links/knoppen (soepele client-nav + prefetch);\n zonder `linkComponent` valt het terug op een gewone `<a>` (huidig gedrag). */}\n <LinkProvider component={linkComponent ?? \"a\"}>\n <ImageProvider component={imageComponent}>\n <div\n className=\"xicms-root\"\n data-dark={theme.dark?.auto ? \"auto\" : undefined}\n style={cssVars as React.CSSProperties}\n >\n <DemoStudio />\n {children}\n <EditBar />\n <DemoLock />\n {resolved.activeIds.has(\"consent\") && (\n <ConsentBanner settings={resolved.settings.consent as ConsentSettings} />\n )}\n </div>\n </ImageProvider>\n </LinkProvider>\n </Theme>\n </EditorProvider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n\n/**\n * Live sync van custom blocks — ALLEEN voor ingelogde editors. Publieke bezoekers krijgen de\n * SSR-startset (customBlocksData) en géén Firestore-listener: dat scheelt een collection-read +\n * open verbinding per bezoeker, conform het getDoc-voor-publiek-patroon van useSiteSettings/News.\n */\nfunction CustomBlocksSync({ siteId, onData }: { siteId: string; onData: (defs: CustomBlockData[]) => void }) {\n const { role } = useAuth();\n const editing = canEdit(role);\n useEffect(() => {\n if (!editing) return;\n const db = getDb();\n if (!db) return;\n return onSnapshot(\n collection(db, \"sites\", siteId, \"customBlocks\"),\n (snap) => {\n const defs: CustomBlockData[] = [];\n snap.forEach((d) => {\n const res = CustomBlockSchema.safeParse({ ...(d.data() as Record<string, unknown>), type: (d.data() as { type?: string }).type ?? d.id });\n if (res.success) defs.push(res.data);\n });\n onData(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n }, [editing, siteId, onData]);\n return null;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nimport { canEdit } from \"./auth/roles\";\nimport { EditBar } from \"./components/EditBar\";\nimport { DemoLock } from \"./lock/DemoLock\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { getDb } from \"./firebase/client\";\nimport { collection, onSnapshot } from \"firebase/firestore\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\nimport { CustomBlockSchema, type CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nimport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nimport { resolveModules } from \"./modules/registry\";\nimport { ModulesProvider } from \"./modules/context\";\nimport { applyBlockOverrides, type BlockOverrides } from \"./modules/overrides\";\nimport { builtinModules } from \"./modules/builtins\";\nimport type { ModuleDefinition, ModulesSiteConfig } from \"./modules/types\";\nimport { ConsentBanner, type ConsentSettings } from \"./modules/consent/ConsentBanner\";\nimport { EditorProvider } from \"./editor/EditorProvider\";\nimport { SiteSettingsProvider, useSiteSettings, useSiteSettingsLiveSync } from \"./settings/useSiteSettings\";\nimport { mergeThemeTokens, type ThemeTokens, type PublicSiteSettings } from \"./schema/settings\";\nimport { initAppCheck } from \"./firebase/appcheck\";\nimport { DemoStudio } from \"./demo/DemoStudio\";\nimport { setDemoActive } from \"./demo/demoStore\";\nimport { Theme, type DefinedTheme } from \"@astryxdesign/core/theme\";\nimport { neutralTheme } from \"@astryxdesign/theme-neutral/built\";\nimport { LinkProvider, type LinkComponentType } from \"@astryxdesign/core/Link\";\nimport { ImageProvider, type ImageComponentType } from \"./media/ImageContext\";\nimport { validateSiteConfig, type SiteConfig } from \"./schema/siteConfig\";\nimport { validateModuleContract } from \"./modules/contract\";\n\ninterface ProviderProps {\n config: SiteConfig;\n /** Losse klant-blokken (via `defineBlock`) buiten een module om. Altijd actief. */\n blocks?: BlockDefinition[];\n /**\n * Klant-modules (via `defineModule`) bovenop de ingebouwde. Worden gemerged op `id` (klant wint)\n * en respecteren `config.modules` voor activatie.\n */\n modules?: ModuleDefinition[];\n /**\n * Per-site presentatie-overrides per block-type (`{ gallery: { Render: MijnGalerij } }`). Vervangt\n * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.\n */\n overrides?: BlockOverrides;\n /**\n * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`\n * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze\n * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).\n */\n customBlocksData?: CustomBlockData[];\n /**\n * SSR-startset van de publieke site-instellingen: geef het resultaat van\n * `getPublicSiteSettings(siteId)` (server) door zodat klant-thema/nav/footer al in de\n * server-HTML zitten (geen theme-flits bij eerste paint) én publiek geen client-read meer\n * doet — de ISR-gecachte layout ververst de settings per revalidate. Zonder deze prop laadt\n * de provider ze alsnog client-side (werkend, maar met mogelijke flits + 1 read per bezoeker).\n */\n settingsData?: PublicSiteSettings;\n /**\n * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.\n * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen\n * `defineTheme(...)` doorgeven voor component-overrides — zónder core te forken.\n */\n astryxTheme?: DefinedTheme;\n /**\n * Framework-link-component (bv. Next `Link`) voor soepele client-navigatie + prefetch. Astryx-\n * componenten met een `href` (knoppen/links/tabs) gebruiken 'm dan i.p.v. een rauwe `<a>` (= volledige\n * page-reload). Core importeert `next/link` niet zelf (optionele peer) — geef 'm hier door vanuit de site.\n */\n linkComponent?: LinkComponentType;\n /**\n * Framework-beeldcomponent (bv. Next `Image`) voor automatische beeldoptimalisatie (responsive\n * srcset, AVIF/WebP, geen layout-shift). Core importeert `next/image` niet zelf — geef 'm hier door.\n */\n imageComponent?: ImageComponentType;\n children: ReactNode;\n}\n\n// Stabiele lege defaults: een `= []`-defaultparameter maakt bij ELKE render een nieuwe\n// array-referentie en laat memo's/effects met `modules`/`blocks` in de deps onnodig hervuren.\nconst NO_BLOCKS: BlockDefinition[] = [];\nconst NO_MODULES: ModuleDefinition[] = [];\n\n// Dev-waarschuwingen één keer per boodschap (React strict-mode dubbelt effects; zonder dedupe\n// verschijnt dezelfde config-waarschuwing telkens opnieuw in de console).\nconst warned = new Set<string>();\nfunction warnOnce(message: string) {\n if (warned.has(message)) return;\n warned.add(message);\n console.warn(message);\n}\n\n/**\n * Wikkel de hele site in deze provider (in `app/layout.tsx`). Hij zet:\n * - de SiteConfig-context (siteId, theme, mode) + live publieke settings (footer/nav/modules);\n * - auth + content (Firebase, env-gedreven; werkt ook zonder Firebase);\n * - de block-registry (ingebouwde + klant-blokken) voor de page-renderer;\n * - de themavariabelen als CSS custom properties;\n * - App Check (alleen als geconfigureerd) en de EditBar/DemoLock.\n */\nexport function XiCMSProvider({ config, blocks = NO_BLOCKS, modules = NO_MODULES, overrides, astryxTheme, linkComponent, imageComponent, customBlocksData, settingsData, children }: ProviderProps) {\n useEffect(() => {\n initAppCheck();\n }, []);\n\n // Dev-vangnet voor (agent-)geschreven configs: meld config- en module-contractfouten meteen in\n // de console i.p.v. een subtiel kapotte site. In productie geen werk — en er wordt nooit gegooid.\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const configIssues = validateSiteConfig(config);\n if (configIssues.length > 0) {\n warnOnce(`[xicms] site.config.ts bevat problemen:\\n- ${configIssues.join(\"\\n- \")}`);\n }\n if (modules.length > 0) {\n const contractIssues = validateModuleContract(modules);\n if (contractIssues.length > 0) {\n warnOnce(`[xicms] klant-modules voldoen niet aan het module-contract:\\n- ${contractIssues.join(\"\\n- \")}`);\n }\n }\n }, [config, modules]);\n\n return (\n <ConfigProvider config={config}>\n <SiteSettingsProvider siteId={config.siteId} initial={settingsData}>\n <ModuleLayer config={config} blocks={blocks} modules={modules} overrides={overrides} astryxTheme={astryxTheme} linkComponent={linkComponent} imageComponent={imageComponent} customBlocksData={customBlocksData}>\n {children}\n </ModuleLayer>\n </SiteSettingsProvider>\n </ConfigProvider>\n );\n}\n\n/**\n * Theme-tokens → CSS custom properties. Puur; wordt in `ModuleLayer` op het LIVE-gemergde thema\n * toegepast (SiteConfig + `settings/public.theme`), zodat huisstijl-wijzigingen uit /beheer direct\n * op de site verschijnen — net als module-instellingen.\n */\nfunction themeCssVars(theme: ThemeTokens): Record<string, string> {\n // Dark-auto via light-dark(): deze variabelen staan INLINE op `.xicms-root`, en inline wint\n // altijd van het `@media (prefers-color-scheme: dark)`-blok in styles.css. Alleen dat blok kan\n // een inline lichte waarde dus nooit overrulen — de tokenwaarde zelf moet meeschakelen.\n // (styles.css zet `color-scheme: light dark` op `[data-dark=\"auto\"]`, vereist voor light-dark().)\n const auto = Boolean(theme.dark?.auto);\n const ld = (light: string, dark: string) => (auto ? `light-dark(${light}, ${dark})` : light);\n const cssVars: Record<string, string> = {\n \"--xicms-primary\": theme.dark?.primary ? ld(theme.primary, theme.dark.primary) : theme.primary,\n \"--xicms-accent\": theme.accent ?? theme.primary,\n \"--xicms-bg\": ld(theme.background ?? \"#ffffff\", theme.dark?.background ?? \"#0f172a\"),\n \"--xicms-fg\": ld(theme.foreground ?? \"#0f172a\", theme.dark?.foreground ?? \"#e2e8f0\"),\n };\n if (theme.fontFamily) cssVars[\"--xicms-font\"] = theme.fontFamily;\n // Optionele schalen → CSS-variabelen (alleen gezet als opgegeven; styles.css heeft defaults).\n for (const [k, v] of Object.entries(theme.spacing ?? {})) if (v) cssVars[`--xicms-spacing-${k}`] = v;\n if (theme.radius?.sm) cssVars[\"--xicms-radius-sm\"] = theme.radius.sm;\n if (theme.radius?.md) cssVars[\"--xicms-radius\"] = theme.radius.md;\n if (theme.radius?.lg) cssVars[\"--xicms-radius-lg\"] = theme.radius.lg;\n if (theme.container?.maxWidth) cssVars[\"--xicms-container-max\"] = theme.container.maxWidth;\n if (theme.container?.padding) cssVars[\"--xicms-container-pad\"] = theme.container.padding;\n if (theme.dark?.background) cssVars[\"--xicms-bg-dark\"] = theme.dark.background;\n if (theme.dark?.foreground) cssVars[\"--xicms-fg-dark\"] = theme.dark.foreground;\n if (theme.dark?.primary) cssVars[\"--xicms-primary-dark\"] = theme.dark.primary;\n\n // Astryx-tokens op XiCMS-waarden mappen, zodat de Astryx-componenten elke site z'n eigen merk\n // (kleur/typografie/radius) volgen. Inline op `.xicms-root` → wint over de theme.css-defaults.\n cssVars[\"--color-accent\"] = \"var(--xicms-primary)\";\n // Tekst óp de accentkleur: wit op een donkere merkkleur, donker op een lichte (geel/pastel) —\n // hardcoded wit maakte knop- en bannertekst onleesbaar op lichte primaire kleuren.\n const onAccent = isDarkColor(theme.primary) ? \"#ffffff\" : \"#0f172a\";\n cssVars[\"--color-on-accent\"] = onAccent;\n cssVars[\"--xicms-on-primary\"] = onAccent;\n cssVars[\"--color-text-accent\"] = \"var(--xicms-primary)\";\n cssVars[\"--radius-element\"] = \"var(--xicms-radius-sm, 8px)\";\n cssVars[\"--radius-container\"] = \"var(--xicms-radius, 14px)\";\n cssVars[\"--radius-page\"] = \"var(--xicms-radius-lg, 24px)\";\n // Volledige paletmapping — ALLEEN als de site een eigen achtergrond/tekst kiest. Zo verschilt niet\n // enkel het accent maar de hele sfeer (kaarten, borders, secundaire tekst) per site; een minimale\n // config blijft op de gepolijste neutrale basis (incl. automatische dark-mode via light-dark()).\n if (theme.background) {\n cssVars[\"--color-background-body\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 4%, var(--xicms-bg))\";\n cssVars[\"--color-background-card\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--color-background-popover\"] = \"var(--xicms-bg)\";\n cssVars[\"--color-background-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 6%, var(--xicms-bg))\";\n // Ook de XiCMS-oppervlaktetokens meeschakelen: hun defaults in styles.css zijn licht (#fff/zwart-\n // rgba) en het dark-blok vuurt alleen bij `data-dark=\"auto\"`. Zonder deze mapping tonen presets\n // met een vaste donkere achtergrond (donker-modern, luxe-donker) witte kaarten en zwarte borders.\n cssVars[\"--xicms-surface\"] = \"color-mix(in srgb, var(--xicms-fg) 3%, var(--xicms-bg))\";\n cssVars[\"--xicms-muted\"] = \"color-mix(in srgb, var(--xicms-fg) 5%, var(--xicms-bg))\";\n }\n if (theme.foreground) {\n cssVars[\"--color-text-primary\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-text-secondary\"] = \"color-mix(in srgb, var(--xicms-fg) 65%, transparent)\";\n cssVars[\"--color-neutral\"] = \"var(--xicms-fg)\";\n cssVars[\"--color-border\"] = \"color-mix(in srgb, var(--xicms-fg) 14%, transparent)\";\n cssVars[\"--xicms-border\"] = \"color-mix(in srgb, var(--xicms-fg) 12%, transparent)\";\n cssVars[\"--xicms-border-strong\"] = \"color-mix(in srgb, var(--xicms-fg) 22%, transparent)\";\n }\n if (theme.headingFont) cssVars[\"--xicms-font-heading\"] = theme.headingFont;\n if (theme.fontFamily) {\n cssVars[\"--font-family-body\"] = \"var(--xicms-font)\";\n // Koppen volgen een apart display/serif-font als dat is opgegeven, anders het body-font.\n cssVars[\"--font-family-heading\"] = theme.headingFont ? \"var(--xicms-font-heading)\" : \"var(--xicms-font)\";\n } else if (theme.headingFont) {\n cssVars[\"--font-family-heading\"] = \"var(--xicms-font-heading)\";\n }\n\n return cssVars;\n}\n\n/**\n * Is deze (hex-)kleur donker? Bepaalt de Astryx-thememodus voor sites met een vaste donkere\n * achtergrond (bv. presets donker-modern/luxe-donker, `dark.auto: false`): zonder deze afleiding\n * bleef Astryx op \"light\" staan en tekenden zijn componenten lichte kaarten op een donkere pagina.\n * Puur en exporteerbaar voor tests; niet-hex waarden gelden als licht (veilige default).\n */\nexport function isDarkColor(color?: string): boolean {\n const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(color?.trim() ?? \"\");\n if (!m) return false;\n let hex = m[1];\n if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c);\n const [r, g, b] = [0, 2, 4].map((i) => parseInt(hex.slice(i, i + 2), 16) / 255);\n return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.45;\n}\n\n/**\n * Binnenlaag: leest de live publieke settings en resolvet daarmee de modules (settings/public.modules\n * legt zich over SiteConfig.modules) én het thema (settings/public.theme over SiteConfig.theme).\n * Zo werken module-aan/uit, -instellingen én huisstijl die de klant in /beheer zet direct door.\n */\nfunction ModuleLayer({\n config,\n blocks = NO_BLOCKS,\n modules = NO_MODULES,\n overrides,\n customBlocksData,\n astryxTheme,\n linkComponent,\n imageComponent,\n children,\n}: ProviderProps) {\n const { settings } = useSiteSettings();\n\n // LIVE thema: wijzigingen uit /beheer (settings/public.theme) winnen per token van SiteConfig.\n const theme = useMemo(() => mergeThemeTokens(config.theme, settings.theme), [config.theme, settings.theme]);\n const cssVars = useMemo(() => themeCssVars(theme), [theme]);\n\n // Demo-bewerkmodus globaal markeren zodat de (Firebase-)dataclients lokaal-only werken.\n // In een effect (niet tijdens render): render-pure + veilig onder concurrent rendering. De\n // demoStore-default is `false`, dus vóór het effect gedraagt alles zich als niet-demo.\n useEffect(() => {\n setDemoActive(Boolean(config.demoEditing));\n }, [config.demoEditing]);\n\n const resolved = useMemo(\n () =>\n resolveModules([...builtinModules, ...modules], {\n modules: config.modules,\n firestoreModules: settings.modules as ModulesSiteConfig | undefined,\n }),\n [config.modules, modules, settings.modules],\n );\n\n // Custom blocks (5.0): runtime-gedefinieerde bloktypes uit sites/{siteId}/customBlocks.\n // Publieke bezoekers krijgen UITSLUITEND de SSR-startset (customBlocksData, server-side gelezen) —\n // géén Firestore-listener per bezoeker (kosten/verbinding). Alleen ingelogde editors krijgen een\n // live sync (CustomBlocksSync, binnen AuthProvider) zodat een nieuw blok uit /beheer of een\n // agent-push zonder redeploy in het toevoeg-menu en op de pagina verschijnt.\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);\n\n const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),\n [resolved.blocks, customDefs, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <CustomBlocksSync siteId={config.siteId} onData={setCustomBlocks} />\n <SiteSettingsSync siteId={config.siteId} />\n <EditorProvider>\n {/* React 19 hijst <link rel=\"stylesheet\"> automatisch naar <head> → lettertype laadt. */}\n {theme.fontUrl && <link rel=\"stylesheet\" href={theme.fontUrl} />}\n {/* Astryx-thema (Neutral basis) — de XiCMS-token-mapping op `.xicms-root` legt elke\n site z'n eigen merk eroverheen. */}\n <Theme theme={astryxTheme ?? neutralTheme} mode={theme.dark?.auto ? \"system\" : isDarkColor(theme.background) ? \"dark\" : \"light\"}>\n {/* Framework-router voor Astryx-links/knoppen (soepele client-nav + prefetch);\n zonder `linkComponent` valt het terug op een gewone `<a>` (huidig gedrag). */}\n <LinkProvider component={linkComponent ?? \"a\"}>\n <ImageProvider component={imageComponent}>\n <div\n className=\"xicms-root\"\n data-dark={theme.dark?.auto ? \"auto\" : undefined}\n style={cssVars as React.CSSProperties}\n >\n <DemoStudio />\n {children}\n <EditBar />\n <DemoLock />\n {resolved.activeIds.has(\"consent\") && (\n <ConsentBanner settings={resolved.settings.consent as ConsentSettings} />\n )}\n </div>\n </ImageProvider>\n </LinkProvider>\n </Theme>\n </EditorProvider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n\n/**\n * Live sync van custom blocks — ALLEEN voor ingelogde editors. Publieke bezoekers krijgen de\n * SSR-startset (customBlocksData) en géén Firestore-listener: dat scheelt een collection-read +\n * open verbinding per bezoeker, conform het getDoc-voor-publiek-patroon van useSiteSettings/News.\n */\n/**\n * Live sync van de publieke site-instellingen — ALLEEN voor ingelogde editors (zelfde patroon als\n * CustomBlocksSync). Zo ziet een editor een /beheer-wijziging (footer/nav/thema) meteen op de site,\n * terwijl publiek op de SSR-seed (settingsData) of de eenmalige getDoc blijft draaien.\n */\nfunction SiteSettingsSync({ siteId }: { siteId: string }) {\n const { role } = useAuth();\n useSiteSettingsLiveSync(siteId, canEdit(role));\n return null;\n}\n\nfunction CustomBlocksSync({ siteId, onData }: { siteId: string; onData: (defs: CustomBlockData[]) => void }) {\n const { role } = useAuth();\n const editing = canEdit(role);\n useEffect(() => {\n if (!editing) return;\n const db = getDb();\n if (!db) return;\n return onSnapshot(\n collection(db, \"sites\", siteId, \"customBlocks\"),\n (snap) => {\n const defs: CustomBlockData[] = [];\n snap.forEach((d) => {\n const res = CustomBlockSchema.safeParse({ ...(d.data() as Record<string, unknown>), type: (d.data() as { type?: string }).type ?? d.id });\n if (res.success) defs.push(res.data);\n });\n onData(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n }, [editing, siteId, onData]);\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -36,14 +36,7 @@ function FieldWidget({
36
36
  f.label
37
37
  ] });
38
38
  case "select":
39
- return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(
40
- Select,
41
- {
42
- value: String(v ?? f.options[0]?.value ?? ""),
43
- onValueChange: set,
44
- options: f.options.map((o) => ({ value: o.value, label: o.label || o.value }))
45
- }
46
- ) });
39
+ return /* @__PURE__ */ jsx(SelectField, { f, v, set });
47
40
  case "number":
48
41
  return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(
49
42
  Input,
@@ -194,6 +187,23 @@ function GenericEditor({ block, siteId, onChange }) {
194
187
  }
195
188
  );
196
189
  }
190
+ function SelectField({
191
+ f,
192
+ v,
193
+ set
194
+ }) {
195
+ useEffect(() => {
196
+ if (v == null && f.options[0] !== void 0) set(f.options[0].value);
197
+ }, []);
198
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(
199
+ Select,
200
+ {
201
+ value: String(v ?? f.options[0]?.value ?? ""),
202
+ onValueChange: set,
203
+ options: f.options.map((o) => ({ value: o.value, label: o.label || o.value }))
204
+ }
205
+ ) });
206
+ }
197
207
  export {
198
208
  FieldsForm,
199
209
  GenericEditor
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/admin/blockEditors/GenericEditor.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * GenericEditor 2.0 (5.0) — het automatisch gegenereerde formulier voor blokken zonder eigen\n * editor, aangedreven door de Field-IR (schema/fields.ts, primo-port):\n *\n * - **repeaters** en **groepen** zijn echte UI (ArrayEditor met toevoegen/verwijderen/↑↓ en een\n * omkaderde subgroep) i.p.v. een JSON-textarea;\n * - **showIf** verbergt velden conditioneel;\n * - date/slider/link/icoon/pagina-verwijzing hebben eigen widgets;\n * - de JSON-textarea bestaat alleen nog als laatste redmiddel (records/scalar-arrays), met\n * schema-validatie vóór commit.\n *\n * `FieldsForm` is los herbruikbaar: custom blocks (Fase B) renderen er hun opgeslagen Field-IR mee.\n */\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { ZodTypeAny } from \"zod\";\nimport { Field as FieldRow, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { RichTextEditor } from \"../editors/RichTextEditor\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { subscribePages, type DraftPage } from \"../lib/pagesClient\";\nimport { introspectToFields, introspectObjectSchema } from \"../lib/zodFields\";\nimport { fieldsDefaults, isFieldVisible, type Field } from \"../../schema/fields\";\n\nexport interface GenericEditorProps {\n block: Record<string, unknown>;\n siteId: string;\n onChange: (next: Record<string, unknown>) => void;\n}\n\n/** Formulier voor een Field-IR over een waarden-object. Recursief (groepen/repeaters). */\nexport function FieldsForm({\n fields,\n values,\n onChange,\n siteId,\n jsonSchemas,\n}: {\n fields: Field[];\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n /** Optioneel: sub-schema per json-veldsleutel voor validatie vóór commit. */\n jsonSchemas?: Record<string, ZodTypeAny>;\n}) {\n return (\n <div className=\"grid gap-3\">\n {fields.filter((f) => isFieldVisible(f, values)).map((f) => (\n <FieldWidget key={f.key} f={f} values={values} onChange={onChange} siteId={siteId} jsonSchema={jsonSchemas?.[f.key]} />\n ))}\n </div>\n );\n}\n\nfunction FieldWidget({\n f,\n values,\n onChange,\n siteId,\n jsonSchema,\n}: {\n f: Field;\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n jsonSchema?: ZodTypeAny;\n}) {\n const v = values[f.key];\n const set = (val: unknown) => onChange({ [f.key]: val });\n\n switch (f.type) {\n case \"switch\":\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <Switch checked={Boolean(v)} onCheckedChange={set} />\n {f.label}\n </label>\n );\n case \"select\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Select\n value={String(v ?? f.options[0]?.value ?? \"\")}\n onValueChange={set}\n options={f.options.map((o) => ({ value: o.value, label: o.label || o.value }))}\n />\n </FieldRow>\n );\n case \"number\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input\n type=\"number\"\n min={f.min}\n max={f.max}\n step={f.step}\n value={v == null ? \"\" : String(v)}\n onChange={(e) => set(e.target.value === \"\" ? undefined : Number(e.target.value))}\n />\n </FieldRow>\n );\n case \"slider\": {\n const val = typeof v === \"number\" ? v : f.min;\n return (\n <FieldRow label={`${f.label} (${val})`} hint={f.hint}>\n <input\n type=\"range\"\n min={f.min}\n max={f.max}\n step={f.step ?? 1}\n value={val}\n onChange={(e) => set(Number(e.target.value))}\n className=\"w-full\"\n />\n </FieldRow>\n );\n }\n case \"date\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input type=\"date\" value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"richtext\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <RichTextEditor siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"image\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"flex items-center gap-2\">\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"URL of kies →\" />\n <MediaPickerButton siteId={siteId} onSelect={(m) => set(m.url)} />\n </div>\n </FieldRow>\n );\n case \"url\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"https://… of /pagina\" />\n </FieldRow>\n );\n case \"icon\":\n return (\n <FieldRow label={f.label} hint={f.hint ?? \"Emoji, bv. ✂️\"}>\n <Input value={String(v ?? \"\")} maxLength={16} onChange={(e) => set(e.target.value)} className=\"w-24\" />\n </FieldRow>\n );\n case \"link\": {\n const link = (v as { label?: string; href?: string } | undefined) ?? {};\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label ?? \"\"} placeholder=\"Tekst\" onChange={(e) => set({ ...link, label: e.target.value })} />\n <Input value={link.href ?? \"\"} placeholder=\"/pagina of https://…\" onChange={(e) => set({ ...link, href: e.target.value })} />\n </div>\n </FieldRow>\n );\n }\n case \"group\": {\n const group = (v as Record<string, unknown> | undefined) ?? {};\n return (\n <fieldset className=\"grid gap-2 rounded-lg border border-slate-200 p-3\">\n <legend className=\"px-1 text-xs font-semibold text-slate-500\">{f.label}</legend>\n <FieldsForm fields={f.fields} values={group} onChange={(p) => set({ ...group, ...p })} siteId={siteId} />\n </fieldset>\n );\n }\n case \"repeater\": {\n const items = (Array.isArray(v) ? v : []) as Record<string, unknown>[];\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <ArrayEditor<Record<string, unknown>>\n items={items}\n onChange={set}\n newItem={() => fieldsDefaults(f.fields)}\n addLabel={`+ ${f.itemLabel ?? \"Item\"}`}\n renderRow={(item, update) => (\n <FieldsForm fields={f.fields} values={item} onChange={(p) => update(p)} siteId={siteId} />\n )}\n />\n </FieldRow>\n );\n }\n case \"pageRef\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <PageRefPicker siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"info\":\n return <p className=\"text-xs text-slate-500\">{f.text}</p>;\n case \"textarea\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Textarea rows={f.rows ?? 4} value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"json\":\n return <JsonField label={f.label} value={v} onCommit={set} schema={jsonSchema} />;\n case \"text\":\n default:\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} placeholder={f.type === \"text\" ? f.placeholder : undefined} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n }\n}\n\n/** Pagina-verwijzing: kies uit de bestaande pagina's van de site (live lijst). */\nfunction PageRefPicker({ siteId, value, onChange }: { siteId: string; value: string; onChange: (v: string) => void }) {\n const [pages, setPages] = useState<DraftPage[]>([]);\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n const options = useMemo(() => {\n const opts = pages\n .slice()\n .sort((a, b) => (a.path < b.path ? -1 : 1))\n .map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` }));\n // Bewaar een waarde die (nog) niet in de lijst staat, zodat niets stilletjes wegvalt.\n if (value && !opts.some((o) => o.value === value)) opts.unshift({ value, label: value });\n return [{ value: \"\", label: \"— geen —\" }, ...opts];\n }, [pages, value]);\n return <Select value={value} onValueChange={onChange} options={options} />;\n}\n\n/** Laatste redmiddel voor complexe velden: JSON met schema-validatie vóór commit. */\nfunction JsonField({\n label,\n value,\n onCommit,\n schema,\n}: {\n label: string;\n value: unknown;\n onCommit: (v: unknown) => void;\n schema?: ZodTypeAny;\n}) {\n const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));\n const [error, setError] = useState<string | null>(null);\n const commit = () => {\n try {\n const parsed = JSON.parse(text) as unknown;\n if (schema) {\n const res = schema.safeParse(parsed);\n if (!res.success) {\n setError(\"Voldoet niet aan het verwachte formaat van dit veld.\");\n return;\n }\n setError(null);\n onCommit(res.data);\n return;\n }\n setError(null);\n onCommit(parsed);\n } catch {\n setError(\"Geen geldige JSON.\");\n }\n };\n return (\n <FieldRow label={`${label} (JSON)`} hint=\"Geavanceerd veld — wordt gecontroleerd vóór opslaan.\">\n <Textarea rows={6} value={text} onChange={(e) => setText(e.target.value)} onBlur={commit} spellCheck={false} className=\"font-mono text-xs\" />\n {error && <p className=\"text-xs text-red-600\">{error}</p>}\n </FieldRow>\n );\n}\n\n/**\n * De blok-editor-fallback: leidt de Field-IR af uit het Zod-schema van het blok (+ optionele\n * `fields`-hints van `defineBlock`) en rendert het formulier. Zo is een blok zonder maatwerk-\n * editor nooit een doodlopend pad voor de klant.\n */\nexport function GenericEditor({ block, siteId, onChange }: GenericEditorProps) {\n const registry = useRegistry();\n const def = registry.get(String(block._type ?? \"\"));\n const fields = useMemo(() => {\n if (!def) return [];\n // Custom blocks (5.0) dragen hun opgeslagen Field-IR mee — rijker dan introspectie\n // (showIf/date/slider-metadata gaat door fieldsToZod heen verloren).\n const custom = (def as { customFields?: Field[] }).customFields;\n return custom ?? introspectToFields(def.schema, def.fields);\n }, [def]);\n // Sub-schema's voor json-velden (validatie vóór commit), uit de platte introspectie.\n const jsonSchemas = useMemo(() => {\n if (!def) return {};\n const out: Record<string, ZodTypeAny> = {};\n for (const f of introspectObjectSchema(def.schema)) if (f.kind === \"json\") out[f.key] = f.schema;\n return out;\n }, [def]);\n if (fields.length === 0) {\n return <p className=\"text-sm text-slate-500\">Voor dit bloktype is geen editor beschikbaar.</p>;\n }\n return (\n <FieldsForm\n fields={fields}\n values={block}\n onChange={(p) => onChange({ ...block, ...p })}\n siteId={siteId}\n jsonSchemas={jsonSchemas}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/admin/blockEditors/GenericEditor.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * GenericEditor 2.0 (5.0) — het automatisch gegenereerde formulier voor blokken zonder eigen\n * editor, aangedreven door de Field-IR (schema/fields.ts, primo-port):\n *\n * - **repeaters** en **groepen** zijn echte UI (ArrayEditor met toevoegen/verwijderen/↑↓ en een\n * omkaderde subgroep) i.p.v. een JSON-textarea;\n * - **showIf** verbergt velden conditioneel;\n * - date/slider/link/icoon/pagina-verwijzing hebben eigen widgets;\n * - de JSON-textarea bestaat alleen nog als laatste redmiddel (records/scalar-arrays), met\n * schema-validatie vóór commit.\n *\n * `FieldsForm` is los herbruikbaar: custom blocks (Fase B) renderen er hun opgeslagen Field-IR mee.\n */\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { ZodTypeAny } from \"zod\";\nimport { Field as FieldRow, Input, Textarea } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { Switch } from \"../ui/switch\";\nimport { ArrayEditor } from \"../ui/ArrayEditor\";\nimport { RichTextEditor } from \"../editors/RichTextEditor\";\nimport { MediaPickerButton } from \"../media/MediaPicker\";\nimport { useRegistry } from \"../../blocks/context\";\nimport { subscribePages, type DraftPage } from \"../lib/pagesClient\";\nimport { introspectToFields, introspectObjectSchema } from \"../lib/zodFields\";\nimport { fieldsDefaults, isFieldVisible, type Field } from \"../../schema/fields\";\n\nexport interface GenericEditorProps {\n block: Record<string, unknown>;\n siteId: string;\n onChange: (next: Record<string, unknown>) => void;\n}\n\n/** Formulier voor een Field-IR over een waarden-object. Recursief (groepen/repeaters). */\nexport function FieldsForm({\n fields,\n values,\n onChange,\n siteId,\n jsonSchemas,\n}: {\n fields: Field[];\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n /** Optioneel: sub-schema per json-veldsleutel voor validatie vóór commit. */\n jsonSchemas?: Record<string, ZodTypeAny>;\n}) {\n return (\n <div className=\"grid gap-3\">\n {fields.filter((f) => isFieldVisible(f, values)).map((f) => (\n <FieldWidget key={f.key} f={f} values={values} onChange={onChange} siteId={siteId} jsonSchema={jsonSchemas?.[f.key]} />\n ))}\n </div>\n );\n}\n\nfunction FieldWidget({\n f,\n values,\n onChange,\n siteId,\n jsonSchema,\n}: {\n f: Field;\n values: Record<string, unknown>;\n onChange: (patch: Record<string, unknown>) => void;\n siteId: string;\n jsonSchema?: ZodTypeAny;\n}) {\n const v = values[f.key];\n const set = (val: unknown) => onChange({ [f.key]: val });\n\n switch (f.type) {\n case \"switch\":\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <Switch checked={Boolean(v)} onCheckedChange={set} />\n {f.label}\n </label>\n );\n case \"select\":\n return <SelectField f={f} v={v} set={set} />;\n case \"number\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input\n type=\"number\"\n min={f.min}\n max={f.max}\n step={f.step}\n value={v == null ? \"\" : String(v)}\n onChange={(e) => set(e.target.value === \"\" ? undefined : Number(e.target.value))}\n />\n </FieldRow>\n );\n case \"slider\": {\n const val = typeof v === \"number\" ? v : f.min;\n return (\n <FieldRow label={`${f.label} (${val})`} hint={f.hint}>\n <input\n type=\"range\"\n min={f.min}\n max={f.max}\n step={f.step ?? 1}\n value={val}\n onChange={(e) => set(Number(e.target.value))}\n className=\"w-full\"\n />\n </FieldRow>\n );\n }\n case \"date\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input type=\"date\" value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"richtext\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <RichTextEditor siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"image\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"flex items-center gap-2\">\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"URL of kies →\" />\n <MediaPickerButton siteId={siteId} onSelect={(m) => set(m.url)} />\n </div>\n </FieldRow>\n );\n case \"url\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} placeholder=\"https://… of /pagina\" />\n </FieldRow>\n );\n case \"icon\":\n return (\n <FieldRow label={f.label} hint={f.hint ?? \"Emoji, bv. ✂️\"}>\n <Input value={String(v ?? \"\")} maxLength={16} onChange={(e) => set(e.target.value)} className=\"w-24\" />\n </FieldRow>\n );\n case \"link\": {\n const link = (v as { label?: string; href?: string } | undefined) ?? {};\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <div className=\"grid grid-cols-2 gap-2\">\n <Input value={link.label ?? \"\"} placeholder=\"Tekst\" onChange={(e) => set({ ...link, label: e.target.value })} />\n <Input value={link.href ?? \"\"} placeholder=\"/pagina of https://…\" onChange={(e) => set({ ...link, href: e.target.value })} />\n </div>\n </FieldRow>\n );\n }\n case \"group\": {\n const group = (v as Record<string, unknown> | undefined) ?? {};\n return (\n <fieldset className=\"grid gap-2 rounded-lg border border-slate-200 p-3\">\n <legend className=\"px-1 text-xs font-semibold text-slate-500\">{f.label}</legend>\n <FieldsForm fields={f.fields} values={group} onChange={(p) => set({ ...group, ...p })} siteId={siteId} />\n </fieldset>\n );\n }\n case \"repeater\": {\n const items = (Array.isArray(v) ? v : []) as Record<string, unknown>[];\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <ArrayEditor<Record<string, unknown>>\n items={items}\n onChange={set}\n newItem={() => fieldsDefaults(f.fields)}\n addLabel={`+ ${f.itemLabel ?? \"Item\"}`}\n renderRow={(item, update) => (\n <FieldsForm fields={f.fields} values={item} onChange={(p) => update(p)} siteId={siteId} />\n )}\n />\n </FieldRow>\n );\n }\n case \"pageRef\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <PageRefPicker siteId={siteId} value={String(v ?? \"\")} onChange={set} />\n </FieldRow>\n );\n case \"info\":\n return <p className=\"text-xs text-slate-500\">{f.text}</p>;\n case \"textarea\":\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Textarea rows={f.rows ?? 4} value={String(v ?? \"\")} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n case \"json\":\n return <JsonField label={f.label} value={v} onCommit={set} schema={jsonSchema} />;\n case \"text\":\n default:\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Input value={String(v ?? \"\")} placeholder={f.type === \"text\" ? f.placeholder : undefined} onChange={(e) => set(e.target.value)} />\n </FieldRow>\n );\n }\n}\n\n/** Pagina-verwijzing: kies uit de bestaande pagina's van de site (live lijst). */\nfunction PageRefPicker({ siteId, value, onChange }: { siteId: string; value: string; onChange: (v: string) => void }) {\n const [pages, setPages] = useState<DraftPage[]>([]);\n useEffect(() => subscribePages(siteId, setPages), [siteId]);\n const options = useMemo(() => {\n const opts = pages\n .slice()\n .sort((a, b) => (a.path < b.path ? -1 : 1))\n .map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` }));\n // Bewaar een waarde die (nog) niet in de lijst staat, zodat niets stilletjes wegvalt.\n if (value && !opts.some((o) => o.value === value)) opts.unshift({ value, label: value });\n return [{ value: \"\", label: \"— geen —\" }, ...opts];\n }, [pages, value]);\n return <Select value={value} onValueChange={onChange} options={options} />;\n}\n\n/** Laatste redmiddel voor complexe velden: JSON met schema-validatie vóór commit. */\nfunction JsonField({\n label,\n value,\n onCommit,\n schema,\n}: {\n label: string;\n value: unknown;\n onCommit: (v: unknown) => void;\n schema?: ZodTypeAny;\n}) {\n const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));\n const [error, setError] = useState<string | null>(null);\n const commit = () => {\n try {\n const parsed = JSON.parse(text) as unknown;\n if (schema) {\n const res = schema.safeParse(parsed);\n if (!res.success) {\n setError(\"Voldoet niet aan het verwachte formaat van dit veld.\");\n return;\n }\n setError(null);\n onCommit(res.data);\n return;\n }\n setError(null);\n onCommit(parsed);\n } catch {\n setError(\"Geen geldige JSON.\");\n }\n };\n return (\n <FieldRow label={`${label} (JSON)`} hint=\"Geavanceerd veld — wordt gecontroleerd vóór opslaan.\">\n <Textarea rows={6} value={text} onChange={(e) => setText(e.target.value)} onBlur={commit} spellCheck={false} className=\"font-mono text-xs\" />\n {error && <p className=\"text-xs text-red-600\">{error}</p>}\n </FieldRow>\n );\n}\n\n/**\n * De blok-editor-fallback: leidt de Field-IR af uit het Zod-schema van het blok (+ optionele\n * `fields`-hints van `defineBlock`) en rendert het formulier. Zo is een blok zonder maatwerk-\n * editor nooit een doodlopend pad voor de klant.\n */\nexport function GenericEditor({ block, siteId, onChange }: GenericEditorProps) {\n const registry = useRegistry();\n const def = registry.get(String(block._type ?? \"\"));\n const fields = useMemo(() => {\n if (!def) return [];\n // Custom blocks (5.0) dragen hun opgeslagen Field-IR mee — rijker dan introspectie\n // (showIf/date/slider-metadata gaat door fieldsToZod heen verloren).\n const custom = (def as { customFields?: Field[] }).customFields;\n return custom ?? introspectToFields(def.schema, def.fields);\n }, [def]);\n // Sub-schema's voor json-velden (validatie vóór commit), uit de platte introspectie.\n const jsonSchemas = useMemo(() => {\n if (!def) return {};\n const out: Record<string, ZodTypeAny> = {};\n for (const f of introspectObjectSchema(def.schema)) if (f.kind === \"json\") out[f.key] = f.schema;\n return out;\n }, [def]);\n if (fields.length === 0) {\n return <p className=\"text-sm text-slate-500\">Voor dit bloktype is geen editor beschikbaar.</p>;\n }\n return (\n <FieldsForm\n fields={fields}\n values={block}\n onChange={(p) => onChange({ ...block, ...p })}\n siteId={siteId}\n jsonSchemas={jsonSchemas}\n />\n );\n}\n\n/**\n * Select als eigen component: commit de default-optie bij mount wanneer er nog geen waarde is.\n * De dropdown TOONDE optie 1 maar sloeg 'm niet op (weergave ≠ opslag) — een verplichte enum kon\n * zo leeg gepubliceerd worden tenzij het Zod-schema toevallig een default had.\n */\nfunction SelectField({\n f,\n v,\n set,\n}: {\n f: Extract<Field, { type: \"select\" }>;\n v: unknown;\n set: (val: unknown) => void;\n}) {\n useEffect(() => {\n if (v == null && f.options[0] !== undefined) set(f.options[0].value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return (\n <FieldRow label={f.label} hint={f.hint}>\n <Select\n value={String(v ?? f.options[0]?.value ?? \"\")}\n onValueChange={set}\n options={f.options.map((o) => ({ value: o.value, label: o.label || o.value }))}\n />\n </FieldRow>\n );\n}\n"],"mappings":"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","names":[]}
@@ -41,7 +41,7 @@ function HeroEditor({ block, siteId, onChange }) {
41
41
  ) }),
42
42
  /* @__PURE__ */ jsx(Field, { label: "Achtergrondafbeelding", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
43
43
  /* @__PURE__ */ jsx(Input, { value: block.imageUrl ?? "", onChange: (e) => patch(block, onChange, { imageUrl: e.target.value }), placeholder: "URL of upload \u2192" }),
44
- /* @__PURE__ */ jsx(ImageUploadButton, { siteId, onUploaded: (url, mediaId) => patch(block, onChange, { imageUrl: url, imageId: mediaId }) })
44
+ /* @__PURE__ */ jsx(ImageUploadButton, { siteId, onUploaded: (url) => patch(block, onChange, { imageUrl: url }) })
45
45
  ] }) }),
46
46
  /* @__PURE__ */ jsx(Field, { label: "Video-achtergrond (mp4/webm, optioneel)", hint: "Overschrijft de afbeelding.", children: /* @__PURE__ */ jsx(Input, { value: block.videoUrl ?? "", onChange: (e) => patch(block, onChange, { videoUrl: e.target.value }) }) }),
47
47
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
@@ -101,7 +101,7 @@ function ImageEditor({ block, siteId, onChange }) {
101
101
  return /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
102
102
  /* @__PURE__ */ jsx(Field, { label: "Afbeelding", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
103
103
  /* @__PURE__ */ jsx(Input, { value: block.url ?? "", onChange: (e) => patch(block, onChange, { url: e.target.value }), placeholder: "URL of upload \u2192" }),
104
- /* @__PURE__ */ jsx(ImageUploadButton, { siteId, onUploaded: (url, mediaId) => patch(block, onChange, { url, mediaId }) })
104
+ /* @__PURE__ */ jsx(ImageUploadButton, { siteId, onUploaded: (url) => patch(block, onChange, { url }) })
105
105
  ] }) }),
106
106
  /* @__PURE__ */ jsx(Field, { label: "Alt-tekst (toegankelijkheid)", hint: "Beschrijf de afbeelding kort.", children: /* @__PURE__ */ jsx(Input, { value: block.alt ?? "", onChange: (e) => patch(block, onChange, { alt: e.target.value }) }) }),
107
107
  /* @__PURE__ */ jsx(Field, { label: "Bijschrift", children: /* @__PURE__ */ jsx(Input, { value: block.caption ?? "", onChange: (e) => patch(block, onChange, { caption: e.target.value }) }) }),