autumnnote 1.6.2 → 1.6.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "1.6.2",
3
+ "version": "1.6.5",
4
4
  "description": "WYSIWYG rich-text editor built with vanilla JavaScript — zero dependencies, no jQuery. Dark mode, @mention, markdown shortcuts, bubble toolbar. React and Vue 3 wrappers included.",
5
5
  "main": "dist/autumnnote.umd.js",
6
6
  "module": "dist/autumnnote.es.js",
@@ -13,7 +13,7 @@
13
13
  * @returns {string|null}
14
14
  */
15
15
  export function detectLang(code) {
16
- if (!code || !code.trim()) return null;
16
+ if (!code?.trim()) return null;
17
17
  const s = code.trim();
18
18
 
19
19
  // ── TypeScript ─────────────────────────────────────────────────────────────
@@ -305,11 +305,9 @@ export function trapFocus(container, onEscape) {
305
305
  e.preventDefault();
306
306
  /** @type {HTMLElement} */ (last).focus();
307
307
  }
308
- } else {
309
- if (document.activeElement === last) {
310
- e.preventDefault();
311
- /** @type {HTMLElement} */ (first).focus();
312
- }
308
+ } else if (document.activeElement === last) {
309
+ e.preventDefault();
310
+ /** @type {HTMLElement} */ (first).focus();
313
311
  }
314
312
  };
315
313
 
@@ -41,7 +41,7 @@ export const italic = () => execCommand('italic');
41
41
  */
42
42
  export function underline() {
43
43
  const sel = globalThis.getSelection();
44
- if (!sel || !sel.rangeCount) return;
44
+ if (!sel?.rangeCount) return;
45
45
  let container = sel.getRangeAt(0).commonAncestorContainer;
46
46
  if (container.nodeType === 3) container = container.parentElement;
47
47
  // Check if we're inside a <u> (DOM truth), to guard against unreliable queryCommandState
@@ -65,7 +65,7 @@ export function underline() {
65
65
  */
66
66
  export function strikethrough() {
67
67
  const sel = globalThis.getSelection();
68
- if (!sel || !sel.rangeCount) return;
68
+ if (!sel?.rangeCount) return;
69
69
  // Use startContainer for consistent detection across collapsed and range
70
70
  // selections — commonAncestorContainer can miss ancestor <s>/<strike> tags
71
71
  // when the selection spans across nested inline elements.
@@ -120,7 +120,7 @@ export const fontName = (name) => execCommand('fontName', name);
120
120
  */
121
121
  export function fontSize(size, editable = document) {
122
122
  const sel = globalThis.getSelection();
123
- const wasCollapsed = !sel || !sel.rangeCount || sel.getRangeAt(0).collapsed;
123
+ const wasCollapsed = !sel?.rangeCount || sel.getRangeAt(0).collapsed;
124
124
 
125
125
  // B-I-3/4: For a collapsed (caret) selection the browser's execCommand
126
126
  // 'fontSize' leaves an internal "pending" state of size-7 (=48px) instead of
@@ -223,7 +223,7 @@ export const indent = () => execCommand('indent');
223
223
  */
224
224
  export function outdent() {
225
225
  const sel = globalThis.getSelection();
226
- if (sel && sel.rangeCount) {
226
+ if (sel?.rangeCount) {
227
227
  let container = sel.getRangeAt(0).commonAncestorContainer;
228
228
  if (container.nodeType === 3) container = container.parentElement;
229
229
  const checkLi = /** @type {Element|null} */ (container)?.closest('.an-checklist li');
@@ -286,7 +286,7 @@ function _checklistItemToP(checkLi) {
286
286
  try {
287
287
  const nr = document.createRange();
288
288
  const firstChild = p.firstChild;
289
- nr.setStart(firstChild && firstChild.nodeType === 3 ? firstChild : p, 0);
289
+ nr.setStart(firstChild?.nodeType === 3 ? firstChild : p, 0);
290
290
  nr.collapse(true);
291
291
  const s = globalThis.getSelection();
292
292
  if (s) { s.removeAllRanges(); s.addRange(nr); }
@@ -406,7 +406,7 @@ export function currentStyle(editable) {
406
406
  */
407
407
  export function toggleInlineCode(_editable) {
408
408
  const sel = globalThis.getSelection();
409
- if (!sel || !sel.rangeCount) return;
409
+ if (!sel?.rangeCount) return;
410
410
  const range = sel.getRangeAt(0);
411
411
  let container = range.commonAncestorContainer;
412
412
  if (container.nodeType === 3) container = container.parentElement;
@@ -477,7 +477,7 @@ export function toggleInlineCode(_editable) {
477
477
  */
478
478
  export function isInlineCode() {
479
479
  const sel = globalThis.getSelection();
480
- if (!sel || !sel.rangeCount) return false;
480
+ if (!sel?.rangeCount) return false;
481
481
  let sc = sel.getRangeAt(0).startContainer;
482
482
  if (sc.nodeType === 3) sc = sc.parentElement;
483
483
  const code = /** @type {Element|null} */ (sc)?.closest('code');
@@ -504,7 +504,7 @@ export function isInlineCode() {
504
504
  */
505
505
  export function toggleChecklist() {
506
506
  const sel = globalThis.getSelection();
507
- if (!sel || !sel.rangeCount) return;
507
+ if (!sel?.rangeCount) return;
508
508
  const range = sel.getRangeAt(0);
509
509
  let container = range.commonAncestorContainer;
510
510
  if (container.nodeType === 3) container = container.parentElement;
@@ -552,7 +552,7 @@ export function toggleChecklist() {
552
552
  // and convert it into a single checklist item.
553
553
  const BLOCK_TAGS = new Set(['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI']);
554
554
  let block = /** @type {Element|null} */ (container);
555
- while (block && block.parentNode && !BLOCK_TAGS.has(block.tagName)) {
555
+ while (block?.parentNode && !BLOCK_TAGS.has(block.tagName)) {
556
556
  block = /** @type {Element|null} */ (block.parentNode);
557
557
  }
558
558
  // Fallback: if no block element found (e.g. cursor directly in editable root), use the
@@ -674,7 +674,7 @@ export function toggleChecklist() {
674
674
  */
675
675
  export function isInChecklist() {
676
676
  const sel = globalThis.getSelection();
677
- if (!sel || !sel.rangeCount) return false;
677
+ if (!sel?.rangeCount) return false;
678
678
  let container = sel.getRangeAt(0).commonAncestorContainer;
679
679
  if (container.nodeType === 3) container = container.parentElement;
680
680
  return !!(/** @type {Element|null} */ (container)?.closest('.an-checklist li'));
@@ -68,7 +68,7 @@ export function handleKeydown(event, editable, options = {}) {
68
68
  // (not a text node), causing the next Backspace to miss Cases A/B and
69
69
  // requiring an extra keypress when two icons are adjacent.
70
70
  const nr = document.createRange();
71
- if (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
71
+ if (prevNode?.nodeType === Node.TEXT_NODE) {
72
72
  nr.setStart(prevNode, prevNode.textContent.length);
73
73
  } else if (prevNode) {
74
74
  nr.setStartAfter(prevNode);
@@ -168,7 +168,7 @@ export function handleKeydown(event, editable, options = {}) {
168
168
  if (isFAIcon(next)) {
169
169
  const after = next.nextSibling;
170
170
  event.preventDefault();
171
- if (after && after.nodeType === Node.TEXT_NODE) {
171
+ if (after?.nodeType === Node.TEXT_NODE) {
172
172
  const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
173
173
  return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
174
174
  }
@@ -178,7 +178,7 @@ export function handleKeydown(event, editable, options = {}) {
178
178
  const icon = next.nextSibling;
179
179
  const after = icon.nextSibling;
180
180
  event.preventDefault();
181
- if (after && after.nodeType === Node.TEXT_NODE) {
181
+ if (after?.nodeType === Node.TEXT_NODE) {
182
182
  const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
183
183
  return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
184
184
  }
@@ -341,11 +341,11 @@ export function handleKeydown(event, editable, options = {}) {
341
341
  // may normalise it to element-level, placing the caret before the
342
342
  // absolutely-positioned checkbox. \u200B is stripped by getHTML().
343
343
  let cursorNode = newLi.childNodes[1]; // first child after checkbox
344
- if (!cursorNode || cursorNode.nodeType !== Node.TEXT_NODE) {
344
+ if (cursorNode?.nodeType !== Node.TEXT_NODE) {
345
345
  cursorNode = document.createTextNode('\u200B');
346
346
  newLi.appendChild(cursorNode);
347
347
  }
348
- checkLi.insertAdjacentElement('afterend', newLi);
348
+ checkLi.after(newLi);
349
349
 
350
350
  const nr = document.createRange();
351
351
  nr.setStart(cursorNode, 0);
package/src/js/index.js CHANGED
@@ -141,7 +141,7 @@ const AutumnNote = {
141
141
  registerButton(btnDef) { registerButton(btnDef); return this; },
142
142
 
143
143
  /** Library version */
144
- version: '1.6.2',
144
+ version: '1.6.5',
145
145
  };
146
146
 
147
147
  // ---------------------------------------------------------------------------
@@ -51,7 +51,7 @@ export class BaseDialog {
51
51
  if (this._dialog) {
52
52
  this._dialog.style.display = 'flex';
53
53
  this._removeTrap = trapFocus(this._dialog, () => this._close());
54
- setTimeout(() => this._firstInput && this._firstInput.focus(), 50);
54
+ setTimeout(() => this._firstInput?.focus(), 50);
55
55
  }
56
56
  }
57
57
 
@@ -389,7 +389,7 @@ export class BubbleToolbar {
389
389
  if (!this._btnCache) return;
390
390
  this._btnCache.forEach((btn) => {
391
391
  const activeFn = _ACTIVE[/** @type {HTMLElement} */ (btn).dataset.name];
392
- btn.classList.toggle('an-active', !!(activeFn && activeFn()));
392
+ btn.classList.toggle('an-active', !!(activeFn?.()));
393
393
  });
394
394
  }
395
395
 
@@ -397,7 +397,7 @@ export class BubbleToolbar {
397
397
  _syncColorStrips() {
398
398
  if (!this._el) return;
399
399
  const sel = globalThis.getSelection();
400
- if (!sel || !sel.rangeCount) return;
400
+ if (!sel?.rangeCount) return;
401
401
  let node = sel.getRangeAt(0).startContainer;
402
402
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
403
403
  if (!node) return;
@@ -102,10 +102,10 @@ export const underlineBtn = btn('underline', 'underline', 'Underline (Ctrl+U)',
102
102
  // consistent behaviour across both collapsed and range selections.
103
103
  if (document.queryCommandState('underline')) return true;
104
104
  const sel = globalThis.getSelection();
105
- if (!sel || !sel.rangeCount) return false;
105
+ if (!sel?.rangeCount) return false;
106
106
  let sc = sel.getRangeAt(0).startContainer;
107
107
  if (sc.nodeType === 3) sc = sc.parentElement;
108
- return !!(sc && /** @type {Element} */ (sc).closest('u'));
108
+ return !!(/** @type {Element} */ (sc)?.closest('u'));
109
109
  });
110
110
  export const strikeBtn = btn('strikethrough', 'strikethrough', 'Strikethrough', () => Style.strikethrough(), () => document.queryCommandState('strikeThrough'));
111
111
  export const superscriptBtn = btn('superscript', 'superscript', 'Superscript', () => Style.superscript(), () => document.queryCommandState('superscript'));
@@ -180,11 +180,11 @@ export const fontSizeBtn = {
180
180
  getValue: (ctx) => {
181
181
  try {
182
182
  const sel = globalThis.getSelection();
183
- if (sel && sel.rangeCount) {
183
+ if (sel?.rangeCount) {
184
184
  let el = /** @type {Element|null} */ (sel.getRangeAt(0).startContainer);
185
- if (el && el.nodeType === 3) el = el.parentElement;
186
- while (el && el.nodeType === 1 && !/** @type {HTMLElement} */ (el).style.fontSize) el = el.parentElement;
187
- const size = (el && /** @type {HTMLElement} */ (el).style.fontSize) ? /** @type {HTMLElement} */ (el).style.fontSize : '';
185
+ if (el?.nodeType === 3) el = el.parentElement;
186
+ while (el?.nodeType === 1 && !/** @type {HTMLElement} */ (el).style.fontSize) el = el.parentElement;
187
+ const size = /** @type {HTMLElement} */ (el)?.style.fontSize || '';
188
188
  if (size) return size;
189
189
  }
190
190
  // Fallback: read the base font size from the editable element itself
@@ -281,10 +281,10 @@ export const lineHeightBtn = {
281
281
  getValue: () => {
282
282
  try {
283
283
  const sel = globalThis.getSelection();
284
- if (!sel || !sel.rangeCount) return '';
284
+ if (!sel?.rangeCount) return '';
285
285
  const BLOCKS = new Set(['P','DIV','H1','H2','H3','H4','H5','H6','LI','BLOCKQUOTE','PRE','TD','TH']);
286
286
  let el = /** @type {Element|null} */ (sel.getRangeAt(0).startContainer);
287
- if (el && el.nodeType === 3) el = el.parentElement;
287
+ if (el?.nodeType === 3) el = el.parentElement;
288
288
  while (el && !BLOCKS.has(/** @type {Element} */ (el).tagName)) el = el.parentElement;
289
289
  if (!el) return '';
290
290
  return /** @type {HTMLElement} */ (el).style.lineHeight || getComputedStyle(/** @type {Element} */ (el)).lineHeight || '';
@@ -86,7 +86,7 @@ export class Editor {
86
86
  // keyup : arrow-key navigation may land at <li>[0]; move to start-of-text.
87
87
  const fixChecklistCursor = (event) => {
88
88
  const sel = globalThis.getSelection();
89
- if (!sel || !sel.rangeCount) return;
89
+ if (!sel?.rangeCount) return;
90
90
  const r = sel.getRangeAt(0);
91
91
  if (!r.collapsed) return;
92
92
  const sc = r.startContainer;
@@ -102,7 +102,7 @@ export class Editor {
102
102
 
103
103
  // For mouse events: ask the browser where the pointer landed so the
104
104
  // cursor respects the actual click position inside the text.
105
- if (event && event.type === 'mouseup') {
105
+ if (event?.type === 'mouseup') {
106
106
  let caret = null;
107
107
  if (document.caretRangeFromPoint) {
108
108
  caret = document.caretRangeFromPoint(event.clientX, event.clientY);
@@ -181,7 +181,7 @@ export class Editor {
181
181
  let _compositionSupSub = null;
182
182
  const onCompositionStart = () => {
183
183
  const sel = globalThis.getSelection();
184
- if (!sel || !sel.rangeCount) { _compositionSupSub = null; return; }
184
+ if (!sel?.rangeCount) { _compositionSupSub = null; return; }
185
185
  let node = sel.getRangeAt(0).startContainer;
186
186
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
187
187
  if (node) {
@@ -196,7 +196,7 @@ export class Editor {
196
196
  _compositionSupSub = null;
197
197
  if (!tag) return;
198
198
  const sel = globalThis.getSelection();
199
- if (!sel || !sel.rangeCount) return;
199
+ if (!sel?.rangeCount) return;
200
200
  let node = sel.getRangeAt(0).startContainer;
201
201
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
202
202
  const el = /** @type {Element} */ (node);
@@ -3,8 +3,8 @@
3
3
  * Click an emoji to insert it directly at the caret — no extra "Insert" step.
4
4
  */
5
5
 
6
- import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
7
- import { withSavedRange } from '../core/range.js';
6
+ import { createElement, on, makeDraggable } from '../core/dom.js';
7
+ import { BaseDialog } from './BaseDialog.js';
8
8
 
9
9
  // ---------------------------------------------------------------------------
10
10
  // Emoji catalogue
@@ -500,37 +500,18 @@ const EMOJI_LIST = [
500
500
  // Dialog class
501
501
  // ---------------------------------------------------------------------------
502
502
 
503
- export class EmojiDialog {
504
- /**
505
- * @param {import('../Context.js').Context} context
506
- */
507
- constructor(context) {
508
- this.context = context;
509
- /** @type {HTMLElement|null} */
510
- this._dialog = null;
511
- this._disposers = [];
512
- this._savedRange = null;
513
- this._activeCat = 'all';
514
- }
503
+ export class EmojiDialog extends BaseDialog {
504
+ _activeCat = 'all';
515
505
 
516
506
  // ---------------------------------------------------------------------------
517
507
  // Lifecycle
518
508
  // ---------------------------------------------------------------------------
519
509
 
520
510
  initialize() {
521
- // Dialog grid is built lazily on first show() to avoid rendering ~500 DOM nodes at load time.
511
+ // Grid is built lazily in show() to avoid ~500 DOM nodes at load time.
522
512
  return this;
523
513
  }
524
514
 
525
- destroy() {
526
- this._disposers.forEach((d) => d());
527
- this._disposers = [];
528
- if (this._dialog && this._dialog.parentNode) {
529
- this._dialog.remove();
530
- }
531
- this._dialog = null;
532
- }
533
-
534
515
  // ---------------------------------------------------------------------------
535
516
  // Public API
536
517
  // ---------------------------------------------------------------------------
@@ -540,9 +521,7 @@ export class EmojiDialog {
540
521
  this._dialog = this._buildDialog();
541
522
  document.body.appendChild(this._dialog);
542
523
  }
543
- withSavedRange((range) => {
544
- this._savedRange = range;
545
- });
524
+ this._saveRange();
546
525
  this._activeCat = 'all';
547
526
  this._searchInput.value = '';
548
527
  this._updateCatTabs();
@@ -612,6 +591,7 @@ export class EmojiDialog {
612
591
  grid.appendChild(cell);
613
592
  });
614
593
  this._grid = grid;
594
+ this._firstInput = searchInput;
615
595
 
616
596
  // Cancel only — clicking an emoji inserts immediately
617
597
  const btnRow = createElement('div', { class: 'an-dialog-actions' });
@@ -724,21 +704,4 @@ export class EmojiDialog {
724
704
  this.context.invoke('editor.afterCommand');
725
705
  }
726
706
 
727
- // ---------------------------------------------------------------------------
728
- // Open / Close
729
- // ---------------------------------------------------------------------------
730
-
731
- _open() {
732
- if (this._dialog) {
733
- this._dialog.style.display = 'flex';
734
- this._removeTrap = trapFocus(this._dialog, () => this._close());
735
- setTimeout(() => this._searchInput?.focus(), 50);
736
- }
737
- }
738
-
739
- _close() {
740
- if (this._dialog) this._dialog.style.display = 'none';
741
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
742
- this._savedRange = null;
743
- }
744
707
  }
@@ -8,14 +8,13 @@
8
8
  */
9
9
 
10
10
  import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
11
+ import { BaseDialog } from './BaseDialog.js';
11
12
 
12
- export class FindReplace {
13
+ export class FindReplace extends BaseDialog {
13
14
  /** @param {import('../Context.js').Context} context */
14
15
  constructor(context) {
15
- this.context = context;
16
+ super(context);
16
17
 
17
- /** @type {HTMLElement|null} */
18
- this._dialog = null;
19
18
  /** @type {HTMLInputElement|null} */
20
19
  this._findInput = null;
21
20
  /** @type {HTMLInputElement|null} */
@@ -39,8 +38,6 @@ export class FindReplace {
39
38
  this._lastQuery = null;
40
39
  this._lastCaseSensitive = null;
41
40
 
42
- this._disposers = [];
43
- this._removeTrap = null;
44
41
  this._focusTimer = null;
45
42
  }
46
43
 
@@ -48,22 +45,11 @@ export class FindReplace {
48
45
  // Lifecycle
49
46
  // ---------------------------------------------------------------------------
50
47
 
51
- initialize() {
52
- this._dialog = this._buildDialog();
53
- document.body.appendChild(this._dialog);
54
- return this;
55
- }
56
-
57
48
  destroy() {
58
49
  clearTimeout(this._focusTimer);
59
50
  this._focusTimer = null;
60
51
  this._clearHighlights();
61
- this._disposers.forEach((d) => d());
62
- this._disposers = [];
63
- if (this._dialog && this._dialog.parentNode) {
64
- this._dialog.remove();
65
- }
66
- this._dialog = null;
52
+ super.destroy();
67
53
  }
68
54
 
69
55
  // ---------------------------------------------------------------------------
@@ -2,8 +2,8 @@
2
2
  * IconDialog.js - Browse and insert FontAwesome Free icons
3
3
  */
4
4
 
5
- import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
6
- import { withSavedRange } from '../core/range.js';
5
+ import { createElement, on, makeDraggable } from '../core/dom.js';
6
+ import { BaseDialog } from './BaseDialog.js';
7
7
 
8
8
  // ---------------------------------------------------------------------------
9
9
  // Icon catalogue — FA 6 Free Solid slug names, grouped by category
@@ -247,19 +247,9 @@ const ICON_LIST = [
247
247
  ['puzzle-piece', 'objects'],
248
248
  ];
249
249
 
250
- export class IconDialog {
251
- /**
252
- * @param {import('../Context.js').Context} context
253
- */
254
- constructor(context) {
255
- this.context = context;
256
- /** @type {HTMLElement|null} */
257
- this._dialog = null;
258
- this._disposers = [];
259
- this._savedRange = null;
260
- this._selectedIcon = null;
261
- this._activeCat = 'all';
262
- }
250
+ export class IconDialog extends BaseDialog {
251
+ _selectedIcon = null;
252
+ _activeCat = 'all';
263
253
 
264
254
  // ---------------------------------------------------------------------------
265
255
  // Lifecycle
@@ -267,7 +257,7 @@ export class IconDialog {
267
257
 
268
258
  initialize() {
269
259
  this._ensureFontAwesome();
270
- // Dialog grid is built lazily on first show() to avoid rendering ~250 icon cells at load time.
260
+ // Grid is built lazily in show() to avoid ~250 icon cells at load time.
271
261
  return this;
272
262
  }
273
263
 
@@ -294,13 +284,6 @@ export class IconDialog {
294
284
  document.head.appendChild(link);
295
285
  }
296
286
 
297
- destroy() {
298
- this._disposers.forEach((d) => d());
299
- this._disposers = [];
300
- this._dialog?.remove();
301
- this._dialog = null;
302
- }
303
-
304
287
  // ---------------------------------------------------------------------------
305
288
  // Public API
306
289
  // ---------------------------------------------------------------------------
@@ -310,9 +293,7 @@ export class IconDialog {
310
293
  this._dialog = this._buildDialog();
311
294
  document.body.appendChild(this._dialog);
312
295
  }
313
- withSavedRange((range) => {
314
- this._savedRange = range;
315
- });
296
+ this._saveRange();
316
297
  this._selectedIcon = null;
317
298
  this._activeCat = 'all';
318
299
  this._searchInput.value = '';
@@ -360,6 +341,7 @@ export class IconDialog {
360
341
  autocomplete: 'off',
361
342
  }));
362
343
  this._searchInput = searchInput;
344
+ this._firstInput = searchInput;
363
345
 
364
346
  // Category tabs
365
347
  const catBar = createElement('div', { class: 'an-icon-cats' });
@@ -598,7 +580,7 @@ export class IconDialog {
598
580
  // Keep a text anchor (\u200B) after the icon so ArrowLeft/ArrowRight and
599
581
  // caret placement at end-of-line stay stable across browsers.
600
582
  let caretTextNode = iconEl.nextSibling;
601
- if (!caretTextNode || caretTextNode.nodeType !== Node.TEXT_NODE) {
583
+ if (caretTextNode?.nodeType !== Node.TEXT_NODE) {
602
584
  caretTextNode = document.createTextNode('\u200B');
603
585
  iconEl.parentNode.insertBefore(caretTextNode, iconEl.nextSibling);
604
586
  } else if (!caretTextNode.textContent) {
@@ -629,22 +611,8 @@ export class IconDialog {
629
611
  this.context.invoke('editor.afterCommand');
630
612
  }
631
613
 
632
- // ---------------------------------------------------------------------------
633
- // Open / Close
634
- // ---------------------------------------------------------------------------
635
-
636
- _open() {
637
- if (this._dialog) {
638
- this._dialog.style.display = 'flex';
639
- this._removeTrap = trapFocus(this._dialog, () => this._close());
640
- setTimeout(() => this._searchInput?.focus(), 50);
641
- }
642
- }
643
-
644
614
  _close() {
645
- if (this._dialog) this._dialog.style.display = 'none';
646
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
647
- this._savedRange = null;
648
615
  this._selectedIcon = null;
616
+ super._close();
649
617
  }
650
618
  }
@@ -399,7 +399,7 @@ export class ImageCropOverlay {
399
399
  const startY = e.clientY;
400
400
  const orig = { ...this._box };
401
401
  const r = this._imgRect;
402
- const lockAR = this._arCheck && this._arCheck.checked;
402
+ const lockAR = this._arCheck?.checked;
403
403
  const aspect = orig.w / (orig.h || 1);
404
404
 
405
405
  const onMove = (me) => {
@@ -181,7 +181,7 @@ export class ImageResizer {
181
181
 
182
182
  // Skip DOM writes when position hasn't changed (common during non-scroll rAF ticks)
183
183
  const p = this._lastOverlayPos;
184
- if (p && p.l === rect.left && p.t === rect.top && p.w === rect.width && p.h === rect.height) return;
184
+ if (p?.l === rect.left && p.t === rect.top && p.w === rect.width && p.h === rect.height) return;
185
185
  this._lastOverlayPos = { l: rect.left, t: rect.top, w: rect.width, h: rect.height };
186
186
 
187
187
  const left = rect.left - containerRect.left + offsetParent.scrollLeft;
@@ -210,7 +210,7 @@ export class Mention {
210
210
 
211
211
  // Fallback: use getClientRects() on the current collapsed selection
212
212
  const sel = globalThis.getSelection();
213
- if (!sel || !sel.rangeCount) return;
213
+ if (!sel?.rangeCount) return;
214
214
  const rects = sel.getRangeAt(0).getClientRects();
215
215
  if (rects.length > 0) {
216
216
  this._caretRect = rects[rects.length - 1];