@overlap/rte 1.0.7 → 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.
- package/dist/components/Dropdown.d.ts.map +1 -1
- package/dist/components/FloatingToolbar.d.ts.map +1 -1
- package/dist/constants.d.ts +0 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/hooks/useCheckbox.d.ts.map +1 -1
- package/dist/hooks/useEditorEvents.d.ts.map +1 -1
- package/dist/index.esm.js +195 -42
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +194 -41
- package/dist/index.js.map +1 -1
- package/dist/plugins/table.d.ts.map +1 -1
- package/dist/styles.css +22 -18
- package/dist/utils/content.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -87,10 +87,45 @@ const Icon = ({ icon, width = 18, height = 18, className }) => {
|
|
|
87
87
|
const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, showCustomColorInput, }) => {
|
|
88
88
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
89
89
|
const [customColor, setCustomColor] = React.useState("#000000");
|
|
90
|
+
const [menuPos, setMenuPos] = React.useState({ top: 0, left: 0 });
|
|
90
91
|
const dropdownRef = React.useRef(null);
|
|
92
|
+
const menuRef = React.useRef(null);
|
|
93
|
+
const buttonRef = React.useRef(null);
|
|
94
|
+
const updateMenuPosition = React.useCallback(() => {
|
|
95
|
+
if (!buttonRef.current)
|
|
96
|
+
return;
|
|
97
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
98
|
+
const pad = 8;
|
|
99
|
+
let top = rect.bottom + 4;
|
|
100
|
+
let left = rect.left;
|
|
101
|
+
const menuEl = menuRef.current;
|
|
102
|
+
if (menuEl) {
|
|
103
|
+
const menuW = menuEl.offsetWidth;
|
|
104
|
+
const menuH = menuEl.offsetHeight;
|
|
105
|
+
if (left + menuW > window.innerWidth - pad) {
|
|
106
|
+
left = window.innerWidth - menuW - pad;
|
|
107
|
+
}
|
|
108
|
+
if (left < pad)
|
|
109
|
+
left = pad;
|
|
110
|
+
if (top + menuH > window.innerHeight - pad) {
|
|
111
|
+
top = rect.top - menuH - 4;
|
|
112
|
+
}
|
|
113
|
+
if (top < pad)
|
|
114
|
+
top = pad;
|
|
115
|
+
}
|
|
116
|
+
setMenuPos({ top, left });
|
|
117
|
+
}, []);
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
if (!isOpen)
|
|
120
|
+
return;
|
|
121
|
+
updateMenuPosition();
|
|
122
|
+
requestAnimationFrame(updateMenuPosition);
|
|
123
|
+
}, [isOpen, updateMenuPosition]);
|
|
91
124
|
React.useEffect(() => {
|
|
92
125
|
const handleClickOutside = (event) => {
|
|
93
|
-
|
|
126
|
+
const target = event.target;
|
|
127
|
+
if (dropdownRef.current && !dropdownRef.current.contains(target) &&
|
|
128
|
+
(!menuRef.current || !menuRef.current.contains(target))) {
|
|
94
129
|
setIsOpen(false);
|
|
95
130
|
}
|
|
96
131
|
};
|
|
@@ -106,7 +141,6 @@ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, show
|
|
|
106
141
|
setIsOpen(false);
|
|
107
142
|
};
|
|
108
143
|
const currentOption = options.find(opt => opt.value === currentValue);
|
|
109
|
-
// Close on Escape key
|
|
110
144
|
React.useEffect(() => {
|
|
111
145
|
if (!isOpen)
|
|
112
146
|
return;
|
|
@@ -119,28 +153,28 @@ const Dropdown = ({ icon, label, options, onSelect, currentValue, disabled, show
|
|
|
119
153
|
document.addEventListener("keydown", handleKeyDown);
|
|
120
154
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
121
155
|
}, [isOpen]);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
156
|
+
const menuContent = isOpen ? (jsxRuntime.jsxs("div", { ref: menuRef, className: "rte-dropdown-menu", role: "listbox", "aria-label": label, style: {
|
|
157
|
+
position: 'fixed',
|
|
158
|
+
top: menuPos.top,
|
|
159
|
+
left: menuPos.left,
|
|
160
|
+
}, onMouseDown: (e) => e.preventDefault(), children: [options.map((option) => (jsxRuntime.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 && (jsxRuntime.jsx("span", { className: `rte-dropdown-color-preview ${currentValue === option.value ? 'active' : ''}`, style: { backgroundColor: option.color } })), option.preview && !option.headingPreview && (jsxRuntime.jsx("span", { className: "rte-dropdown-fontsize-preview", style: { fontSize: `${option.preview}px` }, children: "Aa" })), option.headingPreview && (jsxRuntime.jsx("span", { className: `rte-dropdown-heading-preview ${option.headingPreview}`, children: option.headingPreview === 'p' ? 'Normal' : option.headingPreview.toUpperCase() })), option.icon && jsxRuntime.jsx(Icon, { icon: option.icon, width: 16, height: 16 }), jsxRuntime.jsx("span", { style: { flex: 1, fontWeight: currentValue === option.value ? 600 : 400 }, children: option.label })] }, option.value))), showCustomColorInput && (jsxRuntime.jsxs("div", { className: "rte-color-custom-input", onMouseDown: (e) => e.stopPropagation(), children: [jsxRuntime.jsx("input", { type: "color", value: customColor, onChange: (e) => setCustomColor(e.target.value), title: "Pick a color" }), jsxRuntime.jsx("input", { type: "text", value: customColor, onChange: (e) => {
|
|
161
|
+
const v = e.target.value;
|
|
162
|
+
setCustomColor(v);
|
|
163
|
+
}, placeholder: "#000000", maxLength: 7, onKeyDown: (e) => {
|
|
164
|
+
if (e.key === "Enter") {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
|
|
167
|
+
handleSelect(customColor);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} }), jsxRuntime.jsx("button", { type: "button", className: "rte-color-custom-apply", onClick: () => {
|
|
171
|
+
if (/^#[0-9a-fA-F]{3,6}$/.test(customColor)) {
|
|
172
|
+
handleSelect(customColor);
|
|
173
|
+
}
|
|
174
|
+
}, children: "Apply" })] }))] })) : null;
|
|
175
|
+
return (jsxRuntime.jsxs("div", { className: "rte-dropdown", ref: dropdownRef, onMouseDown: (e) => e.preventDefault(), children: [jsxRuntime.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: [jsxRuntime.jsx(Icon, { icon: icon, width: 18, height: 18 }), currentOption && (jsxRuntime.jsx("span", { className: "rte-dropdown-value", children: currentOption.label }))] }), menuContent && reactDom.createPortal(menuContent, document.body)] }));
|
|
137
176
|
};
|
|
138
177
|
|
|
139
|
-
/** Debounce time for pushing to history after input (ms) */
|
|
140
|
-
const HISTORY_DEBOUNCE_MS = 300;
|
|
141
|
-
/** Width of the clickable checkbox area in pixels */
|
|
142
|
-
const CHECKBOX_CLICK_ZONE_PX = 40;
|
|
143
|
-
|
|
144
178
|
/**
|
|
145
179
|
* Pure DOM utility functions.
|
|
146
180
|
* No React dependencies - only native browser APIs.
|
|
@@ -393,8 +427,8 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
393
427
|
const rect = listItem.getBoundingClientRect();
|
|
394
428
|
const clientX = event.clientX;
|
|
395
429
|
const isInCheckboxArea = listItem.dir === "rtl"
|
|
396
|
-
? clientX >= rect.right
|
|
397
|
-
: clientX <= rect.left
|
|
430
|
+
? clientX >= rect.right
|
|
431
|
+
: clientX <= rect.left;
|
|
398
432
|
if (isInCheckboxArea) {
|
|
399
433
|
event.preventDefault();
|
|
400
434
|
event.stopPropagation();
|
|
@@ -421,8 +455,8 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
421
455
|
const rect = listItem.getBoundingClientRect();
|
|
422
456
|
const clientX = event.clientX;
|
|
423
457
|
const isInCheckboxArea = listItem.dir === "rtl"
|
|
424
|
-
? clientX >= rect.right
|
|
425
|
-
: clientX <= rect.left
|
|
458
|
+
? clientX >= rect.right
|
|
459
|
+
: clientX <= rect.left;
|
|
426
460
|
if (isInCheckboxArea) {
|
|
427
461
|
event.preventDefault();
|
|
428
462
|
}
|
|
@@ -680,6 +714,9 @@ function useCheckbox({ editorRef, isUpdatingRef, pushToHistory, notifyChange, ge
|
|
|
680
714
|
};
|
|
681
715
|
}
|
|
682
716
|
|
|
717
|
+
/** Debounce time for pushing to history after input (ms) */
|
|
718
|
+
const HISTORY_DEBOUNCE_MS = 300;
|
|
719
|
+
|
|
683
720
|
/**
|
|
684
721
|
* Lightweight HTML sanitizer (no external dependencies).
|
|
685
722
|
*
|
|
@@ -810,7 +847,7 @@ const ALLOWED_CONTENT_TAGS = new Set([
|
|
|
810
847
|
"ul", "ol", "li", "a", "strong", "em", "u", "s", "del",
|
|
811
848
|
"sub", "sup", "code", "pre", "blockquote", "br", "hr",
|
|
812
849
|
"img", "table", "thead", "tbody", "tr", "th", "td",
|
|
813
|
-
"b", "i", "strike",
|
|
850
|
+
"b", "i", "strike", "font",
|
|
814
851
|
]);
|
|
815
852
|
/** Checks if an attribute key is safe to set on a DOM element. */
|
|
816
853
|
function isSafeAttribute(key) {
|
|
@@ -1018,6 +1055,7 @@ function domToContent(element) {
|
|
|
1018
1055
|
"sub",
|
|
1019
1056
|
"sup",
|
|
1020
1057
|
"code",
|
|
1058
|
+
"font",
|
|
1021
1059
|
].includes(tagName)) {
|
|
1022
1060
|
const children = [];
|
|
1023
1061
|
Array.from(el.childNodes).forEach((child) => {
|
|
@@ -1107,6 +1145,46 @@ function domToContent(element) {
|
|
|
1107
1145
|
});
|
|
1108
1146
|
}
|
|
1109
1147
|
}
|
|
1148
|
+
// <font> from execCommand('foreColor') or pasted HTML
|
|
1149
|
+
if (tagName === "font") {
|
|
1150
|
+
const fontAttrs = {};
|
|
1151
|
+
const colorAttr = el.getAttribute("color");
|
|
1152
|
+
if (colorAttr)
|
|
1153
|
+
fontAttrs.color = colorAttr;
|
|
1154
|
+
const sizeAttr = el.getAttribute("size");
|
|
1155
|
+
if (sizeAttr) {
|
|
1156
|
+
const sizeMap = {
|
|
1157
|
+
"1": "10px", "2": "13px", "3": "16px", "4": "18px",
|
|
1158
|
+
"5": "24px", "6": "32px", "7": "48px",
|
|
1159
|
+
};
|
|
1160
|
+
const mapped = sizeMap[sizeAttr];
|
|
1161
|
+
if (mapped)
|
|
1162
|
+
fontAttrs.fontSize = mapped;
|
|
1163
|
+
}
|
|
1164
|
+
const style = el.getAttribute("style") || "";
|
|
1165
|
+
if (style) {
|
|
1166
|
+
style.split(";").forEach((rule) => {
|
|
1167
|
+
const [key, value] = rule
|
|
1168
|
+
.split(":")
|
|
1169
|
+
.map((s) => s.trim());
|
|
1170
|
+
if (key && value) {
|
|
1171
|
+
if (key === "font-size")
|
|
1172
|
+
fontAttrs.fontSize = value;
|
|
1173
|
+
else if (key === "color")
|
|
1174
|
+
fontAttrs.color = value;
|
|
1175
|
+
else if (key === "background-color")
|
|
1176
|
+
fontAttrs.backgroundColor = value;
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
return {
|
|
1181
|
+
type: "span",
|
|
1182
|
+
children: children.length > 0 ? children : undefined,
|
|
1183
|
+
attributes: Object.keys(fontAttrs).length > 0
|
|
1184
|
+
? fontAttrs
|
|
1185
|
+
: undefined,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1110
1188
|
// Map tag names to semantic types
|
|
1111
1189
|
const type = tagName === "strong" || tagName === "b"
|
|
1112
1190
|
? "bold"
|
|
@@ -2125,6 +2203,61 @@ function useEditorEvents({ editorRef, historyRef, isUpdatingRef, mountedRef, not
|
|
|
2125
2203
|
}, 0);
|
|
2126
2204
|
return;
|
|
2127
2205
|
}
|
|
2206
|
+
// Exit code block: Enter on empty last line escapes <pre>
|
|
2207
|
+
if (e.key === "Enter" &&
|
|
2208
|
+
!e.shiftKey &&
|
|
2209
|
+
!isModifierPressed) {
|
|
2210
|
+
const sel = window.getSelection();
|
|
2211
|
+
if (sel && sel.rangeCount > 0 && sel.isCollapsed) {
|
|
2212
|
+
const range = sel.getRangeAt(0);
|
|
2213
|
+
const node = range.startContainer;
|
|
2214
|
+
const pre = (node instanceof HTMLElement
|
|
2215
|
+
? node
|
|
2216
|
+
: node.parentElement)?.closest("pre");
|
|
2217
|
+
if (pre && pre.lastChild) {
|
|
2218
|
+
const lastChild = pre.lastChild;
|
|
2219
|
+
const cursorInPre = node === pre &&
|
|
2220
|
+
range.startOffset === pre.childNodes.length;
|
|
2221
|
+
const cursorAtEndOfLastText = node.nodeType === Node.TEXT_NODE &&
|
|
2222
|
+
node === lastChild &&
|
|
2223
|
+
range.startOffset ===
|
|
2224
|
+
(node.textContent?.length ?? 0);
|
|
2225
|
+
const isAtEnd = cursorInPre || cursorAtEndOfLastText;
|
|
2226
|
+
const lastIsBr = lastChild instanceof HTMLElement &&
|
|
2227
|
+
lastChild.tagName === "BR";
|
|
2228
|
+
const endsWithNewline = node.nodeType === Node.TEXT_NODE &&
|
|
2229
|
+
(node.textContent || "").endsWith("\n");
|
|
2230
|
+
if (isAtEnd && (lastIsBr || endsWithNewline)) {
|
|
2231
|
+
e.preventDefault();
|
|
2232
|
+
if (lastIsBr) {
|
|
2233
|
+
pre.removeChild(lastChild);
|
|
2234
|
+
}
|
|
2235
|
+
else if (node.nodeType === Node.TEXT_NODE &&
|
|
2236
|
+
node.textContent) {
|
|
2237
|
+
node.textContent =
|
|
2238
|
+
node.textContent.replace(/\n$/, "");
|
|
2239
|
+
}
|
|
2240
|
+
if (!pre.textContent &&
|
|
2241
|
+
!pre.querySelector("br")) {
|
|
2242
|
+
pre.appendChild(document.createElement("br"));
|
|
2243
|
+
}
|
|
2244
|
+
const p = document.createElement("p");
|
|
2245
|
+
p.appendChild(document.createElement("br"));
|
|
2246
|
+
pre.parentNode?.insertBefore(p, pre.nextSibling);
|
|
2247
|
+
const newRange = document.createRange();
|
|
2248
|
+
newRange.setStart(p, 0);
|
|
2249
|
+
newRange.collapse(true);
|
|
2250
|
+
sel.removeAllRanges();
|
|
2251
|
+
sel.addRange(newRange);
|
|
2252
|
+
const content = domToContent(editor);
|
|
2253
|
+
const serializedSel = serializeSelection(editor);
|
|
2254
|
+
historyRef.current.push(content, serializedSel);
|
|
2255
|
+
notifyChange(content);
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2128
2261
|
// Auto-link: convert URLs to <a> tags on space/enter
|
|
2129
2262
|
if (!isModifierPressed && (e.key === " " || e.key === "Enter")) {
|
|
2130
2263
|
handleAutoLink(editor, e);
|
|
@@ -4019,7 +4152,7 @@ function createAdvancedLinkPlugin(options = {}) {
|
|
|
4019
4152
|
/** Pre-built link plugin with just target enabled (no custom fields). */
|
|
4020
4153
|
const advancedLinkPlugin = createAdvancedLinkPlugin();
|
|
4021
4154
|
|
|
4022
|
-
const InsertTableDialog = ({ onInsert, onClose, }) => {
|
|
4155
|
+
const InsertTableDialog = ({ onInsert, onClose, anchorRect, }) => {
|
|
4023
4156
|
const [rows, setRows] = React.useState(3);
|
|
4024
4157
|
const [cols, setCols] = React.useState(3);
|
|
4025
4158
|
const dialogRef = React.useRef(null);
|
|
@@ -4033,7 +4166,25 @@ const InsertTableDialog = ({ onInsert, onClose, }) => {
|
|
|
4033
4166
|
document.addEventListener("mousedown", handler);
|
|
4034
4167
|
return () => document.removeEventListener("mousedown", handler);
|
|
4035
4168
|
}, [onClose]);
|
|
4036
|
-
|
|
4169
|
+
const style = { position: "fixed" };
|
|
4170
|
+
if (anchorRect) {
|
|
4171
|
+
const pad = 8;
|
|
4172
|
+
let top = anchorRect.bottom + 4;
|
|
4173
|
+
let left = anchorRect.left;
|
|
4174
|
+
if (left + 220 > window.innerWidth - pad) {
|
|
4175
|
+
left = window.innerWidth - 220 - pad;
|
|
4176
|
+
}
|
|
4177
|
+
if (left < pad)
|
|
4178
|
+
left = pad;
|
|
4179
|
+
if (top + 200 > window.innerHeight - pad) {
|
|
4180
|
+
top = anchorRect.top - 200 - 4;
|
|
4181
|
+
}
|
|
4182
|
+
if (top < pad)
|
|
4183
|
+
top = pad;
|
|
4184
|
+
style.top = top;
|
|
4185
|
+
style.left = left;
|
|
4186
|
+
}
|
|
4187
|
+
return reactDom.createPortal(jsxRuntime.jsxs("div", { className: "rte-table-insert-dialog", ref: dialogRef, style: style, onMouseDown: (e) => e.preventDefault(), children: [jsxRuntime.jsx("div", { className: "rte-table-insert-title", children: "Insert Table" }), jsxRuntime.jsxs("div", { className: "rte-table-insert-fields", children: [jsxRuntime.jsxs("label", { className: "rte-table-insert-label", children: [jsxRuntime.jsx("span", { children: "Zeilen" }), jsxRuntime.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() })] }), jsxRuntime.jsxs("label", { className: "rte-table-insert-label", children: [jsxRuntime.jsx("span", { children: "Spalten" }), jsxRuntime.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() })] })] }), jsxRuntime.jsx("button", { type: "button", className: "rte-table-insert-btn", onClick: () => onInsert(rows, cols), children: "Insert" })] }), document.body);
|
|
4037
4188
|
};
|
|
4038
4189
|
const TableContextMenu = ({ x, y, onClose, }) => {
|
|
4039
4190
|
const ref = React.useRef(null);
|
|
@@ -4054,6 +4205,14 @@ const TableContextMenu = ({ x, y, onClose, }) => {
|
|
|
4054
4205
|
};
|
|
4055
4206
|
const TableToolbarButton = (props) => {
|
|
4056
4207
|
const [showDialog, setShowDialog] = React.useState(false);
|
|
4208
|
+
const btnRef = React.useRef(null);
|
|
4209
|
+
const [anchorRect, setAnchorRect] = React.useState(null);
|
|
4210
|
+
const handleToggle = React.useCallback(() => {
|
|
4211
|
+
if (!showDialog && btnRef.current) {
|
|
4212
|
+
setAnchorRect(btnRef.current.getBoundingClientRect());
|
|
4213
|
+
}
|
|
4214
|
+
setShowDialog((v) => !v);
|
|
4215
|
+
}, [showDialog]);
|
|
4057
4216
|
const handleInsert = React.useCallback((rows, cols) => {
|
|
4058
4217
|
setShowDialog(false);
|
|
4059
4218
|
if (!props.editorAPI)
|
|
@@ -4062,7 +4221,6 @@ const TableToolbarButton = (props) => {
|
|
|
4062
4221
|
if (!sel || sel.rangeCount === 0)
|
|
4063
4222
|
return;
|
|
4064
4223
|
const range = sel.getRangeAt(0);
|
|
4065
|
-
// Find the editor's contentEditable root
|
|
4066
4224
|
const container = range.commonAncestorContainer;
|
|
4067
4225
|
const editorEl = container.nodeType === Node.TEXT_NODE
|
|
4068
4226
|
? container.parentElement
|
|
@@ -4071,7 +4229,6 @@ const TableToolbarButton = (props) => {
|
|
|
4071
4229
|
if (!editorRoot)
|
|
4072
4230
|
return;
|
|
4073
4231
|
const table = createTable(rows, cols);
|
|
4074
|
-
// Insert after the current block element
|
|
4075
4232
|
let block = editorEl;
|
|
4076
4233
|
while (block &&
|
|
4077
4234
|
block !== editorRoot &&
|
|
@@ -4084,11 +4241,9 @@ const TableToolbarButton = (props) => {
|
|
|
4084
4241
|
else {
|
|
4085
4242
|
editorRoot.appendChild(table);
|
|
4086
4243
|
}
|
|
4087
|
-
// Add a paragraph after the table so the user can continue typing
|
|
4088
4244
|
const p = document.createElement("p");
|
|
4089
4245
|
p.innerHTML = "<br>";
|
|
4090
4246
|
table.parentNode?.insertBefore(p, table.nextSibling);
|
|
4091
|
-
// Focus the first cell
|
|
4092
4247
|
const firstCell = table.querySelector("td, th");
|
|
4093
4248
|
if (firstCell) {
|
|
4094
4249
|
const newRange = document.createRange();
|
|
@@ -4098,7 +4253,7 @@ const TableToolbarButton = (props) => {
|
|
|
4098
4253
|
sel.addRange(newRange);
|
|
4099
4254
|
}
|
|
4100
4255
|
}, [props.editorAPI]);
|
|
4101
|
-
return (jsxRuntime.jsxs(
|
|
4256
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.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: jsxRuntime.jsx(IconWrapper, { icon: "mdi:table", width: 18, height: 18 }) }), showDialog && (jsxRuntime.jsx(InsertTableDialog, { onInsert: handleInsert, onClose: () => setShowDialog(false), anchorRect: anchorRect }))] }));
|
|
4102
4257
|
};
|
|
4103
4258
|
/* ══════════════════════════════════════════════════════════════════════════
|
|
4104
4259
|
Table Plugin export
|
|
@@ -4308,10 +4463,10 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
|
|
|
4308
4463
|
}
|
|
4309
4464
|
// Center horizontally on the selection, clamp to viewport
|
|
4310
4465
|
let left = rect.left + rect.width / 2 - toolbarW / 2;
|
|
4311
|
-
if (left < pad)
|
|
4312
|
-
left = pad;
|
|
4313
4466
|
if (left + toolbarW > vw - pad)
|
|
4314
4467
|
left = vw - toolbarW - pad;
|
|
4468
|
+
if (left < pad)
|
|
4469
|
+
left = pad;
|
|
4315
4470
|
setPos({ top, left, visible: true });
|
|
4316
4471
|
}, [editorElement]);
|
|
4317
4472
|
// Debounce via requestAnimationFrame for smooth repositioning
|
|
@@ -4356,16 +4511,14 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
|
|
|
4356
4511
|
requestAnimationFrame(() => scheduleUpdate());
|
|
4357
4512
|
}, [editorAPI, scheduleUpdate]);
|
|
4358
4513
|
const isHidden = !pos.visible || leftPlugins.length === 0;
|
|
4359
|
-
return (jsxRuntime.jsx("div", { ref: toolbarRef, className: "rte-floating-toolbar", style: {
|
|
4514
|
+
return reactDom.createPortal(jsxRuntime.jsx("div", { ref: toolbarRef, className: "rte-floating-toolbar", style: {
|
|
4360
4515
|
position: "fixed",
|
|
4361
4516
|
top: pos.top,
|
|
4362
4517
|
left: pos.left,
|
|
4363
4518
|
visibility: isHidden ? "hidden" : "visible",
|
|
4364
4519
|
opacity: isHidden ? 0 : 1,
|
|
4365
4520
|
pointerEvents: isHidden ? "none" : "auto",
|
|
4366
|
-
},
|
|
4367
|
-
// Prevent selection loss when clicking toolbar buttons
|
|
4368
|
-
onMouseDown: (e) => e.preventDefault(), children: jsxRuntime.jsxs("div", { className: "rte-floating-toolbar-content", children: [jsxRuntime.jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
|
|
4521
|
+
}, onMouseDown: (e) => e.preventDefault(), children: jsxRuntime.jsxs("div", { className: "rte-floating-toolbar-content", children: [jsxRuntime.jsx("div", { className: "rte-toolbar-left", children: leftPlugins.map((plugin) => {
|
|
4369
4522
|
if (!plugin.renderButton)
|
|
4370
4523
|
return null;
|
|
4371
4524
|
const isActive = plugin.isActive
|
|
@@ -4399,7 +4552,7 @@ const FloatingToolbar = ({ plugins, editorAPI, editorElement, }) => {
|
|
|
4399
4552
|
disabled: !canExecute,
|
|
4400
4553
|
editorAPI,
|
|
4401
4554
|
});
|
|
4402
|
-
})()] }))] }) }));
|
|
4555
|
+
})()] }))] }) }), document.body);
|
|
4403
4556
|
};
|
|
4404
4557
|
|
|
4405
4558
|
/**
|