minisiwyg-editor 0.1.0 → 0.2.1

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.
package/README.md CHANGED
@@ -8,11 +8,11 @@ Spiritual successor to [Pell](https://github.com/jaredreich/pell) (~1.2kb, 12k s
8
8
 
9
9
  Try it in your browser: **[erikleon.github.io/minisiwyg-editor](https://erikleon.github.io/minisiwyg-editor/)**
10
10
 
11
- The demo runs the full editor + toolbar in ~3.5kb gzipped. Paste an XSS payload (`<img src=x onerror=alert(1)>`) and watch the sanitizer strip it in real time.
11
+ The demo runs the full editor + toolbar in <5kb gzipped. Paste an XSS payload (`<img src=x onerror=alert(1)>`) and watch the sanitizer strip it in real time.
12
12
 
13
13
  ## Features
14
14
 
15
- - **Tiny.** ~3.5kb gzipped total. 5kb hard limit enforced in CI.
15
+ - **Tiny.** <5kb gzipped total. 5kb hard limit enforced in CI.
16
16
  - **Zero runtime dependencies.** Nothing to audit, nothing to break.
17
17
  - **XSS protection at every entry point.** Whitelist-based HTML sanitizer blocks `javascript:`, `data:`, event handlers, and encoded bypass attempts. Tested against OWASP XSS cheat sheet vectors.
18
18
  - **Declarative policy.** JSON-serializable rules define allowed tags, attributes, protocols, depth, and length. Store policies in a database, transmit them over the wire, validate them with a schema.
@@ -70,6 +70,52 @@ import { createEditor } from 'minisiwyg-editor';
70
70
  import { createToolbar } from 'minisiwyg-editor/toolbar';
71
71
  ```
72
72
 
73
+ ## Framework Adapters
74
+
75
+ Thin wrappers for React, Vue, and Svelte ship as optional subpath entries. They're peerDependencies — install only the framework you use.
76
+
77
+ ### React
78
+
79
+ ```tsx
80
+ import { Minisiwyg } from 'minisiwyg-editor/react';
81
+
82
+ function App() {
83
+ return (
84
+ <Minisiwyg
85
+ initialHTML="<p>hello</p>"
86
+ onChange={(html) => console.log(html)}
87
+ editorRef={(editor) => { /* call editor.exec(...), etc. */ }}
88
+ />
89
+ );
90
+ }
91
+ ```
92
+
93
+ ### Vue 3
94
+
95
+ ```vue
96
+ <script setup lang="ts">
97
+ import { Minisiwyg } from 'minisiwyg-editor/vue';
98
+ </script>
99
+
100
+ <template>
101
+ <Minisiwyg initialHTML="<p>hello</p>" @change="(html) => console.log(html)" />
102
+ </template>
103
+ ```
104
+
105
+ ### Svelte
106
+
107
+ Ships as a Svelte [action](https://svelte.dev/docs/svelte-action) so there's no compiler coupling:
108
+
109
+ ```svelte
110
+ <script lang="ts">
111
+ import { minisiwyg } from 'minisiwyg-editor/svelte';
112
+ </script>
113
+
114
+ <div use:minisiwyg={{ initialHTML: '<p>hello</p>', onChange: (html) => console.log(html) }}></div>
115
+ ```
116
+
117
+ All three adapters accept `initialHTML`, `onChange`, and a `policy` override. Pass `value` instead of `initialHTML` to opt in to controlled mode: the adapter reconciles the DOM only when `value` differs from the current editor HTML, so you avoid cursor jumps. Adapters are mount-once — changing `policy` after mount does not re-initialize the editor; remount via `key` (React/Vue) to reset.
118
+
73
119
  ## Sanitizer
74
120
 
75
121
  The sanitizer is the security core. It parses HTML via a `<template>` element (no script execution), walks the DOM tree depth-first, and removes anything not in the whitelist.
@@ -197,8 +243,8 @@ Supported commands: `bold`, `italic`, `heading` (with value `'1'`, `'2'`, or `'3
197
243
  import { createToolbar } from 'minisiwyg-editor/toolbar';
198
244
 
199
245
  const toolbar = createToolbar(editor, {
200
- // Optional. Defaults to all built-in actions in this order:
201
- actions: ['bold', 'italic', 'heading', 'unorderedList', 'orderedList', 'link', 'codeBlock'],
246
+ // Optional. Defaults to all built-in actions, grouped by '|' separators:
247
+ actions: ['bold', 'italic', '|', 'heading', '|', 'unorderedList', 'orderedList', '|', 'link', 'codeBlock'],
202
248
  });
203
249
 
204
250
  document.body.appendChild(toolbar.element);
@@ -0,0 +1,6 @@
1
+ "use strict";var D=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var W=(e,r)=>{for(var o in r)D(e,o,{get:r[o],enumerable:!0})},K=(e,r,o,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let i of $(r))!G.call(e,i)&&i!==o&&D(e,i,{get:()=>r[i],enumerable:!(a=Y(r,i))||a.enumerable});return e};var Z=e=>K(D({},"__esModule",{value:!0}),e);var ee={};W(ee,{Minisiwyg:()=>X});module.exports=Z(ee);var A=require("react");var R={tags:{p:[],br:[],strong:[],em:[],a:["href","title","target"],h1:[],h2:[],h3:[],ul:[],ol:[],li:[],blockquote:[],pre:[],code:[]},strip:!0,maxDepth:10,maxLength:1e5,protocols:["https","http","mailto"]};Object.freeze(R);Object.freeze(R.protocols);for(let e of Object.values(R.tags))Object.freeze(e);Object.freeze(R.tags);var T=R;var S={b:"strong",i:"em"},k=new Set(["href","src","action","formaction"]),Q=new Set(["javascript","data"]);function J(e){let r=e.trim();r=r.replace(/&#x([0-9a-f]+);?/gi,(a,i)=>String.fromCharCode(parseInt(i,16))),r=r.replace(/&#(\d+);?/g,(a,i)=>String.fromCharCode(parseInt(i,10)));try{r=decodeURIComponent(r)}catch{}r=r.replace(/[\s\x00-\x1f\u00A0\u1680\u2000-\u200B\u2028\u2029\u202F\u205F\u3000\uFEFF]+/g,"");let o=r.match(/^([a-z][a-z0-9+\-.]*)\s*:/i);return o?o[1].toLowerCase():null}function P(e,r){let o=J(e);return o===null?!0:Q.has(o)?!1:r.includes(o)}function M(e,r,o){let a=Array.from(e.childNodes);for(let i of a){if(i.nodeType===3)continue;if(i.nodeType!==1){e.removeChild(i);continue}let n=i,u=n.tagName.toLowerCase(),l=S[u];if(l&&(u=l),o>=r.maxDepth){e.removeChild(n);continue}let s=r.tags[u];if(s===void 0){if(r.strip)e.removeChild(n);else{for(M(n,r,o);n.firstChild;)e.insertBefore(n.firstChild,n);e.removeChild(n)}continue}let f=n;if(l&&n.tagName.toLowerCase()!==l){let p=n.ownerDocument.createElement(l);for(;n.firstChild;)p.appendChild(n.firstChild);e.replaceChild(p,n),f=p}let b=Array.from(f.attributes);for(let L of b){let p=L.name.toLowerCase();if(p.startsWith("on")){f.removeAttribute(L.name);continue}if(!s.includes(p)){f.removeAttribute(L.name);continue}k.has(p)&&(P(L.value,r.protocols)||f.removeAttribute(L.name))}M(f,r,o+1)}}function O(e,r){let o=document.createElement("template");if(!e)return o.content;o.innerHTML=e;let a=o.content;return M(a,r,0),r.maxLength>0&&(a.textContent?.length??0)>r.maxLength&&U(a,r.maxLength),a}function U(e,r){let o=r,a=Array.from(e.childNodes);for(let i of a){if(o<=0){e.removeChild(i);continue}if(i.nodeType===3){let n=i.textContent??"";n.length>o?(i.textContent=n.slice(0,o),o=0):o-=n.length}else i.nodeType===1?o=U(i,o):e.removeChild(i)}return o}function V(e,r){let o=0,a=e.parentNode;for(;a&&a!==r;)a.nodeType===1&&o++,a=a.parentNode;return o}function I(e,r,o){let a=e.tagName.toLowerCase(),i=S[a];if(i&&(a=i),V(e,o)>=r.maxDepth)return e.parentNode?.removeChild(e),!0;let u=r.tags[a];if(u===void 0){if(r.strip)e.parentNode?.removeChild(e);else{let s=e.parentNode;if(s){for(;e.firstChild;)s.insertBefore(e.firstChild,e);s.removeChild(e)}}return!0}let l=e;if(i&&e.tagName.toLowerCase()!==i){let s=e.ownerDocument.createElement(i);for(;e.firstChild;)s.appendChild(e.firstChild);for(let f of Array.from(e.attributes))s.setAttribute(f.name,f.value);e.parentNode?.replaceChild(s,e),l=s}for(let s of Array.from(l.attributes)){let f=s.name.toLowerCase();if(f.startsWith("on")){l.removeAttribute(s.name);continue}if(!u.includes(f)){l.removeAttribute(s.name);continue}k.has(f)&&(P(s.value,r.protocols)||l.removeAttribute(s.name))}return!1}function _(e,r,o){let a=Array.from(e.childNodes);for(let i of a){if(i.nodeType!==1){i.nodeType!==3&&e.removeChild(i);continue}I(i,r,o)||_(i,r,o)}}function F(e,r){if(!r||!r.tags)throw new TypeError('Policy must have a "tags" property');let o=!1,a=[];function i(u){for(let l of a)l(u)}let n=new MutationObserver(u=>{if(!o){o=!0;try{for(let l of u)if(l.type==="childList")for(let s of Array.from(l.addedNodes)){if(s.nodeType===3)continue;if(s.nodeType!==1){s.parentNode?.removeChild(s);continue}I(s,r,e)||_(s,r,e)}else if(l.type==="attributes"){let s=l.target;if(s.nodeType!==1)continue;let f=l.attributeName;if(!f)continue;let b=s.tagName.toLowerCase(),L=S[b]||b,p=r.tags[L];if(!p)continue;let w=f.toLowerCase();if(w.startsWith("on")){s.removeAttribute(f);continue}if(!p.includes(w)){s.removeAttribute(f);continue}if(k.has(w)){let y=s.getAttribute(f);y&&!P(y,r.protocols)&&s.removeAttribute(f)}}}catch(l){i(l instanceof Error?l:new Error(String(l)))}finally{o=!1}}});return n.observe(e,{childList:!0,attributes:!0,subtree:!0}),{destroy(){n.disconnect()},on(u,l){u==="error"&&a.push(l)}}}var B=new Set(["bold","italic","heading","blockquote","unorderedList","orderedList","link","unlink","codeBlock"]);function q(e,r){if(!e)throw new TypeError("createEditor requires an HTMLElement");if(!e.ownerDocument||!e.parentNode)throw new TypeError("createEditor requires an element attached to the DOM");let o=r?.policy??T,a={tags:Object.fromEntries(Object.entries(o.tags).map(([d,c])=>[d,[...c]])),strip:o.strip,maxDepth:o.maxDepth,maxLength:o.maxLength,protocols:[...o.protocols]},i={},n=e.ownerDocument;function u(d,...c){for(let t of i[d]??[])t(...c)}function l(){let d=e.innerHTML;u("change",d),r?.onChange?.(d)}e.contentEditable="true";let s=F(e,a);s.on("error",d=>u("error",d));function f(d){d.preventDefault();let c=d.clipboardData;if(!c)return;let t=n.getSelection();if(t&&t.rangeCount>0&&t.anchorNode&&p(t.anchorNode,"PRE")){let E=c.getData("text/plain");if(!E)return;a.maxLength>0&&(e.textContent?.length??0)+E.length>a.maxLength&&u("overflow",a.maxLength);let x=t.getRangeAt(0);x.deleteContents();let H=n.createTextNode(E);x.insertNode(H),x.setStartAfter(H),x.collapse(!0),t.removeAllRanges(),t.addRange(x),u("paste",e.innerHTML),l();return}let g=c.getData("text/html");if(!g){let h=c.getData("text/plain");if(!h)return;g=h.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/\n/g,"<br>")}let m=O(g,a),v=n.getSelection();if(!v||v.rangeCount===0)return;let C=v.getRangeAt(0);if(C.deleteContents(),a.maxLength>0){let h=m.textContent?.length??0;(e.textContent?.length??0)+h>a.maxLength&&u("overflow",a.maxLength)}let N=m.lastChild;if(C.insertNode(m),N){let h=n.createRange();h.setStartAfter(N),h.collapse(!0),v.removeAllRanges(),v.addRange(h)}u("paste",e.innerHTML),l()}function b(){l()}function L(d){let c=n.getSelection();if(!c||c.rangeCount===0)return;let t=c.anchorNode;if(!t)return;let g=p(t,"PRE");if(d.key==="Enter"&&g){d.preventDefault();let m=c.getRangeAt(0);m.deleteContents();let v=n.createTextNode(`
2
+ `);m.insertNode(v),m.setStartAfter(v),m.collapse(!0),c.removeAllRanges(),c.addRange(m),l()}if(d.key==="Backspace"&&g){let m=g.textContent||"";if(c.anchorOffset===0&&(m===""||m===`
3
+ `)){d.preventDefault();let N=n.createElement("p");N.appendChild(n.createElement("br")),g.parentNode?.replaceChild(N,g);let h=n.createRange();h.selectNodeContents(N),h.collapse(!0),c.removeAllRanges(),c.addRange(h),l()}}}e.addEventListener("keydown",L),e.addEventListener("paste",f),e.addEventListener("input",b);function p(d,c){let t=d;for(;t&&t!==e;){if(t.nodeType===1&&t.tagName===c)return t;t=t.parentNode}return null}function w(d){let c=n.getSelection();if(!c||c.rangeCount===0)return!1;let t=c.anchorNode;if(!t)return!1;let g=p(t,d);if(!g)return!1;let m=p(t,"LI"),v=g.parentNode;if(!v)return!1;let C=[],N=null;for(let E of Array.from(g.childNodes)){if(E.nodeType!==1||E.tagName!=="LI")continue;let x=n.createElement("p");for(;E.firstChild;)x.appendChild(E.firstChild);x.firstChild||x.appendChild(n.createElement("br")),C.push(x),E===m&&(N=x)}for(let E of C)v.insertBefore(E,g);v.removeChild(g);let h=N??C[0];if(h){let E=n.createRange();E.selectNodeContents(h),E.collapse(!1),c.removeAllRanges(),c.addRange(E)}return l(),!0}function y(d,c){let t=d;for(;t&&t!==e;){if(t.nodeType===1&&t.tagName===c)return!0;t=t.parentNode}return!1}let z={exec(d,c){if(!B.has(d))throw new Error(`Unknown editor command: "${d}"`);switch(e.focus(),d){case"bold":n.execCommand("bold",!1);break;case"italic":n.execCommand("italic",!1);break;case"heading":{let t=c??"1";if(!["1","2","3"].includes(t))throw new Error(`Invalid heading level: "${t}". Use 1, 2, or 3`);let g=`H${t}`,m=n.getSelection()?.anchorNode;m&&z.queryState("heading")&&y(m,g)?n.execCommand("formatBlock",!1,"<p>"):n.execCommand("formatBlock",!1,`<h${t}>`);break}case"blockquote":z.queryState("blockquote")?n.execCommand("formatBlock",!1,"<p>"):n.execCommand("formatBlock",!1,"<blockquote>");break;case"unorderedList":w("UL")||n.execCommand("insertUnorderedList",!1);break;case"orderedList":w("OL")||n.execCommand("insertOrderedList",!1);break;case"link":{if(!c)throw new Error("Link command requires a URL value");let t=c.trim();if(!P(t,a.protocols)){u("error",new Error(`Protocol not allowed: ${t}`));return}n.execCommand("createLink",!1,t);break}case"unlink":n.execCommand("unlink",!1);break;case"codeBlock":{let t=n.getSelection();if(!t||t.rangeCount===0)break;let g=t.anchorNode,m=g?p(g,"PRE"):null;if(m){let v=n.createElement("p");v.textContent=m.textContent||"",m.parentNode?.replaceChild(v,m);let C=n.createRange();C.selectNodeContents(v),C.collapse(!1),t.removeAllRanges(),t.addRange(C)}else{let C=t.getRangeAt(0).startContainer;for(;C.parentNode&&C.parentNode!==e;)C=C.parentNode;let N=n.createElement("pre"),h=n.createElement("code"),E=C.textContent||"";h.textContent=E.endsWith(`
4
+ `)?E:E+`
5
+ `,N.appendChild(h),C.parentNode===e?e.replaceChild(N,C):e.appendChild(N);let x=n.createRange();x.selectNodeContents(h),x.collapse(!1),t.removeAllRanges(),t.addRange(x)}l();break}}},queryState(d){if(!B.has(d))throw new Error(`Unknown editor command: "${d}"`);let c=n.getSelection();if(!c||c.rangeCount===0)return!1;let t=c.anchorNode;if(!t||!e.contains(t))return!1;switch(d){case"bold":return y(t,"STRONG")||y(t,"B");case"italic":return y(t,"EM")||y(t,"I");case"heading":return y(t,"H1")||y(t,"H2")||y(t,"H3");case"blockquote":return y(t,"BLOCKQUOTE");case"unorderedList":return y(t,"UL");case"orderedList":return y(t,"OL");case"link":return y(t,"A");case"unlink":return!1;case"codeBlock":return y(t,"PRE");default:return!1}},getHTML(){return e.innerHTML},getText(){return e.textContent??""},destroy(){e.removeEventListener("keydown",L),e.removeEventListener("paste",f),e.removeEventListener("input",b),s.destroy(),e.contentEditable="false"},on(d,c){i[d]||(i[d]=[]),i[d].push(c)}};return z}var j=require("react/jsx-runtime");function X(e){let{initialHTML:r,value:o,onChange:a,policy:i,className:n,editorRef:u}=e,l=(0,A.useRef)(null),s=(0,A.useRef)(null),f=(0,A.useRef)(a),b=(0,A.useRef)(i??T);return(0,A.useEffect)(()=>{f.current=a},[a]),(0,A.useEffect)(()=>{let L=l.current;if(!L)return;let p=i??T;b.current=p,L.replaceChildren(O(o??r??"",p));let w=q(L,{policy:p,onChange:y=>f.current?.(y)});return s.current=w,u?.(w),()=>{w.destroy(),s.current=null,u?.(null)}},[]),(0,A.useEffect)(()=>{let L=s.current,p=l.current;!L||!p||o===void 0||L.getHTML()!==o&&p.replaceChildren(O(o,b.current))},[o]),(0,j.jsx)("div",{ref:l,className:n})}
6
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/adapters/react.tsx", "../../src/defaults.ts", "../../src/shared.ts", "../../src/sanitize.ts", "../../src/policy.ts", "../../src/editor.ts"],
4
+ "sourcesContent": ["import { useEffect, useRef } from 'react';\nimport { createEditor } from '../editor';\nimport { sanitizeToFragment } from '../sanitize';\nimport { DEFAULT_POLICY } from '../defaults';\nimport type { Editor, SanitizePolicy } from '../types';\n\nexport type { Editor, SanitizePolicy } from '../types';\n\nexport interface MinisiwygProps {\n initialHTML?: string;\n value?: string;\n onChange?: (html: string) => void;\n policy?: SanitizePolicy;\n className?: string;\n editorRef?: (editor: Editor | null) => void;\n}\n\nexport function Minisiwyg(props: MinisiwygProps) {\n const { initialHTML, value, onChange, policy, className, editorRef } = props;\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorInstance = useRef<Editor | null>(null);\n const onChangeRef = useRef<typeof onChange>(onChange);\n const policyRef = useRef<SanitizePolicy>(policy ?? DEFAULT_POLICY);\n\n useEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n useEffect(() => {\n const el = hostRef.current;\n if (!el) return;\n const effectivePolicy = policy ?? DEFAULT_POLICY;\n policyRef.current = effectivePolicy;\n el.replaceChildren(sanitizeToFragment(value ?? initialHTML ?? '', effectivePolicy));\n const editor = createEditor(el, {\n policy: effectivePolicy,\n onChange: (html) => onChangeRef.current?.(html),\n });\n editorInstance.current = editor;\n editorRef?.(editor);\n return () => {\n editor.destroy();\n editorInstance.current = null;\n editorRef?.(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const editor = editorInstance.current;\n const el = hostRef.current;\n if (!editor || !el || value === undefined) return;\n if (editor.getHTML() !== value) {\n el.replaceChildren(sanitizeToFragment(value, policyRef.current));\n }\n }, [value]);\n\n return <div ref={hostRef} className={className} />;\n}\n", "import type { SanitizePolicy } from './types';\n\nconst policy: SanitizePolicy = {\n tags: {\n p: [],\n br: [],\n strong: [],\n em: [],\n a: ['href', 'title', 'target'],\n h1: [],\n h2: [],\n h3: [],\n ul: [],\n ol: [],\n li: [],\n blockquote: [],\n pre: [],\n code: [],\n },\n strip: true,\n maxDepth: 10,\n maxLength: 100_000,\n protocols: ['https', 'http', 'mailto'],\n};\n\n// Deep freeze to prevent mutation of security-critical defaults\nObject.freeze(policy);\nObject.freeze(policy.protocols);\nfor (const attrs of Object.values(policy.tags)) Object.freeze(attrs);\nObject.freeze(policy.tags);\n\nexport const DEFAULT_POLICY: Readonly<SanitizePolicy> = policy;\n", "/** Tag normalization map: browser-variant tags \u2192 semantic equivalents. */\nexport const TAG_NORMALIZE: Record<string, string> = {\n b: 'strong',\n i: 'em',\n};\n\n/** Attributes that contain URLs and need protocol validation. */\nexport const URL_ATTRS = new Set(['href', 'src', 'action', 'formaction']);\n\n/** Protocols that are always denied regardless of policy. */\nexport const DENIED_PROTOCOLS = new Set(['javascript', 'data']);\n\n/**\n * Parse a URL-like string and extract the protocol.\n * Returns the lowercase protocol name (without colon), or null if none found.\n */\nexport function extractProtocol(value: string): string | null {\n let decoded = value.trim();\n decoded = decoded.replace(/&#x([0-9a-f]+);?/gi, (_, hex) =>\n String.fromCharCode(parseInt(hex, 16)),\n );\n decoded = decoded.replace(/&#(\\d+);?/g, (_, dec) =>\n String.fromCharCode(parseInt(dec, 10)),\n );\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n // keep entity-decoded result\n }\n decoded = decoded.replace(/[\\s\\x00-\\x1f\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]+/g, '');\n const match = decoded.match(/^([a-z][a-z0-9+\\-.]*)\\s*:/i);\n return match ? match[1].toLowerCase() : null;\n}\n\n/**\n * Check if a URL value is allowed by the given protocol list.\n * javascript: and data: are always denied.\n */\nexport function isProtocolAllowed(value: string, allowedProtocols: string[]): boolean {\n const protocol = extractProtocol(value);\n if (protocol === null) return true;\n if (DENIED_PROTOCOLS.has(protocol)) return false;\n return allowedProtocols.includes(protocol);\n}\n", "import type { SanitizePolicy } from './types';\nimport { TAG_NORMALIZE, URL_ATTRS, isProtocolAllowed } from './shared';\nexport { DEFAULT_POLICY } from './defaults';\nexport type { SanitizePolicy } from './types';\n\n/**\n * Walk a DOM tree depth-first and sanitize according to policy.\n * Mutates the tree in place.\n */\nfunction walkAndSanitize(\n parent: Node,\n policy: SanitizePolicy,\n depth: number,\n): void {\n const children = Array.from(parent.childNodes);\n\n for (const node of children) {\n // Text nodes: always allowed (length enforcement happens after walk)\n if (node.nodeType === 3) continue;\n\n // Non-element, non-text nodes (comments, processing instructions): remove\n if (node.nodeType !== 1) {\n parent.removeChild(node);\n continue;\n }\n\n const el = node as Element;\n let tagName = el.tagName.toLowerCase();\n\n // Normalize tags (b\u2192strong, i\u2192em)\n const normalized = TAG_NORMALIZE[tagName];\n if (normalized) {\n tagName = normalized;\n }\n\n // Check depth limit\n if (depth >= policy.maxDepth) {\n parent.removeChild(el);\n continue;\n }\n\n // Check tag whitelist\n const allowedAttrs = policy.tags[tagName];\n if (allowedAttrs === undefined) {\n // Tag not allowed\n if (policy.strip) {\n // Remove the node and all its children\n parent.removeChild(el);\n } else {\n // Unwrap: sanitize children first, then move them up\n walkAndSanitize(el, policy, depth);\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n continue;\n }\n\n // Tag is allowed. If it was normalized, replace with correct element.\n let current: Element = el;\n if (normalized && el.tagName.toLowerCase() !== normalized) {\n const doc = el.ownerDocument!;\n const replacement = doc.createElement(normalized);\n while (el.firstChild) {\n replacement.appendChild(el.firstChild);\n }\n parent.replaceChild(replacement, el);\n current = replacement;\n }\n\n // Strip disallowed attributes\n const attrs = Array.from(current.attributes);\n for (const attr of attrs) {\n const attrName = attr.name.toLowerCase();\n\n // Always strip event handlers (on*)\n if (attrName.startsWith('on')) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n // Check attribute whitelist\n if (!allowedAttrs.includes(attrName)) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n // Validate URL protocols on URL-bearing attributes\n if (URL_ATTRS.has(attrName)) {\n if (!isProtocolAllowed(attr.value, policy.protocols)) {\n current.removeAttribute(attr.name);\n }\n }\n }\n\n // Recurse into children\n walkAndSanitize(current, policy, depth + 1);\n }\n}\n\n/**\n * Sanitize an HTML string and return a DocumentFragment.\n * Avoids the serialize\u2192reparse round-trip that can cause mXSS.\n */\nexport function sanitizeToFragment(html: string, policy: SanitizePolicy): DocumentFragment {\n const template = document.createElement('template');\n if (!html) return template.content;\n\n template.innerHTML = html;\n const fragment = template.content;\n\n walkAndSanitize(fragment, policy, 0);\n\n if (policy.maxLength > 0 && (fragment.textContent?.length ?? 0) > policy.maxLength) {\n truncateToLength(fragment, policy.maxLength);\n }\n\n return fragment;\n}\n\n/**\n * Sanitize an HTML string according to the given policy.\n *\n * Uses a <template> element to parse HTML without executing scripts.\n * Walks the resulting DOM tree depth-first, removing disallowed elements\n * and attributes. Returns the sanitized HTML string.\n */\nexport function sanitize(html: string, policy: SanitizePolicy): string {\n if (!html) return '';\n\n const fragment = sanitizeToFragment(html, policy);\n const container = document.createElement('div');\n container.appendChild(fragment);\n return container.innerHTML;\n}\n\n/**\n * Truncate a DOM tree's text content to a maximum length.\n * Removes nodes beyond the limit while preserving structure.\n */\nfunction truncateToLength(node: Node, maxLength: number): number {\n let remaining = maxLength;\n\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (remaining <= 0) {\n node.removeChild(child);\n continue;\n }\n\n if (child.nodeType === 3) {\n // Text node\n const text = child.textContent ?? '';\n if (text.length > remaining) {\n child.textContent = text.slice(0, remaining);\n remaining = 0;\n } else {\n remaining -= text.length;\n }\n } else if (child.nodeType === 1) {\n remaining = truncateToLength(child, remaining);\n } else {\n node.removeChild(child);\n }\n }\n\n return remaining;\n}\n", "import type { SanitizePolicy } from './types';\nimport { TAG_NORMALIZE, URL_ATTRS, isProtocolAllowed } from './shared';\n\nexport { DEFAULT_POLICY } from './defaults';\nexport type { SanitizePolicy } from './types';\n\nexport interface PolicyEnforcer {\n destroy(): void;\n on(event: 'error', handler: (error: Error) => void): void;\n}\n\n/**\n * Get the nesting depth of a node within a root element.\n */\nfunction getDepth(node: Node, root: Node): number {\n let depth = 0;\n let current = node.parentNode;\n while (current && current !== root) {\n if (current.nodeType === 1) depth++;\n current = current.parentNode;\n }\n return depth;\n}\n\n/**\n * Check if an element is allowed by the policy and fix it if not.\n * Returns true if the node was removed/replaced.\n */\nfunction enforceElement(\n el: Element,\n policy: SanitizePolicy,\n root: HTMLElement,\n): boolean {\n let tagName = el.tagName.toLowerCase();\n const normalized = TAG_NORMALIZE[tagName];\n if (normalized) tagName = normalized;\n\n // Check depth\n const depth = getDepth(el, root);\n if (depth >= policy.maxDepth) {\n el.parentNode?.removeChild(el);\n return true;\n }\n\n // Check tag whitelist\n const allowedAttrs = policy.tags[tagName];\n if (allowedAttrs === undefined) {\n if (policy.strip) {\n el.parentNode?.removeChild(el);\n } else {\n // Unwrap: move children up, then remove the element\n const parent = el.parentNode;\n if (parent) {\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n }\n return true;\n }\n\n // Normalize tag if needed (e.g. <b> \u2192 <strong>)\n let current: Element = el;\n if (normalized && el.tagName.toLowerCase() !== normalized) {\n const replacement = el.ownerDocument.createElement(normalized);\n while (el.firstChild) {\n replacement.appendChild(el.firstChild);\n }\n // Copy allowed attributes\n for (const attr of Array.from(el.attributes)) {\n replacement.setAttribute(attr.name, attr.value);\n }\n el.parentNode?.replaceChild(replacement, el);\n current = replacement;\n }\n\n // Strip disallowed attributes\n for (const attr of Array.from(current.attributes)) {\n const attrName = attr.name.toLowerCase();\n\n if (attrName.startsWith('on')) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n if (!allowedAttrs.includes(attrName)) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n if (URL_ATTRS.has(attrName)) {\n if (!isProtocolAllowed(attr.value, policy.protocols)) {\n current.removeAttribute(attr.name);\n }\n }\n }\n\n return false;\n}\n\n/**\n * Recursively enforce policy on all descendants of a node.\n */\nfunction enforceSubtree(node: Node, policy: SanitizePolicy, root: HTMLElement): void {\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (child.nodeType !== 1) {\n // Remove non-text, non-element nodes (comments, etc.)\n if (child.nodeType !== 3) {\n node.removeChild(child);\n }\n continue;\n }\n const removed = enforceElement(child as Element, policy, root);\n if (!removed) {\n enforceSubtree(child, policy, root);\n }\n }\n}\n\n/**\n * Create a policy enforcer that uses MutationObserver to enforce\n * the sanitization policy on a live DOM element.\n *\n * This is defense-in-depth \u2014 the paste handler is the primary security boundary.\n * The observer catches mutations from execCommand, programmatic DOM manipulation,\n * and other sources.\n */\nexport function createPolicyEnforcer(\n element: HTMLElement,\n policy: SanitizePolicy,\n): PolicyEnforcer {\n if (!policy || !policy.tags) {\n throw new TypeError('Policy must have a \"tags\" property');\n }\n\n let isApplyingFix = false;\n const errorHandlers: Array<(error: Error) => void> = [];\n\n function emitError(error: Error): void {\n for (const handler of errorHandlers) {\n handler(error);\n }\n }\n\n const observer = new MutationObserver((mutations) => {\n if (isApplyingFix) return;\n isApplyingFix = true;\n\n try {\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const node of Array.from(mutation.addedNodes)) {\n // Skip text nodes\n if (node.nodeType === 3) continue;\n\n // Remove non-element nodes\n if (node.nodeType !== 1) {\n node.parentNode?.removeChild(node);\n continue;\n }\n\n const removed = enforceElement(node as Element, policy, element);\n if (!removed) {\n // Also enforce on all descendants of the added node\n enforceSubtree(node, policy, element);\n }\n }\n } else if (mutation.type === 'attributes') {\n const target = mutation.target as Element;\n if (target.nodeType !== 1) continue;\n\n const attrName = mutation.attributeName;\n if (!attrName) continue;\n\n const tagName = target.tagName.toLowerCase();\n const normalizedTag = TAG_NORMALIZE[tagName] || tagName;\n const allowedAttrs = policy.tags[normalizedTag];\n\n if (!allowedAttrs) continue;\n\n const lowerAttr = attrName.toLowerCase();\n\n if (lowerAttr.startsWith('on')) {\n target.removeAttribute(attrName);\n continue;\n }\n\n if (!allowedAttrs.includes(lowerAttr)) {\n target.removeAttribute(attrName);\n continue;\n }\n\n if (URL_ATTRS.has(lowerAttr)) {\n const value = target.getAttribute(attrName);\n if (value && !isProtocolAllowed(value, policy.protocols)) {\n target.removeAttribute(attrName);\n }\n }\n }\n }\n } catch (err) {\n emitError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isApplyingFix = false;\n }\n });\n\n observer.observe(element, {\n childList: true,\n attributes: true,\n subtree: true,\n });\n\n return {\n destroy() {\n observer.disconnect();\n },\n on(event: 'error', handler: (error: Error) => void) {\n if (event === 'error') {\n errorHandlers.push(handler);\n }\n },\n };\n}\n", "import type { SanitizePolicy, EditorOptions, Editor } from './types';\nimport { DEFAULT_POLICY } from './defaults';\nimport { sanitizeToFragment } from './sanitize';\nimport { createPolicyEnforcer, type PolicyEnforcer } from './policy';\nimport { isProtocolAllowed } from './shared';\n\nexport type { Editor, EditorOptions } from './types';\nexport { DEFAULT_POLICY } from './defaults';\n\ntype EditorEvent = 'change' | 'paste' | 'overflow' | 'error';\ntype EventHandler = (...args: unknown[]) => void;\n\nconst SUPPORTED_COMMANDS = new Set([\n 'bold',\n 'italic',\n 'heading',\n 'blockquote',\n 'unorderedList',\n 'orderedList',\n 'link',\n 'unlink',\n 'codeBlock',\n]);\n\n/**\n * Create a contentEditable-based editor with built-in sanitization.\n *\n * The paste handler is the primary security boundary \u2014 it sanitizes HTML\n * before insertion via Selection/Range API. The MutationObserver-based\n * policy enforcer provides defense-in-depth.\n */\nexport function createEditor(\n element: HTMLElement,\n options?: EditorOptions,\n): Editor {\n if (!element) {\n throw new TypeError('createEditor requires an HTMLElement');\n }\n if (!element.ownerDocument || !element.parentNode) {\n throw new TypeError('createEditor requires an element attached to the DOM');\n }\n\n const src = options?.policy ?? DEFAULT_POLICY;\n const policy: SanitizePolicy = {\n tags: Object.fromEntries(\n Object.entries(src.tags).map(([k, v]) => [k, [...v]]),\n ),\n strip: src.strip,\n maxDepth: src.maxDepth,\n maxLength: src.maxLength,\n protocols: [...src.protocols],\n };\n\n const handlers: Record<string, EventHandler[]> = {};\n const doc = element.ownerDocument;\n\n function emit(event: EditorEvent, ...args: unknown[]): void {\n for (const handler of handlers[event] ?? []) {\n handler(...args);\n }\n }\n\n // Notify both subscription paths (editor.on('change') and options.onChange)\n // for programmatic edits that don't fire 'input' (codeBlock toggle, list unwrap).\n function emitChange(): void {\n const html = element.innerHTML;\n emit('change', html);\n options?.onChange?.(html);\n }\n\n // Set up contentEditable\n element.contentEditable = 'true';\n\n // Attach policy enforcer (MutationObserver defense-in-depth)\n const enforcer: PolicyEnforcer = createPolicyEnforcer(element, policy);\n enforcer.on('error', (err) => emit('error', err));\n\n // Paste handler \u2014 the primary security boundary\n function onPaste(e: ClipboardEvent): void {\n e.preventDefault();\n\n const clipboard = e.clipboardData;\n if (!clipboard) return;\n\n // Inside code block: paste as plain text only\n const sel = doc.getSelection();\n if (sel && sel.rangeCount > 0 && sel.anchorNode) {\n const pre = findAncestor(sel.anchorNode, 'PRE');\n if (pre) {\n const text = clipboard.getData('text/plain');\n if (!text) return;\n if (policy.maxLength > 0) {\n const currentLen = element.textContent?.length ?? 0;\n if (currentLen + text.length > policy.maxLength) {\n emit('overflow', policy.maxLength);\n }\n }\n const range = sel.getRangeAt(0);\n range.deleteContents();\n const textNode = doc.createTextNode(text);\n range.insertNode(textNode);\n range.setStartAfter(textNode);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emit('paste', element.innerHTML);\n emitChange();\n return;\n }\n }\n\n // Prefer HTML, fall back to plain text\n let html = clipboard.getData('text/html');\n if (!html) {\n const text = clipboard.getData('text/plain');\n if (!text) return;\n // Escape plain text and convert newlines to <br>\n html = text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n .replace(/\\n/g, '<br>');\n }\n\n // Sanitize through policy \u2014 returns DocumentFragment directly\n // to avoid the serialize\u2192reparse mXSS vector\n const fragment = sanitizeToFragment(html, policy);\n\n // Insert via Selection/Range API (NOT execCommand('insertHTML'))\n const selection = doc.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n\n const range = selection.getRangeAt(0);\n range.deleteContents();\n\n // Check overflow using text content length\n if (policy.maxLength > 0) {\n const pasteTextLen = fragment.textContent?.length ?? 0;\n const currentLen = element.textContent?.length ?? 0;\n if (currentLen + pasteTextLen > policy.maxLength) {\n emit('overflow', policy.maxLength);\n }\n }\n\n // Remember last inserted node for cursor positioning\n let lastNode: Node | null = fragment.lastChild;\n range.insertNode(fragment);\n\n // Move cursor after inserted content\n if (lastNode) {\n const newRange = doc.createRange();\n newRange.setStartAfter(lastNode);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n emit('paste', element.innerHTML);\n emitChange();\n }\n\n // Input handler for change events\n function onInput(): void {\n emitChange();\n }\n\n // Keydown handler for code block behavior\n function onKeydown(e: KeyboardEvent): void {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const anchor = sel.anchorNode;\n if (!anchor) return;\n\n const pre = findAncestor(anchor, 'PRE');\n\n if (e.key === 'Enter' && pre) {\n // Insert newline instead of new paragraph\n e.preventDefault();\n const range = sel.getRangeAt(0);\n range.deleteContents();\n const textNode = doc.createTextNode('\\n');\n range.insertNode(textNode);\n range.setStartAfter(textNode);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emitChange();\n }\n\n if (e.key === 'Backspace' && pre) {\n // At start of empty pre, convert to <p>\n const text = pre.textContent || '';\n const isAtStart = sel.anchorOffset === 0;\n const isEmpty = text === '' || text === '\\n';\n if (isAtStart && isEmpty) {\n e.preventDefault();\n const p = doc.createElement('p');\n p.appendChild(doc.createElement('br'));\n pre.parentNode?.replaceChild(p, pre);\n const range = doc.createRange();\n range.selectNodeContents(p);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emitChange();\n }\n }\n }\n\n element.addEventListener('keydown', onKeydown);\n element.addEventListener('paste', onPaste);\n element.addEventListener('input', onInput);\n\n function findAncestor(node: Node, tagName: string): Element | null {\n let current: Node | null = node;\n while (current && current !== element) {\n if (current.nodeType === 1 && (current as Element).tagName === tagName) return current as Element;\n current = current.parentNode;\n }\n return null;\n }\n\n /**\n * Manually unwrap a list the selection is inside of, replacing each\n * <li> with a <p>. Returns true if an unwrap happened.\n *\n * We do this instead of relying on execCommand toggle-off because\n * browsers unwrap list items into <div> wrappers, which the policy\n * enforcer then strips (losing content).\n */\n function unwrapList(tag: 'UL' | 'OL'): boolean {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return false;\n const anchor = sel.anchorNode;\n if (!anchor) return false;\n const list = findAncestor(anchor, tag);\n if (!list) return false;\n\n const anchorLi = findAncestor(anchor, 'LI');\n const parent = list.parentNode;\n if (!parent) return false;\n\n const paragraphs: HTMLParagraphElement[] = [];\n let focusTarget: HTMLParagraphElement | null = null;\n\n for (const child of Array.from(list.childNodes)) {\n if (child.nodeType !== 1 || (child as Element).tagName !== 'LI') continue;\n const p = doc.createElement('p');\n while (child.firstChild) p.appendChild(child.firstChild);\n if (!p.firstChild) p.appendChild(doc.createElement('br'));\n paragraphs.push(p);\n if (child === anchorLi) focusTarget = p;\n }\n\n for (const p of paragraphs) parent.insertBefore(p, list);\n parent.removeChild(list);\n\n const target = focusTarget ?? paragraphs[0];\n if (target) {\n const r = doc.createRange();\n r.selectNodeContents(target);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n emitChange();\n return true;\n }\n\n function hasAncestor(node: Node, tagName: string): boolean {\n let current: Node | null = node;\n while (current && current !== element) {\n if (current.nodeType === 1 && (current as Element).tagName === tagName) return true;\n current = current.parentNode;\n }\n return false;\n }\n\n const editor: Editor = {\n exec(command: string, value?: string): void {\n if (!SUPPORTED_COMMANDS.has(command)) {\n throw new Error(`Unknown editor command: \"${command}\"`);\n }\n\n element.focus();\n\n switch (command) {\n case 'bold':\n doc.execCommand('bold', false);\n break;\n case 'italic':\n doc.execCommand('italic', false);\n break;\n case 'heading': {\n const level = value ?? '1';\n if (!['1', '2', '3'].includes(level)) {\n throw new Error(`Invalid heading level: \"${level}\". Use 1, 2, or 3`);\n }\n const tag = `H${level}`;\n const anchor = doc.getSelection()?.anchorNode;\n if (anchor && editor.queryState('heading') && hasAncestor(anchor, tag)) {\n doc.execCommand('formatBlock', false, '<p>');\n } else {\n doc.execCommand('formatBlock', false, `<h${level}>`);\n }\n break;\n }\n case 'blockquote':\n if (editor.queryState('blockquote')) {\n doc.execCommand('formatBlock', false, '<p>');\n } else {\n doc.execCommand('formatBlock', false, '<blockquote>');\n }\n break;\n case 'unorderedList':\n if (!unwrapList('UL')) doc.execCommand('insertUnorderedList', false);\n break;\n case 'orderedList':\n if (!unwrapList('OL')) doc.execCommand('insertOrderedList', false);\n break;\n case 'link': {\n if (!value) {\n throw new Error('Link command requires a URL value');\n }\n const trimmed = value.trim();\n if (!isProtocolAllowed(trimmed, policy.protocols)) {\n emit('error', new Error(`Protocol not allowed: ${trimmed}`));\n return;\n }\n doc.execCommand('createLink', false, trimmed);\n break;\n }\n case 'unlink':\n doc.execCommand('unlink', false);\n break;\n case 'codeBlock': {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) break;\n const anchor = sel.anchorNode;\n const pre = anchor ? findAncestor(anchor, 'PRE') : null;\n if (pre) {\n // Toggle off: unwrap <pre><code> to <p>\n const p = doc.createElement('p');\n p.textContent = pre.textContent || '';\n pre.parentNode?.replaceChild(p, pre);\n const r = doc.createRange();\n r.selectNodeContents(p);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n } else {\n // Wrap current block in <pre><code>\n const range = sel.getRangeAt(0);\n let block = range.startContainer;\n while (block.parentNode && block.parentNode !== element) {\n block = block.parentNode;\n }\n const pre2 = doc.createElement('pre');\n const code = doc.createElement('code');\n const blockText = block.textContent || '';\n code.textContent = blockText.endsWith('\\n') ? blockText : blockText + '\\n';\n pre2.appendChild(code);\n if (block.parentNode === element) {\n element.replaceChild(pre2, block);\n } else {\n element.appendChild(pre2);\n }\n const r = doc.createRange();\n r.selectNodeContents(code);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n emitChange();\n break;\n }\n }\n },\n\n queryState(command: string): boolean {\n if (!SUPPORTED_COMMANDS.has(command)) {\n throw new Error(`Unknown editor command: \"${command}\"`);\n }\n\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return false;\n\n const node = sel.anchorNode;\n if (!node || !element.contains(node)) return false;\n\n switch (command) {\n case 'bold':\n return hasAncestor(node, 'STRONG') || hasAncestor(node, 'B');\n case 'italic':\n return hasAncestor(node, 'EM') || hasAncestor(node, 'I');\n case 'heading':\n return hasAncestor(node, 'H1') || hasAncestor(node, 'H2') || hasAncestor(node, 'H3');\n case 'blockquote':\n return hasAncestor(node, 'BLOCKQUOTE');\n case 'unorderedList':\n return hasAncestor(node, 'UL');\n case 'orderedList':\n return hasAncestor(node, 'OL');\n case 'link':\n return hasAncestor(node, 'A');\n case 'unlink':\n return false;\n case 'codeBlock':\n return hasAncestor(node, 'PRE');\n default:\n return false;\n }\n },\n\n getHTML(): string {\n return element.innerHTML;\n },\n\n getText(): string {\n return element.textContent ?? '';\n },\n\n destroy(): void {\n element.removeEventListener('keydown', onKeydown);\n element.removeEventListener('paste', onPaste);\n element.removeEventListener('input', onInput);\n enforcer.destroy();\n element.contentEditable = 'false';\n },\n\n on(event: string, handler: EventHandler): void {\n if (!handlers[event]) handlers[event] = [];\n handlers[event].push(handler);\n },\n };\n\n return editor;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["react_exports", "__export", "Minisiwyg", "__toCommonJS", "import_react", "policy", "attrs", "DEFAULT_POLICY", "TAG_NORMALIZE", "URL_ATTRS", "DENIED_PROTOCOLS", "extractProtocol", "value", "decoded", "_", "hex", "dec", "match", "isProtocolAllowed", "allowedProtocols", "protocol", "walkAndSanitize", "parent", "policy", "depth", "children", "node", "el", "tagName", "normalized", "TAG_NORMALIZE", "allowedAttrs", "current", "replacement", "attrs", "attr", "attrName", "URL_ATTRS", "isProtocolAllowed", "sanitizeToFragment", "html", "template", "fragment", "truncateToLength", "truncateToLength", "node", "maxLength", "remaining", "children", "child", "text", "getDepth", "node", "root", "depth", "current", "enforceElement", "el", "policy", "tagName", "normalized", "TAG_NORMALIZE", "allowedAttrs", "parent", "replacement", "attr", "attrName", "URL_ATTRS", "isProtocolAllowed", "enforceSubtree", "children", "child", "createPolicyEnforcer", "element", "isApplyingFix", "errorHandlers", "emitError", "error", "handler", "observer", "mutations", "mutation", "target", "normalizedTag", "lowerAttr", "value", "err", "event", "SUPPORTED_COMMANDS", "createEditor", "element", "options", "src", "DEFAULT_POLICY", "policy", "k", "v", "handlers", "doc", "emit", "event", "args", "handler", "emitChange", "html", "enforcer", "createPolicyEnforcer", "err", "onPaste", "e", "clipboard", "sel", "findAncestor", "text", "range", "textNode", "fragment", "sanitizeToFragment", "selection", "pasteTextLen", "lastNode", "newRange", "onInput", "onKeydown", "anchor", "pre", "p", "node", "tagName", "current", "unwrapList", "tag", "list", "anchorLi", "parent", "paragraphs", "focusTarget", "child", "target", "r", "hasAncestor", "editor", "command", "value", "level", "trimmed", "isProtocolAllowed", "block", "pre2", "code", "blockText", "import_jsx_runtime", "Minisiwyg", "props", "initialHTML", "value", "onChange", "policy", "className", "editorRef", "hostRef", "editorInstance", "onChangeRef", "policyRef", "DEFAULT_POLICY", "el", "effectivePolicy", "sanitizeToFragment", "editor", "createEditor", "html"]
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { Editor, SanitizePolicy } from '../types';
2
+ export type { Editor, SanitizePolicy } from '../types';
3
+ export interface MinisiwygProps {
4
+ initialHTML?: string;
5
+ value?: string;
6
+ onChange?: (html: string) => void;
7
+ policy?: SanitizePolicy;
8
+ className?: string;
9
+ editorRef?: (editor: Editor | null) => void;
10
+ }
11
+ export declare function Minisiwyg(props: MinisiwygProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import{useEffect as M,useRef as O}from"react";var P={tags:{p:[],br:[],strong:[],em:[],a:["href","title","target"],h1:[],h2:[],h3:[],ul:[],ol:[],li:[],blockquote:[],pre:[],code:[]},strip:!0,maxDepth:10,maxLength:1e5,protocols:["https","http","mailto"]};Object.freeze(P);Object.freeze(P.protocols);for(let e of Object.values(P.tags))Object.freeze(e);Object.freeze(P.tags);var A=P;var R={b:"strong",i:"em"},S=new Set(["href","src","action","formaction"]),j=new Set(["javascript","data"]);function Y(e){let n=e.trim();n=n.replace(/&#x([0-9a-f]+);?/gi,(i,a)=>String.fromCharCode(parseInt(a,16))),n=n.replace(/&#(\d+);?/g,(i,a)=>String.fromCharCode(parseInt(a,10)));try{n=decodeURIComponent(n)}catch{}n=n.replace(/[\s\x00-\x1f\u00A0\u1680\u2000-\u200B\u2028\u2029\u202F\u205F\u3000\uFEFF]+/g,"");let o=n.match(/^([a-z][a-z0-9+\-.]*)\s*:/i);return o?o[1].toLowerCase():null}function T(e,n){let o=Y(e);return o===null?!0:j.has(o)?!1:n.includes(o)}function D(e,n,o){let i=Array.from(e.childNodes);for(let a of i){if(a.nodeType===3)continue;if(a.nodeType!==1){e.removeChild(a);continue}let r=a,u=r.tagName.toLowerCase(),l=R[u];if(l&&(u=l),o>=n.maxDepth){e.removeChild(r);continue}let s=n.tags[u];if(s===void 0){if(n.strip)e.removeChild(r);else{for(D(r,n,o);r.firstChild;)e.insertBefore(r.firstChild,r);e.removeChild(r)}continue}let f=r;if(l&&r.tagName.toLowerCase()!==l){let p=r.ownerDocument.createElement(l);for(;r.firstChild;)p.appendChild(r.firstChild);e.replaceChild(p,r),f=p}let b=Array.from(f.attributes);for(let L of b){let p=L.name.toLowerCase();if(p.startsWith("on")){f.removeAttribute(L.name);continue}if(!s.includes(p)){f.removeAttribute(L.name);continue}S.has(p)&&(T(L.value,n.protocols)||f.removeAttribute(L.name))}D(f,n,o+1)}}function k(e,n){let o=document.createElement("template");if(!e)return o.content;o.innerHTML=e;let i=o.content;return D(i,n,0),n.maxLength>0&&(i.textContent?.length??0)>n.maxLength&&U(i,n.maxLength),i}function U(e,n){let o=n,i=Array.from(e.childNodes);for(let a of i){if(o<=0){e.removeChild(a);continue}if(a.nodeType===3){let r=a.textContent??"";r.length>o?(a.textContent=r.slice(0,o),o=0):o-=r.length}else a.nodeType===1?o=U(a,o):e.removeChild(a)}return o}function $(e,n){let o=0,i=e.parentNode;for(;i&&i!==n;)i.nodeType===1&&o++,i=i.parentNode;return o}function I(e,n,o){let i=e.tagName.toLowerCase(),a=R[i];if(a&&(i=a),$(e,o)>=n.maxDepth)return e.parentNode?.removeChild(e),!0;let u=n.tags[i];if(u===void 0){if(n.strip)e.parentNode?.removeChild(e);else{let s=e.parentNode;if(s){for(;e.firstChild;)s.insertBefore(e.firstChild,e);s.removeChild(e)}}return!0}let l=e;if(a&&e.tagName.toLowerCase()!==a){let s=e.ownerDocument.createElement(a);for(;e.firstChild;)s.appendChild(e.firstChild);for(let f of Array.from(e.attributes))s.setAttribute(f.name,f.value);e.parentNode?.replaceChild(s,e),l=s}for(let s of Array.from(l.attributes)){let f=s.name.toLowerCase();if(f.startsWith("on")){l.removeAttribute(s.name);continue}if(!u.includes(f)){l.removeAttribute(s.name);continue}S.has(f)&&(T(s.value,n.protocols)||l.removeAttribute(s.name))}return!1}function _(e,n,o){let i=Array.from(e.childNodes);for(let a of i){if(a.nodeType!==1){a.nodeType!==3&&e.removeChild(a);continue}I(a,n,o)||_(a,n,o)}}function F(e,n){if(!n||!n.tags)throw new TypeError('Policy must have a "tags" property');let o=!1,i=[];function a(u){for(let l of i)l(u)}let r=new MutationObserver(u=>{if(!o){o=!0;try{for(let l of u)if(l.type==="childList")for(let s of Array.from(l.addedNodes)){if(s.nodeType===3)continue;if(s.nodeType!==1){s.parentNode?.removeChild(s);continue}I(s,n,e)||_(s,n,e)}else if(l.type==="attributes"){let s=l.target;if(s.nodeType!==1)continue;let f=l.attributeName;if(!f)continue;let b=s.tagName.toLowerCase(),L=R[b]||b,p=n.tags[L];if(!p)continue;let w=f.toLowerCase();if(w.startsWith("on")){s.removeAttribute(f);continue}if(!p.includes(w)){s.removeAttribute(f);continue}if(S.has(w)){let y=s.getAttribute(f);y&&!T(y,n.protocols)&&s.removeAttribute(f)}}}catch(l){a(l instanceof Error?l:new Error(String(l)))}finally{o=!1}}});return r.observe(e,{childList:!0,attributes:!0,subtree:!0}),{destroy(){r.disconnect()},on(u,l){u==="error"&&i.push(l)}}}var B=new Set(["bold","italic","heading","blockquote","unorderedList","orderedList","link","unlink","codeBlock"]);function q(e,n){if(!e)throw new TypeError("createEditor requires an HTMLElement");if(!e.ownerDocument||!e.parentNode)throw new TypeError("createEditor requires an element attached to the DOM");let o=n?.policy??A,i={tags:Object.fromEntries(Object.entries(o.tags).map(([d,c])=>[d,[...c]])),strip:o.strip,maxDepth:o.maxDepth,maxLength:o.maxLength,protocols:[...o.protocols]},a={},r=e.ownerDocument;function u(d,...c){for(let t of a[d]??[])t(...c)}function l(){let d=e.innerHTML;u("change",d),n?.onChange?.(d)}e.contentEditable="true";let s=F(e,i);s.on("error",d=>u("error",d));function f(d){d.preventDefault();let c=d.clipboardData;if(!c)return;let t=r.getSelection();if(t&&t.rangeCount>0&&t.anchorNode&&p(t.anchorNode,"PRE")){let E=c.getData("text/plain");if(!E)return;i.maxLength>0&&(e.textContent?.length??0)+E.length>i.maxLength&&u("overflow",i.maxLength);let x=t.getRangeAt(0);x.deleteContents();let H=r.createTextNode(E);x.insertNode(H),x.setStartAfter(H),x.collapse(!0),t.removeAllRanges(),t.addRange(x),u("paste",e.innerHTML),l();return}let g=c.getData("text/html");if(!g){let h=c.getData("text/plain");if(!h)return;g=h.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/\n/g,"<br>")}let m=k(g,i),v=r.getSelection();if(!v||v.rangeCount===0)return;let C=v.getRangeAt(0);if(C.deleteContents(),i.maxLength>0){let h=m.textContent?.length??0;(e.textContent?.length??0)+h>i.maxLength&&u("overflow",i.maxLength)}let N=m.lastChild;if(C.insertNode(m),N){let h=r.createRange();h.setStartAfter(N),h.collapse(!0),v.removeAllRanges(),v.addRange(h)}u("paste",e.innerHTML),l()}function b(){l()}function L(d){let c=r.getSelection();if(!c||c.rangeCount===0)return;let t=c.anchorNode;if(!t)return;let g=p(t,"PRE");if(d.key==="Enter"&&g){d.preventDefault();let m=c.getRangeAt(0);m.deleteContents();let v=r.createTextNode(`
2
+ `);m.insertNode(v),m.setStartAfter(v),m.collapse(!0),c.removeAllRanges(),c.addRange(m),l()}if(d.key==="Backspace"&&g){let m=g.textContent||"";if(c.anchorOffset===0&&(m===""||m===`
3
+ `)){d.preventDefault();let N=r.createElement("p");N.appendChild(r.createElement("br")),g.parentNode?.replaceChild(N,g);let h=r.createRange();h.selectNodeContents(N),h.collapse(!0),c.removeAllRanges(),c.addRange(h),l()}}}e.addEventListener("keydown",L),e.addEventListener("paste",f),e.addEventListener("input",b);function p(d,c){let t=d;for(;t&&t!==e;){if(t.nodeType===1&&t.tagName===c)return t;t=t.parentNode}return null}function w(d){let c=r.getSelection();if(!c||c.rangeCount===0)return!1;let t=c.anchorNode;if(!t)return!1;let g=p(t,d);if(!g)return!1;let m=p(t,"LI"),v=g.parentNode;if(!v)return!1;let C=[],N=null;for(let E of Array.from(g.childNodes)){if(E.nodeType!==1||E.tagName!=="LI")continue;let x=r.createElement("p");for(;E.firstChild;)x.appendChild(E.firstChild);x.firstChild||x.appendChild(r.createElement("br")),C.push(x),E===m&&(N=x)}for(let E of C)v.insertBefore(E,g);v.removeChild(g);let h=N??C[0];if(h){let E=r.createRange();E.selectNodeContents(h),E.collapse(!1),c.removeAllRanges(),c.addRange(E)}return l(),!0}function y(d,c){let t=d;for(;t&&t!==e;){if(t.nodeType===1&&t.tagName===c)return!0;t=t.parentNode}return!1}let z={exec(d,c){if(!B.has(d))throw new Error(`Unknown editor command: "${d}"`);switch(e.focus(),d){case"bold":r.execCommand("bold",!1);break;case"italic":r.execCommand("italic",!1);break;case"heading":{let t=c??"1";if(!["1","2","3"].includes(t))throw new Error(`Invalid heading level: "${t}". Use 1, 2, or 3`);let g=`H${t}`,m=r.getSelection()?.anchorNode;m&&z.queryState("heading")&&y(m,g)?r.execCommand("formatBlock",!1,"<p>"):r.execCommand("formatBlock",!1,`<h${t}>`);break}case"blockquote":z.queryState("blockquote")?r.execCommand("formatBlock",!1,"<p>"):r.execCommand("formatBlock",!1,"<blockquote>");break;case"unorderedList":w("UL")||r.execCommand("insertUnorderedList",!1);break;case"orderedList":w("OL")||r.execCommand("insertOrderedList",!1);break;case"link":{if(!c)throw new Error("Link command requires a URL value");let t=c.trim();if(!T(t,i.protocols)){u("error",new Error(`Protocol not allowed: ${t}`));return}r.execCommand("createLink",!1,t);break}case"unlink":r.execCommand("unlink",!1);break;case"codeBlock":{let t=r.getSelection();if(!t||t.rangeCount===0)break;let g=t.anchorNode,m=g?p(g,"PRE"):null;if(m){let v=r.createElement("p");v.textContent=m.textContent||"",m.parentNode?.replaceChild(v,m);let C=r.createRange();C.selectNodeContents(v),C.collapse(!1),t.removeAllRanges(),t.addRange(C)}else{let C=t.getRangeAt(0).startContainer;for(;C.parentNode&&C.parentNode!==e;)C=C.parentNode;let N=r.createElement("pre"),h=r.createElement("code"),E=C.textContent||"";h.textContent=E.endsWith(`
4
+ `)?E:E+`
5
+ `,N.appendChild(h),C.parentNode===e?e.replaceChild(N,C):e.appendChild(N);let x=r.createRange();x.selectNodeContents(h),x.collapse(!1),t.removeAllRanges(),t.addRange(x)}l();break}}},queryState(d){if(!B.has(d))throw new Error(`Unknown editor command: "${d}"`);let c=r.getSelection();if(!c||c.rangeCount===0)return!1;let t=c.anchorNode;if(!t||!e.contains(t))return!1;switch(d){case"bold":return y(t,"STRONG")||y(t,"B");case"italic":return y(t,"EM")||y(t,"I");case"heading":return y(t,"H1")||y(t,"H2")||y(t,"H3");case"blockquote":return y(t,"BLOCKQUOTE");case"unorderedList":return y(t,"UL");case"orderedList":return y(t,"OL");case"link":return y(t,"A");case"unlink":return!1;case"codeBlock":return y(t,"PRE");default:return!1}},getHTML(){return e.innerHTML},getText(){return e.textContent??""},destroy(){e.removeEventListener("keydown",L),e.removeEventListener("paste",f),e.removeEventListener("input",b),s.destroy(),e.contentEditable="false"},on(d,c){a[d]||(a[d]=[]),a[d].push(c)}};return z}import{jsx as G}from"react/jsx-runtime";function ue(e){let{initialHTML:n,value:o,onChange:i,policy:a,className:r,editorRef:u}=e,l=O(null),s=O(null),f=O(i),b=O(a??A);return M(()=>{f.current=i},[i]),M(()=>{let L=l.current;if(!L)return;let p=a??A;b.current=p,L.replaceChildren(k(o??n??"",p));let w=q(L,{policy:p,onChange:y=>f.current?.(y)});return s.current=w,u?.(w),()=>{w.destroy(),s.current=null,u?.(null)}},[]),M(()=>{let L=s.current,p=l.current;!L||!p||o===void 0||L.getHTML()!==o&&p.replaceChildren(k(o,b.current))},[o]),G("div",{ref:l,className:r})}export{ue as Minisiwyg};
6
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/adapters/react.tsx", "../../src/defaults.ts", "../../src/shared.ts", "../../src/sanitize.ts", "../../src/policy.ts", "../../src/editor.ts"],
4
+ "sourcesContent": ["import { useEffect, useRef } from 'react';\nimport { createEditor } from '../editor';\nimport { sanitizeToFragment } from '../sanitize';\nimport { DEFAULT_POLICY } from '../defaults';\nimport type { Editor, SanitizePolicy } from '../types';\n\nexport type { Editor, SanitizePolicy } from '../types';\n\nexport interface MinisiwygProps {\n initialHTML?: string;\n value?: string;\n onChange?: (html: string) => void;\n policy?: SanitizePolicy;\n className?: string;\n editorRef?: (editor: Editor | null) => void;\n}\n\nexport function Minisiwyg(props: MinisiwygProps) {\n const { initialHTML, value, onChange, policy, className, editorRef } = props;\n const hostRef = useRef<HTMLDivElement | null>(null);\n const editorInstance = useRef<Editor | null>(null);\n const onChangeRef = useRef<typeof onChange>(onChange);\n const policyRef = useRef<SanitizePolicy>(policy ?? DEFAULT_POLICY);\n\n useEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n useEffect(() => {\n const el = hostRef.current;\n if (!el) return;\n const effectivePolicy = policy ?? DEFAULT_POLICY;\n policyRef.current = effectivePolicy;\n el.replaceChildren(sanitizeToFragment(value ?? initialHTML ?? '', effectivePolicy));\n const editor = createEditor(el, {\n policy: effectivePolicy,\n onChange: (html) => onChangeRef.current?.(html),\n });\n editorInstance.current = editor;\n editorRef?.(editor);\n return () => {\n editor.destroy();\n editorInstance.current = null;\n editorRef?.(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n useEffect(() => {\n const editor = editorInstance.current;\n const el = hostRef.current;\n if (!editor || !el || value === undefined) return;\n if (editor.getHTML() !== value) {\n el.replaceChildren(sanitizeToFragment(value, policyRef.current));\n }\n }, [value]);\n\n return <div ref={hostRef} className={className} />;\n}\n", "import type { SanitizePolicy } from './types';\n\nconst policy: SanitizePolicy = {\n tags: {\n p: [],\n br: [],\n strong: [],\n em: [],\n a: ['href', 'title', 'target'],\n h1: [],\n h2: [],\n h3: [],\n ul: [],\n ol: [],\n li: [],\n blockquote: [],\n pre: [],\n code: [],\n },\n strip: true,\n maxDepth: 10,\n maxLength: 100_000,\n protocols: ['https', 'http', 'mailto'],\n};\n\n// Deep freeze to prevent mutation of security-critical defaults\nObject.freeze(policy);\nObject.freeze(policy.protocols);\nfor (const attrs of Object.values(policy.tags)) Object.freeze(attrs);\nObject.freeze(policy.tags);\n\nexport const DEFAULT_POLICY: Readonly<SanitizePolicy> = policy;\n", "/** Tag normalization map: browser-variant tags \u2192 semantic equivalents. */\nexport const TAG_NORMALIZE: Record<string, string> = {\n b: 'strong',\n i: 'em',\n};\n\n/** Attributes that contain URLs and need protocol validation. */\nexport const URL_ATTRS = new Set(['href', 'src', 'action', 'formaction']);\n\n/** Protocols that are always denied regardless of policy. */\nexport const DENIED_PROTOCOLS = new Set(['javascript', 'data']);\n\n/**\n * Parse a URL-like string and extract the protocol.\n * Returns the lowercase protocol name (without colon), or null if none found.\n */\nexport function extractProtocol(value: string): string | null {\n let decoded = value.trim();\n decoded = decoded.replace(/&#x([0-9a-f]+);?/gi, (_, hex) =>\n String.fromCharCode(parseInt(hex, 16)),\n );\n decoded = decoded.replace(/&#(\\d+);?/g, (_, dec) =>\n String.fromCharCode(parseInt(dec, 10)),\n );\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n // keep entity-decoded result\n }\n decoded = decoded.replace(/[\\s\\x00-\\x1f\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]+/g, '');\n const match = decoded.match(/^([a-z][a-z0-9+\\-.]*)\\s*:/i);\n return match ? match[1].toLowerCase() : null;\n}\n\n/**\n * Check if a URL value is allowed by the given protocol list.\n * javascript: and data: are always denied.\n */\nexport function isProtocolAllowed(value: string, allowedProtocols: string[]): boolean {\n const protocol = extractProtocol(value);\n if (protocol === null) return true;\n if (DENIED_PROTOCOLS.has(protocol)) return false;\n return allowedProtocols.includes(protocol);\n}\n", "import type { SanitizePolicy } from './types';\nimport { TAG_NORMALIZE, URL_ATTRS, isProtocolAllowed } from './shared';\nexport { DEFAULT_POLICY } from './defaults';\nexport type { SanitizePolicy } from './types';\n\n/**\n * Walk a DOM tree depth-first and sanitize according to policy.\n * Mutates the tree in place.\n */\nfunction walkAndSanitize(\n parent: Node,\n policy: SanitizePolicy,\n depth: number,\n): void {\n const children = Array.from(parent.childNodes);\n\n for (const node of children) {\n // Text nodes: always allowed (length enforcement happens after walk)\n if (node.nodeType === 3) continue;\n\n // Non-element, non-text nodes (comments, processing instructions): remove\n if (node.nodeType !== 1) {\n parent.removeChild(node);\n continue;\n }\n\n const el = node as Element;\n let tagName = el.tagName.toLowerCase();\n\n // Normalize tags (b\u2192strong, i\u2192em)\n const normalized = TAG_NORMALIZE[tagName];\n if (normalized) {\n tagName = normalized;\n }\n\n // Check depth limit\n if (depth >= policy.maxDepth) {\n parent.removeChild(el);\n continue;\n }\n\n // Check tag whitelist\n const allowedAttrs = policy.tags[tagName];\n if (allowedAttrs === undefined) {\n // Tag not allowed\n if (policy.strip) {\n // Remove the node and all its children\n parent.removeChild(el);\n } else {\n // Unwrap: sanitize children first, then move them up\n walkAndSanitize(el, policy, depth);\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n continue;\n }\n\n // Tag is allowed. If it was normalized, replace with correct element.\n let current: Element = el;\n if (normalized && el.tagName.toLowerCase() !== normalized) {\n const doc = el.ownerDocument!;\n const replacement = doc.createElement(normalized);\n while (el.firstChild) {\n replacement.appendChild(el.firstChild);\n }\n parent.replaceChild(replacement, el);\n current = replacement;\n }\n\n // Strip disallowed attributes\n const attrs = Array.from(current.attributes);\n for (const attr of attrs) {\n const attrName = attr.name.toLowerCase();\n\n // Always strip event handlers (on*)\n if (attrName.startsWith('on')) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n // Check attribute whitelist\n if (!allowedAttrs.includes(attrName)) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n // Validate URL protocols on URL-bearing attributes\n if (URL_ATTRS.has(attrName)) {\n if (!isProtocolAllowed(attr.value, policy.protocols)) {\n current.removeAttribute(attr.name);\n }\n }\n }\n\n // Recurse into children\n walkAndSanitize(current, policy, depth + 1);\n }\n}\n\n/**\n * Sanitize an HTML string and return a DocumentFragment.\n * Avoids the serialize\u2192reparse round-trip that can cause mXSS.\n */\nexport function sanitizeToFragment(html: string, policy: SanitizePolicy): DocumentFragment {\n const template = document.createElement('template');\n if (!html) return template.content;\n\n template.innerHTML = html;\n const fragment = template.content;\n\n walkAndSanitize(fragment, policy, 0);\n\n if (policy.maxLength > 0 && (fragment.textContent?.length ?? 0) > policy.maxLength) {\n truncateToLength(fragment, policy.maxLength);\n }\n\n return fragment;\n}\n\n/**\n * Sanitize an HTML string according to the given policy.\n *\n * Uses a <template> element to parse HTML without executing scripts.\n * Walks the resulting DOM tree depth-first, removing disallowed elements\n * and attributes. Returns the sanitized HTML string.\n */\nexport function sanitize(html: string, policy: SanitizePolicy): string {\n if (!html) return '';\n\n const fragment = sanitizeToFragment(html, policy);\n const container = document.createElement('div');\n container.appendChild(fragment);\n return container.innerHTML;\n}\n\n/**\n * Truncate a DOM tree's text content to a maximum length.\n * Removes nodes beyond the limit while preserving structure.\n */\nfunction truncateToLength(node: Node, maxLength: number): number {\n let remaining = maxLength;\n\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (remaining <= 0) {\n node.removeChild(child);\n continue;\n }\n\n if (child.nodeType === 3) {\n // Text node\n const text = child.textContent ?? '';\n if (text.length > remaining) {\n child.textContent = text.slice(0, remaining);\n remaining = 0;\n } else {\n remaining -= text.length;\n }\n } else if (child.nodeType === 1) {\n remaining = truncateToLength(child, remaining);\n } else {\n node.removeChild(child);\n }\n }\n\n return remaining;\n}\n", "import type { SanitizePolicy } from './types';\nimport { TAG_NORMALIZE, URL_ATTRS, isProtocolAllowed } from './shared';\n\nexport { DEFAULT_POLICY } from './defaults';\nexport type { SanitizePolicy } from './types';\n\nexport interface PolicyEnforcer {\n destroy(): void;\n on(event: 'error', handler: (error: Error) => void): void;\n}\n\n/**\n * Get the nesting depth of a node within a root element.\n */\nfunction getDepth(node: Node, root: Node): number {\n let depth = 0;\n let current = node.parentNode;\n while (current && current !== root) {\n if (current.nodeType === 1) depth++;\n current = current.parentNode;\n }\n return depth;\n}\n\n/**\n * Check if an element is allowed by the policy and fix it if not.\n * Returns true if the node was removed/replaced.\n */\nfunction enforceElement(\n el: Element,\n policy: SanitizePolicy,\n root: HTMLElement,\n): boolean {\n let tagName = el.tagName.toLowerCase();\n const normalized = TAG_NORMALIZE[tagName];\n if (normalized) tagName = normalized;\n\n // Check depth\n const depth = getDepth(el, root);\n if (depth >= policy.maxDepth) {\n el.parentNode?.removeChild(el);\n return true;\n }\n\n // Check tag whitelist\n const allowedAttrs = policy.tags[tagName];\n if (allowedAttrs === undefined) {\n if (policy.strip) {\n el.parentNode?.removeChild(el);\n } else {\n // Unwrap: move children up, then remove the element\n const parent = el.parentNode;\n if (parent) {\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n }\n return true;\n }\n\n // Normalize tag if needed (e.g. <b> \u2192 <strong>)\n let current: Element = el;\n if (normalized && el.tagName.toLowerCase() !== normalized) {\n const replacement = el.ownerDocument.createElement(normalized);\n while (el.firstChild) {\n replacement.appendChild(el.firstChild);\n }\n // Copy allowed attributes\n for (const attr of Array.from(el.attributes)) {\n replacement.setAttribute(attr.name, attr.value);\n }\n el.parentNode?.replaceChild(replacement, el);\n current = replacement;\n }\n\n // Strip disallowed attributes\n for (const attr of Array.from(current.attributes)) {\n const attrName = attr.name.toLowerCase();\n\n if (attrName.startsWith('on')) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n if (!allowedAttrs.includes(attrName)) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n if (URL_ATTRS.has(attrName)) {\n if (!isProtocolAllowed(attr.value, policy.protocols)) {\n current.removeAttribute(attr.name);\n }\n }\n }\n\n return false;\n}\n\n/**\n * Recursively enforce policy on all descendants of a node.\n */\nfunction enforceSubtree(node: Node, policy: SanitizePolicy, root: HTMLElement): void {\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (child.nodeType !== 1) {\n // Remove non-text, non-element nodes (comments, etc.)\n if (child.nodeType !== 3) {\n node.removeChild(child);\n }\n continue;\n }\n const removed = enforceElement(child as Element, policy, root);\n if (!removed) {\n enforceSubtree(child, policy, root);\n }\n }\n}\n\n/**\n * Create a policy enforcer that uses MutationObserver to enforce\n * the sanitization policy on a live DOM element.\n *\n * This is defense-in-depth \u2014 the paste handler is the primary security boundary.\n * The observer catches mutations from execCommand, programmatic DOM manipulation,\n * and other sources.\n */\nexport function createPolicyEnforcer(\n element: HTMLElement,\n policy: SanitizePolicy,\n): PolicyEnforcer {\n if (!policy || !policy.tags) {\n throw new TypeError('Policy must have a \"tags\" property');\n }\n\n let isApplyingFix = false;\n const errorHandlers: Array<(error: Error) => void> = [];\n\n function emitError(error: Error): void {\n for (const handler of errorHandlers) {\n handler(error);\n }\n }\n\n const observer = new MutationObserver((mutations) => {\n if (isApplyingFix) return;\n isApplyingFix = true;\n\n try {\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const node of Array.from(mutation.addedNodes)) {\n // Skip text nodes\n if (node.nodeType === 3) continue;\n\n // Remove non-element nodes\n if (node.nodeType !== 1) {\n node.parentNode?.removeChild(node);\n continue;\n }\n\n const removed = enforceElement(node as Element, policy, element);\n if (!removed) {\n // Also enforce on all descendants of the added node\n enforceSubtree(node, policy, element);\n }\n }\n } else if (mutation.type === 'attributes') {\n const target = mutation.target as Element;\n if (target.nodeType !== 1) continue;\n\n const attrName = mutation.attributeName;\n if (!attrName) continue;\n\n const tagName = target.tagName.toLowerCase();\n const normalizedTag = TAG_NORMALIZE[tagName] || tagName;\n const allowedAttrs = policy.tags[normalizedTag];\n\n if (!allowedAttrs) continue;\n\n const lowerAttr = attrName.toLowerCase();\n\n if (lowerAttr.startsWith('on')) {\n target.removeAttribute(attrName);\n continue;\n }\n\n if (!allowedAttrs.includes(lowerAttr)) {\n target.removeAttribute(attrName);\n continue;\n }\n\n if (URL_ATTRS.has(lowerAttr)) {\n const value = target.getAttribute(attrName);\n if (value && !isProtocolAllowed(value, policy.protocols)) {\n target.removeAttribute(attrName);\n }\n }\n }\n }\n } catch (err) {\n emitError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isApplyingFix = false;\n }\n });\n\n observer.observe(element, {\n childList: true,\n attributes: true,\n subtree: true,\n });\n\n return {\n destroy() {\n observer.disconnect();\n },\n on(event: 'error', handler: (error: Error) => void) {\n if (event === 'error') {\n errorHandlers.push(handler);\n }\n },\n };\n}\n", "import type { SanitizePolicy, EditorOptions, Editor } from './types';\nimport { DEFAULT_POLICY } from './defaults';\nimport { sanitizeToFragment } from './sanitize';\nimport { createPolicyEnforcer, type PolicyEnforcer } from './policy';\nimport { isProtocolAllowed } from './shared';\n\nexport type { Editor, EditorOptions } from './types';\nexport { DEFAULT_POLICY } from './defaults';\n\ntype EditorEvent = 'change' | 'paste' | 'overflow' | 'error';\ntype EventHandler = (...args: unknown[]) => void;\n\nconst SUPPORTED_COMMANDS = new Set([\n 'bold',\n 'italic',\n 'heading',\n 'blockquote',\n 'unorderedList',\n 'orderedList',\n 'link',\n 'unlink',\n 'codeBlock',\n]);\n\n/**\n * Create a contentEditable-based editor with built-in sanitization.\n *\n * The paste handler is the primary security boundary \u2014 it sanitizes HTML\n * before insertion via Selection/Range API. The MutationObserver-based\n * policy enforcer provides defense-in-depth.\n */\nexport function createEditor(\n element: HTMLElement,\n options?: EditorOptions,\n): Editor {\n if (!element) {\n throw new TypeError('createEditor requires an HTMLElement');\n }\n if (!element.ownerDocument || !element.parentNode) {\n throw new TypeError('createEditor requires an element attached to the DOM');\n }\n\n const src = options?.policy ?? DEFAULT_POLICY;\n const policy: SanitizePolicy = {\n tags: Object.fromEntries(\n Object.entries(src.tags).map(([k, v]) => [k, [...v]]),\n ),\n strip: src.strip,\n maxDepth: src.maxDepth,\n maxLength: src.maxLength,\n protocols: [...src.protocols],\n };\n\n const handlers: Record<string, EventHandler[]> = {};\n const doc = element.ownerDocument;\n\n function emit(event: EditorEvent, ...args: unknown[]): void {\n for (const handler of handlers[event] ?? []) {\n handler(...args);\n }\n }\n\n // Notify both subscription paths (editor.on('change') and options.onChange)\n // for programmatic edits that don't fire 'input' (codeBlock toggle, list unwrap).\n function emitChange(): void {\n const html = element.innerHTML;\n emit('change', html);\n options?.onChange?.(html);\n }\n\n // Set up contentEditable\n element.contentEditable = 'true';\n\n // Attach policy enforcer (MutationObserver defense-in-depth)\n const enforcer: PolicyEnforcer = createPolicyEnforcer(element, policy);\n enforcer.on('error', (err) => emit('error', err));\n\n // Paste handler \u2014 the primary security boundary\n function onPaste(e: ClipboardEvent): void {\n e.preventDefault();\n\n const clipboard = e.clipboardData;\n if (!clipboard) return;\n\n // Inside code block: paste as plain text only\n const sel = doc.getSelection();\n if (sel && sel.rangeCount > 0 && sel.anchorNode) {\n const pre = findAncestor(sel.anchorNode, 'PRE');\n if (pre) {\n const text = clipboard.getData('text/plain');\n if (!text) return;\n if (policy.maxLength > 0) {\n const currentLen = element.textContent?.length ?? 0;\n if (currentLen + text.length > policy.maxLength) {\n emit('overflow', policy.maxLength);\n }\n }\n const range = sel.getRangeAt(0);\n range.deleteContents();\n const textNode = doc.createTextNode(text);\n range.insertNode(textNode);\n range.setStartAfter(textNode);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emit('paste', element.innerHTML);\n emitChange();\n return;\n }\n }\n\n // Prefer HTML, fall back to plain text\n let html = clipboard.getData('text/html');\n if (!html) {\n const text = clipboard.getData('text/plain');\n if (!text) return;\n // Escape plain text and convert newlines to <br>\n html = text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n .replace(/\\n/g, '<br>');\n }\n\n // Sanitize through policy \u2014 returns DocumentFragment directly\n // to avoid the serialize\u2192reparse mXSS vector\n const fragment = sanitizeToFragment(html, policy);\n\n // Insert via Selection/Range API (NOT execCommand('insertHTML'))\n const selection = doc.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n\n const range = selection.getRangeAt(0);\n range.deleteContents();\n\n // Check overflow using text content length\n if (policy.maxLength > 0) {\n const pasteTextLen = fragment.textContent?.length ?? 0;\n const currentLen = element.textContent?.length ?? 0;\n if (currentLen + pasteTextLen > policy.maxLength) {\n emit('overflow', policy.maxLength);\n }\n }\n\n // Remember last inserted node for cursor positioning\n let lastNode: Node | null = fragment.lastChild;\n range.insertNode(fragment);\n\n // Move cursor after inserted content\n if (lastNode) {\n const newRange = doc.createRange();\n newRange.setStartAfter(lastNode);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n emit('paste', element.innerHTML);\n emitChange();\n }\n\n // Input handler for change events\n function onInput(): void {\n emitChange();\n }\n\n // Keydown handler for code block behavior\n function onKeydown(e: KeyboardEvent): void {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const anchor = sel.anchorNode;\n if (!anchor) return;\n\n const pre = findAncestor(anchor, 'PRE');\n\n if (e.key === 'Enter' && pre) {\n // Insert newline instead of new paragraph\n e.preventDefault();\n const range = sel.getRangeAt(0);\n range.deleteContents();\n const textNode = doc.createTextNode('\\n');\n range.insertNode(textNode);\n range.setStartAfter(textNode);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emitChange();\n }\n\n if (e.key === 'Backspace' && pre) {\n // At start of empty pre, convert to <p>\n const text = pre.textContent || '';\n const isAtStart = sel.anchorOffset === 0;\n const isEmpty = text === '' || text === '\\n';\n if (isAtStart && isEmpty) {\n e.preventDefault();\n const p = doc.createElement('p');\n p.appendChild(doc.createElement('br'));\n pre.parentNode?.replaceChild(p, pre);\n const range = doc.createRange();\n range.selectNodeContents(p);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emitChange();\n }\n }\n }\n\n element.addEventListener('keydown', onKeydown);\n element.addEventListener('paste', onPaste);\n element.addEventListener('input', onInput);\n\n function findAncestor(node: Node, tagName: string): Element | null {\n let current: Node | null = node;\n while (current && current !== element) {\n if (current.nodeType === 1 && (current as Element).tagName === tagName) return current as Element;\n current = current.parentNode;\n }\n return null;\n }\n\n /**\n * Manually unwrap a list the selection is inside of, replacing each\n * <li> with a <p>. Returns true if an unwrap happened.\n *\n * We do this instead of relying on execCommand toggle-off because\n * browsers unwrap list items into <div> wrappers, which the policy\n * enforcer then strips (losing content).\n */\n function unwrapList(tag: 'UL' | 'OL'): boolean {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return false;\n const anchor = sel.anchorNode;\n if (!anchor) return false;\n const list = findAncestor(anchor, tag);\n if (!list) return false;\n\n const anchorLi = findAncestor(anchor, 'LI');\n const parent = list.parentNode;\n if (!parent) return false;\n\n const paragraphs: HTMLParagraphElement[] = [];\n let focusTarget: HTMLParagraphElement | null = null;\n\n for (const child of Array.from(list.childNodes)) {\n if (child.nodeType !== 1 || (child as Element).tagName !== 'LI') continue;\n const p = doc.createElement('p');\n while (child.firstChild) p.appendChild(child.firstChild);\n if (!p.firstChild) p.appendChild(doc.createElement('br'));\n paragraphs.push(p);\n if (child === anchorLi) focusTarget = p;\n }\n\n for (const p of paragraphs) parent.insertBefore(p, list);\n parent.removeChild(list);\n\n const target = focusTarget ?? paragraphs[0];\n if (target) {\n const r = doc.createRange();\n r.selectNodeContents(target);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n emitChange();\n return true;\n }\n\n function hasAncestor(node: Node, tagName: string): boolean {\n let current: Node | null = node;\n while (current && current !== element) {\n if (current.nodeType === 1 && (current as Element).tagName === tagName) return true;\n current = current.parentNode;\n }\n return false;\n }\n\n const editor: Editor = {\n exec(command: string, value?: string): void {\n if (!SUPPORTED_COMMANDS.has(command)) {\n throw new Error(`Unknown editor command: \"${command}\"`);\n }\n\n element.focus();\n\n switch (command) {\n case 'bold':\n doc.execCommand('bold', false);\n break;\n case 'italic':\n doc.execCommand('italic', false);\n break;\n case 'heading': {\n const level = value ?? '1';\n if (!['1', '2', '3'].includes(level)) {\n throw new Error(`Invalid heading level: \"${level}\". Use 1, 2, or 3`);\n }\n const tag = `H${level}`;\n const anchor = doc.getSelection()?.anchorNode;\n if (anchor && editor.queryState('heading') && hasAncestor(anchor, tag)) {\n doc.execCommand('formatBlock', false, '<p>');\n } else {\n doc.execCommand('formatBlock', false, `<h${level}>`);\n }\n break;\n }\n case 'blockquote':\n if (editor.queryState('blockquote')) {\n doc.execCommand('formatBlock', false, '<p>');\n } else {\n doc.execCommand('formatBlock', false, '<blockquote>');\n }\n break;\n case 'unorderedList':\n if (!unwrapList('UL')) doc.execCommand('insertUnorderedList', false);\n break;\n case 'orderedList':\n if (!unwrapList('OL')) doc.execCommand('insertOrderedList', false);\n break;\n case 'link': {\n if (!value) {\n throw new Error('Link command requires a URL value');\n }\n const trimmed = value.trim();\n if (!isProtocolAllowed(trimmed, policy.protocols)) {\n emit('error', new Error(`Protocol not allowed: ${trimmed}`));\n return;\n }\n doc.execCommand('createLink', false, trimmed);\n break;\n }\n case 'unlink':\n doc.execCommand('unlink', false);\n break;\n case 'codeBlock': {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) break;\n const anchor = sel.anchorNode;\n const pre = anchor ? findAncestor(anchor, 'PRE') : null;\n if (pre) {\n // Toggle off: unwrap <pre><code> to <p>\n const p = doc.createElement('p');\n p.textContent = pre.textContent || '';\n pre.parentNode?.replaceChild(p, pre);\n const r = doc.createRange();\n r.selectNodeContents(p);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n } else {\n // Wrap current block in <pre><code>\n const range = sel.getRangeAt(0);\n let block = range.startContainer;\n while (block.parentNode && block.parentNode !== element) {\n block = block.parentNode;\n }\n const pre2 = doc.createElement('pre');\n const code = doc.createElement('code');\n const blockText = block.textContent || '';\n code.textContent = blockText.endsWith('\\n') ? blockText : blockText + '\\n';\n pre2.appendChild(code);\n if (block.parentNode === element) {\n element.replaceChild(pre2, block);\n } else {\n element.appendChild(pre2);\n }\n const r = doc.createRange();\n r.selectNodeContents(code);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n emitChange();\n break;\n }\n }\n },\n\n queryState(command: string): boolean {\n if (!SUPPORTED_COMMANDS.has(command)) {\n throw new Error(`Unknown editor command: \"${command}\"`);\n }\n\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return false;\n\n const node = sel.anchorNode;\n if (!node || !element.contains(node)) return false;\n\n switch (command) {\n case 'bold':\n return hasAncestor(node, 'STRONG') || hasAncestor(node, 'B');\n case 'italic':\n return hasAncestor(node, 'EM') || hasAncestor(node, 'I');\n case 'heading':\n return hasAncestor(node, 'H1') || hasAncestor(node, 'H2') || hasAncestor(node, 'H3');\n case 'blockquote':\n return hasAncestor(node, 'BLOCKQUOTE');\n case 'unorderedList':\n return hasAncestor(node, 'UL');\n case 'orderedList':\n return hasAncestor(node, 'OL');\n case 'link':\n return hasAncestor(node, 'A');\n case 'unlink':\n return false;\n case 'codeBlock':\n return hasAncestor(node, 'PRE');\n default:\n return false;\n }\n },\n\n getHTML(): string {\n return element.innerHTML;\n },\n\n getText(): string {\n return element.textContent ?? '';\n },\n\n destroy(): void {\n element.removeEventListener('keydown', onKeydown);\n element.removeEventListener('paste', onPaste);\n element.removeEventListener('input', onInput);\n enforcer.destroy();\n element.contentEditable = 'false';\n },\n\n on(event: string, handler: EventHandler): void {\n if (!handlers[event]) handlers[event] = [];\n handlers[event].push(handler);\n },\n };\n\n return editor;\n}\n"],
5
+ "mappings": "AAAA,OAAS,aAAAA,EAAW,UAAAC,MAAc,QCElC,IAAMC,EAAyB,CAC7B,KAAM,CACJ,EAAG,CAAC,EACJ,GAAI,CAAC,EACL,OAAQ,CAAC,EACT,GAAI,CAAC,EACL,EAAG,CAAC,OAAQ,QAAS,QAAQ,EAC7B,GAAI,CAAC,EACL,GAAI,CAAC,EACL,GAAI,CAAC,EACL,GAAI,CAAC,EACL,GAAI,CAAC,EACL,GAAI,CAAC,EACL,WAAY,CAAC,EACb,IAAK,CAAC,EACN,KAAM,CAAC,CACT,EACA,MAAO,GACP,SAAU,GACV,UAAW,IACX,UAAW,CAAC,QAAS,OAAQ,QAAQ,CACvC,EAGA,OAAO,OAAOA,CAAM,EACpB,OAAO,OAAOA,EAAO,SAAS,EAC9B,QAAWC,KAAS,OAAO,OAAOD,EAAO,IAAI,EAAG,OAAO,OAAOC,CAAK,EACnE,OAAO,OAAOD,EAAO,IAAI,EAElB,IAAME,EAA2CF,EC9BjD,IAAMG,EAAwC,CACnD,EAAG,SACH,EAAG,IACL,EAGaC,EAAY,IAAI,IAAI,CAAC,OAAQ,MAAO,SAAU,YAAY,CAAC,EAG3DC,EAAmB,IAAI,IAAI,CAAC,aAAc,MAAM,CAAC,EAMvD,SAASC,EAAgBC,EAA8B,CAC5D,IAAIC,EAAUD,EAAM,KAAK,EACzBC,EAAUA,EAAQ,QAAQ,qBAAsB,CAACC,EAAGC,IAClD,OAAO,aAAa,SAASA,EAAK,EAAE,CAAC,CACvC,EACAF,EAAUA,EAAQ,QAAQ,aAAc,CAACC,EAAGE,IAC1C,OAAO,aAAa,SAASA,EAAK,EAAE,CAAC,CACvC,EACA,GAAI,CACFH,EAAU,mBAAmBA,CAAO,CACtC,MAAQ,CAER,CACAA,EAAUA,EAAQ,QAAQ,+EAAgF,EAAE,EAC5G,IAAMI,EAAQJ,EAAQ,MAAM,4BAA4B,EACxD,OAAOI,EAAQA,EAAM,CAAC,EAAE,YAAY,EAAI,IAC1C,CAMO,SAASC,EAAkBN,EAAeO,EAAqC,CACpF,IAAMC,EAAWT,EAAgBC,CAAK,EACtC,OAAIQ,IAAa,KAAa,GAC1BV,EAAiB,IAAIU,CAAQ,EAAU,GACpCD,EAAiB,SAASC,CAAQ,CAC3C,CClCA,SAASC,EACPC,EACAC,EACAC,EACM,CACN,IAAMC,EAAW,MAAM,KAAKH,EAAO,UAAU,EAE7C,QAAWI,KAAQD,EAAU,CAE3B,GAAIC,EAAK,WAAa,EAAG,SAGzB,GAAIA,EAAK,WAAa,EAAG,CACvBJ,EAAO,YAAYI,CAAI,EACvB,QACF,CAEA,IAAMC,EAAKD,EACPE,EAAUD,EAAG,QAAQ,YAAY,EAG/BE,EAAaC,EAAcF,CAAO,EAMxC,GALIC,IACFD,EAAUC,GAIRL,GAASD,EAAO,SAAU,CAC5BD,EAAO,YAAYK,CAAE,EACrB,QACF,CAGA,IAAMI,EAAeR,EAAO,KAAKK,CAAO,EACxC,GAAIG,IAAiB,OAAW,CAE9B,GAAIR,EAAO,MAETD,EAAO,YAAYK,CAAE,MAChB,CAGL,IADAN,EAAgBM,EAAIJ,EAAQC,CAAK,EAC1BG,EAAG,YACRL,EAAO,aAAaK,EAAG,WAAYA,CAAE,EAEvCL,EAAO,YAAYK,CAAE,CACvB,CACA,QACF,CAGA,IAAIK,EAAmBL,EACvB,GAAIE,GAAcF,EAAG,QAAQ,YAAY,IAAME,EAAY,CAEzD,IAAMI,EADMN,EAAG,cACS,cAAcE,CAAU,EAChD,KAAOF,EAAG,YACRM,EAAY,YAAYN,EAAG,UAAU,EAEvCL,EAAO,aAAaW,EAAaN,CAAE,EACnCK,EAAUC,CACZ,CAGA,IAAMC,EAAQ,MAAM,KAAKF,EAAQ,UAAU,EAC3C,QAAWG,KAAQD,EAAO,CACxB,IAAME,EAAWD,EAAK,KAAK,YAAY,EAGvC,GAAIC,EAAS,WAAW,IAAI,EAAG,CAC7BJ,EAAQ,gBAAgBG,EAAK,IAAI,EACjC,QACF,CAGA,GAAI,CAACJ,EAAa,SAASK,CAAQ,EAAG,CACpCJ,EAAQ,gBAAgBG,EAAK,IAAI,EACjC,QACF,CAGIE,EAAU,IAAID,CAAQ,IACnBE,EAAkBH,EAAK,MAAOZ,EAAO,SAAS,GACjDS,EAAQ,gBAAgBG,EAAK,IAAI,EAGvC,CAGAd,EAAgBW,EAAST,EAAQC,EAAQ,CAAC,CAC5C,CACF,CAMO,SAASe,EAAmBC,EAAcjB,EAA0C,CACzF,IAAMkB,EAAW,SAAS,cAAc,UAAU,EAClD,GAAI,CAACD,EAAM,OAAOC,EAAS,QAE3BA,EAAS,UAAYD,EACrB,IAAME,EAAWD,EAAS,QAE1B,OAAApB,EAAgBqB,EAAUnB,EAAQ,CAAC,EAE/BA,EAAO,UAAY,IAAMmB,EAAS,aAAa,QAAU,GAAKnB,EAAO,WACvEoB,EAAiBD,EAAUnB,EAAO,SAAS,EAGtCmB,CACT,CAsBA,SAASE,EAAiBC,EAAYC,EAA2B,CAC/D,IAAIC,EAAYD,EAEVE,EAAW,MAAM,KAAKH,EAAK,UAAU,EAC3C,QAAWI,KAASD,EAAU,CAC5B,GAAID,GAAa,EAAG,CAClBF,EAAK,YAAYI,CAAK,EACtB,QACF,CAEA,GAAIA,EAAM,WAAa,EAAG,CAExB,IAAMC,EAAOD,EAAM,aAAe,GAC9BC,EAAK,OAASH,GAChBE,EAAM,YAAcC,EAAK,MAAM,EAAGH,CAAS,EAC3CA,EAAY,GAEZA,GAAaG,EAAK,MAEtB,MAAWD,EAAM,WAAa,EAC5BF,EAAYH,EAAiBK,EAAOF,CAAS,EAE7CF,EAAK,YAAYI,CAAK,CAE1B,CAEA,OAAOF,CACT,CC1JA,SAASI,EAASC,EAAYC,EAAoB,CAChD,IAAIC,EAAQ,EACRC,EAAUH,EAAK,WACnB,KAAOG,GAAWA,IAAYF,GACxBE,EAAQ,WAAa,GAAGD,IAC5BC,EAAUA,EAAQ,WAEpB,OAAOD,CACT,CAMA,SAASE,EACPC,EACAC,EACAL,EACS,CACT,IAAIM,EAAUF,EAAG,QAAQ,YAAY,EAC/BG,EAAaC,EAAcF,CAAO,EAKxC,GAJIC,IAAYD,EAAUC,GAGZT,EAASM,EAAIJ,CAAI,GAClBK,EAAO,SAClB,OAAAD,EAAG,YAAY,YAAYA,CAAE,EACtB,GAIT,IAAMK,EAAeJ,EAAO,KAAKC,CAAO,EACxC,GAAIG,IAAiB,OAAW,CAC9B,GAAIJ,EAAO,MACTD,EAAG,YAAY,YAAYA,CAAE,MACxB,CAEL,IAAMM,EAASN,EAAG,WAClB,GAAIM,EAAQ,CACV,KAAON,EAAG,YACRM,EAAO,aAAaN,EAAG,WAAYA,CAAE,EAEvCM,EAAO,YAAYN,CAAE,CACvB,CACF,CACA,MAAO,EACT,CAGA,IAAIF,EAAmBE,EACvB,GAAIG,GAAcH,EAAG,QAAQ,YAAY,IAAMG,EAAY,CACzD,IAAMI,EAAcP,EAAG,cAAc,cAAcG,CAAU,EAC7D,KAAOH,EAAG,YACRO,EAAY,YAAYP,EAAG,UAAU,EAGvC,QAAWQ,KAAQ,MAAM,KAAKR,EAAG,UAAU,EACzCO,EAAY,aAAaC,EAAK,KAAMA,EAAK,KAAK,EAEhDR,EAAG,YAAY,aAAaO,EAAaP,CAAE,EAC3CF,EAAUS,CACZ,CAGA,QAAWC,KAAQ,MAAM,KAAKV,EAAQ,UAAU,EAAG,CACjD,IAAMW,EAAWD,EAAK,KAAK,YAAY,EAEvC,GAAIC,EAAS,WAAW,IAAI,EAAG,CAC7BX,EAAQ,gBAAgBU,EAAK,IAAI,EACjC,QACF,CAEA,GAAI,CAACH,EAAa,SAASI,CAAQ,EAAG,CACpCX,EAAQ,gBAAgBU,EAAK,IAAI,EACjC,QACF,CAEIE,EAAU,IAAID,CAAQ,IACnBE,EAAkBH,EAAK,MAAOP,EAAO,SAAS,GACjDH,EAAQ,gBAAgBU,EAAK,IAAI,EAGvC,CAEA,MAAO,EACT,CAKA,SAASI,EAAejB,EAAYM,EAAwBL,EAAyB,CACnF,IAAMiB,EAAW,MAAM,KAAKlB,EAAK,UAAU,EAC3C,QAAWmB,KAASD,EAAU,CAC5B,GAAIC,EAAM,WAAa,EAAG,CAEpBA,EAAM,WAAa,GACrBnB,EAAK,YAAYmB,CAAK,EAExB,QACF,CACgBf,EAAee,EAAkBb,EAAQL,CAAI,GAE3DgB,EAAeE,EAAOb,EAAQL,CAAI,CAEtC,CACF,CAUO,SAASmB,EACdC,EACAf,EACgB,CAChB,GAAI,CAACA,GAAU,CAACA,EAAO,KACrB,MAAM,IAAI,UAAU,oCAAoC,EAG1D,IAAIgB,EAAgB,GACdC,EAA+C,CAAC,EAEtD,SAASC,EAAUC,EAAoB,CACrC,QAAWC,KAAWH,EACpBG,EAAQD,CAAK,CAEjB,CAEA,IAAME,EAAW,IAAI,iBAAkBC,GAAc,CACnD,GAAI,CAAAN,EACJ,CAAAA,EAAgB,GAEhB,GAAI,CACF,QAAWO,KAAYD,EACrB,GAAIC,EAAS,OAAS,YACpB,QAAW7B,KAAQ,MAAM,KAAK6B,EAAS,UAAU,EAAG,CAElD,GAAI7B,EAAK,WAAa,EAAG,SAGzB,GAAIA,EAAK,WAAa,EAAG,CACvBA,EAAK,YAAY,YAAYA,CAAI,EACjC,QACF,CAEgBI,EAAeJ,EAAiBM,EAAQe,CAAO,GAG7DJ,EAAejB,EAAMM,EAAQe,CAAO,CAExC,SACSQ,EAAS,OAAS,aAAc,CACzC,IAAMC,EAASD,EAAS,OACxB,GAAIC,EAAO,WAAa,EAAG,SAE3B,IAAMhB,EAAWe,EAAS,cAC1B,GAAI,CAACf,EAAU,SAEf,IAAMP,EAAUuB,EAAO,QAAQ,YAAY,EACrCC,EAAgBtB,EAAcF,CAAO,GAAKA,EAC1CG,EAAeJ,EAAO,KAAKyB,CAAa,EAE9C,GAAI,CAACrB,EAAc,SAEnB,IAAMsB,EAAYlB,EAAS,YAAY,EAEvC,GAAIkB,EAAU,WAAW,IAAI,EAAG,CAC9BF,EAAO,gBAAgBhB,CAAQ,EAC/B,QACF,CAEA,GAAI,CAACJ,EAAa,SAASsB,CAAS,EAAG,CACrCF,EAAO,gBAAgBhB,CAAQ,EAC/B,QACF,CAEA,GAAIC,EAAU,IAAIiB,CAAS,EAAG,CAC5B,IAAMC,EAAQH,EAAO,aAAahB,CAAQ,EACtCmB,GAAS,CAACjB,EAAkBiB,EAAO3B,EAAO,SAAS,GACrDwB,EAAO,gBAAgBhB,CAAQ,CAEnC,CACF,CAEJ,OAASoB,EAAK,CACZV,EAAUU,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOA,CAAG,CAAC,CAAC,CAC/D,QAAE,CACAZ,EAAgB,EAClB,EACF,CAAC,EAED,OAAAK,EAAS,QAAQN,EAAS,CACxB,UAAW,GACX,WAAY,GACZ,QAAS,EACX,CAAC,EAEM,CACL,SAAU,CACRM,EAAS,WAAW,CACtB,EACA,GAAGQ,EAAgBT,EAAiC,CAC9CS,IAAU,SACZZ,EAAc,KAAKG,CAAO,CAE9B,CACF,CACF,CCrNA,IAAMU,EAAqB,IAAI,IAAI,CACjC,OACA,SACA,UACA,aACA,gBACA,cACA,OACA,SACA,WACF,CAAC,EASM,SAASC,EACdC,EACAC,EACQ,CACR,GAAI,CAACD,EACH,MAAM,IAAI,UAAU,sCAAsC,EAE5D,GAAI,CAACA,EAAQ,eAAiB,CAACA,EAAQ,WACrC,MAAM,IAAI,UAAU,sDAAsD,EAG5E,IAAME,EAAMD,GAAS,QAAUE,EACzBC,EAAyB,CAC7B,KAAM,OAAO,YACX,OAAO,QAAQF,EAAI,IAAI,EAAE,IAAI,CAAC,CAACG,EAAGC,CAAC,IAAM,CAACD,EAAG,CAAC,GAAGC,CAAC,CAAC,CAAC,CACtD,EACA,MAAOJ,EAAI,MACX,SAAUA,EAAI,SACd,UAAWA,EAAI,UACf,UAAW,CAAC,GAAGA,EAAI,SAAS,CAC9B,EAEMK,EAA2C,CAAC,EAC5CC,EAAMR,EAAQ,cAEpB,SAASS,EAAKC,KAAuBC,EAAuB,CAC1D,QAAWC,KAAWL,EAASG,CAAK,GAAK,CAAC,EACxCE,EAAQ,GAAGD,CAAI,CAEnB,CAIA,SAASE,GAAmB,CAC1B,IAAMC,EAAOd,EAAQ,UACrBS,EAAK,SAAUK,CAAI,EACnBb,GAAS,WAAWa,CAAI,CAC1B,CAGAd,EAAQ,gBAAkB,OAG1B,IAAMe,EAA2BC,EAAqBhB,EAASI,CAAM,EACrEW,EAAS,GAAG,QAAUE,GAAQR,EAAK,QAASQ,CAAG,CAAC,EAGhD,SAASC,EAAQC,EAAyB,CACxCA,EAAE,eAAe,EAEjB,IAAMC,EAAYD,EAAE,cACpB,GAAI,CAACC,EAAW,OAGhB,IAAMC,EAAMb,EAAI,aAAa,EAC7B,GAAIa,GAAOA,EAAI,WAAa,GAAKA,EAAI,YACvBC,EAAaD,EAAI,WAAY,KAAK,EACrC,CACP,IAAME,EAAOH,EAAU,QAAQ,YAAY,EAC3C,GAAI,CAACG,EAAM,OACPnB,EAAO,UAAY,IACFJ,EAAQ,aAAa,QAAU,GACjCuB,EAAK,OAASnB,EAAO,WACpCK,EAAK,WAAYL,EAAO,SAAS,EAGrC,IAAMoB,EAAQH,EAAI,WAAW,CAAC,EAC9BG,EAAM,eAAe,EACrB,IAAMC,EAAWjB,EAAI,eAAee,CAAI,EACxCC,EAAM,WAAWC,CAAQ,EACzBD,EAAM,cAAcC,CAAQ,EAC5BD,EAAM,SAAS,EAAI,EACnBH,EAAI,gBAAgB,EACpBA,EAAI,SAASG,CAAK,EAClBf,EAAK,QAAST,EAAQ,SAAS,EAC/Ba,EAAW,EACX,MACF,CAIF,IAAIC,EAAOM,EAAU,QAAQ,WAAW,EACxC,GAAI,CAACN,EAAM,CACT,IAAMS,EAAOH,EAAU,QAAQ,YAAY,EAC3C,GAAI,CAACG,EAAM,OAEXT,EAAOS,EACJ,QAAQ,KAAM,OAAO,EACrB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,QAAQ,EACtB,QAAQ,KAAM,OAAO,EACrB,QAAQ,MAAO,MAAM,CAC1B,CAIA,IAAMG,EAAWC,EAAmBb,EAAMV,CAAM,EAG1CwB,EAAYpB,EAAI,aAAa,EACnC,GAAI,CAACoB,GAAaA,EAAU,aAAe,EAAG,OAE9C,IAAMJ,EAAQI,EAAU,WAAW,CAAC,EAIpC,GAHAJ,EAAM,eAAe,EAGjBpB,EAAO,UAAY,EAAG,CACxB,IAAMyB,EAAeH,EAAS,aAAa,QAAU,GAClC1B,EAAQ,aAAa,QAAU,GACjC6B,EAAezB,EAAO,WACrCK,EAAK,WAAYL,EAAO,SAAS,CAErC,CAGA,IAAI0B,EAAwBJ,EAAS,UAIrC,GAHAF,EAAM,WAAWE,CAAQ,EAGrBI,EAAU,CACZ,IAAMC,EAAWvB,EAAI,YAAY,EACjCuB,EAAS,cAAcD,CAAQ,EAC/BC,EAAS,SAAS,EAAI,EACtBH,EAAU,gBAAgB,EAC1BA,EAAU,SAASG,CAAQ,CAC7B,CAEAtB,EAAK,QAAST,EAAQ,SAAS,EAC/Ba,EAAW,CACb,CAGA,SAASmB,GAAgB,CACvBnB,EAAW,CACb,CAGA,SAASoB,EAAUd,EAAwB,CACzC,IAAME,EAAMb,EAAI,aAAa,EAC7B,GAAI,CAACa,GAAOA,EAAI,aAAe,EAAG,OAClC,IAAMa,EAASb,EAAI,WACnB,GAAI,CAACa,EAAQ,OAEb,IAAMC,EAAMb,EAAaY,EAAQ,KAAK,EAEtC,GAAIf,EAAE,MAAQ,SAAWgB,EAAK,CAE5BhB,EAAE,eAAe,EACjB,IAAMK,EAAQH,EAAI,WAAW,CAAC,EAC9BG,EAAM,eAAe,EACrB,IAAMC,EAAWjB,EAAI,eAAe;AAAA,CAAI,EACxCgB,EAAM,WAAWC,CAAQ,EACzBD,EAAM,cAAcC,CAAQ,EAC5BD,EAAM,SAAS,EAAI,EACnBH,EAAI,gBAAgB,EACpBA,EAAI,SAASG,CAAK,EAClBX,EAAW,CACb,CAEA,GAAIM,EAAE,MAAQ,aAAegB,EAAK,CAEhC,IAAMZ,EAAOY,EAAI,aAAe,GAGhC,GAFkBd,EAAI,eAAiB,IACvBE,IAAS,IAAMA,IAAS;AAAA,GACd,CACxBJ,EAAE,eAAe,EACjB,IAAMiB,EAAI5B,EAAI,cAAc,GAAG,EAC/B4B,EAAE,YAAY5B,EAAI,cAAc,IAAI,CAAC,EACrC2B,EAAI,YAAY,aAAaC,EAAGD,CAAG,EACnC,IAAMX,EAAQhB,EAAI,YAAY,EAC9BgB,EAAM,mBAAmBY,CAAC,EAC1BZ,EAAM,SAAS,EAAI,EACnBH,EAAI,gBAAgB,EACpBA,EAAI,SAASG,CAAK,EAClBX,EAAW,CACb,CACF,CACF,CAEAb,EAAQ,iBAAiB,UAAWiC,CAAS,EAC7CjC,EAAQ,iBAAiB,QAASkB,CAAO,EACzClB,EAAQ,iBAAiB,QAASgC,CAAO,EAEzC,SAASV,EAAae,EAAYC,EAAiC,CACjE,IAAIC,EAAuBF,EAC3B,KAAOE,GAAWA,IAAYvC,GAAS,CACrC,GAAIuC,EAAQ,WAAa,GAAMA,EAAoB,UAAYD,EAAS,OAAOC,EAC/EA,EAAUA,EAAQ,UACpB,CACA,OAAO,IACT,CAUA,SAASC,EAAWC,EAA2B,CAC7C,IAAMpB,EAAMb,EAAI,aAAa,EAC7B,GAAI,CAACa,GAAOA,EAAI,aAAe,EAAG,MAAO,GACzC,IAAMa,EAASb,EAAI,WACnB,GAAI,CAACa,EAAQ,MAAO,GACpB,IAAMQ,EAAOpB,EAAaY,EAAQO,CAAG,EACrC,GAAI,CAACC,EAAM,MAAO,GAElB,IAAMC,EAAWrB,EAAaY,EAAQ,IAAI,EACpCU,EAASF,EAAK,WACpB,GAAI,CAACE,EAAQ,MAAO,GAEpB,IAAMC,EAAqC,CAAC,EACxCC,EAA2C,KAE/C,QAAWC,KAAS,MAAM,KAAKL,EAAK,UAAU,EAAG,CAC/C,GAAIK,EAAM,WAAa,GAAMA,EAAkB,UAAY,KAAM,SACjE,IAAMX,EAAI5B,EAAI,cAAc,GAAG,EAC/B,KAAOuC,EAAM,YAAYX,EAAE,YAAYW,EAAM,UAAU,EAClDX,EAAE,YAAYA,EAAE,YAAY5B,EAAI,cAAc,IAAI,CAAC,EACxDqC,EAAW,KAAKT,CAAC,EACbW,IAAUJ,IAAUG,EAAcV,EACxC,CAEA,QAAWA,KAAKS,EAAYD,EAAO,aAAaR,EAAGM,CAAI,EACvDE,EAAO,YAAYF,CAAI,EAEvB,IAAMM,EAASF,GAAeD,EAAW,CAAC,EAC1C,GAAIG,EAAQ,CACV,IAAMC,EAAIzC,EAAI,YAAY,EAC1ByC,EAAE,mBAAmBD,CAAM,EAC3BC,EAAE,SAAS,EAAK,EAChB5B,EAAI,gBAAgB,EACpBA,EAAI,SAAS4B,CAAC,CAChB,CACA,OAAApC,EAAW,EACJ,EACT,CAEA,SAASqC,EAAYb,EAAYC,EAA0B,CACzD,IAAIC,EAAuBF,EAC3B,KAAOE,GAAWA,IAAYvC,GAAS,CACrC,GAAIuC,EAAQ,WAAa,GAAMA,EAAoB,UAAYD,EAAS,MAAO,GAC/EC,EAAUA,EAAQ,UACpB,CACA,MAAO,EACT,CAEA,IAAMY,EAAiB,CACrB,KAAKC,EAAiBC,EAAsB,CAC1C,GAAI,CAACvD,EAAmB,IAAIsD,CAAO,EACjC,MAAM,IAAI,MAAM,4BAA4BA,CAAO,GAAG,EAKxD,OAFApD,EAAQ,MAAM,EAENoD,EAAS,CACf,IAAK,OACH5C,EAAI,YAAY,OAAQ,EAAK,EAC7B,MACF,IAAK,SACHA,EAAI,YAAY,SAAU,EAAK,EAC/B,MACF,IAAK,UAAW,CACd,IAAM8C,EAAQD,GAAS,IACvB,GAAI,CAAC,CAAC,IAAK,IAAK,GAAG,EAAE,SAASC,CAAK,EACjC,MAAM,IAAI,MAAM,2BAA2BA,CAAK,mBAAmB,EAErE,IAAMb,EAAM,IAAIa,CAAK,GACfpB,EAAS1B,EAAI,aAAa,GAAG,WAC/B0B,GAAUiB,EAAO,WAAW,SAAS,GAAKD,EAAYhB,EAAQO,CAAG,EACnEjC,EAAI,YAAY,cAAe,GAAO,KAAK,EAE3CA,EAAI,YAAY,cAAe,GAAO,KAAK8C,CAAK,GAAG,EAErD,KACF,CACA,IAAK,aACCH,EAAO,WAAW,YAAY,EAChC3C,EAAI,YAAY,cAAe,GAAO,KAAK,EAE3CA,EAAI,YAAY,cAAe,GAAO,cAAc,EAEtD,MACF,IAAK,gBACEgC,EAAW,IAAI,GAAGhC,EAAI,YAAY,sBAAuB,EAAK,EACnE,MACF,IAAK,cACEgC,EAAW,IAAI,GAAGhC,EAAI,YAAY,oBAAqB,EAAK,EACjE,MACF,IAAK,OAAQ,CACX,GAAI,CAAC6C,EACH,MAAM,IAAI,MAAM,mCAAmC,EAErD,IAAME,EAAUF,EAAM,KAAK,EAC3B,GAAI,CAACG,EAAkBD,EAASnD,EAAO,SAAS,EAAG,CACjDK,EAAK,QAAS,IAAI,MAAM,yBAAyB8C,CAAO,EAAE,CAAC,EAC3D,MACF,CACA/C,EAAI,YAAY,aAAc,GAAO+C,CAAO,EAC5C,KACF,CACA,IAAK,SACH/C,EAAI,YAAY,SAAU,EAAK,EAC/B,MACF,IAAK,YAAa,CAChB,IAAMa,EAAMb,EAAI,aAAa,EAC7B,GAAI,CAACa,GAAOA,EAAI,aAAe,EAAG,MAClC,IAAMa,EAASb,EAAI,WACbc,EAAMD,EAASZ,EAAaY,EAAQ,KAAK,EAAI,KACnD,GAAIC,EAAK,CAEP,IAAMC,EAAI5B,EAAI,cAAc,GAAG,EAC/B4B,EAAE,YAAcD,EAAI,aAAe,GACnCA,EAAI,YAAY,aAAaC,EAAGD,CAAG,EACnC,IAAMc,EAAIzC,EAAI,YAAY,EAC1ByC,EAAE,mBAAmBb,CAAC,EACtBa,EAAE,SAAS,EAAK,EAChB5B,EAAI,gBAAgB,EACpBA,EAAI,SAAS4B,CAAC,CAChB,KAAO,CAGL,IAAIQ,EADUpC,EAAI,WAAW,CAAC,EACZ,eAClB,KAAOoC,EAAM,YAAcA,EAAM,aAAezD,GAC9CyD,EAAQA,EAAM,WAEhB,IAAMC,EAAOlD,EAAI,cAAc,KAAK,EAC9BmD,EAAOnD,EAAI,cAAc,MAAM,EAC/BoD,EAAYH,EAAM,aAAe,GACvCE,EAAK,YAAcC,EAAU,SAAS;AAAA,CAAI,EAAIA,EAAYA,EAAY;AAAA,EACtEF,EAAK,YAAYC,CAAI,EACjBF,EAAM,aAAezD,EACvBA,EAAQ,aAAa0D,EAAMD,CAAK,EAEhCzD,EAAQ,YAAY0D,CAAI,EAE1B,IAAMT,EAAIzC,EAAI,YAAY,EAC1ByC,EAAE,mBAAmBU,CAAI,EACzBV,EAAE,SAAS,EAAK,EAChB5B,EAAI,gBAAgB,EACpBA,EAAI,SAAS4B,CAAC,CAChB,CACApC,EAAW,EACX,KACF,CACF,CACF,EAEA,WAAWuC,EAA0B,CACnC,GAAI,CAACtD,EAAmB,IAAIsD,CAAO,EACjC,MAAM,IAAI,MAAM,4BAA4BA,CAAO,GAAG,EAGxD,IAAM/B,EAAMb,EAAI,aAAa,EAC7B,GAAI,CAACa,GAAOA,EAAI,aAAe,EAAG,MAAO,GAEzC,IAAMgB,EAAOhB,EAAI,WACjB,GAAI,CAACgB,GAAQ,CAACrC,EAAQ,SAASqC,CAAI,EAAG,MAAO,GAE7C,OAAQe,EAAS,CACf,IAAK,OACH,OAAOF,EAAYb,EAAM,QAAQ,GAAKa,EAAYb,EAAM,GAAG,EAC7D,IAAK,SACH,OAAOa,EAAYb,EAAM,IAAI,GAAKa,EAAYb,EAAM,GAAG,EACzD,IAAK,UACH,OAAOa,EAAYb,EAAM,IAAI,GAAKa,EAAYb,EAAM,IAAI,GAAKa,EAAYb,EAAM,IAAI,EACrF,IAAK,aACH,OAAOa,EAAYb,EAAM,YAAY,EACvC,IAAK,gBACH,OAAOa,EAAYb,EAAM,IAAI,EAC/B,IAAK,cACH,OAAOa,EAAYb,EAAM,IAAI,EAC/B,IAAK,OACH,OAAOa,EAAYb,EAAM,GAAG,EAC9B,IAAK,SACH,MAAO,GACT,IAAK,YACH,OAAOa,EAAYb,EAAM,KAAK,EAChC,QACE,MAAO,EACX,CACF,EAEA,SAAkB,CAChB,OAAOrC,EAAQ,SACjB,EAEA,SAAkB,CAChB,OAAOA,EAAQ,aAAe,EAChC,EAEA,SAAgB,CACdA,EAAQ,oBAAoB,UAAWiC,CAAS,EAChDjC,EAAQ,oBAAoB,QAASkB,CAAO,EAC5ClB,EAAQ,oBAAoB,QAASgC,CAAO,EAC5CjB,EAAS,QAAQ,EACjBf,EAAQ,gBAAkB,OAC5B,EAEA,GAAGU,EAAeE,EAA6B,CACxCL,EAASG,CAAK,IAAGH,EAASG,CAAK,EAAI,CAAC,GACzCH,EAASG,CAAK,EAAE,KAAKE,CAAO,CAC9B,CACF,EAEA,OAAOuC,CACT,CL9XS,cAAAU,MAAA,oBAxCF,SAASC,GAAUC,EAAuB,CAC/C,GAAM,CAAE,YAAAC,EAAa,MAAAC,EAAO,SAAAC,EAAU,OAAAC,EAAQ,UAAAC,EAAW,UAAAC,CAAU,EAAIN,EACjEO,EAAUC,EAA8B,IAAI,EAC5CC,EAAiBD,EAAsB,IAAI,EAC3CE,EAAcF,EAAwBL,CAAQ,EAC9CQ,EAAYH,EAAuBJ,GAAUQ,CAAc,EAEjE,OAAAC,EAAU,IAAM,CACdH,EAAY,QAAUP,CACxB,EAAG,CAACA,CAAQ,CAAC,EAEbU,EAAU,IAAM,CACd,IAAMC,EAAKP,EAAQ,QACnB,GAAI,CAACO,EAAI,OACT,IAAMC,EAAkBX,GAAUQ,EAClCD,EAAU,QAAUI,EACpBD,EAAG,gBAAgBE,EAAmBd,GAASD,GAAe,GAAIc,CAAe,CAAC,EAClF,IAAME,EAASC,EAAaJ,EAAI,CAC9B,OAAQC,EACR,SAAWI,GAAST,EAAY,UAAUS,CAAI,CAChD,CAAC,EACD,OAAAV,EAAe,QAAUQ,EACzBX,IAAYW,CAAM,EACX,IAAM,CACXA,EAAO,QAAQ,EACfR,EAAe,QAAU,KACzBH,IAAY,IAAI,CAClB,CAEF,EAAG,CAAC,CAAC,EAELO,EAAU,IAAM,CACd,IAAMI,EAASR,EAAe,QACxBK,EAAKP,EAAQ,QACf,CAACU,GAAU,CAACH,GAAMZ,IAAU,QAC5Be,EAAO,QAAQ,IAAMf,GACvBY,EAAG,gBAAgBE,EAAmBd,EAAOS,EAAU,OAAO,CAAC,CAEnE,EAAG,CAACT,CAAK,CAAC,EAEHJ,EAAC,OAAI,IAAKS,EAAS,UAAWF,EAAW,CAClD",
6
+ "names": ["useEffect", "useRef", "policy", "attrs", "DEFAULT_POLICY", "TAG_NORMALIZE", "URL_ATTRS", "DENIED_PROTOCOLS", "extractProtocol", "value", "decoded", "_", "hex", "dec", "match", "isProtocolAllowed", "allowedProtocols", "protocol", "walkAndSanitize", "parent", "policy", "depth", "children", "node", "el", "tagName", "normalized", "TAG_NORMALIZE", "allowedAttrs", "current", "replacement", "attrs", "attr", "attrName", "URL_ATTRS", "isProtocolAllowed", "sanitizeToFragment", "html", "template", "fragment", "truncateToLength", "truncateToLength", "node", "maxLength", "remaining", "children", "child", "text", "getDepth", "node", "root", "depth", "current", "enforceElement", "el", "policy", "tagName", "normalized", "TAG_NORMALIZE", "allowedAttrs", "parent", "replacement", "attr", "attrName", "URL_ATTRS", "isProtocolAllowed", "enforceSubtree", "children", "child", "createPolicyEnforcer", "element", "isApplyingFix", "errorHandlers", "emitError", "error", "handler", "observer", "mutations", "mutation", "target", "normalizedTag", "lowerAttr", "value", "err", "event", "SUPPORTED_COMMANDS", "createEditor", "element", "options", "src", "DEFAULT_POLICY", "policy", "k", "v", "handlers", "doc", "emit", "event", "args", "handler", "emitChange", "html", "enforcer", "createPolicyEnforcer", "err", "onPaste", "e", "clipboard", "sel", "findAncestor", "text", "range", "textNode", "fragment", "sanitizeToFragment", "selection", "pasteTextLen", "lastNode", "newRange", "onInput", "onKeydown", "anchor", "pre", "p", "node", "tagName", "current", "unwrapList", "tag", "list", "anchorLi", "parent", "paragraphs", "focusTarget", "child", "target", "r", "hasAncestor", "editor", "command", "value", "level", "trimmed", "isProtocolAllowed", "block", "pre2", "code", "blockText", "jsx", "Minisiwyg", "props", "initialHTML", "value", "onChange", "policy", "className", "editorRef", "hostRef", "useRef", "editorInstance", "onChangeRef", "policyRef", "DEFAULT_POLICY", "useEffect", "el", "effectivePolicy", "sanitizeToFragment", "editor", "createEditor", "html"]
7
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";var z=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var Y=Object.prototype.hasOwnProperty;var $=(e,n)=>{for(var o in n)z(e,o,{get:n[o],enumerable:!0})},G=(e,n,o,a)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of j(n))!Y.call(e,i)&&i!==o&&z(e,i,{get:()=>n[i],enumerable:!(a=q(n,i))||a.enumerable});return e};var W=e=>G(z({},"__esModule",{value:!0}),e);var V={};$(V,{minisiwyg:()=>J});module.exports=W(V);var P={tags:{p:[],br:[],strong:[],em:[],a:["href","title","target"],h1:[],h2:[],h3:[],ul:[],ol:[],li:[],blockquote:[],pre:[],code:[]},strip:!0,maxDepth:10,maxLength:1e5,protocols:["https","http","mailto"]};Object.freeze(P);Object.freeze(P.protocols);for(let e of Object.values(P.tags))Object.freeze(e);Object.freeze(P.tags);var b=P;var S={b:"strong",i:"em"},R=new Set(["href","src","action","formaction"]),K=new Set(["javascript","data"]);function Z(e){let n=e.trim();n=n.replace(/&#x([0-9a-f]+);?/gi,(a,i)=>String.fromCharCode(parseInt(i,16))),n=n.replace(/&#(\d+);?/g,(a,i)=>String.fromCharCode(parseInt(i,10)));try{n=decodeURIComponent(n)}catch{}n=n.replace(/[\s\x00-\x1f\u00A0\u1680\u2000-\u200B\u2028\u2029\u202F\u205F\u3000\uFEFF]+/g,"");let o=n.match(/^([a-z][a-z0-9+\-.]*)\s*:/i);return o?o[1].toLowerCase():null}function A(e,n){let o=Z(e);return o===null?!0:K.has(o)?!1:n.includes(o)}function M(e,n,o){let a=Array.from(e.childNodes);for(let i of a){if(i.nodeType===3)continue;if(i.nodeType!==1){e.removeChild(i);continue}let r=i,m=r.tagName.toLowerCase(),d=S[m];if(d&&(m=d),o>=n.maxDepth){e.removeChild(r);continue}let c=n.tags[m];if(c===void 0){if(n.strip)e.removeChild(r);else{for(M(r,n,o);r.firstChild;)e.insertBefore(r.firstChild,r);e.removeChild(r)}continue}let f=r;if(d&&r.tagName.toLowerCase()!==d){let y=r.ownerDocument.createElement(d);for(;r.firstChild;)y.appendChild(r.firstChild);e.replaceChild(y,r),f=y}let N=Array.from(f.attributes);for(let w of N){let y=w.name.toLowerCase();if(y.startsWith("on")){f.removeAttribute(w.name);continue}if(!c.includes(y)){f.removeAttribute(w.name);continue}R.has(y)&&(A(w.value,n.protocols)||f.removeAttribute(w.name))}M(f,n,o+1)}}function k(e,n){let o=document.createElement("template");if(!e)return o.content;o.innerHTML=e;let a=o.content;return M(a,n,0),n.maxLength>0&&(a.textContent?.length??0)>n.maxLength&&H(a,n.maxLength),a}function H(e,n){let o=n,a=Array.from(e.childNodes);for(let i of a){if(o<=0){e.removeChild(i);continue}if(i.nodeType===3){let r=i.textContent??"";r.length>o?(i.textContent=r.slice(0,o),o=0):o-=r.length}else i.nodeType===1?o=H(i,o):e.removeChild(i)}return o}function Q(e,n){let o=0,a=e.parentNode;for(;a&&a!==n;)a.nodeType===1&&o++,a=a.parentNode;return o}function U(e,n,o){let a=e.tagName.toLowerCase(),i=S[a];if(i&&(a=i),Q(e,o)>=n.maxDepth)return e.parentNode?.removeChild(e),!0;let m=n.tags[a];if(m===void 0){if(n.strip)e.parentNode?.removeChild(e);else{let c=e.parentNode;if(c){for(;e.firstChild;)c.insertBefore(e.firstChild,e);c.removeChild(e)}}return!0}let d=e;if(i&&e.tagName.toLowerCase()!==i){let c=e.ownerDocument.createElement(i);for(;e.firstChild;)c.appendChild(e.firstChild);for(let f of Array.from(e.attributes))c.setAttribute(f.name,f.value);e.parentNode?.replaceChild(c,e),d=c}for(let c of Array.from(d.attributes)){let f=c.name.toLowerCase();if(f.startsWith("on")){d.removeAttribute(c.name);continue}if(!m.includes(f)){d.removeAttribute(c.name);continue}R.has(f)&&(A(c.value,n.protocols)||d.removeAttribute(c.name))}return!1}function I(e,n,o){let a=Array.from(e.childNodes);for(let i of a){if(i.nodeType!==1){i.nodeType!==3&&e.removeChild(i);continue}U(i,n,o)||I(i,n,o)}}function _(e,n){if(!n||!n.tags)throw new TypeError('Policy must have a "tags" property');let o=!1,a=[];function i(m){for(let d of a)d(m)}let r=new MutationObserver(m=>{if(!o){o=!0;try{for(let d of m)if(d.type==="childList")for(let c of Array.from(d.addedNodes)){if(c.nodeType===3)continue;if(c.nodeType!==1){c.parentNode?.removeChild(c);continue}U(c,n,e)||I(c,n,e)}else if(d.type==="attributes"){let c=d.target;if(c.nodeType!==1)continue;let f=d.attributeName;if(!f)continue;let N=c.tagName.toLowerCase(),w=S[N]||N,y=n.tags[w];if(!y)continue;let T=f.toLowerCase();if(T.startsWith("on")){c.removeAttribute(f);continue}if(!y.includes(T)){c.removeAttribute(f);continue}if(R.has(T)){let L=c.getAttribute(f);L&&!A(L,n.protocols)&&c.removeAttribute(f)}}}catch(d){i(d instanceof Error?d:new Error(String(d)))}finally{o=!1}}});return r.observe(e,{childList:!0,attributes:!0,subtree:!0}),{destroy(){r.disconnect()},on(m,d){m==="error"&&a.push(d)}}}var F=new Set(["bold","italic","heading","blockquote","unorderedList","orderedList","link","unlink","codeBlock"]);function B(e,n){if(!e)throw new TypeError("createEditor requires an HTMLElement");if(!e.ownerDocument||!e.parentNode)throw new TypeError("createEditor requires an element attached to the DOM");let o=n?.policy??b,a={tags:Object.fromEntries(Object.entries(o.tags).map(([l,s])=>[l,[...s]])),strip:o.strip,maxDepth:o.maxDepth,maxLength:o.maxLength,protocols:[...o.protocols]},i={},r=e.ownerDocument;function m(l,...s){for(let t of i[l]??[])t(...s)}function d(){let l=e.innerHTML;m("change",l),n?.onChange?.(l)}e.contentEditable="true";let c=_(e,a);c.on("error",l=>m("error",l));function f(l){l.preventDefault();let s=l.clipboardData;if(!s)return;let t=r.getSelection();if(t&&t.rangeCount>0&&t.anchorNode&&y(t.anchorNode,"PRE")){let h=s.getData("text/plain");if(!h)return;a.maxLength>0&&(e.textContent?.length??0)+h.length>a.maxLength&&m("overflow",a.maxLength);let v=t.getRangeAt(0);v.deleteContents();let D=r.createTextNode(h);v.insertNode(D),v.setStartAfter(D),v.collapse(!0),t.removeAllRanges(),t.addRange(v),m("paste",e.innerHTML),d();return}let p=s.getData("text/html");if(!p){let g=s.getData("text/plain");if(!g)return;p=g.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/\n/g,"<br>")}let u=k(p,a),C=r.getSelection();if(!C||C.rangeCount===0)return;let E=C.getRangeAt(0);if(E.deleteContents(),a.maxLength>0){let g=u.textContent?.length??0;(e.textContent?.length??0)+g>a.maxLength&&m("overflow",a.maxLength)}let x=u.lastChild;if(E.insertNode(u),x){let g=r.createRange();g.setStartAfter(x),g.collapse(!0),C.removeAllRanges(),C.addRange(g)}m("paste",e.innerHTML),d()}function N(){d()}function w(l){let s=r.getSelection();if(!s||s.rangeCount===0)return;let t=s.anchorNode;if(!t)return;let p=y(t,"PRE");if(l.key==="Enter"&&p){l.preventDefault();let u=s.getRangeAt(0);u.deleteContents();let C=r.createTextNode(`
2
+ `);u.insertNode(C),u.setStartAfter(C),u.collapse(!0),s.removeAllRanges(),s.addRange(u),d()}if(l.key==="Backspace"&&p){let u=p.textContent||"";if(s.anchorOffset===0&&(u===""||u===`
3
+ `)){l.preventDefault();let x=r.createElement("p");x.appendChild(r.createElement("br")),p.parentNode?.replaceChild(x,p);let g=r.createRange();g.selectNodeContents(x),g.collapse(!0),s.removeAllRanges(),s.addRange(g),d()}}}e.addEventListener("keydown",w),e.addEventListener("paste",f),e.addEventListener("input",N);function y(l,s){let t=l;for(;t&&t!==e;){if(t.nodeType===1&&t.tagName===s)return t;t=t.parentNode}return null}function T(l){let s=r.getSelection();if(!s||s.rangeCount===0)return!1;let t=s.anchorNode;if(!t)return!1;let p=y(t,l);if(!p)return!1;let u=y(t,"LI"),C=p.parentNode;if(!C)return!1;let E=[],x=null;for(let h of Array.from(p.childNodes)){if(h.nodeType!==1||h.tagName!=="LI")continue;let v=r.createElement("p");for(;h.firstChild;)v.appendChild(h.firstChild);v.firstChild||v.appendChild(r.createElement("br")),E.push(v),h===u&&(x=v)}for(let h of E)C.insertBefore(h,p);C.removeChild(p);let g=x??E[0];if(g){let h=r.createRange();h.selectNodeContents(g),h.collapse(!1),s.removeAllRanges(),s.addRange(h)}return d(),!0}function L(l,s){let t=l;for(;t&&t!==e;){if(t.nodeType===1&&t.tagName===s)return!0;t=t.parentNode}return!1}let O={exec(l,s){if(!F.has(l))throw new Error(`Unknown editor command: "${l}"`);switch(e.focus(),l){case"bold":r.execCommand("bold",!1);break;case"italic":r.execCommand("italic",!1);break;case"heading":{let t=s??"1";if(!["1","2","3"].includes(t))throw new Error(`Invalid heading level: "${t}". Use 1, 2, or 3`);let p=`H${t}`,u=r.getSelection()?.anchorNode;u&&O.queryState("heading")&&L(u,p)?r.execCommand("formatBlock",!1,"<p>"):r.execCommand("formatBlock",!1,`<h${t}>`);break}case"blockquote":O.queryState("blockquote")?r.execCommand("formatBlock",!1,"<p>"):r.execCommand("formatBlock",!1,"<blockquote>");break;case"unorderedList":T("UL")||r.execCommand("insertUnorderedList",!1);break;case"orderedList":T("OL")||r.execCommand("insertOrderedList",!1);break;case"link":{if(!s)throw new Error("Link command requires a URL value");let t=s.trim();if(!A(t,a.protocols)){m("error",new Error(`Protocol not allowed: ${t}`));return}r.execCommand("createLink",!1,t);break}case"unlink":r.execCommand("unlink",!1);break;case"codeBlock":{let t=r.getSelection();if(!t||t.rangeCount===0)break;let p=t.anchorNode,u=p?y(p,"PRE"):null;if(u){let C=r.createElement("p");C.textContent=u.textContent||"",u.parentNode?.replaceChild(C,u);let E=r.createRange();E.selectNodeContents(C),E.collapse(!1),t.removeAllRanges(),t.addRange(E)}else{let E=t.getRangeAt(0).startContainer;for(;E.parentNode&&E.parentNode!==e;)E=E.parentNode;let x=r.createElement("pre"),g=r.createElement("code"),h=E.textContent||"";g.textContent=h.endsWith(`
4
+ `)?h:h+`
5
+ `,x.appendChild(g),E.parentNode===e?e.replaceChild(x,E):e.appendChild(x);let v=r.createRange();v.selectNodeContents(g),v.collapse(!1),t.removeAllRanges(),t.addRange(v)}d();break}}},queryState(l){if(!F.has(l))throw new Error(`Unknown editor command: "${l}"`);let s=r.getSelection();if(!s||s.rangeCount===0)return!1;let t=s.anchorNode;if(!t||!e.contains(t))return!1;switch(l){case"bold":return L(t,"STRONG")||L(t,"B");case"italic":return L(t,"EM")||L(t,"I");case"heading":return L(t,"H1")||L(t,"H2")||L(t,"H3");case"blockquote":return L(t,"BLOCKQUOTE");case"unorderedList":return L(t,"UL");case"orderedList":return L(t,"OL");case"link":return L(t,"A");case"unlink":return!1;case"codeBlock":return L(t,"PRE");default:return!1}},getHTML(){return e.innerHTML},getText(){return e.textContent??""},destroy(){e.removeEventListener("keydown",w),e.removeEventListener("paste",f),e.removeEventListener("input",N),c.destroy(),e.contentEditable="false"},on(l,s){i[l]||(i[l]=[]),i[l].push(s)}};return O}function J(e,n={}){let o=n.policy??b,a=n.onChange;e.replaceChildren(k(n.value??n.initialHTML??"",o));let i=B(e,{policy:o,onChange:r=>a?.(r)});return n.onReady?.(i),{update(r){a=r.onChange,r.value!==void 0&&i.getHTML()!==r.value&&e.replaceChildren(k(r.value,o))},destroy(){i.destroy()}}}
6
+ //# sourceMappingURL=svelte.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/adapters/svelte.ts", "../../src/defaults.ts", "../../src/shared.ts", "../../src/sanitize.ts", "../../src/policy.ts", "../../src/editor.ts"],
4
+ "sourcesContent": ["import { createEditor } from '../editor';\nimport { sanitizeToFragment } from '../sanitize';\nimport { DEFAULT_POLICY } from '../defaults';\nimport type { Editor, SanitizePolicy } from '../types';\n\nexport type { Editor, SanitizePolicy } from '../types';\n\nexport interface MinisiwygParams {\n initialHTML?: string;\n value?: string;\n policy?: SanitizePolicy;\n onChange?: (html: string) => void;\n onReady?: (editor: Editor) => void;\n}\n\nexport interface MinisiwygAction {\n update(params: MinisiwygParams): void;\n destroy(): void;\n}\n\nexport function minisiwyg(\n node: HTMLElement,\n params: MinisiwygParams = {},\n): MinisiwygAction {\n const effectivePolicy: SanitizePolicy = params.policy ?? DEFAULT_POLICY;\n let onChange = params.onChange;\n\n node.replaceChildren(\n sanitizeToFragment(params.value ?? params.initialHTML ?? '', effectivePolicy),\n );\n const editor = createEditor(node, {\n policy: effectivePolicy,\n onChange: (html) => onChange?.(html),\n });\n params.onReady?.(editor);\n\n return {\n update(next: MinisiwygParams) {\n onChange = next.onChange;\n if (next.value !== undefined && editor.getHTML() !== next.value) {\n node.replaceChildren(sanitizeToFragment(next.value, effectivePolicy));\n }\n },\n destroy() {\n editor.destroy();\n },\n };\n}\n", "import type { SanitizePolicy } from './types';\n\nconst policy: SanitizePolicy = {\n tags: {\n p: [],\n br: [],\n strong: [],\n em: [],\n a: ['href', 'title', 'target'],\n h1: [],\n h2: [],\n h3: [],\n ul: [],\n ol: [],\n li: [],\n blockquote: [],\n pre: [],\n code: [],\n },\n strip: true,\n maxDepth: 10,\n maxLength: 100_000,\n protocols: ['https', 'http', 'mailto'],\n};\n\n// Deep freeze to prevent mutation of security-critical defaults\nObject.freeze(policy);\nObject.freeze(policy.protocols);\nfor (const attrs of Object.values(policy.tags)) Object.freeze(attrs);\nObject.freeze(policy.tags);\n\nexport const DEFAULT_POLICY: Readonly<SanitizePolicy> = policy;\n", "/** Tag normalization map: browser-variant tags \u2192 semantic equivalents. */\nexport const TAG_NORMALIZE: Record<string, string> = {\n b: 'strong',\n i: 'em',\n};\n\n/** Attributes that contain URLs and need protocol validation. */\nexport const URL_ATTRS = new Set(['href', 'src', 'action', 'formaction']);\n\n/** Protocols that are always denied regardless of policy. */\nexport const DENIED_PROTOCOLS = new Set(['javascript', 'data']);\n\n/**\n * Parse a URL-like string and extract the protocol.\n * Returns the lowercase protocol name (without colon), or null if none found.\n */\nexport function extractProtocol(value: string): string | null {\n let decoded = value.trim();\n decoded = decoded.replace(/&#x([0-9a-f]+);?/gi, (_, hex) =>\n String.fromCharCode(parseInt(hex, 16)),\n );\n decoded = decoded.replace(/&#(\\d+);?/g, (_, dec) =>\n String.fromCharCode(parseInt(dec, 10)),\n );\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n // keep entity-decoded result\n }\n decoded = decoded.replace(/[\\s\\x00-\\x1f\\u00A0\\u1680\\u2000-\\u200B\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]+/g, '');\n const match = decoded.match(/^([a-z][a-z0-9+\\-.]*)\\s*:/i);\n return match ? match[1].toLowerCase() : null;\n}\n\n/**\n * Check if a URL value is allowed by the given protocol list.\n * javascript: and data: are always denied.\n */\nexport function isProtocolAllowed(value: string, allowedProtocols: string[]): boolean {\n const protocol = extractProtocol(value);\n if (protocol === null) return true;\n if (DENIED_PROTOCOLS.has(protocol)) return false;\n return allowedProtocols.includes(protocol);\n}\n", "import type { SanitizePolicy } from './types';\nimport { TAG_NORMALIZE, URL_ATTRS, isProtocolAllowed } from './shared';\nexport { DEFAULT_POLICY } from './defaults';\nexport type { SanitizePolicy } from './types';\n\n/**\n * Walk a DOM tree depth-first and sanitize according to policy.\n * Mutates the tree in place.\n */\nfunction walkAndSanitize(\n parent: Node,\n policy: SanitizePolicy,\n depth: number,\n): void {\n const children = Array.from(parent.childNodes);\n\n for (const node of children) {\n // Text nodes: always allowed (length enforcement happens after walk)\n if (node.nodeType === 3) continue;\n\n // Non-element, non-text nodes (comments, processing instructions): remove\n if (node.nodeType !== 1) {\n parent.removeChild(node);\n continue;\n }\n\n const el = node as Element;\n let tagName = el.tagName.toLowerCase();\n\n // Normalize tags (b\u2192strong, i\u2192em)\n const normalized = TAG_NORMALIZE[tagName];\n if (normalized) {\n tagName = normalized;\n }\n\n // Check depth limit\n if (depth >= policy.maxDepth) {\n parent.removeChild(el);\n continue;\n }\n\n // Check tag whitelist\n const allowedAttrs = policy.tags[tagName];\n if (allowedAttrs === undefined) {\n // Tag not allowed\n if (policy.strip) {\n // Remove the node and all its children\n parent.removeChild(el);\n } else {\n // Unwrap: sanitize children first, then move them up\n walkAndSanitize(el, policy, depth);\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n continue;\n }\n\n // Tag is allowed. If it was normalized, replace with correct element.\n let current: Element = el;\n if (normalized && el.tagName.toLowerCase() !== normalized) {\n const doc = el.ownerDocument!;\n const replacement = doc.createElement(normalized);\n while (el.firstChild) {\n replacement.appendChild(el.firstChild);\n }\n parent.replaceChild(replacement, el);\n current = replacement;\n }\n\n // Strip disallowed attributes\n const attrs = Array.from(current.attributes);\n for (const attr of attrs) {\n const attrName = attr.name.toLowerCase();\n\n // Always strip event handlers (on*)\n if (attrName.startsWith('on')) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n // Check attribute whitelist\n if (!allowedAttrs.includes(attrName)) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n // Validate URL protocols on URL-bearing attributes\n if (URL_ATTRS.has(attrName)) {\n if (!isProtocolAllowed(attr.value, policy.protocols)) {\n current.removeAttribute(attr.name);\n }\n }\n }\n\n // Recurse into children\n walkAndSanitize(current, policy, depth + 1);\n }\n}\n\n/**\n * Sanitize an HTML string and return a DocumentFragment.\n * Avoids the serialize\u2192reparse round-trip that can cause mXSS.\n */\nexport function sanitizeToFragment(html: string, policy: SanitizePolicy): DocumentFragment {\n const template = document.createElement('template');\n if (!html) return template.content;\n\n template.innerHTML = html;\n const fragment = template.content;\n\n walkAndSanitize(fragment, policy, 0);\n\n if (policy.maxLength > 0 && (fragment.textContent?.length ?? 0) > policy.maxLength) {\n truncateToLength(fragment, policy.maxLength);\n }\n\n return fragment;\n}\n\n/**\n * Sanitize an HTML string according to the given policy.\n *\n * Uses a <template> element to parse HTML without executing scripts.\n * Walks the resulting DOM tree depth-first, removing disallowed elements\n * and attributes. Returns the sanitized HTML string.\n */\nexport function sanitize(html: string, policy: SanitizePolicy): string {\n if (!html) return '';\n\n const fragment = sanitizeToFragment(html, policy);\n const container = document.createElement('div');\n container.appendChild(fragment);\n return container.innerHTML;\n}\n\n/**\n * Truncate a DOM tree's text content to a maximum length.\n * Removes nodes beyond the limit while preserving structure.\n */\nfunction truncateToLength(node: Node, maxLength: number): number {\n let remaining = maxLength;\n\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (remaining <= 0) {\n node.removeChild(child);\n continue;\n }\n\n if (child.nodeType === 3) {\n // Text node\n const text = child.textContent ?? '';\n if (text.length > remaining) {\n child.textContent = text.slice(0, remaining);\n remaining = 0;\n } else {\n remaining -= text.length;\n }\n } else if (child.nodeType === 1) {\n remaining = truncateToLength(child, remaining);\n } else {\n node.removeChild(child);\n }\n }\n\n return remaining;\n}\n", "import type { SanitizePolicy } from './types';\nimport { TAG_NORMALIZE, URL_ATTRS, isProtocolAllowed } from './shared';\n\nexport { DEFAULT_POLICY } from './defaults';\nexport type { SanitizePolicy } from './types';\n\nexport interface PolicyEnforcer {\n destroy(): void;\n on(event: 'error', handler: (error: Error) => void): void;\n}\n\n/**\n * Get the nesting depth of a node within a root element.\n */\nfunction getDepth(node: Node, root: Node): number {\n let depth = 0;\n let current = node.parentNode;\n while (current && current !== root) {\n if (current.nodeType === 1) depth++;\n current = current.parentNode;\n }\n return depth;\n}\n\n/**\n * Check if an element is allowed by the policy and fix it if not.\n * Returns true if the node was removed/replaced.\n */\nfunction enforceElement(\n el: Element,\n policy: SanitizePolicy,\n root: HTMLElement,\n): boolean {\n let tagName = el.tagName.toLowerCase();\n const normalized = TAG_NORMALIZE[tagName];\n if (normalized) tagName = normalized;\n\n // Check depth\n const depth = getDepth(el, root);\n if (depth >= policy.maxDepth) {\n el.parentNode?.removeChild(el);\n return true;\n }\n\n // Check tag whitelist\n const allowedAttrs = policy.tags[tagName];\n if (allowedAttrs === undefined) {\n if (policy.strip) {\n el.parentNode?.removeChild(el);\n } else {\n // Unwrap: move children up, then remove the element\n const parent = el.parentNode;\n if (parent) {\n while (el.firstChild) {\n parent.insertBefore(el.firstChild, el);\n }\n parent.removeChild(el);\n }\n }\n return true;\n }\n\n // Normalize tag if needed (e.g. <b> \u2192 <strong>)\n let current: Element = el;\n if (normalized && el.tagName.toLowerCase() !== normalized) {\n const replacement = el.ownerDocument.createElement(normalized);\n while (el.firstChild) {\n replacement.appendChild(el.firstChild);\n }\n // Copy allowed attributes\n for (const attr of Array.from(el.attributes)) {\n replacement.setAttribute(attr.name, attr.value);\n }\n el.parentNode?.replaceChild(replacement, el);\n current = replacement;\n }\n\n // Strip disallowed attributes\n for (const attr of Array.from(current.attributes)) {\n const attrName = attr.name.toLowerCase();\n\n if (attrName.startsWith('on')) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n if (!allowedAttrs.includes(attrName)) {\n current.removeAttribute(attr.name);\n continue;\n }\n\n if (URL_ATTRS.has(attrName)) {\n if (!isProtocolAllowed(attr.value, policy.protocols)) {\n current.removeAttribute(attr.name);\n }\n }\n }\n\n return false;\n}\n\n/**\n * Recursively enforce policy on all descendants of a node.\n */\nfunction enforceSubtree(node: Node, policy: SanitizePolicy, root: HTMLElement): void {\n const children = Array.from(node.childNodes);\n for (const child of children) {\n if (child.nodeType !== 1) {\n // Remove non-text, non-element nodes (comments, etc.)\n if (child.nodeType !== 3) {\n node.removeChild(child);\n }\n continue;\n }\n const removed = enforceElement(child as Element, policy, root);\n if (!removed) {\n enforceSubtree(child, policy, root);\n }\n }\n}\n\n/**\n * Create a policy enforcer that uses MutationObserver to enforce\n * the sanitization policy on a live DOM element.\n *\n * This is defense-in-depth \u2014 the paste handler is the primary security boundary.\n * The observer catches mutations from execCommand, programmatic DOM manipulation,\n * and other sources.\n */\nexport function createPolicyEnforcer(\n element: HTMLElement,\n policy: SanitizePolicy,\n): PolicyEnforcer {\n if (!policy || !policy.tags) {\n throw new TypeError('Policy must have a \"tags\" property');\n }\n\n let isApplyingFix = false;\n const errorHandlers: Array<(error: Error) => void> = [];\n\n function emitError(error: Error): void {\n for (const handler of errorHandlers) {\n handler(error);\n }\n }\n\n const observer = new MutationObserver((mutations) => {\n if (isApplyingFix) return;\n isApplyingFix = true;\n\n try {\n for (const mutation of mutations) {\n if (mutation.type === 'childList') {\n for (const node of Array.from(mutation.addedNodes)) {\n // Skip text nodes\n if (node.nodeType === 3) continue;\n\n // Remove non-element nodes\n if (node.nodeType !== 1) {\n node.parentNode?.removeChild(node);\n continue;\n }\n\n const removed = enforceElement(node as Element, policy, element);\n if (!removed) {\n // Also enforce on all descendants of the added node\n enforceSubtree(node, policy, element);\n }\n }\n } else if (mutation.type === 'attributes') {\n const target = mutation.target as Element;\n if (target.nodeType !== 1) continue;\n\n const attrName = mutation.attributeName;\n if (!attrName) continue;\n\n const tagName = target.tagName.toLowerCase();\n const normalizedTag = TAG_NORMALIZE[tagName] || tagName;\n const allowedAttrs = policy.tags[normalizedTag];\n\n if (!allowedAttrs) continue;\n\n const lowerAttr = attrName.toLowerCase();\n\n if (lowerAttr.startsWith('on')) {\n target.removeAttribute(attrName);\n continue;\n }\n\n if (!allowedAttrs.includes(lowerAttr)) {\n target.removeAttribute(attrName);\n continue;\n }\n\n if (URL_ATTRS.has(lowerAttr)) {\n const value = target.getAttribute(attrName);\n if (value && !isProtocolAllowed(value, policy.protocols)) {\n target.removeAttribute(attrName);\n }\n }\n }\n }\n } catch (err) {\n emitError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n isApplyingFix = false;\n }\n });\n\n observer.observe(element, {\n childList: true,\n attributes: true,\n subtree: true,\n });\n\n return {\n destroy() {\n observer.disconnect();\n },\n on(event: 'error', handler: (error: Error) => void) {\n if (event === 'error') {\n errorHandlers.push(handler);\n }\n },\n };\n}\n", "import type { SanitizePolicy, EditorOptions, Editor } from './types';\nimport { DEFAULT_POLICY } from './defaults';\nimport { sanitizeToFragment } from './sanitize';\nimport { createPolicyEnforcer, type PolicyEnforcer } from './policy';\nimport { isProtocolAllowed } from './shared';\n\nexport type { Editor, EditorOptions } from './types';\nexport { DEFAULT_POLICY } from './defaults';\n\ntype EditorEvent = 'change' | 'paste' | 'overflow' | 'error';\ntype EventHandler = (...args: unknown[]) => void;\n\nconst SUPPORTED_COMMANDS = new Set([\n 'bold',\n 'italic',\n 'heading',\n 'blockquote',\n 'unorderedList',\n 'orderedList',\n 'link',\n 'unlink',\n 'codeBlock',\n]);\n\n/**\n * Create a contentEditable-based editor with built-in sanitization.\n *\n * The paste handler is the primary security boundary \u2014 it sanitizes HTML\n * before insertion via Selection/Range API. The MutationObserver-based\n * policy enforcer provides defense-in-depth.\n */\nexport function createEditor(\n element: HTMLElement,\n options?: EditorOptions,\n): Editor {\n if (!element) {\n throw new TypeError('createEditor requires an HTMLElement');\n }\n if (!element.ownerDocument || !element.parentNode) {\n throw new TypeError('createEditor requires an element attached to the DOM');\n }\n\n const src = options?.policy ?? DEFAULT_POLICY;\n const policy: SanitizePolicy = {\n tags: Object.fromEntries(\n Object.entries(src.tags).map(([k, v]) => [k, [...v]]),\n ),\n strip: src.strip,\n maxDepth: src.maxDepth,\n maxLength: src.maxLength,\n protocols: [...src.protocols],\n };\n\n const handlers: Record<string, EventHandler[]> = {};\n const doc = element.ownerDocument;\n\n function emit(event: EditorEvent, ...args: unknown[]): void {\n for (const handler of handlers[event] ?? []) {\n handler(...args);\n }\n }\n\n // Notify both subscription paths (editor.on('change') and options.onChange)\n // for programmatic edits that don't fire 'input' (codeBlock toggle, list unwrap).\n function emitChange(): void {\n const html = element.innerHTML;\n emit('change', html);\n options?.onChange?.(html);\n }\n\n // Set up contentEditable\n element.contentEditable = 'true';\n\n // Attach policy enforcer (MutationObserver defense-in-depth)\n const enforcer: PolicyEnforcer = createPolicyEnforcer(element, policy);\n enforcer.on('error', (err) => emit('error', err));\n\n // Paste handler \u2014 the primary security boundary\n function onPaste(e: ClipboardEvent): void {\n e.preventDefault();\n\n const clipboard = e.clipboardData;\n if (!clipboard) return;\n\n // Inside code block: paste as plain text only\n const sel = doc.getSelection();\n if (sel && sel.rangeCount > 0 && sel.anchorNode) {\n const pre = findAncestor(sel.anchorNode, 'PRE');\n if (pre) {\n const text = clipboard.getData('text/plain');\n if (!text) return;\n if (policy.maxLength > 0) {\n const currentLen = element.textContent?.length ?? 0;\n if (currentLen + text.length > policy.maxLength) {\n emit('overflow', policy.maxLength);\n }\n }\n const range = sel.getRangeAt(0);\n range.deleteContents();\n const textNode = doc.createTextNode(text);\n range.insertNode(textNode);\n range.setStartAfter(textNode);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emit('paste', element.innerHTML);\n emitChange();\n return;\n }\n }\n\n // Prefer HTML, fall back to plain text\n let html = clipboard.getData('text/html');\n if (!html) {\n const text = clipboard.getData('text/plain');\n if (!text) return;\n // Escape plain text and convert newlines to <br>\n html = text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n .replace(/\\n/g, '<br>');\n }\n\n // Sanitize through policy \u2014 returns DocumentFragment directly\n // to avoid the serialize\u2192reparse mXSS vector\n const fragment = sanitizeToFragment(html, policy);\n\n // Insert via Selection/Range API (NOT execCommand('insertHTML'))\n const selection = doc.getSelection();\n if (!selection || selection.rangeCount === 0) return;\n\n const range = selection.getRangeAt(0);\n range.deleteContents();\n\n // Check overflow using text content length\n if (policy.maxLength > 0) {\n const pasteTextLen = fragment.textContent?.length ?? 0;\n const currentLen = element.textContent?.length ?? 0;\n if (currentLen + pasteTextLen > policy.maxLength) {\n emit('overflow', policy.maxLength);\n }\n }\n\n // Remember last inserted node for cursor positioning\n let lastNode: Node | null = fragment.lastChild;\n range.insertNode(fragment);\n\n // Move cursor after inserted content\n if (lastNode) {\n const newRange = doc.createRange();\n newRange.setStartAfter(lastNode);\n newRange.collapse(true);\n selection.removeAllRanges();\n selection.addRange(newRange);\n }\n\n emit('paste', element.innerHTML);\n emitChange();\n }\n\n // Input handler for change events\n function onInput(): void {\n emitChange();\n }\n\n // Keydown handler for code block behavior\n function onKeydown(e: KeyboardEvent): void {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const anchor = sel.anchorNode;\n if (!anchor) return;\n\n const pre = findAncestor(anchor, 'PRE');\n\n if (e.key === 'Enter' && pre) {\n // Insert newline instead of new paragraph\n e.preventDefault();\n const range = sel.getRangeAt(0);\n range.deleteContents();\n const textNode = doc.createTextNode('\\n');\n range.insertNode(textNode);\n range.setStartAfter(textNode);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emitChange();\n }\n\n if (e.key === 'Backspace' && pre) {\n // At start of empty pre, convert to <p>\n const text = pre.textContent || '';\n const isAtStart = sel.anchorOffset === 0;\n const isEmpty = text === '' || text === '\\n';\n if (isAtStart && isEmpty) {\n e.preventDefault();\n const p = doc.createElement('p');\n p.appendChild(doc.createElement('br'));\n pre.parentNode?.replaceChild(p, pre);\n const range = doc.createRange();\n range.selectNodeContents(p);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n emitChange();\n }\n }\n }\n\n element.addEventListener('keydown', onKeydown);\n element.addEventListener('paste', onPaste);\n element.addEventListener('input', onInput);\n\n function findAncestor(node: Node, tagName: string): Element | null {\n let current: Node | null = node;\n while (current && current !== element) {\n if (current.nodeType === 1 && (current as Element).tagName === tagName) return current as Element;\n current = current.parentNode;\n }\n return null;\n }\n\n /**\n * Manually unwrap a list the selection is inside of, replacing each\n * <li> with a <p>. Returns true if an unwrap happened.\n *\n * We do this instead of relying on execCommand toggle-off because\n * browsers unwrap list items into <div> wrappers, which the policy\n * enforcer then strips (losing content).\n */\n function unwrapList(tag: 'UL' | 'OL'): boolean {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return false;\n const anchor = sel.anchorNode;\n if (!anchor) return false;\n const list = findAncestor(anchor, tag);\n if (!list) return false;\n\n const anchorLi = findAncestor(anchor, 'LI');\n const parent = list.parentNode;\n if (!parent) return false;\n\n const paragraphs: HTMLParagraphElement[] = [];\n let focusTarget: HTMLParagraphElement | null = null;\n\n for (const child of Array.from(list.childNodes)) {\n if (child.nodeType !== 1 || (child as Element).tagName !== 'LI') continue;\n const p = doc.createElement('p');\n while (child.firstChild) p.appendChild(child.firstChild);\n if (!p.firstChild) p.appendChild(doc.createElement('br'));\n paragraphs.push(p);\n if (child === anchorLi) focusTarget = p;\n }\n\n for (const p of paragraphs) parent.insertBefore(p, list);\n parent.removeChild(list);\n\n const target = focusTarget ?? paragraphs[0];\n if (target) {\n const r = doc.createRange();\n r.selectNodeContents(target);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n emitChange();\n return true;\n }\n\n function hasAncestor(node: Node, tagName: string): boolean {\n let current: Node | null = node;\n while (current && current !== element) {\n if (current.nodeType === 1 && (current as Element).tagName === tagName) return true;\n current = current.parentNode;\n }\n return false;\n }\n\n const editor: Editor = {\n exec(command: string, value?: string): void {\n if (!SUPPORTED_COMMANDS.has(command)) {\n throw new Error(`Unknown editor command: \"${command}\"`);\n }\n\n element.focus();\n\n switch (command) {\n case 'bold':\n doc.execCommand('bold', false);\n break;\n case 'italic':\n doc.execCommand('italic', false);\n break;\n case 'heading': {\n const level = value ?? '1';\n if (!['1', '2', '3'].includes(level)) {\n throw new Error(`Invalid heading level: \"${level}\". Use 1, 2, or 3`);\n }\n const tag = `H${level}`;\n const anchor = doc.getSelection()?.anchorNode;\n if (anchor && editor.queryState('heading') && hasAncestor(anchor, tag)) {\n doc.execCommand('formatBlock', false, '<p>');\n } else {\n doc.execCommand('formatBlock', false, `<h${level}>`);\n }\n break;\n }\n case 'blockquote':\n if (editor.queryState('blockquote')) {\n doc.execCommand('formatBlock', false, '<p>');\n } else {\n doc.execCommand('formatBlock', false, '<blockquote>');\n }\n break;\n case 'unorderedList':\n if (!unwrapList('UL')) doc.execCommand('insertUnorderedList', false);\n break;\n case 'orderedList':\n if (!unwrapList('OL')) doc.execCommand('insertOrderedList', false);\n break;\n case 'link': {\n if (!value) {\n throw new Error('Link command requires a URL value');\n }\n const trimmed = value.trim();\n if (!isProtocolAllowed(trimmed, policy.protocols)) {\n emit('error', new Error(`Protocol not allowed: ${trimmed}`));\n return;\n }\n doc.execCommand('createLink', false, trimmed);\n break;\n }\n case 'unlink':\n doc.execCommand('unlink', false);\n break;\n case 'codeBlock': {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) break;\n const anchor = sel.anchorNode;\n const pre = anchor ? findAncestor(anchor, 'PRE') : null;\n if (pre) {\n // Toggle off: unwrap <pre><code> to <p>\n const p = doc.createElement('p');\n p.textContent = pre.textContent || '';\n pre.parentNode?.replaceChild(p, pre);\n const r = doc.createRange();\n r.selectNodeContents(p);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n } else {\n // Wrap current block in <pre><code>\n const range = sel.getRangeAt(0);\n let block = range.startContainer;\n while (block.parentNode && block.parentNode !== element) {\n block = block.parentNode;\n }\n const pre2 = doc.createElement('pre');\n const code = doc.createElement('code');\n const blockText = block.textContent || '';\n code.textContent = blockText.endsWith('\\n') ? blockText : blockText + '\\n';\n pre2.appendChild(code);\n if (block.parentNode === element) {\n element.replaceChild(pre2, block);\n } else {\n element.appendChild(pre2);\n }\n const r = doc.createRange();\n r.selectNodeContents(code);\n r.collapse(false);\n sel.removeAllRanges();\n sel.addRange(r);\n }\n emitChange();\n break;\n }\n }\n },\n\n queryState(command: string): boolean {\n if (!SUPPORTED_COMMANDS.has(command)) {\n throw new Error(`Unknown editor command: \"${command}\"`);\n }\n\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return false;\n\n const node = sel.anchorNode;\n if (!node || !element.contains(node)) return false;\n\n switch (command) {\n case 'bold':\n return hasAncestor(node, 'STRONG') || hasAncestor(node, 'B');\n case 'italic':\n return hasAncestor(node, 'EM') || hasAncestor(node, 'I');\n case 'heading':\n return hasAncestor(node, 'H1') || hasAncestor(node, 'H2') || hasAncestor(node, 'H3');\n case 'blockquote':\n return hasAncestor(node, 'BLOCKQUOTE');\n case 'unorderedList':\n return hasAncestor(node, 'UL');\n case 'orderedList':\n return hasAncestor(node, 'OL');\n case 'link':\n return hasAncestor(node, 'A');\n case 'unlink':\n return false;\n case 'codeBlock':\n return hasAncestor(node, 'PRE');\n default:\n return false;\n }\n },\n\n getHTML(): string {\n return element.innerHTML;\n },\n\n getText(): string {\n return element.textContent ?? '';\n },\n\n destroy(): void {\n element.removeEventListener('keydown', onKeydown);\n element.removeEventListener('paste', onPaste);\n element.removeEventListener('input', onInput);\n enforcer.destroy();\n element.contentEditable = 'false';\n },\n\n on(event: string, handler: EventHandler): void {\n if (!handlers[event]) handlers[event] = [];\n handlers[event].push(handler);\n },\n };\n\n return editor;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["svelte_exports", "__export", "minisiwyg", "__toCommonJS", "policy", "attrs", "DEFAULT_POLICY", "TAG_NORMALIZE", "URL_ATTRS", "DENIED_PROTOCOLS", "extractProtocol", "value", "decoded", "_", "hex", "dec", "match", "isProtocolAllowed", "allowedProtocols", "protocol", "walkAndSanitize", "parent", "policy", "depth", "children", "node", "el", "tagName", "normalized", "TAG_NORMALIZE", "allowedAttrs", "current", "replacement", "attrs", "attr", "attrName", "URL_ATTRS", "isProtocolAllowed", "sanitizeToFragment", "html", "template", "fragment", "truncateToLength", "truncateToLength", "node", "maxLength", "remaining", "children", "child", "text", "getDepth", "node", "root", "depth", "current", "enforceElement", "el", "policy", "tagName", "normalized", "TAG_NORMALIZE", "allowedAttrs", "parent", "replacement", "attr", "attrName", "URL_ATTRS", "isProtocolAllowed", "enforceSubtree", "children", "child", "createPolicyEnforcer", "element", "isApplyingFix", "errorHandlers", "emitError", "error", "handler", "observer", "mutations", "mutation", "target", "normalizedTag", "lowerAttr", "value", "err", "event", "SUPPORTED_COMMANDS", "createEditor", "element", "options", "src", "DEFAULT_POLICY", "policy", "k", "v", "handlers", "doc", "emit", "event", "args", "handler", "emitChange", "html", "enforcer", "createPolicyEnforcer", "err", "onPaste", "e", "clipboard", "sel", "findAncestor", "text", "range", "textNode", "fragment", "sanitizeToFragment", "selection", "pasteTextLen", "lastNode", "newRange", "onInput", "onKeydown", "anchor", "pre", "p", "node", "tagName", "current", "unwrapList", "tag", "list", "anchorLi", "parent", "paragraphs", "focusTarget", "child", "target", "r", "hasAncestor", "editor", "command", "value", "level", "trimmed", "isProtocolAllowed", "block", "pre2", "code", "blockText", "minisiwyg", "node", "params", "effectivePolicy", "DEFAULT_POLICY", "onChange", "sanitizeToFragment", "editor", "createEditor", "html", "next"]
7
+ }
@@ -0,0 +1,14 @@
1
+ import type { Editor, SanitizePolicy } from '../types';
2
+ export type { Editor, SanitizePolicy } from '../types';
3
+ export interface MinisiwygParams {
4
+ initialHTML?: string;
5
+ value?: string;
6
+ policy?: SanitizePolicy;
7
+ onChange?: (html: string) => void;
8
+ onReady?: (editor: Editor) => void;
9
+ }
10
+ export interface MinisiwygAction {
11
+ update(params: MinisiwygParams): void;
12
+ destroy(): void;
13
+ }
14
+ export declare function minisiwyg(node: HTMLElement, params?: MinisiwygParams): MinisiwygAction;