@xiwebdesign/xicms-core 7.23.0 → 7.24.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 (97) hide show
  1. package/dist/admin/AdminApp.js +2 -1
  2. package/dist/admin/AdminApp.js.map +1 -1
  3. package/dist/admin/CoreVersion.d.ts +6 -0
  4. package/dist/admin/CoreVersion.js +42 -1
  5. package/dist/admin/CoreVersion.js.map +1 -1
  6. package/dist/admin/SuperadminPanel.js +19 -3
  7. package/dist/admin/SuperadminPanel.js.map +1 -1
  8. package/dist/admin/dashboard/DashboardPanel.js +46 -9
  9. package/dist/admin/dashboard/DashboardPanel.js.map +1 -1
  10. package/dist/admin/forms/FormsPanel.d.ts +9 -0
  11. package/dist/admin/forms/FormsPanel.js +102 -16
  12. package/dist/admin/forms/FormsPanel.js.map +1 -1
  13. package/dist/admin/forms/gezien.d.ts +3 -0
  14. package/dist/admin/forms/gezien.js +22 -0
  15. package/dist/admin/forms/gezien.js.map +1 -0
  16. package/dist/admin/lib/kleur.d.ts +32 -0
  17. package/dist/admin/lib/kleur.js +60 -0
  18. package/dist/admin/lib/kleur.js.map +1 -0
  19. package/dist/admin/lib/pagesClient.d.ts +32 -0
  20. package/dist/admin/lib/pagesClient.js +60 -1
  21. package/dist/admin/lib/pagesClient.js.map +1 -1
  22. package/dist/admin/lib/seoHulp.d.ts +28 -0
  23. package/dist/admin/lib/seoHulp.js +33 -0
  24. package/dist/admin/lib/seoHulp.js.map +1 -0
  25. package/dist/admin/media/MediaManager.js +133 -51
  26. package/dist/admin/media/MediaManager.js.map +1 -1
  27. package/dist/admin/pages/PageEditor.d.ts +14 -0
  28. package/dist/admin/pages/PageEditor.js +192 -123
  29. package/dist/admin/pages/PageEditor.js.map +1 -1
  30. package/dist/admin/pages/PagesPanel.js +88 -40
  31. package/dist/admin/pages/PagesPanel.js.map +1 -1
  32. package/dist/admin/settings/SettingsPanel.js +100 -19
  33. package/dist/admin/settings/SettingsPanel.js.map +1 -1
  34. package/dist/admin/shop/ProductsTab.js +1 -1
  35. package/dist/admin/shop/ProductsTab.js.map +1 -1
  36. package/dist/admin/shop/ShopPanel.js +5 -2
  37. package/dist/admin/shop/ShopPanel.js.map +1 -1
  38. package/dist/admin/stats/StatsPanel.js +30 -2
  39. package/dist/admin/stats/StatsPanel.js.map +1 -1
  40. package/dist/admin/stats/lib.d.ts +14 -1
  41. package/dist/admin/stats/lib.js +46 -2
  42. package/dist/admin/stats/lib.js.map +1 -1
  43. package/dist/admin/visual/VisualEditor.js +80 -4
  44. package/dist/admin/visual/VisualEditor.js.map +1 -1
  45. package/dist/admin.css +1 -1
  46. package/dist/blocks/builtins/FormBlockRender.js +8 -2
  47. package/dist/blocks/builtins/FormBlockRender.js.map +1 -1
  48. package/dist/blocks/builtins/lib/submitForm.d.ts +3 -1
  49. package/dist/blocks/builtins/lib/submitForm.js +2 -2
  50. package/dist/blocks/builtins/lib/submitForm.js.map +1 -1
  51. package/dist/components/EditBar.d.ts +3 -1
  52. package/dist/components/EditBar.js +30 -4
  53. package/dist/components/EditBar.js.map +1 -1
  54. package/dist/components/News.js +8 -4
  55. package/dist/components/News.js.map +1 -1
  56. package/dist/components/PageView.d.ts +5 -0
  57. package/dist/components/PageView.js +24 -2
  58. package/dist/components/PageView.js.map +1 -1
  59. package/dist/components/ResponsiveImage.d.ts +1 -1
  60. package/dist/components/ResponsiveImage.js +3 -1
  61. package/dist/components/ResponsiveImage.js.map +1 -1
  62. package/dist/components/site/SiteNav.d.ts +29 -0
  63. package/dist/components/site/SiteNav.js +82 -0
  64. package/dist/components/site/SiteNav.js.map +1 -0
  65. package/dist/content/types.d.ts +8 -0
  66. package/dist/index.d.ts +3 -1
  67. package/dist/index.js +8 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/media/ImageContext.d.ts +1 -6
  70. package/dist/media/ImageContext.js +6 -1
  71. package/dist/media/ImageContext.js.map +1 -1
  72. package/dist/releaseNotes.d.ts +15 -0
  73. package/dist/releaseNotes.js +28 -0
  74. package/dist/releaseNotes.js.map +1 -0
  75. package/dist/schema/settings.d.ts +105 -0
  76. package/dist/schema/settings.js +45 -5
  77. package/dist/schema/settings.js.map +1 -1
  78. package/dist/schema/siteConfig.d.ts +9 -0
  79. package/dist/server/blockData.js +6 -2
  80. package/dist/server/blockData.js.map +1 -1
  81. package/dist/server/draft.d.ts +11 -0
  82. package/dist/server/draft.js +20 -1
  83. package/dist/server/draft.js.map +1 -1
  84. package/dist/server/index.d.ts +3 -1
  85. package/dist/server/index.js +7 -1
  86. package/dist/server/index.js.map +1 -1
  87. package/dist/server/pages.js +4 -1
  88. package/dist/server/pages.js.map +1 -1
  89. package/dist/server/previewToken.d.ts +34 -0
  90. package/dist/server/previewToken.js +44 -0
  91. package/dist/server/previewToken.js.map +1 -0
  92. package/dist/server/redirects.d.ts +15 -0
  93. package/dist/server/redirects.js +15 -0
  94. package/dist/server/redirects.js.map +1 -0
  95. package/dist/styles.css +110 -0
  96. package/dist/version.js +1 -1
  97. package/package.json +1 -1
@@ -5,7 +5,7 @@ import type { MediaItem } from "../content/types.js";
5
5
  * Server-safe. Valt terug op de originele `url` als er nog geen varianten zijn.
6
6
  */
7
7
  export declare function ResponsiveImage({ media, url, alt, className, style, sizes, priority, }: {
8
- media?: Pick<MediaItem, "url" | "alt" | "width" | "height" | "variants">;
8
+ media?: Pick<MediaItem, "url" | "alt" | "width" | "height" | "variants" | "focal">;
9
9
  url?: string;
10
10
  alt?: string;
11
11
  className?: string;
@@ -13,6 +13,8 @@ function ResponsiveImage({
13
13
  const altText = alt ?? media?.alt ?? "";
14
14
  const ladder = media?.variants?.sizes ?? [];
15
15
  const webp = media?.variants?.webp;
16
+ const focal = media?.focal;
17
+ const stijl = focal && !(style && style.objectPosition) ? { ...style, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` } : style;
16
18
  const srcSet = ladder.length ? ladder.map((v) => `${v.url} ${v.width}w`).join(", ") : void 0;
17
19
  return /* @__PURE__ */ jsxs("picture", { children: [
18
20
  webp && /* @__PURE__ */ jsx("source", { type: "image/webp", srcSet: `${webp.url} ${webp.width}w`, sizes }),
@@ -28,7 +30,7 @@ function ResponsiveImage({
28
30
  loading: priority ? "eager" : "lazy",
29
31
  fetchPriority: priority ? "high" : void 0,
30
32
  className,
31
- style
33
+ style: stijl
32
34
  }
33
35
  )
34
36
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/ResponsiveImage.tsx"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { MediaItem } from \"../content/types\";\n\n/**\n * Rendert een afbeelding met srcset/webp uit de door de pipeline gegenereerde varianten.\n * Server-safe. Valt terug op de originele `url` als er nog geen varianten zijn.\n */\nexport function ResponsiveImage({\n media,\n url,\n alt,\n className,\n style,\n sizes = \"100vw\",\n priority = false,\n}: {\n media?: Pick<MediaItem, \"url\" | \"alt\" | \"width\" | \"height\" | \"variants\">;\n url?: string;\n alt?: string;\n className?: string;\n style?: CSSProperties;\n sizes?: string;\n /** Zet dit op een above-the-fold beeld (bv. hero): eager laden + hoge fetch-prioriteit voor LCP. */\n priority?: boolean;\n}) {\n const src = media?.url ?? url;\n if (!src) return null;\n const altText = alt ?? media?.alt ?? \"\";\n const ladder = media?.variants?.sizes ?? [];\n const webp = media?.variants?.webp;\n\n const srcSet = ladder.length\n ? ladder.map((v) => `${v.url} ${v.width}w`).join(\", \")\n : undefined;\n\n return (\n <picture>\n {webp && <source type=\"image/webp\" srcSet={`${webp.url} ${webp.width}w`} sizes={sizes} />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={src}\n srcSet={srcSet}\n sizes={srcSet ? sizes : undefined}\n alt={altText}\n width={media?.width}\n height={media?.height}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n className={className}\n style={style}\n />\n </picture>\n );\n}\n"],"mappings":"AAoCI,SACW,KADX;AA7BG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AACb,GASG;AACD,QAAM,MAAM,OAAO,OAAO;AAC1B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,OAAO,OAAO,OAAO;AACrC,QAAM,SAAS,OAAO,UAAU,SAAS,CAAC;AAC1C,QAAM,OAAO,OAAO,UAAU;AAE9B,QAAM,SAAS,OAAO,SAClB,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,IACnD;AAEJ,SACE,qBAAC,aACE;AAAA,YAAQ,oBAAC,YAAO,MAAK,cAAa,QAAQ,GAAG,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,OAAc;AAAA,IAEvF;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,OAAO,SAAS,QAAQ;AAAA,QACxB,KAAK;AAAA,QACL,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,SAAS,WAAW,UAAU;AAAA,QAC9B,eAAe,WAAW,SAAS;AAAA,QACnC;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/ResponsiveImage.tsx"],"sourcesContent":["import type { CSSProperties } from \"react\";\nimport type { MediaItem } from \"../content/types\";\n\n/**\n * Rendert een afbeelding met srcset/webp uit de door de pipeline gegenereerde varianten.\n * Server-safe. Valt terug op de originele `url` als er nog geen varianten zijn.\n */\nexport function ResponsiveImage({\n media,\n url,\n alt,\n className,\n style,\n sizes = \"100vw\",\n priority = false,\n}: {\n media?: Pick<MediaItem, \"url\" | \"alt\" | \"width\" | \"height\" | \"variants\" | \"focal\">;\n url?: string;\n alt?: string;\n className?: string;\n style?: CSSProperties;\n sizes?: string;\n /** Zet dit op een above-the-fold beeld (bv. hero): eager laden + hoge fetch-prioriteit voor LCP. */\n priority?: boolean;\n}) {\n const src = media?.url ?? url;\n if (!src) return null;\n const altText = alt ?? media?.alt ?? \"\";\n const ladder = media?.variants?.sizes ?? [];\n const webp = media?.variants?.webp;\n // Focuspunt (7.24): bijsnijden rond het gekozen punt i.p.v. het midden (zie MediaItem.focal).\n const focal = media?.focal;\n const stijl = focal && !(style && style.objectPosition)\n ? { ...style, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` }\n : style;\n\n const srcSet = ladder.length\n ? ladder.map((v) => `${v.url} ${v.width}w`).join(\", \")\n : undefined;\n\n return (\n <picture>\n {webp && <source type=\"image/webp\" srcSet={`${webp.url} ${webp.width}w`} sizes={sizes} />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={src}\n srcSet={srcSet}\n sizes={srcSet ? sizes : undefined}\n alt={altText}\n width={media?.width}\n height={media?.height}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n className={className}\n style={stijl}\n />\n </picture>\n );\n}\n"],"mappings":"AAyCI,SACW,KADX;AAlCG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,WAAW;AACb,GASG;AACD,QAAM,MAAM,OAAO,OAAO;AAC1B,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,OAAO,OAAO,OAAO;AACrC,QAAM,SAAS,OAAO,UAAU,SAAS,CAAC;AAC1C,QAAM,OAAO,OAAO,UAAU;AAE9B,QAAM,QAAQ,OAAO;AACrB,QAAM,QAAQ,SAAS,EAAE,SAAS,MAAM,kBACpC,EAAE,GAAG,OAAO,gBAAgB,GAAG,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,IAC1F;AAEJ,QAAM,SAAS,OAAO,SAClB,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,IACnD;AAEJ,SACE,qBAAC,aACE;AAAA,YAAQ,oBAAC,YAAO,MAAK,cAAa,QAAQ,GAAG,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,OAAc;AAAA,IAEvF;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,OAAO,SAAS,QAAQ;AAAA,QACxB,KAAK;AAAA,QACL,OAAO,OAAO;AAAA,QACd,QAAQ,OAAO;AAAA,QACf,SAAS,WAAW,UAAU;AAAA,QAC9B,eAAe,WAAW,SAAS;AAAA,QACnC;AAAA,QACA,OAAO;AAAA;AAAA,IACT;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,29 @@
1
+ import { type NavItem } from "../../schema/settings.js";
2
+ /**
3
+ * De hoofdnavigatie zoals de klant hem in /beheer → Menu beheert (7.24).
4
+ *
5
+ * Tot 7.24 schreef het Menu-paneel netjes naar `settings.nav`, meldde "De navigatie is
6
+ * bijgewerkt" — en las geen enkele header dat veld. De template en de klantsites renderden
7
+ * `siteConfig.nav` rechtstreeks uit Git, dus wat de klant in het beheer deed kwam nooit op de
8
+ * site. Deze component is de ontbrekende consument: hij leest de samengevoegde config
9
+ * (Firestore wint, Git is de terugval) en is daarmee de énige plek die een menu hoort te tekenen.
10
+ *
11
+ * SSR-veilig: de settings zitten in de server-seed (`settingsData` op XiCMSProvider), dus het menu
12
+ * staat al in de HTML en er is geen flits van het oude menu.
13
+ *
14
+ * Unstyled-by-default, net als `SiteFooter`: alle opmaak hangt aan `xicms-nav*`-klassen en de
15
+ * thema-variabelen, zodat elke site hem kan herstijlen zonder de logica te forken. De klassen die
16
+ * de template gebruikte (`.site-header nav a`) blijven werken doordat dit een gewone `<nav>` met
17
+ * `<a>`'s blijft.
18
+ *
19
+ * Submenu's (één niveau): een item mét kinderen is zélf een link naar zijn overzichtspagina, met
20
+ * ernaast een knop die het submenu openklapt (`aria-expanded`). Zo werkt het menu zonder
21
+ * JavaScript (alle links staan in de HTML), met het toetsenbord (knop = Enter/Spatie, Escape
22
+ * sluit) en op touch (geen hover nodig).
23
+ */
24
+ export declare function SiteNav({ className, ariaLabel, items, }: {
25
+ className?: string;
26
+ ariaLabel?: string;
27
+ /** Overschrijft de items (bv. voor een footer-menu); standaard de beheerde hoofdnavigatie. */
28
+ items?: NavItem[];
29
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,82 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useRef, useState } from "react";
4
+ import { useLinkComponent } from "@astryxdesign/core/Link";
5
+ import { useMergedSiteConfig } from "../../settings/useSiteSettings.js";
6
+ import { isExterneLink, navHref } from "../../schema/settings.js";
7
+ function SiteNav({
8
+ className,
9
+ ariaLabel = "Hoofdnavigatie",
10
+ items
11
+ }) {
12
+ const cfg = useMergedSiteConfig();
13
+ const lijst = items ?? cfg.nav ?? [];
14
+ if (lijst.length === 0) return null;
15
+ return /* @__PURE__ */ jsx("nav", { className: className ?? "xicms-nav", "aria-label": ariaLabel, children: /* @__PURE__ */ jsx("ul", { className: "xicms-nav__list", children: lijst.map((item, i) => /* @__PURE__ */ jsx(NavEntry, { item }, `${item.href}-${i}`)) }) });
16
+ }
17
+ function NavEntry({ item }) {
18
+ const LinkC = useLinkComponent();
19
+ const kinderen = item.children ?? [];
20
+ const [open, setOpen] = useState(false);
21
+ const id = useId();
22
+ const ref = useRef(null);
23
+ useEffect(() => {
24
+ if (!open) return;
25
+ const onDown = (e) => {
26
+ if (!ref.current?.contains(e.target)) setOpen(false);
27
+ };
28
+ const onKey = (e) => {
29
+ if (e.key === "Escape") setOpen(false);
30
+ };
31
+ document.addEventListener("pointerdown", onDown);
32
+ document.addEventListener("keydown", onKey);
33
+ return () => {
34
+ document.removeEventListener("pointerdown", onDown);
35
+ document.removeEventListener("keydown", onKey);
36
+ };
37
+ }, [open]);
38
+ const extern = isExterneLink(item.href);
39
+ const linkProps = {
40
+ href: navHref(item),
41
+ target: item.target === "_blank" ? "_blank" : void 0,
42
+ rel: extern && item.target === "_blank" ? "noopener noreferrer" : void 0
43
+ };
44
+ if (kinderen.length === 0) {
45
+ return /* @__PURE__ */ jsx("li", { className: "xicms-nav__item", children: /* @__PURE__ */ jsx(LinkC, { className: "xicms-nav__link", ...linkProps, children: item.label }) });
46
+ }
47
+ return /* @__PURE__ */ jsxs("li", { ref, className: `xicms-nav__item xicms-nav__item--sub${open ? " is-open" : ""}`, children: [
48
+ /* @__PURE__ */ jsxs("span", { className: "xicms-nav__pair", children: [
49
+ /* @__PURE__ */ jsx(LinkC, { className: "xicms-nav__link", ...linkProps, children: item.label }),
50
+ /* @__PURE__ */ jsx(
51
+ "button",
52
+ {
53
+ type: "button",
54
+ className: "xicms-nav__toggle",
55
+ "aria-expanded": open,
56
+ "aria-controls": id,
57
+ "aria-label": `Submenu van ${item.label} ${open ? "sluiten" : "openen"}`,
58
+ onClick: () => setOpen((v) => !v),
59
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 9l6 6 6-6" }) })
60
+ }
61
+ )
62
+ ] }),
63
+ /* @__PURE__ */ jsx("ul", { id, className: "xicms-nav__sub", hidden: !open, children: kinderen.map((kind, j) => {
64
+ const kExtern = isExterneLink(kind.href);
65
+ return /* @__PURE__ */ jsx("li", { className: "xicms-nav__subitem", children: /* @__PURE__ */ jsx(
66
+ LinkC,
67
+ {
68
+ className: "xicms-nav__sublink",
69
+ href: navHref(kind),
70
+ target: kind.target === "_blank" ? "_blank" : void 0,
71
+ rel: kExtern && kind.target === "_blank" ? "noopener noreferrer" : void 0,
72
+ onClick: () => setOpen(false),
73
+ children: kind.label
74
+ }
75
+ ) }, `${kind.href}-${j}`);
76
+ }) })
77
+ ] });
78
+ }
79
+ export {
80
+ SiteNav
81
+ };
82
+ //# sourceMappingURL=SiteNav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/site/SiteNav.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport { useLinkComponent } from \"@astryxdesign/core/Link\";\nimport { useMergedSiteConfig } from \"../../settings/useSiteSettings\";\nimport { isExterneLink, navHref, type NavItem } from \"../../schema/settings\";\n\n/**\n * De hoofdnavigatie zoals de klant hem in /beheer → Menu beheert (7.24).\n *\n * Tot 7.24 schreef het Menu-paneel netjes naar `settings.nav`, meldde \"De navigatie is\n * bijgewerkt\" — en las geen enkele header dat veld. De template en de klantsites renderden\n * `siteConfig.nav` rechtstreeks uit Git, dus wat de klant in het beheer deed kwam nooit op de\n * site. Deze component is de ontbrekende consument: hij leest de samengevoegde config\n * (Firestore wint, Git is de terugval) en is daarmee de énige plek die een menu hoort te tekenen.\n *\n * SSR-veilig: de settings zitten in de server-seed (`settingsData` op XiCMSProvider), dus het menu\n * staat al in de HTML en er is geen flits van het oude menu.\n *\n * Unstyled-by-default, net als `SiteFooter`: alle opmaak hangt aan `xicms-nav*`-klassen en de\n * thema-variabelen, zodat elke site hem kan herstijlen zonder de logica te forken. De klassen die\n * de template gebruikte (`.site-header nav a`) blijven werken doordat dit een gewone `<nav>` met\n * `<a>`'s blijft.\n *\n * Submenu's (één niveau): een item mét kinderen is zélf een link naar zijn overzichtspagina, met\n * ernaast een knop die het submenu openklapt (`aria-expanded`). Zo werkt het menu zonder\n * JavaScript (alle links staan in de HTML), met het toetsenbord (knop = Enter/Spatie, Escape\n * sluit) en op touch (geen hover nodig).\n */\nexport function SiteNav({\n className,\n ariaLabel = \"Hoofdnavigatie\",\n items,\n}: {\n className?: string;\n ariaLabel?: string;\n /** Overschrijft de items (bv. voor een footer-menu); standaard de beheerde hoofdnavigatie. */\n items?: NavItem[];\n}) {\n const cfg = useMergedSiteConfig();\n const lijst = items ?? cfg.nav ?? [];\n if (lijst.length === 0) return null;\n return (\n <nav className={className ?? \"xicms-nav\"} aria-label={ariaLabel}>\n <ul className=\"xicms-nav__list\">\n {lijst.map((item, i) => (\n <NavEntry key={`${item.href}-${i}`} item={item} />\n ))}\n </ul>\n </nav>\n );\n}\n\nfunction NavEntry({ item }: { item: NavItem }) {\n const LinkC = useLinkComponent();\n const kinderen = item.children ?? [];\n const [open, setOpen] = useState(false);\n const id = useId();\n const ref = useRef<HTMLLIElement>(null);\n\n // Buiten klikken of Escape sluit het submenu — anders blijft het hangen tot je een ander opent.\n useEffect(() => {\n if (!open) return;\n const onDown = (e: PointerEvent) => {\n if (!ref.current?.contains(e.target as Node)) setOpen(false);\n };\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"pointerdown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"pointerdown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open]);\n\n const extern = isExterneLink(item.href);\n const linkProps = {\n href: navHref(item),\n target: item.target === \"_blank\" ? \"_blank\" : undefined,\n rel: extern && item.target === \"_blank\" ? \"noopener noreferrer\" : undefined,\n };\n\n if (kinderen.length === 0) {\n return (\n <li className=\"xicms-nav__item\">\n <LinkC className=\"xicms-nav__link\" {...linkProps}>{item.label}</LinkC>\n </li>\n );\n }\n\n return (\n <li ref={ref} className={`xicms-nav__item xicms-nav__item--sub${open ? \" is-open\" : \"\"}`}>\n <span className=\"xicms-nav__pair\">\n <LinkC className=\"xicms-nav__link\" {...linkProps}>{item.label}</LinkC>\n <button\n type=\"button\"\n className=\"xicms-nav__toggle\"\n aria-expanded={open}\n aria-controls={id}\n aria-label={`Submenu van ${item.label} ${open ? \"sluiten\" : \"openen\"}`}\n onClick={() => setOpen((v) => !v)}\n >\n <svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n </button>\n </span>\n <ul id={id} className=\"xicms-nav__sub\" hidden={!open}>\n {kinderen.map((kind, j) => {\n const kExtern = isExterneLink(kind.href);\n return (\n <li key={`${kind.href}-${j}`} className=\"xicms-nav__subitem\">\n <LinkC\n className=\"xicms-nav__sublink\"\n href={navHref(kind)}\n target={kind.target === \"_blank\" ? \"_blank\" : undefined}\n rel={kExtern && kind.target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n onClick={() => setOpen(false)}\n >\n {kind.label}\n </LinkC>\n </li>\n );\n })}\n </ul>\n </li>\n );\n}\n"],"mappings":"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","names":[]}
@@ -38,4 +38,12 @@ export interface MediaItem {
38
38
  };
39
39
  /** "processing" tot de pipeline klaar is, daarna "ready". */
40
40
  status?: "processing" | "ready";
41
+ /**
42
+ * Focuspunt (7.24): welk deel van de foto in beeld moet blijven als hij bijgesneden wordt
43
+ * (hero op mobiel, vierkante kaart). Fracties 0–1 van links/boven; afwezig = het midden.
44
+ */
45
+ focal?: {
46
+ x: number;
47
+ y: number;
48
+ };
41
49
  }
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ export type { ImageComponentType, ImgProps } from "./media/ImageContext.js";
15
15
  export * from "./blocks/ui/index.js";
16
16
  export { SiteSettingsProvider, useSiteSettings, useMergedSiteConfig, } from "./settings/useSiteSettings.js";
17
17
  export { SiteFooter } from "./components/site/SiteFooter.js";
18
+ export { SiteNav } from "./components/site/SiteNav.js";
18
19
  export { EditorProvider, useEditor } from "./editor/EditorProvider.js";
19
20
  export { definePattern, instantiatePattern, collectPatternTypes, builtinPatterns, type Pattern, type PatternBlock, } from "./patterns/index.js";
20
21
  export { builtinThemePresets, type ThemePreset } from "./theme/presets.js";
@@ -30,7 +31,8 @@ export { customBlockToDefinition } from "./blocks/customTemplate/definition.js";
30
31
  export { interpretTemplate, scopeCss } from "./blocks/customTemplate/interpret.js";
31
32
  export { siteLocales, parseLocaleSlugs, localizedPath, localizePage, localizeBlocks, pruneTranslations, TranslationsSchema, } from "./i18n/overlay.js";
32
33
  export type { Translations, LocaleConfig } from "./i18n/overlay.js";
33
- export type { ThemeTokens, NavItem, SiteSettings, PublicSiteSettings, FormsSender } from "./schema/settings.js";
34
+ export type { ThemeTokens, NavItem, NavLeaf, Redirect, SiteSettings, PublicSiteSettings, FormsSender } from "./schema/settings.js";
35
+ export { zoekRedirect, normaliseerPad, navHref, isExterneLink } from "./schema/settings.js";
34
36
  export type { Seo } from "./schema/seo.js";
35
37
  export type { Page, PageStatus } from "./schema/pages.js";
36
38
  export type { FormDefinition, FormField } from "./schema/forms.js";
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  useMergedSiteConfig
11
11
  } from "./settings/useSiteSettings.js";
12
12
  import { SiteFooter } from "./components/site/SiteFooter.js";
13
+ import { SiteNav } from "./components/site/SiteNav.js";
13
14
  import { EditorProvider, useEditor } from "./editor/EditorProvider.js";
14
15
  import {
15
16
  definePattern,
@@ -33,6 +34,7 @@ import {
33
34
  pruneTranslations,
34
35
  TranslationsSchema
35
36
  } from "./i18n/overlay.js";
37
+ import { zoekRedirect, normaliseerPad, navHref, isExterneLink } from "./schema/settings.js";
36
38
  import { AuthProvider, useAuth } from "./auth/AuthProvider.js";
37
39
  import { LoginForm } from "./auth/LoginForm.js";
38
40
  import {
@@ -119,6 +121,7 @@ export {
119
121
  ResponsiveImage,
120
122
  SiteChrome,
121
123
  SiteFooter,
124
+ SiteNav,
122
125
  SiteSettingsProvider,
123
126
  SymbolSchema,
124
127
  TranslationsSchema,
@@ -149,6 +152,7 @@ export {
149
152
  initAppCheck,
150
153
  instantiatePattern,
151
154
  interpretTemplate,
155
+ isExterneLink,
152
156
  isFieldVisible,
153
157
  isFirebaseConfigured,
154
158
  isModuleActive,
@@ -160,6 +164,8 @@ export {
160
164
  localizedPath,
161
165
  mergeRegistry,
162
166
  moduleIdForBlockType,
167
+ navHref,
168
+ normaliseerPad,
163
169
  parseLocaleSlugs,
164
170
  pruneTranslations,
165
171
  resolveModules,
@@ -182,6 +188,7 @@ export {
182
188
  useSiteSettings,
183
189
  useTheme,
184
190
  validateBlocks,
185
- validateModuleContract
191
+ validateModuleContract,
192
+ zoekRedirect
186
193
  };
187
194
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Publieke runtime-API van @xiwebdesign/xicms-core (lichte client/runtime laag).\n * - Admin (TipTap/Radix) zit in \"@xiwebdesign/xicms-core/admin\".\n * - Server-only code (firebase-admin, sanitize, SEO) zit in \"@xiwebdesign/xicms-core/server\".\n * - Pure schema's in \"@xiwebdesign/xicms-core/schema\".\n */\n\n// Provider & config\nexport { XiCMSProvider } from \"./XiCMSProvider\";\nexport { useSiteConfig } from \"./config\";\n\n// Theming & design-primitieven (maatwerk zonder core te forken).\n// - `defineTheme` maakt een eigen Astryx-thema (tokens + component-overrides) → geef door als\n// XiCMSProvider `astryxTheme`-prop.\n// - De `ui`-primitieven (Card/Stack/Grid/Heading/Text/Btn…) zijn de bouwstenen waarop de ingebouwde\n// blokken draaien; gebruik ze in eigen blokken voor een consistent maar te herstijlen uiterlijk.\nexport { defineTheme, useTheme } from \"@astryxdesign/core/theme\";\nexport type { DefinedTheme } from \"@astryxdesign/core/theme\";\n// Framework-router-integratie: geef `next/link` door als XiCMSProvider `linkComponent`-prop\n// (soepele client-nav + prefetch op alle Astryx-knoppen/links). `useLinkComponent` voor eigen blokken.\nexport { LinkProvider, useLinkComponent } from \"@astryxdesign/core/Link\";\nexport type { LinkComponentType } from \"@astryxdesign/core/Link\";\n// Beeldoptimalisatie: geef `next/image` door als XiCMSProvider `imageComponent`-prop. `Img` is de\n// wrapper die de blokken gebruiken; `useImageComponent` voor eigen blokken.\nexport { Img, ImageProvider, useImageComponent, InlineImageEditProvider } from \"./media/ImageContext\";\nexport type { ImageComponentType, ImgProps } from \"./media/ImageContext\";\nexport * from \"./blocks/ui\";\nexport {\n SiteSettingsProvider,\n useSiteSettings,\n useMergedSiteConfig,\n} from \"./settings/useSiteSettings\";\nexport { SiteFooter } from \"./components/site/SiteFooter\";\nexport { EditorProvider, useEditor } from \"./editor/EditorProvider\";\nexport {\n definePattern,\n instantiatePattern,\n collectPatternTypes,\n builtinPatterns,\n type Pattern,\n type PatternBlock,\n} from \"./patterns\";\nexport { builtinThemePresets, type ThemePreset } from \"./theme/presets\";\nexport { defineSiteConfig } from \"./schema/siteConfig\";\nexport type { SiteConfig, SiteMode } from \"./schema/siteConfig\";\n// 5.0: Field-IR (primo-port), symbolen en custom blocks.\nexport { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from \"./schema/fields\";\nexport type { Field, FieldHints, ShowIf } from \"./schema/fields\";\nexport { SymbolSchema } from \"./schema/symbols\";\nexport type { SymbolDefinition, StoredSymbol } from \"./schema/symbols\";\nexport { CustomBlockSchema } from \"./schema/customBlocks\";\nexport type { CustomBlock, CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nexport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nexport { interpretTemplate, scopeCss } from \"./blocks/customTemplate/interpret\";\n// 5.0: localisatie — overlay-model (primo's locale-per-waarde, additief vertaald).\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n} from \"./i18n/overlay\";\nexport type { Translations, LocaleConfig } from \"./i18n/overlay\";\nexport type { ThemeTokens, NavItem, SiteSettings, PublicSiteSettings, FormsSender } from \"./schema/settings\";\nexport type { Seo } from \"./schema/seo\";\nexport type { Page, PageStatus } from \"./schema/pages\";\nexport type { FormDefinition, FormField } from \"./schema/forms\";\n\n// Auth\nexport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nexport { LoginForm } from \"./auth/LoginForm\";\nexport {\n roleForSite,\n canEdit,\n canManageSite,\n isSuperadmin,\n type Role,\n type XiClaims,\n} from \"./auth/roles\";\n\n// De legacy inline-laag (ContentProvider/useContent + EditableText/EditableRichText/EditableImage,\n// het v1 platte `cms/content`-model) is in 5.0 VERWIJDERD, zoals bij de deprecatie aangekondigd.\n// Inline bewerken loopt via het block-model: markeer tekstvelden met `data-xicms-edit` in de Render.\nexport type { NewsArticle, MediaItem, MediaVariant } from \"./content/types\";\n\nexport { EditBar } from \"./components/EditBar\";\nexport { ExternEmbed } from \"./components/ExternEmbed\";\nexport { ResponsiveImage } from \"./components/ResponsiveImage\";\nexport { PageView } from \"./components/PageView\";\nexport { NewsList, NewsArticleView, useNews } from \"./components/News\";\n\n// Block-systeem (runtime: registry + renderer; editors zitten in /admin)\nexport {\n defineBlock,\n createRegistry,\n mergeRegistry,\n validateBlocks,\n buildBlockAddOptions,\n type BlockDefinition,\n type BlockRegistry,\n type BlockRenderProps,\n type BlockEditorProps,\n type BlockCategory,\n} from \"./blocks/registry\";\nexport { BlockRenderer } from \"./blocks/BlockRenderer\";\nexport { RegistryProvider, useRegistry, BlockDataProvider, useBlockData } from \"./blocks/context\";\nexport { builtinBlocks, ConsentEmbed } from \"./blocks/builtins\";\nexport { PageRenderer } from \"./render/PageRenderer\";\nexport { PublishedPage } from \"./render/PublishedPage\";\n\n// Module-systeem (XiCMS 3.0): modules bundelen blocks + instellingen; per site aan/uit + te stylen.\nexport {\n defineModule,\n resolveModules,\n isModuleActive,\n ModulesProvider,\n useModules,\n useModuleActive,\n useModuleSettings,\n applyBlockOverrides,\n moduleIdForBlockType,\n builtinModules,\n validateModuleContract,\n assertModuleContract,\n useConsent,\n ConsentBanner,\n ConsentGate,\n ConsentReopenLink,\n type ConsentState,\n type ConsentSettings,\n type ConsentCategory,\n type ModuleDefinition,\n type ModuleSiteSetting,\n type ModulesSiteConfig,\n type Capability,\n type ResolvedModules,\n type BlockOverride,\n type BlockOverrides,\n} from \"./modules\";\n\n// Demo-lock\nexport { DemoLock } from \"./lock/DemoLock\";\n\n// Demo-bewerkmodus (lokaal-only) leeft op het subpad `@xiwebdesign/xicms-core/demo` — dit zijn\n// demo-store-internals die niet in de publieke runtime-API thuishoren (klantsites gebruiken ze nooit;\n// alleen de XiCMS.nl-showcase). DemoStudio wordt intern door XiCMSProvider gerenderd.\n\n// Firebase (client)\nexport {\n getFirebaseApp,\n getFirebaseAuth,\n getDb,\n getBucket,\n isFirebaseConfigured,\n} from \"./firebase/client\";\nexport { initAppCheck } from \"./firebase/appcheck\";\nexport { SiteChrome } from \"./SiteChrome\";\nexport { CORE_VERSION, CORE_PACKAGE, isNieuwer } from \"./version\";\n"],"mappings":"AAQA,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAO9B,SAAS,aAAa,gBAAgB;AAItC,SAAS,cAAc,wBAAwB;AAI/C,SAAS,KAAK,eAAe,mBAAmB,+BAA+B;AAE/E,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAC3B,SAAS,gBAAgB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA6C;AACtD,SAAS,wBAAwB;AAGjC,SAAS,aAAa,cAAc,aAAa,gBAAgB,gBAAgB,qBAAqB;AAEtG,SAAS,oBAAoB;AAE7B,SAAS,yBAAyB;AAElC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,gBAAgB;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAQP,SAAS,cAAc,eAAe;AACtC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,UAAU,iBAAiB,eAAe;AAGnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,aAAa,mBAAmB,oBAAoB;AAC/E,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,gBAAgB;AAOzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,cAAc,cAAc,iBAAiB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Publieke runtime-API van @xiwebdesign/xicms-core (lichte client/runtime laag).\n * - Admin (TipTap/Radix) zit in \"@xiwebdesign/xicms-core/admin\".\n * - Server-only code (firebase-admin, sanitize, SEO) zit in \"@xiwebdesign/xicms-core/server\".\n * - Pure schema's in \"@xiwebdesign/xicms-core/schema\".\n */\n\n// Provider & config\nexport { XiCMSProvider } from \"./XiCMSProvider\";\nexport { useSiteConfig } from \"./config\";\n\n// Theming & design-primitieven (maatwerk zonder core te forken).\n// - `defineTheme` maakt een eigen Astryx-thema (tokens + component-overrides) → geef door als\n// XiCMSProvider `astryxTheme`-prop.\n// - De `ui`-primitieven (Card/Stack/Grid/Heading/Text/Btn…) zijn de bouwstenen waarop de ingebouwde\n// blokken draaien; gebruik ze in eigen blokken voor een consistent maar te herstijlen uiterlijk.\nexport { defineTheme, useTheme } from \"@astryxdesign/core/theme\";\nexport type { DefinedTheme } from \"@astryxdesign/core/theme\";\n// Framework-router-integratie: geef `next/link` door als XiCMSProvider `linkComponent`-prop\n// (soepele client-nav + prefetch op alle Astryx-knoppen/links). `useLinkComponent` voor eigen blokken.\nexport { LinkProvider, useLinkComponent } from \"@astryxdesign/core/Link\";\nexport type { LinkComponentType } from \"@astryxdesign/core/Link\";\n// Beeldoptimalisatie: geef `next/image` door als XiCMSProvider `imageComponent`-prop. `Img` is de\n// wrapper die de blokken gebruiken; `useImageComponent` voor eigen blokken.\nexport { Img, ImageProvider, useImageComponent, InlineImageEditProvider } from \"./media/ImageContext\";\nexport type { ImageComponentType, ImgProps } from \"./media/ImageContext\";\nexport * from \"./blocks/ui\";\nexport {\n SiteSettingsProvider,\n useSiteSettings,\n useMergedSiteConfig,\n} from \"./settings/useSiteSettings\";\nexport { SiteFooter } from \"./components/site/SiteFooter\";\n// 7.24: de beheerde hoofdnavigatie — de enige plek die `settings.nav` leest. Zie SiteNav.tsx.\nexport { SiteNav } from \"./components/site/SiteNav\";\nexport { EditorProvider, useEditor } from \"./editor/EditorProvider\";\nexport {\n definePattern,\n instantiatePattern,\n collectPatternTypes,\n builtinPatterns,\n type Pattern,\n type PatternBlock,\n} from \"./patterns\";\nexport { builtinThemePresets, type ThemePreset } from \"./theme/presets\";\nexport { defineSiteConfig } from \"./schema/siteConfig\";\nexport type { SiteConfig, SiteMode } from \"./schema/siteConfig\";\n// 5.0: Field-IR (primo-port), symbolen en custom blocks.\nexport { FieldSchema, FieldsSchema, fieldsToZod, fieldsDefaults, isFieldVisible, isRichTextKey } from \"./schema/fields\";\nexport type { Field, FieldHints, ShowIf } from \"./schema/fields\";\nexport { SymbolSchema } from \"./schema/symbols\";\nexport type { SymbolDefinition, StoredSymbol } from \"./schema/symbols\";\nexport { CustomBlockSchema } from \"./schema/customBlocks\";\nexport type { CustomBlock, CustomBlock as CustomBlockData } from \"./schema/customBlocks\";\nexport { customBlockToDefinition } from \"./blocks/customTemplate/definition\";\nexport { interpretTemplate, scopeCss } from \"./blocks/customTemplate/interpret\";\n// 5.0: localisatie — overlay-model (primo's locale-per-waarde, additief vertaald).\nexport {\n siteLocales,\n parseLocaleSlugs,\n localizedPath,\n localizePage,\n localizeBlocks,\n pruneTranslations,\n TranslationsSchema,\n} from \"./i18n/overlay\";\nexport type { Translations, LocaleConfig } from \"./i18n/overlay\";\nexport type { ThemeTokens, NavItem, NavLeaf, Redirect, SiteSettings, PublicSiteSettings, FormsSender } from \"./schema/settings\";\nexport { zoekRedirect, normaliseerPad, navHref, isExterneLink } from \"./schema/settings\";\nexport type { Seo } from \"./schema/seo\";\nexport type { Page, PageStatus } from \"./schema/pages\";\nexport type { FormDefinition, FormField } from \"./schema/forms\";\n\n// Auth\nexport { AuthProvider, useAuth } from \"./auth/AuthProvider\";\nexport { LoginForm } from \"./auth/LoginForm\";\nexport {\n roleForSite,\n canEdit,\n canManageSite,\n isSuperadmin,\n type Role,\n type XiClaims,\n} from \"./auth/roles\";\n\n// De legacy inline-laag (ContentProvider/useContent + EditableText/EditableRichText/EditableImage,\n// het v1 platte `cms/content`-model) is in 5.0 VERWIJDERD, zoals bij de deprecatie aangekondigd.\n// Inline bewerken loopt via het block-model: markeer tekstvelden met `data-xicms-edit` in de Render.\nexport type { NewsArticle, MediaItem, MediaVariant } from \"./content/types\";\n\nexport { EditBar } from \"./components/EditBar\";\nexport { ExternEmbed } from \"./components/ExternEmbed\";\nexport { ResponsiveImage } from \"./components/ResponsiveImage\";\nexport { PageView } from \"./components/PageView\";\nexport { NewsList, NewsArticleView, useNews } from \"./components/News\";\n\n// Block-systeem (runtime: registry + renderer; editors zitten in /admin)\nexport {\n defineBlock,\n createRegistry,\n mergeRegistry,\n validateBlocks,\n buildBlockAddOptions,\n type BlockDefinition,\n type BlockRegistry,\n type BlockRenderProps,\n type BlockEditorProps,\n type BlockCategory,\n} from \"./blocks/registry\";\nexport { BlockRenderer } from \"./blocks/BlockRenderer\";\nexport { RegistryProvider, useRegistry, BlockDataProvider, useBlockData } from \"./blocks/context\";\nexport { builtinBlocks, ConsentEmbed } from \"./blocks/builtins\";\nexport { PageRenderer } from \"./render/PageRenderer\";\nexport { PublishedPage } from \"./render/PublishedPage\";\n\n// Module-systeem (XiCMS 3.0): modules bundelen blocks + instellingen; per site aan/uit + te stylen.\nexport {\n defineModule,\n resolveModules,\n isModuleActive,\n ModulesProvider,\n useModules,\n useModuleActive,\n useModuleSettings,\n applyBlockOverrides,\n moduleIdForBlockType,\n builtinModules,\n validateModuleContract,\n assertModuleContract,\n useConsent,\n ConsentBanner,\n ConsentGate,\n ConsentReopenLink,\n type ConsentState,\n type ConsentSettings,\n type ConsentCategory,\n type ModuleDefinition,\n type ModuleSiteSetting,\n type ModulesSiteConfig,\n type Capability,\n type ResolvedModules,\n type BlockOverride,\n type BlockOverrides,\n} from \"./modules\";\n\n// Demo-lock\nexport { DemoLock } from \"./lock/DemoLock\";\n\n// Demo-bewerkmodus (lokaal-only) leeft op het subpad `@xiwebdesign/xicms-core/demo` — dit zijn\n// demo-store-internals die niet in de publieke runtime-API thuishoren (klantsites gebruiken ze nooit;\n// alleen de XiCMS.nl-showcase). DemoStudio wordt intern door XiCMSProvider gerenderd.\n\n// Firebase (client)\nexport {\n getFirebaseApp,\n getFirebaseAuth,\n getDb,\n getBucket,\n isFirebaseConfigured,\n} from \"./firebase/client\";\nexport { initAppCheck } from \"./firebase/appcheck\";\nexport { SiteChrome } from \"./SiteChrome\";\nexport { CORE_VERSION, CORE_PACKAGE, isNieuwer } from \"./version\";\n"],"mappings":"AAQA,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAO9B,SAAS,aAAa,gBAAgB;AAItC,SAAS,cAAc,wBAAwB;AAI/C,SAAS,KAAK,eAAe,mBAAmB,+BAA+B;AAE/E,cAAc;AACd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAE3B,SAAS,eAAe;AACxB,SAAS,gBAAgB,iBAAiB;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,2BAA6C;AACtD,SAAS,wBAAwB;AAGjC,SAAS,aAAa,cAAc,aAAa,gBAAgB,gBAAgB,qBAAqB;AAEtG,SAAS,oBAAoB;AAE7B,SAAS,yBAAyB;AAElC,SAAS,+BAA+B;AACxC,SAAS,mBAAmB,gBAAgB;AAE5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,cAAc,gBAAgB,SAAS,qBAAqB;AAMrE,SAAS,cAAc,eAAe;AACtC,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAOP,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,UAAU,iBAAiB,eAAe;AAGnD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,aAAa,mBAAmB,oBAAoB;AAC/E,SAAS,eAAe,oBAAoB;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAG9B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAWK;AAGP,SAAS,gBAAgB;AAOzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,cAAc,cAAc,iBAAiB;","names":[]}
@@ -33,9 +33,4 @@ export interface ImgProps {
33
33
  /** Beeld vult absoluut z'n parent (bv. hero-achtergrond): geen wrapper-box, knop absoluut t.o.v. het blok. */
34
34
  editFill?: boolean;
35
35
  }
36
- /**
37
- * Eén beeld-wrapper voor alle blokken. Gebruikt de geïnjecteerde `imageComponent` (next/image) als
38
- * die er is, anders een gewone `<img>`. De display-grootte regel je met CSS (`style`/`className`,
39
- * bv. `width:100%;height:100%;object-fit:cover`); `width`/`height` zijn de verhouding.
40
- */
41
- export declare function Img({ src, alt, width, height, sizes, priority, className, style, editField, editFill }: ImgProps): import("react").JSX.Element;
36
+ export declare function Img({ src, alt, width, height, sizes, priority, className, style: styleProp, editField, editFill }: ImgProps): import("react").JSX.Element;
@@ -16,13 +16,18 @@ function InlineImageEditProvider({
16
16
  }) {
17
17
  return /* @__PURE__ */ jsx(InlineImageEditContext.Provider, { value: onReplace, children });
18
18
  }
19
- function Img({ src, alt, width, height, sizes, priority, className, style, editField, editFill }) {
19
+ function style_heeftPositie(style) {
20
+ return Boolean(style && "objectPosition" in style && style.objectPosition);
21
+ }
22
+ function Img({ src, alt, width, height, sizes, priority, className, style: styleProp, editField, editFill }) {
20
23
  const ImageComponent = useImageComponent();
21
24
  const onReplace = useContext(InlineImageEditContext);
22
25
  const mediaSeed = useBlockData("media");
23
26
  const eigenLadder = mediaSeed?.[src];
24
27
  const ladder = eigenLadder?.variants?.sizes ?? [];
25
28
  const webp = eigenLadder?.variants?.webp;
29
+ const focal = eigenLadder?.focal;
30
+ const style = focal && !style_heeftPositie(styleProp) ? { ...styleProp, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` } : styleProp;
26
31
  const imageEl = ladder.length > 0 ? /* @__PURE__ */ jsxs("picture", { children: [
27
32
  webp && /* @__PURE__ */ jsx("source", { type: "image/webp", srcSet: `${webp.url} ${webp.width}w`, sizes }),
28
33
  /* @__PURE__ */ jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/media/ImageContext.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, type CSSProperties, type ElementType, type ReactNode } from \"react\";\nimport { useBlockData } from \"../blocks/context\";\nimport type { MediaItem } from \"../content/types\";\n\n/**\n * Optionele framework-beeldcomponent (bv. Next `Image`) voor automatische optimalisatie: responsive\n * `srcset`, moderne formaten (AVIF/WebP), correcte `width/height` (geen layout-shift), lazy-loading.\n * Core importeert `next/image` niet zelf (optionele peer) — de site injecteert 'm via de\n * `imageComponent`-prop op `XiCMSProvider`, net als `linkComponent`. Zonder injectie valt alles terug\n * op een gewone `<img>` (huidig gedrag, werkt overal).\n */\nexport type ImageComponentType = ElementType;\n\nconst ImageContext = createContext<ImageComponentType | null>(null);\n\nexport function ImageProvider({ component, children }: { component?: ImageComponentType; children: ReactNode }) {\n return <ImageContext.Provider value={component ?? null}>{children}</ImageContext.Provider>;\n}\n\nexport function useImageComponent(): ImageComponentType | null {\n return useContext(ImageContext);\n}\n\n/**\n * Inline beeld-bewerken: de visuele editor levert via deze context een `onReplace(field)`-callback\n * voor het GESELECTEERDE blok. Is die actief én geeft een `<Img>` een `editField` mee, dan toont het\n * beeld een vervang-icoontje. Buiten de editor (context = null) rendert `<Img>` exact als voorheen.\n */\n/**\n * `blockKey` komt sinds 7.9 uit de DOM, niet uit een per-blok provider.\n *\n * Tot 7.4 wikkelde de editor élk blok in zijn canvas apart in deze provider, met de blok-key in de\n * closure. Dat canvas is weg — de pagina van de klant ís de editor — en één provider om de hele\n * pagina weet niet meer bij welk blok een foto hoort. De knop lost dat op het moment van klikken\n * op: elk blok draagt al `data-xicms-block` (BlockRenderer), dus `closest()` vanaf de knop is\n * betrouwbaar en kost niets zolang er niemand klikt.\n */\nconst InlineImageEditContext = createContext<((field: string, blockKey?: string) => void) | null>(null);\n\nexport function InlineImageEditProvider({\n onReplace,\n children,\n}: {\n onReplace: ((field: string, blockKey?: string) => void) | null;\n children: ReactNode;\n}) {\n return <InlineImageEditContext.Provider value={onReplace}>{children}</InlineImageEditContext.Provider>;\n}\n\nexport interface ImgProps {\n src: string;\n alt: string;\n /** Intrinsieke breedte/hoogte (verhouding) → nodig voor next/image én tegen CLS. */\n width: number;\n height: number;\n /** Hint voor responsive selectie, bv. \"(max-width: 720px) 100vw, 33vw\". */\n sizes?: string;\n /** Above-the-fold (hero): eager + hoge prioriteit voor LCP. */\n priority?: boolean;\n className?: string;\n style?: CSSProperties;\n /** Blokveld (pad) met de URL — schakelt inline vervangen in de visuele editor in (bv. \"imageUrl\"). */\n editField?: string;\n /** Beeld vult absoluut z'n parent (bv. hero-achtergrond): geen wrapper-box, knop absoluut t.o.v. het blok. */\n editFill?: boolean;\n}\n\n/**\n * Eén beeld-wrapper voor alle blokken. Gebruikt de geïnjecteerde `imageComponent` (next/image) als\n * die er is, anders een gewone `<img>`. De display-grootte regel je met CSS (`style`/`className`,\n * bv. `width:100%;height:100%;object-fit:cover`); `width`/`height` zijn de verhouding.\n */\nexport function Img({ src, alt, width, height, sizes, priority, className, style, editField, editFill }: ImgProps) {\n const ImageComponent = useImageComponent();\n const onReplace = useContext(InlineImageEditContext);\n /**\n * Eigen srcset-ladder vóór next/image (7.13). De upload-pipeline (onMediaUpload, sharp) maakt\n * bij élke upload al webp + een maten-ladder; next/image liet dat werk links liggen en\n * transformeerde dezelfde beelden nóg een keer bij Vercel — betaalde image-transformations,\n * plus transformatie-latency op de eerste hit. De route levert daarom per pagina een\n * `media`-seed (url → varianten, zie server/blockData.ts); staat de URL van dit beeld daarin,\n * dan renderen we een kale <picture> met de eigen ladder en blijft Vercel er helemaal buiten.\n * Geen seed (editor, oudere site, extern beeld zoals Unsplash) → het bestaande pad.\n */\n const mediaSeed = useBlockData<Record<string, Pick<MediaItem, \"variants\" | \"width\" | \"height\">>>(\"media\");\n const eigenLadder = mediaSeed?.[src];\n const ladder = eigenLadder?.variants?.sizes ?? [];\n const webp = eigenLadder?.variants?.webp;\n const imageEl = ladder.length > 0 ? (\n <picture>\n {webp && <source type=\"image/webp\" srcSet={`${webp.url} ${webp.width}w`} sizes={sizes} />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={src}\n srcSet={ladder.map((v) => `${v.url} ${v.width}w`).join(\", \")}\n sizes={sizes}\n alt={alt}\n width={eigenLadder?.width ?? width}\n height={eigenLadder?.height ?? height}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n decoding=\"async\"\n className={className}\n style={style}\n />\n </picture>\n ) : ImageComponent ? (\n <ImageComponent\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n priority={priority || undefined}\n className={className}\n style={style}\n />\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n className={className}\n style={style}\n />\n );\n\n // In de visuele editor (geselecteerd blok): toon een vervang-icoontje over het beeld. We wijzigen\n // alleen de URL → de grootte/stijl blijft (die komt uit het blok), dus blok én beeld behouden hun maat.\n if (onReplace && editField) {\n const replace = (e: { preventDefault: () => void; stopPropagation: () => void; currentTarget?: unknown }) => {\n e.preventDefault();\n e.stopPropagation();\n const el = e.currentTarget as HTMLElement | undefined;\n const key = el?.closest?.(\"[data-xicms-block]\")?.getAttribute(\"data-xicms-block\") ?? undefined;\n onReplace(editField, key);\n };\n const btn = (\n <button type=\"button\" className={`xicms-ve-img__btn${editFill ? \" xicms-ve-img__btn--fill\" : \"\"}`} title=\"Klik om de foto te wijzigen\" onClick={replace}>\n 📷 Foto wijzigen\n </button>\n );\n // Vullend (hero-achtergrond): geen wrapper-box (zou het absoluut gepositioneerde beeld breken);\n // de knop is absoluut t.o.v. het blok-element zelf (dat is `position:relative`).\n if (editFill) return (<>{imageEl}{btn}</>);\n // In-flow beeld: hele beeld klikbaar (duidelijke affordance) + knop rechtsboven.\n return (\n <span className=\"xicms-ve-img\" style={{ position: \"relative\", display: \"block\" }} title=\"Klik om de foto te wijzigen\" onClick={replace}>\n {imageEl}\n {btn}\n </span>\n );\n }\n return imageEl;\n}\n"],"mappings":";AAkBS,SAqIiB,UArIjB,KAyEL,YAzEK;AAhBT,SAAS,eAAe,kBAAwE;AAChG,SAAS,oBAAoB;AAY7B,MAAM,eAAe,cAAyC,IAAI;AAE3D,SAAS,cAAc,EAAE,WAAW,SAAS,GAA4D;AAC9G,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAO,aAAa,MAAO,UAAS;AACpE;AAEO,SAAS,oBAA+C;AAC7D,SAAO,WAAW,YAAY;AAChC;AAgBA,MAAM,yBAAyB,cAAmE,IAAI;AAE/F,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,uBAAuB,UAAvB,EAAgC,OAAO,WAAY,UAAS;AACtE;AAyBO,SAAS,IAAI,EAAE,KAAK,KAAK,OAAO,QAAQ,OAAO,UAAU,WAAW,OAAO,WAAW,SAAS,GAAa;AACjH,QAAM,iBAAiB,kBAAkB;AACzC,QAAM,YAAY,WAAW,sBAAsB;AAUnD,QAAM,YAAY,aAA+E,OAAO;AACxG,QAAM,cAAc,YAAY,GAAG;AACnC,QAAM,SAAS,aAAa,UAAU,SAAS,CAAC;AAChD,QAAM,OAAO,aAAa,UAAU;AACpC,QAAM,UAAU,OAAO,SAAS,IAC9B,qBAAC,aACE;AAAA,YAAQ,oBAAC,YAAO,MAAK,cAAa,QAAQ,GAAG,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,OAAc;AAAA,IAEvF;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,QAAQ,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI;AAAA,QAC3D;AAAA,QACA;AAAA,QACA,OAAO,aAAa,SAAS;AAAA,QAC7B,QAAQ,aAAa,UAAU;AAAA,QAC/B,SAAS,WAAW,UAAU;AAAA,QAC9B,eAAe,WAAW,SAAS;AAAA,QACnC,UAAS;AAAA,QACT;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF,IACE,iBACF;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,YAAY;AAAA,MACtB;AAAA,MACA;AAAA;AAAA,EACF;AAAA;AAAA,IAGA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,WAAW,UAAU;AAAA,QAC9B,eAAe,WAAW,SAAS;AAAA,QACnC;AAAA,QACA;AAAA;AAAA,IACF;AAAA;AAKF,MAAI,aAAa,WAAW;AAC1B,UAAM,UAAU,CAAC,MAA4F;AAC3G,QAAE,eAAe;AACjB,QAAE,gBAAgB;AAClB,YAAM,KAAK,EAAE;AACb,YAAM,MAAM,IAAI,UAAU,oBAAoB,GAAG,aAAa,kBAAkB,KAAK;AACrF,gBAAU,WAAW,GAAG;AAAA,IAC1B;AACA,UAAM,MACJ,oBAAC,YAAO,MAAK,UAAS,WAAW,oBAAoB,WAAW,6BAA6B,EAAE,IAAI,OAAM,+BAA8B,SAAS,SAAS,qCAEzJ;AAIF,QAAI,SAAU,QAAQ,iCAAG;AAAA;AAAA,MAAS;AAAA,OAAI;AAEtC,WACE,qBAAC,UAAK,WAAU,gBAAe,OAAO,EAAE,UAAU,YAAY,SAAS,QAAQ,GAAG,OAAM,+BAA8B,SAAS,SAC5H;AAAA;AAAA,MACA;AAAA,OACH;AAAA,EAEJ;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/media/ImageContext.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useContext, type CSSProperties, type ElementType, type ReactNode } from \"react\";\nimport { useBlockData } from \"../blocks/context\";\nimport type { MediaItem } from \"../content/types\";\n\n/**\n * Optionele framework-beeldcomponent (bv. Next `Image`) voor automatische optimalisatie: responsive\n * `srcset`, moderne formaten (AVIF/WebP), correcte `width/height` (geen layout-shift), lazy-loading.\n * Core importeert `next/image` niet zelf (optionele peer) — de site injecteert 'm via de\n * `imageComponent`-prop op `XiCMSProvider`, net als `linkComponent`. Zonder injectie valt alles terug\n * op een gewone `<img>` (huidig gedrag, werkt overal).\n */\nexport type ImageComponentType = ElementType;\n\nconst ImageContext = createContext<ImageComponentType | null>(null);\n\nexport function ImageProvider({ component, children }: { component?: ImageComponentType; children: ReactNode }) {\n return <ImageContext.Provider value={component ?? null}>{children}</ImageContext.Provider>;\n}\n\nexport function useImageComponent(): ImageComponentType | null {\n return useContext(ImageContext);\n}\n\n/**\n * Inline beeld-bewerken: de visuele editor levert via deze context een `onReplace(field)`-callback\n * voor het GESELECTEERDE blok. Is die actief én geeft een `<Img>` een `editField` mee, dan toont het\n * beeld een vervang-icoontje. Buiten de editor (context = null) rendert `<Img>` exact als voorheen.\n */\n/**\n * `blockKey` komt sinds 7.9 uit de DOM, niet uit een per-blok provider.\n *\n * Tot 7.4 wikkelde de editor élk blok in zijn canvas apart in deze provider, met de blok-key in de\n * closure. Dat canvas is weg — de pagina van de klant ís de editor — en één provider om de hele\n * pagina weet niet meer bij welk blok een foto hoort. De knop lost dat op het moment van klikken\n * op: elk blok draagt al `data-xicms-block` (BlockRenderer), dus `closest()` vanaf de knop is\n * betrouwbaar en kost niets zolang er niemand klikt.\n */\nconst InlineImageEditContext = createContext<((field: string, blockKey?: string) => void) | null>(null);\n\nexport function InlineImageEditProvider({\n onReplace,\n children,\n}: {\n onReplace: ((field: string, blockKey?: string) => void) | null;\n children: ReactNode;\n}) {\n return <InlineImageEditContext.Provider value={onReplace}>{children}</InlineImageEditContext.Provider>;\n}\n\nexport interface ImgProps {\n src: string;\n alt: string;\n /** Intrinsieke breedte/hoogte (verhouding) → nodig voor next/image én tegen CLS. */\n width: number;\n height: number;\n /** Hint voor responsive selectie, bv. \"(max-width: 720px) 100vw, 33vw\". */\n sizes?: string;\n /** Above-the-fold (hero): eager + hoge prioriteit voor LCP. */\n priority?: boolean;\n className?: string;\n style?: CSSProperties;\n /** Blokveld (pad) met de URL — schakelt inline vervangen in de visuele editor in (bv. \"imageUrl\"). */\n editField?: string;\n /** Beeld vult absoluut z'n parent (bv. hero-achtergrond): geen wrapper-box, knop absoluut t.o.v. het blok. */\n editFill?: boolean;\n}\n\n/**\n * Eén beeld-wrapper voor alle blokken. Gebruikt de geïnjecteerde `imageComponent` (next/image) als\n * die er is, anders een gewone `<img>`. De display-grootte regel je met CSS (`style`/`className`,\n * bv. `width:100%;height:100%;object-fit:cover`); `width`/`height` zijn de verhouding.\n */\nfunction style_heeftPositie(style?: CSSProperties): boolean {\n return Boolean(style && \"objectPosition\" in style && style.objectPosition);\n}\n\nexport function Img({ src, alt, width, height, sizes, priority, className, style: styleProp, editField, editFill }: ImgProps) {\n const ImageComponent = useImageComponent();\n const onReplace = useContext(InlineImageEditContext);\n /**\n * Eigen srcset-ladder vóór next/image (7.13). De upload-pipeline (onMediaUpload, sharp) maakt\n * bij élke upload al webp + een maten-ladder; next/image liet dat werk links liggen en\n * transformeerde dezelfde beelden nóg een keer bij Vercel — betaalde image-transformations,\n * plus transformatie-latency op de eerste hit. De route levert daarom per pagina een\n * `media`-seed (url → varianten, zie server/blockData.ts); staat de URL van dit beeld daarin,\n * dan renderen we een kale <picture> met de eigen ladder en blijft Vercel er helemaal buiten.\n * Geen seed (editor, oudere site, extern beeld zoals Unsplash) → het bestaande pad.\n */\n const mediaSeed = useBlockData<Record<string, Pick<MediaItem, \"variants\" | \"width\" | \"height\" | \"focal\">>>(\"media\");\n const eigenLadder = mediaSeed?.[src];\n const ladder = eigenLadder?.variants?.sizes ?? [];\n const webp = eigenLadder?.variants?.webp;\n // Focuspunt (7.24): staat er in het beheer een focuspunt op deze foto, dan snijdt `object-fit:\n // cover` rond dát punt bij in plaats van rond het midden. Alleen als het blok zelf geen\n // object-position zet — een blok dat bewust \"top\" wil, wint.\n const focal = eigenLadder?.focal;\n const style = focal && !style_heeftPositie(styleProp)\n ? { ...styleProp, objectPosition: `${Math.round(focal.x * 100)}% ${Math.round(focal.y * 100)}%` }\n : styleProp;\n const imageEl = ladder.length > 0 ? (\n <picture>\n {webp && <source type=\"image/webp\" srcSet={`${webp.url} ${webp.width}w`} sizes={sizes} />}\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img\n src={src}\n srcSet={ladder.map((v) => `${v.url} ${v.width}w`).join(\", \")}\n sizes={sizes}\n alt={alt}\n width={eigenLadder?.width ?? width}\n height={eigenLadder?.height ?? height}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n decoding=\"async\"\n className={className}\n style={style}\n />\n </picture>\n ) : ImageComponent ? (\n <ImageComponent\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n priority={priority || undefined}\n className={className}\n style={style}\n />\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={src}\n alt={alt}\n width={width}\n height={height}\n sizes={sizes}\n loading={priority ? \"eager\" : \"lazy\"}\n fetchPriority={priority ? \"high\" : undefined}\n className={className}\n style={style}\n />\n );\n\n // In de visuele editor (geselecteerd blok): toon een vervang-icoontje over het beeld. We wijzigen\n // alleen de URL → de grootte/stijl blijft (die komt uit het blok), dus blok én beeld behouden hun maat.\n if (onReplace && editField) {\n const replace = (e: { preventDefault: () => void; stopPropagation: () => void; currentTarget?: unknown }) => {\n e.preventDefault();\n e.stopPropagation();\n const el = e.currentTarget as HTMLElement | undefined;\n const key = el?.closest?.(\"[data-xicms-block]\")?.getAttribute(\"data-xicms-block\") ?? undefined;\n onReplace(editField, key);\n };\n const btn = (\n <button type=\"button\" className={`xicms-ve-img__btn${editFill ? \" xicms-ve-img__btn--fill\" : \"\"}`} title=\"Klik om de foto te wijzigen\" onClick={replace}>\n 📷 Foto wijzigen\n </button>\n );\n // Vullend (hero-achtergrond): geen wrapper-box (zou het absoluut gepositioneerde beeld breken);\n // de knop is absoluut t.o.v. het blok-element zelf (dat is `position:relative`).\n if (editFill) return (<>{imageEl}{btn}</>);\n // In-flow beeld: hele beeld klikbaar (duidelijke affordance) + knop rechtsboven.\n return (\n <span className=\"xicms-ve-img\" style={{ position: \"relative\", display: \"block\" }} title=\"Klik om de foto te wijzigen\" onClick={replace}>\n {imageEl}\n {btn}\n </span>\n );\n }\n return imageEl;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * "Wat is nieuw" (7.24) — korte, klantgerichte notities per release, getoond in /beheer.
3
+ *
4
+ * Features die je bouwt zonder dat de klant het hoort, bestaan voor die klant niet. Dit is de
5
+ * enige plek waar dat verteld wordt; `CoreVersion` toont de notitie van de huidige minor één keer.
6
+ * Alleen wat de KLANT merkt, in zijn woorden — geen interne refactors.
7
+ */
8
+ export interface ReleaseNote {
9
+ /** major.minor — patches tellen niet als "nieuw". */
10
+ versie: string;
11
+ punten: string[];
12
+ }
13
+ export declare const RELEASE_NOTES: ReleaseNote[];
14
+ /** De notitie die bij deze engine-versie hoort (op major.minor). */
15
+ export declare function releaseNoteVoor(versie: string): ReleaseNote | null;
@@ -0,0 +1,28 @@
1
+ const RELEASE_NOTES = [
2
+ {
3
+ versie: "7.24",
4
+ punten: [
5
+ "Het menu dat je onder Menu instelt staat nu \xE9cht op de site \u2014 met submenu's.",
6
+ "Bij Pagina's zie je meteen welke pagina's wijzigingen hebben die nog niet live staan.",
7
+ "Een pagina kun je offline halen (en later weer publiceren) of naar de prullenbak doen \u2014 30 dagen te herstellen.",
8
+ "Verander je het adres van een pagina, dan verwijst het oude adres automatisch door.",
9
+ "Deel een preview-link van een werkversie met iemand zonder account.",
10
+ "Kies een focuspunt op een foto zodat het juiste deel in beeld blijft op mobiel.",
11
+ "Formulieren: inzendingen exporteren als CSV, verwijderen, en zien wat nieuw is.",
12
+ "Statistieken tonen nu ook waar bezoekers vandaan komen en op welk apparaat."
13
+ ]
14
+ },
15
+ {
16
+ versie: "7.23",
17
+ punten: ["Per productoptie (bijvoorbeeld Print of Kleur) kun je aangeven dat elke waarde een eigen foto heeft."]
18
+ }
19
+ ];
20
+ function releaseNoteVoor(versie) {
21
+ const mm = versie.split(".").slice(0, 2).join(".");
22
+ return RELEASE_NOTES.find((n) => n.versie === mm) ?? null;
23
+ }
24
+ export {
25
+ RELEASE_NOTES,
26
+ releaseNoteVoor
27
+ };
28
+ //# sourceMappingURL=releaseNotes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/releaseNotes.ts"],"sourcesContent":["/**\n * \"Wat is nieuw\" (7.24) — korte, klantgerichte notities per release, getoond in /beheer.\n *\n * Features die je bouwt zonder dat de klant het hoort, bestaan voor die klant niet. Dit is de\n * enige plek waar dat verteld wordt; `CoreVersion` toont de notitie van de huidige minor één keer.\n * Alleen wat de KLANT merkt, in zijn woorden — geen interne refactors.\n */\nexport interface ReleaseNote {\n /** major.minor — patches tellen niet als \"nieuw\". */\n versie: string;\n punten: string[];\n}\n\nexport const RELEASE_NOTES: ReleaseNote[] = [\n {\n versie: \"7.24\",\n punten: [\n \"Het menu dat je onder Menu instelt staat nu écht op de site — met submenu's.\",\n \"Bij Pagina's zie je meteen welke pagina's wijzigingen hebben die nog niet live staan.\",\n \"Een pagina kun je offline halen (en later weer publiceren) of naar de prullenbak doen — 30 dagen te herstellen.\",\n \"Verander je het adres van een pagina, dan verwijst het oude adres automatisch door.\",\n \"Deel een preview-link van een werkversie met iemand zonder account.\",\n \"Kies een focuspunt op een foto zodat het juiste deel in beeld blijft op mobiel.\",\n \"Formulieren: inzendingen exporteren als CSV, verwijderen, en zien wat nieuw is.\",\n \"Statistieken tonen nu ook waar bezoekers vandaan komen en op welk apparaat.\",\n ],\n },\n {\n versie: \"7.23\",\n punten: [\"Per productoptie (bijvoorbeeld Print of Kleur) kun je aangeven dat elke waarde een eigen foto heeft.\"],\n },\n];\n\n/** De notitie die bij deze engine-versie hoort (op major.minor). */\nexport function releaseNoteVoor(versie: string): ReleaseNote | null {\n const mm = versie.split(\".\").slice(0, 2).join(\".\");\n return RELEASE_NOTES.find((n) => n.versie === mm) ?? null;\n}\n"],"mappings":"AAaO,MAAM,gBAA+B;AAAA,EAC1C;AAAA,IACE,QAAQ;AAAA,IACR,QAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,QAAQ;AAAA,IACR,QAAQ,CAAC,sGAAsG;AAAA,EACjH;AACF;AAGO,SAAS,gBAAgB,QAAoC;AAClE,QAAM,KAAK,OAAO,MAAM,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG;AACjD,SAAO,cAAc,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK;AACvD;","names":[]}
@@ -11,6 +11,15 @@ import * as z from "zod/mini";
11
11
  * die uit de pas kan lopen met de href. Het beheerscherm mag de gebruiker wél die keuze voorleggen;
12
12
  * wat er wordt opgeslagen is één ondubbelzinnig adres.
13
13
  */
14
+ declare const NavLeafSchema: z.ZodMiniObject<{
15
+ label: z.ZodMiniString<string>;
16
+ href: z.ZodMiniString<string>;
17
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
18
+ _blank: "_blank";
19
+ _embed: "_embed";
20
+ _self: "_self";
21
+ }>>;
22
+ }, z.core.$strip>;
14
23
  export declare const NavItemSchema: z.ZodMiniObject<{
15
24
  label: z.ZodMiniString<string>;
16
25
  href: z.ZodMiniString<string>;
@@ -19,10 +28,43 @@ export declare const NavItemSchema: z.ZodMiniObject<{
19
28
  _embed: "_embed";
20
29
  _self: "_self";
21
30
  }>>;
31
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
32
+ label: z.ZodMiniString<string>;
33
+ href: z.ZodMiniString<string>;
34
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
35
+ _blank: "_blank";
36
+ _embed: "_embed";
37
+ _self: "_self";
38
+ }>>;
39
+ }, z.core.$strip>>>;
22
40
  }, z.core.$strip>;
23
41
  export type NavItem = z.infer<typeof NavItemSchema>;
42
+ export type NavLeaf = z.infer<typeof NavLeafSchema>;
24
43
  /** Verwijst dit item naar buiten? Afgeleid uit de href, zodat er geen tweede waarheid ontstaat. */
25
44
  export declare function isExterneLink(href: string): boolean;
45
+ /**
46
+ * Eén doorverwijzing (7.24): een oud pad dat permanent naar een nieuw pad wijst.
47
+ *
48
+ * Ontstaat vanzelf wanneer een klant het pad van een pagina wijzigt — de oude URL staat in
49
+ * zoekmachines, in mails en op visitekaartjes, en zonder redirect wordt dat een 404. Alleen
50
+ * interne paden (beginnen met `/`): een open redirect naar buiten is een phishing-hulpmiddel.
51
+ */
52
+ export declare const RedirectSchema: z.ZodMiniObject<{
53
+ from: z.ZodMiniString<string>;
54
+ to: z.ZodMiniString<string>;
55
+ }, z.core.$strip>;
56
+ export type Redirect = z.infer<typeof RedirectSchema>;
57
+ /**
58
+ * Normaliseert een pad zoals de catch-all dat doet ("/over-ons/" → "/over-ons", "" → "/"), zodat
59
+ * een redirect matcht ongeacht een afsluitende slash.
60
+ */
61
+ export declare function normaliseerPad(pad: string): string;
62
+ /**
63
+ * Zoekt de bestemming voor een pad in de redirect-lijst. Volgt ketens (a→b, b→c) tot een diepte
64
+ * van vijf en weigert lussen: een klant die eerst /a naar /b hernoemt en later terug, mag daar
65
+ * geen eindeloze redirect aan overhouden. `null` = geen doorverwijzing.
66
+ */
67
+ export declare function zoekRedirect(pad: string, redirects: Redirect[] | undefined): string | null;
26
68
  /** Het pad waarop een ingekaderde externe pagina wordt getoond. */
27
69
  export declare const EMBED_PAD = "/extern";
28
70
  /**
@@ -115,6 +157,15 @@ export declare const FooterColumnSchema: z.ZodMiniObject<{
115
157
  _embed: "_embed";
116
158
  _self: "_self";
117
159
  }>>;
160
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
161
+ label: z.ZodMiniString<string>;
162
+ href: z.ZodMiniString<string>;
163
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
164
+ _blank: "_blank";
165
+ _embed: "_embed";
166
+ _self: "_self";
167
+ }>>;
168
+ }, z.core.$strip>>>;
118
169
  }, z.core.$strip>>>;
119
170
  }, z.core.$strip>;
120
171
  /** Bewerkbare footer (klant past dit aan in /beheer; core levert de unstyled `SiteFooter`). */
@@ -129,6 +180,15 @@ export declare const FooterSettingsSchema: z.ZodMiniObject<{
129
180
  _embed: "_embed";
130
181
  _self: "_self";
131
182
  }>>;
183
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
184
+ label: z.ZodMiniString<string>;
185
+ href: z.ZodMiniString<string>;
186
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
187
+ _blank: "_blank";
188
+ _embed: "_embed";
189
+ _self: "_self";
190
+ }>>;
191
+ }, z.core.$strip>>>;
132
192
  }, z.core.$strip>>>;
133
193
  }, z.core.$strip>>>;
134
194
  social: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
@@ -157,6 +217,15 @@ export declare const SiteSettingsSchema: z.ZodMiniObject<{
157
217
  _embed: "_embed";
158
218
  _self: "_self";
159
219
  }>>;
220
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
221
+ label: z.ZodMiniString<string>;
222
+ href: z.ZodMiniString<string>;
223
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
224
+ _blank: "_blank";
225
+ _embed: "_embed";
226
+ _self: "_self";
227
+ }>>;
228
+ }, z.core.$strip>>>;
160
229
  }, z.core.$strip>>>;
161
230
  theme: z.ZodMiniOptional<z.ZodMiniObject<{
162
231
  primary: z.ZodMiniString<string>;
@@ -223,6 +292,15 @@ export declare const SiteSettingsSchema: z.ZodMiniObject<{
223
292
  _embed: "_embed";
224
293
  _self: "_self";
225
294
  }>>;
295
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
296
+ label: z.ZodMiniString<string>;
297
+ href: z.ZodMiniString<string>;
298
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
299
+ _blank: "_blank";
300
+ _embed: "_embed";
301
+ _self: "_self";
302
+ }>>;
303
+ }, z.core.$strip>>>;
226
304
  }, z.core.$strip>>>;
227
305
  }, z.core.$strip>>>;
228
306
  social: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
@@ -240,6 +318,10 @@ export declare const SiteSettingsSchema: z.ZodMiniObject<{
240
318
  label: z.ZodMiniString<string>;
241
319
  href: z.ZodMiniString<string>;
242
320
  }, z.core.$strip>>>;
321
+ redirects: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
322
+ from: z.ZodMiniString<string>;
323
+ to: z.ZodMiniString<string>;
324
+ }, z.core.$strip>>>;
243
325
  }, z.core.$strip>;
244
326
  export type SiteSettings = z.infer<typeof SiteSettingsSchema>;
245
327
  /**
@@ -257,6 +339,15 @@ export declare const PublicSiteSettingsSchema: z.ZodMiniObject<{
257
339
  _embed: "_embed";
258
340
  _self: "_self";
259
341
  }>>;
342
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
343
+ label: z.ZodMiniString<string>;
344
+ href: z.ZodMiniString<string>;
345
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
346
+ _blank: "_blank";
347
+ _embed: "_embed";
348
+ _self: "_self";
349
+ }>>;
350
+ }, z.core.$strip>>>;
260
351
  }, z.core.$strip>>>;
261
352
  theme: z.ZodMiniOptional<z.ZodMiniObject<{
262
353
  primary: z.ZodMiniString<string>;
@@ -314,6 +405,15 @@ export declare const PublicSiteSettingsSchema: z.ZodMiniObject<{
314
405
  _embed: "_embed";
315
406
  _self: "_self";
316
407
  }>>;
408
+ children: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
409
+ label: z.ZodMiniString<string>;
410
+ href: z.ZodMiniString<string>;
411
+ target: z.ZodMiniOptional<z.ZodMiniEnum<{
412
+ _blank: "_blank";
413
+ _embed: "_embed";
414
+ _self: "_self";
415
+ }>>;
416
+ }, z.core.$strip>>>;
317
417
  }, z.core.$strip>>>;
318
418
  }, z.core.$strip>>>;
319
419
  social: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
@@ -327,7 +427,12 @@ export declare const PublicSiteSettingsSchema: z.ZodMiniObject<{
327
427
  enabled: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
328
428
  config: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniUnknown>>;
329
429
  }, z.core.$strip>]>>>;
430
+ redirects: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniObject<{
431
+ from: z.ZodMiniString<string>;
432
+ to: z.ZodMiniString<string>;
433
+ }, z.core.$strip>>>;
330
434
  }, z.core.$strip>;
331
435
  export type PublicSiteSettings = z.infer<typeof PublicSiteSettingsSchema>;
332
436
  /** Haalt de publiek-veilige subset uit de volledige settings (voor het `settings/public`-doc). */
333
437
  export declare function toPublicSettings(s: SiteSettings): PublicSiteSettings;
438
+ export {};