rich-html-editor 1.2.2 → 1.2.4

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
@@ -1,6 +1,13 @@
1
1
  # rich-html-editor
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/rich-html-editor.svg)](https://www.npmjs.com/package/rich-html-editor) [![license](https://img.shields.io/npm/l/rich-html-editor.svg)](LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/rich-html-editor?logo=npm)](https://www.npmjs.com/package/rich-html-editor)
4
+ [![license](https://img.shields.io/npm/l/rich-html-editor?logo=open-source-initiative)](LICENSE)
5
+ [![downloads](https://img.shields.io/npm/dm/rich-html-editor?logo=npm)](https://www.npmjs.com/package/rich-html-editor)
6
+ [![Playground](https://img.shields.io/badge/demo-live_playground-blue?logo=google-chrome)](https://akshaypatil1.github.io/rich-html-editor/)
7
+ [![Documentation](https://img.shields.io/badge/docs-documentation-success?logo=read-the-docs)](https://akshaypatil1.github.io/rich-html-editor/docs/)
8
+ ![TypeScript](https://img.shields.io/badge/types-TypeScript-blue?logo=typescript)
9
+ ![Framework Agnostic](https://img.shields.io/badge/framework-agnostic-green)
10
+ ![Bundle Size](https://img.shields.io/bundlephobia/minzip/rich-html-editor)
4
11
 
5
12
  > **Edit HTML templates safely — without breaking layout or CSS.**
6
13
 
@@ -9,7 +16,23 @@ It enables **controlled, template-driven editing**, not free-form WYSIWYG chaos.
9
16
 
10
17
  ---
11
18
 
12
- ## 🎥 Demo
19
+ ## 🎥 Demo & Playground
20
+
21
+ ### ▶️ Live Playground (Try it yourself)
22
+
23
+ 👉 **https://akshaypatil1.github.io/rich-html-editor/**
24
+
25
+ Use the playground to:
26
+
27
+ - Edit real HTML inside an iframe
28
+ - Apply formatting safely
29
+ - Export clean HTML instantly
30
+
31
+ > No install. No build. Runs directly in the browser.
32
+
33
+ ---
34
+
35
+ ### 📽️ Video Demo
13
36
 
14
37
  Below is a short demo showing how **rich-html-editor** allows inline editing of an HTML template using a toolbar embedded inside an iframe.
15
38
 
@@ -33,6 +56,14 @@ Most rich text editors allow users to edit _anything_ — which often leads to b
33
56
 
34
57
  ---
35
58
 
59
+ ## Documentation
60
+
61
+ Explore guides, concepts, API reference, and examples:
62
+
63
+ 👉 **https://akshaypatil1.github.io/rich-html-editor/docs/**
64
+
65
+ ---
66
+
36
67
  ## 🚀 Features
37
68
 
38
69
  ### ✏️ Text Formatting
@@ -85,36 +116,39 @@ yarn add rich-html-editor
85
116
 
86
117
  ---
87
118
 
88
- ## Quick Start (Browser / iframe)
119
+ ## Quick Start (Browser / iframe)
89
120
 
90
121
  The editor initializes on an `HTMLIFrameElement`.
91
122
 
92
123
  > ⚠️ The iframe must be **same-origin**. Use `srcdoc` for safety.
93
124
 
94
125
  ```html
95
- <script type="module">
96
- import {
97
- initRichEditor,
98
- getCleanHTML,
99
- editorEventEmitter,
100
- } from "rich-html-editor";
101
-
102
- const iframe = document.createElement("iframe");
103
- iframe.srcdoc =
104
- "<!doctype html><html><head></head><body><div>Edit me</div></body></html>";
105
-
106
- document.body.appendChild(iframe);
107
-
108
- initRichEditor(iframe, { maxStackSize: 50 });
109
-
110
- const off = editorEventEmitter.on("contentChanged", (event) => {
111
- console.log("Content changed:", event);
126
+ <!-- CDN (browser) -->
127
+ <script src="https://unpkg.com/rich-html-editor@latest"></script>
128
+
129
+ <iframe id="frame"></iframe>
130
+
131
+ <script>
132
+ const iframe = document.getElementById("frame");
133
+
134
+ iframe.srcdoc = `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><div>Edit me</div></body></html>`;
135
+
136
+ const EditorClass =
137
+ (window.RichHtmlEditor &&
138
+ (window.RichHtmlEditor.RichHtmlEditor || window.RichHtmlEditor)) ||
139
+ undefined;
140
+ let editor;
141
+
142
+ iframe.addEventListener("load", () => {
143
+ if (!EditorClass) {
144
+ console.warn(
145
+ "RichHtmlEditor not found on window. Did you load the CDN script?"
146
+ );
147
+ return;
148
+ }
149
+ editor = new EditorClass({ iframe, highlightEditable: true });
150
+ editor.init();
112
151
  });
113
-
114
- const html = getCleanHTML();
115
- console.log(html);
116
-
117
- // off(); // unsubscribe when needed
118
152
  </script>
119
153
  ```
120
154
 
@@ -490,4 +490,4 @@ export {
490
490
  pushStandaloneSnapshot,
491
491
  setMaxStackSize
492
492
  };
493
- //# sourceMappingURL=chunk-GJUQLM52.mjs.map
493
+ //# sourceMappingURL=chunk-RTJONDEC.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/events.ts","../src/core/constants.ts","../src/utils/sanitize.ts","../src/core/state.ts"],"sourcesContent":["import type { EditorEventType, EditorEvent, EditorEventHandler } from \"./types\";\n\nexport class EditorEventEmitter {\n private listeners: Map<EditorEventType, Set<EditorEventHandler>> = new Map();\n\n on(type: EditorEventType, handler: EditorEventHandler): () => void {\n if (!this.listeners.has(type)) {\n this.listeners.set(type, new Set());\n }\n this.listeners.get(type)!.add(handler);\n return () => this.off(type, handler);\n }\n\n once(type: EditorEventType, handler: EditorEventHandler): void {\n const unsubscribe = this.on(type, (event) => {\n handler(event);\n unsubscribe();\n });\n }\n\n off(type: EditorEventType, handler: EditorEventHandler): void {\n const handlers = this.listeners.get(type);\n if (handlers) {\n handlers.delete(handler);\n if (handlers.size === 0) this.listeners.delete(type);\n }\n }\n\n emit(event: EditorEvent): void {\n const handlers = this.listeners.get(event.type);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(event);\n } catch (error) {\n console.error(\n `[rich-html-editor] Error in event handler for ${event.type}:`,\n error,\n );\n }\n });\n }\n }\n\n removeAllListeners(type?: EditorEventType): void {\n if (type) this.listeners.delete(type);\n else this.listeners.clear();\n }\n\n listenerCount(type: EditorEventType): number {\n return this.listeners.get(type)?.size ?? 0;\n }\n}\n\nexport const editorEventEmitter = new EditorEventEmitter();\n\nexport function getEditorEventEmitter(): EditorEventEmitter {\n return editorEventEmitter;\n}\n","// Shared constants for the rich-html-editor (moved to core)\nexport const TOOLBAR_ID = \"editor-toolbar\";\nexport const STYLE_ID = \"editor-styles\";\nexport const CLASS_EDITABLE = \"editor-editable-element\";\nexport const CLASS_ACTIVE = \"editor-active-element\";\n\n// Default undo/redo stack size\nexport const DEFAULT_MAX_STACK = 60;\n\n// Toolbar styling constants\nexport const TOOLBAR_BG = \"#f8fafc\";\nexport const TOOLBAR_BORDER = \"#e5e7eb\";\nexport const BUTTON_BORDER = \"#d1d5db\";\nexport const BUTTON_ACTIVE_BG = \"#e0e7ff\";\nexport const BUTTON_BG = \"#fff\";\nexport const BUTTON_COLOR = \"#222\";\nexport const INFO_COLOR = \"#888\";\n\n// Outline colors used by injected styles\nexport const HOVER_OUTLINE = \"#2563eb\";\nexport const ACTIVE_OUTLINE = \"#16a34a\";\n\n// Toolbar label/icon constants (kept in core for reuse)\nexport const LABEL_BOLD = \"<b>B</b>\";\nexport const LABEL_ITALIC = \"<i>I</i>\";\nexport const LABEL_UNDERLINE = \"<u>U</u>\";\nexport const LABEL_STRIKETHROUGH = \"<s>S</s>\";\nexport const LABEL_UNDO = \"↺\";\nexport const LABEL_REDO = \"↻\";\nexport const LABEL_LINK = \"🔗\";\nexport const LABEL_UNORDERED_LIST = `\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <circle cx=\"3\" cy=\"4\" r=\"1\" fill=\"currentColor\" />\n <rect x=\"6\" y=\"3\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n <circle cx=\"3\" cy=\"8\" r=\"1\" fill=\"currentColor\" />\n <rect x=\"6\" y=\"7\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n <circle cx=\"3\" cy=\"12\" r=\"1\" fill=\"currentColor\" />\n <rect x=\"6\" y=\"11\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n </svg>\n`;\nexport const LABEL_ORDERED_LIST = `\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <text x=\"1\" y=\"4\" font-size=\"4\" fill=\"currentColor\">1.</text>\n <rect x=\"6\" y=\"3\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n <text x=\"1\" y=\"8\" font-size=\"4\" fill=\"currentColor\">2.</text>\n <rect x=\"6\" y=\"7\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n <text x=\"1\" y=\"12\" font-size=\"4\" fill=\"currentColor\">3.</text>\n <rect x=\"6\" y=\"11\" width=\"9\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n </svg>\n`;\nexport const LABEL_ALIGN_LEFT = `\n\t<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n\t\t<rect x=\"1\" y=\"2\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"6\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"10\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"14\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t</svg>\n`;\nexport const LABEL_ALIGN_CENTER = `\n\t<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n\t\t<rect x=\"3\" y=\"2\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"6\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"3\" y=\"10\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"14\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t</svg>\n`;\nexport const LABEL_ALIGN_RIGHT = `\n\t<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n\t\t<rect x=\"5\" y=\"2\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"6\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"5\" y=\"10\" width=\"10\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t<rect x=\"1\" y=\"14\" width=\"14\" height=\"2\" rx=\"0.5\" fill=\"currentColor\" />\n\t</svg>\n`;\n\n// Font and size option lists used by the toolbar\nexport const FONT_OPTIONS: { label: string; value: string }[] = [\n { label: \"Arial\", value: \"Arial\" },\n { label: \"Helvetica\", value: \"Helvetica, Arial, sans-serif\" },\n { label: \"Verdana\", value: \"Verdana, Geneva, sans-serif\" },\n { label: \"Tahoma\", value: \"Tahoma, Geneva, sans-serif\" },\n { label: \"Trebuchet MS\", value: \"Trebuchet MS, Helvetica, sans-serif\" },\n { label: \"Georgia\", value: \"Georgia, serif\" },\n { label: \"Times New Roman\", value: \"Times New Roman, Times, serif\" },\n { label: \"Palatino\", value: \"Palatino, 'Palatino Linotype', serif\" },\n { label: \"Garamond\", value: \"Garamond, serif\" },\n { label: \"Book Antiqua\", value: \"'Book Antiqua', Palatino, serif\" },\n { label: \"Courier New\", value: \"'Courier New', Courier, monospace\" },\n { label: \"Lucida Console\", value: \"'Lucida Console', Monaco, monospace\" },\n { label: \"Impact\", value: \"Impact, Charcoal, sans-serif\" },\n { label: \"Comic Sans MS\", value: \"'Comic Sans MS', 'Comic Sans', cursive\" },\n { label: \"Segoe UI\", value: \"'Segoe UI', Tahoma, Geneva, sans-serif\" },\n {\n label: \"Roboto\",\n value: \"Roboto, 'Helvetica Neue', Helvetica, Arial, sans-serif\",\n },\n { label: \"Open Sans\", value: \"'Open Sans', Arial, sans-serif\" },\n { label: \"Lato\", value: \"Lato, 'Helvetica Neue', Arial, sans-serif\" },\n { label: \"Montserrat\", value: \"Montserrat, Arial, sans-serif\" },\n { label: \"Source Sans Pro\", value: \"'Source Sans Pro', Arial, sans-serif\" },\n { label: \"Fira Sans\", value: \"'Fira Sans', Arial, sans-serif\" },\n { label: \"Ubuntu\", value: \"Ubuntu, Arial, sans-serif\" },\n { label: \"Noto Sans\", value: \"'Noto Sans', Arial, sans-serif\" },\n { label: \"Droid Sans\", value: \"'Droid Sans', Arial, sans-serif\" },\n {\n label: \"Helvetica Neue\",\n value: \"'Helvetica Neue', Helvetica, Arial, sans-serif\",\n },\n {\n label: \"System UI\",\n value:\n \"system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\",\n },\n];\n\nexport const SIZE_OPTIONS: { label: string; value: string }[] = [\n { label: \"8\", value: \"8\" },\n { label: \"9\", value: \"9\" },\n { label: \"10\", value: \"10\" },\n { label: \"11\", value: \"11\" },\n { label: \"12\", value: \"12\" },\n { label: \"14\", value: \"14\" },\n { label: \"16\", value: \"16\" },\n { label: \"18\", value: \"18\" },\n { label: \"20\", value: \"20\" },\n { label: \"22\", value: \"22\" },\n { label: \"24\", value: \"24\" },\n { label: \"26\", value: \"26\" },\n { label: \"28\", value: \"28\" },\n { label: \"36\", value: \"36\" },\n { label: \"48\", value: \"48\" },\n { label: \"72\", value: \"72\" },\n];\n\n// Block format options (Paragraph + Headings)\nexport const FORMAT_OPTIONS: { label: string; value: string }[] = [\n { label: \"Heading 1\", value: \"h1\" },\n { label: \"Heading 2\", value: \"h2\" },\n { label: \"Heading 3\", value: \"h3\" },\n { label: \"Heading 4\", value: \"h4\" },\n { label: \"Heading 5\", value: \"h5\" },\n { label: \"Heading 6\", value: \"h6\" },\n];\n\n// Clear formatting label\nexport const LABEL_CLEAR_FORMAT = \"🧹 Clear\";\n\n// Maximum allowed file size for inline image uploads (bytes)\nexport const MAX_FILE_SIZE = 1024 * 1024; // 1MB\n","import createDOMPurify from \"dompurify\";\n\n/**\n * Sanitize HTML using DOMPurify.\n *\n * Works in browser and in jsdom (tests) by accepting either a `Window`\n * or a `Document` (from which `defaultView` is used).\n */\nexport function sanitizeHtml(\n html: string,\n ctx?: Window | Document | null,\n): string {\n if (!html) return \"\";\n // Try to get a Window reference\n let win: Window | null = null;\n if (ctx && (ctx as Document).defaultView) {\n win = (ctx as Document).defaultView as Window;\n } else if (ctx && (ctx as Window).document) {\n win = ctx as Window;\n } else if (typeof window !== \"undefined\") {\n win = window as Window;\n }\n\n // If we have a window, use DOMPurify\n if (win) {\n try {\n const DOMPurify = createDOMPurify(win as any);\n // Preserve element ids and data-* attributes so restoring snapshots\n // during undo/redo does not break page scripts or CSS that rely on\n // those attributes. Still strip <script> and all inline event\n // handlers (on*) to avoid executing arbitrary script.\n // Use ADD_ATTR to allow `id`, and a hook to preserve any `data-` attrs.\n try {\n DOMPurify.addHook(\"uponSanitizeAttribute\", (node: any, data: any) => {\n try {\n if (data && data.attrName && data.attrName.startsWith(\"data-\")) {\n // Keep data-* attributes\n (data as any).keepAttr = true;\n }\n } catch (e) {\n /* ignore hook errors */\n }\n });\n } catch (e) {\n /* addHook may not be available in some environments; ignore */\n }\n\n return DOMPurify.sanitize(html, {\n // Use sensible defaults: allow common formatting tags but strip scripts\n ALLOWED_TAGS: [\n \"a\",\n \"b\",\n \"i\",\n \"em\",\n \"strong\",\n \"u\",\n \"p\",\n \"div\",\n \"span\",\n // Common semantic elements: preserve document structure so undo/redo\n // does not flatten header/section/nav into plain content.\n \"header\",\n \"nav\",\n \"section\",\n \"main\",\n \"footer\",\n \"article\",\n \"aside\",\n \"figure\",\n \"figcaption\",\n \"time\",\n // Interactive / form elements that may appear in content\n \"button\",\n \"input\",\n \"label\",\n \"select\",\n \"option\",\n \"textarea\",\n \"details\",\n \"summary\",\n // Allow <style> tags so user/content-provided CSS is preserved\n // when taking snapshots and during undo/redo operations.\n // DOMPurify will still sanitize the contents of style blocks.\n \"style\",\n // Preserve linked stylesheets so page/editor styling isn't lost\n \"link\",\n \"ul\",\n \"ol\",\n \"li\",\n \"br\",\n \"hr\",\n \"blockquote\",\n \"pre\",\n \"code\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"img\",\n ],\n ALLOWED_ATTR: [\n \"href\",\n \"title\",\n \"alt\",\n \"src\",\n \"class\",\n \"style\",\n // Attributes used by <link> tags\n \"rel\",\n \"type\",\n \"media\",\n ],\n // Also allow `id` attributes so element ids survive sanitization.\n ADD_ATTR: [\"id\"],\n });\n } catch (e) {\n // If DOMPurify initialization fails, fall through to minimal stripping\n }\n }\n\n // Minimal fallback: remove <script> tags and on* attributes\n return html\n .replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, \"\")\n .replace(/on[a-z]+=\\\"[^\"]*\\\"/gi, \"\");\n}\n\nexport default sanitizeHtml;\n","import { editorEventEmitter } from \"./events\";\nimport {\n DEFAULT_MAX_STACK,\n TOOLBAR_ID,\n STYLE_ID,\n CLASS_EDITABLE,\n CLASS_ACTIVE,\n} from \"./constants\";\nimport { sanitizeHtml } from \"../utils/sanitize\";\n\nlet _doc: Document | null = null;\nlet _undoStack: string[] = [];\nlet _redoStack: string[] = [];\nlet _currentEditable: HTMLElement | null = null;\nlet _savedRange: Range | null = null;\nlet _maxStackSize = DEFAULT_MAX_STACK;\n\nexport function _setDoc(doc: Document | null) {\n _doc = doc;\n}\nexport function _getDoc() {\n return _doc;\n}\nexport function _setUndoStack(stack: string[]) {\n _undoStack = stack;\n editorEventEmitter.emit({\n type: \"undoStateChanged\",\n timestamp: Date.now(),\n data: { canUndo: _undoStack.length > 1 },\n });\n}\nexport function _getUndoStack() {\n return _undoStack;\n}\nexport function _setRedoStack(stack: string[]) {\n _redoStack = stack;\n editorEventEmitter.emit({\n type: \"redoStateChanged\",\n timestamp: Date.now(),\n data: { canRedo: _redoStack.length > 0 },\n });\n}\nexport function _getRedoStack() {\n return _redoStack;\n}\nexport function _setCurrentEditable(el: HTMLElement | null) {\n _currentEditable = el;\n editorEventEmitter.emit({\n type: \"selectionChanged\",\n timestamp: Date.now(),\n data: { element: el?.tagName },\n });\n}\nexport function _getCurrentEditable() {\n return _currentEditable;\n}\n\n// Selection save/restore helpers used by toolbar interactions\nexport function _saveSelection() {\n try {\n if (!_doc) return;\n const sel = _doc.getSelection();\n if (!sel) return;\n if (!sel.rangeCount) return;\n _savedRange = sel.getRangeAt(0).cloneRange();\n } catch (e) {\n /* ignore */\n }\n}\n\nexport function _restoreSelection() {\n try {\n if (!_doc) return;\n const sel = _doc.getSelection();\n if (!sel) return;\n if (_savedRange) {\n sel.removeAllRanges();\n sel.addRange(_savedRange);\n _savedRange = null;\n }\n } catch (e) {\n /* ignore */\n }\n}\nexport function pushStandaloneSnapshot(clearRedo = true) {\n if (!_doc) return;\n // Clone the documentElement and remove injected UI (toolbar/style)\n // so snapshots capture only the user's content.\n const clone = _doc.documentElement.cloneNode(true) as HTMLElement;\n const toolbarNode = clone.querySelector(`#${TOOLBAR_ID}`);\n if (toolbarNode && toolbarNode.parentNode)\n toolbarNode.parentNode.removeChild(toolbarNode);\n const styleNode = clone.querySelector(`#${STYLE_ID}`);\n if (styleNode && styleNode.parentNode)\n styleNode.parentNode.removeChild(styleNode);\n // Remove editor-specific attributes/classes so snapshots don't persist\n // transient editing state (contenteditable, toolbar classes, tabindex).\n try {\n const editableNodes = clone.querySelectorAll(\n \"[contenteditable], .\" + CLASS_EDITABLE + \", .\" + CLASS_ACTIVE,\n );\n editableNodes.forEach((el) => {\n try {\n if (el instanceof Element) {\n if (el.hasAttribute(\"contenteditable\"))\n el.removeAttribute(\"contenteditable\");\n if (el.hasAttribute(\"tabindex\")) el.removeAttribute(\"tabindex\");\n el.classList.remove(CLASS_EDITABLE, CLASS_ACTIVE);\n }\n } catch (e) {\n /* ignore */\n }\n });\n } catch (e) {\n /* ignore */\n }\n // Preserve scripts by replacing them with a harmless placeholder\n // that contains the encoded script source/attributes. This allows the\n // sanitizer to run (which strips <script> tags) while keeping the\n // script content available to re-insert and execute on restore.\n try {\n const scripts = Array.from(\n clone.querySelectorAll<HTMLScriptElement>(\"script\"),\n );\n scripts.forEach((s) => {\n try {\n const code = s.textContent || \"\";\n const attrs: Record<string, string> = {};\n Array.from(s.attributes).forEach((a) => (attrs[a.name] = a.value));\n const placeholder = clone.ownerDocument!.createElement(\"span\");\n // encode script body in base64 to survive sanitization\n try {\n // btoa may throw on Unicode; encodeURIComponent first\n const safe =\n typeof btoa !== \"undefined\"\n ? btoa(unescape(encodeURIComponent(code)))\n : encodeURIComponent(code);\n placeholder.setAttribute(\"data-rhe-script\", safe);\n } catch (e) {\n placeholder.setAttribute(\"data-rhe-script\", encodeURIComponent(code));\n }\n if (Object.keys(attrs).length) {\n placeholder.setAttribute(\n \"data-rhe-script-attrs\",\n encodeURIComponent(JSON.stringify(attrs)),\n );\n }\n // mark parent editable region if present so we can reinsert in-place\n const parentMarker = s.closest(\"[data-rhe-id]\") as HTMLElement | null;\n if (parentMarker && parentMarker.getAttribute(\"data-rhe-id\")) {\n placeholder.setAttribute(\n \"data-rhe-script-parent\",\n parentMarker.getAttribute(\"data-rhe-id\")!,\n );\n } else {\n placeholder.setAttribute(\"data-rhe-script-parent\", \"head\");\n }\n s.parentNode?.replaceChild(placeholder, s);\n } catch (e) {\n /* ignore per-script errors */\n }\n });\n } catch (e) {\n /* ignore script extraction errors */\n }\n const snapRaw = clone.outerHTML;\n const snap = sanitizeHtml(snapRaw, _doc);\n if (!_undoStack.length || _undoStack[_undoStack.length - 1] !== snap) {\n _undoStack.push(snap);\n if (_undoStack.length > _maxStackSize) _undoStack.shift();\n editorEventEmitter.emit({\n type: \"contentChanged\",\n timestamp: Date.now(),\n data: { htmlLength: snap.length },\n });\n }\n if (clearRedo) {\n _redoStack = [];\n editorEventEmitter.emit({\n type: \"redoStateChanged\",\n timestamp: Date.now(),\n data: { canRedo: false },\n });\n }\n}\nexport function setMaxStackSize(size: number) {\n _maxStackSize = Math.max(1, size);\n}\n"],"mappings":"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","names":[]}
@@ -1551,6 +1551,40 @@ var RichHtmlEditor = (() => {
1551
1551
  const styleId = STYLE_ID;
1552
1552
  let styleEl = doc.getElementById(styleId);
1553
1553
  const css = `
1554
+ /* Scoped conservative reset for editor UI root to prevent template styles leaking in */
1555
+ #rhe-editor-root {
1556
+ box-sizing: border-box;
1557
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
1558
+ font-size: 14px;
1559
+ line-height: 1.5;
1560
+ color: #0f172a;
1561
+ -webkit-font-smoothing: antialiased;
1562
+ -moz-osx-font-smoothing: grayscale;
1563
+ }
1564
+ #rhe-editor-root *,
1565
+ #rhe-editor-root *::before,
1566
+ #rhe-editor-root *::after {
1567
+ box-sizing: inherit;
1568
+ }
1569
+ /* Restore user-agent defaults for native controls inside the root */
1570
+ #rhe-editor-root button,
1571
+ #rhe-editor-root input,
1572
+ #rhe-editor-root textarea,
1573
+ #rhe-editor-root select {
1574
+ all: revert;
1575
+ font: inherit;
1576
+ color: inherit;
1577
+ background: transparent;
1578
+ border: none;
1579
+ padding: 0;
1580
+ margin: 0;
1581
+ }
1582
+ /* Basic focus visibility for accessibility inside root */
1583
+ #rhe-editor-root :focus {
1584
+ outline: 2px solid Highlight;
1585
+ outline-offset: 2px;
1586
+ }
1587
+
1554
1588
  .${CLASS_EDITABLE}{outline:2px dashed ${HOVER_OUTLINE};cursor:text}
1555
1589
  .${CLASS_ACTIVE}{outline:2px solid ${ACTIVE_OUTLINE};cursor:text}
1556
1590
  #${TOOLBAR_ID} img{cursor:auto}
@@ -1860,6 +1894,28 @@ var RichHtmlEditor = (() => {
1860
1894
  // src/toolbar/render.ts
1861
1895
  init_constants();
1862
1896
 
1897
+ // src/dom/root.ts
1898
+ var EDITOR_ROOT_ID = "rhe-editor-root";
1899
+ function getEditorRoot(doc) {
1900
+ let root = doc.getElementById(EDITOR_ROOT_ID);
1901
+ if (root) return root;
1902
+ root = doc.createElement("div");
1903
+ root.id = EDITOR_ROOT_ID;
1904
+ root.setAttribute("data-rhe-root", "true");
1905
+ try {
1906
+ if (doc.body && doc.body.firstChild)
1907
+ doc.body.insertBefore(root, doc.body.firstChild);
1908
+ else if (doc.body) doc.body.appendChild(root);
1909
+ else doc.documentElement.appendChild(root);
1910
+ } catch (e) {
1911
+ try {
1912
+ doc.documentElement.appendChild(root);
1913
+ } catch (err) {
1914
+ }
1915
+ }
1916
+ return root;
1917
+ }
1918
+
1863
1919
  // src/toolbar/color.ts
1864
1920
  function makeColorInput(doc, options, title, command, initialColor) {
1865
1921
  const input = doc.createElement("input");
@@ -2004,7 +2060,11 @@ var RichHtmlEditor = (() => {
2004
2060
  )
2005
2061
  );
2006
2062
  overflowMenu.appendChild(
2007
- helpers.makeColorInput("Text color", "foreColor", format.foreColor)
2063
+ helpers.makeColorInput(
2064
+ "Text color",
2065
+ "foreColor",
2066
+ format.foreColor
2067
+ )
2008
2068
  );
2009
2069
  overflowMenu.appendChild(
2010
2070
  helpers.makeColorInput(
@@ -2089,7 +2149,9 @@ var RichHtmlEditor = (() => {
2089
2149
  function setupNavigation(toolbar) {
2090
2150
  toolbar.addEventListener("keydown", (e) => {
2091
2151
  const focusable = Array.from(
2092
- toolbar.querySelectorAll("button, select, input, [tabindex]")
2152
+ toolbar.querySelectorAll(
2153
+ "button, select, input, [tabindex]"
2154
+ )
2093
2155
  ).filter((el) => !el.hasAttribute("disabled"));
2094
2156
  if (!focusable.length) return;
2095
2157
  const idx = focusable.indexOf(document.activeElement);
@@ -2264,7 +2326,12 @@ var RichHtmlEditor = (() => {
2264
2326
  makeGroup: makeGroup2
2265
2327
  });
2266
2328
  setupNavigation(toolbar);
2267
- doc.body.insertBefore(toolbar, doc.body.firstChild);
2329
+ try {
2330
+ const root = getEditorRoot(doc);
2331
+ root.insertBefore(toolbar, root.firstChild);
2332
+ } catch (e) {
2333
+ doc.body.insertBefore(toolbar, doc.body.firstChild);
2334
+ }
2268
2335
  }
2269
2336
 
2270
2337
  // src/dom/handlers.ts
@@ -2317,6 +2384,8 @@ var RichHtmlEditor = (() => {
2317
2384
  const fileInput = doc.createElement("input");
2318
2385
  fileInput.type = "file";
2319
2386
  fileInput.accept = "image/*";
2387
+ fileInput.style.width = "100%";
2388
+ fileInput.style.boxSizing = "border-box";
2320
2389
  uploadPane.appendChild(fileInput);
2321
2390
  const uploadMsg = doc.createElement("div");
2322
2391
  uploadMsg.className = "rhe-img-msg";
@@ -2327,6 +2396,8 @@ var RichHtmlEditor = (() => {
2327
2396
  const urlInput = doc.createElement("input");
2328
2397
  urlInput.type = "url";
2329
2398
  urlInput.placeholder = "https://example.com/image.jpg";
2399
+ urlInput.style.width = "100%";
2400
+ urlInput.style.boxSizing = "border-box";
2330
2401
  urlPane.appendChild(urlInput);
2331
2402
  const urlMsg = doc.createElement("div");
2332
2403
  urlMsg.className = "rhe-img-msg";
@@ -2461,7 +2532,12 @@ var RichHtmlEditor = (() => {
2461
2532
  overlay.addEventListener("keydown", (e) => {
2462
2533
  if (e.key === "Escape") close();
2463
2534
  });
2464
- doc.body.appendChild(overlay);
2535
+ try {
2536
+ const root = getEditorRoot(doc);
2537
+ root.appendChild(overlay);
2538
+ } catch (e) {
2539
+ doc.body.appendChild(overlay);
2540
+ }
2465
2541
  fileInput.focus();
2466
2542
  }
2467
2543