@xiwebdesign/xicms-core 4.18.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/XiCMSProvider.d.ts +7 -0
  2. package/dist/XiCMSProvider.js +29 -6
  3. package/dist/XiCMSProvider.js.map +1 -1
  4. package/dist/admin/blockEditors/GenericEditor.d.ts +22 -0
  5. package/dist/admin/blockEditors/GenericEditor.js +201 -0
  6. package/dist/admin/blockEditors/GenericEditor.js.map +1 -0
  7. package/dist/admin/blockEditors/SymbolRefEditor.d.ts +2 -0
  8. package/dist/admin/blockEditors/SymbolRefEditor.js +55 -0
  9. package/dist/admin/blockEditors/SymbolRefEditor.js.map +1 -0
  10. package/dist/admin/blockEditors/index.d.ts +11 -6
  11. package/dist/admin/blockEditors/index.js +4 -608
  12. package/dist/admin/blockEditors/index.js.map +1 -1
  13. package/dist/admin/customBlocks/CustomBlocksPanel.d.ts +3 -0
  14. package/dist/admin/customBlocks/CustomBlocksPanel.js +145 -0
  15. package/dist/admin/customBlocks/CustomBlocksPanel.js.map +1 -0
  16. package/dist/admin/lib/symbolsClient.d.ts +13 -0
  17. package/dist/admin/lib/symbolsClient.js +51 -0
  18. package/dist/admin/lib/symbolsClient.js.map +1 -0
  19. package/dist/admin/lib/zodFields.d.ts +18 -0
  20. package/dist/admin/lib/zodFields.js +65 -2
  21. package/dist/admin/lib/zodFields.js.map +1 -1
  22. package/dist/admin/panelRegistry.js +3 -0
  23. package/dist/admin/panelRegistry.js.map +1 -1
  24. package/dist/admin/settings/SettingsPanel.js +2 -0
  25. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  26. package/dist/admin/settings/SnapshotsCard.d.ts +3 -0
  27. package/dist/admin/settings/SnapshotsCard.js +98 -0
  28. package/dist/admin/settings/SnapshotsCard.js.map +1 -0
  29. package/dist/admin/visual/TranslationPanel.d.ts +13 -0
  30. package/dist/admin/visual/TranslationPanel.js +73 -0
  31. package/dist/admin/visual/TranslationPanel.js.map +1 -0
  32. package/dist/admin/visual/VisualEditor.js +142 -17
  33. package/dist/admin/visual/VisualEditor.js.map +1 -1
  34. package/dist/admin.css +1 -1
  35. package/dist/agentFiles/catalog.d.ts +21 -0
  36. package/dist/agentFiles/catalog.js +78 -0
  37. package/dist/agentFiles/catalog.js.map +1 -0
  38. package/dist/agentFiles/files.d.ts +59 -0
  39. package/dist/agentFiles/files.js +109 -0
  40. package/dist/agentFiles/files.js.map +1 -0
  41. package/dist/blocks/builtins/BeforeAfter.js +1 -0
  42. package/dist/blocks/builtins/BeforeAfter.js.map +1 -1
  43. package/dist/blocks/builtins/Cards.js +1 -0
  44. package/dist/blocks/builtins/Cards.js.map +1 -1
  45. package/dist/blocks/builtins/Cta.js +1 -0
  46. package/dist/blocks/builtins/Cta.js.map +1 -1
  47. package/dist/blocks/builtins/Elements.js +1 -0
  48. package/dist/blocks/builtins/Elements.js.map +1 -1
  49. package/dist/blocks/builtins/Hero.js +2 -2
  50. package/dist/blocks/builtins/Hero.js.map +1 -1
  51. package/dist/blocks/builtins/Jobs.js +1 -0
  52. package/dist/blocks/builtins/Jobs.js.map +1 -1
  53. package/dist/blocks/builtins/Sections.js +1 -0
  54. package/dist/blocks/builtins/Sections.js.map +1 -1
  55. package/dist/blocks/builtins/SymbolRef.d.ts +2 -0
  56. package/dist/blocks/builtins/SymbolRef.js +61 -0
  57. package/dist/blocks/builtins/SymbolRef.js.map +1 -0
  58. package/dist/blocks/customTemplate/definition.d.ts +7 -0
  59. package/dist/blocks/customTemplate/definition.js +34 -0
  60. package/dist/blocks/customTemplate/definition.js.map +1 -0
  61. package/dist/blocks/customTemplate/interpret.d.ts +10 -0
  62. package/dist/blocks/customTemplate/interpret.js +136 -0
  63. package/dist/blocks/customTemplate/interpret.js.map +1 -0
  64. package/dist/blocks/registry.d.ts +8 -0
  65. package/dist/blocks/registry.js.map +1 -1
  66. package/dist/i18n/overlay.d.ts +61 -0
  67. package/dist/i18n/overlay.js +90 -0
  68. package/dist/i18n/overlay.js.map +1 -0
  69. package/dist/index.d.ts +10 -8
  70. package/dist/index.js +32 -9
  71. package/dist/index.js.map +1 -1
  72. package/dist/modules/builtins.js +3 -2
  73. package/dist/modules/builtins.js.map +1 -1
  74. package/dist/schema/blocks.d.ts +159 -0
  75. package/dist/schema/blocks.js +8 -0
  76. package/dist/schema/blocks.js.map +1 -1
  77. package/dist/schema/customBlocks.d.ts +47 -0
  78. package/dist/schema/customBlocks.js +32 -0
  79. package/dist/schema/customBlocks.js.map +1 -0
  80. package/dist/schema/fields.d.ts +122 -0
  81. package/dist/schema/fields.js +120 -0
  82. package/dist/schema/fields.js.map +1 -0
  83. package/dist/schema/forms.d.ts +6 -6
  84. package/dist/schema/pages.d.ts +15 -1
  85. package/dist/schema/pages.js +6 -2
  86. package/dist/schema/pages.js.map +1 -1
  87. package/dist/schema/siteConfig.d.ts +14 -8
  88. package/dist/schema/siteConfig.js +2 -1
  89. package/dist/schema/siteConfig.js.map +1 -1
  90. package/dist/schema/symbols.d.ts +205 -0
  91. package/dist/schema/symbols.js +18 -0
  92. package/dist/schema/symbols.js.map +1 -0
  93. package/dist/server/index.d.ts +2 -1
  94. package/dist/server/index.js +24 -0
  95. package/dist/server/index.js.map +1 -1
  96. package/dist/server/pages.d.ts +21 -0
  97. package/dist/server/pages.js +77 -0
  98. package/dist/server/pages.js.map +1 -1
  99. package/dist/server/seo.d.ts +10 -3
  100. package/dist/server/seo.js +22 -10
  101. package/dist/server/seo.js.map +1 -1
  102. package/dist/styles.css +27 -0
  103. package/package.json +1 -1
  104. package/dist/components/EditableImage.d.ts +0 -18
  105. package/dist/components/EditableImage.js +0 -62
  106. package/dist/components/EditableImage.js.map +0 -1
  107. package/dist/components/EditableRichText.d.ts +0 -14
  108. package/dist/components/EditableRichText.js +0 -26
  109. package/dist/components/EditableRichText.js.map +0 -1
  110. package/dist/components/EditableText.d.ts +0 -21
  111. package/dist/components/EditableText.js +0 -25
  112. package/dist/components/EditableText.js.map +0 -1
  113. package/dist/content/ContentProvider.d.ts +0 -23
  114. package/dist/content/ContentProvider.js +0 -86
  115. package/dist/content/ContentProvider.js.map +0 -1
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { type BlockDefinition } from "./blocks/registry";
3
+ import { type CustomBlock as CustomBlockData } from "./schema/customBlocks";
3
4
  import { type BlockOverrides } from "./modules/overrides";
4
5
  import type { ModuleDefinition } from "./modules/types";
5
6
  import { type DefinedTheme } from "@astryxdesign/core/theme";
@@ -20,6 +21,12 @@ interface ProviderProps {
20
21
  * alleen het uiterlijk; schema/editor/logica blijven van core. De krachtigste maatwerk-naad.
21
22
  */
22
23
  overrides?: BlockOverrides;
24
+ /**
25
+ * SSR-startset van custom blocks (5.0): geef het resultaat van `getSiteCustomBlocks(siteId)`
26
+ * (server) door zodat runtime-gedefinieerde blokken al in de server-HTML zitten. Zonder deze
27
+ * prop laadt de provider ze alsnog client-side (werkend, maar pas na hydratie zichtbaar).
28
+ */
29
+ customBlocksData?: CustomBlockData[];
23
30
  /**
24
31
  * Astryx-basistheme voor de hele site (radius-/spacing-/neutralen-karakter). Standaard `neutral`.
25
32
  * Een site kan een ander pakket kiezen (`@astryxdesign/theme-butter`, `-stone`, …) of een eigen
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useMemo } from "react";
3
+ import { useEffect, useMemo, useState } from "react";
4
4
  import { ConfigProvider } from "./config";
5
5
  import { AuthProvider } from "./auth/AuthProvider";
6
- import { ContentProvider } from "./content/ContentProvider";
7
6
  import { EditBar } from "./components/EditBar";
8
7
  import { DemoLock } from "./lock/DemoLock";
9
8
  import { RegistryProvider } from "./blocks/context";
9
+ import { getDb } from "./firebase/client";
10
+ import { collection, onSnapshot } from "firebase/firestore";
10
11
  import { createRegistry, mergeRegistry } from "./blocks/registry";
12
+ import { CustomBlockSchema } from "./schema/customBlocks";
13
+ import { customBlockToDefinition } from "./blocks/customTemplate/definition";
11
14
  import { resolveModules } from "./modules/registry";
12
15
  import { ModulesProvider } from "./modules/context";
13
16
  import { applyBlockOverrides } from "./modules/overrides";
@@ -120,6 +123,7 @@ function ModuleLayer({
120
123
  blocks = NO_BLOCKS,
121
124
  modules = NO_MODULES,
122
125
  overrides,
126
+ customBlocksData,
123
127
  astryxTheme,
124
128
  linkComponent,
125
129
  imageComponent,
@@ -137,11 +141,30 @@ function ModuleLayer({
137
141
  }),
138
142
  [config.modules, config.features, modules, settings.modules]
139
143
  );
144
+ const [customBlocks, setCustomBlocks] = useState(customBlocksData ?? []);
145
+ useEffect(() => {
146
+ const db = getDb();
147
+ if (!db) return;
148
+ return onSnapshot(
149
+ collection(db, "sites", config.siteId, "customBlocks"),
150
+ (snap) => {
151
+ const defs = [];
152
+ snap.forEach((d) => {
153
+ const res = CustomBlockSchema.safeParse({ ...d.data(), type: d.data().type ?? d.id });
154
+ if (res.success) defs.push(res.data);
155
+ });
156
+ setCustomBlocks(defs);
157
+ },
158
+ () => void 0
159
+ // leesfout → SSR-set blijft staan
160
+ );
161
+ }, [config.siteId]);
162
+ const customDefs = useMemo(() => customBlocks.map(customBlockToDefinition), [customBlocks]);
140
163
  const registry = useMemo(
141
- () => applyBlockOverrides(mergeRegistry(createRegistry(resolved.blocks), blocks), overrides),
142
- [resolved.blocks, blocks, overrides]
164
+ () => applyBlockOverrides(mergeRegistry(mergeRegistry(createRegistry(resolved.blocks), customDefs), blocks), overrides),
165
+ [resolved.blocks, customDefs, blocks, overrides]
143
166
  );
144
- return /* @__PURE__ */ jsx(ModulesProvider, { value: resolved, children: /* @__PURE__ */ jsx(RegistryProvider, { registry, children: /* @__PURE__ */ jsx(AuthProvider, { siteId: config.siteId, demo: Boolean(config.demoEditing), children: /* @__PURE__ */ jsx(ContentProvider, { siteId: config.siteId, defaults: config.defaults, children: /* @__PURE__ */ jsxs(EditorProvider, { children: [
167
+ return /* @__PURE__ */ jsx(ModulesProvider, { value: resolved, children: /* @__PURE__ */ jsx(RegistryProvider, { registry, children: /* @__PURE__ */ jsx(AuthProvider, { siteId: config.siteId, demo: Boolean(config.demoEditing), children: /* @__PURE__ */ jsxs(EditorProvider, { children: [
145
168
  theme.fontUrl && /* @__PURE__ */ jsx("link", { rel: "stylesheet", href: theme.fontUrl }),
146
169
  /* @__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(
147
170
  "div",
@@ -158,7 +181,7 @@ function ModuleLayer({
158
181
  ]
159
182
  }
160
183
  ) }) }) })
161
- ] }) }) }) }) });
184
+ ] }) }) }) });
162
185
  }
163
186
  export {
164
187
  XiCMSProvider,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/XiCMSProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, type ReactNode } from \"react\";\nimport { ConfigProvider } from \"./config\";\nimport { AuthProvider } from \"./auth/AuthProvider\";\nimport { ContentProvider } from \"./content/ContentProvider\";\nimport { EditBar } from \"./components/EditBar\";\nimport { DemoLock } from \"./lock/DemoLock\";\nimport { RegistryProvider } from \"./blocks/context\";\nimport { createRegistry, mergeRegistry, type BlockDefinition } from \"./blocks/registry\";\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 * 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, 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}>\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 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 setDemoActive(Boolean(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 const registry = useMemo(\n () => applyBlockOverrides(mergeRegistry(createRegistry(resolved.blocks), blocks), overrides),\n [resolved.blocks, blocks, overrides],\n );\n\n return (\n <ModulesProvider value={resolved}>\n <RegistryProvider registry={registry}>\n <AuthProvider siteId={config.siteId} demo={Boolean(config.demoEditing)}>\n <ContentProvider siteId={config.siteId} defaults={config.defaults}>\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 </ContentProvider>\n </AuthProvider>\n </RegistryProvider>\n </ModulesProvider>\n );\n}\n"],"mappings":";AA6GQ,cA0JU,YA1JV;AA3GR,SAAS,WAAW,eAA+B;AACnD,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AACjC,SAAS,gBAAgB,qBAA2C;AACpE,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,2BAAgD;AACzD,SAAS,sBAAsB;AAE/B,SAAS,qBAA2C;AACpD,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB,uBAAuB;AACtD,SAAS,wBAA0C;AACnD,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,aAAgC;AACzC,SAAS,oBAAoB;AAC7B,SAAS,oBAA4C;AACrD,SAAS,qBAA8C;AACvD,SAAS,0BAA2C;AACpD,SAAS,8BAA8B;AAsCvC,MAAM,YAA+B,CAAC;AACtC,MAAM,aAAiC,CAAC;AAIxC,MAAM,SAAS,oBAAI,IAAY;AAC/B,SAAS,SAAS,SAAiB;AACjC,MAAI,OAAO,IAAI,OAAO,EAAG;AACzB,SAAO,IAAI,OAAO;AAClB,UAAQ,KAAK,OAAO;AACtB;AAUO,SAAS,cAAc,EAAE,QAAQ,SAAS,WAAW,UAAU,YAAY,WAAW,aAAa,eAAe,gBAAgB,SAAS,GAAkB;AAClK,YAAU,MAAM;AACd,iBAAa;AAAA,EACf,GAAG,CAAC,CAAC;AAIL,YAAU,MAAM;AACd,QAAI,QAAQ,IAAI,aAAa,aAAc;AAC3C,UAAM,eAAe,mBAAmB,MAAM;AAC9C,QAAI,aAAa,SAAS,GAAG;AAC3B,eAAS;AAAA,IAA8C,aAAa,KAAK,MAAM,CAAC,EAAE;AAAA,IACpF;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,YAAM,iBAAiB,uBAAuB,OAAO;AACrD,UAAI,eAAe,SAAS,GAAG;AAC7B,iBAAS;AAAA,IAAkE,eAAe,KAAK,MAAM,CAAC,EAAE;AAAA,MAC1G;AAAA,IACF;AAAA,EACF,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,SACE,oBAAC,kBAAe,QACd,8BAAC,wBAAqB,QAAQ,OAAO,QACnC,8BAAC,eAAY,QAAgB,QAAgB,SAAkB,WAAsB,aAA0B,eAA8B,gBAC1I,UACH,GACF,GACF;AAEJ;AAOA,SAAS,aAAa,OAA4C;AAKhE,QAAM,OAAO,QAAQ,MAAM,MAAM,IAAI;AACrC,QAAM,KAAK,CAAC,OAAe,SAAkB,OAAO,cAAc,KAAK,KAAK,IAAI,MAAM;AACtF,QAAM,UAAkC;AAAA,IACtC,mBAAmB,MAAM,MAAM,UAAU,GAAG,MAAM,SAAS,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,IACvF,kBAAkB,MAAM,UAAU,MAAM;AAAA,IACxC,cAAc,GAAG,MAAM,cAAc,WAAW,MAAM,MAAM,cAAc,SAAS;AAAA,IACnF,cAAc,GAAG,MAAM,cAAc,WAAW,MAAM,MAAM,cAAc,SAAS;AAAA,EACrF;AACA,MAAI,MAAM,WAAY,SAAQ,cAAc,IAAI,MAAM;AAEtD,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,EAAG,KAAI,EAAG,SAAQ,mBAAmB,CAAC,EAAE,IAAI;AACnG,MAAI,MAAM,QAAQ,GAAI,SAAQ,mBAAmB,IAAI,MAAM,OAAO;AAClE,MAAI,MAAM,QAAQ,GAAI,SAAQ,gBAAgB,IAAI,MAAM,OAAO;AAC/D,MAAI,MAAM,QAAQ,GAAI,SAAQ,mBAAmB,IAAI,MAAM,OAAO;AAClE,MAAI,MAAM,WAAW,SAAU,SAAQ,uBAAuB,IAAI,MAAM,UAAU;AAClF,MAAI,MAAM,WAAW,QAAS,SAAQ,uBAAuB,IAAI,MAAM,UAAU;AACjF,MAAI,MAAM,MAAM,WAAY,SAAQ,iBAAiB,IAAI,MAAM,KAAK;AACpE,MAAI,MAAM,MAAM,WAAY,SAAQ,iBAAiB,IAAI,MAAM,KAAK;AACpE,MAAI,MAAM,MAAM,QAAS,SAAQ,sBAAsB,IAAI,MAAM,KAAK;AAItE,UAAQ,gBAAgB,IAAI;AAG5B,QAAM,WAAW,YAAY,MAAM,OAAO,IAAI,YAAY;AAC1D,UAAQ,mBAAmB,IAAI;AAC/B,UAAQ,oBAAoB,IAAI;AAChC,UAAQ,qBAAqB,IAAI;AACjC,UAAQ,kBAAkB,IAAI;AAC9B,UAAQ,oBAAoB,IAAI;AAChC,UAAQ,eAAe,IAAI;AAI3B,MAAI,MAAM,YAAY;AACpB,YAAQ,yBAAyB,IAAI;AACrC,YAAQ,4BAA4B,IAAI;AACxC,YAAQ,yBAAyB,IAAI;AACrC,YAAQ,4BAA4B,IAAI;AACxC,YAAQ,0BAA0B,IAAI;AAItC,YAAQ,iBAAiB,IAAI;AAC7B,YAAQ,eAAe,IAAI;AAAA,EAC7B;AACA,MAAI,MAAM,YAAY;AACpB,YAAQ,sBAAsB,IAAI;AAClC,YAAQ,wBAAwB,IAAI;AACpC,YAAQ,iBAAiB,IAAI;AAC7B,YAAQ,gBAAgB,IAAI;AAC5B,YAAQ,gBAAgB,IAAI;AAC5B,YAAQ,uBAAuB,IAAI;AAAA,EACrC;AACA,MAAI,MAAM,YAAa,SAAQ,sBAAsB,IAAI,MAAM;AAC/D,MAAI,MAAM,YAAY;AACpB,YAAQ,oBAAoB,IAAI;AAEhC,YAAQ,uBAAuB,IAAI,MAAM,cAAc,8BAA8B;AAAA,EACvF,WAAW,MAAM,aAAa;AAC5B,YAAQ,uBAAuB,IAAI;AAAA,EACrC;AAEA,SAAO;AACT;AAQO,SAAS,YAAY,OAAyB;AACnD,QAAM,IAAI,gCAAgC,KAAK,OAAO,KAAK,KAAK,EAAE;AAClE,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,MAAM,EAAE,CAAC;AACb,MAAI,IAAI,WAAW,EAAG,OAAM,IAAI,QAAQ,MAAM,CAAC,MAAM,IAAI,CAAC;AAC1D,QAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG;AAC9E,SAAO,SAAS,IAAI,SAAS,IAAI,SAAS,IAAI;AAChD;AAOA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkB;AAChB,QAAM,EAAE,SAAS,IAAI,gBAAgB;AAGrC,QAAM,QAAQ,QAAQ,MAAM,iBAAiB,OAAO,OAAO,SAAS,KAAK,GAAG,CAAC,OAAO,OAAO,SAAS,KAAK,CAAC;AAC1G,QAAM,UAAU,QAAQ,MAAM,aAAa,KAAK,GAAG,CAAC,KAAK,CAAC;AAG1D,gBAAc,QAAQ,OAAO,WAAW,CAAC;AAEzC,QAAM,WAAW;AAAA,IACf,MACE,eAAe,CAAC,GAAG,gBAAgB,GAAG,OAAO,GAAG;AAAA,MAC9C,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MACjB,kBAAkB,SAAS;AAAA,IAC7B,CAAC;AAAA,IACH,CAAC,OAAO,SAAS,OAAO,UAAU,SAAS,SAAS,OAAO;AAAA,EAC7D;AAEA,QAAM,WAAW;AAAA,IACf,MAAM,oBAAoB,cAAc,eAAe,SAAS,MAAM,GAAG,MAAM,GAAG,SAAS;AAAA,IAC3F,CAAC,SAAS,QAAQ,QAAQ,SAAS;AAAA,EACrC;AAEA,SACE,oBAAC,mBAAgB,OAAO,UACtB,8BAAC,oBAAiB,UAChB,8BAAC,gBAAa,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO,WAAW,GACnE,8BAAC,mBAAgB,QAAQ,OAAO,QAAQ,UAAU,OAAO,UACvD,+BAAC,kBAEE;AAAA,UAAM,WAAW,oBAAC,UAAK,KAAI,cAAa,MAAM,MAAM,SAAS;AAAA,IAG9D,oBAAC,SAAM,OAAO,eAAe,cAAc,MAAM,MAAM,MAAM,OAAO,WAAW,YAAY,MAAM,UAAU,IAAI,SAAS,SAGtH,8BAAC,gBAAa,WAAW,iBAAiB,KACxC,8BAAC,iBAAc,WAAW,gBAC1B;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,aAAW,MAAM,MAAM,OAAO,SAAS;AAAA,QACvC,OAAO;AAAA,QAEP;AAAA,8BAAC,cAAW;AAAA,UACX;AAAA,UACD,oBAAC,WAAQ;AAAA,UACT,oBAAC,YAAS;AAAA,UACT,SAAS,UAAU,IAAI,SAAS,KAC/B,oBAAC,iBAAc,UAAU,SAAS,SAAS,SAA4B;AAAA;AAAA;AAAA,IAE3E,GACA,GACF,GACF;AAAA,KACF,GACF,GACF,GACF,GACF;AAEJ;","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 } from \"./auth/AuthProvider\";\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, 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}>\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 setDemoActive(Boolean(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 // SSR-startset via de prop; daarna live gesynchroniseerd (nieuw blok in /beheer of via een\n // agent-push verschijnt zonder redeploy in het toevoeg-menu en op de pagina).\n const [customBlocks, setCustomBlocks] = useState<CustomBlockData[]>(customBlocksData ?? []);\n useEffect(() => {\n const db = getDb();\n if (!db) return;\n return onSnapshot(\n collection(db, \"sites\", config.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 setCustomBlocks(defs);\n },\n () => undefined, // leesfout → SSR-set blijft staan\n );\n }, [config.siteId]);\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 <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"],"mappings":"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","names":[]}
@@ -0,0 +1,22 @@
1
+ import type { ZodTypeAny } from "zod";
2
+ import { type Field } from "../../schema/fields";
3
+ export interface GenericEditorProps {
4
+ block: Record<string, unknown>;
5
+ siteId: string;
6
+ onChange: (next: Record<string, unknown>) => void;
7
+ }
8
+ /** Formulier voor een Field-IR over een waarden-object. Recursief (groepen/repeaters). */
9
+ export declare function FieldsForm({ fields, values, onChange, siteId, jsonSchemas, }: {
10
+ fields: Field[];
11
+ values: Record<string, unknown>;
12
+ onChange: (patch: Record<string, unknown>) => void;
13
+ siteId: string;
14
+ /** Optioneel: sub-schema per json-veldsleutel voor validatie vóór commit. */
15
+ jsonSchemas?: Record<string, ZodTypeAny>;
16
+ }): import("react").JSX.Element;
17
+ /**
18
+ * De blok-editor-fallback: leidt de Field-IR af uit het Zod-schema van het blok (+ optionele
19
+ * `fields`-hints van `defineBlock`) en rendert het formulier. Zo is een blok zonder maatwerk-
20
+ * editor nooit een doodlopend pad voor de klant.
21
+ */
22
+ export declare function GenericEditor({ block, siteId, onChange }: GenericEditorProps): import("react").JSX.Element;
@@ -0,0 +1,201 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { Field as FieldRow, Input, Textarea } from "../ui/primitives";
5
+ import { Select } from "../ui/select";
6
+ import { Switch } from "../ui/switch";
7
+ import { ArrayEditor } from "../ui/ArrayEditor";
8
+ import { RichTextEditor } from "../editors/RichTextEditor";
9
+ import { MediaPickerButton } from "../media/MediaPicker";
10
+ import { useRegistry } from "../../blocks/context";
11
+ import { subscribePages } from "../lib/pagesClient";
12
+ import { introspectToFields, introspectObjectSchema } from "../lib/zodFields";
13
+ import { fieldsDefaults, isFieldVisible } from "../../schema/fields";
14
+ function FieldsForm({
15
+ fields,
16
+ values,
17
+ onChange,
18
+ siteId,
19
+ jsonSchemas
20
+ }) {
21
+ return /* @__PURE__ */ jsx("div", { className: "grid gap-3", children: fields.filter((f) => isFieldVisible(f, values)).map((f) => /* @__PURE__ */ jsx(FieldWidget, { f, values, onChange, siteId, jsonSchema: jsonSchemas?.[f.key] }, f.key)) });
22
+ }
23
+ function FieldWidget({
24
+ f,
25
+ values,
26
+ onChange,
27
+ siteId,
28
+ jsonSchema
29
+ }) {
30
+ const v = values[f.key];
31
+ const set = (val) => onChange({ [f.key]: val });
32
+ switch (f.type) {
33
+ case "switch":
34
+ return /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
35
+ /* @__PURE__ */ jsx(Switch, { checked: Boolean(v), onCheckedChange: set }),
36
+ f.label
37
+ ] });
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
+ ) });
47
+ case "number":
48
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(
49
+ Input,
50
+ {
51
+ type: "number",
52
+ min: f.min,
53
+ max: f.max,
54
+ step: f.step,
55
+ value: v == null ? "" : String(v),
56
+ onChange: (e) => set(e.target.value === "" ? void 0 : Number(e.target.value))
57
+ }
58
+ ) });
59
+ case "slider": {
60
+ const val = typeof v === "number" ? v : f.min;
61
+ return /* @__PURE__ */ jsx(FieldRow, { label: `${f.label} (${val})`, hint: f.hint, children: /* @__PURE__ */ jsx(
62
+ "input",
63
+ {
64
+ type: "range",
65
+ min: f.min,
66
+ max: f.max,
67
+ step: f.step ?? 1,
68
+ value: val,
69
+ onChange: (e) => set(Number(e.target.value)),
70
+ className: "w-full"
71
+ }
72
+ ) });
73
+ }
74
+ case "date":
75
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(Input, { type: "date", value: String(v ?? ""), onChange: (e) => set(e.target.value) }) });
76
+ case "richtext":
77
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(RichTextEditor, { siteId, value: String(v ?? ""), onChange: set }) });
78
+ case "image":
79
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
80
+ /* @__PURE__ */ jsx(Input, { value: String(v ?? ""), onChange: (e) => set(e.target.value), placeholder: "URL of kies \u2192" }),
81
+ /* @__PURE__ */ jsx(MediaPickerButton, { siteId, onSelect: (m) => set(m.url) })
82
+ ] }) });
83
+ case "url":
84
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(Input, { value: String(v ?? ""), onChange: (e) => set(e.target.value), placeholder: "https://\u2026 of /pagina" }) });
85
+ case "icon":
86
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint ?? "Emoji, bv. \u2702\uFE0F", children: /* @__PURE__ */ jsx(Input, { value: String(v ?? ""), maxLength: 16, onChange: (e) => set(e.target.value), className: "w-24" }) });
87
+ case "link": {
88
+ const link = v ?? {};
89
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
90
+ /* @__PURE__ */ jsx(Input, { value: link.label ?? "", placeholder: "Tekst", onChange: (e) => set({ ...link, label: e.target.value }) }),
91
+ /* @__PURE__ */ jsx(Input, { value: link.href ?? "", placeholder: "/pagina of https://\u2026", onChange: (e) => set({ ...link, href: e.target.value }) })
92
+ ] }) });
93
+ }
94
+ case "group": {
95
+ const group = v ?? {};
96
+ return /* @__PURE__ */ jsxs("fieldset", { className: "grid gap-2 rounded-lg border border-slate-200 p-3", children: [
97
+ /* @__PURE__ */ jsx("legend", { className: "px-1 text-xs font-semibold text-slate-500", children: f.label }),
98
+ /* @__PURE__ */ jsx(FieldsForm, { fields: f.fields, values: group, onChange: (p) => set({ ...group, ...p }), siteId })
99
+ ] });
100
+ }
101
+ case "repeater": {
102
+ const items = Array.isArray(v) ? v : [];
103
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(
104
+ ArrayEditor,
105
+ {
106
+ items,
107
+ onChange: set,
108
+ newItem: () => fieldsDefaults(f.fields),
109
+ addLabel: `+ ${f.itemLabel ?? "Item"}`,
110
+ renderRow: (item, update) => /* @__PURE__ */ jsx(FieldsForm, { fields: f.fields, values: item, onChange: (p) => update(p), siteId })
111
+ }
112
+ ) });
113
+ }
114
+ case "pageRef":
115
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(PageRefPicker, { siteId, value: String(v ?? ""), onChange: set }) });
116
+ case "info":
117
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-slate-500", children: f.text });
118
+ case "textarea":
119
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(Textarea, { rows: f.rows ?? 4, value: String(v ?? ""), onChange: (e) => set(e.target.value) }) });
120
+ case "json":
121
+ return /* @__PURE__ */ jsx(JsonField, { label: f.label, value: v, onCommit: set, schema: jsonSchema });
122
+ case "text":
123
+ default:
124
+ return /* @__PURE__ */ jsx(FieldRow, { label: f.label, hint: f.hint, children: /* @__PURE__ */ jsx(Input, { value: String(v ?? ""), placeholder: f.type === "text" ? f.placeholder : void 0, onChange: (e) => set(e.target.value) }) });
125
+ }
126
+ }
127
+ function PageRefPicker({ siteId, value, onChange }) {
128
+ const [pages, setPages] = useState([]);
129
+ useEffect(() => subscribePages(siteId, setPages), [siteId]);
130
+ const options = useMemo(() => {
131
+ const opts = pages.slice().sort((a, b) => a.path < b.path ? -1 : 1).map((p) => ({ value: p.path, label: `${p.title || p.path} (${p.path})` }));
132
+ if (value && !opts.some((o) => o.value === value)) opts.unshift({ value, label: value });
133
+ return [{ value: "", label: "\u2014 geen \u2014" }, ...opts];
134
+ }, [pages, value]);
135
+ return /* @__PURE__ */ jsx(Select, { value, onValueChange: onChange, options });
136
+ }
137
+ function JsonField({
138
+ label,
139
+ value,
140
+ onCommit,
141
+ schema
142
+ }) {
143
+ const [text, setText] = useState(() => JSON.stringify(value ?? null, null, 2));
144
+ const [error, setError] = useState(null);
145
+ const commit = () => {
146
+ try {
147
+ const parsed = JSON.parse(text);
148
+ if (schema) {
149
+ const res = schema.safeParse(parsed);
150
+ if (!res.success) {
151
+ setError("Voldoet niet aan het verwachte formaat van dit veld.");
152
+ return;
153
+ }
154
+ setError(null);
155
+ onCommit(res.data);
156
+ return;
157
+ }
158
+ setError(null);
159
+ onCommit(parsed);
160
+ } catch {
161
+ setError("Geen geldige JSON.");
162
+ }
163
+ };
164
+ return /* @__PURE__ */ jsxs(FieldRow, { label: `${label} (JSON)`, hint: "Geavanceerd veld \u2014 wordt gecontroleerd v\xF3\xF3r opslaan.", children: [
165
+ /* @__PURE__ */ jsx(Textarea, { rows: 6, value: text, onChange: (e) => setText(e.target.value), onBlur: commit, spellCheck: false, className: "font-mono text-xs" }),
166
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: error })
167
+ ] });
168
+ }
169
+ function GenericEditor({ block, siteId, onChange }) {
170
+ const registry = useRegistry();
171
+ const def = registry.get(String(block._type ?? ""));
172
+ const fields = useMemo(() => {
173
+ if (!def) return [];
174
+ const custom = def.customFields;
175
+ return custom ?? introspectToFields(def.schema, def.fields);
176
+ }, [def]);
177
+ const jsonSchemas = useMemo(() => {
178
+ if (!def) return {};
179
+ const out = {};
180
+ for (const f of introspectObjectSchema(def.schema)) if (f.kind === "json") out[f.key] = f.schema;
181
+ return out;
182
+ }, [def]);
183
+ if (fields.length === 0) {
184
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-slate-500", children: "Voor dit bloktype is geen editor beschikbaar." });
185
+ }
186
+ return /* @__PURE__ */ jsx(
187
+ FieldsForm,
188
+ {
189
+ fields,
190
+ values: block,
191
+ onChange: (p) => onChange({ ...block, ...p }),
192
+ siteId,
193
+ jsonSchemas
194
+ }
195
+ );
196
+ }
197
+ export {
198
+ FieldsForm,
199
+ GenericEditor
200
+ };
201
+ //# sourceMappingURL=GenericEditor.js.map
@@ -0,0 +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":[]}
@@ -0,0 +1,2 @@
1
+ import type { BlockEditorProps } from "./index";
2
+ export declare function SymbolRefEditor({ block, siteId, onChange }: BlockEditorProps): import("react").JSX.Element;
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { Button, Field, Input } from "../ui/primitives";
5
+ import { Select } from "../ui/select";
6
+ import { useToast } from "../ui/toast";
7
+ import { subscribeSymbols, saveSymbol } from "../lib/symbolsClient";
8
+ import { NestedBlocksEditor } from "./index";
9
+ function SymbolRefEditor({ block, siteId, onChange }) {
10
+ const { toast } = useToast();
11
+ const [symbols, setSymbols] = useState([]);
12
+ const [newName, setNewName] = useState("");
13
+ const [draftBlocks, setDraftBlocks] = useState(null);
14
+ const [saving, setSaving] = useState(false);
15
+ const symbolId = String(block.symbolId ?? "");
16
+ useEffect(() => subscribeSymbols(siteId, setSymbols), [siteId]);
17
+ const current = useMemo(() => symbols.find((s) => s.id === symbolId), [symbols, symbolId]);
18
+ useEffect(() => {
19
+ setDraftBlocks(current ? current.blocks : null);
20
+ }, [current?.id]);
21
+ async function persist(name, blocks, id) {
22
+ setSaving(true);
23
+ try {
24
+ const res = await saveSymbol({ siteId, symbolId: id, name, blocks });
25
+ if (!id) onChange({ ...block, symbolId: res.id });
26
+ toast({ title: "Symbool opgeslagen", description: "Alle pagina's met dit symbool zijn bijgewerkt.", tone: "success" });
27
+ } catch (e) {
28
+ toast({ title: "Opslaan mislukt", description: e.message, tone: "error" });
29
+ } finally {
30
+ setSaving(false);
31
+ }
32
+ }
33
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-3", children: [
34
+ /* @__PURE__ */ jsx(Field, { label: "Symbool", hint: "Een gedeelde sectie: wijzigingen gelden op \xE9lke pagina die dit symbool gebruikt.", children: /* @__PURE__ */ jsx(
35
+ Select,
36
+ {
37
+ value: symbolId,
38
+ onValueChange: (v) => onChange({ ...block, symbolId: v }),
39
+ options: [{ value: "", label: "\u2014 kies een symbool \u2014" }, ...symbols.map((s) => ({ value: s.id, label: s.name }))]
40
+ }
41
+ ) }),
42
+ !symbolId && /* @__PURE__ */ jsx(Field, { label: "Of maak een nieuw symbool", hint: "Begint leeg; voeg daarna blokken toe.", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
43
+ /* @__PURE__ */ jsx(Input, { value: newName, placeholder: "Naam (bv. Afspraak-CTA)", onChange: (e) => setNewName(e.target.value) }),
44
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: !newName.trim() || saving, onClick: () => persist(newName.trim(), []), children: "Maak" })
45
+ ] }) }),
46
+ current && draftBlocks && /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ /* @__PURE__ */ jsx(Field, { label: `Inhoud van "${current.name}"`, children: /* @__PURE__ */ jsx(NestedBlocksEditor, { blocks: draftBlocks, siteId, onChange: setDraftBlocks, addLabel: "+ Blok in symbool\u2026" }) }),
48
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", disabled: saving, onClick: () => persist(current.name, draftBlocks, current.id), children: saving ? "Opslaan\u2026" : "Symbool opslaan (overal bijwerken)" }) })
49
+ ] })
50
+ ] });
51
+ }
52
+ export {
53
+ SymbolRefEditor
54
+ };
55
+ //# sourceMappingURL=SymbolRefEditor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/admin/blockEditors/SymbolRefEditor.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Editor voor het symbolRef-blok (5.0): kies een gedeeld symbool, of maak er een nieuwe aan,\n * en bewerk de INHOUD van het symbool direct in het zijpaneel (NestedBlocksEditor — dezelfde\n * geneste blok-editor als kolommen/zijbalk). Opslaan gaat via de saveSymbol-callable en\n * propageert naar élke pagina die het symbool gebruikt; de melding maakt dat expliciet.\n * Loskoppelen (kloon i.p.v. referentie) zit als actie op het blok in de VisualEditor.\n */\nimport { useEffect, useMemo, useState } from \"react\";\nimport { Button, Field, Input } from \"../ui/primitives\";\nimport { Select } from \"../ui/select\";\nimport { useToast } from \"../ui/toast\";\nimport { subscribeSymbols, saveSymbol } from \"../lib/symbolsClient\";\nimport type { StoredSymbol } from \"../../schema/symbols\";\nimport type { BlockEditorProps } from \"./index\";\nimport { NestedBlocksEditor } from \"./index\";\n\nexport function SymbolRefEditor({ block, siteId, onChange }: BlockEditorProps) {\n const { toast } = useToast();\n const [symbols, setSymbols] = useState<StoredSymbol[]>([]);\n const [newName, setNewName] = useState(\"\");\n const [draftBlocks, setDraftBlocks] = useState<Record<string, unknown>[] | null>(null);\n const [saving, setSaving] = useState(false);\n const symbolId = String(block.symbolId ?? \"\");\n\n useEffect(() => subscribeSymbols(siteId, setSymbols), [siteId]);\n const current = useMemo(() => symbols.find((s) => s.id === symbolId), [symbols, symbolId]);\n\n // Bewerk-buffer volgt het gekozen symbool (verse kopie bij wissel).\n useEffect(() => {\n setDraftBlocks(current ? (current.blocks as Record<string, unknown>[]) : null);\n }, [current?.id]); // eslint-disable-line react-hooks/exhaustive-deps\n\n async function persist(name: string, blocks: Record<string, unknown>[], id?: string) {\n setSaving(true);\n try {\n const res = await saveSymbol({ siteId, symbolId: id, name, blocks });\n if (!id) onChange({ ...block, symbolId: res.id });\n toast({ title: \"Symbool opgeslagen\", description: \"Alle pagina's met dit symbool zijn bijgewerkt.\", tone: \"success\" });\n } catch (e) {\n toast({ title: \"Opslaan mislukt\", description: (e as Error).message, tone: \"error\" });\n } finally {\n setSaving(false);\n }\n }\n\n return (\n <div className=\"grid gap-3\">\n <Field label=\"Symbool\" hint=\"Een gedeelde sectie: wijzigingen gelden op élke pagina die dit symbool gebruikt.\">\n <Select\n value={symbolId}\n onValueChange={(v) => onChange({ ...block, symbolId: v })}\n options={[{ value: \"\", label: \"— kies een symbool —\" }, ...symbols.map((s) => ({ value: s.id, label: s.name }))]}\n />\n </Field>\n\n {!symbolId && (\n <Field label=\"Of maak een nieuw symbool\" hint=\"Begint leeg; voeg daarna blokken toe.\">\n <div className=\"flex items-center gap-2\">\n <Input value={newName} placeholder=\"Naam (bv. Afspraak-CTA)\" onChange={(e) => setNewName(e.target.value)} />\n <Button type=\"button\" variant=\"secondary\" size=\"sm\" disabled={!newName.trim() || saving} onClick={() => persist(newName.trim(), [])}>\n Maak\n </Button>\n </div>\n </Field>\n )}\n\n {current && draftBlocks && (\n <>\n <Field label={`Inhoud van \"${current.name}\"`}>\n <NestedBlocksEditor blocks={draftBlocks} siteId={siteId} onChange={setDraftBlocks} addLabel=\"+ Blok in symbool…\" />\n </Field>\n <div>\n <Button type=\"button\" variant=\"primary\" size=\"sm\" disabled={saving} onClick={() => persist(current.name, draftBlocks, current.id)}>\n {saving ? \"Opslaan…\" : \"Symbool opslaan (overal bijwerken)\"}\n </Button>\n </div>\n </>\n )}\n </div>\n );\n}\n"],"mappings":";AAkDQ,SAmBA,UAnBA,KASE,YATF;AAzCR,SAAS,WAAW,SAAS,gBAAgB;AAC7C,SAAS,QAAQ,OAAO,aAAa;AACrC,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB,kBAAkB;AAG7C,SAAS,0BAA0B;AAE5B,SAAS,gBAAgB,EAAE,OAAO,QAAQ,SAAS,GAAqB;AAC7E,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,CAAC,SAAS,UAAU,IAAI,SAAyB,CAAC,CAAC;AACzD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,EAAE;AACzC,QAAM,CAAC,aAAa,cAAc,IAAI,SAA2C,IAAI;AACrF,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,WAAW,OAAO,MAAM,YAAY,EAAE;AAE5C,YAAU,MAAM,iBAAiB,QAAQ,UAAU,GAAG,CAAC,MAAM,CAAC;AAC9D,QAAM,UAAU,QAAQ,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,GAAG,CAAC,SAAS,QAAQ,CAAC;AAGzF,YAAU,MAAM;AACd,mBAAe,UAAW,QAAQ,SAAuC,IAAI;AAAA,EAC/E,GAAG,CAAC,SAAS,EAAE,CAAC;AAEhB,iBAAe,QAAQ,MAAc,QAAmC,IAAa;AACnF,cAAU,IAAI;AACd,QAAI;AACF,YAAM,MAAM,MAAM,WAAW,EAAE,QAAQ,UAAU,IAAI,MAAM,OAAO,CAAC;AACnE,UAAI,CAAC,GAAI,UAAS,EAAE,GAAG,OAAO,UAAU,IAAI,GAAG,CAAC;AAChD,YAAM,EAAE,OAAO,sBAAsB,aAAa,kDAAkD,MAAM,UAAU,CAAC;AAAA,IACvH,SAAS,GAAG;AACV,YAAM,EAAE,OAAO,mBAAmB,aAAc,EAAY,SAAS,MAAM,QAAQ,CAAC;AAAA,IACtF,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAU,cACb;AAAA,wBAAC,SAAM,OAAM,WAAU,MAAK,uFAC1B;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,eAAe,CAAC,MAAM,SAAS,EAAE,GAAG,OAAO,UAAU,EAAE,CAAC;AAAA,QACxD,SAAS,CAAC,EAAE,OAAO,IAAI,OAAO,iCAAuB,GAAG,GAAG,QAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA;AAAA,IACjH,GACF;AAAA,IAEC,CAAC,YACA,oBAAC,SAAM,OAAM,6BAA4B,MAAK,yCAC5C,+BAAC,SAAI,WAAU,2BACb;AAAA,0BAAC,SAAM,OAAO,SAAS,aAAY,2BAA0B,UAAU,CAAC,MAAM,WAAW,EAAE,OAAO,KAAK,GAAG;AAAA,MAC1G,oBAAC,UAAO,MAAK,UAAS,SAAQ,aAAY,MAAK,MAAK,UAAU,CAAC,QAAQ,KAAK,KAAK,QAAQ,SAAS,MAAM,QAAQ,QAAQ,KAAK,GAAG,CAAC,CAAC,GAAG,kBAErI;AAAA,OACF,GACF;AAAA,IAGD,WAAW,eACV,iCACE;AAAA,0BAAC,SAAM,OAAO,eAAe,QAAQ,IAAI,KACvC,8BAAC,sBAAmB,QAAQ,aAAa,QAAgB,UAAU,gBAAgB,UAAS,2BAAqB,GACnH;AAAA,MACA,oBAAC,SACC,8BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,UAAU,QAAQ,SAAS,MAAM,QAAQ,QAAQ,MAAM,aAAa,QAAQ,EAAE,GAC7H,mBAAS,kBAAa,sCACzB,GACF;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
@@ -1,16 +1,21 @@
1
1
  import { type ComponentType } from "react";
2
+ import { GenericEditor } from "./GenericEditor";
2
3
  export interface BlockEditorProps {
3
4
  block: Record<string, unknown>;
4
5
  siteId: string;
5
6
  onChange: (next: Record<string, unknown>) => void;
6
7
  }
7
8
  /**
8
- * Fallback-editor voor bloktypes zonder eigen `Editor` (bv. agent-gebouwde maatwerkblokken):
9
- * genereert automatisch een formulier uit het Zod-schema van het blok. Primitieve velden krijgen
10
- * echte invoervelden (tekst/getal/schakelaar/keuze, `html` → rich text, beeld-URL's → mediakiezer);
11
- * complexe velden (lijsten/objecten) een JSON-veld met validatie vóór opslaan. Zo is een blok
12
- * zonder maatwerk-editor nooit een doodlopend pad voor de klant.
9
+ * Beheert een geneste blok-lijst (kolommen / zijbalk / tab): blok toevoegen, sorteren, verwijderen,
10
+ * en per blok zijn eigen velden bewerken (recursief via dezelfde editor-registry). Zo kun je ook in
11
+ * een zijbalk gewoon widgets plaatsen en instellen, zonder JSON.
13
12
  */
14
- declare function GenericEditor({ block, siteId, onChange }: BlockEditorProps): import("react").JSX.Element;
13
+ export declare function NestedBlocksEditor({ blocks, siteId, onChange, addLabel, }: {
14
+ blocks: Record<string, unknown>[];
15
+ siteId: string;
16
+ onChange: (next: Record<string, unknown>[]) => void;
17
+ addLabel?: string;
18
+ }): import("react").JSX.Element;
19
+ /** Ingebouwde editors per blocktype. Klant-blokken kunnen hun eigen `Editor` meegeven. */
15
20
  export declare const builtinBlockEditors: Record<string, ComponentType<BlockEditorProps>>;
16
21
  export { GenericEditor };