@improba/page-builder 0.1.0 → 0.2.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 (68) hide show
  1. package/README.md +26 -26
  2. package/dist/core.cjs +2 -0
  3. package/dist/core.cjs.map +1 -0
  4. package/dist/core.js +29 -0
  5. package/dist/core.js.map +1 -0
  6. package/dist/index-D79WbFRY.cjs +2 -0
  7. package/dist/index-D79WbFRY.cjs.map +1 -0
  8. package/dist/index-c6HOrx9r.js +523 -0
  9. package/dist/index-c6HOrx9r.js.map +1 -0
  10. package/dist/index.cjs +10 -5
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.js +2402 -3312
  13. package/dist/index.js.map +1 -1
  14. package/dist/style.css +1 -0
  15. package/dist/types/built-in/PbColumn.vue.d.ts +41 -0
  16. package/dist/types/built-in/PbContainer.vue.d.ts +32 -0
  17. package/dist/types/built-in/PbImage.vue.d.ts +42 -0
  18. package/dist/types/built-in/PbRow.vue.d.ts +41 -0
  19. package/dist/types/built-in/PbSection.vue.d.ts +50 -0
  20. package/dist/types/built-in/PbText.vue.d.ts +25 -0
  21. package/dist/types/built-in/PbVideo.vue.d.ts +27 -0
  22. package/dist/types/built-in/index.d.ts +10 -0
  23. package/dist/types/components/PageBuilder.vue.d.ts +50 -0
  24. package/dist/types/components/editor/EditorCanvas.vue.d.ts +67 -0
  25. package/dist/types/components/editor/EditorToolbar.vue.d.ts +97 -0
  26. package/dist/types/components/editor/IframeCanvas.vue.d.ts +102 -0
  27. package/dist/types/components/editor/LeftDrawer.vue.d.ts +46 -0
  28. package/dist/types/components/editor/NodeContextMenu.vue.d.ts +66 -0
  29. package/dist/types/components/editor/PageEditor.vue.d.ts +20 -0
  30. package/dist/types/components/editor/RightDrawer.vue.d.ts +43 -0
  31. package/dist/types/components/editor/TreePanel.vue.d.ts +28 -0
  32. package/dist/types/components/editor/prop-editors/MediaPicker.vue.d.ts +20 -0
  33. package/dist/types/components/editor/prop-editors/PropBooleanEditor.vue.d.ts +18 -0
  34. package/dist/types/components/editor/prop-editors/PropColorEditor.vue.d.ts +18 -0
  35. package/dist/types/components/editor/prop-editors/PropNumberEditor.vue.d.ts +36 -0
  36. package/dist/types/components/editor/prop-editors/PropSelectEditor.vue.d.ts +31 -0
  37. package/dist/types/components/editor/prop-editors/PropTextEditor.vue.d.ts +27 -0
  38. package/dist/types/components/editor/prop-editors/RichTextEditor.vue.d.ts +27 -0
  39. package/dist/types/components/editor/prop-editors/index.d.ts +29 -0
  40. package/dist/types/components/reader/NodeRenderer.vue.d.ts +33 -0
  41. package/dist/types/components/reader/PageReader.vue.d.ts +14 -0
  42. package/dist/types/components/shared/ErrorBoundary.vue.d.ts +21 -0
  43. package/dist/types/composables/use-drag-drop.d.ts +23 -0
  44. package/dist/types/composables/use-editor.d.ts +40 -0
  45. package/dist/types/composables/use-node-tree.d.ts +23 -0
  46. package/dist/types/composables/use-page-builder.d.ts +28 -0
  47. package/dist/types/core/drop-slot.d.ts +12 -0
  48. package/dist/types/core/errors.d.ts +14 -0
  49. package/dist/types/core/iframe-bridge.d.ts +85 -0
  50. package/dist/types/core/index.d.ts +18 -0
  51. package/dist/types/core/registry.d.ts +43 -0
  52. package/dist/types/core/sanitize.d.ts +3 -0
  53. package/dist/types/core/tree.d.ts +56 -0
  54. package/dist/types/core/validation.d.ts +10 -0
  55. package/dist/types/core/virtual-tree.d.ts +44 -0
  56. package/dist/types/i18n/context.d.ts +13 -0
  57. package/dist/types/i18n/index.d.ts +3 -0
  58. package/dist/types/i18n/messages.d.ts +3 -0
  59. package/dist/types/i18n/translator.d.ts +14 -0
  60. package/dist/types/index.d.ts +27 -0
  61. package/dist/types/plugin.d.ts +18 -0
  62. package/dist/types/types/component.d.ts +68 -0
  63. package/dist/types/types/editor.d.ts +54 -0
  64. package/dist/types/types/index.d.ts +6 -0
  65. package/dist/types/types/keys.d.ts +13 -0
  66. package/dist/types/types/node.d.ts +54 -0
  67. package/package.json +8 -3
  68. package/dist/index.css +0 -1
package/README.md CHANGED
@@ -1,33 +1,33 @@
1
1
  # @improba/page-builder
2
2
 
3
- Bibliothèque Vue 3 pour construire et afficher des pages à partir d’un arbre JSON. Elle fournit un **mode lecture** (rendu statique, compatible SSR) et un **mode édition** (éditeur WYSIWYG avec palette de composants, panneau de propriétés, glisser-déposer, undo/redo). Le backend envoie un seul contrat JSON (`IPageData`) ; le frontend le rend et, en mode édition, permet de le modifier visuellement.
3
+ Vue 3 library for building and rendering pages from a JSON tree. It provides **read mode** (static rendering, SSR-compatible) and **edit mode** (WYSIWYG editor with component palette, property panel, drag-and-drop, undo/redo). The backend sends a single JSON contract (`IPageData`); the frontend renders it and, in edit mode, allows visual editing.
4
4
 
5
- **En bref :** installez le plugin Vue, fournissez des données `IPageData`, et utilisez `<PageBuilder>` en `mode="read"` pour l’affichage ou `mode="edit"` pour l’édition. Vous pouvez enregistrer vos propres composants (hero, cartes, etc.) et les utiliser comme blocs dans l’arbre.
5
+ **In short:** install the Vue plugin, provide `IPageData`, and use `<PageBuilder>` with `mode="read"` for display or `mode="edit"` for editing. You can register your own components (hero, cards, etc.) and use them as blocks in the tree.
6
6
 
7
- ## Aperçu
7
+ ## Overview
8
8
 
9
- **Mode édition** — Éditeur WYSIWYG avec palette de composants, panneau de propriétés et prévisualisation responsive.
9
+ **Edit mode** — WYSIWYG editor with component palette, property panel, and responsive preview.
10
10
 
11
- ![Mode édition — toolbar, palette, canvas, propriétés](./docs/images/edit-mode.png)
11
+ ![Edit mode — toolbar, palette, canvas, properties](./docs/images/edit-mode.png)
12
12
 
13
- **Mode lecture** — Rendu de la page sans interface d’édition (compatible SSR).
13
+ **Read mode** — Page rendering without editor UI (SSR-compatible).
14
14
 
15
- ![Mode lecturerendu de la page](./docs/images/read-mode.png)
15
+ ![Read modepage rendering](./docs/images/read-mode.png)
16
16
 
17
- *Pour régénérer les captures : `docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run docs:screenshots"`.*
17
+ *To regenerate screenshots: `docker compose -f docker/docker-compose.yml run --rm e2e sh -lc "npm install && npm run docs:screenshots"`.*
18
18
 
19
- ## Fonctionnalités
19
+ ## Features
20
20
 
21
- - **Mode lecture** — Rendu du contenu à partir d’un arbre JSON, compatible SSR. Intégrable dans Nuxt ou toute app Vue 3.
22
- - **Mode édition** — Éditeur WYSIWYG avec palette de composants, panneau de propriétés, glisser-déposer, undo/redo et prévisualisation responsive (desktop / tablette / mobile).
23
- - **Registre de composants** — Enregistrement de composants Vue personnalisés (props typées, slots, métadonnées d’édition). Livré avec des composants de mise en page et de contenu (PbColumn, PbRow, PbText, PbImage, etc.).
24
- - **Contrat JSON unique** — Le backend envoie un seul payload `IPageData` ; le frontend le rend et l’édite. Séparation claire des responsabilités.
21
+ - **Read mode** — Renders content from a JSON tree, SSR-compatible. Integrable with Nuxt or any Vue 3 app.
22
+ - **Edit mode** — WYSIWYG editor with component palette, property panel, drag-and-drop, undo/redo, and responsive preview (desktop / tablet / mobile).
23
+ - **Component registry** — Register custom Vue components (typed props, slots, edit metadata). Ships with layout and content components (PbColumn, PbRow, PbText, PbImage, etc.).
24
+ - **Single JSON contract** — Backend sends one `IPageData` payload; frontend renders and edits it. Clear separation of concerns.
25
25
 
26
- ## Démarrage rapide
26
+ ## Quick Start
27
27
 
28
- Pour un guide pas à pas (installation, premier rendu, mode édition, composants personnalisés), voir **[Quick Start](./docs/quickstart.md)**.
28
+ For a step-by-step guide (installation, first render, edit mode, custom components), see **[Quick Start](./docs/quickstart.md)**.
29
29
 
30
- Résumé minimal :
30
+ Minimal summary:
31
31
 
32
32
  ### Installation
33
33
 
@@ -204,19 +204,19 @@ docker compose -f docker/docker-compose.yml run --rm e2e npm run e2e:install
204
204
 
205
205
  ## Documentation
206
206
 
207
- Toute la documentation se trouve dans `docs/` :
207
+ All documentation lives in `docs/`:
208
208
 
209
209
  | Document | Description |
210
210
  |----------|-------------|
211
- | **[Quick Start](./docs/quickstart.md)** | Démarrer rapidement : installation, configuration, premier rendu, mode édition, API |
212
- | **[Intégration backend](./docs/backend-integration.md)** | Routes attendues, contrats (IPageData, IPageSavePayload), validation, médias, sécurité |
213
- | **[Architecture](./docs/architecture/)** | Vue d’ensemble, schéma JSON, système de composants, pipeline de rendu, architecture du mode édition |
214
- | **[Fonctionnalités](./docs/features/)** | Mode lecture, mode édition, registre de composants, format JSON |
215
- | **[Conventions](./docs/conventions/)** | Style de code, workflow git |
216
- | **[Roadmap](./docs/plans/roadmap.md)** | Phases et jalons |
217
- | **[Référence API](./docs/api/)** | Sortie TypeDoc (types et fonctions publics) |
218
-
219
- Pour régénérer la référence API :
211
+ | **[Quick Start](./docs/quickstart.md)** | Get started: installation, setup, first render, edit mode, API |
212
+ | **[Backend integration](./docs/backend-integration.md)** | Expected routes, contracts (IPageData, IPageSavePayload), validation, media, security |
213
+ | **[Architecture](./docs/architecture/)** | Overview, JSON schema, component system, rendering pipeline, edit mode architecture |
214
+ | **[Features](./docs/features/)** | Read mode, edit mode, component registry, JSON format |
215
+ | **[Conventions](./docs/conventions/)** | Code style, git workflow |
216
+ | **[Roadmap](./docs/plans/roadmap.md)** | Phases and milestones |
217
+ | **[API reference](./docs/api/)** | TypeDoc output (public types and functions) |
218
+
219
+ To regenerate the API reference:
220
220
 
221
221
  ```bash
222
222
  docker compose -f docker/docker-compose.yml run --rm dev npm run docs:api
package/dist/core.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-D79WbFRY.cjs");exports.PageBuilderError=e.PageBuilderError;exports.cloneTree=e.cloneTree;exports.computeWindowRange=e.computeWindowRange;exports.countNodes=e.countNodes;exports.createNode=e.createNode;exports.createPageBuilderError=e.createPageBuilderError;exports.createStableNodeKey=e.createStableNodeKey;exports.createVirtualTreeIndexMaps=e.createVirtualTreeIndexMaps;exports.extractPlainText=e.extractPlainText;exports.findNodeById=e.findNodeById;exports.findParent=e.findParent;exports.flattenTree=e.flattenTree;exports.getMaxId=e.getMaxId;exports.insertNode=e.insertNode;exports.interpolateProps=e.interpolateProps;exports.isPageBuilderError=e.isPageBuilderError;exports.moveNode=e.moveNode;exports.normalizeSafeHtmlTag=e.normalizeSafeHtmlTag;exports.removeNode=e.removeNode;exports.sanitizeUrlByKind=e.sanitizeUrlByKind;exports.sliceWindow=e.sliceWindow;exports.toErrorMessage=e.toErrorMessage;exports.validateNode=e.validateNode;exports.validatePageData=e.validatePageData;exports.walkTree=e.walkTree;
2
+ //# sourceMappingURL=core.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/dist/core.js ADDED
@@ -0,0 +1,29 @@
1
+ import { P as r, e as s, o, p as t, h as d, c as i, q as l, u as n, w as c, f as g, d as N, l as P, g as m, j as u, i as f, x as p, m as x, n as T, k as v, a as B, y as w, t as y, b as E, v as z, z as I } from "./index-c6HOrx9r.js";
2
+ export {
3
+ r as PageBuilderError,
4
+ s as cloneTree,
5
+ o as computeWindowRange,
6
+ t as countNodes,
7
+ d as createNode,
8
+ i as createPageBuilderError,
9
+ l as createStableNodeKey,
10
+ n as createVirtualTreeIndexMaps,
11
+ c as extractPlainText,
12
+ g as findNodeById,
13
+ N as findParent,
14
+ P as flattenTree,
15
+ m as getMaxId,
16
+ u as insertNode,
17
+ f as interpolateProps,
18
+ p as isPageBuilderError,
19
+ x as moveNode,
20
+ T as normalizeSafeHtmlTag,
21
+ v as removeNode,
22
+ B as sanitizeUrlByKind,
23
+ w as sliceWindow,
24
+ y as toErrorMessage,
25
+ E as validateNode,
26
+ z as validatePageData,
27
+ I as walkTree
28
+ };
29
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,2 @@
1
+ "use strict";var x=Object.defineProperty;var L=(r,t,e)=>t in r?x(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var T=(r,t,e)=>L(r,typeof t!="symbol"?t+"":t,e);class v extends Error{constructor(e,n,o={}){super(n);T(this,"code");T(this,"details");this.name="PageBuilderError",this.code=e,this.details=o.details??{},this.cause=o.cause}}function R(r){return r instanceof v}function E(r,t,e={}){return new v(r,t,e)}function D(r){return r instanceof Error?r.message:String(r)}function O(r,t,e){}const P=new Set(["div","p","span","h1","h2","h3","h4","h5","h6","section","article","blockquote"]),B=new Set(["a","b","blockquote","br","code","div","em","h1","h2","h3","h4","h5","h6","i","li","ol","p","pre","s","span","strong","u","ul"]),V=new Set(["base","embed","form","iframe","input","link","math","meta","noscript","object","script","style","svg","template","textarea"]),F=new Set(["_blank","_parent","_self","_top"]),j=new Set(["nofollow","noopener","noreferrer","sponsored","ugc"]),G=/^data:image\/(?:avif|bmp|gif|jpe?g|png|webp);base64,[a-z0-9+/=\s]+$/i;function I(r){return r.replace(/[\u0000-\u001F\u007F]/g,"")}function H(r){return I(r).replace(/\s+/g,"")}function k(r){const t=r.toLowerCase().split(/\s+/).filter(Boolean).filter(e=>j.has(e));return Array.from(new Set(t)).join(" ")}function K(r){const t=k(r??""),e=new Set(t.split(/\s+/).filter(Boolean));return e.add("noopener"),e.add("noreferrer"),Array.from(e).join(" ")}function U(){var r;if(typeof document<"u"&&typeof((r=document.implementation)==null?void 0:r.createHTMLDocument)=="function")return document.implementation.createHTMLDocument("");if(typeof DOMParser<"u"){const t=new DOMParser().parseFromString("<!doctype html><html><body></body></html>","text/html");if(t!=null&&t.body)return t}return null}function W(r){return r.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}function q(r,t){const e=t.tagName.toLowerCase();for(const n of Array.from(r.attributes)){const o=n.name.toLowerCase(),i=I(n.value).trim();if(!(i.length===0||o.startsWith("on"))){if(o==="title"){t.setAttribute("title",i);continue}if(e==="a"){if(o==="href"){const a=f(i,"link");a.length>0&&t.setAttribute("href",a);continue}if(o==="target"){const a=i.toLowerCase();F.has(a)&&t.setAttribute("target",a);continue}if(o==="rel"){const a=k(i);a.length>0&&t.setAttribute("rel",a)}}}}e==="a"&&t.getAttribute("target")==="_blank"&&t.setAttribute("rel",K(t.getAttribute("rel")))}function A(r,t,e){for(const n of Array.from(r.childNodes)){if(n.nodeType===3){t.appendChild(e.createTextNode(n.textContent??""));continue}if(n.nodeType!==1)continue;const o=n,i=o.tagName.toLowerCase();if(V.has(i))continue;if(!B.has(i)){const d=e.createDocumentFragment();A(o,d,e),t.appendChild(d);continue}const a=e.createElement(i);if(q(o,a),A(o,a,e),i==="a"&&!a.getAttribute("href")){const d=e.createDocumentFragment();for(;a.firstChild;)d.appendChild(a.firstChild);t.appendChild(d);continue}t.appendChild(a)}}function X(r){const t=typeof r=="string"?r:"";if(t.length===0)return"";const e=U();if(!e)return W(t);const n=e.createElement("div");n.innerHTML=t;const o=e.createElement("div");return A(n,o,e),o.innerHTML}function C(r,t="div"){const e=typeof t=="string"?t.trim().toLowerCase():"div",n=P.has(e)?e:"div";if(typeof r!="string")return n;const o=r.trim().toLowerCase();return P.has(o)?o:n}function f(r,t){const e=I(r).trim();if(e.length===0)return"";const n=H(e).toLowerCase(),o=n.match(/^([a-z][a-z0-9+.-]*):/i),i=o==null?void 0:o[1];return!i||i==="http"||i==="https"||t==="link"&&(i==="mailto"||i==="tel")||t==="media"&&i==="blob"||(t==="media"||t==="background")&&i==="data"&&G.test(n)?e:""}const Y=new Set(["draft","published","archived"]),w=200,S=5e3;function m(r){return typeof r=="object"&&r!==null&&!Array.isArray(r)}function s(r,t,e){r.errors.push({path:t,message:e})}function J(r,t,e){if(r.tag===void 0)return;if(typeof r.tag!="string"||r.tag.trim()===""){s(e,`${t}.tag`,"PbText props.tag must be a non-empty string.");return}const n=r.tag.trim().toLowerCase();C(r.tag)!==n&&s(e,`${t}.tag`,"PbText props.tag must be one of: div, p, span, h1, h2, h3, h4, h5, h6, section, article, blockquote.")}function Q(r,t,e){if(typeof r.src!="string"||r.src.trim()===""){s(e,`${t}.src`,"PbImage props.src must be a non-empty string.");return}f(r.src,"media")===""&&s(e,`${t}.src`,"PbImage props.src contains an unsafe URL.")}function Z(r,t,e){if(typeof r.src!="string"||r.src.trim()===""){s(e,`${t}.src`,"PbVideo props.src must be a non-empty string.");return}f(r.src,"media")===""&&s(e,`${t}.src`,"PbVideo props.src contains an unsafe URL.");const n=r.poster;n!=null&&n!==""&&(typeof n!="string"?s(e,`${t}.poster`,"PbVideo props.poster must be a string."):n.trim()!==""&&f(n,"media")===""&&s(e,`${t}.poster`,"PbVideo props.poster contains an unsafe URL."))}function ee(r,t,e){const n=r.backgroundImage;if(!(n==null||n==="")){if(typeof n!="string"){s(e,`${t}.backgroundImage`,"PbSection props.backgroundImage must be a string.");return}n.trim()!==""&&f(n,"background")===""&&s(e,`${t}.backgroundImage`,"PbSection props.backgroundImage contains an unsafe URL.")}}function te(r,t,e,n){if(r==="PbText"){J(t,e,n);return}if(r==="PbImage"){Q(t,e,n);return}if(r==="PbVideo"){Z(t,e,n);return}r==="PbSection"&&ee(t,e,n)}function p(r,t,e,n=0){if(n>w){e.depthGuardTriggered||(s(e,t,`Maximum node depth (${String(w)}) exceeded during validation.`),e.depthGuardTriggered=!0);return}if(e.visitedNodeCount>=S){e.sizeGuardTriggered||(s(e,t,`Maximum node count (${String(S)}) exceeded during validation.`),e.sizeGuardTriggered=!0);return}if(!m(r)){s(e,t,"Node must be an object.");return}if(e.seenNodes.has(r)){s(e,t,"Cycle detected in node tree.");return}e.seenNodes.add(r),e.visitedNodeCount++;const o=r.id,i=r.name,a=r.slot,d=r.props,u=r.children,c=r.readonly;if(!(typeof o=="number"&&Number.isInteger(o))||o<=0?s(e,`${t}.id`,"id must be a positive integer."):(e.seenIds.has(o)&&s(e,`${t}.id`,`Duplicate node id "${o}" found.`),e.seenIds.add(o),o>e.maxObservedId&&(e.maxObservedId=o)),(typeof i!="string"||i.trim()==="")&&s(e,`${t}.name`,"name must be a non-empty string."),a===null||typeof a=="string"||s(e,`${t}.slot`,"slot must be a string or null."),m(d)?typeof i=="string"&&te(i,d,`${t}.props`,e):s(e,`${t}.props`,"props must be an object."),!Array.isArray(u)){s(e,`${t}.children`,"children must be an array.");return}c===void 0||typeof c=="boolean"||s(e,`${t}.readonly`,"readonly must be a boolean when provided.");for(let l=0;l<u.length&&!e.sizeGuardTriggered;l++)p(u[l],`${t}.children[${l}]`,e,n+1)}function re(r,t="node"){const e={errors:[],seenIds:new Set,seenNodes:new WeakSet,maxObservedId:0,visitedNodeCount:0,depthGuardTriggered:!1,sizeGuardTriggered:!1};return p(r,t,e),{isValid:e.errors.length===0,errors:e.errors}}function ne(r){const t={errors:[],seenIds:new Set,seenNodes:new WeakSet,maxObservedId:0,visitedNodeCount:0,depthGuardTriggered:!1,sizeGuardTriggered:!1};if(!m(r))return s(t,"pageData","pageData must be an object."),{isValid:!1,errors:t.errors};const{meta:e,content:n,layout:o,maxId:i,variables:a}=r;if(m(e)?((typeof e.id!="string"||e.id.trim()==="")&&s(t,"meta.id","meta.id must be a non-empty string."),(typeof e.name!="string"||e.name.trim()==="")&&s(t,"meta.name","meta.name must be a non-empty string."),typeof e.url!="string"||e.url.trim()===""?s(t,"meta.url","meta.url must be a non-empty string."):f(e.url,"link")===""&&s(t,"meta.url","meta.url contains an unsafe URL."),(typeof e.status!="string"||!Y.has(e.status))&&s(t,"meta.status","meta.status must be one of: draft, published, archived."),e.updatedAt===void 0||typeof e.updatedAt=="string"||s(t,"meta.updatedAt","meta.updatedAt must be a string when provided."),e.createdAt===void 0||typeof e.createdAt=="string"||s(t,"meta.createdAt","meta.createdAt must be a string when provided.")):s(t,"meta","meta must be an object."),p(n,"content",t),p(o,"layout",t),!(typeof i=="number"&&Number.isInteger(i))||i<0?s(t,"maxId","maxId must be a non-negative integer."):i<t.maxObservedId&&s(t,"maxId",`maxId (${String(i)}) must be greater than or equal to the maximum node id (${String(t.maxObservedId)}).`),!m(a))s(t,"variables","variables must be an object.");else for(const[d,u]of Object.entries(a))typeof u!="string"&&s(t,`variables.${d}`,"Variable values must be strings.");return{isValid:t.errors.length===0,errors:t.errors}}function b(r){return Array.isArray(r.children)?r.children:[]}function z(r,t){const e=Number.isFinite(r)?Math.trunc(r):0;return Math.max(0,Math.min(e,t))}function ie(r){try{return structuredClone(r)}catch(t){throw E("INVALID_NODE","[PageBuilder] Failed to clone node tree. Ensure the tree is serializable and acyclic.",{cause:t})}}function N(r,t){if(r.id===t)return r;for(const e of b(r)){const n=N(e,t);if(n)return n}}function h(r,t){const e=b(r);for(let n=0;n<e.length;n++){if(e[n].id===t)return{parent:r,index:n};const o=h(e[n],t);if(o)return o}}function oe(r,t){const e=h(r,t);if(e)return e.parent.children.splice(e.index,1)[0]}function M(r,t,e,n,o="default"){const i=N(r,t);if(!i)return!1;Array.isArray(i.children)||(i.children=[]);const a=z(n,i.children.length),d={...e,slot:o};return i.children.splice(a,0,d),!0}function ae(r,t,e,n,o="default"){const i=h(r,t);if(!i)return!1;const a=b(i.parent),[d]=a.splice(i.index,1);if(!d)return!1;if(M(r,e,d,n,o))return!0;const c=z(i.index,a.length);return a.splice(c,0,d),!1}function se(r,t,e={}){return{id:r,name:t,slot:e.slot??"default",props:e.props??{},children:e.children??[],readonly:e.readonly}}function y(r,t,e=0){const n=new WeakSet;function o(i,a){if(n.has(i))return!0;if(n.add(i),t(i,a)===!1)return!1;for(const d of b(i))if(o(d,a+1)===!1)return!1;return!0}return o(r,e)}function de(r){let t=0;return y(r,()=>{t++}),t}function ue(r){let t=r.id;return y(r,e=>{Number.isFinite(e.id)&&e.id>t&&(t=e.id)}),t}function ce(r,t){if(!r||typeof r!="object"||Array.isArray(r))return{};const e=t&&typeof t=="object"&&!Array.isArray(t)?t:{},n={};for(const[o,i]of Object.entries(r))typeof i=="string"?n[o]=i.replace(/\{\{\s*(\w+)\s*\}\}/g,(a,d)=>e[d]??`{{ ${d} }}`):n[o]=i;return n}function le(r){const t=[];return y(r,e=>{if(e.props.content&&typeof e.props.content=="string"){const n=e.props.content.replace(/<[^>]*>/g,"");n.trim()&&t.push(n.trim())}}),t.join(" ").replace(/\s+/g," ").trim()}function g(r){return Number.isFinite(r)?Math.trunc(r):0}function _(r){return`ipb-node-${r}`}function fe(r,t={}){const e=t.createKey??(i=>_(i.id)),n=[],o=[{node:r,depth:0,parentId:null}];for(;o.length>0;){const i=o.pop();if(!i)break;const a=n.length;n.push({node:i.node,id:i.node.id,key:e(i.node),depth:i.depth,index:a,parentId:i.parentId});for(let d=i.node.children.length-1;d>=0;d--)o.push({node:i.node.children[d],depth:i.depth+1,parentId:i.node.id})}return n}function $(r,t,e,n=0){const o=Math.max(0,g(r)),i=Math.max(0,g(e)),a=Math.max(0,g(n));if(o===0||i===0)return{start:0,end:0,size:0,total:o};const d=o-1,u=Math.min(Math.max(g(t),0),d),c=Math.max(0,u-a),l=Math.min(o,u+i+a);return{start:c,end:l,size:Math.max(0,l-c),total:o}}function me(r,t,e,n=0){const o=$(r.length,t,e,n);return{rows:r.slice(o.start,o.end),range:o}}function ge(r){const t=[],e=new Map,n=new Map;for(let o=0;o<r.length;o++){const i=r[o];t[o]=i.key,e.has(i.key)||e.set(i.key,o),n.has(i.id)||n.set(i.id,o)}return{keyByIndex:t,indexByKey:e,indexByNodeId:n}}exports.PageBuilderError=v;exports.cloneTree=ie;exports.computeWindowRange=$;exports.countNodes=de;exports.createNode=se;exports.createPageBuilderError=E;exports.createStableNodeKey=_;exports.createVirtualTreeIndexMaps=ge;exports.extractPlainText=le;exports.findNodeById=N;exports.findParent=h;exports.flattenTree=fe;exports.getMaxId=ue;exports.insertNode=M;exports.interpolateProps=ce;exports.isPageBuilderError=R;exports.moveNode=ae;exports.normalizeSafeHtmlTag=C;exports.removeNode=oe;exports.reportDevDiagnostic=O;exports.sanitizeRichTextHtml=X;exports.sanitizeUrlByKind=f;exports.sliceWindow=me;exports.toErrorMessage=D;exports.validateNode=re;exports.validatePageData=ne;exports.walkTree=y;
2
+ //# sourceMappingURL=index-D79WbFRY.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-D79WbFRY.cjs","sources":["../src/core/errors.ts","../src/core/sanitize.ts","../src/core/validation.ts","../src/core/tree.ts","../src/core/virtual-tree.ts"],"sourcesContent":["export type PageBuilderErrorCode =\n | 'INVALID_PAGE_DATA'\n | 'INVALID_NODE'\n | 'INVALID_SNAPSHOT'\n | 'MISSING_COMPONENT'\n | 'DUPLICATE_COMPONENT'\n | 'RENDER_FAILURE'\n | 'UNKNOWN';\n\nexport interface PageBuilderErrorOptions {\n details?: Record<string, unknown>;\n cause?: unknown;\n}\n\nexport class PageBuilderError extends Error {\n readonly code: PageBuilderErrorCode;\n readonly details: Record<string, unknown>;\n\n constructor(code: PageBuilderErrorCode, message: string, options: PageBuilderErrorOptions = {}) {\n super(message);\n this.name = 'PageBuilderError';\n this.code = code;\n this.details = options.details ?? {};\n this.cause = options.cause;\n }\n}\n\nexport function isPageBuilderError(error: unknown): error is PageBuilderError {\n return error instanceof PageBuilderError;\n}\n\nexport function createPageBuilderError(\n code: PageBuilderErrorCode,\n message: string,\n options: PageBuilderErrorOptions = {},\n): PageBuilderError {\n return new PageBuilderError(code, message, options);\n}\n\nexport function toErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n return String(error);\n}\n\nexport function reportDevDiagnostic(\n context: string,\n error: unknown,\n details?: Record<string, unknown>,\n): void {\n if (!import.meta.env.DEV) return;\n const normalized = isPageBuilderError(error)\n ? error\n : createPageBuilderError('UNKNOWN', toErrorMessage(error), {\n cause: error,\n details,\n });\n\n const mergedDetails = details\n ? {\n ...normalized.details,\n ...details,\n }\n : normalized.details;\n\n console.error(`[PageBuilder][${context}] ${normalized.message}`, {\n code: normalized.code,\n details: mergedDetails,\n cause: normalized.cause,\n });\n}\n","const SAFE_TEXT_CONTAINER_TAGS = new Set([\n 'div',\n 'p',\n 'span',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'section',\n 'article',\n 'blockquote',\n]);\n\nconst SAFE_RICH_TEXT_TAGS = new Set([\n 'a',\n 'b',\n 'blockquote',\n 'br',\n 'code',\n 'div',\n 'em',\n 'h1',\n 'h2',\n 'h3',\n 'h4',\n 'h5',\n 'h6',\n 'i',\n 'li',\n 'ol',\n 'p',\n 'pre',\n 's',\n 'span',\n 'strong',\n 'u',\n 'ul',\n]);\n\nconst DROP_ENTIRELY_TAGS = new Set([\n 'base',\n 'embed',\n 'form',\n 'iframe',\n 'input',\n 'link',\n 'math',\n 'meta',\n 'noscript',\n 'object',\n 'script',\n 'style',\n 'svg',\n 'template',\n 'textarea',\n]);\n\nconst SAFE_LINK_TARGETS = new Set(['_blank', '_parent', '_self', '_top']);\nconst SAFE_LINK_REL_TOKENS = new Set(['nofollow', 'noopener', 'noreferrer', 'sponsored', 'ugc']);\nconst SAFE_DATA_IMAGE_URL_PATTERN = /^data:image\\/(?:avif|bmp|gif|jpe?g|png|webp);base64,[a-z0-9+/=\\s]+$/i;\n\nfunction stripControlCharacters(value: string): string {\n return value.replace(/[\\u0000-\\u001F\\u007F]/g, '');\n}\n\nfunction normalizeProtocolProbe(value: string): string {\n return stripControlCharacters(value).replace(/\\s+/g, '');\n}\n\nfunction sanitizeLinkRel(rel: string): string {\n const safeTokens = rel\n .toLowerCase()\n .split(/\\s+/)\n .filter(Boolean)\n .filter((token) => SAFE_LINK_REL_TOKENS.has(token));\n return Array.from(new Set(safeTokens)).join(' ');\n}\n\nfunction withSafeBlankTargetRel(existingRel: string | null): string {\n const safeRel = sanitizeLinkRel(existingRel ?? '');\n const relTokens = new Set(safeRel.split(/\\s+/).filter(Boolean));\n relTokens.add('noopener');\n relTokens.add('noreferrer');\n return Array.from(relTokens).join(' ');\n}\n\nfunction createSanitizationDocument(): Document | null {\n if (\n typeof document !== 'undefined'\n && typeof document.implementation?.createHTMLDocument === 'function'\n ) {\n return document.implementation.createHTMLDocument('');\n }\n\n if (typeof DOMParser !== 'undefined') {\n const parsed = new DOMParser().parseFromString('<!doctype html><html><body></body></html>', 'text/html');\n if (parsed?.body) return parsed;\n }\n\n return null;\n}\n\nfunction escapeHtml(rawHtml: string): string {\n return rawHtml\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;');\n}\n\nfunction sanitizeRichTextAttributes(source: Element, target: HTMLElement): void {\n const tagName = target.tagName.toLowerCase();\n\n for (const attribute of Array.from(source.attributes)) {\n const attrName = attribute.name.toLowerCase();\n const attrValue = stripControlCharacters(attribute.value).trim();\n\n if (attrValue.length === 0 || attrName.startsWith('on')) continue;\n\n if (attrName === 'title') {\n target.setAttribute('title', attrValue);\n continue;\n }\n\n if (tagName !== 'a') continue;\n\n if (attrName === 'href') {\n const sanitizedHref = sanitizeUrlByKind(attrValue, 'link');\n if (sanitizedHref.length > 0) target.setAttribute('href', sanitizedHref);\n continue;\n }\n\n if (attrName === 'target') {\n const normalizedTarget = attrValue.toLowerCase();\n if (SAFE_LINK_TARGETS.has(normalizedTarget)) {\n target.setAttribute('target', normalizedTarget);\n }\n continue;\n }\n\n if (attrName === 'rel') {\n const sanitizedRel = sanitizeLinkRel(attrValue);\n if (sanitizedRel.length > 0) target.setAttribute('rel', sanitizedRel);\n }\n }\n\n if (tagName === 'a' && target.getAttribute('target') === '_blank') {\n target.setAttribute('rel', withSafeBlankTargetRel(target.getAttribute('rel')));\n }\n}\n\nfunction sanitizeRichTextChildren(source: ParentNode, target: ParentNode, doc: Document): void {\n for (const node of Array.from(source.childNodes)) {\n if (node.nodeType === 3) {\n target.appendChild(doc.createTextNode(node.textContent ?? ''));\n continue;\n }\n\n if (node.nodeType !== 1) continue;\n\n const sourceElement = node as Element;\n const sourceTagName = sourceElement.tagName.toLowerCase();\n\n if (DROP_ENTIRELY_TAGS.has(sourceTagName)) continue;\n\n if (!SAFE_RICH_TEXT_TAGS.has(sourceTagName)) {\n const unwrappedChildren = doc.createDocumentFragment();\n sanitizeRichTextChildren(sourceElement, unwrappedChildren, doc);\n target.appendChild(unwrappedChildren);\n continue;\n }\n\n const sanitizedElement = doc.createElement(sourceTagName);\n sanitizeRichTextAttributes(sourceElement, sanitizedElement);\n sanitizeRichTextChildren(sourceElement, sanitizedElement, doc);\n\n if (sourceTagName === 'a' && !sanitizedElement.getAttribute('href')) {\n const safeChildren = doc.createDocumentFragment();\n while (sanitizedElement.firstChild) {\n safeChildren.appendChild(sanitizedElement.firstChild);\n }\n target.appendChild(safeChildren);\n continue;\n }\n\n target.appendChild(sanitizedElement);\n }\n}\n\nexport function sanitizeRichTextHtml(html: string): string {\n const rawHtml = typeof html === 'string' ? html : '';\n if (rawHtml.length === 0) return '';\n\n const doc = createSanitizationDocument();\n if (!doc) return escapeHtml(rawHtml);\n\n const source = doc.createElement('div');\n source.innerHTML = rawHtml;\n\n const output = doc.createElement('div');\n sanitizeRichTextChildren(source, output, doc);\n return output.innerHTML;\n}\n\nexport function normalizeSafeHtmlTag(tag: unknown, fallback = 'div'): string {\n const normalizedFallback = typeof fallback === 'string' ? fallback.trim().toLowerCase() : 'div';\n const safeFallback = SAFE_TEXT_CONTAINER_TAGS.has(normalizedFallback) ? normalizedFallback : 'div';\n if (typeof tag !== 'string') return safeFallback;\n\n const normalizedTag = tag.trim().toLowerCase();\n if (!SAFE_TEXT_CONTAINER_TAGS.has(normalizedTag)) return safeFallback;\n return normalizedTag;\n}\n\nexport function sanitizeUrlByKind(url: string, kind: 'link' | 'media' | 'background'): string {\n const sanitizedInput = stripControlCharacters(url).trim();\n if (sanitizedInput.length === 0) return '';\n\n const protocolProbe = normalizeProtocolProbe(sanitizedInput).toLowerCase();\n const schemeMatch = protocolProbe.match(/^([a-z][a-z0-9+.-]*):/i);\n const scheme = schemeMatch?.[1];\n\n if (!scheme) return sanitizedInput;\n\n if (scheme === 'http' || scheme === 'https') return sanitizedInput;\n\n if (kind === 'link' && (scheme === 'mailto' || scheme === 'tel')) {\n return sanitizedInput;\n }\n\n if (kind === 'media' && scheme === 'blob') {\n return sanitizedInput;\n }\n\n if ((kind === 'media' || kind === 'background') && scheme === 'data') {\n return SAFE_DATA_IMAGE_URL_PATTERN.test(protocolProbe) ? sanitizedInput : '';\n }\n\n return '';\n}\n","import type { INode, IPageData, IPageMeta } from '@/types/node';\nimport { normalizeSafeHtmlTag, sanitizeUrlByKind } from '@/core/sanitize';\n\nexport interface IValidationError {\n path: string;\n message: string;\n}\n\nexport interface IValidationResult {\n isValid: boolean;\n errors: IValidationError[];\n}\n\ninterface IValidationContext {\n errors: IValidationError[];\n seenIds: Set<number>;\n seenNodes: WeakSet<object>;\n maxObservedId: number;\n visitedNodeCount: number;\n depthGuardTriggered: boolean;\n sizeGuardTriggered: boolean;\n}\n\nconst PAGE_STATUSES = new Set<IPageMeta['status']>(['draft', 'published', 'archived']);\nconst MAX_VALIDATION_DEPTH = 200;\nconst MAX_VALIDATION_NODE_COUNT = 5000;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction addError(context: IValidationContext, path: string, message: string): void {\n context.errors.push({ path, message });\n}\n\nfunction validatePbTextProps(\n props: Record<string, unknown>,\n path: string,\n context: IValidationContext,\n): void {\n if (props.tag === undefined) return;\n\n if (typeof props.tag !== 'string' || props.tag.trim() === '') {\n addError(context, `${path}.tag`, 'PbText props.tag must be a non-empty string.');\n return;\n }\n\n const normalizedTag = props.tag.trim().toLowerCase();\n if (normalizeSafeHtmlTag(props.tag) !== normalizedTag) {\n addError(\n context,\n `${path}.tag`,\n 'PbText props.tag must be one of: div, p, span, h1, h2, h3, h4, h5, h6, section, article, blockquote.',\n );\n }\n}\n\nfunction validatePbImageProps(\n props: Record<string, unknown>,\n path: string,\n context: IValidationContext,\n): void {\n if (typeof props.src !== 'string' || props.src.trim() === '') {\n addError(context, `${path}.src`, 'PbImage props.src must be a non-empty string.');\n return;\n }\n\n if (sanitizeUrlByKind(props.src, 'media') === '') {\n addError(context, `${path}.src`, 'PbImage props.src contains an unsafe URL.');\n }\n}\n\nfunction validatePbVideoProps(\n props: Record<string, unknown>,\n path: string,\n context: IValidationContext,\n): void {\n if (typeof props.src !== 'string' || props.src.trim() === '') {\n addError(context, `${path}.src`, 'PbVideo props.src must be a non-empty string.');\n return;\n }\n\n if (sanitizeUrlByKind(props.src, 'media') === '') {\n addError(context, `${path}.src`, 'PbVideo props.src contains an unsafe URL.');\n }\n\n const poster = props.poster;\n if (poster !== undefined && poster !== null && poster !== '') {\n if (typeof poster !== 'string') {\n addError(context, `${path}.poster`, 'PbVideo props.poster must be a string.');\n } else if (poster.trim() !== '' && sanitizeUrlByKind(poster, 'media') === '') {\n addError(context, `${path}.poster`, 'PbVideo props.poster contains an unsafe URL.');\n }\n }\n}\n\nfunction validatePbSectionProps(\n props: Record<string, unknown>,\n path: string,\n context: IValidationContext,\n): void {\n const backgroundImage = props.backgroundImage;\n if (backgroundImage === undefined || backgroundImage === null || backgroundImage === '') return;\n\n if (typeof backgroundImage !== 'string') {\n addError(context, `${path}.backgroundImage`, 'PbSection props.backgroundImage must be a string.');\n return;\n }\n\n if (backgroundImage.trim() !== '' && sanitizeUrlByKind(backgroundImage, 'background') === '') {\n addError(context, `${path}.backgroundImage`, 'PbSection props.backgroundImage contains an unsafe URL.');\n }\n}\n\nfunction validateKnownComponentPropsInto(\n name: string,\n props: Record<string, unknown>,\n path: string,\n context: IValidationContext,\n): void {\n if (name === 'PbText') {\n validatePbTextProps(props, path, context);\n return;\n }\n\n if (name === 'PbImage') {\n validatePbImageProps(props, path, context);\n return;\n }\n\n if (name === 'PbVideo') {\n validatePbVideoProps(props, path, context);\n return;\n }\n\n if (name === 'PbSection') {\n validatePbSectionProps(props, path, context);\n }\n}\n\nfunction validateNodeInto(node: unknown, path: string, context: IValidationContext, depth = 0): void {\n if (depth > MAX_VALIDATION_DEPTH) {\n if (!context.depthGuardTriggered) {\n addError(\n context,\n path,\n `Maximum node depth (${String(MAX_VALIDATION_DEPTH)}) exceeded during validation.`,\n );\n context.depthGuardTriggered = true;\n }\n return;\n }\n\n if (context.visitedNodeCount >= MAX_VALIDATION_NODE_COUNT) {\n if (!context.sizeGuardTriggered) {\n addError(\n context,\n path,\n `Maximum node count (${String(MAX_VALIDATION_NODE_COUNT)}) exceeded during validation.`,\n );\n context.sizeGuardTriggered = true;\n }\n return;\n }\n\n if (!isRecord(node)) {\n addError(context, path, 'Node must be an object.');\n return;\n }\n\n if (context.seenNodes.has(node)) {\n addError(context, path, 'Cycle detected in node tree.');\n return;\n }\n context.seenNodes.add(node);\n context.visitedNodeCount++;\n\n const id = node.id;\n const name = node.name;\n const slot = node.slot;\n const props = node.props;\n const children = node.children;\n const readonly = node.readonly;\n\n if (!(typeof id === 'number' && Number.isInteger(id)) || id <= 0) {\n addError(context, `${path}.id`, 'id must be a positive integer.');\n } else {\n if (context.seenIds.has(id)) {\n addError(context, `${path}.id`, `Duplicate node id \"${id}\" found.`);\n }\n context.seenIds.add(id);\n if (id > context.maxObservedId) context.maxObservedId = id;\n }\n\n if (typeof name !== 'string' || name.trim() === '') {\n addError(context, `${path}.name`, 'name must be a non-empty string.');\n }\n\n if (!(slot === null || typeof slot === 'string')) {\n addError(context, `${path}.slot`, 'slot must be a string or null.');\n }\n\n if (!isRecord(props)) {\n addError(context, `${path}.props`, 'props must be an object.');\n } else if (typeof name === 'string') {\n validateKnownComponentPropsInto(name, props, `${path}.props`, context);\n }\n\n if (!Array.isArray(children)) {\n addError(context, `${path}.children`, 'children must be an array.');\n return;\n }\n\n if (!(readonly === undefined || typeof readonly === 'boolean')) {\n addError(context, `${path}.readonly`, 'readonly must be a boolean when provided.');\n }\n\n for (let index = 0; index < children.length; index++) {\n if (context.sizeGuardTriggered) break;\n validateNodeInto(children[index], `${path}.children[${index}]`, context, depth + 1);\n }\n}\n\nexport function validateNode(node: unknown, path = 'node'): IValidationResult {\n const context: IValidationContext = {\n errors: [],\n seenIds: new Set<number>(),\n seenNodes: new WeakSet<object>(),\n maxObservedId: 0,\n visitedNodeCount: 0,\n depthGuardTriggered: false,\n sizeGuardTriggered: false,\n };\n\n validateNodeInto(node, path, context);\n\n return {\n isValid: context.errors.length === 0,\n errors: context.errors,\n };\n}\n\nexport function validatePageData(pageData: unknown): IValidationResult {\n const context: IValidationContext = {\n errors: [],\n seenIds: new Set<number>(),\n seenNodes: new WeakSet<object>(),\n maxObservedId: 0,\n visitedNodeCount: 0,\n depthGuardTriggered: false,\n sizeGuardTriggered: false,\n };\n\n if (!isRecord(pageData)) {\n addError(context, 'pageData', 'pageData must be an object.');\n return {\n isValid: false,\n errors: context.errors,\n };\n }\n\n const { meta, content, layout, maxId, variables } = pageData;\n\n if (!isRecord(meta)) {\n addError(context, 'meta', 'meta must be an object.');\n } else {\n if (typeof meta.id !== 'string' || meta.id.trim() === '') {\n addError(context, 'meta.id', 'meta.id must be a non-empty string.');\n }\n if (typeof meta.name !== 'string' || meta.name.trim() === '') {\n addError(context, 'meta.name', 'meta.name must be a non-empty string.');\n }\n if (typeof meta.url !== 'string' || meta.url.trim() === '') {\n addError(context, 'meta.url', 'meta.url must be a non-empty string.');\n } else if (sanitizeUrlByKind(meta.url, 'link') === '') {\n addError(context, 'meta.url', 'meta.url contains an unsafe URL.');\n }\n if (typeof meta.status !== 'string' || !PAGE_STATUSES.has(meta.status as IPageMeta['status'])) {\n addError(\n context,\n 'meta.status',\n 'meta.status must be one of: draft, published, archived.',\n );\n }\n if (!(meta.updatedAt === undefined || typeof meta.updatedAt === 'string')) {\n addError(context, 'meta.updatedAt', 'meta.updatedAt must be a string when provided.');\n }\n if (!(meta.createdAt === undefined || typeof meta.createdAt === 'string')) {\n addError(context, 'meta.createdAt', 'meta.createdAt must be a string when provided.');\n }\n }\n\n validateNodeInto(content, 'content', context);\n validateNodeInto(layout, 'layout', context);\n\n if (!(typeof maxId === 'number' && Number.isInteger(maxId)) || maxId < 0) {\n addError(context, 'maxId', 'maxId must be a non-negative integer.');\n } else if (maxId < context.maxObservedId) {\n addError(\n context,\n 'maxId',\n `maxId (${String(maxId)}) must be greater than or equal to the maximum node id (${String(context.maxObservedId)}).`,\n );\n }\n\n if (!isRecord(variables)) {\n addError(context, 'variables', 'variables must be an object.');\n } else {\n for (const [key, value] of Object.entries(variables)) {\n if (typeof value !== 'string') {\n addError(context, `variables.${key}`, 'Variable values must be strings.');\n }\n }\n }\n\n return {\n isValid: context.errors.length === 0,\n errors: context.errors,\n };\n}\n","import type { INode } from '@/types/node';\nimport { createPageBuilderError } from '@/core/errors';\n\nfunction getChildren(node: INode): INode[] {\n return Array.isArray(node.children) ? node.children : [];\n}\n\nfunction clampIndex(index: number, length: number): number {\n const normalized = Number.isFinite(index) ? Math.trunc(index) : 0;\n return Math.max(0, Math.min(normalized, length));\n}\n\n/**\n * Deep clone a node tree using structured clone.\n */\nexport function cloneTree(node: INode): INode {\n try {\n return structuredClone(node);\n } catch (error) {\n throw createPageBuilderError(\n 'INVALID_NODE',\n '[PageBuilder] Failed to clone node tree. Ensure the tree is serializable and acyclic.',\n {\n cause: error,\n },\n );\n }\n}\n\n/**\n * Find a node by ID in the tree. Returns undefined if not found.\n */\nexport function findNodeById(root: INode, id: number): INode | undefined {\n if (root.id === id) return root;\n for (const child of getChildren(root)) {\n const found = findNodeById(child, id);\n if (found) return found;\n }\n return undefined;\n}\n\n/**\n * Find the parent of a node by the child's ID.\n * Returns the parent node and the child's index, or undefined.\n */\nexport function findParent(\n root: INode,\n childId: number,\n): { parent: INode; index: number } | undefined {\n const children = getChildren(root);\n for (let i = 0; i < children.length; i++) {\n if (children[i].id === childId) {\n return { parent: root, index: i };\n }\n const found = findParent(children[i], childId);\n if (found) return found;\n }\n return undefined;\n}\n\n/**\n * Remove a node by ID from the tree. Returns the removed node or undefined.\n */\nexport function removeNode(root: INode, id: number): INode | undefined {\n const result = findParent(root, id);\n if (!result) return undefined;\n return result.parent.children.splice(result.index, 1)[0];\n}\n\n/**\n * Insert a node as a child of a target node at a specific index and slot.\n */\nexport function insertNode(\n root: INode,\n parentId: number,\n node: INode,\n index: number,\n slot: string = 'default',\n): boolean {\n const parent = findNodeById(root, parentId);\n if (!parent) return false;\n if (!Array.isArray(parent.children)) {\n parent.children = [];\n }\n\n const targetIndex = clampIndex(index, parent.children.length);\n const insertedNode = { ...node, slot };\n parent.children.splice(targetIndex, 0, insertedNode);\n return true;\n}\n\n/**\n * Move a node within the tree to a new parent at a specific index.\n */\nexport function moveNode(\n root: INode,\n nodeId: number,\n newParentId: number,\n index: number,\n slot: string = 'default',\n): boolean {\n const sourceParentResult = findParent(root, nodeId);\n if (!sourceParentResult) return false;\n\n const sourceChildren = getChildren(sourceParentResult.parent);\n const [node] = sourceChildren.splice(sourceParentResult.index, 1);\n if (!node) return false;\n\n const moved = insertNode(root, newParentId, node, index, slot);\n if (moved) return true;\n\n // Roll back on failure so the caller never loses nodes due to invalid moves.\n const rollbackIndex = clampIndex(sourceParentResult.index, sourceChildren.length);\n sourceChildren.splice(rollbackIndex, 0, node);\n return false;\n}\n\n/**\n * Create a new node with default values and a given ID.\n */\nexport function createNode(\n id: number,\n name: string,\n options: Partial<Pick<INode, 'slot' | 'props' | 'children' | 'readonly'>> = {},\n): INode {\n return {\n id,\n name,\n slot: options.slot ?? 'default',\n props: options.props ?? {},\n children: options.children ?? [],\n readonly: options.readonly,\n };\n}\n\n/**\n * Walk the tree depth-first and call visitor for each node.\n * Return `false` from visitor to stop the entire traversal (not just the subtree).\n */\nexport function walkTree(root: INode, visitor: (node: INode, depth: number) => boolean | void, depth = 0): boolean {\n const visited = new WeakSet<object>();\n\n function visitNode(node: INode, currentDepth: number): boolean {\n if (visited.has(node as object)) return true;\n visited.add(node as object);\n\n if (visitor(node, currentDepth) === false) return false;\n for (const child of getChildren(node)) {\n if (visitNode(child, currentDepth + 1) === false) return false;\n }\n return true;\n }\n\n return visitNode(root, depth);\n}\n\n/**\n * Count the total number of nodes in the tree.\n */\nexport function countNodes(root: INode): number {\n let count = 0;\n walkTree(root, () => { count++; });\n return count;\n}\n\n/**\n * Get the maximum ID in the tree.\n */\nexport function getMaxId(root: INode): number {\n let max = root.id;\n walkTree(root, (node) => {\n if (Number.isFinite(node.id) && node.id > max) max = node.id;\n });\n return max;\n}\n\n/**\n * Interpolate template variables in node props.\n * Replaces `{{ VAR }}` patterns with values from the variables map.\n */\nexport function interpolateProps(\n props: Record<string, unknown>,\n variables: Record<string, string>,\n): Record<string, unknown> {\n if (!props || typeof props !== 'object' || Array.isArray(props)) {\n return {};\n }\n\n const safeVariables =\n variables && typeof variables === 'object' && !Array.isArray(variables) ? variables : {};\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(props)) {\n if (typeof value === 'string') {\n result[key] = value.replace(/\\{\\{\\s*(\\w+)\\s*\\}\\}/g, (_, varName: string) => {\n return safeVariables[varName] ?? `{{ ${varName} }}`;\n });\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Extract plain text from a node tree by collecting text content\n * from PbText (and similar) components and stripping HTML tags.\n */\nexport function extractPlainText(node: INode): string {\n const texts: string[] = [];\n walkTree(node, (n) => {\n if (n.props.content && typeof n.props.content === 'string') {\n const stripped = n.props.content.replace(/<[^>]*>/g, '');\n if (stripped.trim()) texts.push(stripped.trim());\n }\n });\n return texts.join(' ').replace(/\\s+/g, ' ').trim();\n}\n","import type { INode } from '@/types/node';\n\nexport interface IVirtualTreeRow {\n node: INode;\n id: number;\n key: string;\n depth: number;\n index: number;\n parentId: number | null;\n}\n\nexport interface IVirtualWindowRange {\n start: number;\n end: number;\n size: number;\n total: number;\n}\n\nexport interface IVirtualTreeIndexMaps {\n keyByIndex: string[];\n indexByKey: Map<string, number>;\n indexByNodeId: Map<number, number>;\n}\n\nexport interface IFlattenTreeOptions {\n createKey?: (node: INode) => string;\n}\n\nfunction toSafeInteger(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.trunc(value);\n}\n\nexport function createStableNodeKey(nodeId: number): string {\n return `ipb-node-${nodeId}`;\n}\n\n/**\n * Flatten a node tree in depth-first pre-order with depth metadata.\n * Uses an iterative stack to avoid recursion depth issues on large trees.\n */\nexport function flattenTree(root: INode, options: IFlattenTreeOptions = {}): IVirtualTreeRow[] {\n const createKey = options.createKey ?? ((node: INode) => createStableNodeKey(node.id));\n const rows: IVirtualTreeRow[] = [];\n const stack: Array<{ node: INode; depth: number; parentId: number | null }> = [\n { node: root, depth: 0, parentId: null },\n ];\n\n while (stack.length > 0) {\n const current = stack.pop();\n if (!current) break;\n\n const index = rows.length;\n rows.push({\n node: current.node,\n id: current.node.id,\n key: createKey(current.node),\n depth: current.depth,\n index,\n parentId: current.parentId,\n });\n\n for (let i = current.node.children.length - 1; i >= 0; i--) {\n stack.push({\n node: current.node.children[i],\n depth: current.depth + 1,\n parentId: current.node.id,\n });\n }\n }\n\n return rows;\n}\n\n/**\n * Compute a clamped window range over a flat list.\n */\nexport function computeWindowRange(\n total: number,\n startIndex: number,\n windowSize: number,\n overscan = 0,\n): IVirtualWindowRange {\n const safeTotal = Math.max(0, toSafeInteger(total));\n const safeWindowSize = Math.max(0, toSafeInteger(windowSize));\n const safeOverscan = Math.max(0, toSafeInteger(overscan));\n\n if (safeTotal === 0 || safeWindowSize === 0) {\n return { start: 0, end: 0, size: 0, total: safeTotal };\n }\n\n const maxStart = safeTotal - 1;\n const safeStartIndex = Math.min(Math.max(toSafeInteger(startIndex), 0), maxStart);\n const start = Math.max(0, safeStartIndex - safeOverscan);\n const end = Math.min(safeTotal, safeStartIndex + safeWindowSize + safeOverscan);\n\n return {\n start,\n end,\n size: Math.max(0, end - start),\n total: safeTotal,\n };\n}\n\n/**\n * Slice a list using a computed virtual window.\n */\nexport function sliceWindow<T>(\n rows: readonly T[],\n startIndex: number,\n windowSize: number,\n overscan = 0,\n): { rows: T[]; range: IVirtualWindowRange } {\n const range = computeWindowRange(rows.length, startIndex, windowSize, overscan);\n return {\n rows: rows.slice(range.start, range.end),\n range,\n };\n}\n\n/**\n * Build stable key/index lookup maps from flattened tree rows.\n */\nexport function createVirtualTreeIndexMaps(rows: readonly IVirtualTreeRow[]): IVirtualTreeIndexMaps {\n const keyByIndex: string[] = [];\n const indexByKey = new Map<string, number>();\n const indexByNodeId = new Map<number, number>();\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n keyByIndex[i] = row.key;\n\n if (!indexByKey.has(row.key)) {\n indexByKey.set(row.key, i);\n }\n\n if (!indexByNodeId.has(row.id)) {\n indexByNodeId.set(row.id, i);\n }\n }\n\n return {\n keyByIndex,\n indexByKey,\n indexByNodeId,\n };\n}\n"],"names":["PageBuilderError","code","message","options","__publicField","isPageBuilderError","error","createPageBuilderError","toErrorMessage","reportDevDiagnostic","context","details","SAFE_TEXT_CONTAINER_TAGS","SAFE_RICH_TEXT_TAGS","DROP_ENTIRELY_TAGS","SAFE_LINK_TARGETS","SAFE_LINK_REL_TOKENS","SAFE_DATA_IMAGE_URL_PATTERN","stripControlCharacters","value","normalizeProtocolProbe","sanitizeLinkRel","rel","safeTokens","token","withSafeBlankTargetRel","existingRel","safeRel","relTokens","createSanitizationDocument","_a","parsed","escapeHtml","rawHtml","sanitizeRichTextAttributes","source","target","tagName","attribute","attrName","attrValue","sanitizedHref","sanitizeUrlByKind","normalizedTarget","sanitizedRel","sanitizeRichTextChildren","doc","node","sourceElement","sourceTagName","unwrappedChildren","sanitizedElement","safeChildren","sanitizeRichTextHtml","html","output","normalizeSafeHtmlTag","tag","fallback","normalizedFallback","safeFallback","normalizedTag","url","kind","sanitizedInput","protocolProbe","schemeMatch","scheme","PAGE_STATUSES","MAX_VALIDATION_DEPTH","MAX_VALIDATION_NODE_COUNT","isRecord","addError","path","validatePbTextProps","props","validatePbImageProps","validatePbVideoProps","poster","validatePbSectionProps","backgroundImage","validateKnownComponentPropsInto","name","validateNodeInto","depth","id","slot","children","readonly","index","validateNode","validatePageData","pageData","meta","content","layout","maxId","variables","key","getChildren","clampIndex","length","normalized","cloneTree","findNodeById","root","child","found","findParent","childId","i","removeNode","result","insertNode","parentId","parent","targetIndex","insertedNode","moveNode","nodeId","newParentId","sourceParentResult","sourceChildren","rollbackIndex","createNode","walkTree","visitor","visited","visitNode","currentDepth","countNodes","count","getMaxId","max","interpolateProps","safeVariables","_","varName","extractPlainText","texts","n","stripped","toSafeInteger","createStableNodeKey","flattenTree","createKey","rows","stack","current","computeWindowRange","total","startIndex","windowSize","overscan","safeTotal","safeWindowSize","safeOverscan","maxStart","safeStartIndex","start","end","sliceWindow","range","createVirtualTreeIndexMaps","keyByIndex","indexByKey","indexByNodeId","row"],"mappings":"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"}