autumnnote 1.5.0 → 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 +6 -4
  2. package/dist/autumnnote.css +324 -2
  3. package/dist/autumnnote.es.js +638 -227
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +637 -226
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +1 -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 +3 -2
  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
@@ -39,24 +39,28 @@ export class ImageTooltip {
39
39
  this._disposers.push(
40
40
  on(editable, 'mouseover', (e) => {
41
41
  if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
42
- const img = e.target.closest('img');
42
+ const img = /** @type {Element} */ (e.target)?.closest('img');
43
43
  // Skip when the image is inside a link — LinkTooltip takes priority there
44
44
  if (img && editable.contains(img) && !img.closest('a[href]')) {
45
45
  this._scheduleShow(img);
46
46
  }
47
47
  }, { passive: true }),
48
48
  on(editable, 'mouseout', (e) => {
49
- const to = e.relatedTarget;
49
+ const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
50
50
  if (!to || (!editable.contains(to) && !this._el.contains(to))) {
51
51
  this._scheduleHide();
52
52
  }
53
53
  }, { passive: true }),
54
54
  // Hide when image is deselected by clicking elsewhere
55
55
  on(document, 'click', (e) => {
56
- if (this._activeImg && !this._activeImg.contains(e.target) && !this._el.contains(e.target)) {
56
+ const et = /** @type {Node} */ (e.target);
57
+ if (this._activeImg && !this._activeImg.contains(et) && !this._el.contains(et)) {
57
58
  this._hide();
58
59
  }
59
60
  }),
61
+ // Hide when the page scrolls or resizes — the tooltip position becomes stale
62
+ on(window, 'scroll', () => this._hide(), { passive: true }),
63
+ on(window, 'resize', () => this._hide(), { passive: true }),
60
64
  );
61
65
 
62
66
  return this;
@@ -181,7 +185,7 @@ export class ImageTooltip {
181
185
  this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY);
182
186
  }
183
187
 
184
- _show(img) {
188
+ _show(_img) {
185
189
  this._el.style.display = 'flex';
186
190
  // Defer positioning: offsetWidth on a newly-visible element forces layout
187
191
  requestAnimationFrame(() => {
@@ -3,7 +3,7 @@
3
3
  * Inspired by Summernote's LinkDialog — rewritten without jQuery
4
4
  */
5
5
 
6
- import { createElement, on, trapFocus } from '../core/dom.js';
6
+ import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
7
7
  import { withSavedRange } from '../core/range.js';
8
8
 
9
9
  export class LinkDialog {
@@ -63,42 +63,47 @@ export class LinkDialog {
63
63
  const overlay = createElement('div', { class: 'an-dialog-overlay', role: 'dialog', 'aria-modal': 'true', 'aria-label': L.ariaLabel });
64
64
  const box = createElement('div', { class: 'an-dialog-box' });
65
65
 
66
+ const header = createElement('div', { class: 'an-dialog-header' });
67
+ const iconEl = createElement('span', { class: 'an-dialog-icon' });
68
+ 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>`;
66
69
  const title = createElement('h3', { class: 'an-dialog-title' });
67
70
  title.textContent = L.title;
71
+ header.appendChild(iconEl);
72
+ header.appendChild(title);
68
73
 
69
74
  // URL field
70
75
  const urlLabel = createElement('label', { class: 'an-label' });
71
76
  urlLabel.textContent = L.url;
72
- const urlInput = createElement('input', {
77
+ const urlInput = /** @type {HTMLInputElement} */ (createElement('input', {
73
78
  type: 'url',
74
79
  class: 'an-input',
75
80
  placeholder: L.urlPlaceholder,
76
81
  id: 'an-link-url',
77
82
  name: 'url',
78
83
  autocomplete: 'off',
79
- });
84
+ }));
80
85
  this._urlInput = urlInput;
81
86
 
82
87
  // Text field
83
88
  const textLabel = createElement('label', { class: 'an-label' });
84
89
  textLabel.textContent = L.displayText;
85
- const textInput = createElement('input', {
90
+ const textInput = /** @type {HTMLInputElement} */ (createElement('input', {
86
91
  type: 'text',
87
92
  class: 'an-input',
88
93
  placeholder: L.textPlaceholder,
89
94
  id: 'an-link-text',
90
95
  name: 'linkText',
91
96
  autocomplete: 'off',
92
- });
97
+ }));
93
98
  this._textInput = textInput;
94
99
 
95
100
  // Open in new tab
96
101
  const tabLabel = createElement('label', { class: 'an-label an-label-inline' });
97
- const tabCheckbox = createElement('input', {
102
+ const tabCheckbox = /** @type {HTMLInputElement} */ (createElement('input', {
98
103
  type: 'checkbox',
99
104
  id: 'an-link-newtab',
100
105
  name: 'openInNewTab',
101
- });
106
+ }));
102
107
  this._tabCheckbox = tabCheckbox;
103
108
  tabLabel.appendChild(tabCheckbox);
104
109
  tabLabel.appendChild(document.createTextNode(' ' + L.openInNewTab));
@@ -112,15 +117,16 @@ export class LinkDialog {
112
117
  btnRow.appendChild(insertBtn);
113
118
  btnRow.appendChild(cancelBtn);
114
119
 
115
- box.append(title, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
120
+ box.append(header, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
116
121
  overlay.appendChild(box);
122
+ makeDraggable(header, box);
117
123
 
118
124
  // Events
119
125
  const d1 = on(insertBtn, 'click', () => this._onInsert());
120
126
  const d2 = on(cancelBtn, 'click', () => this._close());
121
127
  const d3 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
122
- const d4 = on(urlInput, 'keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this._onInsert(); } });
123
- const d5 = on(textInput, 'keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this._onInsert(); } });
128
+ const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
129
+ const d5 = on(textInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
124
130
  this._disposers.push(d1, d2, d3, d4, d5);
125
131
 
126
132
  return overlay;
@@ -142,9 +148,9 @@ export class LinkDialog {
142
148
  }
143
149
  }
144
150
  if (anchor) {
145
- this._urlInput.value = anchor.getAttribute('href') || '';
151
+ this._urlInput.value = /** @type {Element} */ (anchor).getAttribute('href') || '';
146
152
  this._textInput.value = anchor.textContent || '';
147
- this._tabCheckbox.checked = anchor.getAttribute('target') === '_blank';
153
+ this._tabCheckbox.checked = /** @type {Element} */ (anchor).getAttribute('target') === '_blank';
148
154
  } else {
149
155
  this._urlInput.value = '';
150
156
  this._textInput.value = sel ? sel.toString() : '';
@@ -33,18 +33,21 @@ export class LinkTooltip {
33
33
  this._disposers.push(
34
34
  // Detect when pointer enters a link
35
35
  on(editable, 'mouseover', (e) => {
36
- const anchor = e.target.closest('a[href]');
36
+ const anchor = /** @type {Element} */ (e.target)?.closest('a[href]');
37
37
  if (anchor && editable.contains(anchor)) {
38
38
  this._scheduleShow(anchor);
39
39
  }
40
40
  }),
41
41
  // Detect when pointer leaves the editable area entirely
42
42
  on(editable, 'mouseout', (e) => {
43
- const to = e.relatedTarget;
43
+ const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
44
44
  if (!to || (!editable.contains(to) && !this._el.contains(to))) {
45
45
  this._scheduleHide();
46
46
  }
47
47
  }),
48
+ // Hide when the page scrolls or resizes — the tooltip position becomes stale
49
+ on(window, 'scroll', () => this._hide(), { passive: true }),
50
+ on(window, 'resize', () => this._hide(), { passive: true }),
48
51
  );
49
52
 
50
53
  return this;
@@ -96,11 +96,11 @@ export class Mention {
96
96
  // Event delegation: single listeners on the container instead of per-item
97
97
  el.addEventListener('mousedown', (e) => e.preventDefault());
98
98
  el.addEventListener('click', (e) => {
99
- const item = e.target.closest('.an-mention-item');
99
+ const item = /** @type {HTMLElement} */ (/** @type {Element} */ (e.target)?.closest('.an-mention-item'));
100
100
  if (item) this._select(+item.dataset.index);
101
101
  });
102
102
  el.addEventListener('mousemove', (e) => {
103
- const item = e.target.closest('.an-mention-item');
103
+ const item = /** @type {HTMLElement} */ (/** @type {Element} */ (e.target)?.closest('.an-mention-item'));
104
104
  if (item) this._highlightItem(+item.dataset.index);
105
105
  });
106
106
 
@@ -119,7 +119,7 @@ export class Mention {
119
119
  const li = document.createElement('div');
120
120
  li.className = 'an-mention-item';
121
121
  li.setAttribute('role', 'option');
122
- li.dataset.index = i;
122
+ li.dataset.index = String(i);
123
123
  if (item.avatar) {
124
124
  const img = document.createElement('img');
125
125
  img.src = item.avatar;
@@ -136,7 +136,7 @@ export class Statusbar {
136
136
  const MIN_EDITABLE = 40;
137
137
  const fixedH = Array.from(containerEl.children)
138
138
  .filter(child => !child.classList.contains('an-editable'))
139
- .reduce((sum, child) => sum + child.offsetHeight, 0);
139
+ .reduce((sum, child) => sum + /** @type {HTMLElement} */ (child).offsetHeight, 0);
140
140
  const trueMin = Math.max(this.options.minHeight || 100, fixedH + MIN_EDITABLE);
141
141
  containerEl.style.height = `${Math.max(trueMin, startH + delta)}px`;
142
142
  };
@@ -207,7 +207,7 @@ export class Statusbar {
207
207
  _bindContentEvents() {
208
208
  const editable = this.context.layoutInfo.editable;
209
209
  const updateDebounced = debounce(() => this.update(), 200);
210
- const d = on(editable, 'input', updateDebounced);
210
+ const d = on(editable, 'input', /** @type {EventListener} */ (updateDebounced));
211
211
  this._disposers.push(d);
212
212
  }
213
213
 
@@ -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,16 +196,19 @@ 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) &&
@@ -201,14 +218,20 @@ export class TableTooltip {
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 && 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 && 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(window, 'scroll', () => this._hide(), { passive: true }),
234
+ on(window, 'resize', () => this._hide(), { passive: true }),
212
235
  );
213
236
 
214
237
  this._initResize();
@@ -369,6 +392,10 @@ export class TableTooltip {
369
392
  this._sizePopover.parentNode.removeChild(this._sizePopover);
370
393
  }
371
394
  this._sizePopover = null;
395
+ if (this._shadePopover && this._shadePopover.parentNode) {
396
+ this._shadePopover.parentNode.removeChild(this._shadePopover);
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 && cell.style.backgroundColor) || 'transparent';
557
+ }
558
+
503
559
  _hide() {
504
560
  this._el.style.display = 'none';
505
561
  this._activeTable = null;
@@ -551,7 +607,7 @@ export class TableTooltip {
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');
610
+ const cellFromSel = container && /** @type {Element} */ (container).closest('td, th');
555
611
  if (cellFromSel && this._activeTable && this._activeTable.contains(cellFromSel)) {
556
612
  return cellFromSel;
557
613
  }
@@ -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);
@@ -931,14 +987,16 @@ export class TableTooltip {
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
  });
@@ -964,7 +1022,7 @@ export class TableTooltip {
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) {
@@ -1033,4 +1091,108 @@ 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
1139
+ this._disposers.push(on(pop, 'mousedown', (e) => e.preventDefault()));
1140
+
1141
+ // Keep tooltip alive while hovering popover
1142
+ this._disposers.push(
1143
+ on(pop, 'mouseenter', () => this._clearTimers()),
1144
+ on(pop, 'mouseleave', () => this._scheduleHide()),
1145
+ );
1146
+
1147
+ // Close on outside click
1148
+ this._disposers.push(on(document, 'click', (e) => {
1149
+ const et = /** @type {Node} */ (e.target);
1150
+ if (this._shadePopover &&
1151
+ this._shadePopover.style.display !== 'none' &&
1152
+ !this._shadePopover.contains(et) &&
1153
+ !(this._el && this._el.contains(et))) {
1154
+ this._hideCellShadePopover();
1155
+ }
1156
+ }));
1157
+
1158
+ return pop;
1159
+ }
1160
+
1161
+ _openCellShadePopover() {
1162
+ if (!this._shadePopover) return;
1163
+ const L = this.context.locale.tooltips.table;
1164
+ if (this._shadeTitleEl) this._shadeTitleEl.textContent = L.cellBackground;
1165
+ if (this._shadeNoBtn) this._shadeNoBtn.textContent = L.noShading;
1166
+
1167
+ this._shadePopover.style.display = 'block';
1168
+ requestAnimationFrame(() => {
1169
+ if (!this._shadePopover || !this._el) return;
1170
+ const pw = this._shadePopover.offsetWidth || 170;
1171
+ const ph = this._shadePopover.offsetHeight || 120;
1172
+ const tipRect = this._el.getBoundingClientRect();
1173
+ let left = tipRect.left;
1174
+ let top = tipRect.bottom + 6;
1175
+ if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
1176
+ if (top + ph > window.innerHeight - 8) top = tipRect.top - ph - 6;
1177
+ this._shadePopover.style.left = `${Math.max(8, left)}px`;
1178
+ this._shadePopover.style.top = `${Math.max(8, top)}px`;
1179
+ });
1180
+ }
1181
+
1182
+ _hideCellShadePopover() {
1183
+ if (this._shadePopover) this._shadePopover.style.display = 'none';
1184
+ }
1185
+
1186
+ _applyCellShade(color) {
1187
+ const cells = this._selectMode ? this._selectedCells : [this._getCell()];
1188
+ cells.forEach((cell) => {
1189
+ if (cell) cell.style.backgroundColor = color;
1190
+ });
1191
+ // Update the color strip on the shade button to reflect the applied color
1192
+ if (this._shadeColorStrip) {
1193
+ this._shadeColorStrip.style.background = color || 'transparent';
1194
+ }
1195
+ this._hideCellShadePopover();
1196
+ this.context.invoke('editor.afterCommand');
1197
+ }
1036
1198
  }
@@ -286,7 +286,7 @@ export class Toolbar {
286
286
  });
287
287
 
288
288
  const d2 = on(grid, 'mouseover', (e) => {
289
- const cell = e.target.closest('.an-table-cell');
289
+ const cell = /** @type {Element} */ (e.target)?.closest('.an-table-cell');
290
290
  if (!cell) return;
291
291
  setHighlight(+cell.getAttribute('data-row'), +cell.getAttribute('data-col'));
292
292
  });
@@ -294,7 +294,7 @@ export class Toolbar {
294
294
  const d3 = on(grid, 'mouseleave', () => setHighlight(0, 0));
295
295
 
296
296
  const d4 = on(grid, 'click', (e) => {
297
- const cell = e.target.closest('.an-table-cell');
297
+ const cell = /** @type {Element} */ (e.target)?.closest('.an-table-cell');
298
298
  if (!cell) return;
299
299
  const rows = +cell.getAttribute('data-row');
300
300
  const cols = +cell.getAttribute('data-col');
@@ -313,7 +313,7 @@ export class Toolbar {
313
313
 
314
314
  wrap.appendChild(btn);
315
315
  document.body.appendChild(popup);
316
- return wrap;
316
+ return /** @type {HTMLDivElement} */ (wrap);
317
317
  }
318
318
 
319
319
  /**
@@ -384,7 +384,7 @@ export class Toolbar {
384
384
  });
385
385
 
386
386
  const customRow = createElement('div', { class: 'an-color-custom' });
387
- const colorInput = createElement('input', { type: 'color', value: currentColor, title: this.context.locale.toolbar.customColor || 'Custom color' });
387
+ const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', value: currentColor, title: this.context.locale.toolbar.customColor || 'Custom color' }));
388
388
  const customLabel = createElement('span', {}, [this.context.locale.toolbar.customColor || 'Custom color']);
389
389
  customRow.appendChild(colorInput);
390
390
  customRow.appendChild(customLabel);
@@ -472,17 +472,17 @@ export class Toolbar {
472
472
  });
473
473
 
474
474
  const d3b = on(swatches, 'click', (e) => {
475
- const sw = e.target.closest('.an-color-swatch');
476
- if (sw) applyColor(sw.dataset.color);
475
+ const sw = /** @type {Element} */ (e.target)?.closest('.an-color-swatch');
476
+ if (sw) applyColor(/** @type {HTMLElement} */ (sw).dataset.color);
477
477
  });
478
478
 
479
479
  const d4 = on(colorInput, 'change', (e) => {
480
- applyColor(e.target.value);
480
+ applyColor(/** @type {HTMLInputElement} */ (e.target).value);
481
481
  });
482
482
 
483
483
  const d5 = on(document, 'click', (e) => {
484
484
  // popup is in document.body, not inside wrap — check both
485
- if (isOpen && !wrap.contains(e.target) && !popup.contains(e.target)) closePopup();
485
+ if (isOpen && !wrap.contains(/** @type {Node} */ (e.target)) && !popup.contains(/** @type {Node} */ (e.target))) closePopup();
486
486
  });
487
487
 
488
488
  const d6 = on(popup, 'click', (e) => e.stopPropagation());
@@ -513,7 +513,7 @@ export class Toolbar {
513
513
  wrap.appendChild(applyBtn);
514
514
  wrap.appendChild(arrowBtn);
515
515
  document.body.appendChild(popup);
516
- return wrap;
516
+ return /** @type {HTMLDivElement} */ (wrap);
517
517
  }
518
518
 
519
519
  /**
@@ -583,7 +583,7 @@ export class Toolbar {
583
583
  });
584
584
 
585
585
  this._disposers.push(dMousedown, disposer);
586
- return select;
586
+ return /** @type {HTMLSelectElement} */ (select);
587
587
  }
588
588
 
589
589
  /**
@@ -638,7 +638,7 @@ export class Toolbar {
638
638
  });
639
639
 
640
640
  this._disposers.push(disposer);
641
- return btn;
641
+ return /** @type {HTMLButtonElement} */ (btn);
642
642
  }
643
643
 
644
644
  // ---------------------------------------------------------------------------
@@ -659,7 +659,7 @@ export class Toolbar {
659
659
  // count as "the host page loaded FA" for toolbar icon rendering purposes.
660
660
  const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
661
661
  .filter((l) => l.id !== 'an-fontawesome-css')
662
- .map((l) => l.href || '').join(' ');
662
+ .map((l) => /** @type {HTMLLinkElement} */ (l).href || '').join(' ');
663
663
  _faPageLevelReady = /fontawesome|font-awesome|use\.fontawesome|all\.css/.test(links);
664
664
  return _faPageLevelReady;
665
665
  }
@@ -689,7 +689,7 @@ export class Toolbar {
689
689
  btn.classList.toggle('active', !!def.isActive(this.context));
690
690
  }
691
691
  if (def && typeof def.isDisabled === 'function') {
692
- btn.disabled = !!def.isDisabled(this.context);
692
+ /** @type {HTMLButtonElement} */ (btn).disabled = !!def.isDisabled(this.context);
693
693
  }
694
694
  });
695
695
 
@@ -706,10 +706,11 @@ export class Toolbar {
706
706
  || '';
707
707
  }
708
708
  // Try to match against available options (case-insensitive)
709
- const matched = Array.from(select.options).find(
709
+ const sel = /** @type {HTMLSelectElement} */ (select);
710
+ const matched = Array.from(sel.options).find(
710
711
  (opt) => opt.value && opt.value.toLowerCase() === raw.toLowerCase()
711
712
  );
712
- select.value = matched ? matched.value : '';
713
+ sel.value = matched ? matched.value : '';
713
714
  });
714
715
  }
715
716