@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.
- package/dist/components/Dropdown.d.ts.map +1 -1
- package/dist/components/FloatingToolbar.d.ts.map +1 -1
- package/dist/hooks/useEditorEvents.d.ts.map +1 -1
- package/dist/index.esm.js +188 -33
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +187 -32
- package/dist/index.js.map +1 -1
- package/dist/plugins/table.d.ts.map +1 -1
- package/dist/styles.css +18 -14
- package/dist/utils/content.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
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;
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
/**
|