smartrte-react 0.2.5 → 0.2.7

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.
@@ -50,6 +50,11 @@ type ClassicEditorProps = {
50
50
  * - "dark": Uses the built-in dark theme.
51
51
  */
52
52
  theme?: SrteTheme;
53
+ /**
54
+ * Show the inline font-size dropdown in the toolbar.
55
+ * Defaults to true. Heading block controls are independent of this option.
56
+ */
57
+ showFontSize?: boolean;
53
58
  /**
54
59
  * Additional CSS class name(s) to apply to the editor's root element.
55
60
  * Useful for custom theming by overriding CSS custom properties.
@@ -57,5 +62,5 @@ type ClassicEditorProps = {
57
62
  */
58
63
  className?: string;
59
64
  };
60
- export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, fonts, defaultFont, preserveFontFamily, preserveColors, preserveDocxStyles, theme, className, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
65
+ export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, fonts, defaultFont, preserveFontFamily, preserveColors, preserveDocxStyles, theme, showFontSize, className, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
61
66
  export {};
@@ -17,7 +17,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
17
17
  { name: "Times New Roman", value: "'Times New Roman', Times, serif" },
18
18
  { name: "Verdana", value: "Verdana, Geneva, sans-serif" },
19
19
  { name: "Courier New", value: "'Courier New', Courier, monospace" },
20
- ], defaultFont, preserveFontFamily = false, preserveColors = false, preserveDocxStyles = true, theme = "light", className, }) {
20
+ ], defaultFont, preserveFontFamily = false, preserveColors = false, preserveDocxStyles = true, theme = "light", showFontSize = true, className, }) {
21
21
  ensureStyleSheet();
22
22
  const editableRef = useRef(null);
23
23
  const editorScrollRef = useRef(null);
@@ -37,6 +37,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
37
37
  const [imageOverlay, setImageOverlay] = useState(null);
38
38
  const resizingRef = useRef(null);
39
39
  const draggedImageRef = useRef(null);
40
+ const draggedBlockRef = useRef(null);
41
+ const dragHandleHideTimerRef = useRef(null);
42
+ const [dragHandle, setDragHandle] = useState(null);
40
43
  const tableResizeRef = useRef(null);
41
44
  const [showTableDialog, setShowTableDialog] = useState(false);
42
45
  const [tableRows, setTableRows] = useState(3);
@@ -52,8 +55,13 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
52
55
  const [showSpecialChars, setShowSpecialChars] = useState(false);
53
56
  const [colorPickerType, setColorPickerType] = useState('text');
54
57
  const savedRangeRef = useRef(null);
58
+ const historyRef = useRef({
59
+ undo: [],
60
+ redo: [],
61
+ });
55
62
  const [currentFontSize, setCurrentFontSize] = useState("");
56
63
  const [currentFont, setCurrentFont] = useState("");
64
+ const [currentBlockType, setCurrentBlockType] = useState("p");
57
65
  const [activeState, setActiveState] = useState({
58
66
  bold: false,
59
67
  italic: false,
@@ -104,6 +112,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
104
112
  if (node.nodeType === Node.TEXT_NODE)
105
113
  node = node.parentNode;
106
114
  const element = node instanceof HTMLElement ? node : null;
115
+ const block = element?.closest("p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,div");
116
+ const tag = block?.tagName.toLowerCase();
117
+ setCurrentBlockType(tag === "h1" || tag === "h2" || tag === "h3" ? tag : "p");
107
118
  setActiveState({
108
119
  bold: document.queryCommandState("bold"),
109
120
  italic: document.queryCommandState("italic"),
@@ -139,6 +150,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
139
150
  }, []);
140
151
  const exec = (command, valueArg) => {
141
152
  try {
153
+ if (command === "undo" && restoreEditorHistory("undo"))
154
+ return;
155
+ if (command === "redo" && restoreEditorHistory("redo"))
156
+ return;
142
157
  if (!restoreSavedSelection()) {
143
158
  safeSelectRange(getSelectionRangeInEditor());
144
159
  }
@@ -164,7 +179,22 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
164
179
  catch { }
165
180
  };
166
181
  const applyFormatBlock = (blockName) => {
167
- exec("formatBlock", blockName);
182
+ try {
183
+ if (!restoreSavedSelection()) {
184
+ safeSelectRange(getSelectionRangeInEditor());
185
+ }
186
+ if (applyFormatBlockFallback(blockName)) {
187
+ const tag = normalizeBlockTag(blockName);
188
+ if (tag === "p" || tag === "h1" || tag === "h2" || tag === "h3") {
189
+ setCurrentBlockType(tag);
190
+ }
191
+ handleInput();
192
+ requestAnimationFrame(updateActiveState);
193
+ return;
194
+ }
195
+ exec("formatBlock", blockName);
196
+ }
197
+ catch { }
168
198
  };
169
199
  const emitChange = () => {
170
200
  const el = editableRef.current;
@@ -176,6 +206,63 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
176
206
  onChange(html);
177
207
  }
178
208
  };
209
+ const pushEditorHistory = () => {
210
+ const editor = editableRef.current;
211
+ if (!editor)
212
+ return;
213
+ const selectionCells = selectionRef.current
214
+ ? getCellsInGridRect(selectionRef.current.tbody, selectionRef.current.sr, selectionRef.current.sc, selectionRef.current.er, selectionRef.current.ec)
215
+ : [];
216
+ const decor = selectionCells.map((cell) => ({
217
+ cell,
218
+ background: cell.style.background,
219
+ outline: cell.style.outline,
220
+ outlineOffset: cell.style.outlineOffset,
221
+ previousBackground: cell.__rtePrevBg,
222
+ }));
223
+ decor.forEach(({ cell, previousBackground }) => {
224
+ if (previousBackground != null)
225
+ cell.style.background = previousBackground || "";
226
+ cell.style.outline = "";
227
+ cell.style.outlineOffset = "";
228
+ });
229
+ const html = editor.innerHTML;
230
+ decor.forEach(({ cell, background, outline, outlineOffset }) => {
231
+ cell.style.background = background;
232
+ cell.style.outline = outline;
233
+ cell.style.outlineOffset = outlineOffset;
234
+ });
235
+ const undo = historyRef.current.undo;
236
+ if (undo[undo.length - 1] !== html) {
237
+ undo.push(html);
238
+ if (undo.length > 100)
239
+ undo.shift();
240
+ }
241
+ historyRef.current.redo = [];
242
+ };
243
+ const restoreEditorHistory = (dir) => {
244
+ const editor = editableRef.current;
245
+ if (!editor)
246
+ return false;
247
+ const history = historyRef.current;
248
+ const from = dir === "undo" ? history.undo : history.redo;
249
+ const to = dir === "undo" ? history.redo : history.undo;
250
+ const html = from.pop();
251
+ if (html == null)
252
+ return false;
253
+ to.push(editor.innerHTML);
254
+ editor.innerHTML = html;
255
+ fixNegativeMargins(editor);
256
+ ensureTableWrappers(editor);
257
+ addTableResizeHandles();
258
+ clearSelectionDecor();
259
+ setTableMenu(null);
260
+ if (html !== lastEmittedRef.current) {
261
+ lastEmittedRef.current = html;
262
+ onChange?.(html);
263
+ }
264
+ return true;
265
+ };
179
266
  const restoreSavedSelection = () => {
180
267
  const editor = editableRef.current;
181
268
  if (!editor)
@@ -321,19 +408,71 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
321
408
  cell.parentElement?.replaceChild(replacement, cell);
322
409
  return replacement;
323
410
  };
324
- const applyFormatBlockFallback = (blockName) => {
325
- const editor = editableRef.current;
326
- const block = getCurrentBlock();
411
+ const normalizeBlockTag = (blockName) => {
327
412
  const tag = blockName.replace(/[<>]/g, "").toLowerCase() || "p";
328
- if (!editor || !block || block === editor || !/^(p|h1|h2|h3|h4|h5|h6|pre|blockquote)$/.test(tag))
329
- return;
413
+ return /^(p|h1|h2|h3|h4|h5|h6|pre|blockquote)$/.test(tag) ? tag : null;
414
+ };
415
+ const sortInDocumentOrder = (elements) => {
416
+ return [...elements].sort((a, b) => {
417
+ if (a === b)
418
+ return 0;
419
+ const position = a.compareDocumentPosition(b);
420
+ return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
421
+ });
422
+ };
423
+ const replaceBlockTag = (block, tag) => {
424
+ if (block.tagName.toLowerCase() === tag)
425
+ return block;
330
426
  const replacement = document.createElement(tag);
331
427
  copyCellOrBlockStyles(block, replacement);
332
428
  block.parentElement?.replaceChild(replacement, block);
333
- const range = document.createRange();
334
- range.selectNodeContents(replacement);
335
- range.collapse(false);
336
- safeSelectRange(range);
429
+ return replacement;
430
+ };
431
+ const applyFormatBlockFallback = (blockName) => {
432
+ const editor = editableRef.current;
433
+ const range = getSelectionRangeInEditor();
434
+ const tag = normalizeBlockTag(blockName);
435
+ if (!editor || !range || !tag)
436
+ return false;
437
+ if (!editor.innerHTML.trim()) {
438
+ const block = document.createElement(tag);
439
+ block.innerHTML = "<br>";
440
+ editor.appendChild(block);
441
+ focusElementEnd(block);
442
+ return true;
443
+ }
444
+ if (range.collapsed) {
445
+ const block = getCurrentBlock();
446
+ if (!block || block === editor || block.closest("ul,ol") || !block.parentElement)
447
+ return false;
448
+ const replacement = replaceBlockTag(block, tag);
449
+ focusElementEnd(replacement);
450
+ return true;
451
+ }
452
+ const selectedBlocks = sortInDocumentOrder(getSelectedBlocks(range)).filter((block) => {
453
+ if (!editor.contains(block) || block === editor)
454
+ return false;
455
+ if (block.closest("ul,ol"))
456
+ return false;
457
+ return Boolean(block.parentElement);
458
+ });
459
+ if (selectedBlocks.length > 0) {
460
+ let lastReplacement = null;
461
+ selectedBlocks.forEach((block) => {
462
+ lastReplacement = replaceBlockTag(block, tag);
463
+ });
464
+ if (lastReplacement)
465
+ focusElementEnd(lastReplacement);
466
+ return true;
467
+ }
468
+ const block = document.createElement(tag);
469
+ const contents = range.extractContents();
470
+ block.appendChild(contents);
471
+ if (!block.innerHTML.trim())
472
+ block.innerHTML = "<br>";
473
+ range.insertNode(block);
474
+ focusElementEnd(block);
475
+ return true;
337
476
  };
338
477
  const cloneListShell = (list, tagName) => {
339
478
  const clone = document.createElement(tagName || list.tagName.toLowerCase());
@@ -544,7 +683,50 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
544
683
  };
545
684
  const toggleBlockquote = () => {
546
685
  try {
686
+ if (!restoreSavedSelection())
687
+ safeSelectRange(getSelectionRangeInEditor());
547
688
  const range = getSelectionRangeInEditor();
689
+ const editor = editableRef.current;
690
+ if (!editor || !range)
691
+ return;
692
+ const unwrapQuote = (quote) => {
693
+ const parent = quote.parentElement;
694
+ if (!parent)
695
+ return false;
696
+ const moved = [];
697
+ while (quote.firstChild) {
698
+ const child = quote.firstChild;
699
+ parent.insertBefore(child, quote);
700
+ if (child instanceof HTMLElement)
701
+ moved.push(child);
702
+ }
703
+ quote.remove();
704
+ focusElementEnd(moved[moved.length - 1] || parent);
705
+ return true;
706
+ };
707
+ const wrapBlocks = (blocks) => {
708
+ const selected = sortInDocumentOrder(blocks).filter((block) => {
709
+ if (!editor.contains(block) || block === editor)
710
+ return false;
711
+ if (block.closest("ul,ol"))
712
+ return false;
713
+ if (block.tagName.toLowerCase() === "blockquote")
714
+ return false;
715
+ return Boolean(block.parentElement);
716
+ });
717
+ if (selected.length === 0)
718
+ return false;
719
+ let lastWrapped = null;
720
+ selected.forEach((block) => {
721
+ const quote = document.createElement("blockquote");
722
+ block.parentElement?.insertBefore(quote, block);
723
+ quote.appendChild(block);
724
+ lastWrapped = block;
725
+ });
726
+ if (lastWrapped)
727
+ focusElementEnd(lastWrapped);
728
+ return true;
729
+ };
548
730
  if (!range)
549
731
  return;
550
732
  let node = range.commonAncestorContainer;
@@ -553,16 +735,36 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
553
735
  const element = node;
554
736
  const quote = element?.closest?.('blockquote');
555
737
  if (quote && editableRef.current?.contains(quote)) {
556
- const replacement = document.createElement('p');
557
- replacement.innerHTML = quote.innerHTML || '<br>';
558
- quote.parentElement?.replaceChild(replacement, quote);
559
- const nextRange = document.createRange();
560
- nextRange.selectNodeContents(replacement);
561
- nextRange.collapse(false);
562
- safeSelectRange(nextRange);
563
- handleInput();
564
- return;
738
+ pushEditorHistory();
739
+ if (unwrapQuote(quote)) {
740
+ handleInput();
741
+ requestAnimationFrame(updateActiveState);
742
+ return;
743
+ }
565
744
  }
745
+ if (range.collapsed) {
746
+ const block = getCurrentBlock();
747
+ if (block) {
748
+ pushEditorHistory();
749
+ if (!wrapBlocks([block]))
750
+ return;
751
+ handleInput();
752
+ requestAnimationFrame(updateActiveState);
753
+ return;
754
+ }
755
+ }
756
+ else {
757
+ const blocks = getSelectedBlocks(range);
758
+ if (blocks.length > 0) {
759
+ pushEditorHistory();
760
+ if (!wrapBlocks(blocks))
761
+ return;
762
+ handleInput();
763
+ requestAnimationFrame(updateActiveState);
764
+ return;
765
+ }
766
+ }
767
+ pushEditorHistory();
566
768
  exec("formatBlock", "<blockquote>");
567
769
  }
568
770
  catch { }
@@ -685,8 +887,158 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
685
887
  const applyTextColor = (color) => {
686
888
  exec("foreColor", color);
687
889
  };
890
+ const parseCssColor = (value) => {
891
+ const normalized = value.trim().toLowerCase();
892
+ if (!normalized || normalized === "transparent")
893
+ return null;
894
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(normalized);
895
+ if (hex) {
896
+ const raw = hex[1].length === 3
897
+ ? hex[1].split("").map((part) => part + part).join("")
898
+ : hex[1];
899
+ return {
900
+ r: parseInt(raw.slice(0, 2), 16),
901
+ g: parseInt(raw.slice(2, 4), 16),
902
+ b: parseInt(raw.slice(4, 6), 16),
903
+ };
904
+ }
905
+ const rgb = /^rgba?\(([^)]+)\)$/.exec(normalized);
906
+ if (!rgb)
907
+ return null;
908
+ const parts = rgb[1].split(",").map((part) => Number(part.trim()));
909
+ if (parts.length < 3 || parts.some((part) => !Number.isFinite(part)))
910
+ return null;
911
+ if (parts.length >= 4 && parts[3] === 0)
912
+ return null;
913
+ return {
914
+ r: Math.max(0, Math.min(255, parts[0])),
915
+ g: Math.max(0, Math.min(255, parts[1])),
916
+ b: Math.max(0, Math.min(255, parts[2])),
917
+ };
918
+ };
919
+ const cssColorToHex = (value) => {
920
+ const color = parseCssColor(value);
921
+ if (!color)
922
+ return "";
923
+ const toHex = (part) => Math.round(part).toString(16).padStart(2, "0");
924
+ return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;
925
+ };
926
+ const getRangeStartElement = (range) => {
927
+ const editor = editableRef.current;
928
+ if (!editor || !range)
929
+ return null;
930
+ let node = range.startContainer;
931
+ if (node === editor && editor.childNodes[range.startOffset]) {
932
+ node = editor.childNodes[range.startOffset];
933
+ }
934
+ if (node.nodeType === Node.TEXT_NODE)
935
+ node = node.parentElement;
936
+ return node instanceof HTMLElement && editor.contains(node) ? node : null;
937
+ };
938
+ const getInlineBackgroundHex = (element) => {
939
+ const editor = editableRef.current;
940
+ let current = element;
941
+ while (current && current !== editor) {
942
+ const inlineHex = cssColorToHex(current.style.backgroundColor || current.style.background);
943
+ if (inlineHex)
944
+ return inlineHex;
945
+ current = current.parentElement;
946
+ }
947
+ return "";
948
+ };
949
+ const currentPickerColorHex = () => {
950
+ if (typeof window === "undefined")
951
+ return colorPickerType === "text" ? "#000000" : "#ffffff";
952
+ const editor = editableRef.current;
953
+ if (!editor)
954
+ return colorPickerType === "text" ? "#000000" : "#ffffff";
955
+ const element = getRangeStartElement(getSelectionRangeInEditor());
956
+ if (colorPickerType === "background") {
957
+ return getInlineBackgroundHex(element) || "#ffffff";
958
+ }
959
+ const target = element || editor;
960
+ return cssColorToHex(window.getComputedStyle(target).color) || "#000000";
961
+ };
962
+ const relativeLuminance = (color) => {
963
+ const channel = (value) => {
964
+ const next = value / 255;
965
+ return next <= 0.03928 ? next / 12.92 : Math.pow((next + 0.055) / 1.055, 2.4);
966
+ };
967
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
968
+ };
969
+ const contrastRatio = (fg, bg) => {
970
+ const lighter = Math.max(relativeLuminance(fg), relativeLuminance(bg));
971
+ const darker = Math.min(relativeLuminance(fg), relativeLuminance(bg));
972
+ return (lighter + 0.05) / (darker + 0.05);
973
+ };
974
+ const readableTextColorForBackground = (background) => {
975
+ const bg = parseCssColor(background);
976
+ if (!bg)
977
+ return "";
978
+ const candidates = ["#111827", "#f9fafb", "#1f2937", "#ffffff", "#000000", "#374151", "#e5e7eb"];
979
+ return candidates
980
+ .map((candidate) => ({
981
+ color: candidate,
982
+ ratio: contrastRatio(parseCssColor(candidate), bg),
983
+ }))
984
+ .sort((a, b) => b.ratio - a.ratio)[0]?.color || "";
985
+ };
688
986
  const applyBackgroundColor = (color) => {
689
- exec("hiliteColor", color);
987
+ try {
988
+ const editor = editableRef.current;
989
+ if (!editor)
990
+ return;
991
+ if (!restoreSavedSelection())
992
+ safeSelectRange(getSelectionRangeInEditor());
993
+ const range = getSelectionRangeInEditor();
994
+ const readableColor = readableTextColorForBackground(color);
995
+ if (!range)
996
+ return;
997
+ const applyToElement = (element) => {
998
+ element.style.backgroundColor = color;
999
+ if (readableColor)
1000
+ element.style.color = readableColor;
1001
+ };
1002
+ if (range.collapsed) {
1003
+ const span = document.createElement("span");
1004
+ applyToElement(span);
1005
+ span.textContent = "\u200B";
1006
+ range.insertNode(span);
1007
+ const nextRange = document.createRange();
1008
+ nextRange.setStart(span.firstChild || span, 1);
1009
+ nextRange.collapse(true);
1010
+ safeSelectRange(nextRange);
1011
+ savedRangeRef.current = nextRange.cloneRange();
1012
+ handleInput();
1013
+ return;
1014
+ }
1015
+ const selectedCells = Array.from(editor.querySelectorAll("td,th"))
1016
+ .filter((cell) => {
1017
+ try {
1018
+ return range.intersectsNode(cell);
1019
+ }
1020
+ catch {
1021
+ return false;
1022
+ }
1023
+ });
1024
+ if (selectedCells.length > 0) {
1025
+ selectedCells.forEach(applyToElement);
1026
+ handleInput();
1027
+ return;
1028
+ }
1029
+ const span = document.createElement("span");
1030
+ applyToElement(span);
1031
+ const fragment = range.extractContents();
1032
+ span.appendChild(fragment);
1033
+ range.insertNode(span);
1034
+ range.selectNodeContents(span);
1035
+ safeSelectRange(range);
1036
+ savedRangeRef.current = range.cloneRange();
1037
+ handleInput();
1038
+ }
1039
+ catch {
1040
+ exec("hiliteColor", color);
1041
+ }
690
1042
  };
691
1043
  const insertImage = () => {
692
1044
  if (!media)
@@ -1788,9 +2140,123 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1788
2140
  link.remove();
1789
2141
  URL.revokeObjectURL(url);
1790
2142
  };
2143
+ const buildStandaloneHtmlDocument = (html) => `<!doctype html>
2144
+ <html lang="en">
2145
+ <head>
2146
+ <meta charset="utf-8">
2147
+ <meta name="viewport" content="width=device-width, initial-scale=1">
2148
+ <title>Smart RTE Export</title>
2149
+ <style>
2150
+ :root {
2151
+ --srte-bg: #ffffff;
2152
+ --srte-text: #111111;
2153
+ --srte-text-muted: #4b5563;
2154
+ --srte-border: #d1d5db;
2155
+ --srte-border-light: #e5e7eb;
2156
+ --srte-accent: #1e90ff;
2157
+ --srte-accent-bg: rgba(30, 144, 255, 0.15);
2158
+ --srte-surface-subtle: #f3f4f6;
2159
+ }
2160
+ html, body {
2161
+ margin: 0;
2162
+ background: var(--srte-bg);
2163
+ color: var(--srte-text);
2164
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2165
+ line-height: 1.6;
2166
+ }
2167
+ body {
2168
+ padding: 32px;
2169
+ }
2170
+ .srte-export {
2171
+ max-width: 960px;
2172
+ margin: 0 auto;
2173
+ }
2174
+ .srte-export h1,
2175
+ .srte-export h2,
2176
+ .srte-export h3,
2177
+ .srte-export h4,
2178
+ .srte-export h5,
2179
+ .srte-export h6 {
2180
+ line-height: 1.3;
2181
+ margin: 1.25em 0 0.5em;
2182
+ font-weight: 700;
2183
+ color: var(--srte-text);
2184
+ }
2185
+ .srte-export p {
2186
+ margin: 0 0 0.85em;
2187
+ }
2188
+ .srte-export blockquote {
2189
+ border-left: 4px solid var(--srte-accent);
2190
+ margin: 0.75em 0;
2191
+ padding: 0.5em 1em;
2192
+ background: var(--srte-surface-subtle);
2193
+ color: var(--srte-text);
2194
+ }
2195
+ .srte-export ul,
2196
+ .srte-export ol {
2197
+ margin: 0.75em 0;
2198
+ padding-left: 1.75em;
2199
+ list-style-position: outside;
2200
+ }
2201
+ .srte-export ul {
2202
+ list-style-type: disc;
2203
+ }
2204
+ .srte-export ol {
2205
+ list-style-type: decimal;
2206
+ }
2207
+ .srte-export li {
2208
+ display: list-item;
2209
+ margin: 0.25em 0;
2210
+ padding-left: 0.25em;
2211
+ }
2212
+ .srte-export table {
2213
+ border-collapse: collapse;
2214
+ width: 100%;
2215
+ margin: 12px 0;
2216
+ }
2217
+ .srte-export td,
2218
+ .srte-export th {
2219
+ border: 1px solid var(--srte-border);
2220
+ padding: 8px;
2221
+ vertical-align: top;
2222
+ text-align: left;
2223
+ }
2224
+ .srte-export th {
2225
+ background: var(--srte-surface-subtle);
2226
+ font-weight: 700;
2227
+ }
2228
+ .srte-export img {
2229
+ max-width: 100%;
2230
+ height: auto;
2231
+ }
2232
+ .srte-export pre,
2233
+ .srte-export code {
2234
+ background: var(--srte-surface-subtle);
2235
+ color: var(--srte-text);
2236
+ border-radius: 4px;
2237
+ }
2238
+ .srte-export pre {
2239
+ padding: 12px;
2240
+ overflow-x: auto;
2241
+ white-space: pre-wrap;
2242
+ }
2243
+ @media print {
2244
+ body {
2245
+ padding: 0;
2246
+ }
2247
+ .srte-export {
2248
+ max-width: none;
2249
+ }
2250
+ }
2251
+ </style>
2252
+ </head>
2253
+ <body>
2254
+ <main class="srte-export">${html || "<p></p>"}</main>
2255
+ </body>
2256
+ </html>`;
1791
2257
  const exportHtml = () => {
1792
2258
  const html = editableRef.current?.innerHTML || "";
1793
- downloadText("smart-rte-export.html", html, "text/html");
2259
+ downloadText("smart-rte-export.html", buildStandaloneHtmlDocument(html), "text/html;charset=utf-8");
1794
2260
  };
1795
2261
  const exportMarkdown = () => {
1796
2262
  const html = editableRef.current?.innerHTML || "";
@@ -2121,6 +2587,35 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2121
2587
  console.error("Error wrapping tables", e);
2122
2588
  }
2123
2589
  };
2590
+ const isCaretBoundaryBlock = (node) => {
2591
+ if (!(node instanceof HTMLElement))
2592
+ return false;
2593
+ const tag = node.tagName.toLowerCase();
2594
+ return (tag === "blockquote" ||
2595
+ tag === "table" ||
2596
+ node.getAttribute("data-table-wrapper") === "true");
2597
+ };
2598
+ const isEmptyCaretParagraph = (node) => {
2599
+ if (!(node instanceof HTMLParagraphElement))
2600
+ return false;
2601
+ return !node.textContent?.trim();
2602
+ };
2603
+ const createCaretParagraph = () => {
2604
+ const paragraph = document.createElement("p");
2605
+ paragraph.innerHTML = "<br>";
2606
+ paragraph.setAttribute("data-srte-caret-boundary", "true");
2607
+ return paragraph;
2608
+ };
2609
+ const ensureCaretBoundaryParagraphs = (root) => {
2610
+ const first = root.firstElementChild;
2611
+ if (isCaretBoundaryBlock(first) && !isEmptyCaretParagraph(first.previousSibling)) {
2612
+ root.insertBefore(createCaretParagraph(), first);
2613
+ }
2614
+ const last = root.lastElementChild;
2615
+ if (isCaretBoundaryBlock(last) && !isEmptyCaretParagraph(last.nextSibling)) {
2616
+ root.appendChild(createCaretParagraph());
2617
+ }
2618
+ };
2124
2619
  const handleInput = () => {
2125
2620
  if (isComposingRef.current)
2126
2621
  return;
@@ -2131,6 +2626,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2131
2626
  fixNegativeMargins(el);
2132
2627
  // Ensure tables are wrapped for horizontal scrolling
2133
2628
  ensureTableWrappers(el);
2629
+ // Keep a reachable typing position around isolating blocks at document edges
2630
+ ensureCaretBoundaryParagraphs(el);
2134
2631
  // Add resize handles to tables
2135
2632
  addTableResizeHandles();
2136
2633
  if (!onChange)
@@ -2161,8 +2658,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2161
2658
  const el = editableRef.current;
2162
2659
  if (!el)
2163
2660
  return;
2164
- // Ensure editor is focused and selection is inside
2165
- el.focus();
2661
+ if (!restoreSavedSelection()) {
2662
+ el.focus();
2663
+ }
2166
2664
  let sel = window.getSelection();
2167
2665
  let range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
2168
2666
  if (!range || !el.contains(range.commonAncestorContainer)) {
@@ -2181,10 +2679,14 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2181
2679
  const node = wrapper.firstChild;
2182
2680
  if (!node || !range)
2183
2681
  return;
2682
+ pushEditorHistory();
2683
+ if (!range.collapsed)
2684
+ range.deleteContents();
2184
2685
  range.insertNode(node);
2185
2686
  // Add resize handles to the new table
2186
- if (node instanceof HTMLTableElement) {
2187
- const tbody = node.querySelector('tbody');
2687
+ const insertedTable = node instanceof HTMLTableElement ? node : node.querySelector("table");
2688
+ if (insertedTable) {
2689
+ const tbody = insertedTable.querySelector('tbody');
2188
2690
  if (tbody) {
2189
2691
  const rows = Array.from(tbody.querySelectorAll('tr'));
2190
2692
  rows.forEach((row, index) => {
@@ -2339,6 +2841,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2339
2841
  const anchor = grid[sr]?.[sc];
2340
2842
  if (!anchor)
2341
2843
  return;
2844
+ pushEditorHistory();
2342
2845
  // Collect content and remove other cells
2343
2846
  const contents = [];
2344
2847
  const cellsToMerge = getCellsInGridRect(tbody, sr, sc, er, ec);
@@ -2526,19 +3029,43 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2526
3029
  handleInput();
2527
3030
  };
2528
3031
  const applyBgToSelection = (hex, fallbackCell) => {
3032
+ const readableColor = readableTextColorForBackground(hex);
3033
+ const applyFill = (cell) => {
3034
+ cell.style.background = hex;
3035
+ if (readableColor)
3036
+ cell.style.color = readableColor;
3037
+ };
2529
3038
  const sel = shouldUseTableSelection(fallbackCell) ? selectionRef.current : null;
2530
3039
  if (sel) {
2531
3040
  const cells = getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec);
2532
3041
  clearSelectionDecor();
2533
- cells.forEach((cell) => {
2534
- cell.style.background = hex;
2535
- });
3042
+ cells.forEach(applyFill);
2536
3043
  }
2537
3044
  else if (fallbackCell) {
2538
3045
  clearSelectionDecor();
2539
- fallbackCell.style.background = hex;
3046
+ applyFill(fallbackCell);
2540
3047
  }
2541
3048
  };
3049
+ const tableCellFillHex = (cell) => {
3050
+ const storedSelectionBackground = cell.__rtePrevBg;
3051
+ const inlineBackground = typeof storedSelectionBackground === "string"
3052
+ ? storedSelectionBackground
3053
+ : cell.style.backgroundColor || cell.style.background;
3054
+ const inlineHex = cssColorToHex(inlineBackground || "");
3055
+ if (inlineHex)
3056
+ return inlineHex;
3057
+ const computedHex = cssColorToHex(window.getComputedStyle(cell).backgroundColor);
3058
+ return computedHex || "#ffffff";
3059
+ };
3060
+ const tableMenuFillHex = (cell) => {
3061
+ const sel = shouldUseTableSelection(cell) ? selectionRef.current : null;
3062
+ if (!sel)
3063
+ return tableCellFillHex(cell);
3064
+ const cells = getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec);
3065
+ const first = cells[0] ? tableCellFillHex(cells[0]) : tableCellFillHex(cell);
3066
+ const allSame = cells.every((candidate) => tableCellFillHex(candidate) === first);
3067
+ return allSame ? first : tableCellFillHex(cell);
3068
+ };
2542
3069
  const toggleBorderSelection = (fallbackCell) => {
2543
3070
  const applyToggle = (cell) => {
2544
3071
  const cur = cell.style.border;
@@ -2554,6 +3081,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2554
3081
  }
2555
3082
  };
2556
3083
  const runTableCellAction = (cell, action) => {
3084
+ pushEditorHistory();
2557
3085
  action(cell);
2558
3086
  handleInput();
2559
3087
  setTableMenu(null);
@@ -2577,6 +3105,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2577
3105
  const cells = getColumnCells(table, colIndex);
2578
3106
  if (cells.length === 0)
2579
3107
  return;
3108
+ pushEditorHistory();
2580
3109
  const firstCell = cells[0];
2581
3110
  const currentWidth = firstCell.offsetWidth;
2582
3111
  // Unlock table width so it can grow
@@ -2603,6 +3132,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2603
3132
  return;
2604
3133
  const cells = cellsOfRow(row);
2605
3134
  const currentHeight = row.offsetHeight;
3135
+ pushEditorHistory();
2606
3136
  tableResizeRef.current = {
2607
3137
  type: 'row',
2608
3138
  table,
@@ -2654,6 +3184,184 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2654
3184
  handleInput();
2655
3185
  }
2656
3186
  };
3187
+ const getRangeFromPoint = (x, y) => {
3188
+ let range = null;
3189
+ // @ts-ignore
3190
+ if (document.caretRangeFromPoint) {
3191
+ // @ts-ignore
3192
+ range = document.caretRangeFromPoint(x, y);
3193
+ }
3194
+ else if (document.caretPositionFromPoint) {
3195
+ const pos = document.caretPositionFromPoint(x, y);
3196
+ if (pos) {
3197
+ range = document.createRange();
3198
+ range.setStart(pos.offsetNode, pos.offset);
3199
+ }
3200
+ }
3201
+ return range;
3202
+ };
3203
+ const getMovableElementFromNode = (node) => {
3204
+ const editor = editableRef.current;
3205
+ if (!editor || !node)
3206
+ return null;
3207
+ let element = node instanceof HTMLElement ? node : node.parentElement;
3208
+ if (!element || !editor.contains(element))
3209
+ return null;
3210
+ const image = element.closest("img");
3211
+ if (image && editor.contains(image)) {
3212
+ return (image.parentElement?.tagName === "A" ? image.parentElement : image);
3213
+ }
3214
+ const tableElement = element.closest("table");
3215
+ if (tableElement && editor.contains(tableElement)) {
3216
+ return (tableElement.closest('[data-table-wrapper="true"]') || tableElement);
3217
+ }
3218
+ const listElement = element.closest("ul,ol");
3219
+ if (listElement && editor.contains(listElement))
3220
+ return listElement;
3221
+ const block = element.closest('[data-table-wrapper="true"],blockquote,pre,p,h1,h2,h3,h4,h5,h6,div');
3222
+ if (!block || block === editor || !editor.contains(block))
3223
+ return null;
3224
+ if (block.getAttribute("data-srte-caret-boundary") === "true")
3225
+ return null;
3226
+ return block;
3227
+ };
3228
+ const updateDragHandleForTarget = (target) => {
3229
+ const scroller = editorScrollRef.current;
3230
+ if (dragHandleHideTimerRef.current != null) {
3231
+ window.clearTimeout(dragHandleHideTimerRef.current);
3232
+ dragHandleHideTimerRef.current = null;
3233
+ }
3234
+ if (!target || !scroller || !editableRef.current?.contains(target)) {
3235
+ setDragHandle(null);
3236
+ return;
3237
+ }
3238
+ const targetRect = target.getBoundingClientRect();
3239
+ const scrollRect = scroller.getBoundingClientRect();
3240
+ const next = {
3241
+ left: Math.max(4, targetRect.left - scrollRect.left + scroller.scrollLeft - 30),
3242
+ top: targetRect.top - scrollRect.top + scroller.scrollTop,
3243
+ height: Math.max(24, targetRect.height),
3244
+ target,
3245
+ };
3246
+ setDragHandle((current) => {
3247
+ if (current?.target === next.target &&
3248
+ Math.abs(current.left - next.left) < 1 &&
3249
+ Math.abs(current.top - next.top) < 1 &&
3250
+ Math.abs(current.height - next.height) < 1) {
3251
+ return current;
3252
+ }
3253
+ return next;
3254
+ });
3255
+ };
3256
+ const scheduleDragHandleHide = () => {
3257
+ if (dragHandleHideTimerRef.current != null) {
3258
+ window.clearTimeout(dragHandleHideTimerRef.current);
3259
+ }
3260
+ dragHandleHideTimerRef.current = window.setTimeout(() => {
3261
+ dragHandleHideTimerRef.current = null;
3262
+ if (!draggedBlockRef.current)
3263
+ setDragHandle(null);
3264
+ }, 120);
3265
+ };
3266
+ const getImageFromMovableBlock = (block) => {
3267
+ if (block.tagName === "IMG")
3268
+ return block;
3269
+ return block.querySelector("img");
3270
+ };
3271
+ const dropBlockAtPoint = (block, x, y) => {
3272
+ const editor = editableRef.current;
3273
+ if (!editor || !editor.contains(block))
3274
+ return false;
3275
+ const under = document.elementFromPoint(x, y);
3276
+ const draggedImage = getImageFromMovableBlock(block);
3277
+ const targetCell = draggedImage ? getClosestCell(under) : null;
3278
+ if (draggedImage && targetCell && !block.contains(targetCell)) {
3279
+ if (targetCell.contains(block))
3280
+ return false;
3281
+ const range = getRangeFromPoint(x, y);
3282
+ if (range && targetCell.contains(range.commonAncestorContainer)) {
3283
+ range.insertNode(block);
3284
+ }
3285
+ else {
3286
+ targetCell.appendChild(block);
3287
+ }
3288
+ return true;
3289
+ }
3290
+ const target = getMovableElementFromNode(under);
3291
+ if (target && target !== block && !block.contains(target) && !target.contains(block)) {
3292
+ const rect = target.getBoundingClientRect();
3293
+ const parent = target.parentElement;
3294
+ if (!parent)
3295
+ return false;
3296
+ if (y < rect.top + rect.height / 2)
3297
+ parent.insertBefore(block, target);
3298
+ else
3299
+ parent.insertBefore(block, target.nextSibling);
3300
+ return true;
3301
+ }
3302
+ const range = getRangeFromPoint(x, y);
3303
+ if (range && editor.contains(range.commonAncestorContainer)) {
3304
+ if (block.contains(range.commonAncestorContainer))
3305
+ return false;
3306
+ range.insertNode(block);
3307
+ return true;
3308
+ }
3309
+ editor.appendChild(block);
3310
+ return true;
3311
+ };
3312
+ const getTopLevelMovableElement = () => {
3313
+ const editor = editableRef.current;
3314
+ if (!editor)
3315
+ return null;
3316
+ const imageTarget = selectedImage?.parentElement?.tagName === "A"
3317
+ ? selectedImage.parentElement
3318
+ : selectedImage;
3319
+ if (imageTarget && editor.contains(imageTarget))
3320
+ return imageTarget;
3321
+ const range = getSelectionRangeInEditor();
3322
+ let node = range?.commonAncestorContainer || null;
3323
+ if (node && node.nodeType === Node.TEXT_NODE)
3324
+ node = node.parentNode;
3325
+ let element = node instanceof HTMLElement ? node : null;
3326
+ if (!element)
3327
+ return null;
3328
+ return getMovableElementFromNode(element);
3329
+ };
3330
+ const elementSibling = (element, direction) => {
3331
+ let sibling = direction === "previous" ? element.previousSibling : element.nextSibling;
3332
+ while (sibling && sibling.nodeType === Node.TEXT_NODE && !sibling.textContent?.trim()) {
3333
+ sibling = direction === "previous" ? sibling.previousSibling : sibling.nextSibling;
3334
+ }
3335
+ return sibling;
3336
+ };
3337
+ const moveCurrentElement = (direction) => {
3338
+ const editor = editableRef.current;
3339
+ const target = getTopLevelMovableElement();
3340
+ if (!editor || !target)
3341
+ return;
3342
+ pushEditorHistory();
3343
+ if (direction === "up") {
3344
+ const previous = elementSibling(target, "previous");
3345
+ if (previous)
3346
+ target.parentElement?.insertBefore(target, previous);
3347
+ }
3348
+ else if (direction === "down") {
3349
+ const next = elementSibling(target, "next");
3350
+ if (next)
3351
+ target.parentElement?.insertBefore(next, target);
3352
+ }
3353
+ else {
3354
+ const current = parseInt(target.style.marginLeft || "0", 10) || 0;
3355
+ const nextMargin = direction === "right"
3356
+ ? Math.min(current + 24, 240)
3357
+ : Math.max(current - 24, 0);
3358
+ target.style.marginLeft = nextMargin ? `${nextMargin}px` : "";
3359
+ }
3360
+ focusElementEnd(target);
3361
+ setSelectedImage(target.tagName === "IMG" ? target : target.querySelector("img"));
3362
+ scheduleImageOverlay();
3363
+ handleInput();
3364
+ };
2657
3365
  const addTableResizeHandles = () => {
2658
3366
  if (!table)
2659
3367
  return;
@@ -2760,7 +3468,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2760
3468
  } }), _jsx("input", { ref: mdInputRef, type: "file", accept: ".md,.markdown,text/markdown,text/plain", style: { display: "none" }, onChange: (e) => {
2761
3469
  importTextFile(e.currentTarget.files, "md");
2762
3470
  e.currentTarget.value = "";
2763
- } }), _jsxs("select", { defaultValue: "p", onMouseDown: preserveEditorSelection, onChange: (e) => {
3471
+ } }), _jsxs("select", { value: currentBlockType, onMouseDown: preserveEditorSelection, onChange: (e) => {
2764
3472
  const val = e.target.value;
2765
3473
  if (val === "p")
2766
3474
  applyFormatBlock("<p>");
@@ -2777,7 +3485,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2777
3485
  borderRadius: 6,
2778
3486
  background: "var(--srte-input-bg)",
2779
3487
  color: "var(--srte-input-text)",
2780
- }, children: [_jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" })] }), _jsx("button", { title: "Bold", onClick: () => exec("bold"), "aria-pressed": activeState.bold, style: activeButtonStyle(activeState.bold), children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), "aria-pressed": activeState.italic, style: activeButtonStyle(activeState.italic, { fontStyle: "italic" }), children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), "aria-pressed": activeState.underline, style: activeButtonStyle(activeState.underline, { textDecoration: "underline" }), children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), "aria-pressed": activeState.strikeThrough, style: activeButtonStyle(activeState.strikeThrough, { textDecoration: "line-through" }), children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
3488
+ }, children: [_jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" })] }), _jsx("button", { title: "Bold", onClick: () => exec("bold"), "aria-pressed": activeState.bold, style: activeButtonStyle(activeState.bold), children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), "aria-pressed": activeState.italic, style: activeButtonStyle(activeState.italic, { fontStyle: "italic" }), children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), "aria-pressed": activeState.underline, style: activeButtonStyle(activeState.underline, { textDecoration: "underline" }), children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), "aria-pressed": activeState.strikeThrough, style: activeButtonStyle(activeState.strikeThrough, { textDecoration: "line-through" }), children: "S" }), showFontSize && (_jsxs("select", { value: currentFontSize, onMouseDown: () => {
2781
3489
  // Save selection before dropdown interaction
2782
3490
  const sel = window.getSelection();
2783
3491
  if (sel && sel.rangeCount > 0) {
@@ -2794,7 +3502,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2794
3502
  borderRadius: 6,
2795
3503
  background: "var(--srte-input-bg)",
2796
3504
  color: "var(--srte-input-text)",
2797
- }, children: [_jsx("option", { value: "", disabled: true, children: "Size" }), _jsx("option", { value: "8", children: "8" }), _jsx("option", { value: "9", children: "9" }), _jsx("option", { value: "10", children: "10" }), _jsx("option", { value: "11", children: "11" }), _jsx("option", { value: "12", children: "12" }), _jsx("option", { value: "14", children: "14" }), _jsx("option", { value: "18", children: "18" }), _jsx("option", { value: "24", children: "24" }), _jsx("option", { value: "30", children: "30" }), _jsx("option", { value: "36", children: "36" }), _jsx("option", { value: "48", children: "48" }), _jsx("option", { value: "60", children: "60" }), _jsx("option", { value: "72", children: "72" }), _jsx("option", { value: "96", children: "96" })] }), preserveFontFamily && (_jsxs("select", { value: currentFont, onMouseDown: () => {
3505
+ }, children: [_jsx("option", { value: "", disabled: true, children: "Size" }), _jsx("option", { value: "8", children: "8" }), _jsx("option", { value: "9", children: "9" }), _jsx("option", { value: "10", children: "10" }), _jsx("option", { value: "11", children: "11" }), _jsx("option", { value: "12", children: "12" }), _jsx("option", { value: "14", children: "14" }), _jsx("option", { value: "18", children: "18" }), _jsx("option", { value: "24", children: "24" }), _jsx("option", { value: "30", children: "30" }), _jsx("option", { value: "36", children: "36" }), _jsx("option", { value: "48", children: "48" }), _jsx("option", { value: "60", children: "60" }), _jsx("option", { value: "72", children: "72" }), _jsx("option", { value: "96", children: "96" })] })), preserveFontFamily && (_jsxs("select", { value: currentFont, onMouseDown: () => {
2798
3506
  const sel = window.getSelection();
2799
3507
  if (sel && sel.rangeCount > 0) {
2800
3508
  const range = sel.getRangeAt(0);
@@ -3002,7 +3710,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3002
3710
  borderRadius: 6,
3003
3711
  background: "var(--srte-input-bg)",
3004
3712
  color: "var(--srte-input-text)",
3005
- }, children: "\u2795 Table" })), _jsx("button", { title: "Undo", onClick: () => exec("undo"), style: {
3713
+ }, children: "\u2795 Table" })), _jsxs("div", { style: {
3714
+ display: "inline-flex",
3715
+ gap: 4,
3716
+ alignItems: "center",
3717
+ marginLeft: 6,
3718
+ }, children: [_jsx("span", { style: { fontSize: 12, opacity: 0.7 }, children: "Move:" }), _jsx("button", { title: "Move selected block up", onClick: () => moveCurrentElement("up"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2191" }), _jsx("button", { title: "Move selected block down", onClick: () => moveCurrentElement("down"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2193" }), _jsx("button", { title: "Move selected block left", onClick: () => moveCurrentElement("left"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2190" }), _jsx("button", { title: "Move selected block right", onClick: () => moveCurrentElement("right"), style: activeButtonStyle(false, { height: 28, minWidth: 28, padding: "0 6px" }), children: "\u2192" })] }), _jsx("button", { title: "Undo", onClick: () => exec("undo"), style: {
3006
3719
  height: 32,
3007
3720
  padding: "0 10px",
3008
3721
  border: "1px solid var(--srte-input-border)",
@@ -3169,7 +3882,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3169
3882
  borderRadius: 4,
3170
3883
  background: color,
3171
3884
  cursor: 'pointer',
3172
- }, title: color }, color))) }), _jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("label", { style: { display: 'block', marginBottom: 6, fontSize: 12 }, children: "Custom color:" }), _jsx("input", { type: "color", onChange: (e) => {
3885
+ }, title: color }, color))) }), _jsxs("div", { style: { marginBottom: 12 }, children: [_jsx("label", { style: { display: 'block', marginBottom: 6, fontSize: 12 }, children: "Custom color:" }), _jsx("input", { type: "color", value: currentPickerColorHex(), onChange: (e) => {
3173
3886
  if (colorPickerType === 'text') {
3174
3887
  applyTextColor(e.target.value);
3175
3888
  }
@@ -3338,7 +4051,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3338
4051
  borderRadius: 4,
3339
4052
  background: "var(--srte-input-bg)",
3340
4053
  color: "var(--srte-modal-text)",
3341
- }, title: sym, children: sym }, i)))] })] }) })), _jsxs("div", { ref: editorScrollRef, style: {
4054
+ }, title: sym, children: sym }, i)))] })] }) })), _jsxs("div", { ref: editorScrollRef, onScroll: () => updateDragHandleForTarget(dragHandle?.target || null), style: {
3342
4055
  width: "100%",
3343
4056
  maxWidth: "100%",
3344
4057
  flex: "1 1 auto",
@@ -3354,6 +4067,13 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3354
4067
  }, children: [_jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onKeyUp: updateActiveState, onMouseUp: updateActiveState, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
3355
4068
  isComposingRef.current = false;
3356
4069
  handleInput();
4070
+ }, onMouseMove: (e) => {
4071
+ if (draggedBlockRef.current)
4072
+ return;
4073
+ updateDragHandleForTarget(getMovableElementFromNode(e.target));
4074
+ }, onMouseLeave: () => {
4075
+ if (!draggedBlockRef.current)
4076
+ scheduleDragHandleHide();
3357
4077
  }, onPaste: (e) => {
3358
4078
  const items = e.clipboardData?.files;
3359
4079
  if (media && items && items.length) {
@@ -3371,29 +4091,30 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3371
4091
  }
3372
4092
  }, onDragOver: (e) => {
3373
4093
  // Allow dragging images within editor and file drops
3374
- if (draggedImageRef.current ||
4094
+ if (draggedBlockRef.current ||
4095
+ draggedImageRef.current ||
3375
4096
  e.dataTransfer?.types?.includes("Files")) {
3376
4097
  e.preventDefault();
3377
4098
  }
3378
4099
  }, onDrop: (e) => {
4100
+ if (draggedBlockRef.current) {
4101
+ e.preventDefault();
4102
+ const block = draggedBlockRef.current;
4103
+ draggedBlockRef.current = null;
4104
+ pushEditorHistory();
4105
+ if (dropBlockAtPoint(block, e.clientX, e.clientY)) {
4106
+ focusElementEnd(block);
4107
+ updateDragHandleForTarget(block);
4108
+ handleInput();
4109
+ }
4110
+ return;
4111
+ }
3379
4112
  // Move existing dragged image inside editor
3380
4113
  if (draggedImageRef.current) {
3381
4114
  e.preventDefault();
3382
4115
  const x = e.clientX;
3383
4116
  const y = e.clientY;
3384
- let range = null;
3385
- // @ts-ignore
3386
- if (document.caretRangeFromPoint) {
3387
- // @ts-ignore
3388
- range = document.caretRangeFromPoint(x, y);
3389
- }
3390
- else if (document.caretPositionFromPoint) {
3391
- const pos = document.caretPositionFromPoint(x, y);
3392
- if (pos) {
3393
- range = document.createRange();
3394
- range.setStart(pos.offsetNode, pos.offset);
3395
- }
3396
- }
4117
+ let range = getRangeFromPoint(x, y);
3397
4118
  const img = draggedImageRef.current;
3398
4119
  draggedImageRef.current = null;
3399
4120
  if (range &&
@@ -3402,6 +4123,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3402
4123
  // Avoid inserting inside the image itself
3403
4124
  if (range.startContainer === img || range.endContainer === img)
3404
4125
  return;
4126
+ pushEditorHistory();
3405
4127
  // If dropping inside a link, insert right after the link element
3406
4128
  let container = range.commonAncestorContainer;
3407
4129
  let linkAncestor = null;
@@ -3434,19 +4156,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3434
4156
  // Try to move caret to drop point
3435
4157
  const x = e.clientX;
3436
4158
  const y = e.clientY;
3437
- let range = null;
3438
- // @ts-ignore
3439
- if (document.caretRangeFromPoint) {
3440
- // @ts-ignore
3441
- range = document.caretRangeFromPoint(x, y);
3442
- }
3443
- else if (document.caretPositionFromPoint) {
3444
- const pos = document.caretPositionFromPoint(x, y);
3445
- if (pos) {
3446
- range = document.createRange();
3447
- range.setStart(pos.offsetNode, pos.offset);
3448
- }
3449
- }
4159
+ let range = getRangeFromPoint(x, y);
3450
4160
  if (range) {
3451
4161
  const sel = window.getSelection();
3452
4162
  sel?.removeAllRanges();
@@ -3488,6 +4198,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3488
4198
  }
3489
4199
  }, onDragEnd: () => {
3490
4200
  draggedImageRef.current = null;
4201
+ draggedBlockRef.current = null;
3491
4202
  }, style: {
3492
4203
  minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
3493
4204
  maxWidth: "100%",
@@ -3506,6 +4217,20 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3506
4217
  }
3507
4218
  updateActiveState();
3508
4219
  }, onKeyDown: (e) => {
4220
+ if ((e.metaKey || e.ctrlKey) && String(e.key).toLowerCase() === "z") {
4221
+ const restored = restoreEditorHistory(e.shiftKey ? "redo" : "undo");
4222
+ if (restored) {
4223
+ e.preventDefault();
4224
+ return;
4225
+ }
4226
+ }
4227
+ if ((e.metaKey || e.ctrlKey) && String(e.key).toLowerCase() === "y") {
4228
+ const restored = restoreEditorHistory("redo");
4229
+ if (restored) {
4230
+ e.preventDefault();
4231
+ return;
4232
+ }
4233
+ }
3509
4234
  if (formula &&
3510
4235
  (e.metaKey || e.ctrlKey) &&
3511
4236
  String(e.key).toLowerCase() === "m") {
@@ -3627,7 +4352,55 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3627
4352
  setTableMenu(null);
3628
4353
  setImageMenu(null);
3629
4354
  }
3630
- } }), selectedImage && imageOverlay && (_jsxs("div", { style: {
4355
+ } }), dragHandle && !readOnly && (_jsx("button", { type: "button", draggable: true, title: "Drag block", "aria-label": "Drag block", onMouseEnter: () => {
4356
+ if (dragHandleHideTimerRef.current != null) {
4357
+ window.clearTimeout(dragHandleHideTimerRef.current);
4358
+ dragHandleHideTimerRef.current = null;
4359
+ }
4360
+ }, onMouseLeave: () => {
4361
+ if (!draggedBlockRef.current)
4362
+ scheduleDragHandleHide();
4363
+ }, onMouseDown: (e) => {
4364
+ e.stopPropagation();
4365
+ }, onDragStart: (e) => {
4366
+ draggedBlockRef.current = dragHandle.target;
4367
+ e.dataTransfer.effectAllowed = "move";
4368
+ e.dataTransfer.setData("text/plain", "moving-block");
4369
+ try {
4370
+ const ghost = dragHandle.target.cloneNode(true);
4371
+ ghost.style.position = "fixed";
4372
+ ghost.style.left = "-10000px";
4373
+ ghost.style.top = "-10000px";
4374
+ ghost.style.width = `${Math.min(dragHandle.target.getBoundingClientRect().width, 480)}px`;
4375
+ ghost.style.opacity = "0.75";
4376
+ document.body.appendChild(ghost);
4377
+ e.dataTransfer.setDragImage(ghost, 12, 12);
4378
+ window.setTimeout(() => ghost.remove(), 0);
4379
+ }
4380
+ catch { }
4381
+ }, onDragEnd: () => {
4382
+ draggedBlockRef.current = null;
4383
+ updateDragHandleForTarget(dragHandle.target);
4384
+ }, style: {
4385
+ position: "absolute",
4386
+ left: dragHandle.left,
4387
+ top: dragHandle.top + Math.max(0, (dragHandle.height - 24) / 2),
4388
+ width: 24,
4389
+ height: 24,
4390
+ display: "inline-flex",
4391
+ alignItems: "center",
4392
+ justifyContent: "center",
4393
+ border: "1px solid var(--srte-border)",
4394
+ borderRadius: 6,
4395
+ background: "var(--srte-input-bg)",
4396
+ color: "var(--srte-input-text)",
4397
+ boxShadow: "var(--srte-menu-shadow)",
4398
+ cursor: "grab",
4399
+ zIndex: 8,
4400
+ fontSize: 14,
4401
+ lineHeight: 1,
4402
+ padding: 0,
4403
+ }, children: "\u22EE\u22EE" })), selectedImage && imageOverlay && (_jsxs("div", { style: {
3631
4404
  position: "absolute",
3632
4405
  left: imageOverlay.left,
3633
4406
  top: imageOverlay.top,
@@ -3758,7 +4531,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3758
4531
  alignItems: "center",
3759
4532
  padding: "4px 6px",
3760
4533
  fontSize: 12,
3761
- }, children: [_jsx("span", { children: "Fill:" }), _jsx("input", { type: "color", defaultValue: "#ffffff", onChange: (e) => {
4534
+ }, children: [_jsx("span", { children: "Fill:" }), _jsx("input", { type: "color", value: tableMenuFillHex(tableMenu.cell), onChange: (e) => {
3762
4535
  runTableCellAction(tableMenu.cell, (cell) => applyBgToSelection(e.target.value, cell));
3763
4536
  }, style: {
3764
4537
  width: 28,
@@ -13,10 +13,15 @@ export function MediaManager(props) {
13
13
  if (!open)
14
14
  return;
15
15
  setError(null);
16
- if (activeTab === "library") {
16
+ }, [open]);
17
+ useEffect(() => {
18
+ if (!open || activeTab !== "library")
19
+ return;
20
+ const timer = window.setTimeout(() => {
17
21
  performSearch();
18
- }
19
- }, [open, activeTab]);
22
+ }, 300);
23
+ return () => window.clearTimeout(timer);
24
+ }, [open, activeTab, query]);
20
25
  const performSearch = async () => {
21
26
  try {
22
27
  const items = await adapter.search({ q: query, mimePrefix: "image/" });
@@ -37,7 +37,7 @@ function ClassicEditorHost(props, ref) {
37
37
  }
38
38
  }
39
39
  catch { }
40
- }, placeholder: props.placeholder, minHeight: props.minHeight, maxHeight: props.maxHeight, readOnly: props.readOnly, table: props.table, media: props.media, formula: props.formula, mediaManager: props.mediaManager, theme: props.theme, className: props.className }) }));
40
+ }, placeholder: props.placeholder, minHeight: props.minHeight, maxHeight: props.maxHeight, readOnly: props.readOnly, table: props.table, media: props.media, formula: props.formula, showFontSize: props.showFontSize, mediaManager: props.mediaManager, theme: props.theme, className: props.className }) }));
41
41
  }
42
42
  const ClassicEditorHostWithRef = React.forwardRef(ClassicEditorHost);
43
43
  function initClassicEditor(opts) {
package/dist/theme.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export type SrteTheme = 'light' | 'dark';
2
- export declare const SRTE_DEFAULT_CSS = "\n.srte-editor {\n --srte-bg: #ffffff;\n --srte-text: #111111;\n --srte-text-muted: #4b5563;\n --srte-border: #dddddd;\n --srte-border-light: #eeeeee;\n --srte-toolbar-bg: #ffffff;\n --srte-input-bg: #ffffff;\n --srte-input-text: #111111;\n --srte-input-border: #e5e7eb;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.35);\n --srte-modal-backdrop-filter: blur(2px);\n --srte-modal-bg: #ffffff;\n --srte-modal-text: #000000;\n --srte-menu-bg: #ffffff;\n --srte-menu-text: #111111;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n --srte-accent: #1e90ff;\n --srte-accent-bg: rgba(30, 144, 255, 0.15);\n --srte-danger: #dc2626;\n --srte-primary: #2563eb;\n --srte-surface-subtle: #f3f4f6;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #f3f4f6;\n}\n.srte-editor.srte-dark {\n --srte-bg: #1e1e1e;\n --srte-text: #e0e0e0;\n --srte-text-muted: #9ca3af;\n --srte-border: #3a3a3a;\n --srte-border-light: #2e2e2e;\n --srte-toolbar-bg: #252525;\n --srte-input-bg: #2a2a2a;\n --srte-input-text: #e0e0e0;\n --srte-input-border: #444444;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.22);\n --srte-modal-backdrop-filter: blur(10px) saturate(0.9);\n --srte-modal-bg: #1e293b;\n --srte-modal-text: #e0e0e0;\n --srte-menu-bg: #1e293b;\n --srte-menu-text: #e0e0e0;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);\n --srte-accent: #3b9eff;\n --srte-accent-bg: rgba(59, 158, 255, 0.2);\n --srte-danger: #ef4444;\n --srte-primary: #3b82f6;\n --srte-surface-subtle: #333333;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #333333;\n}\n.srte-editor [contenteditable] blockquote {\n border-left: 4px solid var(--srte-accent);\n margin: 0.75em 0;\n padding: 0.5em 1em;\n background: var(--srte-surface-subtle);\n color: var(--srte-text);\n}\n.srte-editor [contenteditable] ul {\n list-style-type: disc;\n list-style-position: outside;\n margin: 0.75em 0;\n padding-left: 1.75em;\n}\n.srte-editor [contenteditable] ol {\n list-style-type: decimal;\n list-style-position: outside;\n margin: 0.75em 0;\n padding-left: 1.75em;\n}\n.srte-editor [contenteditable] li {\n display: list-item;\n margin: 0.25em 0;\n padding-left: 0.25em;\n}\n.srte-editor [contenteditable] li::marker {\n color: currentColor;\n}\n.srte-editor.srte-dark [contenteditable] [style*=\"color\"]:not(.srte-preserve-colors):not(.srte-preserve-colors *),\n.srte-editor.srte-dark [contenteditable] [style*=\"background\"]:not(.srte-preserve-colors):not(.srte-preserve-colors *) {\n color: var(--srte-text) !important;\n background: transparent !important;\n background-color: transparent !important;\n}\n.srte-editor [contenteditable] sub,\n.srte-editor [contenteditable] sup {\n line-height: 0;\n}\n";
2
+ export declare const SRTE_DEFAULT_CSS = "\n.srte-editor {\n --srte-bg: #ffffff;\n --srte-text: #111111;\n --srte-text-muted: #4b5563;\n --srte-border: #dddddd;\n --srte-border-light: #eeeeee;\n --srte-toolbar-bg: #ffffff;\n --srte-input-bg: #ffffff;\n --srte-input-text: #111111;\n --srte-input-border: #e5e7eb;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.35);\n --srte-modal-backdrop-filter: blur(2px);\n --srte-modal-bg: #ffffff;\n --srte-modal-text: #000000;\n --srte-menu-bg: #ffffff;\n --srte-menu-text: #111111;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);\n --srte-accent: #1e90ff;\n --srte-accent-bg: rgba(30, 144, 255, 0.15);\n --srte-danger: #dc2626;\n --srte-primary: #2563eb;\n --srte-surface-subtle: #f3f4f6;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #f3f4f6;\n}\n.srte-editor.srte-dark {\n --srte-bg: #1e1e1e;\n --srte-text: #e0e0e0;\n --srte-text-muted: #9ca3af;\n --srte-border: #3a3a3a;\n --srte-border-light: #2e2e2e;\n --srte-toolbar-bg: #252525;\n --srte-input-bg: #2a2a2a;\n --srte-input-text: #e0e0e0;\n --srte-input-border: #444444;\n --srte-modal-backdrop: rgba(0, 0, 0, 0.22);\n --srte-modal-backdrop-filter: blur(10px) saturate(0.9);\n --srte-modal-bg: #1e293b;\n --srte-modal-text: #e0e0e0;\n --srte-menu-bg: #1e293b;\n --srte-menu-text: #e0e0e0;\n --srte-menu-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);\n --srte-accent: #3b9eff;\n --srte-accent-bg: rgba(59, 158, 255, 0.2);\n --srte-danger: #ef4444;\n --srte-primary: #3b82f6;\n --srte-surface-subtle: #333333;\n --srte-on-primary: #ffffff;\n --srte-cancel-bg: #333333;\n}\n.srte-editor [contenteditable] blockquote {\n border-left: 4px solid var(--srte-accent);\n margin: 0.75em 0;\n padding: 0.5em 1em;\n background: var(--srte-surface-subtle);\n color: var(--srte-text);\n}\n.srte-editor [contenteditable] p,\n.srte-editor [contenteditable] h1,\n.srte-editor [contenteditable] h2,\n.srte-editor [contenteditable] h3 {\n color: inherit;\n}\n.srte-editor [contenteditable] p {\n display: block;\n margin: 0 0 0.75em;\n font-size: 1em;\n font-weight: 400;\n line-height: 1.6;\n}\n.srte-editor [contenteditable] h1,\n.srte-editor [contenteditable] h2,\n.srte-editor [contenteditable] h3 {\n display: block;\n margin: 0.75em 0 0.4em;\n font-weight: 700;\n line-height: 1.25;\n}\n.srte-editor [contenteditable] h1 {\n font-size: 2em;\n}\n.srte-editor [contenteditable] h2 {\n font-size: 1.5em;\n}\n.srte-editor [contenteditable] h3 {\n font-size: 1.25em;\n}\n.srte-editor [contenteditable] > :first-child {\n margin-top: 0;\n}\n.srte-editor [contenteditable] ul {\n list-style-type: disc;\n list-style-position: outside;\n margin: 0.75em 0;\n padding-left: 1.75em;\n}\n.srte-editor [contenteditable] ol {\n list-style-type: decimal;\n list-style-position: outside;\n margin: 0.75em 0;\n padding-left: 1.75em;\n}\n.srte-editor [contenteditable] li {\n display: list-item;\n margin: 0.25em 0;\n padding-left: 0.25em;\n}\n.srte-editor [contenteditable] li::marker {\n color: currentColor;\n}\n.srte-editor.srte-dark [contenteditable] [style*=\"color\"]:not(td):not(th):not(.srte-preserve-colors):not(.srte-preserve-colors *),\n.srte-editor.srte-dark [contenteditable] [style*=\"background\"]:not(td):not(th):not(.srte-preserve-colors):not(.srte-preserve-colors *) {\n color: var(--srte-text) !important;\n background: transparent !important;\n background-color: transparent !important;\n}\n.srte-editor [contenteditable] sub,\n.srte-editor [contenteditable] sup {\n line-height: 0;\n}\n";
3
3
  export declare function ensureStyleSheet(): void;
package/dist/theme.js CHANGED
@@ -56,6 +56,39 @@ export const SRTE_DEFAULT_CSS = `
56
56
  background: var(--srte-surface-subtle);
57
57
  color: var(--srte-text);
58
58
  }
59
+ .srte-editor [contenteditable] p,
60
+ .srte-editor [contenteditable] h1,
61
+ .srte-editor [contenteditable] h2,
62
+ .srte-editor [contenteditable] h3 {
63
+ color: inherit;
64
+ }
65
+ .srte-editor [contenteditable] p {
66
+ display: block;
67
+ margin: 0 0 0.75em;
68
+ font-size: 1em;
69
+ font-weight: 400;
70
+ line-height: 1.6;
71
+ }
72
+ .srte-editor [contenteditable] h1,
73
+ .srte-editor [contenteditable] h2,
74
+ .srte-editor [contenteditable] h3 {
75
+ display: block;
76
+ margin: 0.75em 0 0.4em;
77
+ font-weight: 700;
78
+ line-height: 1.25;
79
+ }
80
+ .srte-editor [contenteditable] h1 {
81
+ font-size: 2em;
82
+ }
83
+ .srte-editor [contenteditable] h2 {
84
+ font-size: 1.5em;
85
+ }
86
+ .srte-editor [contenteditable] h3 {
87
+ font-size: 1.25em;
88
+ }
89
+ .srte-editor [contenteditable] > :first-child {
90
+ margin-top: 0;
91
+ }
59
92
  .srte-editor [contenteditable] ul {
60
93
  list-style-type: disc;
61
94
  list-style-position: outside;
@@ -76,8 +109,8 @@ export const SRTE_DEFAULT_CSS = `
76
109
  .srte-editor [contenteditable] li::marker {
77
110
  color: currentColor;
78
111
  }
79
- .srte-editor.srte-dark [contenteditable] [style*="color"]:not(.srte-preserve-colors):not(.srte-preserve-colors *),
80
- .srte-editor.srte-dark [contenteditable] [style*="background"]:not(.srte-preserve-colors):not(.srte-preserve-colors *) {
112
+ .srte-editor.srte-dark [contenteditable] [style*="color"]:not(td):not(th):not(.srte-preserve-colors):not(.srte-preserve-colors *),
113
+ .srte-editor.srte-dark [contenteditable] [style*="background"]:not(td):not(th):not(.srte-preserve-colors):not(.srte-preserve-colors *) {
81
114
  color: var(--srte-text) !important;
82
115
  background: transparent !important;
83
116
  background-color: transparent !important;
@@ -91,8 +124,13 @@ const SRTE_STYLE_ID = 'srte-theme-defaults';
91
124
  export function ensureStyleSheet() {
92
125
  if (typeof document === 'undefined')
93
126
  return;
94
- if (document.getElementById(SRTE_STYLE_ID))
127
+ const existing = document.getElementById(SRTE_STYLE_ID);
128
+ if (existing) {
129
+ if (existing.textContent !== SRTE_DEFAULT_CSS) {
130
+ existing.textContent = SRTE_DEFAULT_CSS;
131
+ }
95
132
  return;
133
+ }
96
134
  const style = document.createElement('style');
97
135
  style.id = SRTE_STYLE_ID;
98
136
  style.textContent = SRTE_DEFAULT_CSS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smartrte-react",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",