@xiwebdesign/xicms-core 7.9.1 → 7.11.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/editor/EditorProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState, lazy, Suspense, type ReactNode } from \"react\";\n// Het bloktype van de PAGINA zelf, zodat wat hier langskomt per definitie past in wat\n// `PublishedPage` rendert. Een eigen definitie zou hier stil uit de pas kunnen lopen.\nimport type { Page } from \"../schema/pages\";\n\ntype PaginaBlokken = Page[\"blocks\"];\n\n/**\n * Lichte runtime-context voor het inline (visuele) bewerken op de live pagina. De echte editor\n * (TipTap/Radix, zwaar) wordt PAS geladen als een editor hem opent — publieke bezoekers downloaden\n * hem nooit. De editor leeft in de admin-entry; we laden 'm via een dynamische import (eigen chunk).\n *\n * ── WAAROM DE BLOKKEN HIER LANGSKOMEN ─────────────────────────────────────────────────────\n *\n * De editor tekende de pagina vroeger zélf, in een eigen canvas binnen een schermvullende overlay.\n * Je klikte \"Pagina bewerken\" en je site verdween achter een donker paneel met een nabootsing erin.\n * Dat voelde niet als je site bewerken maar als een ander programma openen.\n *\n * Nu blijft de PAGINA de pagina en wordt alleen de DATA vervangen: de editor laadt de werkversie en\n * zet die hier neer, waarna `PublishedPage` de werkversie rendert in plaats van de gepubliceerde\n * blokken. Dezelfde componenten, dezelfde huisstijl, dezelfde layout — alleen andere inhoud.\n *\n * Die omweg via de data is wezenlijk, geen implementatiedetail. Alleen potloodjes over de live\n * pagina leggen zou betekenen dat je de WERKVERSIE bewerkt terwijl je de GEPUBLICEERDE tekst ziet.\n * Zodra je iets wijzigt en nog niet publiceert, lopen die twee uit elkaar en bewerk je iets anders\n * dan er staat. Wat je ziet moet zijn wat je bewerkt.\n *\n * `editPath` hoort erbij: een site kan meerdere gerenderde pagina's in beeld hebben (een\n * ingekaderde pagina, een preview). Alleen het pad dat de editor open heeft mag zijn blokken\n * verwisselen.\n *\n * ── WAAROM DE MODUS EEN REFRESH MOET OVERLEVEN (7.8) ──────────────────────────────────────\n *\n * Bewerken was een toestand die alleen in het geheugen van één render-boom bestond. Een per ongeluk\n * ingedrukte F5 — of een klik op een link in je eigen site, want dat is een volledige navigatie —\n * zette je terug op de live site. Je wijzigingen waren niet weg (die staan in de werkversie in\n * Firestore), maar je zág ze niet meer, en dat is niet te onderscheiden van kwijt zijn.\n *\n * Daarom staat de modus in `sessionStorage`: hij overleeft een refresh en het navigeren door de\n * site, en verdwijnt vanzelf zodra het tabblad dichtgaat. Bewust niet `localStorage` — dan zit je\n * morgen in een nieuw venster nog steeds in de bewerkmodus zonder dat je erom vroeg.\n */\nconst VisualEditor = lazy(() => import(\"../admin/visual/VisualEditor\"));\n\n/** \"live\" = precies wat een bezoeker ziet. \"bewerken\" = dezelfde pagina, met werkversie en gereedschap. */\nexport type Bewerkmodus = \"live\" | \"bewerken\";\n\nconst OPSLAGSLEUTEL = \"xicms-bewerkmodus\";\n\ninterface EditorCtx {\n modus: Bewerkmodus;\n /** Blijft bestaan naast `modus` omdat het door de hele codebase heen gelezen wordt. */\n open: boolean;\n zetModus: (modus: Bewerkmodus) => void;\n openEditor: () => void;\n closeEditor: () => void;\n /** De blokken van de werkversie die de pagina moet tonen; `null` = toon gewoon het gepubliceerde. */\n draftBlocks: PaginaBlokken | null;\n /** Het pad waarvoor `draftBlocks` geldt. */\n editPath: string | null;\n /** Door de editor aangeroepen zodra de werkversie laadt of wijzigt. */\n publishDraft: (path: string, blocks: PaginaBlokken | null) => void;\n /**\n * \"Vervang de foto in veld X van blok Y\" — gezet door de VisualEditor zolang hij open is.\n *\n * De 📷-knopjes op de foto's zitten in de RENDER van de pagina (Img), de mediakiezer zit in de\n * EDITOR. Die twee delen geen boom meer sinds het canvas weg is; dit is de brug. `null` buiten\n * de bewerkmodus, en dan tekent Img geen knopjes.\n */\n fotoVervanger: ((field: string, blockKey?: string) => void) | null;\n zetFotoVervanger: (fn: ((field: string, blockKey?: string) => void) | null) => void;\n}\n\nconst Ctx = createContext<EditorCtx>({\n modus: \"live\",\n open: false,\n zetModus: () => {},\n openEditor: () => {},\n closeEditor: () => {},\n draftBlocks: null,\n editPath: null,\n publishDraft: () => {},\n fotoVervanger: null,\n zetFotoVervanger: () => {},\n});\n\nexport function EditorProvider({ children }: { children: ReactNode }) {\n /**\n * Start altijd op \"live\", ook als de opslag iets anders zegt.\n *\n * De server rendert deze boom zonder toegang tot `sessionStorage`. Zou de eerste client-render\n * meteen \"bewerken\" teruggeven, dan wijkt hij af van de HTML die er al staat en gooit React de\n * boom weg (hydration mismatch). Vandaar: eerst hetzelfde als de server, daarna in een effect\n * bijstellen. Eén frame \"live\" is niet zichtbaar; een kapotte hydration wel.\n */\n const [modus, setModusState] = useState<Bewerkmodus>(\"live\");\n const [draft, setDraft] = useState<{ path: string; blocks: PaginaBlokken } | null>(null);\n const [fotoVervanger, setFotoVervanger] = useState<((field: string, blockKey?: string) => void) | null>(null);\n // In state gewikkeld: een functie rechtstreeks aan een setState geven zou hem AANROEPEN\n // (functionele updater) in plaats van opslaan.\n const zetFotoVervanger = useCallback((fn: ((field: string, blockKey?: string) => void) | null) => {\n setFotoVervanger(() => fn);\n }, []);\n\n useEffect(() => {\n try {\n if (sessionStorage.getItem(OPSLAGSLEUTEL) === \"bewerken\") setModusState(\"bewerken\");\n } catch {\n /* privémodus of geblokkeerde opslag: dan gewoon op de live site beginnen */\n }\n }, []);\n\n const zetModus = useCallback((next: Bewerkmodus) => {\n setModusState(next);\n try {\n if (next === \"bewerken\") sessionStorage.setItem(OPSLAGSLEUTEL, \"bewerken\");\n else sessionStorage.removeItem(OPSLAGSLEUTEL);\n } catch {\n /* de modus werkt ook zonder opslag; alleen een refresh valt dan terug op live */\n }\n // De werkversie hoort bij de bewerkmodus. Blijft hij staan na het sluiten, dan ziet de bezoeker\n // ongepubliceerde tekst terwijl het gereedschap weg is — een pagina die iets toont wat niet\n // live is. De werkversie zelf blijft in Firestore staan; alleen deze weergave stopt.\n if (next === \"live\") setDraft(null);\n }, []);\n\n const publishDraft = useCallback((path: string, blocks: PaginaBlokken | null) => {\n setDraft(blocks ? { path, blocks } : null);\n }, []);\n\n const value = useMemo<EditorCtx>(() => ({\n modus,\n open: modus === \"bewerken\",\n zetModus,\n openEditor: () => zetModus(\"bewerken\"),\n closeEditor: () => zetModus(\"live\"),\n draftBlocks: draft?.blocks ?? null,\n editPath: draft?.path ?? null,\n publishDraft,\n fotoVervanger,\n zetFotoVervanger,\n }), [modus, draft, zetModus, publishDraft, fotoVervanger, zetFotoVervanger]);\n\n return (\n <Ctx.Provider value={value}>\n {children}\n {modus === \"bewerken\" && (\n <Suspense fallback={null}>\n <VisualEditor onClose={() => zetModus(\"live\")} />\n </Suspense>\n )}\n </Ctx.Provider>\n );\n}\n\n/** Open/sluit de visuele pagina-editor. */\nexport function useEditor(): EditorCtx {\n return useContext(Ctx);\n}\n"],"mappings":";AAkJI,SAIM,KAJN;AAhJJ,SAAS,eAAe,aAAa,YAAY,WAAW,SAAS,UAAU,MAAM,gBAAgC;AA0CrH,MAAM,eAAe,KAAK,MAAM,OAAO,8BAA8B,CAAC;AAKtE,MAAM,gBAAgB;AA0BtB,MAAM,MAAM,cAAyB;AAAA,EACnC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,YAAY,MAAM;AAAA,EAAC;AAAA,EACnB,aAAa,MAAM;AAAA,EAAC;AAAA,EACpB,aAAa;AAAA,EACb,UAAU;AAAA,EACV,cAAc,MAAM;AAAA,EAAC;AAAA,EACrB,eAAe;AAAA,EACf,kBAAkB,MAAM;AAAA,EAAC;AAC3B,CAAC;AAEM,SAAS,eAAe,EAAE,SAAS,GAA4B;AASpE,QAAM,CAAC,OAAO,aAAa,IAAI,SAAsB,MAAM;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyD,IAAI;AACvF,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAA8D,IAAI;AAG5G,QAAM,mBAAmB,YAAY,CAAC,OAA4D;AAChG,qBAAiB,MAAM,EAAE;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI;AACF,UAAI,eAAe,QAAQ,aAAa,MAAM,WAAY,eAAc,UAAU;AAAA,IACpF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW,YAAY,CAAC,SAAsB;AAClD,kBAAc,IAAI;AAClB,QAAI;AACF,UAAI,SAAS,WAAY,gBAAe,QAAQ,eAAe,UAAU;AAAA,UACpE,gBAAe,WAAW,aAAa;AAAA,IAC9C,QAAQ;AAAA,IAER;AAIA,QAAI,SAAS,OAAQ,UAAS,IAAI;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,YAAY,CAAC,MAAc,WAAiC;AAC/E,aAAS,SAAS,EAAE,MAAM,OAAO,IAAI,IAAI;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,QAAmB,OAAO;AAAA,IACtC;AAAA,IACA,MAAM,UAAU;AAAA,IAChB;AAAA,IACA,YAAY,MAAM,SAAS,UAAU;AAAA,IACrC,aAAa,MAAM,SAAS,MAAM;AAAA,IAClC,aAAa,OAAO,UAAU;AAAA,IAC9B,UAAU,OAAO,QAAQ;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,CAAC,OAAO,OAAO,UAAU,cAAc,eAAe,gBAAgB,CAAC;AAE3E,SACE,qBAAC,IAAI,UAAJ,EAAa,OACX;AAAA;AAAA,IACA,UAAU,cACT,oBAAC,YAAS,UAAU,MAClB,8BAAC,gBAAa,SAAS,MAAM,SAAS,MAAM,GAAG,GACjD;AAAA,KAEJ;AAEJ;AAGO,SAAS,YAAuB;AACrC,SAAO,WAAW,GAAG;AACvB;","names":[]}
1
+ {"version":3,"sources":["../../src/editor/EditorProvider.tsx"],"sourcesContent":["\"use client\";\n\nimport { createContext, useCallback, useContext, useEffect, useMemo, useState, type ComponentType, type ReactNode } from \"react\";\n// Het bloktype van de PAGINA zelf, zodat wat hier langskomt per definitie past in wat\n// `PublishedPage` rendert. Een eigen definitie zou hier stil uit de pas kunnen lopen.\nimport type { Page } from \"../schema/pages\";\n\ntype PaginaBlokken = Page[\"blocks\"];\n\n/**\n * Lichte runtime-context voor het inline (visuele) bewerken op de live pagina. De echte editor\n * (TipTap/Radix, zwaar) wordt PAS geladen als een editor hem opent — publieke bezoekers downloaden\n * hem nooit. De editor leeft in de admin-entry; we laden 'm via een dynamische import (eigen chunk).\n *\n * ── WAAROM DE BLOKKEN HIER LANGSKOMEN ─────────────────────────────────────────────────────\n *\n * De editor tekende de pagina vroeger zélf, in een eigen canvas binnen een schermvullende overlay.\n * Je klikte \"Pagina bewerken\" en je site verdween achter een donker paneel met een nabootsing erin.\n * Dat voelde niet als je site bewerken maar als een ander programma openen.\n *\n * Nu blijft de PAGINA de pagina en wordt alleen de DATA vervangen: de editor laadt de werkversie en\n * zet die hier neer, waarna `PublishedPage` de werkversie rendert in plaats van de gepubliceerde\n * blokken. Dezelfde componenten, dezelfde huisstijl, dezelfde layout — alleen andere inhoud.\n *\n * Die omweg via de data is wezenlijk, geen implementatiedetail. Alleen potloodjes over de live\n * pagina leggen zou betekenen dat je de WERKVERSIE bewerkt terwijl je de GEPUBLICEERDE tekst ziet.\n * Zodra je iets wijzigt en nog niet publiceert, lopen die twee uit elkaar en bewerk je iets anders\n * dan er staat. Wat je ziet moet zijn wat je bewerkt.\n *\n * `editPath` hoort erbij: een site kan meerdere gerenderde pagina's in beeld hebben (een\n * ingekaderde pagina, een preview). Alleen het pad dat de editor open heeft mag zijn blokken\n * verwisselen.\n *\n * ── WAAROM DE MODUS EEN REFRESH MOET OVERLEVEN (7.8) ──────────────────────────────────────\n *\n * Bewerken was een toestand die alleen in het geheugen van één render-boom bestond. Een per ongeluk\n * ingedrukte F5 — of een klik op een link in je eigen site, want dat is een volledige navigatie —\n * zette je terug op de live site. Je wijzigingen waren niet weg (die staan in de werkversie in\n * Firestore), maar je zág ze niet meer, en dat is niet te onderscheiden van kwijt zijn.\n *\n * Daarom staat de modus in `sessionStorage`: hij overleeft een refresh en het navigeren door de\n * site, en verdwijnt vanzelf zodra het tabblad dichtgaat. Bewust niet `localStorage` — dan zit je\n * morgen in een nieuw venster nog steeds in de bewerkmodus zonder dat je erom vroeg.\n */\n/**\n * BEWUST GEEN `lazy()` + `<Suspense>`. De chunk-splitsing is dezelfde (de dynamische import\n * hieronder maakt zijn eigen chunk), maar Suspense heeft een gedraging die ons hier de kop kostte:\n * als de boom suspendt, koppelt React de al-gemonteerde children tijdelijk los en weer vast\n * (disconnect/reconnect van de passive effects). In dat her-aansluitpad draaide Astryx'\n * `useScrollLock`-effect dubbel zonder tussenliggende cleanup, waardoor zijn module-brede teller\n * nooit meer op nul kwam — en een teller boven nul betekent: `body { position: fixed }`. De hele\n * site stond stil: niet meer scrollen, dus ook geen bewerkknoppen bij blokken onder de vouw.\n * Zelf laden in een effect en pas renderen als de module er is, heeft dat Suspense-pad niet.\n */\ntype VisualEditorComponent = ComponentType<{ onClose: () => void }>;\nlet visualEditorModule: VisualEditorComponent | null = null;\n\n/** \"live\" = precies wat een bezoeker ziet. \"bewerken\" = dezelfde pagina, met werkversie en gereedschap. */\nexport type Bewerkmodus = \"live\" | \"bewerken\";\n\nconst OPSLAGSLEUTEL = \"xicms-bewerkmodus\";\n\ninterface EditorCtx {\n modus: Bewerkmodus;\n /** Blijft bestaan naast `modus` omdat het door de hele codebase heen gelezen wordt. */\n open: boolean;\n zetModus: (modus: Bewerkmodus) => void;\n openEditor: () => void;\n closeEditor: () => void;\n /** De blokken van de werkversie die de pagina moet tonen; `null` = toon gewoon het gepubliceerde. */\n draftBlocks: PaginaBlokken | null;\n /** Het pad waarvoor `draftBlocks` geldt. */\n editPath: string | null;\n /** Door de editor aangeroepen zodra de werkversie laadt of wijzigt. */\n publishDraft: (path: string, blocks: PaginaBlokken | null) => void;\n /**\n * \"Vervang de foto in veld X van blok Y\" — gezet door de VisualEditor zolang hij open is.\n *\n * De 📷-knopjes op de foto's zitten in de RENDER van de pagina (Img), de mediakiezer zit in de\n * EDITOR. Die twee delen geen boom meer sinds het canvas weg is; dit is de brug. `null` buiten\n * de bewerkmodus, en dan tekent Img geen knopjes.\n */\n fotoVervanger: ((field: string, blockKey?: string) => void) | null;\n zetFotoVervanger: (fn: ((field: string, blockKey?: string) => void) | null) => void;\n}\n\nconst Ctx = createContext<EditorCtx>({\n modus: \"live\",\n open: false,\n zetModus: () => {},\n openEditor: () => {},\n closeEditor: () => {},\n draftBlocks: null,\n editPath: null,\n publishDraft: () => {},\n fotoVervanger: null,\n zetFotoVervanger: () => {},\n});\n\nexport function EditorProvider({ children }: { children: ReactNode }) {\n /**\n * Start altijd op \"live\", ook als de opslag iets anders zegt.\n *\n * De server rendert deze boom zonder toegang tot `sessionStorage`. Zou de eerste client-render\n * meteen \"bewerken\" teruggeven, dan wijkt hij af van de HTML die er al staat en gooit React de\n * boom weg (hydration mismatch). Vandaar: eerst hetzelfde als de server, daarna in een effect\n * bijstellen. Eén frame \"live\" is niet zichtbaar; een kapotte hydration wel.\n */\n const [modus, setModusState] = useState<Bewerkmodus>(\"live\");\n // De editor-component zodra zijn chunk binnen is; module-cache zodat een tweede keer openen\n // (of een andere pagina in dezelfde sessie) niet opnieuw op het netwerk wacht.\n const [VisualEditor, setVisualEditor] = useState<VisualEditorComponent | null>(() => visualEditorModule);\n const [draft, setDraft] = useState<{ path: string; blocks: PaginaBlokken } | null>(null);\n const [fotoVervanger, setFotoVervanger] = useState<((field: string, blockKey?: string) => void) | null>(null);\n // In state gewikkeld: een functie rechtstreeks aan een setState geven zou hem AANROEPEN\n // (functionele updater) in plaats van opslaan.\n const zetFotoVervanger = useCallback((fn: ((field: string, blockKey?: string) => void) | null) => {\n setFotoVervanger(() => fn);\n }, []);\n\n useEffect(() => {\n try {\n if (sessionStorage.getItem(OPSLAGSLEUTEL) === \"bewerken\") setModusState(\"bewerken\");\n } catch {\n /* privémodus of geblokkeerde opslag: dan gewoon op de live site beginnen */\n }\n }, []);\n\n // De editor-chunk laden zodra de bewerkmodus aangaat (zie het commentaar bij\n // `visualEditorModule` waarom dit met de hand gebeurt en niet via lazy/Suspense).\n useEffect(() => {\n if (modus !== \"bewerken\" || VisualEditor) return;\n let losgekoppeld = false;\n void import(\"../admin/visual/VisualEditor\").then((m) => {\n visualEditorModule = m.default;\n if (!losgekoppeld) setVisualEditor(() => m.default);\n });\n return () => {\n losgekoppeld = true;\n };\n }, [modus, VisualEditor]);\n\n const zetModus = useCallback((next: Bewerkmodus) => {\n setModusState(next);\n try {\n if (next === \"bewerken\") sessionStorage.setItem(OPSLAGSLEUTEL, \"bewerken\");\n else sessionStorage.removeItem(OPSLAGSLEUTEL);\n } catch {\n /* de modus werkt ook zonder opslag; alleen een refresh valt dan terug op live */\n }\n // De werkversie hoort bij de bewerkmodus. Blijft hij staan na het sluiten, dan ziet de bezoeker\n // ongepubliceerde tekst terwijl het gereedschap weg is — een pagina die iets toont wat niet\n // live is. De werkversie zelf blijft in Firestore staan; alleen deze weergave stopt.\n if (next === \"live\") setDraft(null);\n }, []);\n\n const publishDraft = useCallback((path: string, blocks: PaginaBlokken | null) => {\n setDraft(blocks ? { path, blocks } : null);\n }, []);\n\n const value = useMemo<EditorCtx>(() => ({\n modus,\n open: modus === \"bewerken\",\n zetModus,\n openEditor: () => zetModus(\"bewerken\"),\n closeEditor: () => zetModus(\"live\"),\n draftBlocks: draft?.blocks ?? null,\n editPath: draft?.path ?? null,\n publishDraft,\n fotoVervanger,\n zetFotoVervanger,\n }), [modus, draft, zetModus, publishDraft, fotoVervanger, zetFotoVervanger]);\n\n return (\n <Ctx.Provider value={value}>\n {children}\n {modus === \"bewerken\" && VisualEditor && <VisualEditor onClose={() => zetModus(\"live\")} />}\n </Ctx.Provider>\n );\n}\n\n/** Open/sluit de visuele pagina-editor. */\nexport function useEditor(): EditorCtx {\n return useContext(Ctx);\n}\n"],"mappings":";AA8KI,SAE2C,KAF3C;AA5KJ,SAAS,eAAe,aAAa,YAAY,WAAW,SAAS,gBAAoD;AAqDzH,IAAI,qBAAmD;AAKvD,MAAM,gBAAgB;AA0BtB,MAAM,MAAM,cAAyB;AAAA,EACnC,OAAO;AAAA,EACP,MAAM;AAAA,EACN,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,YAAY,MAAM;AAAA,EAAC;AAAA,EACnB,aAAa,MAAM;AAAA,EAAC;AAAA,EACpB,aAAa;AAAA,EACb,UAAU;AAAA,EACV,cAAc,MAAM;AAAA,EAAC;AAAA,EACrB,eAAe;AAAA,EACf,kBAAkB,MAAM;AAAA,EAAC;AAC3B,CAAC;AAEM,SAAS,eAAe,EAAE,SAAS,GAA4B;AASpE,QAAM,CAAC,OAAO,aAAa,IAAI,SAAsB,MAAM;AAG3D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAuC,MAAM,kBAAkB;AACvG,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAyD,IAAI;AACvF,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAA8D,IAAI;AAG5G,QAAM,mBAAmB,YAAY,CAAC,OAA4D;AAChG,qBAAiB,MAAM,EAAE;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,QAAI;AACF,UAAI,eAAe,QAAQ,aAAa,MAAM,WAAY,eAAc,UAAU;AAAA,IACpF,QAAQ;AAAA,IAER;AAAA,EACF,GAAG,CAAC,CAAC;AAIL,YAAU,MAAM;AACd,QAAI,UAAU,cAAc,aAAc;AAC1C,QAAI,eAAe;AACnB,SAAK,OAAO,8BAA8B,EAAE,KAAK,CAAC,MAAM;AACtD,2BAAqB,EAAE;AACvB,UAAI,CAAC,aAAc,iBAAgB,MAAM,EAAE,OAAO;AAAA,IACpD,CAAC;AACD,WAAO,MAAM;AACX,qBAAe;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,OAAO,YAAY,CAAC;AAExB,QAAM,WAAW,YAAY,CAAC,SAAsB;AAClD,kBAAc,IAAI;AAClB,QAAI;AACF,UAAI,SAAS,WAAY,gBAAe,QAAQ,eAAe,UAAU;AAAA,UACpE,gBAAe,WAAW,aAAa;AAAA,IAC9C,QAAQ;AAAA,IAER;AAIA,QAAI,SAAS,OAAQ,UAAS,IAAI;AAAA,EACpC,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,YAAY,CAAC,MAAc,WAAiC;AAC/E,aAAS,SAAS,EAAE,MAAM,OAAO,IAAI,IAAI;AAAA,EAC3C,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,QAAmB,OAAO;AAAA,IACtC;AAAA,IACA,MAAM,UAAU;AAAA,IAChB;AAAA,IACA,YAAY,MAAM,SAAS,UAAU;AAAA,IACrC,aAAa,MAAM,SAAS,MAAM;AAAA,IAClC,aAAa,OAAO,UAAU;AAAA,IAC9B,UAAU,OAAO,QAAQ;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,CAAC,OAAO,OAAO,UAAU,cAAc,eAAe,gBAAgB,CAAC;AAE3E,SACE,qBAAC,IAAI,UAAJ,EAAa,OACX;AAAA;AAAA,IACA,UAAU,cAAc,gBAAgB,oBAAC,gBAAa,SAAS,MAAM,SAAS,MAAM,GAAG;AAAA,KAC1F;AAEJ;AAGO,SAAS,YAAuB;AACrC,SAAO,WAAW,GAAG;AACvB;","names":[]}
package/dist/styles.css CHANGED
@@ -324,20 +324,11 @@
324
324
  user-select: none;
325
325
  }
326
326
  .xicms-ve__body {
327
- flex: 1 1 auto;
328
- display: grid;
329
- /* Standaard alleen het canvas. Het paneel is een lade die je met het tandwiel opent; sinds
330
- alle tekst inline bewerkbaar is, kost een altijd-open zijbalk 360px voor iets wat je maar
331
- zelden nodig hebt — en hij duwt de pagina die je aan het vormgeven bent uit beeld. */
332
- grid-template-columns: 1fr;
333
- min-height: 0;
334
- }
335
- .xicms-ve__body.is-panel-open {
336
- /* 360px was gekozen voor blok-instellingen (een handvol velden). Sinds het productformulier in
337
- ditzelfde paneel opent (7.8) is dat te smal: twee kolommen velden, varianten-tabellen en
338
- foto-rijen vouwden zich dubbel. clamp: nooit smaller dan de oude 360, nooit meer dan 480, en
339
- op een gewoon scherm ± een kwart — de pagina blijft de hoofdzaak. */
340
- grid-template-columns: 1fr clamp(360px, 28vw, 480px);
327
+ /* Net als de shell: geen eigen doos. Dit was een schermvullend grid met [canvas | paneel],
328
+ maar het canvas bestaat niet meer — de live pagina eronder ís het werkblad. Als grid-in-de-
329
+ flow belandde het paneel ONDER de volledige pagina: technisch aanwezig, feitelijk
330
+ onvindbaar. Het paneel positioneert zichzelf nu als vaste lade (hieronder). */
331
+ display: contents;
341
332
  }
342
333
  .xicms-ve__canvas {
343
334
  overflow: auto;
@@ -349,6 +340,20 @@
349
340
  background-size: 24px 24px;
350
341
  }
351
342
  .xicms-ve__panel {
343
+ /* Vaste lade rechts, van onder de editorbalk tot aan de demo-balk (of de schermrand).
344
+ 360–480 breed: nooit smaller dan blok-instellingen nodig hebben, nooit meer dan het
345
+ productformulier vraagt — de pagina blijft de hoofdzaak. z-index boven de publiceerknop
346
+ zou de knop verstoppen; erónder blijven (10001) en niet de volle breedte pakken is genoeg. */
347
+ position: fixed;
348
+ top: 2.6rem;
349
+ right: 0;
350
+ bottom: var(--xicms-bottombar, 0px);
351
+ width: clamp(360px, 28vw, 480px);
352
+ max-width: 100vw;
353
+ /* Boven de blokknoppen en potloodjes (10004-10005): die horen bij de pagina en moeten onder
354
+ de lade doorlopen, niet er bovenop zweven. */
355
+ z-index: 10007;
356
+ box-shadow: -12px 0 32px rgba(15, 23, 42, 0.18);
352
357
  overflow: auto;
353
358
  border-left: 1px solid var(--color-border, #e2e8f0);
354
359
  background: var(--color-background-card, #fff);
@@ -377,6 +382,7 @@
377
382
  }
378
383
  .xicms-ve__inserter {
379
384
  display: flex;
385
+ flex-wrap: wrap;
380
386
  align-items: center;
381
387
  gap: 0.75rem;
382
388
  margin-top: 1rem;
@@ -402,17 +408,19 @@
402
408
  order: 3;
403
409
  flex-basis: 100%;
404
410
  }
405
- .xicms-ve__body {
406
- grid-template-columns: 1fr;
407
- grid-template-rows: 1fr auto;
408
- }
409
411
  .xicms-ve__canvas {
410
412
  padding: 1rem;
411
413
  }
414
+ /* Telefoon: de lade komt van onderen (een zijlade van 360px zou het hele scherm zijn). */
412
415
  .xicms-ve__panel {
416
+ top: auto;
417
+ left: 0;
418
+ right: 0;
419
+ width: auto;
420
+ max-height: 60vh;
413
421
  border-left: 0;
414
422
  border-top: 2px solid #cbd5e1;
415
- max-height: 48vh;
423
+ box-shadow: 0 -12px 32px rgba(15, 23, 42, 0.18);
416
424
  }
417
425
  .xicms-ve__inserter {
418
426
  flex-wrap: wrap;
@@ -3170,6 +3178,12 @@
3170
3178
  .xicms-ve-blokknop--gevaar:hover {
3171
3179
  background: rgba(220, 38, 38, 0.85);
3172
3180
  }
3181
+ .xicms-ve-blokknop--klaar {
3182
+ background: rgba(22, 163, 74, 0.5);
3183
+ }
3184
+ .xicms-ve-blokknop--klaar:hover {
3185
+ background: rgba(22, 163, 74, 0.9);
3186
+ }
3173
3187
 
3174
3188
  /* Producten dragen een iets andere tint zodat je ziet dat je iets anders aanwijst dan een blok:
3175
3189
  wijzigingen aan een product gaan meteen live en horen niet met paginatekst verward te worden. */
@@ -3214,6 +3228,17 @@
3214
3228
  right: 20px;
3215
3229
  bottom: calc(20px + var(--xicms-bottombar, 0px));
3216
3230
  z-index: 10006;
3231
+ transition: right 0.2s ease;
3232
+ }
3233
+ /* Lade open → knop schuift mee naar links, anders ligt hij óver het formulier waarin je net
3234
+ aan het typen bent. Alleen op schermen waar de lade een zijlade is (op mobiel komt hij van
3235
+ onderen en blijft de knop gewoon rechts). */
3236
+ @media (min-width: 721px) {
3237
+ :root:has(.xicms-ve__panel) .xicms-ve-publiceer {
3238
+ right: calc(clamp(360px, 28vw, 480px) + 20px);
3239
+ }
3240
+ }
3241
+ .xicms-ve-publiceer {
3217
3242
  display: grid;
3218
3243
  gap: 2px;
3219
3244
  justify-items: center;
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const CORE_VERSION = true ? "7.9.1" : "0.0.0-dev";
1
+ const CORE_VERSION = true ? "7.11.0" : "0.0.0-dev";
2
2
  const CORE_PACKAGE = "@xiwebdesign/xicms-core";
3
3
  function isNieuwer(beschikbaar, huidig) {
4
4
  const delen = (v) => v.split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * De versie van de engine waarop deze site draait.\n *\n * De waarde wordt bij het bouwen ingevuld (tsup `define`), niet uit package.json geïmporteerd: met\n * `bundle: false` zou zo'n import als losse JSON-module in `dist` belanden en bij de consumer niet\n * bestaan. In de monorepo (tests, dev) is de define er niet — vandaar de terugval, die meteen\n * zichtbaar maakt dát je naar een ongebouwde core kijkt.\n */\ndeclare const __XICMS_CORE_VERSION__: string | undefined;\n\nexport const CORE_VERSION: string =\n typeof __XICMS_CORE_VERSION__ === \"string\" ? __XICMS_CORE_VERSION__ : \"0.0.0-dev\";\n\n/** De npm-registry, waar `npm run check:core-update` en het beheer de laatste versie ophalen. */\nexport const CORE_PACKAGE = \"@xiwebdesign/xicms-core\";\n\n/**\n * Vergelijkt twee semver-strings zonder dependency. Geeft true als `beschikbaar` nieuwer is.\n * Pre-release-suffixen tellen niet mee — voor \"is er een update?\" volstaat major.minor.patch.\n */\nexport function isNieuwer(beschikbaar: string, huidig: string): boolean {\n const delen = (v: string) => v.split(\"-\")[0].split(\".\").map((n) => Number.parseInt(n, 10) || 0);\n const [aM, aN, aP] = delen(beschikbaar);\n const [bM, bN, bP] = delen(huidig);\n if (aM !== bM) return aM > bM;\n if (aN !== bN) return aN > bN;\n return aP > bP;\n}\n"],"mappings":"AAUO,MAAM,eACX,OAA6C,UAAyB;AAGjE,MAAM,eAAe;AAMrB,SAAS,UAAU,aAAqB,QAAyB;AACtE,QAAM,QAAQ,CAAC,MAAc,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC;AAC9F,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,MAAM,WAAW;AACtC,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,MAAM,MAAM;AACjC,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,SAAO,KAAK;AACd;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/**\n * De versie van de engine waarop deze site draait.\n *\n * De waarde wordt bij het bouwen ingevuld (tsup `define`), niet uit package.json geïmporteerd: met\n * `bundle: false` zou zo'n import als losse JSON-module in `dist` belanden en bij de consumer niet\n * bestaan. In de monorepo (tests, dev) is de define er niet — vandaar de terugval, die meteen\n * zichtbaar maakt dát je naar een ongebouwde core kijkt.\n */\ndeclare const __XICMS_CORE_VERSION__: string | undefined;\n\nexport const CORE_VERSION: string =\n typeof __XICMS_CORE_VERSION__ === \"string\" ? __XICMS_CORE_VERSION__ : \"0.0.0-dev\";\n\n/** De npm-registry, waar `npm run check:core-update` en het beheer de laatste versie ophalen. */\nexport const CORE_PACKAGE = \"@xiwebdesign/xicms-core\";\n\n/**\n * Vergelijkt twee semver-strings zonder dependency. Geeft true als `beschikbaar` nieuwer is.\n * Pre-release-suffixen tellen niet mee — voor \"is er een update?\" volstaat major.minor.patch.\n */\nexport function isNieuwer(beschikbaar: string, huidig: string): boolean {\n const delen = (v: string) => v.split(\"-\")[0].split(\".\").map((n) => Number.parseInt(n, 10) || 0);\n const [aM, aN, aP] = delen(beschikbaar);\n const [bM, bN, bP] = delen(huidig);\n if (aM !== bM) return aM > bM;\n if (aN !== bN) return aN > bN;\n return aP > bP;\n}\n"],"mappings":"AAUO,MAAM,eACX,OAA6C,WAAyB;AAGjE,MAAM,eAAe;AAMrB,SAAS,UAAU,aAAqB,QAAyB;AACtE,QAAM,QAAQ,CAAC,MAAc,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC;AAC9F,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,MAAM,WAAW;AACtC,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,MAAM,MAAM;AACjC,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,MAAI,OAAO,GAAI,QAAO,KAAK;AAC3B,SAAO,KAAK;AACd;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiwebdesign/xicms-core",
3
- "version": "7.9.1",
3
+ "version": "7.11.0",
4
4
  "description": "XiCMS engine: gestructureerde content, pagina's, draft/publish, admin (TipTap+Radix), SEO en beveiliging voor klantsites.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -102,8 +102,8 @@
102
102
  }
103
103
  },
104
104
  "dependencies": {
105
- "@astryxdesign/core": "^0.4.2",
106
- "@astryxdesign/theme-neutral": "^0.4.2",
105
+ "@astryxdesign/core": "^0.5.1",
106
+ "@astryxdesign/theme-neutral": "^0.5.1",
107
107
  "@radix-ui/react-tabs": "^1.1.21",
108
108
  "@tiptap/extension-image": "^3.30.1",
109
109
  "@tiptap/extension-link": "^3.30.1",
@@ -120,7 +120,7 @@
120
120
  "zod": "^4.4.3"
121
121
  },
122
122
  "devDependencies": {
123
- "@astryxdesign/cli": "^0.4.2",
123
+ "@astryxdesign/cli": "^0.5.1",
124
124
  "@tailwindcss/cli": "^4.3.3",
125
125
  "@testing-library/dom": "^10.4.1",
126
126
  "@testing-library/react": "^16.3.2",