autumnnote 1.5.0 → 1.6.1

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 (53) hide show
  1. package/README.md +10 -8
  2. package/dist/autumnnote.css +324 -4
  3. package/dist/autumnnote.es.js +943 -526
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +935 -519
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +21 -3
  8. package/src/js/Context.js +23 -16
  9. package/src/js/core/detectLang.js +98 -0
  10. package/src/js/core/dom.js +67 -11
  11. package/src/js/core/env.js +1 -1
  12. package/src/js/core/func.js +1 -1
  13. package/src/js/core/lists.js +1 -1
  14. package/src/js/core/markdown.js +32 -31
  15. package/src/js/core/range.js +8 -8
  16. package/src/js/editing/History.js +10 -10
  17. package/src/js/editing/Style.js +44 -44
  18. package/src/js/editing/Table.js +5 -7
  19. package/src/js/editing/Typing.js +19 -19
  20. package/src/js/i18n/en.js +2 -0
  21. package/src/js/i18n/vi.js +2 -0
  22. package/src/js/index.js +3 -2
  23. package/src/js/module/AutoSaveRestore.js +2 -4
  24. package/src/js/module/BubbleToolbar.js +51 -34
  25. package/src/js/module/Buttons.js +20 -18
  26. package/src/js/module/Clipboard.js +16 -17
  27. package/src/js/module/CodeTooltip.js +48 -24
  28. package/src/js/module/Codeview.js +5 -7
  29. package/src/js/module/ContextMenu.js +37 -37
  30. package/src/js/module/Editor.js +77 -26
  31. package/src/js/module/EmojiDialog.js +24 -18
  32. package/src/js/module/FindReplace.js +93 -66
  33. package/src/js/module/Fullscreen.js +1 -1
  34. package/src/js/module/IconDialog.js +39 -35
  35. package/src/js/module/ImageCropOverlay.js +13 -13
  36. package/src/js/module/ImageDialog.js +19 -13
  37. package/src/js/module/ImageResizer.js +5 -5
  38. package/src/js/module/ImageTooltip.js +20 -16
  39. package/src/js/module/LinkDialog.js +20 -14
  40. package/src/js/module/LinkTooltip.js +15 -12
  41. package/src/js/module/MarkdownShortcuts.js +11 -14
  42. package/src/js/module/Mention.js +11 -13
  43. package/src/js/module/Placeholder.js +1 -1
  44. package/src/js/module/ShortcutsDialog.js +2 -4
  45. package/src/js/module/Statusbar.js +5 -7
  46. package/src/js/module/TableTooltip.js +196 -36
  47. package/src/js/module/Toolbar.js +35 -34
  48. package/src/js/module/VideoDialog.js +16 -10
  49. package/src/js/module/VideoResizer.js +7 -9
  50. package/src/js/module/VideoTooltip.js +15 -10
  51. package/src/js/renderer.js +5 -3
  52. package/src/js/settings.js +53 -36
  53. package/src/styles/autumnnote.scss +332 -7
@@ -109,9 +109,7 @@ export class Statusbar {
109
109
  this._dragDisposers.forEach((d) => d());
110
110
  this._dragDisposers = null;
111
111
  }
112
- if (this.el && this.el.parentNode) {
113
- this.el.parentNode.removeChild(this.el);
114
- }
112
+ this.el?.remove();
115
113
  this.el = null;
116
114
  }
117
115
 
@@ -136,7 +134,7 @@ export class Statusbar {
136
134
  const MIN_EDITABLE = 40;
137
135
  const fixedH = Array.from(containerEl.children)
138
136
  .filter(child => !child.classList.contains('an-editable'))
139
- .reduce((sum, child) => sum + child.offsetHeight, 0);
137
+ .reduce((sum, child) => sum + /** @type {HTMLElement} */ (child).offsetHeight, 0);
140
138
  const trueMin = Math.max(this.options.minHeight || 100, fixedH + MIN_EDITABLE);
141
139
  containerEl.style.height = `${Math.max(trueMin, startH + delta)}px`;
142
140
  };
@@ -207,7 +205,7 @@ export class Statusbar {
207
205
  _bindContentEvents() {
208
206
  const editable = this.context.layoutInfo.editable;
209
207
  const updateDebounced = debounce(() => this.update(), 200);
210
- const d = on(editable, 'input', updateDebounced);
208
+ const d = on(editable, 'input', /** @type {EventListener} */ (updateDebounced));
211
209
  this._disposers.push(d);
212
210
  }
213
211
 
@@ -217,7 +215,7 @@ export class Statusbar {
217
215
  // textContent is faster than innerText (no layout flush, no CSS visibility check)
218
216
  const text = editable.textContent || '';
219
217
  const words = _countWords(text);
220
- const chars = text.replace(/\n/g, '').length;
218
+ const chars = text.replaceAll('\n', '').length;
221
219
  const maxWords = this.options.maxWords || 0;
222
220
  const maxChars = this.options.maxChars || 0;
223
221
 
@@ -249,6 +247,6 @@ export class Statusbar {
249
247
  */
250
248
  getCharCount() {
251
249
  const editable = this.context.layoutInfo.editable;
252
- return ((editable.innerText || '').replace(/\n/g, '')).length;
250
+ return (editable.innerText || '').replaceAll('\n', '').length;
253
251
  }
254
252
  }
@@ -58,7 +58,7 @@ function getCellAfterVisualCol(row, visualIdx) {
58
58
  if (vIdx > visualIdx) {
59
59
  // next cell after the one that starts at / spans visualIdx
60
60
  const next = c.nextElementSibling;
61
- return (next && next.tagName === 'TD' || next && next.tagName === 'TH') ? next : null;
61
+ return (next && next.tagName === 'TD' || next && next.tagName === 'TH') ? /** @type {HTMLTableCellElement} */ (next) : null;
62
62
  }
63
63
  }
64
64
  return null;
@@ -115,8 +115,15 @@ const ICONS = {
115
115
  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>`,
116
116
  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>`,
117
117
  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>`,
118
+ 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>`,
118
119
  };
119
120
 
121
+ const SHADE_PRESETS = [
122
+ '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#efefef', '#ffffff',
123
+ '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#9900ff', '#ff00ff',
124
+ '#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3', '#c9daf8', '#d9d2e9', '#ead1dc',
125
+ ];
126
+
120
127
  export class TableTooltip {
121
128
  /** @param {import('../Context.js').Context} context */
122
129
  constructor(context) {
@@ -131,6 +138,10 @@ export class TableTooltip {
131
138
  this._sizeApply = null;
132
139
  this._sizeTitleEl = null;
133
140
  this._sizeInputEl = null;
141
+ // Cell shade popover
142
+ this._shadePopover = null;
143
+ this._shadeTitleEl = null;
144
+ this._shadeColorStrip = null;
134
145
  // Cell selection
135
146
  this._selectMode = false;
136
147
  this._selectedCells = [];
@@ -147,6 +158,9 @@ export class TableTooltip {
147
158
  this._sizePopover = this._buildSizePopover();
148
159
  document.body.appendChild(this._sizePopover);
149
160
 
161
+ this._shadePopover = this._buildCellShadePopover();
162
+ document.body.appendChild(this._shadePopover);
163
+
150
164
  const editable = this.context.layoutInfo.editable;
151
165
  this._editable = editable;
152
166
 
@@ -182,33 +196,42 @@ export class TableTooltip {
182
196
  this._disposers.push(
183
197
  on(editable, 'mouseover', (e) => {
184
198
  if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
185
- const table = e.target.closest('table');
199
+ const table = /** @type {Element} */ (e.target)?.closest('table');
186
200
  if (table && editable.contains(table)) {
187
- const cell = e.target.closest('td, th');
188
- if (cell) this._activeCell = cell;
201
+ const cell = /** @type {Element} */ (e.target)?.closest('td, th');
202
+ if (cell) {
203
+ this._activeCell = cell;
204
+ this._syncShadeStrip();
205
+ }
189
206
  this._scheduleShow(table);
190
207
  }
191
208
  }, { passive: true }),
192
209
  on(editable, 'mouseout', (e) => {
193
210
  if (this._selectMode) return; // keep tooltip alive during cell selection
194
- const to = e.relatedTarget;
211
+ const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
195
212
  if (!to || (
196
213
  !editable.contains(to) &&
197
214
  !this._el.contains(to) &&
198
- !(this._sizePopover && this._sizePopover.contains(to))
215
+ !this._sizePopover?.contains(to)
199
216
  )) {
200
217
  this._scheduleHide();
201
218
  }
202
219
  }, { passive: true }),
203
220
  on(document, 'click', (e) => {
204
- if (this._selectMode && this._activeTable && this._activeTable.contains(e.target)) return;
221
+ const et = /** @type {Node} */ (e.target);
222
+ if (this._selectMode && this._activeTable?.contains(et)) return;
205
223
  if (this._activeTable &&
206
- !this._activeTable.contains(e.target) &&
207
- !this._el.contains(e.target) &&
208
- !(this._sizePopover && this._sizePopover.contains(e.target))) {
224
+ !this._activeTable.contains(et) &&
225
+ !this._el.contains(et) &&
226
+ !this._sizePopover?.contains(et)) {
209
227
  this._hide();
210
228
  }
211
229
  }),
230
+ // Sync shade strip whenever selection moves to a different cell
231
+ on(document, 'selectionchange', () => this._syncShadeStrip()),
232
+ // Hide when the page scrolls or resizes — the tooltip position becomes stale
233
+ on(globalThis, 'scroll', () => this._hide(), { passive: true }),
234
+ on(globalThis, 'resize', () => this._hide(), { passive: true }),
212
235
  );
213
236
 
214
237
  this._initResize();
@@ -363,12 +386,16 @@ export class TableTooltip {
363
386
  this._clearTimers();
364
387
  this._disposers.forEach((d) => d());
365
388
  this._disposers = [];
366
- if (this._el && this._el.parentNode) this._el.parentNode.removeChild(this._el);
389
+ if (this._el && this._el.parentNode) this._el.remove();
367
390
  this._el = null;
368
391
  if (this._sizePopover && this._sizePopover.parentNode) {
369
- this._sizePopover.parentNode.removeChild(this._sizePopover);
392
+ this._sizePopover.remove();
370
393
  }
371
394
  this._sizePopover = null;
395
+ if (this._shadePopover && this._shadePopover.parentNode) {
396
+ this._shadePopover.remove();
397
+ }
398
+ this._shadePopover = null;
372
399
  }
373
400
 
374
401
  // ---------------------------------------------------------------------------
@@ -417,6 +444,27 @@ export class TableTooltip {
417
444
 
418
445
  el.appendChild(this._sep());
419
446
 
447
+ // Cell background shading — uses color-strip variant like foreColor/hiliteColor buttons
448
+ const shadeBtn = createElement('button', {
449
+ type: 'button',
450
+ class: 'an-link-tooltip-btn an-link-tooltip-btn--shade',
451
+ title: L.cellBackground,
452
+ });
453
+ const shadeSvgWrap = createElement('span', { class: 'an-bubble-btn-svg' });
454
+ shadeSvgWrap.innerHTML = ICONS.cellShade;
455
+ const shadeStrip = createElement('span', { class: 'an-link-tooltip-color-strip' });
456
+ shadeBtn.appendChild(shadeSvgWrap);
457
+ shadeBtn.appendChild(shadeStrip);
458
+ this._shadeColorStrip = shadeStrip;
459
+ this._disposers.push(on(shadeBtn, 'click', (e) => {
460
+ e.preventDefault();
461
+ e.stopPropagation();
462
+ this._openCellShadePopover();
463
+ }));
464
+ el.appendChild(shadeBtn);
465
+
466
+ el.appendChild(this._sep());
467
+
420
468
  // Resize
421
469
  el.appendChild(this._makeBtn(ICONS.colWidth, L.columnWidth, () => this._openSizePopover('col')));
422
470
  el.appendChild(this._makeBtn(ICONS.rowHeight, L.rowHeight, () => this._openSizePopover('row')));
@@ -434,7 +482,8 @@ export class TableTooltip {
434
482
  on(el, 'mouseenter', () => this._clearTimers()),
435
483
  on(el, 'mouseleave', () => {
436
484
  if (this._selectMode) return; // keep tooltip alive during cell selection
437
- if (this._sizePopover && this._sizePopover.style.display !== 'none') return;
485
+ if (this._sizePopover && this._sizePopover.style.display !== 'none') return;
486
+ if (this._shadePopover && this._shadePopover.style.display !== 'none') return;
438
487
  this._scheduleHide();
439
488
  }),
440
489
  );
@@ -494,12 +543,19 @@ export class TableTooltip {
494
543
  _show() {
495
544
  if (!this._activeTable) return;
496
545
  this._el.style.display = 'flex';
546
+ this._syncShadeStrip();
497
547
  // Defer: offsetWidth on a newly-visible element forces layout synchronously
498
548
  requestAnimationFrame(() => {
499
549
  if (this._activeTable) this._positionNear(this._activeTable);
500
550
  });
501
551
  }
502
552
 
553
+ _syncShadeStrip() {
554
+ if (!this._shadeColorStrip || !this._el || this._el.style.display === 'none') return;
555
+ const cell = this._getCell();
556
+ this._shadeColorStrip.style.background = cell?.style.backgroundColor || 'transparent';
557
+ }
558
+
503
559
  _hide() {
504
560
  this._el.style.display = 'none';
505
561
  this._activeTable = null;
@@ -534,7 +590,7 @@ export class TableTooltip {
534
590
  let top = rect.top - tipH - margin;
535
591
 
536
592
  if (top < margin) top = rect.bottom + margin;
537
- if (left + tipW > window.innerWidth - margin) left = window.innerWidth - tipW - margin;
593
+ if (left + tipW > globalThis.innerWidth - margin) left = globalThis.innerWidth - tipW - margin;
538
594
  if (left < margin) left = margin;
539
595
 
540
596
  this._el.style.left = `${left}px`;
@@ -547,17 +603,17 @@ export class TableTooltip {
547
603
 
548
604
  _getCell() {
549
605
  // Prefer the cell under the current text cursor (most intuitive for operations)
550
- const sel = window.getSelection();
606
+ const sel = globalThis.getSelection();
551
607
  if (sel && sel.rangeCount) {
552
608
  let container = sel.getRangeAt(0).commonAncestorContainer;
553
609
  if (container.nodeType === 3) container = container.parentElement;
554
- const cellFromSel = container && container.closest && container.closest('td, th');
555
- if (cellFromSel && this._activeTable && this._activeTable.contains(cellFromSel)) {
610
+ const cellFromSel = /** @type {Element} */ (container)?.closest('td, th');
611
+ if (cellFromSel && this._activeTable?.contains(cellFromSel)) {
556
612
  return cellFromSel;
557
613
  }
558
614
  }
559
615
  return this._activeCell
560
- || (this._activeTable && this._activeTable.querySelector('td, th'));
616
+ || this._activeTable?.querySelector('td, th');
561
617
  }
562
618
 
563
619
  // ---------------------------------------------------------------------------
@@ -679,7 +735,7 @@ export class TableTooltip {
679
735
  const bi = allRows.indexOf(best);
680
736
  const ri = allRows.indexOf(r);
681
737
  return position === 'above' ? (ri < bi ? r : best) : (ri > bi ? r : best);
682
- });
738
+ }, selectedRows[0]);
683
739
  const colCount = Array.from(refRow.cells).reduce((sum, c) => sum + (c.colSpan || 1), 0);
684
740
  const newRow = document.createElement('tr');
685
741
  const refCells = Array.from(refRow.cells);
@@ -730,7 +786,7 @@ export class TableTooltip {
730
786
  if (bodyRowsToDelete.length >= totalBodyRows) return;
731
787
  this._activeCell = null;
732
788
  this._clearSelection();
733
- selectedRows.forEach((r) => r.parentElement?.removeChild(r));
789
+ selectedRows.forEach((r) => r.remove());
734
790
  requestAnimationFrame(() => this._positionNear(this._activeTable));
735
791
  this.context.invoke('editor.afterCommand');
736
792
  }
@@ -754,7 +810,7 @@ export class TableTooltip {
754
810
  });
755
811
  this._activeCell = null;
756
812
  this._clearSelection();
757
- cellsToDelete.forEach((c) => c.parentElement?.removeChild(c));
813
+ cellsToDelete.forEach((c) => c.remove());
758
814
  requestAnimationFrame(() => this._positionNear(this._activeTable));
759
815
  this.context.invoke('editor.afterCommand');
760
816
  }
@@ -768,7 +824,7 @@ export class TableTooltip {
768
824
  // Prefer user panel-selected cells; fall back to text-selection range
769
825
  let selected = this._getSelectedCells().filter((c) => table.contains(c));
770
826
  if (selected.length < 2) {
771
- const sel = window.getSelection();
827
+ const sel = globalThis.getSelection();
772
828
  if (!sel || sel.rangeCount === 0) return;
773
829
  const range = sel.getRangeAt(0);
774
830
  const allCells = Array.from(table.querySelectorAll('td, th'));
@@ -810,7 +866,7 @@ export class TableTooltip {
810
866
  first.rowSpan = maxR - minR + 1;
811
867
  first.style.verticalAlign = 'middle';
812
868
  first.innerHTML = rectCells.map((c) => c.innerHTML).join('');
813
- rectCells.slice(1).forEach((c) => c.parentElement?.removeChild(c));
869
+ rectCells.slice(1).forEach((c) => c.remove());
814
870
 
815
871
  this._clearSelection();
816
872
  this.context.invoke('editor.afterCommand');
@@ -820,7 +876,7 @@ export class TableTooltip {
820
876
  const table = this._activeTable;
821
877
  if (!table) return;
822
878
  this._hide();
823
- if (table.parentNode) table.parentNode.removeChild(table);
879
+ if (table.parentNode) table.remove();
824
880
  this.context.invoke('editor.afterCommand');
825
881
  }
826
882
 
@@ -901,9 +957,9 @@ export class TableTooltip {
901
957
 
902
958
  const titleEl = createElement('div', { class: 'an-size-popover-title' });
903
959
  const body = createElement('div', { class: 'an-size-popover-body' });
904
- const inputEl = createElement('input', {
960
+ const inputEl = /** @type {HTMLInputElement} */ (createElement('input', {
905
961
  type: 'number', class: 'an-size-input', min: '1', max: '2000', step: '1',
906
- });
962
+ }));
907
963
  const unitEl = createElement('span', { class: 'an-size-unit' }, ['px']);
908
964
  body.appendChild(inputEl);
909
965
  body.appendChild(unitEl);
@@ -925,20 +981,22 @@ export class TableTooltip {
925
981
  this._sizeApply = null;
926
982
 
927
983
  const d1 = on(applyBtn, 'click', () => {
928
- const val = parseInt(this._sizeInputEl.value, 10);
984
+ const val = Number.parseInt(this._sizeInputEl.value, 10);
929
985
  if (val > 0 && typeof this._sizeApply === 'function') this._sizeApply(val);
930
986
  this._hideSizePopover();
931
987
  });
932
988
  const d2 = on(cancelBtn, 'click', () => this._hideSizePopover());
933
989
  const d3 = on(inputEl, 'keydown', (e) => {
934
- if (e.key === 'Enter') { e.preventDefault(); applyBtn.click(); }
935
- if (e.key === 'Escape') this._hideSizePopover();
990
+ const ke = /** @type {KeyboardEvent} */ (e);
991
+ if (ke.key === 'Enter') { e.preventDefault(); applyBtn.click(); }
992
+ if (ke.key === 'Escape') this._hideSizePopover();
936
993
  });
937
994
  const d4 = on(document, 'click', (e) => {
995
+ const et = /** @type {Node} */ (e.target);
938
996
  if (this._sizePopover &&
939
997
  this._sizePopover.style.display !== 'none' &&
940
- !this._sizePopover.contains(e.target) &&
941
- !this._el.contains(e.target)) {
998
+ !this._sizePopover.contains(et) &&
999
+ !this._el.contains(et)) {
942
1000
  this._hideSizePopover();
943
1001
  }
944
1002
  });
@@ -958,13 +1016,13 @@ export class TableTooltip {
958
1016
  if (!table) return;
959
1017
  const firstCell = table.querySelector('td, th');
960
1018
  const currentPx = firstCell
961
- ? (parseInt(firstCell.style.borderWidth, 10) ||
962
- parseInt(window.getComputedStyle(firstCell).borderWidth, 10) || 1)
1019
+ ? (Number.parseInt(firstCell.style.borderWidth, 10) ||
1020
+ Number.parseInt(globalThis.getComputedStyle(firstCell).borderWidth, 10) || 1)
963
1021
  : 1;
964
1022
  this._sizeTitleEl.textContent = this.context.locale.tooltips.table.tableBorderWidthPx;
965
1023
  this._sizeInputEl.min = '0';
966
1024
  this._sizeInputEl.max = '10';
967
- this._sizeInputEl.value = currentPx;
1025
+ this._sizeInputEl.value = String(currentPx);
968
1026
  this._sizeApply = (val) => {
969
1027
  const cells = Array.from(table.querySelectorAll('td, th'));
970
1028
  if (val === 0) {
@@ -1021,8 +1079,8 @@ export class TableTooltip {
1021
1079
  const ph = this._sizePopover.offsetHeight || 110;
1022
1080
  let left = tipRect.left;
1023
1081
  let top = tipRect.bottom + 6;
1024
- if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
1025
- if (top + ph > window.innerHeight - 8) top = tipRect.top - ph - 6;
1082
+ if (left + pw > globalThis.innerWidth - 8) left = globalThis.innerWidth - pw - 8;
1083
+ if (top + ph > globalThis.innerHeight - 8) top = tipRect.top - ph - 6;
1026
1084
  this._sizePopover.style.left = `${left}px`;
1027
1085
  this._sizePopover.style.top = `${top}px`;
1028
1086
  if (this._sizeInputEl) { this._sizeInputEl.focus(); this._sizeInputEl.select(); }
@@ -1033,4 +1091,106 @@ export class TableTooltip {
1033
1091
  if (this._sizePopover) this._sizePopover.style.display = 'none';
1034
1092
  this._sizeApply = null;
1035
1093
  }
1094
+
1095
+ // ---------------------------------------------------------------------------
1096
+ // Cell background shade popover
1097
+ // ---------------------------------------------------------------------------
1098
+
1099
+ _buildCellShadePopover() {
1100
+ const pop = createElement('div', { class: 'an-cell-shade-popover' });
1101
+ pop.style.display = 'none';
1102
+
1103
+ const title = createElement('div', { class: 'an-size-popover-title' });
1104
+ pop.appendChild(title);
1105
+ this._shadeTitleEl = title;
1106
+
1107
+ // 24-color palette — reuse existing CSS classes
1108
+ const palette = createElement('div', { class: 'an-context-color-palette' });
1109
+ SHADE_PRESETS.forEach((color) => {
1110
+ const sw = createElement('div', { class: 'an-context-color-swatch', title: color });
1111
+ sw.style.background = color;
1112
+ this._disposers.push(on(sw, 'click', (e) => {
1113
+ e.stopPropagation();
1114
+ this._applyCellShade(color);
1115
+ }));
1116
+ palette.appendChild(sw);
1117
+ });
1118
+ pop.appendChild(palette);
1119
+
1120
+ // "No shading" row — clears background color
1121
+ const noShadeRow = createElement('div', { class: 'an-context-color-custom' });
1122
+ const noShadeBtn = createElement('button', { type: 'button', class: 'an-shade-no-color' });
1123
+ this._disposers.push(on(noShadeBtn, 'click', () => this._applyCellShade('')));
1124
+ noShadeRow.appendChild(noShadeBtn);
1125
+ pop.appendChild(noShadeRow);
1126
+ this._shadeNoBtn = noShadeBtn;
1127
+
1128
+ // Custom color input
1129
+ const customRow = createElement('div', { class: 'an-context-color-custom' });
1130
+ const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', class: 'an-shade-color-input', value: '#ffffff' }));
1131
+ const customLabel = createElement('span');
1132
+ customLabel.textContent = 'Custom…';
1133
+ this._disposers.push(on(colorInput, 'change', () => this._applyCellShade(colorInput.value)));
1134
+ customRow.appendChild(colorInput);
1135
+ customRow.appendChild(customLabel);
1136
+ pop.appendChild(customRow);
1137
+
1138
+ // Prevent mousedown from collapsing editor selection; keep tooltip alive while hovering
1139
+ this._disposers.push(
1140
+ on(pop, 'mousedown', (e) => e.preventDefault()),
1141
+ on(pop, 'mouseenter', () => this._clearTimers()),
1142
+ on(pop, 'mouseleave', () => this._scheduleHide()),
1143
+ );
1144
+
1145
+ // Close on outside click
1146
+ this._disposers.push(on(document, 'click', (e) => {
1147
+ const et = /** @type {Node} */ (e.target);
1148
+ if (this._shadePopover &&
1149
+ this._shadePopover.style.display !== 'none' &&
1150
+ !this._shadePopover.contains(et) &&
1151
+ !this._el?.contains(et)) {
1152
+ this._hideCellShadePopover();
1153
+ }
1154
+ }));
1155
+
1156
+ return pop;
1157
+ }
1158
+
1159
+ _openCellShadePopover() {
1160
+ if (!this._shadePopover) return;
1161
+ const L = this.context.locale.tooltips.table;
1162
+ if (this._shadeTitleEl) this._shadeTitleEl.textContent = L.cellBackground;
1163
+ if (this._shadeNoBtn) this._shadeNoBtn.textContent = L.noShading;
1164
+
1165
+ this._shadePopover.style.display = 'block';
1166
+ requestAnimationFrame(() => {
1167
+ if (!this._shadePopover || !this._el) return;
1168
+ const pw = this._shadePopover.offsetWidth || 170;
1169
+ const ph = this._shadePopover.offsetHeight || 120;
1170
+ const tipRect = this._el.getBoundingClientRect();
1171
+ let left = tipRect.left;
1172
+ let top = tipRect.bottom + 6;
1173
+ if (left + pw > globalThis.innerWidth - 8) left = globalThis.innerWidth - pw - 8;
1174
+ if (top + ph > globalThis.innerHeight - 8) top = tipRect.top - ph - 6;
1175
+ this._shadePopover.style.left = `${Math.max(8, left)}px`;
1176
+ this._shadePopover.style.top = `${Math.max(8, top)}px`;
1177
+ });
1178
+ }
1179
+
1180
+ _hideCellShadePopover() {
1181
+ if (this._shadePopover) this._shadePopover.style.display = 'none';
1182
+ }
1183
+
1184
+ _applyCellShade(color) {
1185
+ const cells = this._selectMode ? this._selectedCells : [this._getCell()];
1186
+ cells.forEach((cell) => {
1187
+ if (cell) cell.style.backgroundColor = color;
1188
+ });
1189
+ // Update the color strip on the shade button to reflect the applied color
1190
+ if (this._shadeColorStrip) {
1191
+ this._shadeColorStrip.style.background = color || 'transparent';
1192
+ }
1193
+ this._hideCellShadePopover();
1194
+ this.context.invoke('editor.afterCommand');
1195
+ }
1036
1196
  }