autumnnote 2.1.0 → 2.3.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 (65) hide show
  1. package/README.md +74 -6
  2. package/dist/autumnnote.cjs +27 -22
  3. package/dist/autumnnote.es.js +474 -591
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.min.js +27 -22
  6. package/dist/autumnnote.umd.js +27 -22
  7. package/dist/autumnnote.umd.js.map +1 -1
  8. package/dist/icon-data-V0Xqv-wX.js +255 -0
  9. package/dist/icon-data-V0Xqv-wX.js.map +1 -0
  10. package/package.json +12 -3
  11. package/types/index.d.ts +8 -0
  12. package/src/js/Context.js +0 -854
  13. package/src/js/core/detectLang.js +0 -98
  14. package/src/js/core/dom.js +0 -372
  15. package/src/js/core/env.js +0 -38
  16. package/src/js/core/key.js +0 -66
  17. package/src/js/core/lists.js +0 -121
  18. package/src/js/core/markdown.js +0 -695
  19. package/src/js/core/range.js +0 -194
  20. package/src/js/core/sanitise.js +0 -304
  21. package/src/js/editing/History.js +0 -266
  22. package/src/js/editing/Style.js +0 -812
  23. package/src/js/editing/Table.js +0 -105
  24. package/src/js/editing/Typing.js +0 -397
  25. package/src/js/index.js +0 -193
  26. package/src/js/index.umd.js +0 -17
  27. package/src/js/module/AutoSaveRestore.js +0 -125
  28. package/src/js/module/BaseDialog.js +0 -133
  29. package/src/js/module/BaseMediaTooltip.js +0 -142
  30. package/src/js/module/BaseResizer.js +0 -322
  31. package/src/js/module/BubbleToolbar.js +0 -483
  32. package/src/js/module/Buttons.js +0 -399
  33. package/src/js/module/Clipboard.js +0 -579
  34. package/src/js/module/CodeTooltip.js +0 -493
  35. package/src/js/module/Codeview.js +0 -125
  36. package/src/js/module/ContextMenu.js +0 -621
  37. package/src/js/module/Editor.js +0 -747
  38. package/src/js/module/EmojiDialog.js +0 -254
  39. package/src/js/module/FindReplace.js +0 -512
  40. package/src/js/module/Fullscreen.js +0 -80
  41. package/src/js/module/IconDialog.js +0 -618
  42. package/src/js/module/ImageCropOverlay.js +0 -586
  43. package/src/js/module/ImageDialog.js +0 -193
  44. package/src/js/module/ImageResizer.js +0 -42
  45. package/src/js/module/ImageTooltip.js +0 -285
  46. package/src/js/module/LinkDialog.js +0 -145
  47. package/src/js/module/LinkTooltip.js +0 -250
  48. package/src/js/module/MarkdownShortcuts.js +0 -250
  49. package/src/js/module/Mention.js +0 -365
  50. package/src/js/module/Placeholder.js +0 -51
  51. package/src/js/module/ShortcutsDialog.js +0 -111
  52. package/src/js/module/SlashMenu.js +0 -376
  53. package/src/js/module/Statusbar.js +0 -246
  54. package/src/js/module/TableTooltip.js +0 -1392
  55. package/src/js/module/Toolbar.js +0 -855
  56. package/src/js/module/VideoDialog.js +0 -193
  57. package/src/js/module/VideoResizer.js +0 -66
  58. package/src/js/module/VideoTooltip.js +0 -248
  59. package/src/js/module/emoji-data.js +0 -496
  60. package/src/js/module/table-grid.js +0 -102
  61. package/src/js/module/table-icons.js +0 -33
  62. package/src/js/renderer.js +0 -120
  63. package/src/js/settings.js +0 -214
  64. package/src/styles/_variables.scss +0 -48
  65. package/src/styles/autumnnote.scss +0 -2877
@@ -1,483 +0,0 @@
1
- /**
2
- * BubbleToolbar.js — A mini floating toolbar that appears above the current
3
- * text selection, allowing quick access to common formatting actions without
4
- * reaching for the main toolbar.
5
- *
6
- * Activated when `bubbleToolbar: true`. The set of visible buttons is
7
- * controlled by `bubbleToolbarItems` (array of button name strings).
8
- *
9
- * Built-in names: 'bold', 'italic', 'underline', 'strikethrough',
10
- * 'link', 'foreColor', 'hiliteColor', 'removeFormat', 'inlineCode'.
11
- * Any name not found in the built-in map is silently ignored.
12
- */
13
-
14
- import { on } from '../core/dom.js';
15
-
16
- const COLOR_PRESETS = [
17
- '#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#efefef', '#ffffff',
18
- '#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#9900ff', '#ff00ff',
19
- '#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3', '#c9daf8', '#d9d2e9', '#ead1dc',
20
- ];
21
-
22
- // Minimal SVG icon set — only what the bubble toolbar needs.
23
- const _S = 'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
24
- const _svg = (p) =>
25
- `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" ${_S} style="display:block">${p}</svg>`;
26
-
27
- const _ICONS = {
28
- bold: _svg('<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>'),
29
- italic: _svg('<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>'),
30
- underline: _svg('<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/>'),
31
- strikethrough: _svg('<path d="M17.3 12H6.7"/><path d="M10 6.5C10 5.1 11.1 4 12.5 4c1.4 0 2.5 1.1 2.5 2.5 0 .8-.4 1.5-1 2"/><path d="M14 17.5C14 19 12.9 20 11.5 20 10.1 20 9 18.9 9 17.5c0-.8.4-1.5 1-2"/>'),
32
- link: _svg('<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"/>'),
33
- foreColor: _svg('<path d="M4 20L12 4L20 20"/><line x1="7.5" y1="14" x2="16.5" y2="14"/>'),
34
- hiliteColor: _svg('<path d="M3 21v-4l9-9 4 4-9 9z"/><path d="M12 8l4 4"/><line x1="3" y1="21" x2="21" y2="21"/>'),
35
- removeFormat: _svg('<path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/>'),
36
- inlineCode: _svg('<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"/><path d="M16 3h1a2 2 0 0 1 2 2v5c0 1.1.9 2 2 2a2 2 0 0 1-2 2v5a2 2 0 0 1-2 2h-1"/>'),
37
- };
38
-
39
- // Color buttons are handled via _openColorPicker; not in _ACTIONS.
40
- const _ACTIONS = {
41
- bold: (ctx) => ctx.invoke('editor.bold'),
42
- italic: (ctx) => ctx.invoke('editor.italic'),
43
- underline: (ctx) => ctx.invoke('editor.underline'),
44
- strikethrough: (ctx) => ctx.invoke('editor.strikethrough'),
45
- link: (ctx) => ctx.invoke('linkDialog.show'),
46
- removeFormat: (ctx) => {
47
- const editable = ctx.layoutInfo?.editable;
48
- if (!editable) return;
49
- editable.focus();
50
- document.execCommand('removeFormat');
51
- // Also strip inline style attributes which execCommand('removeFormat') misses
52
- const sel = globalThis.getSelection();
53
- if (sel?.rangeCount > 0 && !sel.getRangeAt(0).collapsed) {
54
- const range = sel.getRangeAt(0);
55
- const ancestor = range.commonAncestorContainer;
56
- const root = /** @type {Element|null} */ (ancestor.nodeType === 1 ? ancestor : ancestor.parentElement);
57
- if (root) {
58
- const candidates = [root, ...root.querySelectorAll('[style]')];
59
- for (const el of candidates) {
60
- if (el.hasAttribute('style') && range.intersectsNode(el)) {
61
- el.removeAttribute('style');
62
- }
63
- }
64
- }
65
- }
66
- ctx.invoke('editor.afterCommand');
67
- },
68
- inlineCode: (ctx) => ctx.invoke('editor.inlineCode'),
69
- };
70
-
71
- const _ACTIVE = {
72
- bold: () => document.queryCommandState('bold'),
73
- italic: () => document.queryCommandState('italic'),
74
- underline: () => document.queryCommandState('underline'),
75
- strikethrough: () => document.queryCommandState('strikeThrough'),
76
- };
77
-
78
- const _COLOR_TYPES = { foreColor: true, hiliteColor: true };
79
-
80
- export class BubbleToolbar {
81
- /** @param {import('../Context.js').Context} context */
82
- constructor(context) {
83
- this.context = context;
84
- this.options = context.options;
85
-
86
- /** @type {HTMLElement|null} */
87
- this._el = null;
88
- /** @type {HTMLElement|null} */
89
- this._picker = null;
90
- this._pickerType = null; // 'foreColor' | 'hiliteColor'
91
- this._savedRange = null;
92
- this._visible = false;
93
- this._rafId = null;
94
- this._contextMenuOpen = false;
95
- this._disposers = [];
96
-
97
- this._onSelectionChange = this._onSelectionChange.bind(this);
98
- this._onMousedown = this._onMousedown.bind(this);
99
- this._onKeydown = this._onKeydown.bind(this);
100
- this._onContextMenu = this._onContextMenu.bind(this);
101
- }
102
-
103
- initialize() {
104
- if (!this.options.bubbleToolbar) return this;
105
- this._build();
106
- this._buildColorPicker();
107
- const d1 = on(document, 'selectionchange', this._onSelectionChange);
108
- const d2 = on(document, 'mousedown', this._onMousedown);
109
- const d3 = on(document, 'keydown', this._onKeydown);
110
- const d4 = on(document, 'contextmenu', this._onContextMenu);
111
- const d5 = this.context.on('contextMenu:show', () => {
112
- this._contextMenuOpen = true;
113
- this._hide();
114
- });
115
- const d6 = this.context.on('contextMenu:hide', () => {
116
- this._contextMenuOpen = false;
117
- });
118
- const d7 = on(globalThis, 'scroll', () => this._hide(), { passive: true });
119
- const d8 = on(globalThis, 'resize', () => this._hide(), { passive: true });
120
- this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8);
121
- return this;
122
- }
123
-
124
- destroy() {
125
- this._el?.remove();
126
- this._el = null;
127
- this._picker?.remove();
128
- this._picker = null;
129
- this._disposers.forEach((d) => d());
130
- this._disposers = [];
131
- cancelAnimationFrame(this._rafId);
132
- }
133
-
134
- // ---------------------------------------------------------------------------
135
- // Build
136
- // ---------------------------------------------------------------------------
137
-
138
- _build() {
139
- const el = document.createElement('div');
140
- el.className = 'an-bubble-toolbar';
141
- el.setAttribute('role', 'toolbar');
142
- el.setAttribute('aria-label', 'Formatting');
143
-
144
- const items = this.options.bubbleToolbarItems || ['bold', 'italic', 'underline', 'link', 'foreColor', 'hiliteColor', 'removeFormat'];
145
- for (const name of items) {
146
- if (!_ICONS[name]) continue;
147
- const btn = document.createElement('button');
148
- btn.type = 'button';
149
- btn.dataset.name = name;
150
- btn.setAttribute('aria-label', name);
151
-
152
- if (_COLOR_TYPES[name]) {
153
- // Color button: SVG icon stacked above a colored strip, matching ContextMenu style
154
- btn.className = 'an-bubble-btn an-bubble-btn--color';
155
- const iconWrap = document.createElement('span');
156
- iconWrap.className = 'an-bubble-btn-svg';
157
- iconWrap.innerHTML = _ICONS[name];
158
- const strip = document.createElement('span');
159
- strip.className = 'an-bubble-color-strip';
160
- strip.style.background = name === 'foreColor' ? '#000000' : 'transparent';
161
- btn.appendChild(iconWrap);
162
- btn.appendChild(strip);
163
- } else {
164
- btn.className = 'an-bubble-btn';
165
- btn.innerHTML = _ICONS[name];
166
- }
167
-
168
- btn.addEventListener('mousedown', (e) => {
169
- // Prevent mousedown from collapsing selection before click fires
170
- e.preventDefault();
171
- });
172
- btn.addEventListener('click', (e) => {
173
- e.preventDefault();
174
- e.stopPropagation();
175
-
176
- // Color buttons: save selection and open the picker
177
- if (_COLOR_TYPES[name]) {
178
- this._openColorPicker(name, btn);
179
- return;
180
- }
181
-
182
- this.context.invoke('editor.focus');
183
- if (_ACTIONS[name]) _ACTIONS[name](this.context);
184
- this._syncActive();
185
- });
186
- el.appendChild(btn);
187
- }
188
-
189
- document.body.appendChild(el);
190
- this._el = el;
191
- // Cache button references once — avoids querySelectorAll on every selectionchange
192
- this._btnCache = Array.from(el.querySelectorAll('.an-bubble-btn'));
193
- }
194
-
195
- _buildColorPicker() {
196
- const picker = document.createElement('div');
197
- picker.className = 'an-bubble-color-picker';
198
- picker.style.display = 'none';
199
-
200
- // Prevent any mousedown inside the picker from collapsing the editor selection
201
- picker.addEventListener('mousedown', (e) => e.preventDefault());
202
-
203
- const palette = document.createElement('div');
204
- palette.className = 'an-context-color-palette';
205
-
206
- COLOR_PRESETS.forEach((color) => {
207
- const sw = document.createElement('div');
208
- sw.className = 'an-context-color-swatch';
209
- sw.title = color;
210
- sw.style.background = color;
211
- sw.setAttribute('role', 'button');
212
- sw.setAttribute('aria-label', color);
213
- sw.addEventListener('click', (e) => {
214
- e.stopPropagation();
215
- this._applyColor(this._pickerType, color);
216
- });
217
- palette.appendChild(sw);
218
- });
219
-
220
- // "No highlight" swatch — only shown for hiliteColor
221
- const noColorBtn = document.createElement('div');
222
- noColorBtn.className = 'an-context-color-swatch an-context-color-none';
223
- noColorBtn.title = 'No highlight';
224
- noColorBtn.setAttribute('role', 'button');
225
- noColorBtn.setAttribute('aria-label', 'No highlight');
226
- noColorBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="4" y1="4" x2="20" y2="20"/><line x1="20" y1="4" x2="4" y2="20"/></svg>`;
227
- noColorBtn.addEventListener('click', (e) => {
228
- e.stopPropagation();
229
- this._applyColor('hiliteColor', 'transparent');
230
- });
231
-
232
- // Custom color input row
233
- const customRow = document.createElement('div');
234
- customRow.className = 'an-context-color-custom';
235
- const colorInput = document.createElement('input');
236
- colorInput.type = 'color';
237
- colorInput.value = '#000000';
238
- colorInput.title = 'Custom color';
239
- // stopPropagation so the picker's mousedown-preventDefault doesn't block the native color dialog
240
- colorInput.addEventListener('mousedown', (e) => e.stopPropagation());
241
- colorInput.addEventListener('change', () => {
242
- this._applyColor(this._pickerType, colorInput.value);
243
- });
244
- const customLabel = document.createElement('span');
245
- customLabel.textContent = 'Custom…';
246
- customRow.appendChild(colorInput);
247
- customRow.appendChild(customLabel);
248
-
249
- picker.appendChild(palette);
250
- picker.appendChild(customRow);
251
- document.body.appendChild(picker);
252
-
253
- this._picker = picker;
254
- const pickerAny = /** @type {any} */ (picker);
255
- pickerAny._paletteEl = palette;
256
- pickerAny._noColorBtn = noColorBtn;
257
- pickerAny._colorInput = colorInput;
258
- }
259
-
260
- _openColorPicker(type, anchorBtn) {
261
- // Save current selection before the picker might shift focus
262
- const sel = globalThis.getSelection();
263
- if (sel?.rangeCount > 0) {
264
- this._savedRange = sel.getRangeAt(0).cloneRange();
265
- }
266
-
267
- this._pickerType = type;
268
-
269
- // Toggle "No highlight" swatch based on type
270
- const pickerAny = /** @type {any} */ (this._picker);
271
- const palette = pickerAny._paletteEl;
272
- const noColorBtn = pickerAny._noColorBtn;
273
- if (type === 'hiliteColor') {
274
- if (!palette.contains(noColorBtn)) palette.appendChild(noColorBtn);
275
- } else if (palette.contains(noColorBtn)) {
276
- noColorBtn.remove();
277
- }
278
-
279
- // Seed the custom color input
280
- /** @type {any} */ (this._picker)._colorInput.value = type === 'foreColor' ? '#000000' : '#ffff00';
281
-
282
- // Position the picker above the bubble toolbar (not blocking the content below it).
283
- // Fall back to below the toolbar if there's not enough room above.
284
- this._picker.style.display = 'block';
285
- const pw = this._picker.offsetWidth;
286
- const ph = this._picker.offsetHeight;
287
- const toolbarRect = this._el.getBoundingClientRect();
288
-
289
- let top = toolbarRect.top - ph - 6;
290
- if (top < 8) top = toolbarRect.bottom + 6;
291
-
292
- // Align horizontally with the clicked button, clamped to viewport
293
- const btnRect = anchorBtn.getBoundingClientRect();
294
- let left = btnRect.left;
295
- left = Math.max(8, Math.min(left, globalThis.innerWidth - pw - 8));
296
-
297
- this._picker.style.left = `${left}px`;
298
- this._picker.style.top = `${top}px`;
299
- }
300
-
301
- _closeColorPicker() {
302
- if (this._picker) this._picker.style.display = 'none';
303
- this._pickerType = null;
304
- }
305
-
306
- /** Restore the saved selection, apply execCommand, update the color strip, then close the picker. */
307
- _applyColor(type, color) {
308
- const editable = this.context.layoutInfo?.editable;
309
- if (!editable || !this._savedRange) return;
310
-
311
- editable.focus();
312
- const sel = globalThis.getSelection();
313
- sel.removeAllRanges();
314
- try { sel.addRange(this._savedRange.cloneRange()); } catch (_) { void _; return; }
315
-
316
- // Firefox does not support 'hiliteColor'; fall back to 'backColor'
317
- const cmd = type === 'hiliteColor' ? 'hiliteColor' : type;
318
- if (!document.execCommand(cmd, false, color) && cmd === 'hiliteColor') {
319
- document.execCommand('backColor', false, color);
320
- }
321
- this.context.invoke('editor.afterCommand');
322
-
323
- // Update the color strip on the corresponding button
324
- const name = type === 'hiliteColor' ? 'hiliteColor' : 'foreColor';
325
- const btn = this._el?.querySelector(`[data-name="${name}"]`);
326
- const strip = btn?.querySelector('.an-bubble-color-strip');
327
- if (strip) /** @type {HTMLElement} */ (strip).style.background = color === 'transparent' ? 'transparent' : color;
328
-
329
- this._closeColorPicker();
330
- this._syncActive();
331
- }
332
-
333
- // ---------------------------------------------------------------------------
334
- // Show / hide
335
- // ---------------------------------------------------------------------------
336
-
337
- _show(rect) {
338
- if (!this._el) return;
339
- const el = this._el;
340
-
341
- // Measure while invisible — position:fixed so no scroll offset needed
342
- el.style.visibility = 'hidden';
343
- el.style.display = 'flex';
344
-
345
- const bw = el.offsetWidth;
346
- const bh = el.offsetHeight;
347
- const gap = 8;
348
-
349
- // Center horizontally above the selection; flip below if no room above
350
- let left = rect.left + rect.width / 2 - bw / 2;
351
- let top = rect.top - bh - gap;
352
-
353
- left = Math.max(8, Math.min(left, globalThis.innerWidth - bw - 8));
354
-
355
- if (top < 8) {
356
- top = rect.bottom + gap;
357
- }
358
-
359
- // If the Table Tooltip is visible, avoid overlapping it by flipping below the selection.
360
- // The Table Tooltip sits above the table, which is the same vertical zone the bubble
361
- // toolbar would normally occupy when text is selected inside a table cell.
362
- const tableTooltipEl = document.querySelector('.an-table-tooltip');
363
- if (tableTooltipEl && /** @type {HTMLElement} */ (tableTooltipEl).style.display !== 'none') {
364
- const ttRect = tableTooltipEl.getBoundingClientRect();
365
- const overlapsVertically = top < ttRect.bottom + gap && top + bh > ttRect.top - gap;
366
- if (overlapsVertically) {
367
- top = rect.bottom + gap;
368
- if (top + bh > globalThis.innerHeight - 8) top = ttRect.bottom + gap;
369
- }
370
- }
371
-
372
- el.style.top = `${top}px`;
373
- el.style.left = `${left}px`;
374
- el.style.visibility = '';
375
-
376
- this._syncActive();
377
- this._syncColorStrips();
378
- this._visible = true;
379
- }
380
-
381
- _hide() {
382
- if (!this._el) return;
383
- this._el.style.display = 'none';
384
- this._visible = false;
385
- this._closeColorPicker();
386
- }
387
-
388
- _syncActive() {
389
- if (!this._btnCache) return;
390
- this._btnCache.forEach((btn) => {
391
- const activeFn = _ACTIVE[/** @type {HTMLElement} */ (btn).dataset.name];
392
- btn.classList.toggle('an-active', !!(activeFn?.()));
393
- });
394
- }
395
-
396
- /** Read the current selection's color and update the color-strip indicators. */
397
- _syncColorStrips() {
398
- if (!this._el) return;
399
- const sel = globalThis.getSelection();
400
- if (!sel?.rangeCount) return;
401
- let node = sel.getRangeAt(0).startContainer;
402
- if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
403
- if (!node) return;
404
- const cs = globalThis.getComputedStyle(/** @type {Element} */ (node));
405
-
406
- const foreBtn = this._el.querySelector('[data-name="foreColor"]');
407
- const foreStrip = foreBtn?.querySelector('.an-bubble-color-strip');
408
- if (foreStrip) /** @type {HTMLElement} */ (foreStrip).style.background = cs.color || '#000000';
409
-
410
- const hiliteBtn = this._el.querySelector('[data-name="hiliteColor"]');
411
- const hiliteStrip = hiliteBtn?.querySelector('.an-bubble-color-strip');
412
- if (hiliteStrip) {
413
- const bg = cs.backgroundColor;
414
- /** @type {HTMLElement} */ (hiliteStrip).style.background = (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') ? 'transparent' : bg;
415
- }
416
- }
417
-
418
- // ---------------------------------------------------------------------------
419
- // Events
420
- // ---------------------------------------------------------------------------
421
-
422
- _onSelectionChange() {
423
- cancelAnimationFrame(this._rafId);
424
- this._rafId = requestAnimationFrame(() => {
425
- if (this._contextMenuOpen) return;
426
- // Keep toolbar visible while color picker is open
427
- if (this._picker && this._picker.style.display !== 'none') return;
428
-
429
- const sel = globalThis.getSelection();
430
- if (!sel || sel.isCollapsed || !sel.rangeCount) {
431
- this._hide();
432
- return;
433
- }
434
-
435
- const editable = this.context.layoutInfo.editable;
436
- if (!editable.contains(sel.anchorNode)) {
437
- this._hide();
438
- return;
439
- }
440
-
441
- // Don't show in read-only mode
442
- if (this.context.options.readOnly) {
443
- this._hide();
444
- return;
445
- }
446
-
447
- const range = sel.getRangeAt(0);
448
- const rect = range.getBoundingClientRect();
449
- if (!rect || rect.width === 0) {
450
- this._hide();
451
- return;
452
- }
453
-
454
- this._show(rect);
455
- });
456
- }
457
-
458
- _onMousedown(e) {
459
- // Hide when clicking outside both the editable, the bubble toolbar, and the color picker
460
- if (!this._visible) return;
461
- if (this._el?.contains(e.target)) return;
462
- if (this._picker?.contains(e.target)) return;
463
- const editable = this.context.layoutInfo.editable;
464
- if (editable.contains(e.target)) return;
465
- this._hide();
466
- }
467
-
468
- _onKeydown(e) {
469
- if (e.key === 'Escape') {
470
- if (this._picker && this._picker.style.display !== 'none') {
471
- this._closeColorPicker();
472
- } else if (this._visible) {
473
- this._hide();
474
- }
475
- }
476
- }
477
-
478
- _onContextMenu() {
479
- // Hide immediately on right-click — contextMenu:show event will also set the flag,
480
- // but firing here prevents a one-frame flicker before the event propagates.
481
- this._hide();
482
- }
483
- }