@overlap/rte 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,UAAU,aAAa;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3H,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAsJ5C,CAAC"}
1
+ {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAIxE,UAAU,aAAa;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3H,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,aAAa,CAwM5C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingToolbar.d.ts","sourceRoot":"","sources":["../../src/components/FloatingToolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAAe,SAAS,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAE1D,UAAU,oBAAoB;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0L1D,CAAC"}
1
+ {"version":3,"file":"FloatingToolbar.d.ts","sourceRoot":"","sources":["../../src/components/FloatingToolbar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,EAAe,SAAS,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAE1D,UAAU,oBAAoB;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,SAAS,CAAC;IACrB,aAAa,EAAE,WAAW,GAAG,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CA0L1D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useEditorEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorEvents.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,UAAU,sBAAsB;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,UAAU,EAAE;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC;IACxC,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,UAAU,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACjC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,qBAAqB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD,mBAAmB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACnD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,EACnB,IAAI,EACJ,IAAI,GACP,EAAE,sBAAsB,QAoOxB"}
1
+ {"version":3,"file":"useEditorEvents.d.ts","sourceRoot":"","sources":["../../src/hooks/useEditorEvents.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,UAAU,sBAAsB;IAC5B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,UAAU,EAAE;QAAE,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC;IACxC,aAAa,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACpC,UAAU,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACjC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,qBAAqB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACrD,mBAAmB,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC;IACnD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,mBAAmB,EACnB,IAAI,EACJ,IAAI,GACP,EAAE,sBAAsB,QA0TxB"}
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
+ import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
 
5
5
  const BoldIcon = ({ width = 18, height = 18, className, }) => (jsx("svg", { width: width, height: height, viewBox: "0 0 24 24", fill: "currentColor", className: className, children: jsx("path", { d: "M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z" }) }));
@@ -83,10 +83,45 @@ const Icon = ({ icon, width = 18, height = 18, className }) => {
83
83
  const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, showCustomColorInput, }) => {
84
84
  const [isOpen, setIsOpen] = useState(false);
85
85
  const [customColor, setCustomColor] = useState("#000000");
86
+ const [menuPos, setMenuPos] = useState({ top: 0, left: 0 });
86
87
  const dropdownRef = useRef(null);
88
+ const menuRef = useRef(null);
89
+ const buttonRef = useRef(null);
90
+ const updateMenuPosition = useCallback(() => {
91
+ if (!buttonRef.current)
92
+ return;
93
+ const rect = buttonRef.current.getBoundingClientRect();
94
+ const pad = 8;
95
+ let top = rect.bottom + 4;
96
+ let left = rect.left;
97
+ const menuEl = menuRef.current;
98
+ if (menuEl) {
99
+ const menuW = menuEl.offsetWidth;
100
+ const menuH = menuEl.offsetHeight;
101
+ if (left + menuW > window.innerWidth - pad) {
102
+ left = window.innerWidth - menuW - pad;
103
+ }
104
+ if (left < pad)
105
+ left = pad;
106
+ if (top + menuH > window.innerHeight - pad) {
107
+ top = rect.top - menuH - 4;
108
+ }
109
+ if (top < pad)
110
+ top = pad;
111
+ }
112
+ setMenuPos({ top, left });
113
+ }, []);
114
+ useEffect(() => {
115
+ if (!isOpen)
116
+ return;
117
+ updateMenuPosition();
118
+ requestAnimationFrame(updateMenuPosition);
119
+ }, [isOpen, updateMenuPosition]);
87
120
  useEffect(() => {
88
121
  const handleClickOutside = (event) => {
89
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
122
+ const target = event.target;
123
+ if (dropdownRef.current && !dropdownRef.current.contains(target) &&
124
+ (!menuRef.current || !menuRef.current.contains(target))) {
90
125
  setIsOpen(false);
91
126
  }
92
127
  };
@@ -102,7 +137,6 @@ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, show
102
137
  setIsOpen(false);
103
138
  };
104
139
  const currentOption = options.find(opt => opt.value === currentValue);
105
- // Close on Escape key
106
140
  useEffect(() => {
107
141
  if (!isOpen)
108
142
  return;
@@ -115,21 +149,26 @@ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, show
115
149
  document.addEventListener("keydown", handleKeyDown);
116
150
  return () => document.removeEventListener("keydown", handleKeyDown);
117
151
  }, [isOpen]);
118
- return (jsxs("div", { className: "rte-dropdown", ref: dropdownRef, onMouseDown: (e) => e.preventDefault(), children: [jsxs("button", { type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `rte-toolbar-button rte-dropdown-button ${currentOption ? 'rte-dropdown-button-has-value' : ''}`, title: label, "aria-label": label, "aria-expanded": isOpen, "aria-haspopup": "listbox", children: [jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), isOpen && (jsxs("div", { className: "rte-dropdown-menu", role: "listbox", "aria-label": label, children: [options.map((option) => (jsxs("button", { type: "button", role: "option", "aria-selected": currentValue === option.value, className: `rte-dropdown-item ${currentValue === option.value ? 'rte-dropdown-item-active' : ''}`, onClick: () => handleSelect(option.value), children: [option.color && (jsx("span", { className: `rte-dropdown-color-preview ${currentValue === option.value ? 'active' : ''}`, style: { backgroundColor: option.color } })), option.preview && !option.headingPreview && (jsx("span", { className: "rte-dropdown-fontsize-preview", style: { fontSize: `${option.preview}px` }, children: "Aa" })), option.headingPreview && (jsx("span", { className: `rte-dropdown-heading-preview ${option.headingPreview}`, children: option.headingPreview === 'p' ? 'Normal' : option.headingPreview.toUpperCase() })), option.icon && jsx(Icon, { icon: option.icon, width: 16, height: 16 }), jsx("span", { style: { flex: 1, fontWeight: currentValue === option.value ? 600 : 400 }, children: option.label })] }, option.value))), showCustomColorInput && (jsxs("div", { className: "rte-color-custom-input", onMouseDown: (e) => e.stopPropagation(), children: [jsx("input", { type: "color", value: customColor, onChange: (e) => setCustomColor(e.target.value), title: "Pick a color" }), jsx("input", { type: "text", value: customColor, onChange: (e) => {
119
- const v = e.target.value;
120
- setCustomColor(v);
121
- }, placeholder: "#000000", maxLength: 7, onKeyDown: (e) => {
122
- if (e.key === "Enter") {
123
- e.preventDefault();
124
- if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
125
- handleSelect(customColor);
126
- }
127
- }
128
- } }), jsx("button", { type: "button", className: "rte-color-custom-apply", onClick: () => {
129
- if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
130
- handleSelect(customColor);
131
- }
132
- }, children: "Apply" })] }))] }))] }));
152
+ const menuContent = isOpen ? (jsxs("div", { ref: menuRef, className: "rte-dropdown-menu", role: "listbox", "aria-label": label, style: {
153
+ position: 'fixed',
154
+ top: menuPos.top,
155
+ left: menuPos.left,
156
+ }, onMouseDown: (e) => e.preventDefault(), children: [options.map((option) => (jsxs("button", { type: "button", role: "option", "aria-selected": currentValue === option.value, className: `rte-dropdown-item ${currentValue === option.value ? 'rte-dropdown-item-active' : ''}`, onClick: () => handleSelect(option.value), children: [option.color && (jsx("span", { className: `rte-dropdown-color-preview ${currentValue === option.value ? 'active' : ''}`, style: { backgroundColor: option.color } })), option.preview && !option.headingPreview && (jsx("span", { className: "rte-dropdown-fontsize-preview", style: { fontSize: `${option.preview}px` }, children: "Aa" })), option.headingPreview && (jsx("span", { className: `rte-dropdown-heading-preview ${option.headingPreview}`, children: option.headingPreview === 'p' ? 'Normal' : option.headingPreview.toUpperCase() })), option.icon && jsx(Icon, { icon: option.icon, width: 16, height: 16 }), jsx("span", { style: { flex: 1, fontWeight: currentValue === option.value ? 600 : 400 }, children: option.label })] }, option.value))), showCustomColorInput && (jsxs("div", { className: "rte-color-custom-input", onMouseDown: (e) => e.stopPropagation(), children: [jsx("input", { type: "color", value: customColor, onChange: (e) => setCustomColor(e.target.value), title: "Pick a color" }), jsx("input", { type: "text", value: customColor, onChange: (e) => {
157
+ const v = e.target.value;
158
+ setCustomColor(v);
159
+ }, placeholder: "#000000", maxLength: 7, onKeyDown: (e) => {
160
+ if (e.key === "Enter") {
161
+ e.preventDefault();
162
+ if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
163
+ handleSelect(customColor);
164
+ }
165
+ }
166
+ } }), jsx("button", { type: "button", className: "rte-color-custom-apply", onClick: () => {
167
+ if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
168
+ handleSelect(customColor);
169
+ }
170
+ }, children: "Apply" })] }))] })) : null;
171
+ return (jsxs("div", { className: "rte-dropdown", ref: dropdownRef, onMouseDown: (e) => e.preventDefault(), children: [jsxs("button", { ref: buttonRef, type: "button", onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, className: `rte-toolbar-button rte-dropdown-button ${currentOption ? 'rte-dropdown-button-has-value' : ''}`, title: label, "aria-label": label, "aria-expanded": isOpen, "aria-haspopup": "listbox", children: [jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), menuContent && createPortal(menuContent, document.body)] }));
133
172
  };
134
173
 
135
174
  /**
@@ -804,7 +843,7 @@ const ALLOWED_CONTENT_TAGS = new Set([
804
843
  "ul", "ol", "li", "a", "strong", "em", "u", "s", "del",
805
844
  "sub", "sup", "code", "pre", "blockquote", "br", "hr",
806
845
  "img", "table", "thead", "tbody", "tr", "th", "td",
807
- "b", "i", "strike",
846
+ "b", "i", "strike", "font",
808
847
  ]);
809
848
  /** Checks if an attribute key is safe to set on a DOM element. */
810
849
  function isSafeAttribute(key) {
@@ -1012,6 +1051,7 @@ function domToContent(element) {
1012
1051
  "sub",
1013
1052
  "sup",
1014
1053
  "code",
1054
+ "font",
1015
1055
  ].includes(tagName)) {
1016
1056
  const children = [];
1017
1057
  Array.from(el.childNodes).forEach((child) => {
@@ -1101,6 +1141,46 @@ function domToContent(element) {
1101
1141
  });
1102
1142
  }
1103
1143
  }
1144
+ // <font> from execCommand('foreColor') or pasted HTML
1145
+ if (tagName === "font") {
1146
+ const fontAttrs = {};
1147
+ const colorAttr = el.getAttribute("color");
1148
+ if (colorAttr)
1149
+ fontAttrs.color = colorAttr;
1150
+ const sizeAttr = el.getAttribute("size");
1151
+ if (sizeAttr) {
1152
+ const sizeMap = {
1153
+ "1": "10px", "2": "13px", "3": "16px", "4": "18px",
1154
+ "5": "24px", "6": "32px", "7": "48px",
1155
+ };
1156
+ const mapped = sizeMap[sizeAttr];
1157
+ if (mapped)
1158
+ fontAttrs.fontSize = mapped;
1159
+ }
1160
+ const style = el.getAttribute("style") || "";
1161
+ if (style) {
1162
+ style.split(";").forEach((rule) => {
1163
+ const [key, value] = rule
1164
+ .split(":")
1165
+ .map((s) => s.trim());
1166
+ if (key && value) {
1167
+ if (key === "font-size")
1168
+ fontAttrs.fontSize = value;
1169
+ else if (key === "color")
1170
+ fontAttrs.color = value;
1171
+ else if (key === "background-color")
1172
+ fontAttrs.backgroundColor = value;
1173
+ }
1174
+ });
1175
+ }
1176
+ return {
1177
+ type: "span",
1178
+ children: children.length > 0 ? children : undefined,
1179
+ attributes: Object.keys(fontAttrs).length > 0
1180
+ ? fontAttrs
1181
+ : undefined,
1182
+ };
1183
+ }
1104
1184
  // Map tag names to semantic types
1105
1185
  const type = tagName === "strong" || tagName === "b"
1106
1186
  ? "bold"
@@ -2119,6 +2199,61 @@ function useEditorEvents({ editorRef, historyRef, isUpdatingRef, mountedRef, not
2119
2199
  }, 0);
2120
2200
  return;
2121
2201
  }
2202
+ // Exit code block: Enter on empty last line escapes <pre>
2203
+ if (e.key === "Enter" &&
2204
+ !e.shiftKey &&
2205
+ !isModifierPressed) {
2206
+ const sel = window.getSelection();
2207
+ if (sel && sel.rangeCount > 0 && sel.isCollapsed) {
2208
+ const range = sel.getRangeAt(0);
2209
+ const node = range.startContainer;
2210
+ const pre = (node instanceof HTMLElement
2211
+ ? node
2212
+ : node.parentElement)?.closest("pre");
2213
+ if (pre && pre.lastChild) {
2214
+ const lastChild = pre.lastChild;
2215
+ const cursorInPre = node === pre &&
2216
+ range.startOffset === pre.childNodes.length;
2217
+ const cursorAtEndOfLastText = node.nodeType === Node.TEXT_NODE &&
2218
+ node === lastChild &&
2219
+ range.startOffset ===
2220
+ (node.textContent?.length ?? 0);
2221
+ const isAtEnd = cursorInPre || cursorAtEndOfLastText;
2222
+ const lastIsBr = lastChild instanceof HTMLElement &&
2223
+ lastChild.tagName === "BR";
2224
+ const endsWithNewline = node.nodeType === Node.TEXT_NODE &&
2225
+ (node.textContent || "").endsWith("\n");
2226
+ if (isAtEnd && (lastIsBr || endsWithNewline)) {
2227
+ e.preventDefault();
2228
+ if (lastIsBr) {
2229
+ pre.removeChild(lastChild);
2230
+ }
2231
+ else if (node.nodeType === Node.TEXT_NODE &&
2232
+ node.textContent) {
2233
+ node.textContent =
2234
+ node.textContent.replace(/\n$/, "");
2235
+ }
2236
+ if (!pre.textContent &&
2237
+ !pre.querySelector("br")) {
2238
+ pre.appendChild(document.createElement("br"));
2239
+ }
2240
+ const p = document.createElement("p");
2241
+ p.appendChild(document.createElement("br"));
2242
+ pre.parentNode?.insertBefore(p, pre.nextSibling);
2243
+ const newRange = document.createRange();
2244
+ newRange.setStart(p, 0);
2245
+ newRange.collapse(true);
2246
+ sel.removeAllRanges();
2247
+ sel.addRange(newRange);
2248
+ const content = domToContent(editor);
2249
+ const serializedSel = serializeSelection(editor);
2250
+ historyRef.current.push(content, serializedSel);
2251
+ notifyChange(content);
2252
+ return;
2253
+ }
2254
+ }
2255
+ }
2256
+ }
2122
2257
  // Auto-link: convert URLs to <a> tags on space/enter
2123
2258
  if (!isModifierPressed && (e.key === " " || e.key === "Enter")) {
2124
2259
  handleAutoLink(editor, e);
@@ -4013,7 +4148,7 @@ function createAdvancedLinkPlugin(options = {}) {
4013
4148
  /** Pre-built link plugin with just target enabled (no custom fields). */
4014
4149
  const advancedLinkPlugin = createAdvancedLinkPlugin();
4015
4150
 
4016
- const InsertTableDialog = ({ onInsert, onClose, }) => {
4151
+ const InsertTableDialog = ({ onInsert, onClose, anchorRect, }) => {
4017
4152
  const [rows, setRows] = useState(3);
4018
4153
  const [cols, setCols] = useState(3);
4019
4154
  const dialogRef = useRef(null);
@@ -4027,7 +4162,25 @@ const InsertTableDialog = ({ onInsert, onClose, }) => {
4027
4162
  document.addEventListener("mousedown", handler);
4028
4163
  return () => document.removeEventListener("mousedown", handler);
4029
4164
  }, [onClose]);
4030
- return (jsxs("div", { className: "rte-table-insert-dialog", ref: dialogRef, children: [jsx("div", { className: "rte-table-insert-title", children: "Insert Table" }), jsxs("div", { className: "rte-table-insert-fields", children: [jsxs("label", { className: "rte-table-insert-label", children: [jsx("span", { children: "Zeilen" }), jsx("input", { type: "number", min: 1, max: 20, value: rows, onChange: (e) => setRows(Math.max(1, Math.min(20, parseInt(e.target.value) || 1))), className: "rte-table-insert-input" })] }), jsxs("label", { className: "rte-table-insert-label", children: [jsx("span", { children: "Spalten" }), jsx("input", { type: "number", min: 1, max: 10, value: cols, onChange: (e) => setCols(Math.max(1, Math.min(10, parseInt(e.target.value) || 1))), className: "rte-table-insert-input" })] })] }), jsx("button", { type: "button", className: "rte-table-insert-btn", onClick: () => onInsert(rows, cols), children: "Insert" })] }));
4165
+ const style = { position: "fixed" };
4166
+ if (anchorRect) {
4167
+ const pad = 8;
4168
+ let top = anchorRect.bottom + 4;
4169
+ let left = anchorRect.left;
4170
+ if (left + 220 > window.innerWidth - pad) {
4171
+ left = window.innerWidth - 220 - pad;
4172
+ }
4173
+ if (left < pad)
4174
+ left = pad;
4175
+ if (top + 200 > window.innerHeight - pad) {
4176
+ top = anchorRect.top - 200 - 4;
4177
+ }
4178
+ if (top < pad)
4179
+ top = pad;
4180
+ style.top = top;
4181
+ style.left = left;
4182
+ }
4183
+ return createPortal(jsxs("div", { className: "rte-table-insert-dialog", ref: dialogRef, style: style, onMouseDown: (e) => e.preventDefault(), children: [jsx("div", { className: "rte-table-insert-title", children: "Insert Table" }), jsxs("div", { className: "rte-table-insert-fields", children: [jsxs("label", { className: "rte-table-insert-label", children: [jsx("span", { children: "Zeilen" }), jsx("input", { type: "number", min: 1, max: 20, value: rows, onChange: (e) => setRows(Math.max(1, Math.min(20, parseInt(e.target.value) || 1))), className: "rte-table-insert-input", onMouseDown: (e) => e.stopPropagation() })] }), jsxs("label", { className: "rte-table-insert-label", children: [jsx("span", { children: "Spalten" }), jsx("input", { type: "number", min: 1, max: 10, value: cols, onChange: (e) => setCols(Math.max(1, Math.min(10, parseInt(e.target.value) || 1))), className: "rte-table-insert-input", onMouseDown: (e) => e.stopPropagation() })] })] }), jsx("button", { type: "button", className: "rte-table-insert-btn", onClick: () => onInsert(rows, cols), children: "Insert" })] }), document.body);
4031
4184
  };
4032
4185
  const TableContextMenu = ({ x, y, onClose, }) => {
4033
4186
  const ref = useRef(null);
@@ -4048,6 +4201,14 @@ const TableContextMenu = ({ x, y, onClose, }) => {
4048
4201
  };
4049
4202
  const TableToolbarButton = (props) => {
4050
4203
  const [showDialog, setShowDialog] = useState(false);
4204
+ const btnRef = useRef(null);
4205
+ const [anchorRect, setAnchorRect] = useState(null);
4206
+ const handleToggle = useCallback(() => {
4207
+ if (!showDialog && btnRef.current) {
4208
+ setAnchorRect(btnRef.current.getBoundingClientRect());
4209
+ }
4210
+ setShowDialog((v) => !v);
4211
+ }, [showDialog]);
4051
4212
  const handleInsert = useCallback((rows, cols) => {
4052
4213
  setShowDialog(false);
4053
4214
  if (!props.editorAPI)
@@ -4056,7 +4217,6 @@ const TableToolbarButton = (props) => {
4056
4217
  if (!sel || sel.rangeCount === 0)
4057
4218
  return;
4058
4219
  const range = sel.getRangeAt(0);
4059
- // Find the editor's contentEditable root
4060
4220
  const container = range.commonAncestorContainer;
4061
4221
  const editorEl = container.nodeType === Node.TEXT_NODE
4062
4222
  ? container.parentElement
@@ -4065,7 +4225,6 @@ const TableToolbarButton = (props) => {
4065
4225
  if (!editorRoot)
4066
4226
  return;
4067
4227
  const table = createTable(rows, cols);
4068
- // Insert after the current block element
4069
4228
  let block = editorEl;
4070
4229
  while (block &&
4071
4230
  block !== editorRoot &&
@@ -4078,11 +4237,9 @@ const TableToolbarButton = (props) => {
4078
4237
  else {
4079
4238
  editorRoot.appendChild(table);
4080
4239
  }
4081
- // Add a paragraph after the table so the user can continue typing
4082
4240
  const p = document.createElement("p");
4083
4241
  p.innerHTML = "<br>";
4084
4242
  table.parentNode?.insertBefore(p, table.nextSibling);
4085
- // Focus the first cell
4086
4243
  const firstCell = table.querySelector("td, th");
4087
4244
  if (firstCell) {
4088
4245
  const newRange = document.createRange();
@@ -4092,7 +4249,7 @@ const TableToolbarButton = (props) => {
4092
4249
  sel.addRange(newRange);
4093
4250
  }
4094
4251
  }, [props.editorAPI]);
4095
- return (jsxs("div", { style: { position: "relative" }, children: [jsx("button", { type: "button", onClick: () => setShowDialog(!showDialog), disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Table", "aria-label": "Table", children: jsx(IconWrapper, { icon: "mdi:table", width: 18, height: 18 }) }), showDialog && (jsx(InsertTableDialog, { onInsert: handleInsert, onClose: () => setShowDialog(false) }))] }));
4252
+ return (jsxs(Fragment, { children: [jsx("button", { ref: btnRef, type: "button", onClick: handleToggle, disabled: props.disabled, className: `rte-toolbar-button ${props.isActive ? "rte-toolbar-button-active" : ""}`, title: "Table", "aria-label": "Table", children: jsx(IconWrapper, { icon: "mdi:table", width: 18, height: 18 }) }), showDialog && (jsx(InsertTableDialog, { onInsert: handleInsert, onClose: () => setShowDialog(false), anchorRect: anchorRect }))] }));
4096
4253
  };
4097
4254
  /* ══════════════════════════════════════════════════════════════════════════
4098
4255
  Table Plugin export
@@ -4302,10 +4459,10 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
4302
4459
  }
4303
4460
  // Center horizontally on the selection, clamp to viewport
4304
4461
  let left = rect.left + rect.width / 2 - toolbarW / 2;
4305
- if (left < pad)
4306
- left = pad;
4307
4462
  if (left + toolbarW > vw - pad)
4308
4463
  left = vw - toolbarW - pad;
4464
+ if (left < pad)
4465
+ left = pad;
4309
4466
  setPos({ top, left, visible: true });
4310
4467
  }, [editorElement]);
4311
4468
  // Debounce via requestAnimationFrame for smooth repositioning
@@ -4350,16 +4507,14 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
4350
4507
  requestAnimationFrame(() => scheduleUpdate());
4351
4508
  }, [editorAPI, scheduleUpdate]);
4352
4509
  const isHidden = !pos.visible || leftPlugins.length === 0;
4353
- return (jsx("div", { ref: toolbarRef, className: "rte-floating-toolbar", style: {
4510
+ return createPortal(jsx("div", { ref: toolbarRef, className: "rte-floating-toolbar", style: {
4354
4511
  position: "fixed",
4355
4512
  top: pos.top,
4356
4513
  left: pos.left,
4357
4514
  visibility: isHidden ? "hidden" : "visible",
4358
4515
  opacity: isHidden ? 0 : 1,
4359
4516
  pointerEvents: isHidden ? "none" : "auto",
4360
- },
4361
- // Prevent selection loss when clicking toolbar buttons
4362
- onMouseDown: (e) => e.preventDefault(), children: jsxs("div", { className: "rte-floating-toolbar-content", children: [jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
4517
+ }, onMouseDown: (e) => e.preventDefault(), children: jsxs("div", { className: "rte-floating-toolbar-content", children: [jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
4363
4518
  if (!plugin.renderButton)
4364
4519
  return null;
4365
4520
  const isActive = plugin.isActive
@@ -4393,7 +4548,7 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
4393
4548
  disabled: !canExecute,
4394
4549
  editorAPI,
4395
4550
  });
4396
- })()] }))] }) }));
4551
+ })()] }))] }) }), document.body);
4397
4552
  };
4398
4553
 
4399
4554
  /**