autumnnote 1.4.2 → 1.6.0

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.
Files changed (46) hide show
  1. package/README.md +102 -21
  2. package/dist/autumnnote.css +324 -2
  3. package/dist/autumnnote.es.js +700 -228
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +691 -235
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +8 -1
  8. package/src/js/Context.js +8 -3
  9. package/src/js/core/detectLang.js +98 -0
  10. package/src/js/core/dom.js +62 -6
  11. package/src/js/core/markdown.js +14 -13
  12. package/src/js/core/range.js +2 -2
  13. package/src/js/editing/History.js +6 -6
  14. package/src/js/editing/Style.js +19 -19
  15. package/src/js/editing/Table.js +4 -6
  16. package/src/js/editing/Typing.js +6 -6
  17. package/src/js/i18n/en.js +2 -0
  18. package/src/js/i18n/vi.js +2 -0
  19. package/src/js/index.js +4 -3
  20. package/src/js/module/BubbleToolbar.js +30 -13
  21. package/src/js/module/Buttons.js +16 -14
  22. package/src/js/module/Clipboard.js +5 -5
  23. package/src/js/module/CodeTooltip.js +42 -16
  24. package/src/js/module/Codeview.js +2 -2
  25. package/src/js/module/ContextMenu.js +12 -12
  26. package/src/js/module/Editor.js +64 -12
  27. package/src/js/module/EmojiDialog.js +19 -13
  28. package/src/js/module/FindReplace.js +85 -59
  29. package/src/js/module/Fullscreen.js +1 -1
  30. package/src/js/module/IconDialog.js +26 -20
  31. package/src/js/module/ImageCropOverlay.js +6 -6
  32. package/src/js/module/ImageDialog.js +18 -12
  33. package/src/js/module/ImageResizer.js +1 -1
  34. package/src/js/module/ImageTooltip.js +8 -4
  35. package/src/js/module/LinkDialog.js +18 -12
  36. package/src/js/module/LinkTooltip.js +5 -2
  37. package/src/js/module/Mention.js +3 -3
  38. package/src/js/module/Statusbar.js +2 -2
  39. package/src/js/module/TableTooltip.js +180 -18
  40. package/src/js/module/Toolbar.js +16 -15
  41. package/src/js/module/VideoDialog.js +8 -2
  42. package/src/js/module/VideoResizer.js +3 -3
  43. package/src/js/module/VideoTooltip.js +11 -6
  44. package/src/js/renderer.js +4 -2
  45. package/src/js/settings.js +53 -36
  46. package/src/styles/autumnnote.scss +332 -4
@@ -375,16 +375,60 @@ function trapFocus(container, onEscape) {
375
375
  if (e.shiftKey) {
376
376
  if (document.activeElement === first) {
377
377
  e.preventDefault();
378
- last.focus();
378
+ /** @type {HTMLElement} */ last.focus();
379
379
  }
380
380
  } else if (document.activeElement === last) {
381
381
  e.preventDefault();
382
- first.focus();
382
+ /** @type {HTMLElement} */ first.focus();
383
383
  }
384
384
  };
385
385
  document.addEventListener("keydown", handler);
386
386
  return () => document.removeEventListener("keydown", handler);
387
387
  }
388
+ /**
389
+ * Makes a dialog box draggable by its handle element.
390
+ * On first drag the box is pinned to its current viewport coordinates via
391
+ * `position:fixed`, freeing it from the parent flex container's centering.
392
+ * The position is clamped to the visible viewport.
393
+ *
394
+ * @param {HTMLElement} handle Element the user grabs (title bar / header)
395
+ * @param {HTMLElement} box Element that actually moves
396
+ * @returns {Function} Cleanup function (removes the mousedown listener)
397
+ */
398
+ function makeDraggable(handle, box) {
399
+ handle.style.cursor = "grab";
400
+ const onMousedown = (e) => {
401
+ if (e.button !== 0) return;
402
+ if (e.target.closest("button, input, select, textarea, a")) return;
403
+ e.preventDefault();
404
+ if (!box.dataset.anDragPinned) {
405
+ const r = box.getBoundingClientRect();
406
+ box.style.position = "fixed";
407
+ box.style.margin = "0";
408
+ box.style.left = `${r.left}px`;
409
+ box.style.top = `${r.top}px`;
410
+ box.dataset.anDragPinned = "1";
411
+ }
412
+ const startX = e.clientX - parseFloat(box.style.left);
413
+ const startY = e.clientY - parseFloat(box.style.top);
414
+ handle.style.cursor = "grabbing";
415
+ const onMove = (ev) => {
416
+ const bw = box.offsetWidth;
417
+ const bh = box.offsetHeight;
418
+ box.style.left = `${Math.max(0, Math.min(ev.clientX - startX, window.innerWidth - bw))}px`;
419
+ box.style.top = `${Math.max(0, Math.min(ev.clientY - startY, window.innerHeight - bh))}px`;
420
+ };
421
+ const onUp = () => {
422
+ handle.style.cursor = "grab";
423
+ document.removeEventListener("mousemove", onMove);
424
+ document.removeEventListener("mouseup", onUp);
425
+ };
426
+ document.addEventListener("mousemove", onMove);
427
+ document.addEventListener("mouseup", onUp);
428
+ };
429
+ handle.addEventListener("mousedown", onMousedown);
430
+ return () => handle.removeEventListener("mousedown", onMousedown);
431
+ }
388
432
  //#endregion
389
433
  //#region src/js/core/range.js
390
434
  /**
@@ -566,7 +610,7 @@ function underline() {
566
610
  if (!sel || !sel.rangeCount) return;
567
611
  let container = sel.getRangeAt(0).commonAncestorContainer;
568
612
  if (container.nodeType === 3) container = container.parentElement;
569
- const uEl = container && container.closest && container.closest("u");
613
+ const uEl = container && container.closest("u");
570
614
  const nativeState = document.queryCommandState("underline");
571
615
  if (uEl && !nativeState) {
572
616
  const parent = uEl.parentNode;
@@ -586,7 +630,7 @@ function strikethrough() {
586
630
  if (!sel || !sel.rangeCount) return;
587
631
  let sc = sel.getRangeAt(0).startContainer;
588
632
  if (sc.nodeType === 3) sc = sc.parentElement;
589
- const sEl = sc && sc.closest && (sc.closest("s") || sc.closest("strike"));
633
+ const sEl = sc && (sc.closest("s") || sc.closest("strike"));
590
634
  const nativeState = document.queryCommandState("strikeThrough");
591
635
  if (sEl && !nativeState) {
592
636
  const parent = sEl.parentNode;
@@ -623,7 +667,7 @@ var fontName = (name) => execCommand("fontName", name);
623
667
  * Sets the font size (in pt or with unit) for the selection.
624
668
  * Uses a span-based approach to set px sizes precisely.
625
669
  * @param {string} size - e.g. '14px'
626
- * @param {HTMLElement} [editable] - scoping element to avoid touching nodes outside this editor
670
+ * @param {HTMLElement|Document} [editable] - scoping element to avoid touching nodes outside this editor
627
671
  */
628
672
  function fontSize(size, editable = document) {
629
673
  const sel = window.getSelection();
@@ -705,7 +749,7 @@ function outdent() {
705
749
  if (sel && sel.rangeCount) {
706
750
  let container = sel.getRangeAt(0).commonAncestorContainer;
707
751
  if (container.nodeType === 3) container = container.parentElement;
708
- const checkLi = container && container.closest && container.closest(".an-checklist li");
752
+ const checkLi = container && container.closest(".an-checklist li");
709
753
  if (checkLi) {
710
754
  _checklistItemToP(checkLi);
711
755
  return;
@@ -827,15 +871,15 @@ function lineHeight(value) {
827
871
  /**
828
872
  * Wraps the selection in an inline <code> element, or unwraps it if the
829
873
  * cursor is already inside a <code> that is not inside a <pre>.
830
- * @param {HTMLElement} [editable]
874
+ * @param {HTMLElement} [_editable]
831
875
  */
832
- function toggleInlineCode(editable) {
876
+ function toggleInlineCode(_editable) {
833
877
  const sel = window.getSelection();
834
878
  if (!sel || !sel.rangeCount) return;
835
879
  const range = sel.getRangeAt(0);
836
880
  let container = range.commonAncestorContainer;
837
881
  if (container.nodeType === 3) container = container.parentElement;
838
- const codeEl = container && container.closest ? container.closest("code") : null;
882
+ const codeEl = container && container.closest("code");
839
883
  if (codeEl && !codeEl.closest("pre")) {
840
884
  const parent = codeEl.parentNode;
841
885
  const prevSibling = codeEl.previousSibling;
@@ -890,7 +934,7 @@ function isInlineCode() {
890
934
  if (!sel || !sel.rangeCount) return false;
891
935
  let sc = sel.getRangeAt(0).startContainer;
892
936
  if (sc.nodeType === 3) sc = sc.parentElement;
893
- const code = sc && sc.closest ? sc.closest("code") : null;
937
+ const code = sc && sc.closest("code");
894
938
  return !!(code && !code.closest("pre"));
895
939
  }
896
940
  /**
@@ -913,11 +957,11 @@ function toggleChecklist() {
913
957
  const range = sel.getRangeAt(0);
914
958
  let container = range.commonAncestorContainer;
915
959
  if (container.nodeType === 3) container = container.parentElement;
916
- const ul = container.closest && container.closest(".an-checklist");
960
+ const ul = container && container.closest(".an-checklist");
917
961
  if (ul) {
918
962
  const selectedLis = Array.from(ul.querySelectorAll("li")).filter((li) => sel.containsNode(li, true));
919
963
  if (selectedLis.length > 0) {
920
- let firstP = null;
964
+ /** @type {HTMLElement|null} */ let firstP = null;
921
965
  selectedLis.forEach((li) => {
922
966
  const p = document.createElement("p");
923
967
  for (const child of li.childNodes) {
@@ -1015,7 +1059,7 @@ function toggleChecklist() {
1015
1059
  if (blocks.length === 0) return;
1016
1060
  const newUl = document.createElement("ul");
1017
1061
  newUl.className = "an-checklist";
1018
- let lastTextNode = null;
1062
+ /** @type {Text|null} */ let lastTextNode = null;
1019
1063
  blocks.forEach((block) => {
1020
1064
  const li = document.createElement("li");
1021
1065
  const cb = document.createElement("input");
@@ -1048,7 +1092,7 @@ function isInChecklist() {
1048
1092
  if (!sel || !sel.rangeCount) return false;
1049
1093
  let container = sel.getRangeAt(0).commonAncestorContainer;
1050
1094
  if (container.nodeType === 3) container = container.parentElement;
1051
- return !!(container && container.closest && container.closest(".an-checklist li"));
1095
+ return !!(container && container.closest(".an-checklist li"));
1052
1096
  }
1053
1097
  //#endregion
1054
1098
  //#region src/js/module/Buttons.js
@@ -1059,12 +1103,14 @@ function isInChecklist() {
1059
1103
  */
1060
1104
  /**
1061
1105
  * @typedef {object} DropdownDef
1062
- * @property {string} name - unique identifier
1063
- * @property {'select'} type - discriminator for Toolbar renderer
1106
+ * @property {string} name - unique identifier
1107
+ * @property {'select'} type - discriminator for Toolbar renderer
1064
1108
  * @property {string} tooltip
1065
- * @property {string[]} [items] - overridden at render time from options
1066
- * @property {Function} action - called with (context, value)
1067
- * @property {Function} [getValue] - called with (context) to get current value
1109
+ * @property {Array<string|{value:string,label:string,disabled?:boolean}>} [items] - overridden at render time from options
1110
+ * @property {Function} action - called with (context, value)
1111
+ * @property {Function} [getValue] - called with (context) to get current value
1112
+ * @property {string} [selectClass] - extra CSS class(es) for the <select>
1113
+ * @property {string} [placeholder] - placeholder text for the empty option
1068
1114
  */
1069
1115
  /**
1070
1116
  * @typedef {object} ButtonDef
@@ -1132,7 +1178,7 @@ var underlineBtn = btn("underline", "underline", "Underline (Ctrl+U)", () => und
1132
1178
  if (!sel || !sel.rangeCount) return false;
1133
1179
  let sc = sel.getRangeAt(0).startContainer;
1134
1180
  if (sc.nodeType === 3) sc = sc.parentElement;
1135
- return !!(sc && sc.closest && sc.closest("u"));
1181
+ return !!(sc && sc.closest("u"));
1136
1182
  });
1137
1183
  var strikeBtn = btn("strikethrough", "strikethrough", "Strikethrough", () => strikethrough(), () => document.queryCommandState("strikeThrough"));
1138
1184
  var superscriptBtn = btn("superscript", "superscript", "Superscript", () => superscript(), () => document.queryCommandState("superscript"));
@@ -1194,9 +1240,9 @@ var fontSizeBtn = {
1194
1240
  const sel = window.getSelection();
1195
1241
  if (sel && sel.rangeCount) {
1196
1242
  let el = sel.getRangeAt(0).startContainer;
1197
- if (el.nodeType === 3) el = el.parentElement;
1243
+ if (el && el.nodeType === 3) el = el.parentElement;
1198
1244
  while (el && el.nodeType === 1 && !el.style.fontSize) el = el.parentElement;
1199
- const size = el && el.style && el.style.fontSize ? el.style.fontSize : "";
1245
+ const size = el && el.style.fontSize ? el.style.fontSize : "";
1200
1246
  if (size) return size;
1201
1247
  }
1202
1248
  const editable = ctx && ctx.layoutInfo && ctx.layoutInfo.editable;
@@ -1321,8 +1367,11 @@ var lineHeightBtn = {
1321
1367
  "TH"
1322
1368
  ]);
1323
1369
  let el = sel.getRangeAt(0).startContainer;
1324
- if (el.nodeType === 3) el = el.parentElement;
1325
- while (el && !BLOCKS.has(el.tagName)) el = el.parentElement;
1370
+ if (el && el.nodeType === 3) el = el.parentElement;
1371
+ while (el && !BLOCKS.has(
1372
+ /** @type {Element} */
1373
+ el.tagName
1374
+ )) el = el.parentElement;
1326
1375
  if (!el) return "";
1327
1376
  return el.style.lineHeight || getComputedStyle(el).lineHeight || "";
1328
1377
  } catch {
@@ -1416,47 +1465,64 @@ var defaultToolbar = [
1416
1465
  */
1417
1466
  /**
1418
1467
  * @typedef {object} AsnOptions
1419
- * @property {string} [placeholder] - Placeholder text when editor is empty
1420
- * @property {number} [height] - Editor height in px (min)
1421
- * @property {number} [minHeight] - Minimum height in px
1422
- * @property {number} [maxHeight] - Maximum height in px (0 = unlimited)
1423
- * @property {boolean} [focus] - Auto-focus on init
1424
- * @property {boolean} [resizable] - Show resize handle
1425
- * @property {Array} [toolbar] - Toolbar button group config
1426
- * @property {boolean} [pasteAsPlainText] - Force plain-text paste
1427
- * @property {boolean} [pasteCleanHTML] - Sanitise HTML on paste
1468
+ * @property {string} [placeholder] - Placeholder text when editor is empty
1469
+ * @property {number} [height] - Editor height in px (min)
1470
+ * @property {number} [minHeight] - Minimum height in px
1471
+ * @property {number} [maxHeight] - Maximum height in px (0 = unlimited)
1472
+ * @property {boolean} [focus] - Auto-focus on init
1473
+ * @property {boolean} [resizable] - Show resize handle
1474
+ * @property {Array} [toolbar] - Toolbar button group config
1475
+ * @property {boolean} [useBootstrap] - Use Bootstrap button classes on toolbar buttons
1476
+ * @property {string} [toolbarButtonClass] - CSS classes for Bootstrap toolbar buttons
1477
+ * @property {boolean} [useFontAwesome] - Use Font Awesome icons (default: true)
1478
+ * @property {string} [fontAwesomeClass] - Font Awesome prefix class, e.g. 'fas' or 'fa-solid'
1479
+ * @property {boolean} [pasteAsPlainText] - Force plain-text paste
1480
+ * @property {boolean} [pasteCleanHTML] - Sanitise HTML on paste
1428
1481
  * @property {boolean} [pasteStripAttributes] - Strip class/style/data-* from pasted HTML (default: false)
1429
- * @property {boolean} [allowImageUpload] - Allow file upload in image dialog
1430
- * @property {number} [maxImageSize] - Max upload size in MB
1431
- * @property {number} [tabSize] - Spaces per tab in non-list context
1432
- * @property {Function} [onChange] - Callback on content change
1433
- * @property {Function} [onFocus] - Callback on focus
1434
- * @property {Function} [onBlur] - Callback on blur
1435
- * @property {Function} [onImageUpload] - Custom upload handler: (files) => void
1436
- * @property {boolean} [stickyToolbar] - Stick the toolbar to the viewport top when scrolling
1437
- * @property {number} [stickyToolbarOffset] - Top offset in px for sticky toolbar (e.g. fixed nav height)
1438
- * @property {string} [theme] - 'light' (default) | 'dark'
1439
- * @property {boolean} [codeHighlight] - Auto-load Prism.js for syntax highlighting of code blocks
1440
- * @property {string} [codeHighlightCDN] - CDN base URL for Prism assets (defaults to cdnjs)
1441
- * @property {boolean} [markdownPaste] - Convert pasted Markdown text to HTML (default: true)
1442
- * @property {boolean} [readOnly] - Start editor in read-only / non-editable mode
1443
- * @property {boolean} [spellcheck] - Enable browser spellcheck in the editable area (default: true)
1444
- * @property {string} [direction] - Text direction: 'ltr' (default) | 'rtl'
1445
- * @property {string} [toolbarOverflow] - Toolbar overflow strategy: 'wrap' (default) | 'scroll'
1446
- * @property {boolean} [autoSave] - Auto-save content to localStorage on change
1447
- * @property {string} [autoSaveKey] - localStorage key used for auto-save (default: 'autumnnote-autosave')
1448
- * @property {number} [maxChars] - Maximum character count (0 = unlimited). Shows warning in statusbar.
1449
- * @property {number} [maxWords] - Maximum word count (0 = unlimited). Shows warning in statusbar.
1450
- * @property {boolean} [tableHeaderRow] - Insert a header row (<thead><th>) when creating tables
1451
- * @property {Function} [onPaste] - Callback fired on every paste: ({ text, html }) => void
1452
- * @property {Function} [onSelectionChange] - Callback fired on cursor/selection change: (context) => void
1453
- * @property {string[]} [colorSwatches] - Custom brand colour swatches prepended to the colour-picker palette
1482
+ * @property {boolean} [allowImageUpload] - Allow file upload in image dialog
1483
+ * @property {number} [maxImageSize] - Max upload size in MB
1484
+ * @property {number} [tabSize] - Spaces per tab in non-list context
1485
+ * @property {number} [historyLimit] - Maximum undo/redo history steps
1486
+ * @property {string} [defaultFontFamily] - Default font family applied to the editable area on init
1487
+ * @property {string} [defaultFontSize] - Default font size applied to the editable area on init (e.g. '14px')
1488
+ * @property {string[]} [fontFamilies] - Font families shown in the font-family toolbar dropdown
1489
+ * @property {Function} [onChange] - Callback on content change
1490
+ * @property {Function} [onFocus] - Callback on focus
1491
+ * @property {Function} [onBlur] - Callback on blur
1492
+ * @property {Function} [onInit] - Callback after the editor has initialised
1493
+ * @property {Function} [onImageUpload] - Custom upload handler: (files) => void
1494
+ * @property {Function} [onImageError] - Callback when an image upload error occurs
1495
+ * @property {boolean} [stickyToolbar] - Stick the toolbar to the viewport top when scrolling
1496
+ * @property {number} [stickyToolbarOffset] - Top offset in px for sticky toolbar (e.g. fixed nav height)
1497
+ * @property {string} [theme] - 'light' (default) | 'dark'
1498
+ * @property {boolean} [codeHighlight] - Auto-load Prism.js for syntax highlighting of code blocks
1499
+ * @property {string} [codeHighlightCDN] - CDN base URL for Prism assets (defaults to cdnjs)
1500
+ * @property {boolean} [markdownPaste] - Convert pasted Markdown text to HTML (default: true)
1501
+ * @property {boolean} [readOnly] - Start editor in read-only / non-editable mode
1502
+ * @property {boolean} [spellcheck] - Enable browser spellcheck in the editable area (default: true)
1503
+ * @property {string} [direction] - Text direction: 'ltr' (default) | 'rtl'
1504
+ * @property {string} [toolbarOverflow] - Toolbar overflow strategy: 'wrap' (default) | 'scroll'
1505
+ * @property {boolean} [autoSave] - Auto-save content to localStorage on change
1506
+ * @property {string} [autoSaveKey] - localStorage key used for auto-save (default: 'autumnnote-autosave')
1507
+ * @property {number} [maxChars] - Maximum character count (0 = unlimited). Shows warning in statusbar.
1508
+ * @property {number} [maxWords] - Maximum word count (0 = unlimited). Shows warning in statusbar.
1509
+ * @property {boolean} [tableHeaderRow] - Insert a header row (<thead><th>) when creating tables
1510
+ * @property {Function} [onPaste] - Callback fired on every paste: ({ text, html }) => void
1511
+ * @property {Function} [onSelectionChange] - Callback fired on cursor/selection change: (context) => void
1512
+ * @property {string[]} [colorSwatches] - Custom brand colour swatches prepended to the colour-picker palette
1454
1513
  * @property {Function} [onDestroy] - Callback fired when the editor is destroyed: (context) => void
1455
1514
  * @property {Function} [onCharLimitReached] - Callback fired when the character limit is hit: (context) => void
1456
1515
  * @property {Function} [onWordLimitReached] - Callback fired when the word limit is hit: (context) => void
1457
- * @property {string} [focusColor] - Custom focus ring colour, e.g. '#f97316'. Overrides the default blue.
1516
+ * @property {string} [focusColor] - Custom focus ring colour, e.g. '#f97316'. Overrides the default blue.
1517
+ * @property {boolean} [autoSaveRestore] - Show a restore banner when a previously auto-saved draft exists
1518
+ * @property {number} [autoSaveRestoreTimeout] - Maximum age in days for a draft to be offered for restore (0 = no expiry)
1519
+ * @property {Function} [onAutoSaveRestore] - Callback fired after the user chooses to restore a draft
1520
+ * @property {boolean} [markdownShortcuts] - Convert markdown syntax typed inline to HTML
1521
+ * @property {boolean} [bubbleToolbar] - Show a mini floating toolbar above text selections
1522
+ * @property {string[]} [bubbleToolbarItems] - Button names for the bubble toolbar
1523
+ * @property {object|null} [mention] - @mention configuration (onSearch, minChars, ...)
1524
+ * @property {string} [lang] - Display language or partial locale object override
1458
1525
  */
1459
- /** @type {AsnOptions} */
1460
1526
  var defaultOptions = {
1461
1527
  placeholder: "",
1462
1528
  height: 200,
@@ -1590,6 +1656,7 @@ var en = {
1590
1656
  chooseHighlightColor: "Choose highlight color",
1591
1657
  customColor: "Custom color",
1592
1658
  insertTableLabel: "Insert Table",
1659
+ /** Map of paragraph-style value → label (only values needing translation) */
1593
1660
  paragraphItems: {
1594
1661
  p: "Normal",
1595
1662
  blockquote: "Quote",
@@ -1632,6 +1699,7 @@ var en = {
1632
1699
  widthPlaceholder: "560",
1633
1700
  insertBtn: "Insert",
1634
1701
  cancelBtn: "Cancel",
1702
+ /** @param {string} type */
1635
1703
  detected: (type) => `Detected: ${type}`,
1636
1704
  unknownFormat: "Unknown format — will try direct video embed",
1637
1705
  invalidUrl: "Invalid URL — please enter a valid video link."
@@ -1794,9 +1862,13 @@ var en = {
1794
1862
  },
1795
1863
  statusbar: {
1796
1864
  resizeHandle: "Resize editor",
1865
+ /** @param {number} n */
1797
1866
  words: (n) => `Words: ${n}`,
1867
+ /** @param {number} n @param {number} max */
1798
1868
  wordsLimit: (n, max) => `Words: ${n}/${max}`,
1869
+ /** @param {number} n */
1799
1870
  chars: (n) => `Chars: ${n}`,
1871
+ /** @param {number} n @param {number} max */
1800
1872
  charsLimit: (n, max) => `Chars: ${n}/${max}`
1801
1873
  },
1802
1874
  tooltips: {
@@ -1849,6 +1921,8 @@ var en = {
1849
1921
  rowHeight: "Row Height",
1850
1922
  tableBorderWidth: "Table Border Width",
1851
1923
  deleteTable: "Delete Table",
1924
+ cellBackground: "Cell Background",
1925
+ noShading: "No Shading",
1852
1926
  columnWidthPx: "Column Width (px)",
1853
1927
  rowHeightPx: "Row Height (px)",
1854
1928
  tableBorderWidthPx: "Table Border Width (px)",
@@ -1869,7 +1943,9 @@ var en = {
1869
1943
  }
1870
1944
  },
1871
1945
  errors: {
1946
+ /** @param {string} type */
1872
1947
  imageFormat: (type) => `Format "${type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`,
1948
+ /** @param {number} maxSize */
1873
1949
  imageSize: (maxSize) => `Image file is too large. Maximum allowed size is ${maxSize} MB.`
1874
1950
  }
1875
1951
  };
@@ -2202,6 +2278,8 @@ var locales = {
2202
2278
  rowHeight: "Chiều cao hàng",
2203
2279
  tableBorderWidth: "Độ rộng viền bảng",
2204
2280
  deleteTable: "Xóa bảng",
2281
+ cellBackground: "Màu Nền Ô",
2282
+ noShading: "Xóa Màu Nền",
2205
2283
  columnWidthPx: "Chiều rộng cột (px)",
2206
2284
  rowHeightPx: "Chiều cao hàng (px)",
2207
2285
  tableBorderWidthPx: "Độ rộng viền bảng (px)",
@@ -4403,7 +4481,10 @@ function renderLayout(targetEl, options) {
4403
4481
  else if (options.minHeight) editable.style.minHeight = `${options.minHeight}px`;
4404
4482
  if (options.maxHeight) editable.style.maxHeight = `${options.maxHeight}px`;
4405
4483
  container.appendChild(editable);
4406
- if (options.theme === "dark") container.classList.add("an-theme-dark");
4484
+ if (options.theme === "dark") {
4485
+ container.classList.add("an-theme-dark");
4486
+ document.body.classList.add("an-theme-dark");
4487
+ }
4407
4488
  if (options.readOnly) {
4408
4489
  container.classList.add("an-disabled");
4409
4490
  editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
@@ -4441,7 +4522,7 @@ var History = class {
4441
4522
  constructor(editable, limit = 100) {
4442
4523
  this.editable = editable;
4443
4524
  this._limit = limit;
4444
- /** @type {Array<{html: string, range: {sc: string, so: number, ec: string, eo: number}|null}>} */
4525
+ /** @type {Array<{html: string, images?: Record<string,string>, sel: {start: number, end: number}|null}>} */
4445
4526
  this.stack = [];
4446
4527
  this.stackOffset = -1;
4447
4528
  this._savePoint();
@@ -4640,8 +4721,7 @@ var History = class {
4640
4721
  * Build an HTML table with the given number of columns and rows, optionally including a header row.
4641
4722
  * @param {number} cols - Number of columns in each row.
4642
4723
  * @param {number} rows - Total number of rows to create (including header when `headerRow` is true).
4643
- * @param {{ headerRow?: boolean }} [opts] - Options object.
4644
- * @param {boolean} [opts.headerRow=false] - When true and `rows > 0`, creates a header row (`<thead>`) plus body rows for the remainder.
4724
+ * @param {{ headerRow?: boolean }} [opts] - Options: `headerRow` creates a `<thead>` when true.
4645
4725
  * @returns {HTMLTableElement} The constructed `<table>` element with a `<tbody>` and optional `<thead>`; each cell contains a `<br>` placeholder.
4646
4726
  */
4647
4727
  function createTable(cols, rows, opts = {}) {
@@ -4675,7 +4755,6 @@ function createTable(cols, rows, opts = {}) {
4675
4755
  * @param {number} cols - Number of columns for the new table.
4676
4756
  * @param {number} rows - Number of rows for the new table.
4677
4757
  * @param {{ headerRow?: boolean }} [opts] - Options for table creation.
4678
- * @param {boolean} [opts.headerRow=false] - If true, include a header row as the first row.
4679
4758
  */
4680
4759
  function insertTable(cols, rows, opts = {}) {
4681
4760
  if (cols <= 0 || rows <= 0) return;
@@ -4698,7 +4777,7 @@ function insertTable(cols, rows, opts = {}) {
4698
4777
  "PRE"
4699
4778
  ]);
4700
4779
  let anchor = range.startContainer;
4701
- if (anchor.nodeType === 3) anchor = anchor.parentElement;
4780
+ if (anchor && anchor.nodeType === 3) anchor = anchor.parentElement;
4702
4781
  while (anchor && !BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentElement) anchor = anchor.parentElement;
4703
4782
  if (anchor && BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentNode) {
4704
4783
  anchor.after(table);
@@ -4816,7 +4895,7 @@ function handleKeydown(event, editable, options = {}) {
4816
4895
  const textNode = r.startContainer;
4817
4896
  if (r.startOffset === 0 && isFAIcon(textNode.previousSibling)) {
4818
4897
  event.preventDefault();
4819
- textNode.previousSibling.remove();
4898
+ /** @type {ChildNode} */ textNode.previousSibling.remove();
4820
4899
  return true;
4821
4900
  }
4822
4901
  if (r.startOffset === 1 && textNode.textContent === "​" && isFAIcon(textNode.previousSibling)) {
@@ -4962,7 +5041,7 @@ function handleKeydown(event, editable, options = {}) {
4962
5041
  }
4963
5042
  return false;
4964
5043
  }
4965
- const videoWrapper = el && el.closest && el.closest(".an-video-wrapper");
5044
+ const videoWrapper = el && el.closest(".an-video-wrapper");
4966
5045
  if (videoWrapper) {
4967
5046
  event.preventDefault();
4968
5047
  const p = document.createElement("p");
@@ -4976,7 +5055,7 @@ function handleKeydown(event, editable, options = {}) {
4976
5055
  sel.addRange(nr);
4977
5056
  return true;
4978
5057
  }
4979
- const checkLi = el && el.closest && el.closest(".an-checklist li");
5058
+ const checkLi = el && el.closest(".an-checklist li");
4980
5059
  if (checkLi) {
4981
5060
  event.preventDefault();
4982
5061
  const ul = checkLi.closest(".an-checklist");
@@ -5076,8 +5155,9 @@ function htmlToMarkdown(html) {
5076
5155
  function _domToMd(node, depth = 0) {
5077
5156
  if (node.nodeType === 3) return node.textContent.replace(/\s+/g, " ");
5078
5157
  if (node.nodeType !== 1) return "";
5079
- const tag = node.nodeName.toLowerCase();
5080
- const inner = () => Array.from(node.childNodes).map((n) => _domToMd(n, depth)).join("");
5158
+ const el = node;
5159
+ const tag = el.nodeName.toLowerCase();
5160
+ const inner = () => Array.from(el.childNodes).map((n) => _domToMd(n, depth)).join("");
5081
5161
  switch (tag) {
5082
5162
  case "p":
5083
5163
  case "div": return `\n\n${inner()}\n\n`;
@@ -5095,34 +5175,34 @@ function _domToMd(node, depth = 0) {
5095
5175
  case "del":
5096
5176
  case "s":
5097
5177
  case "strike": return `~~${inner()}~~`;
5098
- case "sup": return `^${inner()}`;
5099
- case "sub": return `~${inner()}`;
5178
+ case "sup": return `^${inner()}^`;
5179
+ case "sub": return `~${inner()}~`;
5100
5180
  case "code":
5101
- if (node.closest("pre")) return inner();
5181
+ if (el.closest("pre")) return inner();
5102
5182
  return `\`${inner()}\``;
5103
5183
  case "pre": {
5104
- const codeEl = node.querySelector("code");
5184
+ const codeEl = el.querySelector("code");
5105
5185
  const langMatch = (codeEl && codeEl.className || "").match(/language-(\S+)/);
5106
- return `\n\n\`\`\`${langMatch ? langMatch[1] : ""}\n${(codeEl || node).textContent || ""}\n\`\`\`\n\n`;
5186
+ return `\n\n\`\`\`${langMatch ? langMatch[1] : ""}\n${(codeEl || el).textContent || ""}\n\`\`\`\n\n`;
5107
5187
  }
5108
5188
  case "blockquote": return `\n\n${inner().trim().split("\n").map((l) => `> ${l}`).join("\n")}\n\n`;
5109
5189
  case "a": {
5110
- const href = node.getAttribute("href") || "";
5190
+ const href = el.getAttribute("href") || "";
5111
5191
  return `[${inner()}](${href})`;
5112
5192
  }
5113
5193
  case "img": {
5114
- const src = node.getAttribute("src") || "";
5115
- return `![${node.getAttribute("alt") || ""}](${src})`;
5194
+ const src = el.getAttribute("src") || "";
5195
+ return `![${el.getAttribute("alt") || ""}](${src})`;
5116
5196
  }
5117
5197
  case "ul": {
5118
- const items = Array.from(node.querySelectorAll(":scope > li"));
5198
+ const items = Array.from(el.querySelectorAll(":scope > li"));
5119
5199
  if (!items.length) return inner();
5120
5200
  const indent = " ".repeat(depth);
5121
5201
  const lines = items.map((li) => `${indent}- ${_domToMd(li, depth + 1).trim()}`).join("\n");
5122
5202
  return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
5123
5203
  }
5124
5204
  case "ol": {
5125
- const items = Array.from(node.querySelectorAll(":scope > li"));
5205
+ const items = Array.from(el.querySelectorAll(":scope > li"));
5126
5206
  if (!items.length) return inner();
5127
5207
  const indent = " ".repeat(depth);
5128
5208
  const lines = items.map((li, i) => `${indent}${i + 1}. ${_domToMd(li, depth + 1).trim()}`).join("\n");
@@ -5131,7 +5211,7 @@ function _domToMd(node, depth = 0) {
5131
5211
  case "li": return inner();
5132
5212
  case "hr": return "\n\n---\n\n";
5133
5213
  case "table": {
5134
- const rows = Array.from(node.querySelectorAll("tr"));
5214
+ const rows = Array.from(el.querySelectorAll("tr"));
5135
5215
  if (!rows.length) return inner();
5136
5216
  const cellTexts = rows.map((tr) => Array.from(tr.querySelectorAll("th, td")).map((c) => c.textContent.trim().replace(/\|/g, "\\|")));
5137
5217
  const cols = Math.max(...cellTexts.map((r) => r.length));
@@ -5279,6 +5359,47 @@ function _escAttr(v) {
5279
5359
  return String(v).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5280
5360
  }
5281
5361
  //#endregion
5362
+ //#region src/js/core/detectLang.js
5363
+ /**
5364
+ * detectLang.js — Heuristic programming-language detection for code snippets.
5365
+ *
5366
+ * Returns a Prism.js language identifier or null when no language can be
5367
+ * determined with reasonable confidence.
5368
+ *
5369
+ * Detection order (conflicts in parentheses):
5370
+ * TypeScript → Rust → PHP → Java → Kotlin → Swift → Go
5371
+ * → JavaScript → HTML → CSS → JSON → SQL → Python → Ruby
5372
+ * → Bash → C++ → C# → C → XML
5373
+ *
5374
+ * @param {string} code
5375
+ * @returns {string|null}
5376
+ */
5377
+ function detectLang(code) {
5378
+ if (!code || !code.trim()) return null;
5379
+ const s = code.trim();
5380
+ if (/(:\s*(string|number|boolean|void|never|any|unknown)\b|interface\s+\w+\s*\{|type\s+\w+\s*[=<(]|<\w+>\s*[;,)]|readonly\s+\w|enum\s+\w+\s*\{|\?\s*:\s*\w|as\s+\w+\s*[;,)\]])/.test(s)) return "typescript";
5381
+ if (/\bprintln!\s*\(|\bprint!\s*\(|\bfn\s+\w+\s*(<[^>]*>)?\s*\(|\blet\s+mut\s|\bpub\s+fn\s|\buse\s+std::|\bimpl\s+\w+|\bOption<|\bResult<\w+/.test(s)) return "rust";
5382
+ if (/(<\?php\b|<\?=|\becho\s+.*\$\w|\$this->|\$\w+\s*=\s*\w|\bforeach\s*\(\s*\$|Illuminate\\)/.test(s)) return "php";
5383
+ if (/\bpublic\s+(class|static|void|int|String)\s+\w|System\.out\.(print|println)\s*\(|@(Override|Autowired|Component|Service|Controller)\b|import\s+java\.(util|io|lang|net)\.|throws\s+\w+Exception/.test(s)) return "java";
5384
+ if (/\bfun\s+\w+\s*\(|\bdata\s+class\s+\w+|\bcompanion\s+object\b|\bval\s+\w+\s*:\s*\w|\bprintln\s*\(/.test(s)) return "kotlin";
5385
+ if (/\bguard\s+(let|var)\b|\bprotocol\s+\w+\s*\{|\bextension\s+\w+|\bfunc\s+\w+[^(]*\([^)]*\)\s*->\s*\w|\blet\s+\w+\s*:\s*[A-Z]\w*|\bSwiftUI\b/.test(s)) return "swift";
5386
+ if (/\bpackage\s+\w+\b|\bfmt\.(Print|Println|Sprintf|Errorf|Fprintf)\s*\(|:=\s*\w|\bgoroutine\b|\bchan\s+\w|\bgo\s+func\b/.test(s)) return "go";
5387
+ if (/\b(const\s+\w|let\s+\w+\s*=|var\s+\w+\s*=|function\s+\w|\=>\s*[{(]|import\s+.*\bfrom\b\s*['"]|require\s*\(|console\.(log|error|warn|info)|document\.\w|window\.\w|async\s+function|\bPromise\b|React\.|useState\s*\(|\.then\s*\()/.test(s)) return "javascript";
5388
+ if (/^<!DOCTYPE html/i.test(s) || /<(html|head|body|div|section|article|nav|p|a|img|ul|ol|li|table|form|input|button|script|style)\b[^>]*>/i.test(s)) return "html";
5389
+ if (/(^|\n)\s*(\/\/\s+\S|&[:.[\w]|\$\w+\s*:|@(mixin|include|extend|each|if|for|use|forward)\b|#\{)/.test(s) && /[\w#.*&[\]:(),>+~ -]+\s*\{/.test(s)) return "scss";
5390
+ if (/(^|\n)\s*[\w#.*:[\]&, +-]+\s*\{[^}]*[\w-]+\s*:[^{}:;]+[;}\n]/m.test(s) && !/<\w|function\s|def\s|:\s*(string|number)/.test(s)) return "css";
5391
+ if (/^\s*[{[]/.test(s) && /"\w[\w\s-]*"\s*:/.test(s) && !/\bfunction\b|\bdef\b/.test(s)) return "json";
5392
+ if (/(^|\n)\s*(SELECT\s|INSERT\s+INTO|UPDATE\s+\w|DELETE\s+FROM|CREATE\s+(TABLE|DATABASE|INDEX|VIEW)|DROP\s+(TABLE|DATABASE)|ALTER\s+TABLE|WITH\s+\w+\s+AS\s*\()/im.test(s)) return "sql";
5393
+ if (/\bdef\s+\w+\s*\([^)]*\)\s*:|(^|\n)\s*class\s+\w+.*:\s*$|(^|\n)\s*import\s+\w|(^|\n)\s*from\s+\w+\s+import\s+|\bprint\s*\(|if\s+__name__\s*==\s*['"]__main__['"]/m.test(s)) return "python";
5394
+ if (/\bputs\s+\S|\battr_(accessor|reader|writer)\s|\.each\s+do\s*\|\w+\s*\||\bdo\s*\|\w+\s*\|.*\bend\b|\bdef\s+\w+[^:]*\n[\s\S]*?\bend\b/.test(s)) return "ruby";
5395
+ if (/^#!.*\/(ba|z|da|fi|k)?sh\b/m.test(s) || /\b(echo\s+["']|grep\s+|awk\s+|sed\s+['"\\/-]|chmod\s+|sudo\s+|apt(-get)?\s+install|brew\s+install|npm\s+(install|run|start|build)|pip\s+(install|3\s)|docker\s+(run|build|compose)|kubectl\s+|git\s+(clone|add|commit|push|pull|checkout))\b/.test(s)) return "bash";
5396
+ if (/\bcout\s*<<|\bcin\s*>>|using\s+namespace\s+std\b|std::\w|\btemplate\s*<\w|\b#include\s*<(iostream|vector|map|set|algorithm|string|memory)>/.test(s)) return "cpp";
5397
+ if (/\busing\s+System\b|Console\.(Write|WriteLine)\s*\(|\bget;\s*set;|\basync\s+Task[<\s]|IEnumerable<|\bLINQ\b|\.Select\s*\(|\.Where\s*\(/.test(s)) return "csharp";
5398
+ if (/\b#include\s*<(stdio|stdlib|string|math|time|ctype)\.h>|\bprintf\s*\(|\bscanf\s*\(|int\s+main\s*\(\s*(void|int\s+argc)|\bmalloc\s*\(|\bfree\s*\(/.test(s) && !/namespace|cout|cin|std::/.test(s)) return "c";
5399
+ if (/^<\?xml\s/i.test(s) || /xmlns:|<\/[\w:]+>/.test(s)) return "xml";
5400
+ return null;
5401
+ }
5402
+ //#endregion
5282
5403
  //#region src/js/module/Editor.js
5283
5404
  /**
5284
5405
  * Editor.js - Core editing command module
@@ -5333,7 +5454,8 @@ var Editor = class {
5333
5454
  if (!r.collapsed) return;
5334
5455
  const sc = r.startContainer;
5335
5456
  if (sc.nodeType !== Node.ELEMENT_NODE) return;
5336
- const li = sc.matches && sc.matches(".an-checklist li") ? sc : null;
5457
+ const scEl = sc;
5458
+ const li = scEl.matches(".an-checklist li") ? scEl : null;
5337
5459
  if (!li) return;
5338
5460
  const cb = li.querySelector("input[type=\"checkbox\"]");
5339
5461
  if (!cb) return;
@@ -5373,7 +5495,7 @@ var Editor = class {
5373
5495
  return;
5374
5496
  }
5375
5497
  const target = e.target;
5376
- if (target && (target.nodeName === "IFRAME" || target.closest && target.closest(".an-video-wrapper"))) e.preventDefault();
5498
+ if (target && (target.nodeName === "IFRAME" || target.closest(".an-video-wrapper"))) e.preventDefault();
5377
5499
  }), on(editable, "drop", (e) => {
5378
5500
  if (isReadOnly()) e.preventDefault();
5379
5501
  }));
@@ -5387,9 +5509,12 @@ var Editor = class {
5387
5509
  }
5388
5510
  let node = sel.getRangeAt(0).startContainer;
5389
5511
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
5390
- if (node && node.closest) if (node.closest("sup")) _compositionSupSub = "superscript";
5391
- else if (node.closest("sub")) _compositionSupSub = "subscript";
5392
- else _compositionSupSub = null;
5512
+ if (node) {
5513
+ const el = node;
5514
+ if (el.closest("sup")) _compositionSupSub = "superscript";
5515
+ else if (el.closest("sub")) _compositionSupSub = "subscript";
5516
+ else _compositionSupSub = null;
5517
+ }
5393
5518
  };
5394
5519
  const onCompositionEnd = () => {
5395
5520
  const tag = _compositionSupSub;
@@ -5399,7 +5524,8 @@ var Editor = class {
5399
5524
  if (!sel || !sel.rangeCount) return;
5400
5525
  let node = sel.getRangeAt(0).startContainer;
5401
5526
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
5402
- if (!(node && node.closest && (tag === "superscript" ? node.closest("sup") : node.closest("sub")))) document.execCommand(tag);
5527
+ const el = node;
5528
+ if (!(el && (tag === "superscript" ? el.closest("sup") : el.closest("sub")))) document.execCommand(tag);
5403
5529
  };
5404
5530
  this._disposers.push(on(editable, "compositionstart", onCompositionStart), on(editable, "compositionend", onCompositionEnd));
5405
5531
  }
@@ -5488,6 +5614,7 @@ var Editor = class {
5488
5614
  }
5489
5615
  afterCommand() {
5490
5616
  this._cleanOrphanedFigures();
5617
+ this._ensureTrailingParagraph();
5491
5618
  this.context.invoke("toolbar.refresh");
5492
5619
  this.context.invoke("statusbar.update");
5493
5620
  this._scheduleSnapshot();
@@ -5514,6 +5641,31 @@ var Editor = class {
5514
5641
  if (!fig.querySelector("img")) fig.parentNode.removeChild(fig);
5515
5642
  });
5516
5643
  }
5644
+ /**
5645
+ * Ensures the editable always ends with a plain paragraph so the cursor can
5646
+ * be placed after block elements that do not naturally allow it
5647
+ * (pre, blockquote, table, figure, ul, ol, hr).
5648
+ * Without this, clicking below the last such element does nothing.
5649
+ */
5650
+ _ensureTrailingParagraph() {
5651
+ const editable = this.context.layoutInfo.editable;
5652
+ if (!editable) return;
5653
+ const last = editable.lastElementChild;
5654
+ if (!last) return;
5655
+ if (new Set([
5656
+ "PRE",
5657
+ "BLOCKQUOTE",
5658
+ "TABLE",
5659
+ "FIGURE",
5660
+ "UL",
5661
+ "OL",
5662
+ "HR"
5663
+ ]).has(last.nodeName)) {
5664
+ const p = document.createElement("p");
5665
+ p.innerHTML = "<br>";
5666
+ editable.appendChild(p);
5667
+ }
5668
+ }
5517
5669
  focus() {
5518
5670
  this.context.layoutInfo.editable.focus();
5519
5671
  }
@@ -5697,10 +5849,24 @@ var Editor = class {
5697
5849
  this.context.print();
5698
5850
  }
5699
5851
  /**
5700
- * @param {string} tagName - e.g. 'h1', 'p', 'blockquote'
5852
+ * @param {string} tagName - e.g. 'h1', 'p', 'blockquote', 'pre'
5701
5853
  */
5702
5854
  formatBlock(tagName) {
5703
5855
  formatBlock(tagName);
5856
+ if (tagName === "pre") {
5857
+ const sel = window.getSelection();
5858
+ if (sel && sel.rangeCount > 0) {
5859
+ const container = sel.getRangeAt(0).commonAncestorContainer;
5860
+ const pre = container.nodeType === 1 ? container.closest("pre") : container.parentElement?.closest("pre");
5861
+ if (pre && !pre.getAttribute("data-language")) {
5862
+ const lang = detectLang(pre.textContent || "");
5863
+ if (lang) {
5864
+ this.context.invoke("codeTooltip.applyLanguage", pre, lang);
5865
+ return;
5866
+ }
5867
+ }
5868
+ }
5869
+ }
5704
5870
  this.afterCommand();
5705
5871
  }
5706
5872
  /**
@@ -5757,8 +5923,8 @@ var Editor = class {
5757
5923
  if (openInNewTab) {
5758
5924
  const link = this._getClosestAnchor();
5759
5925
  if (link) {
5760
- link.setAttribute("target", "_blank");
5761
- link.setAttribute("rel", "noopener noreferrer");
5926
+ /** @type {Element} */ link.setAttribute("target", "_blank");
5927
+ /** @type {Element} */ link.setAttribute("rel", "noopener noreferrer");
5762
5928
  }
5763
5929
  }
5764
5930
  }
@@ -6040,13 +6206,13 @@ var Toolbar = class {
6040
6206
  else openPopup();
6041
6207
  });
6042
6208
  const d2 = on(grid, "mouseover", (e) => {
6043
- const cell = e.target.closest(".an-table-cell");
6209
+ const cell = e.target?.closest(".an-table-cell");
6044
6210
  if (!cell) return;
6045
6211
  setHighlight(+cell.getAttribute("data-row"), +cell.getAttribute("data-col"));
6046
6212
  });
6047
6213
  const d3 = on(grid, "mouseleave", () => setHighlight(0, 0));
6048
6214
  const d4 = on(grid, "click", (e) => {
6049
- const cell = e.target.closest(".an-table-cell");
6215
+ const cell = e.target?.closest(".an-table-cell");
6050
6216
  if (!cell) return;
6051
6217
  const rows = +cell.getAttribute("data-row");
6052
6218
  const cols = +cell.getAttribute("data-col");
@@ -6209,11 +6375,17 @@ var Toolbar = class {
6209
6375
  e.preventDefault();
6210
6376
  });
6211
6377
  const d3b = on(swatches, "click", (e) => {
6212
- const sw = e.target.closest(".an-color-swatch");
6213
- if (sw) applyColor(sw.dataset.color);
6378
+ const sw = e.target?.closest(".an-color-swatch");
6379
+ if (sw) applyColor(
6380
+ /** @type {HTMLElement} */
6381
+ sw.dataset.color
6382
+ );
6214
6383
  });
6215
6384
  const d4 = on(colorInput, "change", (e) => {
6216
- applyColor(e.target.value);
6385
+ applyColor(
6386
+ /** @type {HTMLInputElement} */
6387
+ e.target.value
6388
+ );
6217
6389
  });
6218
6390
  const d5 = on(document, "click", (e) => {
6219
6391
  if (isOpen && !wrap.contains(e.target) && !popup.contains(e.target)) closePopup();
@@ -6348,15 +6520,17 @@ var Toolbar = class {
6348
6520
  this.el.querySelectorAll("button[data-btn]").forEach((btn) => {
6349
6521
  const def = btnMap.get(btn.getAttribute("data-btn"));
6350
6522
  if (def && typeof def.isActive === "function") btn.classList.toggle("active", !!def.isActive(this.context));
6351
- if (def && typeof def.isDisabled === "function") btn.disabled = !!def.isDisabled(this.context);
6523
+ if (def && typeof def.isDisabled === "function")
6524
+ /** @type {HTMLButtonElement} */ btn.disabled = !!def.isDisabled(this.context);
6352
6525
  });
6353
6526
  this.el.querySelectorAll("select[data-btn]").forEach((select) => {
6354
6527
  const def = btnMap.get(select.getAttribute("data-btn"));
6355
6528
  if (!def || typeof def.getValue !== "function") return;
6356
6529
  let raw = (def.getValue(this.context) || "").replace(/["']/g, "").trim();
6357
6530
  if (!raw) raw = this.options.defaultFontFamily || this.options.fontFamilies && this.options.fontFamilies[0] || "";
6358
- const matched = Array.from(select.options).find((opt) => opt.value && opt.value.toLowerCase() === raw.toLowerCase());
6359
- select.value = matched ? matched.value : "";
6531
+ const sel = select;
6532
+ const matched = Array.from(sel.options).find((opt) => opt.value && opt.value.toLowerCase() === raw.toLowerCase());
6533
+ sel.value = matched ? matched.value : "";
6360
6534
  });
6361
6535
  }
6362
6536
  /**
@@ -7150,8 +7324,13 @@ var LinkDialog = class {
7150
7324
  "aria-label": L.ariaLabel
7151
7325
  });
7152
7326
  const box = createElement("div", { class: "an-dialog-box" });
7327
+ const header = createElement("div", { class: "an-dialog-header" });
7328
+ const iconEl = createElement("span", { class: "an-dialog-icon" });
7329
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`;
7153
7330
  const title = createElement("h3", { class: "an-dialog-title" });
7154
7331
  title.textContent = L.title;
7332
+ header.appendChild(iconEl);
7333
+ header.appendChild(title);
7155
7334
  const urlLabel = createElement("label", { class: "an-label" });
7156
7335
  urlLabel.textContent = L.url;
7157
7336
  const urlInput = createElement("input", {
@@ -7196,8 +7375,9 @@ var LinkDialog = class {
7196
7375
  cancelBtn.textContent = L.cancelBtn;
7197
7376
  btnRow.appendChild(insertBtn);
7198
7377
  btnRow.appendChild(cancelBtn);
7199
- box.append(title, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
7378
+ box.append(header, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
7200
7379
  overlay.appendChild(box);
7380
+ makeDraggable(header, box);
7201
7381
  const d1 = on(insertBtn, "click", () => this._onInsert());
7202
7382
  const d2 = on(cancelBtn, "click", () => this._close());
7203
7383
  const d3 = on(overlay, "click", (e) => {
@@ -7327,8 +7507,13 @@ var ImageDialog = class {
7327
7507
  "aria-label": L.ariaLabel
7328
7508
  });
7329
7509
  const box = createElement("div", { class: "an-dialog-box" });
7510
+ const header = createElement("div", { class: "an-dialog-header" });
7511
+ const iconEl = createElement("span", { class: "an-dialog-icon" });
7512
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`;
7330
7513
  const title = createElement("h3", { class: "an-dialog-title" });
7331
7514
  title.textContent = L.title;
7515
+ header.appendChild(iconEl);
7516
+ header.appendChild(title);
7332
7517
  const urlLabel = createElement("label", { class: "an-label" });
7333
7518
  urlLabel.textContent = L.imageUrl;
7334
7519
  const urlInput = createElement("input", {
@@ -7347,7 +7532,7 @@ var ImageDialog = class {
7347
7532
  autocomplete: "off"
7348
7533
  });
7349
7534
  this._altInput = altInput;
7350
- box.append(title, urlLabel, urlInput, altLabel, altInput);
7535
+ box.append(header, urlLabel, urlInput, altLabel, altInput);
7351
7536
  const alignLabel = createElement("label", { class: "an-label" });
7352
7537
  alignLabel.textContent = L.alignment;
7353
7538
  const alignRow = createElement("div", { class: "an-align-row" });
@@ -7416,6 +7601,7 @@ var ImageDialog = class {
7416
7601
  btnRow.appendChild(cancelBtn);
7417
7602
  box.append(btnRow);
7418
7603
  overlay.appendChild(box);
7604
+ makeDraggable(header, box);
7419
7605
  const d1 = on(insertBtn, "click", () => this._onInsert());
7420
7606
  const d2 = on(cancelBtn, "click", () => this._close());
7421
7607
  const d3 = on(overlay, "click", (e) => {
@@ -7553,8 +7739,13 @@ var VideoDialog = class {
7553
7739
  "aria-label": L.ariaLabel
7554
7740
  });
7555
7741
  const box = createElement("div", { class: "an-dialog-box" });
7742
+ const header = createElement("div", { class: "an-dialog-header" });
7743
+ const iconEl = createElement("span", { class: "an-dialog-icon" });
7744
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>`;
7556
7745
  const title = createElement("h3", { class: "an-dialog-title" });
7557
7746
  title.textContent = L.title;
7747
+ header.appendChild(iconEl);
7748
+ header.appendChild(title);
7558
7749
  const urlLabel = createElement("label", { class: "an-label" });
7559
7750
  urlLabel.textContent = L.videoUrl;
7560
7751
  const urlInput = createElement("input", {
@@ -7590,8 +7781,9 @@ var VideoDialog = class {
7590
7781
  cancelBtn.textContent = L.cancelBtn;
7591
7782
  btnRow.appendChild(insertBtn);
7592
7783
  btnRow.appendChild(cancelBtn);
7593
- box.append(title, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
7784
+ box.append(header, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
7594
7785
  overlay.appendChild(box);
7786
+ makeDraggable(header, box);
7595
7787
  const d0 = on(urlInput, "input", () => {
7596
7788
  const info = this._parseVideoUrl(urlInput.value.trim());
7597
7789
  hintEl.textContent = info ? this.context.locale.videoDialog.detected(info.type) : urlInput.value ? this.context.locale.videoDialog.unknownFormat : "";
@@ -7774,7 +7966,7 @@ var ImageResizer = class {
7774
7966
  };
7775
7967
  this._disposers.push(on(editable, "click", (e) => this._onEditorClick(e)), on(editable, "contextmenu", (e) => {
7776
7968
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
7777
- const img = e.target.closest("img");
7969
+ const img = e.target?.closest("img");
7778
7970
  if (img) this._select(img);
7779
7971
  }), on(document, "click", (e) => this._onDocClick(e)), on(window, "scroll", () => this._updateOverlayPosition(), { passive: true }), on(window, "resize", onWindowResize, { passive: true }), on(editable, "scroll", () => this._updateOverlayPosition(), { passive: true }));
7780
7972
  return this;
@@ -8184,12 +8376,12 @@ var LinkTooltip = class {
8184
8376
  document.body.appendChild(this._el);
8185
8377
  const editable = this.context.layoutInfo.editable;
8186
8378
  this._disposers.push(on(editable, "mouseover", (e) => {
8187
- const anchor = e.target.closest("a[href]");
8379
+ const anchor = e.target?.closest("a[href]");
8188
8380
  if (anchor && editable.contains(anchor)) this._scheduleShow(anchor);
8189
8381
  }), on(editable, "mouseout", (e) => {
8190
8382
  const to = e.relatedTarget;
8191
8383
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
8192
- }));
8384
+ }), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
8193
8385
  return this;
8194
8386
  }
8195
8387
  destroy() {
@@ -8374,14 +8566,15 @@ var ImageTooltip = class {
8374
8566
  const editable = this.context.layoutInfo.editable;
8375
8567
  this._disposers.push(on(editable, "mouseover", (e) => {
8376
8568
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
8377
- const img = e.target.closest("img");
8569
+ const img = e.target?.closest("img");
8378
8570
  if (img && editable.contains(img) && !img.closest("a[href]")) this._scheduleShow(img);
8379
8571
  }, { passive: true }), on(editable, "mouseout", (e) => {
8380
8572
  const to = e.relatedTarget;
8381
8573
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
8382
8574
  }, { passive: true }), on(document, "click", (e) => {
8383
- if (this._activeImg && !this._activeImg.contains(e.target) && !this._el.contains(e.target)) this._hide();
8384
- }));
8575
+ const et = e.target;
8576
+ if (this._activeImg && !this._activeImg.contains(et) && !this._el.contains(et)) this._hide();
8577
+ }), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
8385
8578
  return this;
8386
8579
  }
8387
8580
  destroy() {
@@ -8465,7 +8658,7 @@ var ImageTooltip = class {
8465
8658
  clearTimeout(this._hideTimer);
8466
8659
  this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY$3);
8467
8660
  }
8468
- _show(img) {
8661
+ _show(_img) {
8469
8662
  this._el.style.display = "flex";
8470
8663
  requestAnimationFrame(() => {
8471
8664
  if (this._activeImg) this._positionNear(this._activeImg);
@@ -8657,14 +8850,16 @@ var VideoTooltip = class {
8657
8850
  const editable = this.context.layoutInfo.editable;
8658
8851
  this._disposers.push(on(editable, "mouseover", (e) => {
8659
8852
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
8660
- const wrapper = e.target.closest(".an-video-wrapper");
8853
+ const target = e.target;
8854
+ const wrapper = target && target.closest ? target.closest(".an-video-wrapper") : null;
8661
8855
  if (wrapper && editable.contains(wrapper)) this._scheduleShow(wrapper);
8662
8856
  }, { passive: true }), on(editable, "mouseout", (e) => {
8663
8857
  const to = e.relatedTarget;
8664
8858
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
8665
8859
  }, { passive: true }), on(document, "click", (e) => {
8666
- if (this._activeWrapper && !this._activeWrapper.contains(e.target) && !this._el.contains(e.target)) this._hide();
8667
- }));
8860
+ const target = e.target;
8861
+ if (this._activeWrapper && !this._activeWrapper.contains(target) && !this._el.contains(target)) this._hide();
8862
+ }), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
8668
8863
  return this;
8669
8864
  }
8670
8865
  destroy() {
@@ -8744,7 +8939,7 @@ var VideoTooltip = class {
8744
8939
  if (this._hideTimer) return;
8745
8940
  this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY$2);
8746
8941
  }
8747
- _show(wrapper) {
8942
+ _show(_wrapper) {
8748
8943
  this._el.style.display = "flex";
8749
8944
  requestAnimationFrame(() => {
8750
8945
  if (this._activeWrapper) this._positionNear(this._activeWrapper);
@@ -8968,8 +9163,35 @@ var ICONS$2 = {
8968
9163
  rowHeight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="17" x2="20" y2="17"/><line x1="12" y1="7" x2="12" y2="17"/><path d="M9 10l3-3 3 3"/><path d="M9 14l3 3 3-3"/></svg>`,
8969
9164
  tableBorder: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6" stroke-width="1"/><line x1="3" y1="13" x2="21" y2="13" stroke-width="2"/><line x1="3" y1="20" x2="21" y2="20" stroke-width="3"/></svg>`,
8970
9165
  deleteTable: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="16" y1="16" x2="22" y2="22" stroke="#ef4444"/><line x1="22" y1="16" x2="16" y2="22" stroke="#ef4444"/></svg>`,
8971
- selectCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z" fill="currentColor" opacity="0.15"/><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z"/></svg>`
9166
+ selectCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z" fill="currentColor" opacity="0.15"/><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z"/></svg>`,
9167
+ cellShade: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 11L8.93 3.36a1 1 0 0 0-1.29.08L3.22 7.8a1 1 0 0 0-.07 1.29L11 20"/><path d="m5 14 5-5"/><path d="M22 22a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2c0-1.5 2.5-5 3.5-5s3.5 3.5 3.5 5z"/></svg>`
8972
9168
  };
9169
+ var SHADE_PRESETS = [
9170
+ "#000000",
9171
+ "#434343",
9172
+ "#666666",
9173
+ "#999999",
9174
+ "#b7b7b7",
9175
+ "#cccccc",
9176
+ "#efefef",
9177
+ "#ffffff",
9178
+ "#ff0000",
9179
+ "#ff9900",
9180
+ "#ffff00",
9181
+ "#00ff00",
9182
+ "#00ffff",
9183
+ "#4a86e8",
9184
+ "#9900ff",
9185
+ "#ff00ff",
9186
+ "#f4cccc",
9187
+ "#fce5cd",
9188
+ "#fff2cc",
9189
+ "#d9ead3",
9190
+ "#d0e0e3",
9191
+ "#c9daf8",
9192
+ "#d9d2e9",
9193
+ "#ead1dc"
9194
+ ];
8973
9195
  var TableTooltip = class {
8974
9196
  /** @param {import('../Context.js').Context} context */
8975
9197
  constructor(context) {
@@ -8984,6 +9206,9 @@ var TableTooltip = class {
8984
9206
  this._sizeApply = null;
8985
9207
  this._sizeTitleEl = null;
8986
9208
  this._sizeInputEl = null;
9209
+ this._shadePopover = null;
9210
+ this._shadeTitleEl = null;
9211
+ this._shadeColorStrip = null;
8987
9212
  this._selectMode = false;
8988
9213
  this._selectedCells = [];
8989
9214
  this._selectStart = null;
@@ -8996,6 +9221,8 @@ var TableTooltip = class {
8996
9221
  document.body.appendChild(this._el);
8997
9222
  this._sizePopover = this._buildSizePopover();
8998
9223
  document.body.appendChild(this._sizePopover);
9224
+ this._shadePopover = this._buildCellShadePopover();
9225
+ document.body.appendChild(this._shadePopover);
8999
9226
  const editable = this.context.layoutInfo.editable;
9000
9227
  this._editable = editable;
9001
9228
  const onSelMousedown = (e) => {
@@ -9022,10 +9249,13 @@ var TableTooltip = class {
9022
9249
  this._disposers.push(on(editable, "mousedown", onSelMousedown), on(editable, "mousemove", onSelMousemove), on(document, "mouseup", onSelMouseup));
9023
9250
  this._disposers.push(on(editable, "mouseover", (e) => {
9024
9251
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
9025
- const table = e.target.closest("table");
9252
+ const table = e.target?.closest("table");
9026
9253
  if (table && editable.contains(table)) {
9027
- const cell = e.target.closest("td, th");
9028
- if (cell) this._activeCell = cell;
9254
+ const cell = e.target?.closest("td, th");
9255
+ if (cell) {
9256
+ this._activeCell = cell;
9257
+ this._syncShadeStrip();
9258
+ }
9029
9259
  this._scheduleShow(table);
9030
9260
  }
9031
9261
  }, { passive: true }), on(editable, "mouseout", (e) => {
@@ -9033,9 +9263,10 @@ var TableTooltip = class {
9033
9263
  const to = e.relatedTarget;
9034
9264
  if (!to || !editable.contains(to) && !this._el.contains(to) && !(this._sizePopover && this._sizePopover.contains(to))) this._scheduleHide();
9035
9265
  }, { passive: true }), on(document, "click", (e) => {
9036
- if (this._selectMode && this._activeTable && this._activeTable.contains(e.target)) return;
9037
- if (this._activeTable && !this._activeTable.contains(e.target) && !this._el.contains(e.target) && !(this._sizePopover && this._sizePopover.contains(e.target))) this._hide();
9038
- }));
9266
+ const et = e.target;
9267
+ if (this._selectMode && this._activeTable && this._activeTable.contains(et)) return;
9268
+ if (this._activeTable && !this._activeTable.contains(et) && !this._el.contains(et) && !(this._sizePopover && this._sizePopover.contains(et))) this._hide();
9269
+ }), on(document, "selectionchange", () => this._syncShadeStrip()), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
9039
9270
  this._initResize();
9040
9271
  return this;
9041
9272
  }
@@ -9165,6 +9396,8 @@ var TableTooltip = class {
9165
9396
  this._el = null;
9166
9397
  if (this._sizePopover && this._sizePopover.parentNode) this._sizePopover.parentNode.removeChild(this._sizePopover);
9167
9398
  this._sizePopover = null;
9399
+ if (this._shadePopover && this._shadePopover.parentNode) this._shadePopover.parentNode.removeChild(this._shadePopover);
9400
+ this._shadePopover = null;
9168
9401
  }
9169
9402
  _buildTooltip() {
9170
9403
  const L = this.context.locale.tooltips.table;
@@ -9192,6 +9425,24 @@ var TableTooltip = class {
9192
9425
  el.appendChild(this._makeBtn(ICONS$2.mergeCells, L.mergeCells, () => this._mergeCells()));
9193
9426
  el.appendChild(this._makeBtn(ICONS$2.unmergeCells, L.unmergeCells, () => this._unmergeCells()));
9194
9427
  el.appendChild(this._sep());
9428
+ const shadeBtn = createElement("button", {
9429
+ type: "button",
9430
+ class: "an-link-tooltip-btn an-link-tooltip-btn--shade",
9431
+ title: L.cellBackground
9432
+ });
9433
+ const shadeSvgWrap = createElement("span", { class: "an-bubble-btn-svg" });
9434
+ shadeSvgWrap.innerHTML = ICONS$2.cellShade;
9435
+ const shadeStrip = createElement("span", { class: "an-link-tooltip-color-strip" });
9436
+ shadeBtn.appendChild(shadeSvgWrap);
9437
+ shadeBtn.appendChild(shadeStrip);
9438
+ this._shadeColorStrip = shadeStrip;
9439
+ this._disposers.push(on(shadeBtn, "click", (e) => {
9440
+ e.preventDefault();
9441
+ e.stopPropagation();
9442
+ this._openCellShadePopover();
9443
+ }));
9444
+ el.appendChild(shadeBtn);
9445
+ el.appendChild(this._sep());
9195
9446
  el.appendChild(this._makeBtn(ICONS$2.colWidth, L.columnWidth, () => this._openSizePopover("col")));
9196
9447
  el.appendChild(this._makeBtn(ICONS$2.rowHeight, L.rowHeight, () => this._openSizePopover("row")));
9197
9448
  el.appendChild(this._makeBtn(ICONS$2.tableBorder, L.tableBorderWidth, () => this._openSizePopover("border")));
@@ -9200,6 +9451,7 @@ var TableTooltip = class {
9200
9451
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => {
9201
9452
  if (this._selectMode) return;
9202
9453
  if (this._sizePopover && this._sizePopover.style.display !== "none") return;
9454
+ if (this._shadePopover && this._shadePopover.style.display !== "none") return;
9203
9455
  this._scheduleHide();
9204
9456
  }));
9205
9457
  return el;
@@ -9246,10 +9498,16 @@ var TableTooltip = class {
9246
9498
  _show() {
9247
9499
  if (!this._activeTable) return;
9248
9500
  this._el.style.display = "flex";
9501
+ this._syncShadeStrip();
9249
9502
  requestAnimationFrame(() => {
9250
9503
  if (this._activeTable) this._positionNear(this._activeTable);
9251
9504
  });
9252
9505
  }
9506
+ _syncShadeStrip() {
9507
+ if (!this._shadeColorStrip || !this._el || this._el.style.display === "none") return;
9508
+ const cell = this._getCell();
9509
+ this._shadeColorStrip.style.background = cell && cell.style.backgroundColor || "transparent";
9510
+ }
9253
9511
  _hide() {
9254
9512
  this._el.style.display = "none";
9255
9513
  this._activeTable = null;
@@ -9288,7 +9546,7 @@ var TableTooltip = class {
9288
9546
  if (sel && sel.rangeCount) {
9289
9547
  let container = sel.getRangeAt(0).commonAncestorContainer;
9290
9548
  if (container.nodeType === 3) container = container.parentElement;
9291
- const cellFromSel = container && container.closest && container.closest("td, th");
9549
+ const cellFromSel = container && container.closest("td, th");
9292
9550
  if (cellFromSel && this._activeTable && this._activeTable.contains(cellFromSel)) return cellFromSel;
9293
9551
  }
9294
9552
  return this._activeCell || this._activeTable && this._activeTable.querySelector("td, th");
@@ -9615,14 +9873,16 @@ var TableTooltip = class {
9615
9873
  });
9616
9874
  const d2 = on(cancelBtn, "click", () => this._hideSizePopover());
9617
9875
  const d3 = on(inputEl, "keydown", (e) => {
9618
- if (e.key === "Enter") {
9876
+ const ke = e;
9877
+ if (ke.key === "Enter") {
9619
9878
  e.preventDefault();
9620
9879
  applyBtn.click();
9621
9880
  }
9622
- if (e.key === "Escape") this._hideSizePopover();
9881
+ if (ke.key === "Escape") this._hideSizePopover();
9623
9882
  });
9624
9883
  const d4 = on(document, "click", (e) => {
9625
- if (this._sizePopover && this._sizePopover.style.display !== "none" && !this._sizePopover.contains(e.target) && !this._el.contains(e.target)) this._hideSizePopover();
9884
+ const et = e.target;
9885
+ if (this._sizePopover && this._sizePopover.style.display !== "none" && !this._sizePopover.contains(et) && !this._el.contains(et)) this._hideSizePopover();
9626
9886
  });
9627
9887
  const d5 = on(popover, "mouseenter", () => this._clearTimers());
9628
9888
  const d6 = on(popover, "mouseleave", () => this._scheduleHide());
@@ -9640,7 +9900,7 @@ var TableTooltip = class {
9640
9900
  this._sizeTitleEl.textContent = this.context.locale.tooltips.table.tableBorderWidthPx;
9641
9901
  this._sizeInputEl.min = "0";
9642
9902
  this._sizeInputEl.max = "10";
9643
- this._sizeInputEl.value = currentPx;
9903
+ this._sizeInputEl.value = String(currentPx);
9644
9904
  this._sizeApply = (val) => {
9645
9905
  const cells = Array.from(table.querySelectorAll("td, th"));
9646
9906
  if (val === 0) cells.forEach((c) => {
@@ -9709,6 +9969,85 @@ var TableTooltip = class {
9709
9969
  if (this._sizePopover) this._sizePopover.style.display = "none";
9710
9970
  this._sizeApply = null;
9711
9971
  }
9972
+ _buildCellShadePopover() {
9973
+ const pop = createElement("div", { class: "an-cell-shade-popover" });
9974
+ pop.style.display = "none";
9975
+ const title = createElement("div", { class: "an-size-popover-title" });
9976
+ pop.appendChild(title);
9977
+ this._shadeTitleEl = title;
9978
+ const palette = createElement("div", { class: "an-context-color-palette" });
9979
+ SHADE_PRESETS.forEach((color) => {
9980
+ const sw = createElement("div", {
9981
+ class: "an-context-color-swatch",
9982
+ title: color
9983
+ });
9984
+ sw.style.background = color;
9985
+ this._disposers.push(on(sw, "click", (e) => {
9986
+ e.stopPropagation();
9987
+ this._applyCellShade(color);
9988
+ }));
9989
+ palette.appendChild(sw);
9990
+ });
9991
+ pop.appendChild(palette);
9992
+ const noShadeRow = createElement("div", { class: "an-context-color-custom" });
9993
+ const noShadeBtn = createElement("button", {
9994
+ type: "button",
9995
+ class: "an-shade-no-color"
9996
+ });
9997
+ this._disposers.push(on(noShadeBtn, "click", () => this._applyCellShade("")));
9998
+ noShadeRow.appendChild(noShadeBtn);
9999
+ pop.appendChild(noShadeRow);
10000
+ this._shadeNoBtn = noShadeBtn;
10001
+ const customRow = createElement("div", { class: "an-context-color-custom" });
10002
+ const colorInput = createElement("input", {
10003
+ type: "color",
10004
+ class: "an-shade-color-input",
10005
+ value: "#ffffff"
10006
+ });
10007
+ const customLabel = createElement("span");
10008
+ customLabel.textContent = "Custom…";
10009
+ this._disposers.push(on(colorInput, "change", () => this._applyCellShade(colorInput.value)));
10010
+ customRow.appendChild(colorInput);
10011
+ customRow.appendChild(customLabel);
10012
+ pop.appendChild(customRow);
10013
+ this._disposers.push(on(pop, "mousedown", (e) => e.preventDefault()));
10014
+ this._disposers.push(on(pop, "mouseenter", () => this._clearTimers()), on(pop, "mouseleave", () => this._scheduleHide()));
10015
+ this._disposers.push(on(document, "click", (e) => {
10016
+ const et = e.target;
10017
+ if (this._shadePopover && this._shadePopover.style.display !== "none" && !this._shadePopover.contains(et) && !(this._el && this._el.contains(et))) this._hideCellShadePopover();
10018
+ }));
10019
+ return pop;
10020
+ }
10021
+ _openCellShadePopover() {
10022
+ if (!this._shadePopover) return;
10023
+ const L = this.context.locale.tooltips.table;
10024
+ if (this._shadeTitleEl) this._shadeTitleEl.textContent = L.cellBackground;
10025
+ if (this._shadeNoBtn) this._shadeNoBtn.textContent = L.noShading;
10026
+ this._shadePopover.style.display = "block";
10027
+ requestAnimationFrame(() => {
10028
+ if (!this._shadePopover || !this._el) return;
10029
+ const pw = this._shadePopover.offsetWidth || 170;
10030
+ const ph = this._shadePopover.offsetHeight || 120;
10031
+ const tipRect = this._el.getBoundingClientRect();
10032
+ let left = tipRect.left;
10033
+ let top = tipRect.bottom + 6;
10034
+ if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
10035
+ if (top + ph > window.innerHeight - 8) top = tipRect.top - ph - 6;
10036
+ this._shadePopover.style.left = `${Math.max(8, left)}px`;
10037
+ this._shadePopover.style.top = `${Math.max(8, top)}px`;
10038
+ });
10039
+ }
10040
+ _hideCellShadePopover() {
10041
+ if (this._shadePopover) this._shadePopover.style.display = "none";
10042
+ }
10043
+ _applyCellShade(color) {
10044
+ (this._selectMode ? this._selectedCells : [this._getCell()]).forEach((cell) => {
10045
+ if (cell) cell.style.backgroundColor = color;
10046
+ });
10047
+ if (this._shadeColorStrip) this._shadeColorStrip.style.background = color || "transparent";
10048
+ this._hideCellShadePopover();
10049
+ this.context.invoke("editor.afterCommand");
10050
+ }
9712
10051
  };
9713
10052
  //#endregion
9714
10053
  //#region src/js/module/CodeTooltip.js
@@ -9741,13 +10080,14 @@ var CodeTooltip = class {
9741
10080
  const editable = this.context.layoutInfo.editable;
9742
10081
  this._disposers.push(on(editable, "mouseover", (e) => {
9743
10082
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
9744
- const pre = e.target.closest("pre");
10083
+ const pre = e.target?.closest("pre");
9745
10084
  if (pre && editable.contains(pre)) this._scheduleShow(pre);
9746
10085
  }), on(editable, "mouseout", (e) => {
9747
10086
  const to = e.relatedTarget;
9748
10087
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
9749
10088
  }), on(document, "click", (e) => {
9750
- if (this._activePre && !this._activePre.contains(e.target) && !this._el.contains(e.target)) this._hide();
10089
+ const et = e.target;
10090
+ if (this._activePre && !this._activePre.contains(et) && !this._el.contains(et)) this._hide();
9751
10091
  }));
9752
10092
  return this;
9753
10093
  }
@@ -9782,6 +10122,7 @@ var CodeTooltip = class {
9782
10122
  ["python", "Python"],
9783
10123
  ["html", "HTML"],
9784
10124
  ["css", "CSS"],
10125
+ ["scss", "SCSS"],
9785
10126
  ["json", "JSON"],
9786
10127
  ["xml", "XML"],
9787
10128
  ["bash", "Bash / Shell"],
@@ -9937,10 +10278,27 @@ var CodeTooltip = class {
9937
10278
  this.context.invoke("editor.afterCommand");
9938
10279
  this._positionNear(pre);
9939
10280
  }
10281
+ /**
10282
+ * Applies a language to a given <pre> element: sets classes, data-language,
10283
+ * and triggers Prism highlighting. Called by the auto-detect flow.
10284
+ * @param {HTMLElement} pre
10285
+ * @param {string} lang - Prism language identifier, e.g. 'javascript'
10286
+ */
10287
+ applyLanguage(pre, lang) {
10288
+ if (!pre || !lang) return;
10289
+ const savedPre = this._activePre;
10290
+ this._langSelect && this._langSelect.value;
10291
+ this._activePre = pre;
10292
+ if (this._langSelect) this._langSelect.value = lang;
10293
+ this._onLangChange();
10294
+ if (this._langSelect) this._langSelect.value = lang;
10295
+ this._activePre = savedPre || pre;
10296
+ }
9940
10297
  _onLangChange() {
9941
10298
  const pre = this._activePre;
9942
10299
  if (!pre) return;
9943
10300
  const lang = this._langSelect.value;
10301
+ const _w = window;
9944
10302
  let codeEl = pre.querySelector("code");
9945
10303
  if (!codeEl) {
9946
10304
  codeEl = document.createElement("code");
@@ -9954,12 +10312,12 @@ var CodeTooltip = class {
9954
10312
  else pre.removeAttribute("data-language");
9955
10313
  const applyPrism = () => {
9956
10314
  codeEl.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
9957
- window.Prism.highlightElement(codeEl);
10315
+ _w.Prism.highlightElement(codeEl);
9958
10316
  this.context.invoke("editor.afterCommand");
9959
10317
  };
9960
10318
  if (lang) {
9961
- if (typeof window.Prism !== "undefined") {
9962
- if (window.Prism.languages[lang]) {
10319
+ if (typeof _w.Prism !== "undefined") {
10320
+ if (_w.Prism.languages[lang]) {
9963
10321
  applyPrism();
9964
10322
  return;
9965
10323
  }
@@ -9967,7 +10325,7 @@ var CodeTooltip = class {
9967
10325
  return;
9968
10326
  } else if (this._prismScript) {
9969
10327
  this._prismScript.addEventListener("load", () => {
9970
- if (window.Prism.languages[lang]) applyPrism();
10328
+ if (_w.Prism.languages[lang]) applyPrism();
9971
10329
  else this._loadPrismComponent(lang, applyPrism);
9972
10330
  }, { once: true });
9973
10331
  return;
@@ -9980,7 +10338,8 @@ var CodeTooltip = class {
9980
10338
  * Called once at initialize time. Fire-and-forget; errors are silent.
9981
10339
  */
9982
10340
  _ensurePrism() {
9983
- if (!this.context.options.codeHighlight || window.Prism) return;
10341
+ const _w = window;
10342
+ if (!this.context.options.codeHighlight || _w.Prism) return;
9984
10343
  const cdn = this.context.options.codeHighlightCDN;
9985
10344
  const themeHref = `${cdn}/themes/prism-tomorrow.min.css`;
9986
10345
  const scriptSrc = `${cdn}/prism.min.js`;
@@ -9992,7 +10351,7 @@ var CodeTooltip = class {
9992
10351
  }
9993
10352
  const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
9994
10353
  if (existingScript) {
9995
- this._prismScript = window.Prism ? null : existingScript;
10354
+ this._prismScript = _w.Prism ? null : existingScript;
9996
10355
  return;
9997
10356
  }
9998
10357
  const script = document.createElement("script");
@@ -10012,10 +10371,11 @@ var CodeTooltip = class {
10012
10371
  * @param {Function} cb – called once the grammar is ready
10013
10372
  */
10014
10373
  _loadPrismComponent(lang, cb) {
10374
+ const _w = window;
10015
10375
  const src = `${this.context.options.codeHighlightCDN}/components/prism-${lang}.min.js`;
10016
10376
  if (document.querySelector(`script[src="${src}"]`)) {
10017
10377
  const poll = setInterval(() => {
10018
- if (window.Prism && window.Prism.languages[lang]) {
10378
+ if (_w.Prism && _w.Prism.languages[lang]) {
10019
10379
  clearInterval(poll);
10020
10380
  cb();
10021
10381
  }
@@ -12428,15 +12788,19 @@ var EmojiDialog = class {
12428
12788
  });
12429
12789
  const box = createElement("div", { class: "an-dialog-box an-emoji-box" });
12430
12790
  const titleRow = createElement("div", { class: "an-icon-title-row" });
12791
+ const titleGroup = createElement("div", { class: "an-dialog-title-group" });
12792
+ const iconEl = createElement("span", { class: "an-dialog-icon an-dialog-icon--sm" });
12793
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 13s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>`;
12431
12794
  const title = createElement("h3", { class: "an-dialog-title" });
12432
12795
  title.textContent = L.title;
12796
+ titleGroup.append(iconEl, title);
12433
12797
  const closeBtn = createElement("button", {
12434
12798
  type: "button",
12435
12799
  class: "an-icon-close",
12436
12800
  "aria-label": L.close
12437
12801
  });
12438
12802
  closeBtn.innerHTML = "&times;";
12439
- titleRow.append(title, closeBtn);
12803
+ titleRow.append(titleGroup, closeBtn);
12440
12804
  const searchInput = createElement("input", {
12441
12805
  type: "search",
12442
12806
  class: "an-input an-icon-search",
@@ -12485,6 +12849,7 @@ var EmojiDialog = class {
12485
12849
  btnRow.appendChild(cancelBtn);
12486
12850
  box.append(titleRow, searchInput, catBar, grid, btnRow);
12487
12851
  overlay.appendChild(box);
12852
+ makeDraggable(titleRow, box);
12488
12853
  const d1 = on(closeBtn, "click", () => this._close());
12489
12854
  const d2 = on(cancelBtn, "click", () => this._close());
12490
12855
  const d3 = on(overlay, "click", (e) => {
@@ -12492,7 +12857,7 @@ var EmojiDialog = class {
12492
12857
  });
12493
12858
  const d4 = on(searchInput, "input", () => this._filterEmojis(searchInput.value, this._activeCat));
12494
12859
  const d5 = on(catBar, "click", (e) => {
12495
- const tab = e.target.closest("[data-cat]");
12860
+ const tab = e.target?.closest("[data-cat]");
12496
12861
  if (tab) {
12497
12862
  this._activeCat = tab.dataset.cat;
12498
12863
  this._updateCatTabs();
@@ -12500,7 +12865,7 @@ var EmojiDialog = class {
12500
12865
  }
12501
12866
  });
12502
12867
  const d6 = on(grid, "click", (e) => {
12503
- const cell = e.target.closest(".an-emoji-cell");
12868
+ const cell = e.target?.closest(".an-emoji-cell");
12504
12869
  if (cell) this._onEmojiClick(cell.dataset.char);
12505
12870
  });
12506
12871
  this._disposers.push(d1, d2, d3, d4, d5, d6);
@@ -12508,17 +12873,22 @@ var EmojiDialog = class {
12508
12873
  }
12509
12874
  _updateCatTabs() {
12510
12875
  this._catBar.querySelectorAll(".an-icon-cat").forEach((tab) => {
12511
- tab.classList.toggle("active", tab.dataset.cat === this._activeCat);
12876
+ tab.classList.toggle(
12877
+ "active",
12878
+ /** @type {HTMLElement} */
12879
+ tab.dataset.cat === this._activeCat
12880
+ );
12512
12881
  });
12513
12882
  }
12514
12883
  _filterEmojis(query, cat) {
12515
12884
  const q = (query || "").trim().toLowerCase();
12516
12885
  let count = 0;
12517
12886
  this._grid.querySelectorAll(".an-emoji-cell").forEach((cell) => {
12518
- const matchCat = !cat || cat === "all" || cell.dataset.cat === cat;
12519
- const matchQuery = !q || cell.dataset.keywords.includes(q) || cell.dataset.char === q;
12887
+ const hCell = cell;
12888
+ const matchCat = !cat || cat === "all" || hCell.dataset.cat === cat;
12889
+ const matchQuery = !q || hCell.dataset.keywords.includes(q) || hCell.dataset.char === q;
12520
12890
  const visible = matchCat && matchQuery;
12521
- cell.style.display = visible ? "" : "none";
12891
+ hCell.style.display = visible ? "" : "none";
12522
12892
  if (visible) count++;
12523
12893
  });
12524
12894
  let empty = this._grid.querySelector(".an-icon-empty");
@@ -12527,7 +12897,7 @@ var EmojiDialog = class {
12527
12897
  empty.textContent = "No emojis found";
12528
12898
  this._grid.appendChild(empty);
12529
12899
  }
12530
- empty.style.display = count > 0 ? "none" : "";
12900
+ /** @type {HTMLElement} */ empty.style.display = count > 0 ? "none" : "";
12531
12901
  }
12532
12902
  _onEmojiClick(char) {
12533
12903
  const savedRange = this._savedRange;
@@ -12541,7 +12911,7 @@ var EmojiDialog = class {
12541
12911
  range.collapse(false);
12542
12912
  }
12543
12913
  const _sc = range.startContainer;
12544
- const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
12914
+ const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest("td, th");
12545
12915
  range.deleteContents();
12546
12916
  if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
12547
12917
  range.setStart(_tdAnchor, 0);
@@ -12901,15 +13271,19 @@ var IconDialog = class {
12901
13271
  });
12902
13272
  const box = createElement("div", { class: "an-dialog-box an-icon-box" });
12903
13273
  const titleRow = createElement("div", { class: "an-icon-title-row" });
13274
+ const titleGroup = createElement("div", { class: "an-dialog-title-group" });
13275
+ const iconEl = createElement("span", { class: "an-dialog-icon an-dialog-icon--sm" });
13276
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>`;
12904
13277
  const title = createElement("h3", { class: "an-dialog-title" });
12905
13278
  title.textContent = L.title;
13279
+ titleGroup.append(iconEl, title);
12906
13280
  const closeBtn = createElement("button", {
12907
13281
  type: "button",
12908
13282
  class: "an-icon-close",
12909
13283
  "aria-label": L.close
12910
13284
  });
12911
13285
  closeBtn.innerHTML = "&times;";
12912
- titleRow.append(title, closeBtn);
13286
+ titleRow.append(titleGroup, closeBtn);
12913
13287
  const searchInput = createElement("input", {
12914
13288
  type: "search",
12915
13289
  class: "an-input an-icon-search",
@@ -13024,6 +13398,7 @@ var IconDialog = class {
13024
13398
  this._insertBtn = insertBtn;
13025
13399
  box.append(titleRow, searchInput, catBar, grid, optRow, preview, btnRow);
13026
13400
  overlay.appendChild(box);
13401
+ makeDraggable(titleRow, box);
13027
13402
  const d1 = on(closeBtn, "click", () => this._close());
13028
13403
  const d2 = on(cancelBtn, "click", () => this._close());
13029
13404
  const d3 = on(insertBtn, "click", () => this._onInsert());
@@ -13032,7 +13407,7 @@ var IconDialog = class {
13032
13407
  });
13033
13408
  const d5 = on(searchInput, "input", () => this._filterIcons(searchInput.value, this._activeCat));
13034
13409
  const d6 = on(catBar, "click", (e) => {
13035
- const tab = e.target.closest("[data-cat]");
13410
+ const tab = e.target?.closest("[data-cat]");
13036
13411
  if (tab) {
13037
13412
  this._activeCat = tab.dataset.cat;
13038
13413
  this._updateCatTabs();
@@ -13040,7 +13415,7 @@ var IconDialog = class {
13040
13415
  }
13041
13416
  });
13042
13417
  const d7 = on(grid, "click", (e) => {
13043
- const cell = e.target.closest(".an-icon-cell");
13418
+ const cell = e.target?.closest(".an-icon-cell");
13044
13419
  if (cell) this._selectIcon(cell.dataset.name);
13045
13420
  });
13046
13421
  const d8 = on(styleSelect, "change", () => this._updatePreview(this._selectedIcon));
@@ -13052,19 +13427,24 @@ var IconDialog = class {
13052
13427
  }
13053
13428
  _updateCatTabs() {
13054
13429
  this._catBar.querySelectorAll(".an-icon-cat").forEach((tab) => {
13055
- tab.classList.toggle("active", tab.dataset.cat === this._activeCat);
13430
+ tab.classList.toggle(
13431
+ "active",
13432
+ /** @type {HTMLElement} */
13433
+ tab.dataset.cat === this._activeCat
13434
+ );
13056
13435
  });
13057
13436
  }
13058
13437
  _filterIcons(query, cat) {
13059
13438
  const q = (query || "").trim().toLowerCase();
13060
13439
  let visibleCount = 0;
13061
13440
  this._grid.querySelectorAll(".an-icon-cell").forEach((cell) => {
13062
- const name = cell.dataset.name;
13063
- const cellCat = cell.dataset.cat;
13441
+ const hCell = cell;
13442
+ const name = hCell.dataset.name;
13443
+ const cellCat = hCell.dataset.cat;
13064
13444
  const matchesCat = !cat || cat === "all" || cellCat === cat;
13065
13445
  const matchesQuery = !q || name.includes(q);
13066
13446
  const visible = matchesCat && matchesQuery;
13067
- cell.style.display = visible ? "" : "none";
13447
+ hCell.style.display = visible ? "" : "none";
13068
13448
  if (visible) visibleCount++;
13069
13449
  });
13070
13450
  let empty = this._grid.querySelector(".an-icon-empty");
@@ -13073,12 +13453,16 @@ var IconDialog = class {
13073
13453
  empty.textContent = "No icons found";
13074
13454
  this._grid.appendChild(empty);
13075
13455
  }
13076
- empty.style.display = visibleCount > 0 ? "none" : "";
13456
+ /** @type {HTMLElement} */ empty.style.display = visibleCount > 0 ? "none" : "";
13077
13457
  }
13078
13458
  _selectIcon(name) {
13079
13459
  this._selectedIcon = name;
13080
13460
  this._grid.querySelectorAll(".an-icon-cell").forEach((cell) => {
13081
- cell.classList.toggle("active", cell.dataset.name === name);
13461
+ cell.classList.toggle(
13462
+ "active",
13463
+ /** @type {HTMLElement} */
13464
+ cell.dataset.name === name
13465
+ );
13082
13466
  });
13083
13467
  this._insertBtn.removeAttribute("disabled");
13084
13468
  this._updatePreview(name);
@@ -13117,7 +13501,7 @@ var IconDialog = class {
13117
13501
  range.collapse(false);
13118
13502
  }
13119
13503
  const _sc = range.startContainer;
13120
- const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
13504
+ const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest("td, th");
13121
13505
  range.deleteContents();
13122
13506
  if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
13123
13507
  range.setStart(_tdAnchor, 0);
@@ -13388,13 +13772,13 @@ var ContextMenu = class {
13388
13772
  this._menuDisposers.forEach((d) => {
13389
13773
  try {
13390
13774
  d();
13391
- } catch (e) {}
13775
+ } catch (_e) {}
13392
13776
  });
13393
13777
  this._menuDisposers = [];
13394
13778
  this._disposers.forEach((d) => {
13395
13779
  try {
13396
13780
  d();
13397
- } catch (e) {}
13781
+ } catch (_e) {}
13398
13782
  });
13399
13783
  this._disposers = [];
13400
13784
  if (this.el && this.el.parentNode) this.el.parentNode.removeChild(this.el);
@@ -13585,13 +13969,13 @@ var ContextMenu = class {
13585
13969
  });
13586
13970
  this._menuDisposers.push(offHeader);
13587
13971
  const offMove = on(gridEl, "mousemove", (e) => {
13588
- const cell = e.target.closest("[data-row]");
13972
+ const cell = e.target?.closest("[data-row]");
13589
13973
  if (!cell) return;
13590
13974
  setHighlight(+cell.dataset.row, +cell.dataset.col);
13591
13975
  });
13592
13976
  const offLeave = on(gridEl, "mouseleave", () => setHighlight(0, 0));
13593
13977
  const offClick = on(gridEl, "click", (e) => {
13594
- const cell = e.target.closest("[data-row]");
13978
+ const cell = e.target?.closest("[data-row]");
13595
13979
  if (!cell) return;
13596
13980
  const rows = +cell.dataset.row;
13597
13981
  const cols = +cell.dataset.col;
@@ -13616,7 +14000,7 @@ var ContextMenu = class {
13616
14000
  class: "an-context-item",
13617
14001
  "data-name": it.name || ""
13618
14002
  });
13619
- if (typeof it.disabled === "function" ? it.disabled(this.context) : !!it.disabled) btn.disabled = true;
14003
+ if (typeof it.disabled === "function" ? it.disabled(this.context) : !!it.disabled) /** @type {HTMLButtonElement} */ btn.disabled = true;
13620
14004
  if (it.icon) {
13621
14005
  const iconSpan = createElement("span", {
13622
14006
  class: "an-context-icon",
@@ -13648,7 +14032,7 @@ var ContextMenu = class {
13648
14032
  const winSel = window.getSelection();
13649
14033
  this._savedRange = winSel && winSel.rangeCount > 0 ? winSel.getRangeAt(0).cloneRange() : null;
13650
14034
  this._renderItems(this._items);
13651
- let openX = event.clientX;
14035
+ const openX = event.clientX;
13652
14036
  let openY = event.clientY;
13653
14037
  if (this._savedRange && !this._savedRange.collapsed) try {
13654
14038
  const selRect = this._savedRange.getBoundingClientRect();
@@ -13814,7 +14198,7 @@ var ContextMenu = class {
13814
14198
  while (el = iter.nextNode()) {
13815
14199
  if (!editable.contains(el) || el === editable) continue;
13816
14200
  try {
13817
- if (range.intersectsNode(el)) el.removeAttribute("style");
14201
+ if (range.intersectsNode(el)) /** @type {Element} */ el.removeAttribute("style");
13818
14202
  } catch {}
13819
14203
  }
13820
14204
  this.context.invoke("editor.afterCommand");
@@ -14076,8 +14460,8 @@ var FindReplace = class {
14076
14460
  const replaceActions = this._dialog.querySelector(".an-fr-replace-actions");
14077
14461
  const title = this._dialog.querySelector(".an-dialog-title");
14078
14462
  const isReplace = this._mode === "replace";
14079
- if (replaceRow) replaceRow.style.display = isReplace ? "" : "none";
14080
- if (replaceActions) replaceActions.style.display = isReplace ? "" : "none";
14463
+ if (replaceRow) /** @type {HTMLElement} */ replaceRow.style.display = isReplace ? "" : "none";
14464
+ if (replaceActions) /** @type {HTMLElement} */ replaceActions.style.display = isReplace ? "" : "none";
14081
14465
  if (title) title.textContent = isReplace ? this.context.locale.findReplace.findReplaceTitle : this.context.locale.findReplace.findTitle;
14082
14466
  }
14083
14467
  _buildDialog() {
@@ -14088,106 +14472,123 @@ var FindReplace = class {
14088
14472
  "aria-modal": "true",
14089
14473
  "aria-label": L.findReplaceTitle
14090
14474
  });
14091
- const box = createElement("div", { class: "an-dialog-box" });
14092
- const titleRow = createElement("div", { class: "an-icon-title-row" });
14475
+ const box = createElement("div", { class: "an-dialog-box an-fr-box" });
14476
+ const header = createElement("div", { class: "an-fr-header" });
14477
+ const titleGroup = createElement("div", { class: "an-dialog-title-group" });
14478
+ const iconEl = createElement("span", { class: "an-dialog-icon an-dialog-icon--sm" });
14479
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
14093
14480
  const title = createElement("h3", { class: "an-dialog-title" });
14094
14481
  title.textContent = L.findTitle;
14482
+ titleGroup.append(iconEl, title);
14095
14483
  const closeBtn = createElement("button", {
14096
14484
  type: "button",
14097
14485
  class: "an-icon-close",
14098
- "aria-label": "Close"
14486
+ title: L.close,
14487
+ "aria-label": L.close
14099
14488
  });
14100
- closeBtn.textContent = L.close;
14489
+ closeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
14101
14490
  this._closeBtn = closeBtn;
14102
- titleRow.append(title, closeBtn);
14103
- box.appendChild(titleRow);
14104
- const findRow = createElement("div", { class: "an-fr-find-row" });
14491
+ header.append(titleGroup, closeBtn);
14492
+ box.appendChild(header);
14493
+ const searchBar = createElement("div", { class: "an-fr-search-bar" });
14105
14494
  const findInput = createElement("input", {
14106
14495
  type: "text",
14107
- class: "an-input",
14496
+ class: "an-input an-fr-input",
14108
14497
  placeholder: L.findPlaceholder,
14109
14498
  "aria-label": L.searchAriaLabel
14110
14499
  });
14111
14500
  this._findInput = findInput;
14112
- findRow.appendChild(findInput);
14113
- box.appendChild(findRow);
14114
- const optRow = createElement("div", { class: "an-fr-options-row" });
14115
- const caseLabel = createElement("label", { class: "an-label an-label-inline" });
14116
14501
  const caseCheckbox = createElement("input", {
14117
14502
  type: "checkbox",
14118
- "aria-label": "Case sensitive"
14503
+ style: "display:none",
14504
+ "aria-hidden": "true"
14119
14505
  });
14120
14506
  this._caseCheckbox = caseCheckbox;
14121
- caseLabel.append(caseCheckbox, document.createTextNode(L.caseSensitive));
14122
- const counter = createElement("span", { class: "an-fr-counter" });
14123
- this._counterEl = counter;
14124
- optRow.append(caseLabel, counter);
14125
- box.appendChild(optRow);
14126
- const findActions = createElement("div", { class: "an-dialog-actions an-fr-find-actions" });
14507
+ const caseBtn = createElement("button", {
14508
+ type: "button",
14509
+ class: "an-fr-icon-btn",
14510
+ title: "Case sensitive",
14511
+ "aria-label": "Case sensitive"
14512
+ });
14513
+ caseBtn.textContent = "Aa";
14127
14514
  const prevBtn = createElement("button", {
14128
14515
  type: "button",
14129
- class: "an-btn"
14516
+ class: "an-fr-icon-btn",
14517
+ title: "Previous (Shift+Enter)",
14518
+ "aria-label": "Previous"
14130
14519
  });
14131
- prevBtn.textContent = L.prevBtn;
14520
+ prevBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>`;
14132
14521
  const nextBtn = createElement("button", {
14133
14522
  type: "button",
14134
- class: "an-btn an-btn-primary"
14523
+ class: "an-fr-icon-btn",
14524
+ title: "Next (Enter)",
14525
+ "aria-label": "Next"
14135
14526
  });
14136
- nextBtn.textContent = L.nextBtn;
14137
- findActions.append(prevBtn, nextBtn);
14138
- box.appendChild(findActions);
14527
+ nextBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
14528
+ const counter = createElement("span", { class: "an-fr-counter" });
14529
+ this._counterEl = counter;
14530
+ searchBar.append(findInput, caseCheckbox, caseBtn, prevBtn, nextBtn, counter);
14531
+ box.appendChild(searchBar);
14139
14532
  const replaceRow = createElement("div", { class: "an-fr-replace-row" });
14140
14533
  replaceRow.style.display = "none";
14141
14534
  const replaceInput = createElement("input", {
14142
14535
  type: "text",
14143
- class: "an-input",
14536
+ class: "an-input an-fr-input",
14144
14537
  placeholder: L.replacePlaceholder,
14145
14538
  "aria-label": L.replaceAriaLabel
14146
14539
  });
14147
14540
  this._replaceInput = replaceInput;
14148
- replaceRow.appendChild(replaceInput);
14149
- box.appendChild(replaceRow);
14150
- const replaceActions = createElement("div", { class: "an-dialog-actions an-fr-replace-actions" });
14151
- replaceActions.style.display = "none";
14152
14541
  const replaceBtn = createElement("button", {
14153
14542
  type: "button",
14154
- class: "an-btn"
14543
+ class: "an-btn an-fr-replace-btn"
14155
14544
  });
14156
14545
  replaceBtn.textContent = L.replaceBtn;
14157
14546
  const replaceAllBtn = createElement("button", {
14158
14547
  type: "button",
14159
- class: "an-btn an-btn-primary"
14548
+ class: "an-btn an-btn-primary an-fr-replace-btn"
14160
14549
  });
14161
14550
  replaceAllBtn.textContent = L.replaceAllBtn;
14162
- replaceActions.append(replaceBtn, replaceAllBtn);
14551
+ replaceRow.append(replaceInput, replaceBtn, replaceAllBtn);
14552
+ box.appendChild(replaceRow);
14553
+ const replaceActions = createElement("div", { class: "an-fr-replace-actions" });
14554
+ replaceActions.style.display = "none";
14163
14555
  box.appendChild(replaceActions);
14164
14556
  overlay.appendChild(box);
14557
+ makeDraggable(header, box);
14165
14558
  const d1 = on(closeBtn, "click", () => this._close());
14166
14559
  const d2 = on(overlay, "click", (e) => {
14167
14560
  if (e.target === overlay) this._close();
14168
14561
  });
14169
14562
  const d3 = on(findInput, "input", () => this._onSearch());
14170
- const d4 = on(caseCheckbox, "change", () => {
14563
+ const d4 = on(caseBtn, "click", () => {
14564
+ this._caseSensitive = !this._caseSensitive;
14565
+ caseCheckbox.checked = this._caseSensitive;
14566
+ caseBtn.classList.toggle("an-fr-icon-btn--active", this._caseSensitive);
14567
+ this._onSearch();
14568
+ });
14569
+ const d5 = on(caseCheckbox, "change", () => {
14171
14570
  this._caseSensitive = caseCheckbox.checked;
14571
+ caseBtn.classList.toggle("an-fr-icon-btn--active", this._caseSensitive);
14172
14572
  this._onSearch();
14173
14573
  });
14174
- const d5 = on(nextBtn, "click", () => this._next());
14175
- const d6 = on(prevBtn, "click", () => this._prev());
14176
- const d7 = on(replaceBtn, "click", () => this._replace());
14177
- const d8 = on(replaceAllBtn, "click", () => this._replaceAll());
14178
- const d9 = on(findInput, "keydown", (e) => {
14179
- if (e.key === "Enter") {
14574
+ const d6 = on(nextBtn, "click", () => this._next());
14575
+ const d7 = on(prevBtn, "click", () => this._prev());
14576
+ const d8 = on(replaceBtn, "click", () => this._replace());
14577
+ const d9 = on(replaceAllBtn, "click", () => this._replaceAll());
14578
+ const d10 = on(findInput, "keydown", (e) => {
14579
+ const ke = e;
14580
+ if (ke.key === "Enter") {
14180
14581
  e.preventDefault();
14181
- e.shiftKey ? this._prev() : this._next();
14582
+ ke.shiftKey ? this._prev() : this._next();
14182
14583
  }
14183
14584
  });
14184
- const d10 = on(replaceInput, "keydown", (e) => {
14585
+ const d11 = on(replaceInput, "keydown", (e) => {
14185
14586
  if (e.key === "Enter") {
14186
14587
  e.preventDefault();
14187
14588
  this._replace();
14188
14589
  }
14189
14590
  });
14190
- this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
14591
+ this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
14191
14592
  return overlay;
14192
14593
  }
14193
14594
  _onSearch() {
@@ -14548,9 +14949,10 @@ var ImageCropOverlay = class {
14548
14949
  }), on(h, "touchstart", (e) => {
14549
14950
  e.preventDefault();
14550
14951
  e.stopPropagation();
14952
+ const te = e;
14551
14953
  this._startHandleDrag({
14552
- clientX: e.touches[0].clientX,
14553
- clientY: e.touches[0].clientY
14954
+ clientX: te.touches[0].clientX,
14955
+ clientY: te.touches[0].clientY
14554
14956
  }, id);
14555
14957
  }, { passive: false }));
14556
14958
  this._handles[id] = h;
@@ -14567,9 +14969,10 @@ var ImageCropOverlay = class {
14567
14969
  if (e.target !== cropBox && e.target !== grid) return;
14568
14970
  e.preventDefault();
14569
14971
  e.stopPropagation();
14972
+ const te2 = e;
14570
14973
  this._startBoxMove({
14571
- clientX: e.touches[0].clientX,
14572
- clientY: e.touches[0].clientY
14974
+ clientX: te2.touches[0].clientX,
14975
+ clientY: te2.touches[0].clientY
14573
14976
  });
14574
14977
  }, { passive: false }));
14575
14978
  const infoEl = document.createElement("div");
@@ -14731,9 +15134,10 @@ var ImageCropOverlay = class {
14731
15134
  _attachDocDrag(onMove) {
14732
15135
  const onTouchMove = (e) => {
14733
15136
  e.preventDefault();
15137
+ const te3 = e;
14734
15138
  onMove({
14735
- clientX: e.touches[0].clientX,
14736
- clientY: e.touches[0].clientY
15139
+ clientX: te3.touches[0].clientX,
15140
+ clientY: te3.touches[0].clientY
14737
15141
  });
14738
15142
  };
14739
15143
  const cleanup = () => {
@@ -15310,7 +15714,9 @@ var BubbleToolbar = class {
15310
15714
  const d6 = this.context.on("contextMenu:hide", () => {
15311
15715
  this._contextMenuOpen = false;
15312
15716
  });
15313
- this._disposers.push(d1, d2, d3, d4, d5, d6);
15717
+ const d7 = on(window, "scroll", () => this._hide(), { passive: true });
15718
+ const d8 = on(window, "resize", () => this._hide(), { passive: true });
15719
+ this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8);
15314
15720
  return this;
15315
15721
  }
15316
15722
  destroy() {
@@ -15424,20 +15830,22 @@ var BubbleToolbar = class {
15424
15830
  picker.appendChild(customRow);
15425
15831
  document.body.appendChild(picker);
15426
15832
  this._picker = picker;
15427
- this._picker._paletteEl = palette;
15428
- this._picker._noColorBtn = noColorBtn;
15429
- this._picker._colorInput = colorInput;
15833
+ const pickerAny = picker;
15834
+ pickerAny._paletteEl = palette;
15835
+ pickerAny._noColorBtn = noColorBtn;
15836
+ pickerAny._colorInput = colorInput;
15430
15837
  }
15431
15838
  _openColorPicker(type, anchorBtn) {
15432
15839
  const sel = window.getSelection();
15433
15840
  if (sel && sel.rangeCount > 0) this._savedRange = sel.getRangeAt(0).cloneRange();
15434
15841
  this._pickerType = type;
15435
- const palette = this._picker._paletteEl;
15436
- const noColorBtn = this._picker._noColorBtn;
15842
+ const pickerAny = this._picker;
15843
+ const palette = pickerAny._paletteEl;
15844
+ const noColorBtn = pickerAny._noColorBtn;
15437
15845
  if (type === "hiliteColor") {
15438
15846
  if (!palette.contains(noColorBtn)) palette.appendChild(noColorBtn);
15439
15847
  } else if (palette.contains(noColorBtn)) palette.removeChild(noColorBtn);
15440
- this._picker._colorInput.value = type === "foreColor" ? "#000000" : "#ffff00";
15848
+ /** @type {any} */ this._picker._colorInput.value = type === "foreColor" ? "#000000" : "#ffff00";
15441
15849
  this._picker.style.display = "block";
15442
15850
  const pw = this._picker.offsetWidth;
15443
15851
  const ph = this._picker.offsetHeight;
@@ -15471,7 +15879,7 @@ var BubbleToolbar = class {
15471
15879
  const name = type === "hiliteColor" ? "hiliteColor" : "foreColor";
15472
15880
  const btn = this._el && this._el.querySelector(`[data-name="${name}"]`);
15473
15881
  const strip = btn && btn.querySelector(".an-bubble-color-strip");
15474
- if (strip) strip.style.background = color === "transparent" ? "transparent" : color;
15882
+ if (strip) /** @type {HTMLElement} */ strip.style.background = color === "transparent" ? "transparent" : color;
15475
15883
  this._closeColorPicker();
15476
15884
  this._syncActive();
15477
15885
  }
@@ -15487,6 +15895,14 @@ var BubbleToolbar = class {
15487
15895
  let top = rect.top - bh - gap;
15488
15896
  left = Math.max(8, Math.min(left, window.innerWidth - bw - 8));
15489
15897
  if (top < 8) top = rect.bottom + gap;
15898
+ const tableTooltipEl = document.querySelector(".an-table-tooltip");
15899
+ if (tableTooltipEl && tableTooltipEl.style.display !== "none") {
15900
+ const ttRect = tableTooltipEl.getBoundingClientRect();
15901
+ if (top < ttRect.bottom + gap && top + bh > ttRect.top - gap) {
15902
+ top = rect.bottom + gap;
15903
+ if (top + bh > window.innerHeight - 8) top = ttRect.bottom + gap;
15904
+ }
15905
+ }
15490
15906
  el.style.top = `${top}px`;
15491
15907
  el.style.left = `${left}px`;
15492
15908
  el.style.visibility = "";
@@ -15518,12 +15934,12 @@ var BubbleToolbar = class {
15518
15934
  const cs = window.getComputedStyle(node);
15519
15935
  const foreBtn = this._el.querySelector("[data-name=\"foreColor\"]");
15520
15936
  const foreStrip = foreBtn && foreBtn.querySelector(".an-bubble-color-strip");
15521
- if (foreStrip) foreStrip.style.background = cs.color || "#000000";
15937
+ if (foreStrip) /** @type {HTMLElement} */ foreStrip.style.background = cs.color || "#000000";
15522
15938
  const hiliteBtn = this._el.querySelector("[data-name=\"hiliteColor\"]");
15523
15939
  const hiliteStrip = hiliteBtn && hiliteBtn.querySelector(".an-bubble-color-strip");
15524
15940
  if (hiliteStrip) {
15525
15941
  const bg = cs.backgroundColor;
15526
- hiliteStrip.style.background = !bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent" ? "transparent" : bg;
15942
+ /** @type {HTMLElement} */ hiliteStrip.style.background = !bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent" ? "transparent" : bg;
15527
15943
  }
15528
15944
  }
15529
15945
  _onSelectionChange() {
@@ -15651,11 +16067,11 @@ var Mention = class {
15651
16067
  el.setAttribute("role", "listbox");
15652
16068
  el.addEventListener("mousedown", (e) => e.preventDefault());
15653
16069
  el.addEventListener("click", (e) => {
15654
- const item = e.target.closest(".an-mention-item");
16070
+ const item = e.target?.closest(".an-mention-item");
15655
16071
  if (item) this._select(+item.dataset.index);
15656
16072
  });
15657
16073
  el.addEventListener("mousemove", (e) => {
15658
- const item = e.target.closest(".an-mention-item");
16074
+ const item = e.target?.closest(".an-mention-item");
15659
16075
  if (item) this._highlightItem(+item.dataset.index);
15660
16076
  });
15661
16077
  document.body.appendChild(el);
@@ -15670,7 +16086,7 @@ var Mention = class {
15670
16086
  const li = document.createElement("div");
15671
16087
  li.className = "an-mention-item";
15672
16088
  li.setAttribute("role", "option");
15673
- li.dataset.index = i;
16089
+ li.dataset.index = String(i);
15674
16090
  if (item.avatar) {
15675
16091
  const img = document.createElement("img");
15676
16092
  img.src = item.avatar;
@@ -15920,7 +16336,7 @@ var Context = class {
15920
16336
  /**
15921
16337
  * Registers and initialises a custom module on this instance.
15922
16338
  * @param {string} name
15923
- * @param {Function} ModuleClass
16339
+ * @param {new (ctx: this) => any} ModuleClass
15924
16340
  * @returns {this}
15925
16341
  */
15926
16342
  registerModule(name, ModuleClass) {
@@ -16227,10 +16643,12 @@ var Context = class {
16227
16643
  this._disposers.forEach((d) => d());
16228
16644
  this._disposers = [];
16229
16645
  const container = this.layoutInfo.container;
16646
+ const wasDark = container && container.classList.contains("an-theme-dark");
16230
16647
  if (container && container.parentNode) {
16231
16648
  this.targetEl.style.display = "";
16232
16649
  container.parentNode.removeChild(container);
16233
16650
  }
16651
+ if (wasDark && !document.querySelector(".an-container.an-theme-dark")) document.body.classList.remove("an-theme-dark");
16234
16652
  if (typeof this.options.onDestroy === "function") this.options.onDestroy(this);
16235
16653
  this._alive = false;
16236
16654
  this._listeners.clear();
@@ -16239,7 +16657,8 @@ var Context = class {
16239
16657
  * Syncs editor HTML back into the original textarea/input for form submission.
16240
16658
  */
16241
16659
  _syncToTarget() {
16242
- if (this.targetEl.tagName === "TEXTAREA" || this.targetEl.tagName === "INPUT") this.targetEl.value = this.getHTML();
16660
+ if (this.targetEl.tagName === "TEXTAREA" || this.targetEl.tagName === "INPUT")
16661
+ /** @type {HTMLInputElement} */ this.targetEl.value = this.getHTML();
16243
16662
  }
16244
16663
  };
16245
16664
  //#endregion
@@ -16359,13 +16778,21 @@ function any(arr, predicate) {
16359
16778
  */
16360
16779
  var userAgent = navigator.userAgent;
16361
16780
  var env = {
16781
+ /** True if browser is Chrome */
16362
16782
  isChrome: /Chrome\//.test(userAgent),
16783
+ /** True if browser is Firefox */
16363
16784
  isFF: /Firefox\//.test(userAgent),
16785
+ /** True if browser is Safari (not Chrome) */
16364
16786
  isSafari: /^((?!chrome|android).)*safari/i.test(userAgent),
16787
+ /** True if browser is Edge (Chromium) */
16365
16788
  isEdge: /Edg\//.test(userAgent),
16789
+ /** True if running on macOS */
16366
16790
  isMac: /Macintosh/.test(userAgent),
16791
+ /** True if running on mobile */
16367
16792
  isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent),
16793
+ /** True if touch is supported */
16368
16794
  isTouch: "ontouchstart" in window || navigator.maxTouchPoints > 0,
16795
+ /** Modifier key name depending on platform */
16369
16796
  modifierKey: /Macintosh/.test(userAgent) ? "metaKey" : "ctrlKey"
16370
16797
  };
16371
16798
  //#endregion
@@ -16374,6 +16801,13 @@ var _originalDefaults = { ...defaultOptions };
16374
16801
  /** @type {WeakMap<Element, Context>} */
16375
16802
  var instances = /* @__PURE__ */ new WeakMap();
16376
16803
  var AutumnNote = {
16804
+ /**
16805
+ * Creates (or returns existing) editor instance on one or more elements.
16806
+ *
16807
+ * @param {string|Element|NodeList|Element[]} selector
16808
+ * @param {import('./settings.js').AsnOptions} [options]
16809
+ * @returns {Context|Context[]} single Context or array of Contexts
16810
+ */
16377
16811
  create(selector, options = {}) {
16378
16812
  const ctxs = resolveElements(selector).map((el) => {
16379
16813
  if (instances.has(el)) return instances.get(el);
@@ -16384,6 +16818,10 @@ var AutumnNote = {
16384
16818
  });
16385
16819
  return ctxs.length === 1 ? ctxs[0] : ctxs;
16386
16820
  },
16821
+ /**
16822
+ * Destroys the editor(s) on the given selector.
16823
+ * @param {string|Element|NodeList|Element[]} selector
16824
+ */
16387
16825
  destroy(selector) {
16388
16826
  resolveElements(selector).forEach((el) => {
16389
16827
  const ctx = instances.get(el);
@@ -16393,23 +16831,45 @@ var AutumnNote = {
16393
16831
  }
16394
16832
  });
16395
16833
  },
16834
+ /**
16835
+ * Returns the Context instance for a given element (or null).
16836
+ * @param {string|Element} selector
16837
+ * @returns {Context|null}
16838
+ */
16396
16839
  getInstance(selector) {
16397
16840
  const el = typeof selector === "string" ? document.querySelector(selector) : selector;
16398
16841
  return el ? instances.get(el) || null : null;
16399
16842
  },
16843
+ /** Returns a shallow copy of the default options (read-only snapshot). */
16400
16844
  get defaults() {
16401
16845
  return { ...defaultOptions };
16402
16846
  },
16847
+ /** Merges properties into the global defaults, applied to all future instances. */
16403
16848
  setDefaults(overrides) {
16404
16849
  Object.assign(defaultOptions, overrides);
16405
16850
  },
16851
+ /** Restores global defaults to their original factory values. */
16406
16852
  resetDefaults() {
16407
16853
  Object.keys(defaultOptions).forEach((k) => delete defaultOptions[k]);
16408
16854
  Object.assign(defaultOptions, _originalDefaults);
16409
16855
  },
16856
+ /**
16857
+ * Registers a custom module to be included in every new editor instance.
16858
+ * @param {string} name - unique module key used for ctx.invoke() calls
16859
+ * @param {Function} ModuleClass - class with initialize() and optional destroy()
16860
+ */
16410
16861
  registerModule(name, ModuleClass) {
16411
16862
  _customModules.set(name, ModuleClass);
16412
16863
  },
16864
+ /**
16865
+ * Installs a plugin globally — applied to every future editor instance.
16866
+ * Plugin `buttons` are registered to the global button registry immediately
16867
+ * so they are available when Toolbar initialises inside create().
16868
+ * Plugin `install()` is called after all built-in modules have initialised.
16869
+ * @param {object} plugin - { name, version?, buttons?, install?, uninstall? }
16870
+ * @param {object} [options] - Forwarded to plugin.install(context, options)
16871
+ * @returns {typeof AutumnNote}
16872
+ */
16413
16873
  use(plugin, options = {}) {
16414
16874
  if (!plugin || typeof plugin.name !== "string") throw new TypeError("[AutumnNote] AutumnNote.use: plugin must have a string `name` property.");
16415
16875
  if (_globalPlugins.has(plugin.name)) {
@@ -16423,14 +16883,26 @@ var AutumnNote = {
16423
16883
  });
16424
16884
  return this;
16425
16885
  },
16886
+ /**
16887
+ * Returns true if a plugin with the given name has been registered globally.
16888
+ * @param {string} name
16889
+ * @returns {boolean}
16890
+ */
16426
16891
  hasPlugin(name) {
16427
16892
  return _globalPlugins.has(name);
16428
16893
  },
16894
+ /**
16895
+ * Registers a single button definition in the global button registry.
16896
+ * After create(), call ctx.invoke('toolbar.rebuild') to render new buttons.
16897
+ * @param {object} btnDef - ButtonDef-compatible object with a `name` string
16898
+ * @returns {typeof AutumnNote}
16899
+ */
16429
16900
  registerButton(btnDef) {
16430
16901
  registerButton(btnDef);
16431
16902
  return this;
16432
16903
  },
16433
- version: "1.4.2"
16904
+ /** Library version */
16905
+ version: "1.5.0"
16434
16906
  };
16435
16907
  /**
16436
16908
  * @param {string|Element|NodeList|Element[]} selector
@@ -16443,6 +16915,6 @@ function resolveElements(selector) {
16443
16915
  return [];
16444
16916
  }
16445
16917
  //#endregion
16446
- export { Context, ELEMENT_NODE, TEXT_NODE, WrappedRange, _buttonRegistry, alignCenterBtn, alignJustifyBtn, alignLeftBtn, alignRightBtn, all, ancestors, any, backColorBtn, boldBtn, checklistBtn, children, chunk, clamp, closest, closestPara, codeviewBtn, collapsedRange, compose, createElement, currentRange, debounce, AutumnNote as default, defaultOptions, defaultToolbar, directionBtn, emojiBtn, env, findBtn, findReplaceBtn, first, flatten, fontFamilyBtn, fontSizeBtn, foreColorBtn, fromNativeRange, fullscreenBtn, getButton, groupBy, hrBtn, iconBtn, identity, imageBtn, indentBtn, initial, inlineCodeBtn, insertAfter, isAnchor, isEditable, isElement, isEmpty, isFunction, isImage, isInline, isInsideEditable, isKey, isLi, isList, isModifier, isNil, isPara, isPlainObject, isSelectionInside, isString, isTable, isText, isVoid, italicBtn, key, last, lineHeightBtn, linkBtn, locales, mergeDeep, nextElement, nodeValue, olBtn, on, outdentBtn, outerHtml, paragraphStyleBtn, placeCaret, prevElement, printBtn, rangeFromElement, rect2bnd, redoBtn, registerButton, remove, removeFormatBtn, resolveLocale, sanitiseHTML, sanitiseUrl, shortcutsBtn, splitText, strikeBtn, subscriptBtn, superscriptBtn, tableBtn, tail, throttle, trapFocus, ulBtn, underlineBtn, undoBtn, unique, unwrap, videoBtn, withSavedRange, wrap };
16918
+ export { Context, ELEMENT_NODE, TEXT_NODE, WrappedRange, _buttonRegistry, alignCenterBtn, alignJustifyBtn, alignLeftBtn, alignRightBtn, all, ancestors, any, backColorBtn, boldBtn, checklistBtn, children, chunk, clamp, closest, closestPara, codeviewBtn, collapsedRange, compose, createElement, currentRange, debounce, AutumnNote as default, defaultOptions, defaultToolbar, directionBtn, emojiBtn, env, findBtn, findReplaceBtn, first, flatten, fontFamilyBtn, fontSizeBtn, foreColorBtn, fromNativeRange, fullscreenBtn, getButton, groupBy, hrBtn, iconBtn, identity, imageBtn, indentBtn, initial, inlineCodeBtn, insertAfter, isAnchor, isEditable, isElement, isEmpty, isFunction, isImage, isInline, isInsideEditable, isKey, isLi, isList, isModifier, isNil, isPara, isPlainObject, isSelectionInside, isString, isTable, isText, isVoid, italicBtn, key, last, lineHeightBtn, linkBtn, locales, makeDraggable, mergeDeep, nextElement, nodeValue, olBtn, on, outdentBtn, outerHtml, paragraphStyleBtn, placeCaret, prevElement, printBtn, rangeFromElement, rect2bnd, redoBtn, registerButton, remove, removeFormatBtn, resolveLocale, sanitiseHTML, sanitiseUrl, shortcutsBtn, splitText, strikeBtn, subscriptBtn, superscriptBtn, tableBtn, tail, throttle, trapFocus, ulBtn, underlineBtn, undoBtn, unique, unwrap, videoBtn, withSavedRange, wrap };
16447
16919
 
16448
16920
  //# sourceMappingURL=autumnnote.es.js.map