smartrte-react 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
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
- import { MediaManager } from "./MediaManager";
3
+ import { MediaManager } from "./MediaManager.js";
4
4
  import * as pdfjsLib from 'pdfjs-dist';
5
5
  import mammoth from 'mammoth';
6
6
  import JSZip from 'jszip';
7
- import { ensureStyleSheet } from '../theme';
7
+ import { ensureStyleSheet } from '../theme.js';
8
8
  // Initialize PDF.js worker
9
9
  if (typeof window !== 'undefined') {
10
10
  pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
@@ -20,6 +20,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
20
20
  ], defaultFont, preserveFontFamily = false, preserveColors = false, preserveDocxStyles = true, theme = "light", className, }) {
21
21
  ensureStyleSheet();
22
22
  const editableRef = useRef(null);
23
+ const editorScrollRef = useRef(null);
23
24
  const lastEmittedRef = useRef("");
24
25
  const isComposingRef = useRef(false);
25
26
  const fileInputRef = useRef(null);
@@ -53,6 +54,18 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
53
54
  const savedRangeRef = useRef(null);
54
55
  const [currentFontSize, setCurrentFontSize] = useState("");
55
56
  const [currentFont, setCurrentFont] = useState("");
57
+ const [activeState, setActiveState] = useState({
58
+ bold: false,
59
+ italic: false,
60
+ underline: false,
61
+ strikeThrough: false,
62
+ subscript: false,
63
+ superscript: false,
64
+ unorderedList: false,
65
+ orderedList: false,
66
+ blockquote: false,
67
+ codeBlock: false,
68
+ });
56
69
  useEffect(() => {
57
70
  const el = editableRef.current;
58
71
  if (!el)
@@ -77,6 +90,35 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
77
90
  el.removeEventListener("contextmenu", onCtx, { capture: true });
78
91
  };
79
92
  }, [value]);
93
+ const updateActiveState = () => {
94
+ const editor = editableRef.current;
95
+ if (!editor)
96
+ return;
97
+ try {
98
+ const sel = window.getSelection();
99
+ const range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
100
+ const inEditor = range && editor.contains(range.commonAncestorContainer);
101
+ if (!inEditor)
102
+ return;
103
+ let node = range.commonAncestorContainer;
104
+ if (node.nodeType === Node.TEXT_NODE)
105
+ node = node.parentNode;
106
+ const element = node instanceof HTMLElement ? node : null;
107
+ setActiveState({
108
+ bold: document.queryCommandState("bold"),
109
+ italic: document.queryCommandState("italic"),
110
+ underline: document.queryCommandState("underline"),
111
+ strikeThrough: document.queryCommandState("strikeThrough"),
112
+ subscript: document.queryCommandState("subscript"),
113
+ superscript: document.queryCommandState("superscript"),
114
+ unorderedList: Boolean(element?.closest("ul")),
115
+ orderedList: Boolean(element?.closest("ol")),
116
+ blockquote: Boolean(element?.closest("blockquote")),
117
+ codeBlock: Boolean(element?.closest("pre")),
118
+ });
119
+ }
120
+ catch { }
121
+ };
80
122
  // Save selection whenever it changes
81
123
  useEffect(() => {
82
124
  const saveSelection = () => {
@@ -86,6 +128,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
86
128
  const editor = editableRef.current;
87
129
  if (editor && editor.contains(range.commonAncestorContainer)) {
88
130
  savedRangeRef.current = range.cloneRange();
131
+ updateActiveState();
89
132
  }
90
133
  }
91
134
  };
@@ -96,8 +139,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
96
139
  }, []);
97
140
  const exec = (command, valueArg) => {
98
141
  try {
99
- document.execCommand(command, false, valueArg);
100
- emitChange();
142
+ if (!restoreSavedSelection()) {
143
+ safeSelectRange(getSelectionRangeInEditor());
144
+ }
145
+ if (command === "insertUnorderedList") {
146
+ toggleList("ul");
147
+ return;
148
+ }
149
+ if (command === "insertOrderedList") {
150
+ toggleList("ol");
151
+ return;
152
+ }
153
+ const beforeHtml = editableRef.current?.innerHTML || "";
154
+ const ok = document.execCommand(command, false, valueArg);
155
+ const afterHtml = editableRef.current?.innerHTML || "";
156
+ const needsFallback = !ok ||
157
+ (command === "formatBlock" && beforeHtml === afterHtml);
158
+ if (needsFallback) {
159
+ if (command === "formatBlock" && valueArg)
160
+ applyFormatBlockFallback(valueArg);
161
+ }
162
+ handleInput();
101
163
  }
102
164
  catch { }
103
165
  };
@@ -114,6 +176,29 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
114
176
  onChange(html);
115
177
  }
116
178
  };
179
+ const restoreSavedSelection = () => {
180
+ const editor = editableRef.current;
181
+ if (!editor)
182
+ return false;
183
+ const saved = savedRangeRef.current;
184
+ if (saved && editor.contains(saved.commonAncestorContainer)) {
185
+ editor.focus({ preventScroll: true });
186
+ safeSelectRange(saved.cloneRange());
187
+ return true;
188
+ }
189
+ editor.focus({ preventScroll: true });
190
+ return false;
191
+ };
192
+ const preserveEditorSelection = () => {
193
+ const editor = editableRef.current;
194
+ const sel = window.getSelection();
195
+ if (!editor || !sel || sel.rangeCount === 0)
196
+ return;
197
+ const range = sel.getRangeAt(0);
198
+ if (editor.contains(range.commonAncestorContainer)) {
199
+ savedRangeRef.current = range.cloneRange();
200
+ }
201
+ };
117
202
  const insertLink = () => {
118
203
  const url = window.prompt("Enter URL", "https://");
119
204
  if (!url)
@@ -124,7 +209,6 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
124
209
  const editor = editableRef.current;
125
210
  if (!editor)
126
211
  return null;
127
- editor.focus();
128
212
  const sel = window.getSelection();
129
213
  if (sel && sel.rangeCount > 0) {
130
214
  const range = sel.getRangeAt(0);
@@ -139,6 +223,307 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
139
223
  range.collapse(false);
140
224
  return range;
141
225
  };
226
+ const getCurrentBlock = () => {
227
+ const editor = editableRef.current;
228
+ const range = getSelectionRangeInEditor();
229
+ if (!editor || !range)
230
+ return null;
231
+ let node = range.commonAncestorContainer;
232
+ if (node.nodeType === Node.TEXT_NODE)
233
+ node = node.parentNode;
234
+ const element = node instanceof HTMLElement ? node : null;
235
+ const block = element?.closest("p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,div");
236
+ if (!block || block === editor || !editor.contains(block))
237
+ return null;
238
+ return block;
239
+ };
240
+ const blockSelector = "p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,div";
241
+ const getSelectedBlocks = (range) => {
242
+ const editor = editableRef.current;
243
+ if (!editor)
244
+ return [];
245
+ const blocks = Array.from(editor.querySelectorAll(blockSelector))
246
+ .filter((block) => {
247
+ if (block === editor)
248
+ return false;
249
+ const parentBlock = block.parentElement?.closest(blockSelector);
250
+ if (parentBlock && parentBlock !== editor && editor.contains(parentBlock))
251
+ return false;
252
+ try {
253
+ return range.intersectsNode(block);
254
+ }
255
+ catch {
256
+ return false;
257
+ }
258
+ });
259
+ if (blocks.length > 0)
260
+ return blocks;
261
+ const current = getCurrentBlock();
262
+ return current ? [current] : [];
263
+ };
264
+ const getSelectedListItems = (blocks) => {
265
+ const seen = new Set();
266
+ const items = [];
267
+ blocks.forEach((block) => {
268
+ const li = block.tagName.toLowerCase() === "li"
269
+ ? block
270
+ : block.closest("li");
271
+ if (li && editableRef.current?.contains(li) && !seen.has(li)) {
272
+ seen.add(li);
273
+ items.push(li);
274
+ }
275
+ });
276
+ return items;
277
+ };
278
+ const copyCellOrBlockStyles = (from, to) => {
279
+ to.innerHTML = from.innerHTML || "<br>";
280
+ const style = from.getAttribute("style");
281
+ if (style)
282
+ to.setAttribute("style", style);
283
+ };
284
+ const applyHeaderCellStyle = (cell) => {
285
+ cell.style.fontWeight = "700";
286
+ cell.style.background = "#f3f4f6";
287
+ cell.style.textAlign = cell.style.textAlign || "left";
288
+ };
289
+ const clearHeaderCellStyle = (cell) => {
290
+ if (cell.style.fontWeight === "700" || cell.style.fontWeight === "bold")
291
+ cell.style.fontWeight = "";
292
+ if (cell.style.background === "var(--srte-surface-subtle)" || cell.style.background === "rgb(243, 244, 246)" || cell.style.background === "#f3f4f6")
293
+ cell.style.background = "";
294
+ };
295
+ const replaceTableCellTag = (cell, tag) => {
296
+ const replacement = document.createElement(tag);
297
+ replacement.innerHTML = cell.innerHTML || "&nbsp;";
298
+ replacement.colSpan = cell.colSpan;
299
+ replacement.rowSpan = cell.rowSpan;
300
+ const style = cell.getAttribute("style");
301
+ if (style)
302
+ replacement.setAttribute("style", style);
303
+ if (cell.__rtePrevBg != null) {
304
+ replacement.style.background = cell.__rtePrevBg || "";
305
+ delete cell.__rtePrevBg;
306
+ }
307
+ if (replacement.style.background === "var(--srte-accent-bg)" ||
308
+ replacement.style.background.includes("59, 130, 246") ||
309
+ replacement.style.background.includes("59, 158, 255")) {
310
+ replacement.style.background = "";
311
+ }
312
+ replacement.style.outline = "";
313
+ replacement.style.outlineOffset = "";
314
+ replacement.style.border = replacement.style.border || "1px solid #d1d5db";
315
+ replacement.style.padding = replacement.style.padding || "6px";
316
+ replacement.style.minWidth = replacement.style.minWidth || "60px";
317
+ if (tag === "th")
318
+ applyHeaderCellStyle(replacement);
319
+ else
320
+ clearHeaderCellStyle(replacement);
321
+ cell.parentElement?.replaceChild(replacement, cell);
322
+ return replacement;
323
+ };
324
+ const applyFormatBlockFallback = (blockName) => {
325
+ const editor = editableRef.current;
326
+ const block = getCurrentBlock();
327
+ 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;
330
+ const replacement = document.createElement(tag);
331
+ copyCellOrBlockStyles(block, replacement);
332
+ block.parentElement?.replaceChild(replacement, block);
333
+ const range = document.createRange();
334
+ range.selectNodeContents(replacement);
335
+ range.collapse(false);
336
+ safeSelectRange(range);
337
+ };
338
+ const cloneListShell = (list, tagName) => {
339
+ const clone = document.createElement(tagName || list.tagName.toLowerCase());
340
+ Array.from(list.attributes).forEach((attr) => clone.setAttribute(attr.name, attr.value));
341
+ return clone;
342
+ };
343
+ const focusElementEnd = (element) => {
344
+ const range = document.createRange();
345
+ range.selectNodeContents(element);
346
+ range.collapse(false);
347
+ safeSelectRange(range);
348
+ savedRangeRef.current = range.cloneRange();
349
+ };
350
+ const insertEmptyListAtSelection = (listTag) => {
351
+ const editor = editableRef.current;
352
+ const range = getSelectionRangeInEditor();
353
+ if (!editor || !range)
354
+ return;
355
+ const list = document.createElement(listTag);
356
+ const li = document.createElement("li");
357
+ li.innerHTML = "<br>";
358
+ list.appendChild(li);
359
+ range.deleteContents();
360
+ range.insertNode(list);
361
+ focusElementEnd(li);
362
+ };
363
+ const unwrapListItem = (li, list) => {
364
+ const parent = list.parentElement;
365
+ if (!parent)
366
+ return;
367
+ const paragraph = document.createElement("p");
368
+ paragraph.innerHTML = li.innerHTML || "<br>";
369
+ const beforeList = cloneListShell(list);
370
+ const afterList = cloneListShell(list);
371
+ while (list.firstChild && list.firstChild !== li) {
372
+ beforeList.appendChild(list.firstChild);
373
+ }
374
+ while (li.nextSibling) {
375
+ afterList.appendChild(li.nextSibling);
376
+ }
377
+ if (beforeList.childNodes.length)
378
+ parent.insertBefore(beforeList, list);
379
+ parent.insertBefore(paragraph, list);
380
+ if (afterList.childNodes.length)
381
+ parent.insertBefore(afterList, list);
382
+ li.remove();
383
+ if (!list.querySelector("li"))
384
+ list.remove();
385
+ focusElementEnd(paragraph);
386
+ };
387
+ const convertListTag = (list, listTag) => {
388
+ const replacement = cloneListShell(list, listTag);
389
+ replacement.innerHTML = list.innerHTML;
390
+ list.parentElement?.replaceChild(replacement, list);
391
+ const li = replacement.querySelector("li");
392
+ focusElementEnd(li || replacement);
393
+ };
394
+ const getOrCreateNestedList = (li, listTag) => {
395
+ let nested = Array.from(li.children).find((child) => {
396
+ const tag = child.tagName.toLowerCase();
397
+ return tag === "ul" || tag === "ol";
398
+ });
399
+ if (nested && nested.tagName.toLowerCase() !== listTag) {
400
+ const replacement = cloneListShell(nested, listTag);
401
+ replacement.innerHTML = nested.innerHTML;
402
+ nested.parentElement?.replaceChild(replacement, nested);
403
+ nested = replacement;
404
+ }
405
+ if (!nested) {
406
+ nested = document.createElement(listTag);
407
+ li.appendChild(nested);
408
+ }
409
+ return nested;
410
+ };
411
+ const nestSelectedListItems = (items, listTag) => {
412
+ if (items.length === 0)
413
+ return false;
414
+ let changed = false;
415
+ let lastTargetList = null;
416
+ items.forEach((li) => {
417
+ const previous = li.previousElementSibling;
418
+ if (!previous || previous.tagName.toLowerCase() !== "li")
419
+ return;
420
+ const targetList = getOrCreateNestedList(previous, listTag);
421
+ targetList.appendChild(li);
422
+ lastTargetList = targetList;
423
+ changed = true;
424
+ });
425
+ if (changed) {
426
+ const lastItem = lastTargetList?.lastElementChild;
427
+ if (lastItem)
428
+ focusElementEnd(lastItem);
429
+ }
430
+ return changed;
431
+ };
432
+ const convertSelectedBlocksToList = (blocks, listTag) => {
433
+ const editor = editableRef.current;
434
+ if (!editor || blocks.length === 0)
435
+ return false;
436
+ const selected = blocks.filter((block) => {
437
+ if (!editor.contains(block))
438
+ return false;
439
+ if (block.tagName.toLowerCase() === "li")
440
+ return false;
441
+ if (block.closest("ul,ol"))
442
+ return false;
443
+ return block.parentElement;
444
+ });
445
+ if (selected.length === 0)
446
+ return false;
447
+ const groups = new Map();
448
+ selected.forEach((block) => {
449
+ const parent = block.parentElement;
450
+ if (!parent)
451
+ return;
452
+ const group = groups.get(parent) || [];
453
+ group.push(block);
454
+ groups.set(parent, group);
455
+ });
456
+ let lastLi = null;
457
+ groups.forEach((group, parent) => {
458
+ group.sort((a, b) => {
459
+ const position = a.compareDocumentPosition(b);
460
+ return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
461
+ });
462
+ const list = document.createElement(listTag);
463
+ parent.insertBefore(list, group[0]);
464
+ group.forEach((block) => {
465
+ const li = document.createElement("li");
466
+ li.innerHTML = block.innerHTML || "<br>";
467
+ list.appendChild(li);
468
+ block.remove();
469
+ lastLi = li;
470
+ });
471
+ });
472
+ if (lastLi)
473
+ focusElementEnd(lastLi);
474
+ return true;
475
+ };
476
+ const toggleList = (listTag) => {
477
+ const editor = editableRef.current;
478
+ if (!editor)
479
+ return;
480
+ if (!restoreSavedSelection())
481
+ safeSelectRange(getSelectionRangeInEditor());
482
+ const range = getSelectionRangeInEditor();
483
+ if (range && !range.collapsed) {
484
+ const blocks = getSelectedBlocks(range);
485
+ const selectedListItems = getSelectedListItems(blocks);
486
+ const convertedBlocks = convertSelectedBlocksToList(blocks, listTag);
487
+ const nestedItems = nestSelectedListItems(selectedListItems, listTag);
488
+ if (convertedBlocks || nestedItems) {
489
+ handleInput();
490
+ requestAnimationFrame(updateActiveState);
491
+ return;
492
+ }
493
+ }
494
+ const block = getCurrentBlock();
495
+ if (!block) {
496
+ insertEmptyListAtSelection(listTag);
497
+ handleInput();
498
+ requestAnimationFrame(updateActiveState);
499
+ return;
500
+ }
501
+ const currentList = block.closest("ul,ol");
502
+ if (currentList && editor.contains(currentList)) {
503
+ if (currentList.tagName.toLowerCase() === listTag) {
504
+ const li = block.closest("li");
505
+ if (li)
506
+ unwrapListItem(li, currentList);
507
+ }
508
+ else {
509
+ convertListTag(currentList, listTag);
510
+ }
511
+ handleInput();
512
+ requestAnimationFrame(updateActiveState);
513
+ return;
514
+ }
515
+ const list = document.createElement(listTag);
516
+ const li = document.createElement("li");
517
+ li.innerHTML = block.innerHTML || "<br>";
518
+ list.appendChild(li);
519
+ block.parentElement?.replaceChild(list, block);
520
+ focusElementEnd(li);
521
+ handleInput();
522
+ requestAnimationFrame(updateActiveState);
523
+ };
524
+ const toggleListFallback = (listTag) => {
525
+ toggleList(listTag);
526
+ };
142
527
  const insertTextAtSelection = (text) => {
143
528
  if (!text)
144
529
  return;
@@ -310,15 +695,17 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
310
695
  };
311
696
  const scheduleImageOverlay = () => {
312
697
  const img = selectedImage;
698
+ const scroller = editorScrollRef.current;
313
699
  if (!img) {
314
700
  setImageOverlay(null);
315
701
  return;
316
702
  }
317
703
  try {
318
704
  const rect = img.getBoundingClientRect();
705
+ const hostRect = scroller?.getBoundingClientRect();
319
706
  setImageOverlay({
320
- left: rect.left,
321
- top: rect.top,
707
+ left: hostRect && scroller ? rect.left - hostRect.left + scroller.scrollLeft : rect.left,
708
+ top: hostRect && scroller ? rect.top - hostRect.top + scroller.scrollTop : rect.top,
322
709
  width: rect.width,
323
710
  height: rect.height,
324
711
  });
@@ -617,6 +1004,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
617
1004
  if (!media)
618
1005
  return;
619
1006
  const list = Array.from(files).filter((f) => f.type.startsWith("image/"));
1007
+ if (mediaManager) {
1008
+ try {
1009
+ const uploaded = await mediaManager.upload(list);
1010
+ uploaded.forEach((item) => insertImageAtSelection(item));
1011
+ return;
1012
+ }
1013
+ catch (error) {
1014
+ console.error("Image upload failed, inserting local image data instead:", error);
1015
+ }
1016
+ }
620
1017
  for (const f of list) {
621
1018
  await new Promise((resolve) => {
622
1019
  const reader = new FileReader();
@@ -866,7 +1263,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
866
1263
  cellContents[colIdx] += styledTxt;
867
1264
  }
868
1265
  cellContents.forEach(content => {
869
- rowHtml += `<td style="border:1px solid var(--srte-border);padding:8px;vertical-align:top;">${content || '&nbsp;'}</td>`;
1266
+ rowHtml += `<td style="border:1px solid #d1d5db;padding:8px;vertical-align:top;">${content || '&nbsp;'}</td>`;
870
1267
  });
871
1268
  rowHtml += '</tr>';
872
1269
  tableHtml += rowHtml;
@@ -1525,13 +1922,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1525
1922
  };
1526
1923
  const exportPdf = () => {
1527
1924
  const html = editableRef.current?.innerHTML || "";
1528
- const printWindow = window.open("", "_blank", "noopener,noreferrer,width=900,height=700");
1925
+ const printWindow = window.open("", "_blank", "width=900,height=700");
1529
1926
  if (!printWindow)
1530
1927
  return;
1531
- printWindow.document.write(`<!doctype html><html><head><title>Export PDF</title><style>body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6;padding:32px;color:#111}table{border-collapse:collapse;width:100%;margin:12px 0}td,th{border:1px solid #d1d5db;padding:8px;vertical-align:top}img{max-width:100%;height:auto}blockquote{border-left:4px solid #d1d5db;padding-left:12px;color:#374151}pre,code{background:#f3f4f6}pre{padding:12px;white-space:pre-wrap}@media print{body{padding:0}}</style></head><body>${html}</body></html>`);
1928
+ printWindow.document.open();
1929
+ printWindow.document.write(`<!doctype html><html><head><title>Export PDF</title><style>@page{margin:18mm}html,body{background:#fff}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6;padding:32px;color:#111}table{border-collapse:collapse;width:100%;margin:12px 0;break-inside:auto}tr,img,blockquote,pre{break-inside:avoid}td,th{border:1px solid #d1d5db;padding:8px;vertical-align:top}img{max-width:100%;height:auto}blockquote{border-left:4px solid #d1d5db;padding-left:12px;color:#374151}pre,code{background:#f3f4f6}pre{padding:12px;white-space:pre-wrap}@media print{body{padding:0}}</style></head><body>${html || "<p></p>"}<script>window.addEventListener("load",function(){setTimeout(function(){window.focus();window.print();},150);});</script></body></html>`);
1532
1930
  printWindow.document.close();
1533
- printWindow.focus();
1534
- setTimeout(() => printWindow.print(), 250);
1535
1931
  };
1536
1932
  const fixNegativeMargins = (root) => {
1537
1933
  try {
@@ -1753,7 +2149,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1753
2149
  html += "<tr>";
1754
2150
  for (let c = 0; c < safeCols; c++) {
1755
2151
  html +=
1756
- '<td style="border:1px solid var(--srte-border);padding:6px;min-width:60px;">&nbsp;</td>';
2152
+ '<td style="border:1px solid #d1d5db;padding:6px;min-width:60px;">&nbsp;</td>';
1757
2153
  }
1758
2154
  html += "</tr>";
1759
2155
  }
@@ -1836,16 +2232,56 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1836
2232
  }
1837
2233
  catch { }
1838
2234
  };
2235
+ const getTableGrid = (tbody) => {
2236
+ const rows = Array.from(tbody.querySelectorAll("tr"));
2237
+ const grid = [];
2238
+ rows.forEach((row, rIdx) => {
2239
+ grid[rIdx] = grid[rIdx] || [];
2240
+ let cIdx = 0;
2241
+ const cells = cellsOfRow(row);
2242
+ cells.forEach((cell) => {
2243
+ while (grid[rIdx][cIdx])
2244
+ cIdx += 1;
2245
+ const rowSpan = Math.max(1, cell.rowSpan || 1);
2246
+ const colSpan = Math.max(1, cell.colSpan || 1);
2247
+ for (let r = rIdx; r < rIdx + rowSpan; r += 1) {
2248
+ grid[r] = grid[r] || [];
2249
+ for (let c = cIdx; c < cIdx + colSpan; c += 1) {
2250
+ grid[r][c] = cell;
2251
+ }
2252
+ }
2253
+ cIdx += colSpan;
2254
+ });
2255
+ });
2256
+ return { rows, grid };
2257
+ };
2258
+ const getCellsInGridRect = (tbody, sr, sc, er, ec) => {
2259
+ const { grid } = getTableGrid(tbody);
2260
+ const seen = new Set();
2261
+ const cells = [];
2262
+ for (let r = sr; r <= er; r += 1) {
2263
+ for (let c = sc; c <= ec; c += 1) {
2264
+ const cell = grid[r]?.[c];
2265
+ if (cell && !seen.has(cell)) {
2266
+ seen.add(cell);
2267
+ cells.push(cell);
2268
+ }
2269
+ }
2270
+ }
2271
+ return cells;
2272
+ };
1839
2273
  const getCellPosition = (cell) => {
1840
2274
  const row = cell.parentElement;
1841
2275
  const tbody = row?.parentElement;
1842
2276
  const table = tbody?.parentElement;
1843
2277
  if (!row || !tbody || !table)
1844
2278
  return null;
1845
- const rows = Array.from(tbody.querySelectorAll("tr"));
2279
+ const { rows, grid } = getTableGrid(tbody);
1846
2280
  const rIdx = rows.indexOf(row);
1847
- const cells = Array.from(row.children).filter((c) => ["TD", "TH"].includes(c.tagName));
1848
- const cIdx = cells.indexOf(cell);
2281
+ let cIdx = -1;
2282
+ if (rIdx >= 0) {
2283
+ cIdx = (grid[rIdx] || []).findIndex((candidate) => candidate === cell);
2284
+ }
1849
2285
  return { row, tbody, table, rIdx, cIdx };
1850
2286
  };
1851
2287
  const cellsOfRow = (row) => Array.from(row.children).filter((c) => ["TD", "TH"].includes(c.tagName));
@@ -1854,42 +2290,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1854
2290
  if (!sel)
1855
2291
  return;
1856
2292
  const { tbody, sr, sc, er, ec } = sel;
1857
- const rows = Array.from(tbody.querySelectorAll("tr"));
1858
- for (let r = sr; r <= er; r++) {
1859
- const row = rows[r];
1860
- const cells = cellsOfRow(row);
1861
- for (let c = sc; c <= ec; c++) {
1862
- const cell = cells[c];
1863
- if (!cell)
1864
- continue;
1865
- if (cell.__rtePrevBg != null) {
1866
- cell.style.background = cell.__rtePrevBg;
1867
- delete cell.__rtePrevBg;
1868
- }
1869
- cell.style.outline = "";
1870
- cell.style.outlineOffset = "";
2293
+ const cells = getCellsInGridRect(tbody, sr, sc, er, ec);
2294
+ cells.forEach((cell) => {
2295
+ if (cell.__rtePrevBg != null) {
2296
+ cell.style.background = cell.__rtePrevBg;
2297
+ delete cell.__rtePrevBg;
1871
2298
  }
1872
- }
2299
+ cell.style.outline = "";
2300
+ cell.style.outlineOffset = "";
2301
+ });
1873
2302
  selectionRef.current = null;
1874
2303
  };
1875
2304
  const updateSelectionDecor = (tbody, sr, sc, er, ec) => {
1876
2305
  clearSelectionDecor();
1877
2306
  selectionRef.current = { tbody, sr, sc, er, ec };
1878
- const rows = Array.from(tbody.querySelectorAll("tr"));
1879
- for (let r = sr; r <= er; r++) {
1880
- const row = rows[r];
1881
- const cells = cellsOfRow(row);
1882
- for (let c = sc; c <= ec; c++) {
1883
- const cell = cells[c];
1884
- if (!cell)
1885
- continue;
1886
- cell.__rtePrevBg =
1887
- cell.style.background || "";
1888
- cell.style.background = "var(--srte-accent-bg)";
1889
- cell.style.outline = "2px solid var(--srte-accent)";
1890
- cell.style.outlineOffset = "-2px";
1891
- }
1892
- }
2307
+ const cells = getCellsInGridRect(tbody, sr, sc, er, ec);
2308
+ cells.forEach((cell) => {
2309
+ cell.__rtePrevBg = cell.style.background || "";
2310
+ cell.style.background = "var(--srte-accent-bg)";
2311
+ cell.style.outline = "2px solid var(--srte-accent)";
2312
+ cell.style.outlineOffset = "-2px";
2313
+ });
1893
2314
  };
1894
2315
  const canMergeSelection = () => {
1895
2316
  const sel = selectionRef.current;
@@ -1897,32 +2318,37 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1897
2318
  return false;
1898
2319
  return sel.sr !== sel.er || sel.sc !== sel.ec;
1899
2320
  };
2321
+ const canMergeFromCell = (cell) => Boolean(cell && canMergeSelection() && isCellInsideSelection(cell));
2322
+ const canSplitCell = (cell) => Boolean(cell && (Math.max(1, cell.rowSpan || 1) > 1 || Math.max(1, cell.colSpan || 1) > 1));
2323
+ const isCellInsideSelection = (cell) => {
2324
+ const sel = selectionRef.current;
2325
+ if (!sel)
2326
+ return false;
2327
+ const pos = getCellPosition(cell);
2328
+ if (!pos || pos.tbody !== sel.tbody)
2329
+ return false;
2330
+ return pos.rIdx >= sel.sr && pos.rIdx <= sel.er && pos.cIdx >= sel.sc && pos.cIdx <= sel.ec;
2331
+ };
2332
+ const shouldUseTableSelection = (fallbackCell) => Boolean(selectionRef.current && fallbackCell && isCellInsideSelection(fallbackCell));
1900
2333
  const mergeSelection = () => {
1901
2334
  const sel = selectionRef.current;
1902
2335
  if (!sel)
1903
2336
  return;
1904
2337
  const { tbody, sr, sc, er, ec } = sel;
1905
- const rows = Array.from(tbody.querySelectorAll("tr"));
1906
- const anchorRow = rows[sr];
1907
- const anchor = cellsOfRow(anchorRow)[sc];
2338
+ const { grid } = getTableGrid(tbody);
2339
+ const anchor = grid[sr]?.[sc];
1908
2340
  if (!anchor)
1909
2341
  return;
1910
2342
  // Collect content and remove other cells
1911
2343
  const contents = [];
1912
- for (let r = sr; r <= er; r++) {
1913
- const row = rows[r];
1914
- const cells = cellsOfRow(row);
1915
- for (let c = sc; c <= ec; c++) {
1916
- const cell = cells[c];
1917
- if (!cell)
1918
- continue;
1919
- if (r === sr && c === sc)
1920
- continue;
1921
- const html = cell.innerHTML.trim();
1922
- if (html)
1923
- contents.push(html);
1924
- }
1925
- }
2344
+ const cellsToMerge = getCellsInGridRect(tbody, sr, sc, er, ec);
2345
+ cellsToMerge.forEach((cell) => {
2346
+ if (cell === anchor)
2347
+ return;
2348
+ const html = cell.innerHTML.trim();
2349
+ if (html)
2350
+ contents.push(html);
2351
+ });
1926
2352
  if (contents.length) {
1927
2353
  anchor.innerHTML = (anchor.innerHTML || "") + " " + contents.join(" ");
1928
2354
  }
@@ -1930,18 +2356,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1930
2356
  anchor.colSpan = ec - sc + 1;
1931
2357
  anchor.rowSpan = er - sr + 1;
1932
2358
  // Remove other cells
1933
- for (let r = sr; r <= er; r++) {
1934
- const row = rows[r];
1935
- const cells = cellsOfRow(row);
1936
- for (let c = ec; c >= sc; c--) {
1937
- const cell = cells[c];
1938
- if (!cell)
1939
- continue;
1940
- if (r === sr && c === sc)
1941
- continue;
2359
+ cellsToMerge.forEach((cell) => {
2360
+ if (cell !== anchor)
1942
2361
  cell.remove();
1943
- }
1944
- }
2362
+ });
1945
2363
  moveCaretToCell(anchor, false);
1946
2364
  clearSelectionDecor();
1947
2365
  handleInput();
@@ -1955,7 +2373,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1955
2373
  const numCols = Array.from(row.children).filter((c) => ["TD", "TH"].includes(c.tagName)).length;
1956
2374
  for (let i = 0; i < numCols; i++) {
1957
2375
  const td = document.createElement("td");
1958
- td.style.border = "1px solid var(--srte-border)";
2376
+ td.style.border = "1px solid #d1d5db";
1959
2377
  td.style.padding = "6px";
1960
2378
  td.style.minWidth = "60px";
1961
2379
  td.innerHTML = "&nbsp;";
@@ -1985,7 +2403,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
1985
2403
  for (const r of rows) {
1986
2404
  const cells = Array.from(r.children).filter((c) => ["TD", "TH"].includes(c.tagName));
1987
2405
  const td = document.createElement("td");
1988
- td.style.border = "1px solid var(--srte-border)";
2406
+ td.style.border = "1px solid #d1d5db";
1989
2407
  td.style.padding = "6px";
1990
2408
  td.style.minWidth = "60px";
1991
2409
  td.innerHTML = "&nbsp;";
@@ -2014,14 +2432,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2014
2432
  table.parentElement?.removeChild(table);
2015
2433
  };
2016
2434
  const toggleHeaderCell = (cell) => {
2435
+ clearSelectionDecor();
2017
2436
  const isTh = cell.tagName === "TH";
2018
- const replacement = document.createElement(isTh ? "td" : "th");
2019
- replacement.innerHTML = cell.innerHTML || "&nbsp;";
2020
- replacement.style.border =
2021
- cell.style.border || "1px solid var(--srte-border)";
2022
- replacement.style.padding = cell.style.padding || "6px";
2023
- replacement.style.minWidth = cell.style.minWidth || "60px";
2024
- cell.parentElement?.replaceChild(replacement, cell);
2437
+ replaceTableCellTag(cell, isTh ? "td" : "th");
2438
+ handleInput();
2025
2439
  };
2026
2440
  const deleteTable = (cell) => {
2027
2441
  const pos = getCellPosition(cell);
@@ -2046,7 +2460,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2046
2460
  const currentRow = Array.from(tbody.querySelectorAll("tr"))[rIdx];
2047
2461
  for (let j = 1; j < cs; j++) {
2048
2462
  const td = document.createElement("td");
2049
- td.style.border = "1px solid var(--srte-border)";
2463
+ td.style.border = "1px solid #d1d5db";
2050
2464
  td.style.padding = "6px";
2051
2465
  td.style.minWidth = "60px";
2052
2466
  td.innerHTML = "&nbsp;";
@@ -2059,7 +2473,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2059
2473
  const row = Array.from(tbody.querySelectorAll("tr"))[rIdx + i];
2060
2474
  for (let j = 0; j < cs; j++) {
2061
2475
  const td = document.createElement("td");
2062
- td.style.border = "1px solid var(--srte-border)";
2476
+ td.style.border = "1px solid #d1d5db";
2063
2477
  td.style.padding = "6px";
2064
2478
  td.style.minWidth = "60px";
2065
2479
  td.innerHTML = "&nbsp;";
@@ -2074,29 +2488,17 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2074
2488
  const pos = getCellPosition(cell);
2075
2489
  if (!pos)
2076
2490
  return;
2077
- const { tbody } = pos;
2078
- const firstRow = tbody.querySelector("tr");
2079
- if (!firstRow)
2080
- return;
2081
- const cells = cellsOfRow(firstRow);
2491
+ clearSelectionDecor();
2492
+ const { row } = pos;
2493
+ const cells = cellsOfRow(row);
2082
2494
  const shouldMakeHeader = cells.some((c) => c.tagName !== "TH");
2083
2495
  for (const c of cells) {
2084
2496
  const isTh = c.tagName === "TH";
2085
2497
  if (shouldMakeHeader && !isTh) {
2086
- const th = document.createElement("th");
2087
- th.innerHTML = c.innerHTML || "&nbsp;";
2088
- th.style.border = c.style.border || "1px solid var(--srte-border)";
2089
- th.style.padding = c.style.padding || "6px";
2090
- th.style.minWidth = c.style.minWidth || "60px";
2091
- firstRow.replaceChild(th, c);
2498
+ replaceTableCellTag(c, "th");
2092
2499
  }
2093
2500
  else if (!shouldMakeHeader && isTh) {
2094
- const td = document.createElement("td");
2095
- td.innerHTML = c.innerHTML || "&nbsp;";
2096
- td.style.border = c.style.border || "1px solid var(--srte-border)";
2097
- td.style.padding = c.style.padding || "6px";
2098
- td.style.minWidth = c.style.minWidth || "60px";
2099
- firstRow.replaceChild(td, c);
2501
+ replaceTableCellTag(c, "td");
2100
2502
  }
2101
2503
  }
2102
2504
  handleInput();
@@ -2105,35 +2507,30 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2105
2507
  const pos = getCellPosition(cell);
2106
2508
  if (!pos)
2107
2509
  return;
2510
+ clearSelectionDecor();
2108
2511
  const { tbody, cIdx } = pos;
2109
- const rows = Array.from(tbody.querySelectorAll("tr"));
2110
- const columnCells = rows
2111
- .map((row) => cellsOfRow(row)[cIdx])
2112
- .filter(Boolean);
2512
+ const { grid } = getTableGrid(tbody);
2513
+ const seen = new Set();
2514
+ const columnCells = grid
2515
+ .map((row) => row?.[cIdx])
2516
+ .filter((candidate) => {
2517
+ if (!candidate || seen.has(candidate))
2518
+ return false;
2519
+ seen.add(candidate);
2520
+ return true;
2521
+ });
2113
2522
  const shouldMakeHeader = columnCells.some((c) => c.tagName !== "TH");
2114
2523
  for (const c of columnCells) {
2115
- const replacement = document.createElement(shouldMakeHeader ? "th" : "td");
2116
- replacement.innerHTML = c.innerHTML || "&nbsp;";
2117
- replacement.style.border = c.style.border || "1px solid var(--srte-border)";
2118
- replacement.style.padding = c.style.padding || "6px";
2119
- replacement.style.minWidth = c.style.minWidth || "60px";
2120
- c.parentElement?.replaceChild(replacement, c);
2524
+ replaceTableCellTag(c, shouldMakeHeader ? "th" : "td");
2121
2525
  }
2122
2526
  handleInput();
2123
2527
  };
2124
2528
  const applyBgToSelection = (hex, fallbackCell) => {
2125
- const sel = selectionRef.current;
2529
+ const sel = shouldUseTableSelection(fallbackCell) ? selectionRef.current : null;
2126
2530
  if (sel) {
2127
- const rows = Array.from(sel.tbody.querySelectorAll("tr"));
2128
- for (let r = sel.sr; r <= sel.er; r++) {
2129
- const row = rows[r];
2130
- const cells = cellsOfRow(row);
2131
- for (let c = sel.sc; c <= sel.ec; c++) {
2132
- const cell = cells[c];
2133
- if (cell)
2134
- cell.style.background = hex;
2135
- }
2136
- }
2531
+ getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec).forEach((cell) => {
2532
+ cell.style.background = hex;
2533
+ });
2137
2534
  }
2138
2535
  else if (fallbackCell) {
2139
2536
  fallbackCell.style.background = hex;
@@ -2143,25 +2540,21 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2143
2540
  const applyToggle = (cell) => {
2144
2541
  const cur = cell.style.border;
2145
2542
  cell.style.border =
2146
- cur && cur !== "none" ? "none" : "1px solid #000";
2543
+ cur && cur !== "none" ? "none" : "1px solid #d1d5db";
2147
2544
  };
2148
- const sel = selectionRef.current;
2545
+ const sel = shouldUseTableSelection(fallbackCell) ? selectionRef.current : null;
2149
2546
  if (sel) {
2150
- const rows = Array.from(sel.tbody.querySelectorAll("tr"));
2151
- for (let r = sel.sr; r <= sel.er; r++) {
2152
- const row = rows[r];
2153
- const cells = cellsOfRow(row);
2154
- for (let c = sel.sc; c <= sel.ec; c++) {
2155
- const cell = cells[c];
2156
- if (cell)
2157
- applyToggle(cell);
2158
- }
2159
- }
2547
+ getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec).forEach(applyToggle);
2160
2548
  }
2161
2549
  else if (fallbackCell) {
2162
2550
  applyToggle(fallbackCell);
2163
2551
  }
2164
2552
  };
2553
+ const runTableCellAction = (cell, action) => {
2554
+ action(cell);
2555
+ handleInput();
2556
+ setTableMenu(null);
2557
+ };
2165
2558
  // Table column and row resizing functions
2166
2559
  const getColumnCells = (table, colIndex) => {
2167
2560
  const tbody = table.querySelector('tbody');
@@ -2282,6 +2675,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2282
2675
  });
2283
2676
  });
2284
2677
  };
2678
+ const activeButtonStyle = (active = false, extra = {}) => ({
2679
+ height: 32,
2680
+ minWidth: 32,
2681
+ padding: "0 8px",
2682
+ border: active
2683
+ ? "2px solid var(--srte-accent)"
2684
+ : "1px solid var(--srte-input-border)",
2685
+ borderRadius: 6,
2686
+ background: active ? "var(--srte-accent-bg)" : "var(--srte-input-bg)",
2687
+ color: "var(--srte-input-text)",
2688
+ boxShadow: active ? "inset 0 0 0 1px var(--srte-accent)" : "none",
2689
+ ...extra,
2690
+ });
2691
+ const preserveToolbarMouseDown = (event) => {
2692
+ const target = event.target;
2693
+ const button = target?.closest("button");
2694
+ preserveEditorSelection();
2695
+ if (button && !button.hasAttribute("disabled")) {
2696
+ event.preventDefault();
2697
+ }
2698
+ };
2285
2699
  const editorClass = `srte-editor${theme === 'dark' ? ' srte-dark' : ''}${className ? ' ' + className : ''}`;
2286
2700
  return (_jsxs("div", { className: editorClass, style: {
2287
2701
  border: "1px solid var(--srte-border)",
@@ -2294,7 +2708,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2294
2708
  background: "var(--srte-bg)",
2295
2709
  color: "var(--srte-text)",
2296
2710
  boxSizing: "border-box"
2297
- }, children: [_jsxs("div", { style: {
2711
+ }, children: [_jsxs("div", { onMouseDown: preserveToolbarMouseDown, style: {
2298
2712
  display: "flex",
2299
2713
  flexWrap: "wrap",
2300
2714
  maxWidth: "100%",
@@ -2343,7 +2757,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2343
2757
  } }), _jsx("input", { ref: mdInputRef, type: "file", accept: ".md,.markdown,text/markdown,text/plain", style: { display: "none" }, onChange: (e) => {
2344
2758
  importTextFile(e.currentTarget.files, "md");
2345
2759
  e.currentTarget.value = "";
2346
- } }), _jsxs("select", { defaultValue: "p", onChange: (e) => {
2760
+ } }), _jsxs("select", { defaultValue: "p", onMouseDown: preserveEditorSelection, onChange: (e) => {
2347
2761
  const val = e.target.value;
2348
2762
  if (val === "p")
2349
2763
  applyFormatBlock("<p>");
@@ -2360,42 +2774,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2360
2774
  borderRadius: 6,
2361
2775
  background: "var(--srte-input-bg)",
2362
2776
  color: "var(--srte-input-text)",
2363
- }, 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"), style: {
2364
- height: 32,
2365
- minWidth: 32,
2366
- padding: "0 8px",
2367
- border: "1px solid var(--srte-input-border)",
2368
- borderRadius: 6,
2369
- background: "var(--srte-input-bg)",
2370
- color: "var(--srte-input-text)",
2371
- }, children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), style: {
2372
- height: 32,
2373
- minWidth: 32,
2374
- padding: "0 8px",
2375
- border: "1px solid var(--srte-input-border)",
2376
- borderRadius: 6,
2377
- background: "var(--srte-input-bg)",
2378
- fontStyle: "italic",
2379
- color: "var(--srte-input-text)",
2380
- }, children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), style: {
2381
- height: 32,
2382
- minWidth: 32,
2383
- padding: "0 8px",
2384
- border: "1px solid var(--srte-input-border)",
2385
- borderRadius: 6,
2386
- background: "var(--srte-input-bg)",
2387
- textDecoration: "underline",
2388
- color: "var(--srte-input-text)",
2389
- }, children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), style: {
2390
- height: 32,
2391
- minWidth: 32,
2392
- padding: "0 8px",
2393
- border: "1px solid var(--srte-input-border)",
2394
- borderRadius: 6,
2395
- background: "var(--srte-input-bg)",
2396
- textDecoration: "line-through",
2397
- color: "var(--srte-input-text)",
2398
- }, children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
2777
+ }, 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: () => {
2399
2778
  // Save selection before dropdown interaction
2400
2779
  const sel = window.getSelection();
2401
2780
  if (sel && sel.rangeCount > 0) {
@@ -2452,45 +2831,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2452
2831
  borderRadius: 6,
2453
2832
  background: "var(--srte-input-bg)",
2454
2833
  color: "var(--srte-input-text)",
2455
- }, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onClick: () => exec("subscript"), style: {
2456
- height: 32,
2457
- minWidth: 32,
2458
- padding: "0 8px",
2459
- border: "1px solid var(--srte-input-border)",
2460
- borderRadius: 6,
2461
- background: "var(--srte-input-bg)",
2462
- color: "var(--srte-input-text)",
2463
- }, children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onClick: () => exec("superscript"), style: {
2464
- height: 32,
2465
- minWidth: 32,
2466
- padding: "0 8px",
2467
- border: "1px solid var(--srte-input-border)",
2468
- borderRadius: 6,
2469
- background: "var(--srte-input-bg)",
2470
- color: "var(--srte-input-text)",
2471
- }, children: ["X", _jsx("sup", { children: "2" })] }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
2472
- height: 32,
2473
- padding: "0 10px",
2474
- border: "1px solid var(--srte-input-border)",
2475
- borderRadius: 6,
2476
- background: "var(--srte-input-bg)",
2477
- color: "var(--srte-input-text)",
2478
- }, children: "\u2022 List" }), _jsx("button", { title: "Numbered list", onClick: () => exec("insertOrderedList"), style: {
2479
- height: 32,
2480
- padding: "0 10px",
2481
- border: "1px solid var(--srte-input-border)",
2482
- borderRadius: 6,
2483
- background: "var(--srte-input-bg)",
2484
- color: "var(--srte-input-text)",
2485
- }, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: toggleBlockquote, style: {
2486
- height: 32,
2487
- minWidth: 32,
2488
- padding: "0 8px",
2489
- border: "1px solid var(--srte-input-border)",
2490
- borderRadius: 6,
2491
- background: "var(--srte-input-bg)",
2492
- color: "var(--srte-input-text)",
2493
- }, children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
2834
+ }, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onClick: () => exec("subscript"), "aria-pressed": activeState.subscript, style: activeButtonStyle(activeState.subscript), children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onClick: () => exec("superscript"), "aria-pressed": activeState.superscript, style: activeButtonStyle(activeState.superscript), children: ["X", _jsx("sup", { children: "2" })] }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), "aria-pressed": activeState.unorderedList, style: activeButtonStyle(activeState.unorderedList, { padding: "0 10px" }), children: "\u2022 List" }), _jsx("button", { title: "Numbered list", onClick: () => exec("insertOrderedList"), "aria-pressed": activeState.orderedList, style: activeButtonStyle(activeState.orderedList, { padding: "0 10px" }), children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: toggleBlockquote, "aria-pressed": activeState.blockquote, style: activeButtonStyle(activeState.blockquote), children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
2494
2835
  height: 32,
2495
2836
  minWidth: 32,
2496
2837
  padding: "0 8px",
@@ -2498,16 +2839,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2498
2839
  borderRadius: 6,
2499
2840
  background: "var(--srte-input-bg)",
2500
2841
  color: "var(--srte-input-text)",
2501
- }, children: "\u03A9" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), style: {
2502
- height: 32,
2842
+ }, children: "\u03A9" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), "aria-pressed": activeState.codeBlock, style: activeButtonStyle(activeState.codeBlock, {
2503
2843
  minWidth: 36,
2504
- padding: "0 8px",
2505
- border: "1px solid var(--srte-input-border)",
2506
- borderRadius: 6,
2507
- background: "var(--srte-input-bg)",
2508
2844
  fontFamily: "ui-monospace, SFMono-Regular, Menlo",
2509
- color: "var(--srte-input-text)",
2510
- }, children: "< />" }), formula && (_jsx("button", { title: "Insert formula", onClick: () => setShowFormulaDialog(true), style: {
2845
+ }), children: "< />" }), formula && (_jsx("button", { title: "Insert formula", onClick: () => setShowFormulaDialog(true), style: {
2511
2846
  height: 32,
2512
2847
  minWidth: 32,
2513
2848
  padding: "0 8px",
@@ -2685,17 +3020,19 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2685
3020
  position: "fixed",
2686
3021
  inset: 0,
2687
3022
  background: "var(--srte-modal-backdrop)",
3023
+ backdropFilter: "var(--srte-modal-backdrop-filter)",
3024
+ WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
2688
3025
  display: "flex",
2689
3026
  alignItems: "center",
2690
3027
  justifyContent: "center",
2691
- zIndex: 50,
3028
+ zIndex: 90,
2692
3029
  }, onClick: () => setShowTableDialog(false), children: _jsxs("div", { style: {
2693
3030
  background: "var(--srte-modal-bg)",
2694
3031
  color: "var(--srte-modal-text)",
2695
3032
  padding: 16,
2696
3033
  borderRadius: 8,
2697
3034
  minWidth: 280,
2698
- }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 8 }, children: "Insert table" }), _jsxs("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [_jsx("div", { style: {
3035
+ }, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 8 }, children: "Insert table" }), _jsxs("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [_jsx("div", { style: {
2699
3036
  display: "grid",
2700
3037
  gridTemplateColumns: "repeat(10, 18px)",
2701
3038
  gap: 2,
@@ -2729,6 +3066,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2729
3066
  position: "fixed",
2730
3067
  inset: 0,
2731
3068
  background: "var(--srte-modal-backdrop)",
3069
+ backdropFilter: "var(--srte-modal-backdrop-filter)",
3070
+ WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
2732
3071
  display: "flex",
2733
3072
  alignItems: "center",
2734
3073
  justifyContent: "center",
@@ -2783,6 +3122,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2783
3122
  position: "fixed",
2784
3123
  inset: 0,
2785
3124
  background: "var(--srte-modal-backdrop)",
3125
+ backdropFilter: "var(--srte-modal-backdrop-filter)",
3126
+ WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
2786
3127
  display: "flex",
2787
3128
  alignItems: "center",
2788
3129
  justifyContent: "center",
@@ -2849,6 +3190,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2849
3190
  position: "fixed",
2850
3191
  inset: 0,
2851
3192
  background: "var(--srte-modal-backdrop)",
3193
+ backdropFilter: "var(--srte-modal-backdrop-filter)",
3194
+ WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
2852
3195
  display: "flex",
2853
3196
  alignItems: "center",
2854
3197
  justifyContent: "center",
@@ -2893,6 +3236,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2893
3236
  position: "fixed",
2894
3237
  inset: 0,
2895
3238
  background: "var(--srte-modal-backdrop)",
3239
+ backdropFilter: "var(--srte-modal-backdrop-filter)",
3240
+ WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
2896
3241
  display: "flex",
2897
3242
  alignItems: "center",
2898
3243
  justifyContent: "center",
@@ -2990,7 +3335,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
2990
3335
  borderRadius: 4,
2991
3336
  background: "var(--srte-input-bg)",
2992
3337
  color: "var(--srte-modal-text)",
2993
- }, title: sym, children: sym }, i)))] })] }) })), _jsx("div", { style: {
3338
+ }, title: sym, children: sym }, i)))] })] }) })), _jsxs("div", { ref: editorScrollRef, style: {
2994
3339
  width: "100%",
2995
3340
  maxWidth: "100%",
2996
3341
  flex: "1 1 auto",
@@ -3003,374 +3348,376 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3003
3348
  boxSizing: "border-box",
3004
3349
  position: "relative",
3005
3350
  scrollPaddingBottom: 24,
3006
- }, children: _jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
3007
- isComposingRef.current = false;
3008
- handleInput();
3009
- }, onPaste: (e) => {
3010
- const items = e.clipboardData?.files;
3011
- if (media && items && items.length) {
3012
- const hasImage = Array.from(items).some((f) => f.type.startsWith("image/"));
3013
- if (hasImage) {
3351
+ }, children: [_jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onKeyUp: updateActiveState, onMouseUp: updateActiveState, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
3352
+ isComposingRef.current = false;
3353
+ handleInput();
3354
+ }, onPaste: (e) => {
3355
+ const items = e.clipboardData?.files;
3356
+ if (media && items && items.length) {
3357
+ const hasImage = Array.from(items).some((f) => f.type.startsWith("image/"));
3358
+ if (hasImage) {
3359
+ e.preventDefault();
3360
+ handleLocalImageFiles(items);
3361
+ return;
3362
+ }
3363
+ }
3364
+ const html = e.clipboardData?.getData("text/html");
3365
+ if (html) {
3014
3366
  e.preventDefault();
3015
- handleLocalImageFiles(items);
3016
- return;
3367
+ insertCleanHtml(cleanPastedHtml(html));
3017
3368
  }
3018
- }
3019
- const html = e.clipboardData?.getData("text/html");
3020
- if (html) {
3021
- e.preventDefault();
3022
- insertCleanHtml(cleanPastedHtml(html));
3023
- }
3024
- }, onDragOver: (e) => {
3025
- // Allow dragging images within editor and file drops
3026
- if (draggedImageRef.current ||
3027
- e.dataTransfer?.types?.includes("Files")) {
3028
- e.preventDefault();
3029
- }
3030
- }, onDrop: (e) => {
3031
- // Move existing dragged image inside editor
3032
- if (draggedImageRef.current) {
3033
- e.preventDefault();
3034
- const x = e.clientX;
3035
- const y = e.clientY;
3036
- let range = null;
3037
- // @ts-ignore
3038
- if (document.caretRangeFromPoint) {
3039
- // @ts-ignore
3040
- range = document.caretRangeFromPoint(x, y);
3369
+ }, onDragOver: (e) => {
3370
+ // Allow dragging images within editor and file drops
3371
+ if (draggedImageRef.current ||
3372
+ e.dataTransfer?.types?.includes("Files")) {
3373
+ e.preventDefault();
3041
3374
  }
3042
- else if (document.caretPositionFromPoint) {
3043
- const pos = document.caretPositionFromPoint(x, y);
3044
- if (pos) {
3045
- range = document.createRange();
3046
- range.setStart(pos.offsetNode, pos.offset);
3375
+ }, onDrop: (e) => {
3376
+ // Move existing dragged image inside editor
3377
+ if (draggedImageRef.current) {
3378
+ e.preventDefault();
3379
+ const x = e.clientX;
3380
+ const y = e.clientY;
3381
+ let range = null;
3382
+ // @ts-ignore
3383
+ if (document.caretRangeFromPoint) {
3384
+ // @ts-ignore
3385
+ range = document.caretRangeFromPoint(x, y);
3047
3386
  }
3048
- }
3049
- const img = draggedImageRef.current;
3050
- draggedImageRef.current = null;
3051
- if (range &&
3052
- img &&
3053
- editableRef.current?.contains(range.commonAncestorContainer)) {
3054
- // Avoid inserting inside the image itself
3055
- if (range.startContainer === img || range.endContainer === img)
3056
- return;
3057
- // If dropping inside a link, insert right after the link element
3058
- let container = range.commonAncestorContainer;
3059
- let linkAncestor = null;
3060
- let el = container;
3061
- while (el && el !== editableRef.current) {
3062
- if (el.tagName === "A") {
3063
- linkAncestor = el;
3064
- break;
3387
+ else if (document.caretPositionFromPoint) {
3388
+ const pos = document.caretPositionFromPoint(x, y);
3389
+ if (pos) {
3390
+ range = document.createRange();
3391
+ range.setStart(pos.offsetNode, pos.offset);
3065
3392
  }
3066
- el = el.parentElement;
3067
- }
3068
- if (linkAncestor) {
3069
- linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
3070
3393
  }
3071
- else {
3072
- range.insertNode(img);
3394
+ const img = draggedImageRef.current;
3395
+ draggedImageRef.current = null;
3396
+ if (range &&
3397
+ img &&
3398
+ editableRef.current?.contains(range.commonAncestorContainer)) {
3399
+ // Avoid inserting inside the image itself
3400
+ if (range.startContainer === img || range.endContainer === img)
3401
+ return;
3402
+ // If dropping inside a link, insert right after the link element
3403
+ let container = range.commonAncestorContainer;
3404
+ let linkAncestor = null;
3405
+ let el = container;
3406
+ while (el && el !== editableRef.current) {
3407
+ if (el.tagName === "A") {
3408
+ linkAncestor = el;
3409
+ break;
3410
+ }
3411
+ el = el.parentElement;
3412
+ }
3413
+ if (linkAncestor) {
3414
+ linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
3415
+ }
3416
+ else {
3417
+ range.insertNode(img);
3418
+ }
3419
+ const r = document.createRange();
3420
+ r.setStartAfter(img);
3421
+ r.collapse(true);
3422
+ safeSelectRange(r);
3423
+ setSelectedImage(img);
3424
+ scheduleImageOverlay();
3425
+ handleInput();
3073
3426
  }
3074
- const r = document.createRange();
3075
- r.setStartAfter(img);
3076
- r.collapse(true);
3077
- safeSelectRange(r);
3078
- setSelectedImage(img);
3079
- scheduleImageOverlay();
3080
- handleInput();
3427
+ return;
3081
3428
  }
3082
- return;
3083
- }
3084
- if (media && e.dataTransfer?.files?.length) {
3085
- e.preventDefault();
3086
- // Try to move caret to drop point
3087
- const x = e.clientX;
3088
- const y = e.clientY;
3089
- let range = null;
3090
- // @ts-ignore
3091
- if (document.caretRangeFromPoint) {
3429
+ if (media && e.dataTransfer?.files?.length) {
3430
+ e.preventDefault();
3431
+ // Try to move caret to drop point
3432
+ const x = e.clientX;
3433
+ const y = e.clientY;
3434
+ let range = null;
3092
3435
  // @ts-ignore
3093
- range = document.caretRangeFromPoint(x, y);
3094
- }
3095
- else if (document.caretPositionFromPoint) {
3096
- const pos = document.caretPositionFromPoint(x, y);
3097
- if (pos) {
3098
- range = document.createRange();
3099
- range.setStart(pos.offsetNode, pos.offset);
3436
+ if (document.caretRangeFromPoint) {
3437
+ // @ts-ignore
3438
+ range = document.caretRangeFromPoint(x, y);
3100
3439
  }
3440
+ else if (document.caretPositionFromPoint) {
3441
+ const pos = document.caretPositionFromPoint(x, y);
3442
+ if (pos) {
3443
+ range = document.createRange();
3444
+ range.setStart(pos.offsetNode, pos.offset);
3445
+ }
3446
+ }
3447
+ if (range) {
3448
+ const sel = window.getSelection();
3449
+ sel?.removeAllRanges();
3450
+ sel?.addRange(range);
3451
+ }
3452
+ handleLocalImageFiles(e.dataTransfer.files);
3101
3453
  }
3102
- if (range) {
3103
- const sel = window.getSelection();
3104
- sel?.removeAllRanges();
3105
- sel?.addRange(range);
3454
+ }, onClick: (e) => {
3455
+ const t = e.target;
3456
+ if (t && t.tagName === "IMG") {
3457
+ setSelectedImage(t);
3458
+ scheduleImageOverlay();
3106
3459
  }
3107
- handleLocalImageFiles(e.dataTransfer.files);
3108
- }
3109
- }, onClick: (e) => {
3110
- const t = e.target;
3111
- if (t && t.tagName === "IMG") {
3112
- setSelectedImage(t);
3113
- scheduleImageOverlay();
3114
- }
3115
- else {
3116
- setSelectedImage(null);
3117
- setImageOverlay(null);
3118
- }
3119
- }, onDragStart: (e) => {
3120
- const t = e.target;
3121
- if (t && t.tagName === "IMG") {
3122
- draggedImageRef.current = t;
3123
- try {
3124
- e.dataTransfer?.setData("text/plain", "moving-image");
3125
- e.dataTransfer.effectAllowed = "move";
3126
- // Provide a subtle drag image
3127
- const dt = e.dataTransfer;
3128
- if (dt && typeof dt.setDragImage === "function") {
3129
- const ghost = new Image();
3130
- ghost.src = t.src;
3131
- ghost.width = Math.min(120, t.width);
3132
- ghost.height = Math.min(120, t.height);
3133
- dt.setDragImage(ghost, 10, 10);
3460
+ else {
3461
+ setSelectedImage(null);
3462
+ setImageOverlay(null);
3463
+ }
3464
+ }, onDragStart: (e) => {
3465
+ const t = e.target;
3466
+ if (t && t.tagName === "IMG") {
3467
+ draggedImageRef.current = t;
3468
+ try {
3469
+ e.dataTransfer?.setData("text/plain", "moving-image");
3470
+ e.dataTransfer.effectAllowed = "move";
3471
+ // Provide a subtle drag image
3472
+ const dt = e.dataTransfer;
3473
+ if (dt && typeof dt.setDragImage === "function") {
3474
+ const ghost = new Image();
3475
+ ghost.src = t.src;
3476
+ ghost.width = Math.min(120, t.width);
3477
+ ghost.height = Math.min(120, t.height);
3478
+ dt.setDragImage(ghost, 10, 10);
3479
+ }
3134
3480
  }
3481
+ catch { }
3135
3482
  }
3136
- catch { }
3137
- }
3138
- else {
3483
+ else {
3484
+ draggedImageRef.current = null;
3485
+ }
3486
+ }, onDragEnd: () => {
3139
3487
  draggedImageRef.current = null;
3140
- }
3141
- }, onDragEnd: () => {
3142
- draggedImageRef.current = null;
3143
- }, style: {
3144
- minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
3145
- maxWidth: "100%",
3146
- overflowX: "visible",
3147
- padding: "16px",
3148
- paddingBottom: "32px",
3149
- outline: "none",
3150
- lineHeight: 1.6,
3151
- boxSizing: "border-box",
3152
- fontFamily: defaultFont || "inherit",
3153
- }, "data-placeholder": placeholder, onFocus: (e) => {
3154
- // Ensure the editor has at least one paragraph to type into
3155
- const el = e.currentTarget;
3156
- if (!el.innerHTML || el.innerHTML === "<br>") {
3157
- el.innerHTML = "<p><br></p>";
3158
- }
3159
- }, onKeyDown: (e) => {
3160
- if (formula &&
3161
- (e.metaKey || e.ctrlKey) &&
3162
- String(e.key).toLowerCase() === "m") {
3163
- e.preventDefault();
3164
- setShowFormulaDialog(true);
3165
- return;
3166
- }
3167
- // Keep Tab for indentation in lists; otherwise insert 2 spaces
3168
- if (e.key === "Tab") {
3169
- e.preventDefault();
3170
- if (document.queryCommandState("insertUnorderedList") ||
3171
- document.queryCommandState("insertOrderedList")) {
3172
- exec(e.shiftKey ? "outdent" : "indent");
3488
+ }, style: {
3489
+ minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
3490
+ maxWidth: "100%",
3491
+ overflowX: "visible",
3492
+ padding: "16px",
3493
+ paddingBottom: "32px",
3494
+ outline: "none",
3495
+ lineHeight: 1.6,
3496
+ boxSizing: "border-box",
3497
+ fontFamily: defaultFont || "inherit",
3498
+ }, "data-placeholder": placeholder, onFocus: (e) => {
3499
+ // Ensure the editor has at least one paragraph to type into
3500
+ const el = e.currentTarget;
3501
+ if (!el.innerHTML || el.innerHTML === "<br>") {
3502
+ el.innerHTML = "<p><br></p>";
3173
3503
  }
3174
- else {
3175
- document.execCommand("insertText", false, " ");
3504
+ updateActiveState();
3505
+ }, onKeyDown: (e) => {
3506
+ if (formula &&
3507
+ (e.metaKey || e.ctrlKey) &&
3508
+ String(e.key).toLowerCase() === "m") {
3509
+ e.preventDefault();
3510
+ setShowFormulaDialog(true);
3511
+ return;
3176
3512
  }
3177
- }
3178
- // Table navigation with arrows inside cells
3179
- if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
3180
- const sel = window.getSelection();
3181
- const cell = getClosestCell(sel?.anchorNode || null);
3182
- if (table &&
3183
- cell &&
3184
- cell.parentElement &&
3185
- cell.parentElement.parentElement) {
3186
- const row = cell.parentElement;
3187
- const tbody = row.parentElement;
3188
- const cells = Array.from(row.children).filter((c) => c.tagName === "TD" ||
3189
- c.tagName === "TH");
3190
- const rows = Array.from(tbody.children);
3191
- const rIdx = rows.indexOf(row);
3192
- const cIdx = cells.indexOf(cell);
3193
- const atStart = (sel?.anchorOffset || 0) === 0;
3194
- const cellTextLen = (cell.textContent || "").length;
3195
- const atEnd = (sel?.anchorOffset || 0) >= cellTextLen;
3196
- let target = null;
3197
- if (e.key === "ArrowLeft" && atStart && cIdx > 0) {
3198
- target = row.children[cIdx - 1];
3199
- }
3200
- else if (e.key === "ArrowRight" &&
3201
- atEnd &&
3202
- cIdx < row.children.length - 1) {
3203
- target = row.children[cIdx + 1];
3204
- }
3205
- else if (e.key === "ArrowUp" && rIdx > 0 && atStart) {
3206
- target = rows[rIdx - 1].children[cIdx];
3513
+ // Keep Tab for indentation in lists; otherwise insert 2 spaces
3514
+ if (e.key === "Tab") {
3515
+ e.preventDefault();
3516
+ if (document.queryCommandState("insertUnorderedList") ||
3517
+ document.queryCommandState("insertOrderedList")) {
3518
+ exec(e.shiftKey ? "outdent" : "indent");
3207
3519
  }
3208
- else if (e.key === "ArrowDown" &&
3209
- rIdx < rows.length - 1 &&
3210
- atEnd) {
3211
- target = rows[rIdx + 1].children[cIdx];
3520
+ else {
3521
+ document.execCommand("insertText", false, " ");
3212
3522
  }
3213
- if (target) {
3214
- e.preventDefault();
3215
- moveCaretToCell(target, e.key === "ArrowRight" || e.key === "ArrowDown");
3523
+ }
3524
+ // Table navigation with arrows inside cells
3525
+ if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
3526
+ const sel = window.getSelection();
3527
+ const cell = getClosestCell(sel?.anchorNode || null);
3528
+ if (table &&
3529
+ cell &&
3530
+ cell.parentElement &&
3531
+ cell.parentElement.parentElement) {
3532
+ const row = cell.parentElement;
3533
+ const tbody = row.parentElement;
3534
+ const cells = Array.from(row.children).filter((c) => c.tagName === "TD" ||
3535
+ c.tagName === "TH");
3536
+ const rows = Array.from(tbody.children);
3537
+ const rIdx = rows.indexOf(row);
3538
+ const cIdx = cells.indexOf(cell);
3539
+ const atStart = (sel?.anchorOffset || 0) === 0;
3540
+ const cellTextLen = (cell.textContent || "").length;
3541
+ const atEnd = (sel?.anchorOffset || 0) >= cellTextLen;
3542
+ let target = null;
3543
+ if (e.key === "ArrowLeft" && atStart && cIdx > 0) {
3544
+ target = row.children[cIdx - 1];
3545
+ }
3546
+ else if (e.key === "ArrowRight" &&
3547
+ atEnd &&
3548
+ cIdx < row.children.length - 1) {
3549
+ target = row.children[cIdx + 1];
3550
+ }
3551
+ else if (e.key === "ArrowUp" && rIdx > 0 && atStart) {
3552
+ target = rows[rIdx - 1].children[cIdx];
3553
+ }
3554
+ else if (e.key === "ArrowDown" &&
3555
+ rIdx < rows.length - 1 &&
3556
+ atEnd) {
3557
+ target = rows[rIdx + 1].children[cIdx];
3558
+ }
3559
+ if (target) {
3560
+ e.preventDefault();
3561
+ moveCaretToCell(target, e.key === "ArrowRight" || e.key === "ArrowDown");
3562
+ }
3216
3563
  }
3217
3564
  }
3218
- }
3219
- }, onMouseDown: (e) => {
3220
- const cell = getClosestCell(e.target);
3221
- if (!cell) {
3222
- clearSelectionDecor();
3223
- return;
3224
- }
3225
- const pos = getCellPosition(cell);
3226
- if (!pos)
3227
- return;
3228
- selectingRef.current = { tbody: pos.tbody, start: cell };
3229
- const onMove = (ev) => {
3230
- const under = document.elementFromPoint(ev.clientX, ev.clientY);
3231
- const overCell = getClosestCell(under);
3232
- const startInfo = selectingRef.current;
3233
- if (!overCell || !startInfo)
3565
+ }, onMouseDown: (e) => {
3566
+ const cell = getClosestCell(e.target);
3567
+ if (!cell) {
3568
+ clearSelectionDecor();
3234
3569
  return;
3235
- const a = getCellPosition(startInfo.start);
3236
- const b = getCellPosition(overCell);
3237
- if (!a || !b || a.tbody !== b.tbody)
3238
- return;
3239
- const sr = Math.min(a.rIdx, b.rIdx);
3240
- const sc = Math.min(a.cIdx, b.cIdx);
3241
- const er = Math.max(a.rIdx, b.rIdx);
3242
- const ec = Math.max(a.cIdx, b.cIdx);
3243
- updateSelectionDecor(a.tbody, sr, sc, er, ec);
3244
- };
3245
- const onUp = () => {
3246
- window.removeEventListener("mousemove", onMove);
3247
- window.removeEventListener("mouseup", onUp);
3248
- selectingRef.current = null;
3249
- };
3250
- window.addEventListener("mousemove", onMove);
3251
- window.addEventListener("mouseup", onUp);
3252
- }, onContextMenu: (e) => {
3253
- const target = e.target;
3254
- if (target && target.tagName === "IMG") {
3255
- e.preventDefault();
3256
- const vw = window.innerWidth;
3257
- const vh = window.innerHeight;
3258
- const menuW = 220;
3259
- const menuH = 200;
3260
- const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
3261
- const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
3262
- setImageMenu({ x, y, img: target });
3263
- setTableMenu(null);
3264
- return;
3265
- }
3266
- const cell = getClosestCell(e.target);
3267
- if (cell) {
3268
- e.preventDefault();
3269
- const vw = window.innerWidth;
3270
- const vh = window.innerHeight;
3271
- const menuW = 220;
3272
- const menuH = 300;
3273
- const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
3274
- const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
3275
- setTableMenu({ x, y, cell });
3276
- }
3277
- else {
3278
- setTableMenu(null);
3279
- setImageMenu(null);
3280
- }
3281
- } }) }), selectedImage && imageOverlay && (_jsxs("div", { style: {
3282
- position: "fixed",
3283
- left: imageOverlay.left,
3284
- top: imageOverlay.top,
3285
- width: imageOverlay.width,
3286
- height: imageOverlay.height,
3287
- pointerEvents: "none",
3288
- zIndex: 49,
3289
- }, children: [_jsx("div", { style: {
3290
- position: "absolute",
3291
- inset: 0,
3292
- outline: "2px solid var(--srte-accent)",
3293
- outlineOffset: -2,
3294
- } }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
3295
- e.preventDefault();
3296
- if (!selectedImage)
3570
+ }
3571
+ const pos = getCellPosition(cell);
3572
+ if (!pos)
3297
3573
  return;
3298
- resizingRef.current = {
3299
- side: "left",
3300
- startX: e.clientX,
3301
- startWidth: selectedImage.getBoundingClientRect().width,
3302
- };
3574
+ selectingRef.current = { tbody: pos.tbody, start: cell };
3303
3575
  const onMove = (ev) => {
3304
- const info = resizingRef.current;
3305
- if (!info || !selectedImage)
3576
+ const under = document.elementFromPoint(ev.clientX, ev.clientY);
3577
+ const overCell = getClosestCell(under);
3578
+ const startInfo = selectingRef.current;
3579
+ if (!overCell || !startInfo)
3306
3580
  return;
3307
- const delta = info.startX - ev.clientX;
3308
- const next = Math.max(80, Math.round(info.startWidth + delta));
3309
- selectedImage.style.width = next + "px";
3310
- selectedImage.style.height = "auto";
3311
- scheduleImageOverlay();
3312
- };
3313
- const onUp = () => {
3314
- window.removeEventListener("mousemove", onMove);
3315
- window.removeEventListener("mouseup", onUp);
3316
- resizingRef.current = null;
3317
- handleInput();
3318
- };
3319
- window.addEventListener("mousemove", onMove);
3320
- window.addEventListener("mouseup", onUp);
3321
- }, style: {
3322
- position: "absolute",
3323
- left: -6,
3324
- top: "50%",
3325
- transform: "translateY(-50%)",
3326
- width: 8,
3327
- height: 24,
3328
- background: "var(--srte-accent)",
3329
- borderRadius: 2,
3330
- cursor: "ew-resize",
3331
- pointerEvents: "auto",
3332
- } }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
3333
- e.preventDefault();
3334
- if (!selectedImage)
3335
- return;
3336
- resizingRef.current = {
3337
- side: "right",
3338
- startX: e.clientX,
3339
- startWidth: selectedImage.getBoundingClientRect().width,
3340
- };
3341
- const onMove = (ev) => {
3342
- const info = resizingRef.current;
3343
- if (!info || !selectedImage)
3581
+ const a = getCellPosition(startInfo.start);
3582
+ const b = getCellPosition(overCell);
3583
+ if (!a || !b || a.tbody !== b.tbody)
3344
3584
  return;
3345
- const delta = ev.clientX - info.startX;
3346
- const next = Math.max(80, Math.round(info.startWidth + delta));
3347
- selectedImage.style.width = next + "px";
3348
- selectedImage.style.height = "auto";
3349
- scheduleImageOverlay();
3585
+ const sr = Math.min(a.rIdx, b.rIdx);
3586
+ const sc = Math.min(a.cIdx, b.cIdx);
3587
+ const er = Math.max(a.rIdx, b.rIdx);
3588
+ const ec = Math.max(a.cIdx, b.cIdx);
3589
+ updateSelectionDecor(a.tbody, sr, sc, er, ec);
3350
3590
  };
3351
3591
  const onUp = () => {
3352
3592
  window.removeEventListener("mousemove", onMove);
3353
3593
  window.removeEventListener("mouseup", onUp);
3354
- resizingRef.current = null;
3355
- handleInput();
3594
+ selectingRef.current = null;
3356
3595
  };
3357
3596
  window.addEventListener("mousemove", onMove);
3358
3597
  window.addEventListener("mouseup", onUp);
3359
- }, style: {
3598
+ }, onContextMenu: (e) => {
3599
+ const target = e.target;
3600
+ if (target && target.tagName === "IMG") {
3601
+ e.preventDefault();
3602
+ const vw = window.innerWidth;
3603
+ const vh = window.innerHeight;
3604
+ const menuW = 220;
3605
+ const menuH = 200;
3606
+ const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
3607
+ const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
3608
+ setImageMenu({ x, y, img: target });
3609
+ setTableMenu(null);
3610
+ return;
3611
+ }
3612
+ const cell = getClosestCell(e.target);
3613
+ if (cell) {
3614
+ e.preventDefault();
3615
+ const vw = window.innerWidth;
3616
+ const vh = window.innerHeight;
3617
+ const menuW = 220;
3618
+ const menuH = 300;
3619
+ const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
3620
+ const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
3621
+ setTableMenu({ x, y, cell });
3622
+ }
3623
+ else {
3624
+ setTableMenu(null);
3625
+ setImageMenu(null);
3626
+ }
3627
+ } }), selectedImage && imageOverlay && (_jsxs("div", { style: {
3360
3628
  position: "absolute",
3361
- right: -6,
3362
- top: "50%",
3363
- transform: "translateY(-50%)",
3364
- width: 8,
3365
- height: 24,
3366
- background: "var(--srte-accent)",
3367
- borderRadius: 2,
3368
- cursor: "ew-resize",
3369
- pointerEvents: "auto",
3370
- } })] })), tableMenu && (_jsx("div", { style: {
3629
+ left: imageOverlay.left,
3630
+ top: imageOverlay.top,
3631
+ width: imageOverlay.width,
3632
+ height: imageOverlay.height,
3633
+ pointerEvents: "none",
3634
+ zIndex: 5,
3635
+ }, children: [_jsx("div", { style: {
3636
+ position: "absolute",
3637
+ inset: 0,
3638
+ outline: "2px solid var(--srte-accent)",
3639
+ outlineOffset: -2,
3640
+ } }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
3641
+ e.preventDefault();
3642
+ if (!selectedImage)
3643
+ return;
3644
+ resizingRef.current = {
3645
+ side: "left",
3646
+ startX: e.clientX,
3647
+ startWidth: selectedImage.getBoundingClientRect().width,
3648
+ };
3649
+ const onMove = (ev) => {
3650
+ const info = resizingRef.current;
3651
+ if (!info || !selectedImage)
3652
+ return;
3653
+ const delta = info.startX - ev.clientX;
3654
+ const next = Math.max(80, Math.round(info.startWidth + delta));
3655
+ selectedImage.style.width = next + "px";
3656
+ selectedImage.style.height = "auto";
3657
+ scheduleImageOverlay();
3658
+ };
3659
+ const onUp = () => {
3660
+ window.removeEventListener("mousemove", onMove);
3661
+ window.removeEventListener("mouseup", onUp);
3662
+ resizingRef.current = null;
3663
+ handleInput();
3664
+ };
3665
+ window.addEventListener("mousemove", onMove);
3666
+ window.addEventListener("mouseup", onUp);
3667
+ }, style: {
3668
+ position: "absolute",
3669
+ left: -4,
3670
+ top: "50%",
3671
+ transform: "translateY(-50%)",
3672
+ width: 8,
3673
+ height: 24,
3674
+ background: "var(--srte-accent)",
3675
+ borderRadius: 2,
3676
+ cursor: "ew-resize",
3677
+ pointerEvents: "auto",
3678
+ } }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
3679
+ e.preventDefault();
3680
+ if (!selectedImage)
3681
+ return;
3682
+ resizingRef.current = {
3683
+ side: "right",
3684
+ startX: e.clientX,
3685
+ startWidth: selectedImage.getBoundingClientRect().width,
3686
+ };
3687
+ const onMove = (ev) => {
3688
+ const info = resizingRef.current;
3689
+ if (!info || !selectedImage)
3690
+ return;
3691
+ const delta = ev.clientX - info.startX;
3692
+ const next = Math.max(80, Math.round(info.startWidth + delta));
3693
+ selectedImage.style.width = next + "px";
3694
+ selectedImage.style.height = "auto";
3695
+ scheduleImageOverlay();
3696
+ };
3697
+ const onUp = () => {
3698
+ window.removeEventListener("mousemove", onMove);
3699
+ window.removeEventListener("mouseup", onUp);
3700
+ resizingRef.current = null;
3701
+ handleInput();
3702
+ };
3703
+ window.addEventListener("mousemove", onMove);
3704
+ window.addEventListener("mouseup", onUp);
3705
+ }, style: {
3706
+ position: "absolute",
3707
+ right: -4,
3708
+ top: "50%",
3709
+ transform: "translateY(-50%)",
3710
+ width: 8,
3711
+ height: 24,
3712
+ background: "var(--srte-accent)",
3713
+ borderRadius: 2,
3714
+ cursor: "ew-resize",
3715
+ pointerEvents: "auto",
3716
+ } })] }))] }), tableMenu && (_jsx("div", { style: {
3371
3717
  position: "fixed",
3372
3718
  inset: 0,
3373
3719
  zIndex: 60,
3720
+ background: "transparent",
3374
3721
  }, onClick: () => setTableMenu(null), onContextMenu: (e) => {
3375
3722
  // Prevent native menu while overlay is shown and reposition our menu
3376
3723
  e.preventDefault();
@@ -3402,56 +3749,53 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3402
3749
  maxHeight: 260,
3403
3750
  overflowY: "auto",
3404
3751
  color: "var(--srte-menu-text)",
3405
- }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11, margin: "2px 6px 6px" }, children: "Table" }), _jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsxs("button", { style: {
3406
- display: "flex",
3407
- alignItems: "center",
3408
- gap: 8,
3409
- padding: "6px 8px",
3410
- fontSize: 12,
3411
- }, onClick: () => setShowTableDialog(true), children: [_jsx("span", { children: "\u2795" }), _jsx("span", { children: "Insert table\u2026" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("div", { style: {
3752
+ }, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11, margin: "2px 6px 6px" }, children: "Table" }), _jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsxs("div", { style: {
3412
3753
  display: "flex",
3413
3754
  gap: 8,
3414
3755
  alignItems: "center",
3415
3756
  padding: "4px 6px",
3416
3757
  fontSize: 12,
3417
3758
  }, children: [_jsx("span", { children: "Fill:" }), _jsx("input", { type: "color", defaultValue: "#ffffff", onChange: (e) => {
3418
- applyBgToSelection(e.target.value, tableMenu.cell);
3419
- setTableMenu(null);
3759
+ runTableCellAction(tableMenu.cell, (cell) => applyBgToSelection(e.target.value, cell));
3420
3760
  }, style: {
3421
3761
  width: 28,
3422
3762
  height: 18,
3423
3763
  padding: 0,
3424
3764
  border: "none",
3425
3765
  background: "transparent",
3426
- } })] }), _jsxs("button", { style: {
3766
+ } })] }), _jsxs("button", { title: "Show or hide border for this cell, or for the selected cell range.", style: {
3427
3767
  display: "flex",
3428
3768
  alignItems: "center",
3429
3769
  gap: 8,
3430
3770
  padding: "6px 8px",
3431
3771
  fontSize: 12,
3432
3772
  }, onClick: () => {
3433
- toggleBorderSelection(tableMenu.cell);
3434
- setTableMenu(null);
3435
- }, children: [_jsx("span", { children: "\u25A6" }), _jsx("span", { children: "Toggle border" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { disabled: !canMergeSelection(), style: {
3773
+ runTableCellAction(tableMenu.cell, toggleBorderSelection);
3774
+ }, children: [_jsx("span", { children: "\u25A6" }), _jsx("span", { children: "Show/hide cell border" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { disabled: !canMergeFromCell(tableMenu.cell), style: {
3436
3775
  display: "flex",
3437
3776
  alignItems: "center",
3438
3777
  gap: 8,
3439
3778
  padding: "6px 8px",
3440
3779
  fontSize: 12,
3441
- opacity: canMergeSelection() ? 1 : 0.5,
3442
- cursor: canMergeSelection() ? "pointer" : "default",
3780
+ opacity: canMergeFromCell(tableMenu.cell) ? 1 : 0.45,
3781
+ cursor: canMergeFromCell(tableMenu.cell) ? "pointer" : "not-allowed",
3443
3782
  }, onClick: () => {
3783
+ if (!canMergeFromCell(tableMenu.cell))
3784
+ return;
3444
3785
  mergeSelection();
3445
3786
  setTableMenu(null);
3446
- }, children: [_jsx("span", { children: "\u21C4" }), _jsx("span", { children: "Merge cells" })] }), _jsxs("button", { style: {
3787
+ }, children: [_jsx("span", { children: "\u21C4" }), _jsx("span", { children: "Merge cells" })] }), _jsxs("button", { disabled: !canSplitCell(tableMenu.cell), title: canSplitCell(tableMenu.cell) ? "Split this merged cell back into individual cells." : "Only merged cells can be split.", style: {
3447
3788
  display: "flex",
3448
3789
  alignItems: "center",
3449
3790
  gap: 8,
3450
3791
  padding: "6px 8px",
3451
3792
  fontSize: 12,
3793
+ opacity: canSplitCell(tableMenu.cell) ? 1 : 0.45,
3794
+ cursor: canSplitCell(tableMenu.cell) ? "pointer" : "not-allowed",
3452
3795
  }, onClick: () => {
3453
- splitCell(tableMenu.cell);
3454
- setTableMenu(null);
3796
+ if (!canSplitCell(tableMenu.cell))
3797
+ return;
3798
+ runTableCellAction(tableMenu.cell, splitCell);
3455
3799
  }, children: [_jsx("span", { children: "\u2922" }), _jsx("span", { children: "Split cell" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
3456
3800
  display: "flex",
3457
3801
  alignItems: "center",
@@ -3459,8 +3803,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3459
3803
  padding: "6px 8px",
3460
3804
  fontSize: 12,
3461
3805
  }, onClick: () => {
3462
- addRow(tableMenu.cell, "above");
3463
- setTableMenu(null);
3806
+ runTableCellAction(tableMenu.cell, (cell) => addRow(cell, "above"));
3464
3807
  }, children: [_jsx("span", { children: "\u21A5" }), _jsx("span", { children: "Row above" })] }), _jsxs("button", { style: {
3465
3808
  display: "flex",
3466
3809
  alignItems: "center",
@@ -3468,8 +3811,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3468
3811
  padding: "6px 8px",
3469
3812
  fontSize: 12,
3470
3813
  }, onClick: () => {
3471
- addRow(tableMenu.cell, "below");
3472
- setTableMenu(null);
3814
+ runTableCellAction(tableMenu.cell, (cell) => addRow(cell, "below"));
3473
3815
  }, children: [_jsx("span", { children: "\u21A7" }), _jsx("span", { children: "Row below" })] }), _jsxs("button", { style: {
3474
3816
  display: "flex",
3475
3817
  alignItems: "center",
@@ -3477,8 +3819,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3477
3819
  padding: "6px 8px",
3478
3820
  fontSize: 12,
3479
3821
  }, onClick: () => {
3480
- addCol(tableMenu.cell, "left");
3481
- setTableMenu(null);
3822
+ runTableCellAction(tableMenu.cell, (cell) => addCol(cell, "left"));
3482
3823
  }, children: [_jsx("span", { children: "\u2190" }), _jsx("span", { children: "Column left" })] }), _jsxs("button", { style: {
3483
3824
  display: "flex",
3484
3825
  alignItems: "center",
@@ -3486,8 +3827,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3486
3827
  padding: "6px 8px",
3487
3828
  fontSize: 12,
3488
3829
  }, onClick: () => {
3489
- addCol(tableMenu.cell, "right");
3490
- setTableMenu(null);
3830
+ runTableCellAction(tableMenu.cell, (cell) => addCol(cell, "right"));
3491
3831
  }, children: [_jsx("span", { children: "\u2192" }), _jsx("span", { children: "Column right" })] }), _jsxs("button", { style: {
3492
3832
  display: "flex",
3493
3833
  alignItems: "center",
@@ -3495,8 +3835,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3495
3835
  padding: "6px 8px",
3496
3836
  fontSize: 12,
3497
3837
  }, onClick: () => {
3498
- deleteRow(tableMenu.cell);
3499
- setTableMenu(null);
3838
+ runTableCellAction(tableMenu.cell, deleteRow);
3500
3839
  }, children: [_jsx("span", { children: "\u2716" }), _jsx("span", { children: "Delete row" })] }), _jsxs("button", { style: {
3501
3840
  display: "flex",
3502
3841
  alignItems: "center",
@@ -3504,8 +3843,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3504
3843
  padding: "6px 8px",
3505
3844
  fontSize: 12,
3506
3845
  }, onClick: () => {
3507
- deleteCol(tableMenu.cell);
3508
- setTableMenu(null);
3846
+ runTableCellAction(tableMenu.cell, deleteCol);
3509
3847
  }, children: [_jsx("span", { children: "\u2716" }), _jsx("span", { children: "Delete column" })] }), _jsxs("button", { style: {
3510
3848
  display: "flex",
3511
3849
  alignItems: "center",
@@ -3513,36 +3851,37 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
3513
3851
  padding: "6px 8px",
3514
3852
  fontSize: 12,
3515
3853
  }, onClick: () => {
3516
- toggleHeaderCell(tableMenu.cell);
3517
- setTableMenu(null);
3518
- }, children: [_jsx("span", { children: "H" }), _jsx("span", { children: "Toggle header" })] }), _jsxs("button", { style: {
3854
+ runTableCellAction(tableMenu.cell, toggleHeaderCell);
3855
+ }, children: [_jsx("span", { children: "H" }), _jsx("span", { children: tableMenu.cell.tagName === "TH" ? "Remove cell header" : "Make cell header" })] }), _jsxs("button", { style: {
3519
3856
  display: "flex",
3520
3857
  alignItems: "center",
3521
3858
  gap: 8,
3522
3859
  padding: "6px 8px",
3523
3860
  fontSize: 12,
3524
3861
  }, onClick: () => {
3525
- toggleHeaderRow(tableMenu.cell);
3526
- setTableMenu(null);
3527
- }, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Toggle header row" })] }), _jsxs("button", { style: {
3862
+ runTableCellAction(tableMenu.cell, toggleHeaderRow);
3863
+ }, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Make this row header" })] }), _jsxs("button", { style: {
3528
3864
  display: "flex",
3529
3865
  alignItems: "center",
3530
3866
  gap: 8,
3531
3867
  padding: "6px 8px",
3532
3868
  fontSize: 12,
3533
3869
  }, onClick: () => {
3534
- toggleHeaderColumn(tableMenu.cell);
3535
- setTableMenu(null);
3536
- }, children: [_jsx("span", { children: "H\u2195" }), _jsx("span", { children: "Toggle header column" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
3870
+ runTableCellAction(tableMenu.cell, toggleHeaderColumn);
3871
+ }, children: [_jsx("span", { children: "H\u2195" }), _jsx("span", { children: "Make this column header" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
3537
3872
  display: "flex",
3538
3873
  alignItems: "center",
3539
3874
  gap: 8,
3540
3875
  padding: "6px 8px",
3541
3876
  fontSize: 12,
3542
3877
  }, onClick: () => {
3543
- deleteTable(tableMenu.cell);
3544
- setTableMenu(null);
3545
- }, children: [_jsx("span", { children: "\uD83D\uDDD1" }), _jsx("span", { children: "Delete table" })] })] })] }) })), imageMenu && (_jsx("div", { style: { position: "fixed", inset: 0, zIndex: 60 }, onClick: () => setImageMenu(null), onContextMenu: (e) => {
3878
+ runTableCellAction(tableMenu.cell, deleteTable);
3879
+ }, children: [_jsx("span", { children: "\uD83D\uDDD1" }), _jsx("span", { children: "Delete table" })] })] })] }) })), imageMenu && (_jsx("div", { style: {
3880
+ position: "fixed",
3881
+ inset: 0,
3882
+ zIndex: 60,
3883
+ background: "transparent",
3884
+ }, onClick: () => setImageMenu(null), onContextMenu: (e) => {
3546
3885
  e.preventDefault();
3547
3886
  const vw = window.innerWidth;
3548
3887
  const vh = window.innerHeight;