smartrte-react 0.1.13 → 0.1.14

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.
@@ -10,6 +10,21 @@ type ClassicEditorProps = {
10
10
  media?: boolean;
11
11
  formula?: boolean;
12
12
  mediaManager?: MediaManagerAdapter;
13
+ /**
14
+ * Optional custom list of fonts to display in the toolbar.
15
+ * If not provided, a default set of web-safe fonts will be used.
16
+ * Example: [{ name: 'Robto', value: 'Roboto, sans-serif' }]
17
+ */
18
+ fonts?: {
19
+ name: string;
20
+ value: string;
21
+ }[];
22
+ /**
23
+ * The default font family to apply to the editor content.
24
+ * This sets the font-family style of the editable area.
25
+ * Example: "Arial, sans-serif"
26
+ */
27
+ defaultFont?: string;
13
28
  };
14
- export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function ClassicEditor({ value, onChange, placeholder, minHeight, maxHeight, readOnly, table, media, formula, mediaManager, fonts, defaultFont, }: ClassicEditorProps): import("react/jsx-runtime").JSX.Element;
15
30
  export {};
@@ -1,7 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  import { MediaManager } from "./MediaManager";
4
- export function ClassicEditor({ value, onChange, placeholder = "Type here…", minHeight = 200, maxHeight = 500, readOnly = false, table = true, media = true, formula = true, mediaManager, }) {
4
+ export function ClassicEditor({ value, onChange, placeholder = "Type here…", minHeight = 200, maxHeight = 500, readOnly = false, table = true, media = true, formula = true, mediaManager, fonts = [
5
+ { name: "Arial", value: "Arial, Helvetica, sans-serif" },
6
+ { name: "Georgia", value: "Georgia, serif" },
7
+ { name: "Impact", value: "Impact, Charcoal, sans-serif" },
8
+ { name: "Tahoma", value: "Tahoma, Geneva, sans-serif" },
9
+ { name: "Times New Roman", value: "'Times New Roman', Times, serif" },
10
+ { name: "Verdana", value: "Verdana, Geneva, sans-serif" },
11
+ { name: "Courier New", value: "'Courier New', Courier, monospace" },
12
+ ], defaultFont, }) {
5
13
  const editableRef = useRef(null);
6
14
  const lastEmittedRef = useRef("");
7
15
  const isComposingRef = useRef(false);
@@ -25,7 +33,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
25
33
  const [showColorPicker, setShowColorPicker] = useState(false);
26
34
  const [colorPickerType, setColorPickerType] = useState('text');
27
35
  const savedRangeRef = useRef(null);
28
- const [currentFontSize, setCurrentFontSize] = useState("11");
36
+ const [currentFontSize, setCurrentFontSize] = useState("");
37
+ const [currentFont, setCurrentFont] = useState("");
29
38
  useEffect(() => {
30
39
  const el = editableRef.current;
31
40
  if (!el)
@@ -33,6 +42,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
33
42
  // Initialize with provided HTML only when externally controlled value changes
34
43
  if (typeof value === "string" && value !== el.innerHTML) {
35
44
  el.innerHTML = value || "";
45
+ fixNegativeMargins(el);
46
+ ensureTableWrappers(el);
36
47
  }
37
48
  // Suppress native context menu inside table cells at capture phase
38
49
  const onCtx = (evt) => {
@@ -152,6 +163,57 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
152
163
  console.error('Error applying font size:', error);
153
164
  }
154
165
  };
166
+ const applyFontFamily = (font) => {
167
+ try {
168
+ setCurrentFont(font);
169
+ const editor = editableRef.current;
170
+ if (!editor)
171
+ return;
172
+ editor.focus();
173
+ let range = null;
174
+ const sel = window.getSelection();
175
+ if (sel && sel.rangeCount > 0) {
176
+ const currentRange = sel.getRangeAt(0);
177
+ if (editor.contains(currentRange.commonAncestorContainer)) {
178
+ range = currentRange;
179
+ }
180
+ }
181
+ if (!range && savedRangeRef.current) {
182
+ range = savedRangeRef.current.cloneRange();
183
+ }
184
+ if (!range)
185
+ return;
186
+ if (range.collapsed) {
187
+ const span = document.createElement('span');
188
+ span.style.fontFamily = font;
189
+ span.textContent = '\u200B';
190
+ range.insertNode(span);
191
+ const newRange = document.createRange();
192
+ newRange.setStart(span.firstChild, 1);
193
+ newRange.collapse(true);
194
+ if (sel) {
195
+ sel.removeAllRanges();
196
+ sel.addRange(newRange);
197
+ }
198
+ handleInput();
199
+ return;
200
+ }
201
+ const span = document.createElement('span');
202
+ span.style.fontFamily = font;
203
+ const fragment = range.extractContents();
204
+ span.appendChild(fragment);
205
+ range.insertNode(span);
206
+ if (sel) {
207
+ range.selectNodeContents(span);
208
+ sel.removeAllRanges();
209
+ sel.addRange(range);
210
+ }
211
+ handleInput();
212
+ }
213
+ catch (error) {
214
+ console.error('Error applying font family:', error);
215
+ }
216
+ };
155
217
  const applyTextColor = (color) => {
156
218
  exec("foreColor", color);
157
219
  };
@@ -466,11 +528,61 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
466
528
  });
467
529
  }
468
530
  };
531
+ const fixNegativeMargins = (root) => {
532
+ try {
533
+ const nodes = root.querySelectorAll('*');
534
+ for (let i = 0; i < nodes.length; i++) {
535
+ const node = nodes[i];
536
+ if (node.style && node.style.marginLeft && node.style.marginLeft.trim().startsWith('-')) {
537
+ node.style.marginLeft = '0px';
538
+ }
539
+ }
540
+ }
541
+ catch { }
542
+ };
543
+ const ensureTableWrappers = (root) => {
544
+ try {
545
+ const tables = root.querySelectorAll('table');
546
+ tables.forEach((table) => {
547
+ const parent = table.parentElement;
548
+ if (parent && parent.getAttribute('data-table-wrapper') !== 'true') {
549
+ const wrapper = document.createElement('div');
550
+ wrapper.setAttribute('data-table-wrapper', 'true');
551
+ wrapper.style.overflowX = 'auto';
552
+ wrapper.style.webkitOverflowScrolling = 'touch';
553
+ wrapper.style.width = '100%';
554
+ wrapper.style.maxWidth = '100%';
555
+ wrapper.style.display = 'block';
556
+ // Use insertBefore + appendChild to move element without losing too much state
557
+ // simpler than replaceChild for wrapping
558
+ parent.insertBefore(wrapper, table);
559
+ wrapper.appendChild(table);
560
+ }
561
+ // Always ensure table takes full width
562
+ if (table.style.width !== '100%') {
563
+ table.style.width = '100%';
564
+ }
565
+ // Ensure min-width is set
566
+ if (!table.style.minWidth || table.style.minWidth === '0px') {
567
+ table.style.minWidth = '100%';
568
+ }
569
+ });
570
+ }
571
+ catch (e) {
572
+ console.error("Error wrapping tables", e);
573
+ }
574
+ };
469
575
  const handleInput = () => {
470
576
  if (isComposingRef.current)
471
577
  return;
472
578
  const el = editableRef.current;
473
- if (!el || !onChange)
579
+ if (!el)
580
+ return;
581
+ // Auto-fix negative margins that might cause visibility issues
582
+ fixNegativeMargins(el);
583
+ // Ensure tables are wrapped for horizontal scrolling
584
+ ensureTableWrappers(el);
585
+ if (!onChange)
474
586
  return;
475
587
  const html = el.innerHTML;
476
588
  if (html !== lastEmittedRef.current) {
@@ -481,7 +593,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
481
593
  const buildTableHTML = (rows, cols) => {
482
594
  const safeRows = Math.max(1, Math.min(50, Math.floor(rows) || 1));
483
595
  const safeCols = Math.max(1, Math.min(20, Math.floor(cols) || 1));
484
- let html = '<table style="border-collapse:collapse;width:100%;"><tbody>';
596
+ let html = '<div data-table-wrapper="true" style="overflow-x:auto;-webkit-overflow-scrolling:touch;width:100%;max-width:100%;display:block;"><table style="border-collapse:collapse;min-width:100%;"><tbody>';
485
597
  for (let r = 0; r < safeRows; r++) {
486
598
  html += "<tr>";
487
599
  for (let c = 0; c < safeCols; c++) {
@@ -490,7 +602,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
490
602
  }
491
603
  html += "</tr>";
492
604
  }
493
- html += "</tbody></table>";
605
+ html += "</tbody></table></div>";
494
606
  return html;
495
607
  };
496
608
  const insertTable = () => {
@@ -895,6 +1007,9 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
895
1007
  return;
896
1008
  const firstCell = cells[0];
897
1009
  const currentWidth = firstCell.offsetWidth;
1010
+ // Unlock table width so it can grow
1011
+ table.style.width = "max-content";
1012
+ table.style.minWidth = "100%";
898
1013
  tableResizeRef.current = {
899
1014
  type: 'column',
900
1015
  table,
@@ -991,14 +1106,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
991
1106
  });
992
1107
  });
993
1108
  };
994
- return (_jsxs("div", { style: { border: "1px solid #ddd", borderRadius: 6 }, children: [_jsxs("div", { style: {
1109
+ return (_jsxs("div", { style: {
1110
+ border: "1px solid #ddd",
1111
+ borderRadius: 6,
1112
+ width: "100%",
1113
+ maxWidth: "100vw",
1114
+ overflow: "hidden",
1115
+ display: "flex",
1116
+ flexDirection: "column",
1117
+ background: "#fff",
1118
+ boxSizing: "border-box"
1119
+ }, children: [_jsxs("div", { style: {
995
1120
  display: "flex",
996
1121
  flexWrap: "wrap",
1122
+ maxWidth: "100%",
997
1123
  gap: 8,
998
1124
  padding: 8,
999
1125
  borderBottom: "1px solid #eee",
1000
- background: "#fff",
1001
- color: "#111",
1002
1126
  position: "sticky",
1003
1127
  top: 0,
1004
1128
  zIndex: 1,
@@ -1097,7 +1221,24 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1097
1221
  borderRadius: 6,
1098
1222
  background: "#fff",
1099
1223
  color: "#111",
1100
- }, children: [_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" })] }), _jsxs("button", { title: "Text Color", onClick: () => {
1224
+ }, 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" })] }), _jsxs("select", { value: currentFont, onMouseDown: () => {
1225
+ const sel = window.getSelection();
1226
+ if (sel && sel.rangeCount > 0) {
1227
+ const range = sel.getRangeAt(0);
1228
+ const editor = editableRef.current;
1229
+ if (editor && editor.contains(range.commonAncestorContainer) && !range.collapsed) {
1230
+ savedRangeRef.current = range.cloneRange();
1231
+ }
1232
+ }
1233
+ }, onChange: (e) => applyFontFamily(e.target.value), title: "Font Family", style: {
1234
+ height: 32,
1235
+ padding: "0 8px",
1236
+ border: "1px solid #e5e7eb",
1237
+ borderRadius: 6,
1238
+ background: "#fff",
1239
+ color: "#111",
1240
+ maxWidth: 100,
1241
+ }, children: [_jsx("option", { value: "", disabled: true, children: "Font" }), fonts.map((f) => (_jsx("option", { value: f.value, children: f.name }, f.value)))] }), _jsx("button", { title: "Text Color", onClick: () => {
1101
1242
  setColorPickerType('text');
1102
1243
  setShowColorPicker(true);
1103
1244
  }, style: {
@@ -1109,16 +1250,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1109
1250
  background: "#fff",
1110
1251
  color: "#111",
1111
1252
  position: "relative",
1112
- }, children: [_jsx("span", { style: { fontWeight: 700 }, children: "A" }), _jsx("div", { style: {
1113
- position: "absolute",
1114
- bottom: 4,
1115
- left: "50%",
1116
- transform: "translateX(-50%)",
1117
- width: 16,
1118
- height: 3,
1119
- background: "#000",
1120
- borderRadius: 1,
1121
- } })] }), _jsx("button", { title: "Background Color", onClick: () => {
1253
+ }, children: _jsx("span", { style: { fontWeight: 700 }, children: "A" }) }), _jsx("button", { title: "Background Color", onClick: () => {
1122
1254
  setColorPickerType('background');
1123
1255
  setShowColorPicker(true);
1124
1256
  }, style: {
@@ -1129,7 +1261,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1129
1261
  borderRadius: 6,
1130
1262
  background: "#fff",
1131
1263
  color: "#111",
1132
- }, children: _jsx("span", { style: { fontWeight: 700, background: "#ffeb3b", padding: "2px 4px" }, children: "A" }) }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
1264
+ }, children: _jsx("span", { style: { fontWeight: 700, padding: "2px 4px" }, children: "A" }) }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
1133
1265
  height: 32,
1134
1266
  padding: "0 10px",
1135
1267
  border: "1px solid #e5e7eb",
@@ -1332,6 +1464,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1332
1464
  padding: 16,
1333
1465
  borderRadius: 8,
1334
1466
  minWidth: 320,
1467
+ maxWidth: "90vw",
1335
1468
  color: "#000",
1336
1469
  }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 12 }, children: colorPickerType === 'text' ? 'Select Text Color' : 'Select Background Color' }), _jsx("div", { style: {
1337
1470
  display: "grid",
@@ -1485,273 +1618,285 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1485
1618
  borderRadius: 4,
1486
1619
  background: "#fff",
1487
1620
  color: "#000",
1488
- }, title: sym, children: sym }, i)))] })] }) })), _jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
1489
- isComposingRef.current = false;
1490
- handleInput();
1491
- }, onPaste: (e) => {
1492
- const items = e.clipboardData?.files;
1493
- if (media && items && items.length) {
1494
- const hasImage = Array.from(items).some((f) => f.type.startsWith("image/"));
1495
- if (hasImage) {
1621
+ }, title: sym, children: sym }, i)))] })] }) })), _jsx("div", { style: {
1622
+ width: "100%",
1623
+ maxWidth: "100%",
1624
+ flex: "1 1 auto",
1625
+ minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
1626
+ maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
1627
+ overflowY: "auto",
1628
+ overflowX: "hidden",
1629
+ boxSizing: "border-box",
1630
+ position: "relative",
1631
+ }, children: _jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
1632
+ isComposingRef.current = false;
1633
+ handleInput();
1634
+ }, onPaste: (e) => {
1635
+ const items = e.clipboardData?.files;
1636
+ if (media && items && items.length) {
1637
+ const hasImage = Array.from(items).some((f) => f.type.startsWith("image/"));
1638
+ if (hasImage) {
1639
+ e.preventDefault();
1640
+ handleLocalImageFiles(items);
1641
+ }
1642
+ }
1643
+ }, onDragOver: (e) => {
1644
+ // Allow dragging images within editor and file drops
1645
+ if (draggedImageRef.current ||
1646
+ e.dataTransfer?.types?.includes("Files")) {
1496
1647
  e.preventDefault();
1497
- handleLocalImageFiles(items);
1498
1648
  }
1499
- }
1500
- }, onDragOver: (e) => {
1501
- // Allow dragging images within editor and file drops
1502
- if (draggedImageRef.current ||
1503
- e.dataTransfer?.types?.includes("Files")) {
1504
- e.preventDefault();
1505
- }
1506
- }, onDrop: (e) => {
1507
- // Move existing dragged image inside editor
1508
- if (draggedImageRef.current) {
1509
- e.preventDefault();
1510
- const x = e.clientX;
1511
- const y = e.clientY;
1512
- let range = null;
1513
- // @ts-ignore
1514
- if (document.caretRangeFromPoint) {
1649
+ }, onDrop: (e) => {
1650
+ // Move existing dragged image inside editor
1651
+ if (draggedImageRef.current) {
1652
+ e.preventDefault();
1653
+ const x = e.clientX;
1654
+ const y = e.clientY;
1655
+ let range = null;
1515
1656
  // @ts-ignore
1516
- range = document.caretRangeFromPoint(x, y);
1517
- }
1518
- else if (document.caretPositionFromPoint) {
1519
- const pos = document.caretPositionFromPoint(x, y);
1520
- if (pos) {
1521
- range = document.createRange();
1522
- range.setStart(pos.offsetNode, pos.offset);
1657
+ if (document.caretRangeFromPoint) {
1658
+ // @ts-ignore
1659
+ range = document.caretRangeFromPoint(x, y);
1523
1660
  }
1524
- }
1525
- const img = draggedImageRef.current;
1526
- draggedImageRef.current = null;
1527
- if (range &&
1528
- img &&
1529
- editableRef.current?.contains(range.commonAncestorContainer)) {
1530
- // Avoid inserting inside the image itself
1531
- if (range.startContainer === img || range.endContainer === img)
1532
- return;
1533
- // If dropping inside a link, insert right after the link element
1534
- let container = range.commonAncestorContainer;
1535
- let linkAncestor = null;
1536
- let el = container;
1537
- while (el && el !== editableRef.current) {
1538
- if (el.tagName === "A") {
1539
- linkAncestor = el;
1540
- break;
1661
+ else if (document.caretPositionFromPoint) {
1662
+ const pos = document.caretPositionFromPoint(x, y);
1663
+ if (pos) {
1664
+ range = document.createRange();
1665
+ range.setStart(pos.offsetNode, pos.offset);
1541
1666
  }
1542
- el = el.parentElement;
1543
1667
  }
1544
- if (linkAncestor) {
1545
- linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
1546
- }
1547
- else {
1548
- range.insertNode(img);
1668
+ const img = draggedImageRef.current;
1669
+ draggedImageRef.current = null;
1670
+ if (range &&
1671
+ img &&
1672
+ editableRef.current?.contains(range.commonAncestorContainer)) {
1673
+ // Avoid inserting inside the image itself
1674
+ if (range.startContainer === img || range.endContainer === img)
1675
+ return;
1676
+ // If dropping inside a link, insert right after the link element
1677
+ let container = range.commonAncestorContainer;
1678
+ let linkAncestor = null;
1679
+ let el = container;
1680
+ while (el && el !== editableRef.current) {
1681
+ if (el.tagName === "A") {
1682
+ linkAncestor = el;
1683
+ break;
1684
+ }
1685
+ el = el.parentElement;
1686
+ }
1687
+ if (linkAncestor) {
1688
+ linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
1689
+ }
1690
+ else {
1691
+ range.insertNode(img);
1692
+ }
1693
+ const r = document.createRange();
1694
+ r.setStartAfter(img);
1695
+ r.collapse(true);
1696
+ safeSelectRange(r);
1697
+ setSelectedImage(img);
1698
+ scheduleImageOverlay();
1699
+ handleInput();
1549
1700
  }
1550
- const r = document.createRange();
1551
- r.setStartAfter(img);
1552
- r.collapse(true);
1553
- safeSelectRange(r);
1554
- setSelectedImage(img);
1555
- scheduleImageOverlay();
1556
- handleInput();
1701
+ return;
1557
1702
  }
1558
- return;
1559
- }
1560
- if (media && e.dataTransfer?.files?.length) {
1561
- e.preventDefault();
1562
- // Try to move caret to drop point
1563
- const x = e.clientX;
1564
- const y = e.clientY;
1565
- let range = null;
1566
- // @ts-ignore
1567
- if (document.caretRangeFromPoint) {
1703
+ if (media && e.dataTransfer?.files?.length) {
1704
+ e.preventDefault();
1705
+ // Try to move caret to drop point
1706
+ const x = e.clientX;
1707
+ const y = e.clientY;
1708
+ let range = null;
1568
1709
  // @ts-ignore
1569
- range = document.caretRangeFromPoint(x, y);
1570
- }
1571
- else if (document.caretPositionFromPoint) {
1572
- const pos = document.caretPositionFromPoint(x, y);
1573
- if (pos) {
1574
- range = document.createRange();
1575
- range.setStart(pos.offsetNode, pos.offset);
1710
+ if (document.caretRangeFromPoint) {
1711
+ // @ts-ignore
1712
+ range = document.caretRangeFromPoint(x, y);
1576
1713
  }
1714
+ else if (document.caretPositionFromPoint) {
1715
+ const pos = document.caretPositionFromPoint(x, y);
1716
+ if (pos) {
1717
+ range = document.createRange();
1718
+ range.setStart(pos.offsetNode, pos.offset);
1719
+ }
1720
+ }
1721
+ if (range) {
1722
+ const sel = window.getSelection();
1723
+ sel?.removeAllRanges();
1724
+ sel?.addRange(range);
1725
+ }
1726
+ handleLocalImageFiles(e.dataTransfer.files);
1577
1727
  }
1578
- if (range) {
1579
- const sel = window.getSelection();
1580
- sel?.removeAllRanges();
1581
- sel?.addRange(range);
1728
+ }, onClick: (e) => {
1729
+ const t = e.target;
1730
+ if (t && t.tagName === "IMG") {
1731
+ setSelectedImage(t);
1732
+ scheduleImageOverlay();
1582
1733
  }
1583
- handleLocalImageFiles(e.dataTransfer.files);
1584
- }
1585
- }, onClick: (e) => {
1586
- const t = e.target;
1587
- if (t && t.tagName === "IMG") {
1588
- setSelectedImage(t);
1589
- scheduleImageOverlay();
1590
- }
1591
- else {
1592
- setSelectedImage(null);
1593
- setImageOverlay(null);
1594
- }
1595
- }, onDragStart: (e) => {
1596
- const t = e.target;
1597
- if (t && t.tagName === "IMG") {
1598
- draggedImageRef.current = t;
1599
- try {
1600
- e.dataTransfer?.setData("text/plain", "moving-image");
1601
- e.dataTransfer.effectAllowed = "move";
1602
- // Provide a subtle drag image
1603
- const dt = e.dataTransfer;
1604
- if (dt && typeof dt.setDragImage === "function") {
1605
- const ghost = new Image();
1606
- ghost.src = t.src;
1607
- ghost.width = Math.min(120, t.width);
1608
- ghost.height = Math.min(120, t.height);
1609
- dt.setDragImage(ghost, 10, 10);
1734
+ else {
1735
+ setSelectedImage(null);
1736
+ setImageOverlay(null);
1737
+ }
1738
+ }, onDragStart: (e) => {
1739
+ const t = e.target;
1740
+ if (t && t.tagName === "IMG") {
1741
+ draggedImageRef.current = t;
1742
+ try {
1743
+ e.dataTransfer?.setData("text/plain", "moving-image");
1744
+ e.dataTransfer.effectAllowed = "move";
1745
+ // Provide a subtle drag image
1746
+ const dt = e.dataTransfer;
1747
+ if (dt && typeof dt.setDragImage === "function") {
1748
+ const ghost = new Image();
1749
+ ghost.src = t.src;
1750
+ ghost.width = Math.min(120, t.width);
1751
+ ghost.height = Math.min(120, t.height);
1752
+ dt.setDragImage(ghost, 10, 10);
1753
+ }
1610
1754
  }
1755
+ catch { }
1611
1756
  }
1612
- catch { }
1613
- }
1614
- else {
1757
+ else {
1758
+ draggedImageRef.current = null;
1759
+ }
1760
+ }, onDragEnd: () => {
1615
1761
  draggedImageRef.current = null;
1616
- }
1617
- }, onDragEnd: () => {
1618
- draggedImageRef.current = null;
1619
- }, style: {
1620
- padding: 12,
1621
- minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
1622
- maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
1623
- overflowY: "auto",
1624
- outline: "none",
1625
- lineHeight: 1.6,
1626
- }, "data-placeholder": placeholder, onFocus: (e) => {
1627
- // Ensure the editor has at least one paragraph to type into
1628
- const el = e.currentTarget;
1629
- if (!el.innerHTML || el.innerHTML === "<br>") {
1630
- el.innerHTML = "<p><br></p>";
1631
- }
1632
- }, onKeyDown: (e) => {
1633
- if (formula &&
1634
- (e.metaKey || e.ctrlKey) &&
1635
- String(e.key).toLowerCase() === "m") {
1636
- e.preventDefault();
1637
- setShowFormulaDialog(true);
1638
- return;
1639
- }
1640
- // Keep Tab for indentation in lists; otherwise insert 2 spaces
1641
- if (e.key === "Tab") {
1642
- e.preventDefault();
1643
- if (document.queryCommandState("insertUnorderedList") ||
1644
- document.queryCommandState("insertOrderedList")) {
1645
- exec(e.shiftKey ? "outdent" : "indent");
1762
+ }, style: {
1763
+ minHeight: "100%",
1764
+ maxWidth: "100%",
1765
+ overflowX: "hidden",
1766
+ padding: "16px",
1767
+ outline: "none",
1768
+ lineHeight: 1.6,
1769
+ boxSizing: "border-box",
1770
+ fontFamily: defaultFont || "inherit",
1771
+ }, "data-placeholder": placeholder, onFocus: (e) => {
1772
+ // Ensure the editor has at least one paragraph to type into
1773
+ const el = e.currentTarget;
1774
+ if (!el.innerHTML || el.innerHTML === "<br>") {
1775
+ el.innerHTML = "<p><br></p>";
1646
1776
  }
1647
- else {
1648
- document.execCommand("insertText", false, " ");
1777
+ }, onKeyDown: (e) => {
1778
+ if (formula &&
1779
+ (e.metaKey || e.ctrlKey) &&
1780
+ String(e.key).toLowerCase() === "m") {
1781
+ e.preventDefault();
1782
+ setShowFormulaDialog(true);
1783
+ return;
1649
1784
  }
1650
- }
1651
- // Table navigation with arrows inside cells
1652
- if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
1653
- const sel = window.getSelection();
1654
- const cell = getClosestCell(sel?.anchorNode || null);
1655
- if (table &&
1656
- cell &&
1657
- cell.parentElement &&
1658
- cell.parentElement.parentElement) {
1659
- const row = cell.parentElement;
1660
- const tbody = row.parentElement;
1661
- const cells = Array.from(row.children).filter((c) => c.tagName === "TD" ||
1662
- c.tagName === "TH");
1663
- const rows = Array.from(tbody.children);
1664
- const rIdx = rows.indexOf(row);
1665
- const cIdx = cells.indexOf(cell);
1666
- const atStart = (sel?.anchorOffset || 0) === 0;
1667
- const cellTextLen = (cell.textContent || "").length;
1668
- const atEnd = (sel?.anchorOffset || 0) >= cellTextLen;
1669
- let target = null;
1670
- if (e.key === "ArrowLeft" && atStart && cIdx > 0) {
1671
- target = row.children[cIdx - 1];
1672
- }
1673
- else if (e.key === "ArrowRight" &&
1674
- atEnd &&
1675
- cIdx < row.children.length - 1) {
1676
- target = row.children[cIdx + 1];
1677
- }
1678
- else if (e.key === "ArrowUp" && rIdx > 0 && atStart) {
1679
- target = rows[rIdx - 1].children[cIdx];
1785
+ // Keep Tab for indentation in lists; otherwise insert 2 spaces
1786
+ if (e.key === "Tab") {
1787
+ e.preventDefault();
1788
+ if (document.queryCommandState("insertUnorderedList") ||
1789
+ document.queryCommandState("insertOrderedList")) {
1790
+ exec(e.shiftKey ? "outdent" : "indent");
1680
1791
  }
1681
- else if (e.key === "ArrowDown" &&
1682
- rIdx < rows.length - 1 &&
1683
- atEnd) {
1684
- target = rows[rIdx + 1].children[cIdx];
1792
+ else {
1793
+ document.execCommand("insertText", false, " ");
1685
1794
  }
1686
- if (target) {
1687
- e.preventDefault();
1688
- moveCaretToCell(target, e.key === "ArrowRight" || e.key === "ArrowDown");
1795
+ }
1796
+ // Table navigation with arrows inside cells
1797
+ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
1798
+ const sel = window.getSelection();
1799
+ const cell = getClosestCell(sel?.anchorNode || null);
1800
+ if (table &&
1801
+ cell &&
1802
+ cell.parentElement &&
1803
+ cell.parentElement.parentElement) {
1804
+ const row = cell.parentElement;
1805
+ const tbody = row.parentElement;
1806
+ const cells = Array.from(row.children).filter((c) => c.tagName === "TD" ||
1807
+ c.tagName === "TH");
1808
+ const rows = Array.from(tbody.children);
1809
+ const rIdx = rows.indexOf(row);
1810
+ const cIdx = cells.indexOf(cell);
1811
+ const atStart = (sel?.anchorOffset || 0) === 0;
1812
+ const cellTextLen = (cell.textContent || "").length;
1813
+ const atEnd = (sel?.anchorOffset || 0) >= cellTextLen;
1814
+ let target = null;
1815
+ if (e.key === "ArrowLeft" && atStart && cIdx > 0) {
1816
+ target = row.children[cIdx - 1];
1817
+ }
1818
+ else if (e.key === "ArrowRight" &&
1819
+ atEnd &&
1820
+ cIdx < row.children.length - 1) {
1821
+ target = row.children[cIdx + 1];
1822
+ }
1823
+ else if (e.key === "ArrowUp" && rIdx > 0 && atStart) {
1824
+ target = rows[rIdx - 1].children[cIdx];
1825
+ }
1826
+ else if (e.key === "ArrowDown" &&
1827
+ rIdx < rows.length - 1 &&
1828
+ atEnd) {
1829
+ target = rows[rIdx + 1].children[cIdx];
1830
+ }
1831
+ if (target) {
1832
+ e.preventDefault();
1833
+ moveCaretToCell(target, e.key === "ArrowRight" || e.key === "ArrowDown");
1834
+ }
1689
1835
  }
1690
1836
  }
1691
- }
1692
- }, onMouseDown: (e) => {
1693
- const cell = getClosestCell(e.target);
1694
- if (!cell) {
1695
- clearSelectionDecor();
1696
- return;
1697
- }
1698
- const pos = getCellPosition(cell);
1699
- if (!pos)
1700
- return;
1701
- selectingRef.current = { tbody: pos.tbody, start: cell };
1702
- const onMove = (ev) => {
1703
- const under = document.elementFromPoint(ev.clientX, ev.clientY);
1704
- const overCell = getClosestCell(under);
1705
- const startInfo = selectingRef.current;
1706
- if (!overCell || !startInfo)
1837
+ }, onMouseDown: (e) => {
1838
+ const cell = getClosestCell(e.target);
1839
+ if (!cell) {
1840
+ clearSelectionDecor();
1707
1841
  return;
1708
- const a = getCellPosition(startInfo.start);
1709
- const b = getCellPosition(overCell);
1710
- if (!a || !b || a.tbody !== b.tbody)
1842
+ }
1843
+ const pos = getCellPosition(cell);
1844
+ if (!pos)
1711
1845
  return;
1712
- const sr = Math.min(a.rIdx, b.rIdx);
1713
- const sc = Math.min(a.cIdx, b.cIdx);
1714
- const er = Math.max(a.rIdx, b.rIdx);
1715
- const ec = Math.max(a.cIdx, b.cIdx);
1716
- updateSelectionDecor(a.tbody, sr, sc, er, ec);
1717
- };
1718
- const onUp = () => {
1719
- window.removeEventListener("mousemove", onMove);
1720
- window.removeEventListener("mouseup", onUp);
1721
- selectingRef.current = null;
1722
- };
1723
- window.addEventListener("mousemove", onMove);
1724
- window.addEventListener("mouseup", onUp);
1725
- }, onContextMenu: (e) => {
1726
- const target = e.target;
1727
- if (target && target.tagName === "IMG") {
1728
- e.preventDefault();
1729
- const vw = window.innerWidth;
1730
- const vh = window.innerHeight;
1731
- const menuW = 220;
1732
- const menuH = 200;
1733
- const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
1734
- const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
1735
- setImageMenu({ x, y, img: target });
1736
- setTableMenu(null);
1737
- return;
1738
- }
1739
- const cell = getClosestCell(e.target);
1740
- if (cell) {
1741
- e.preventDefault();
1742
- const vw = window.innerWidth;
1743
- const vh = window.innerHeight;
1744
- const menuW = 220;
1745
- const menuH = 300;
1746
- const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
1747
- const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
1748
- setTableMenu({ x, y, cell });
1749
- }
1750
- else {
1751
- setTableMenu(null);
1752
- setImageMenu(null);
1753
- }
1754
- } }), selectedImage && imageOverlay && (_jsxs("div", { style: {
1846
+ selectingRef.current = { tbody: pos.tbody, start: cell };
1847
+ const onMove = (ev) => {
1848
+ const under = document.elementFromPoint(ev.clientX, ev.clientY);
1849
+ const overCell = getClosestCell(under);
1850
+ const startInfo = selectingRef.current;
1851
+ if (!overCell || !startInfo)
1852
+ return;
1853
+ const a = getCellPosition(startInfo.start);
1854
+ const b = getCellPosition(overCell);
1855
+ if (!a || !b || a.tbody !== b.tbody)
1856
+ return;
1857
+ const sr = Math.min(a.rIdx, b.rIdx);
1858
+ const sc = Math.min(a.cIdx, b.cIdx);
1859
+ const er = Math.max(a.rIdx, b.rIdx);
1860
+ const ec = Math.max(a.cIdx, b.cIdx);
1861
+ updateSelectionDecor(a.tbody, sr, sc, er, ec);
1862
+ };
1863
+ const onUp = () => {
1864
+ window.removeEventListener("mousemove", onMove);
1865
+ window.removeEventListener("mouseup", onUp);
1866
+ selectingRef.current = null;
1867
+ };
1868
+ window.addEventListener("mousemove", onMove);
1869
+ window.addEventListener("mouseup", onUp);
1870
+ }, onContextMenu: (e) => {
1871
+ const target = e.target;
1872
+ if (target && target.tagName === "IMG") {
1873
+ e.preventDefault();
1874
+ const vw = window.innerWidth;
1875
+ const vh = window.innerHeight;
1876
+ const menuW = 220;
1877
+ const menuH = 200;
1878
+ const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
1879
+ const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
1880
+ setImageMenu({ x, y, img: target });
1881
+ setTableMenu(null);
1882
+ return;
1883
+ }
1884
+ const cell = getClosestCell(e.target);
1885
+ if (cell) {
1886
+ e.preventDefault();
1887
+ const vw = window.innerWidth;
1888
+ const vh = window.innerHeight;
1889
+ const menuW = 220;
1890
+ const menuH = 300;
1891
+ const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
1892
+ const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
1893
+ setTableMenu({ x, y, cell });
1894
+ }
1895
+ else {
1896
+ setTableMenu(null);
1897
+ setImageMenu(null);
1898
+ }
1899
+ } }) }), selectedImage && imageOverlay && (_jsxs("div", { style: {
1755
1900
  position: "fixed",
1756
1901
  left: imageOverlay.left,
1757
1902
  top: imageOverlay.top,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smartrte-react",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
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",