quasar-ui-danx 0.5.0 → 0.5.2

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 (81) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/dist/danx.es.js +16119 -10641
  3. package/dist/danx.es.js.map +1 -1
  4. package/dist/danx.umd.js +202 -123
  5. package/dist/danx.umd.js.map +1 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +8 -1
  8. package/src/components/Utility/Buttons/ActionButton.vue +15 -5
  9. package/src/components/Utility/Code/CodeViewer.vue +41 -16
  10. package/src/components/Utility/Code/CodeViewerCollapsed.vue +2 -0
  11. package/src/components/Utility/Code/CodeViewerFooter.vue +3 -1
  12. package/src/components/Utility/Code/LanguageBadge.vue +278 -5
  13. package/src/components/Utility/Code/MarkdownContent.vue +31 -163
  14. package/src/components/Utility/Code/index.ts +3 -0
  15. package/src/components/Utility/Markdown/ContextMenu.vue +314 -0
  16. package/src/components/Utility/Markdown/HotkeyHelpPopover.vue +259 -0
  17. package/src/components/Utility/Markdown/LineTypeMenu.vue +226 -0
  18. package/src/components/Utility/Markdown/LinkPopover.vue +331 -0
  19. package/src/components/Utility/Markdown/MarkdownEditor.vue +233 -0
  20. package/src/components/Utility/Markdown/MarkdownEditorContent.vue +296 -0
  21. package/src/components/Utility/Markdown/MarkdownEditorFooter.vue +50 -0
  22. package/src/components/Utility/Markdown/TablePopover.vue +420 -0
  23. package/src/components/Utility/Markdown/index.ts +11 -0
  24. package/src/components/Utility/Markdown/types.ts +27 -0
  25. package/src/components/Utility/Widgets/LabelPillWidget.vue +20 -0
  26. package/src/components/Utility/index.ts +1 -0
  27. package/src/composables/index.ts +1 -0
  28. package/src/composables/markdown/features/useBlockquotes.spec.ts +428 -0
  29. package/src/composables/markdown/features/useBlockquotes.ts +248 -0
  30. package/src/composables/markdown/features/useCodeBlockManager.ts +369 -0
  31. package/src/composables/markdown/features/useCodeBlocks.spec.ts +805 -0
  32. package/src/composables/markdown/features/useCodeBlocks.ts +774 -0
  33. package/src/composables/markdown/features/useContextMenu.ts +444 -0
  34. package/src/composables/markdown/features/useFocusTracking.ts +116 -0
  35. package/src/composables/markdown/features/useHeadings.spec.ts +834 -0
  36. package/src/composables/markdown/features/useHeadings.ts +290 -0
  37. package/src/composables/markdown/features/useInlineFormatting.spec.ts +705 -0
  38. package/src/composables/markdown/features/useInlineFormatting.ts +402 -0
  39. package/src/composables/markdown/features/useLineTypeMenu.ts +285 -0
  40. package/src/composables/markdown/features/useLinks.spec.ts +388 -0
  41. package/src/composables/markdown/features/useLinks.ts +374 -0
  42. package/src/composables/markdown/features/useLists.spec.ts +834 -0
  43. package/src/composables/markdown/features/useLists.ts +747 -0
  44. package/src/composables/markdown/features/usePopoverManager.ts +181 -0
  45. package/src/composables/markdown/features/useTables.spec.ts +1601 -0
  46. package/src/composables/markdown/features/useTables.ts +1107 -0
  47. package/src/composables/markdown/index.ts +16 -0
  48. package/src/composables/markdown/useMarkdownEditor.spec.ts +332 -0
  49. package/src/composables/markdown/useMarkdownEditor.ts +1077 -0
  50. package/src/composables/markdown/useMarkdownHotkeys.spec.ts +791 -0
  51. package/src/composables/markdown/useMarkdownHotkeys.ts +266 -0
  52. package/src/composables/markdown/useMarkdownSelection.ts +219 -0
  53. package/src/composables/markdown/useMarkdownSync.ts +549 -0
  54. package/src/composables/useCodeFormat.ts +17 -10
  55. package/src/composables/useCodeViewerEditor.spec.ts +655 -0
  56. package/src/composables/useCodeViewerEditor.ts +174 -20
  57. package/src/helpers/formats/highlightCSS.ts +236 -0
  58. package/src/helpers/formats/highlightHTML.ts +483 -0
  59. package/src/helpers/formats/highlightJavaScript.ts +346 -0
  60. package/src/helpers/formats/highlightSyntax.ts +15 -4
  61. package/src/helpers/formats/index.ts +3 -0
  62. package/src/helpers/formats/markdown/htmlToMarkdown/convertHeadings.ts +41 -0
  63. package/src/helpers/formats/markdown/htmlToMarkdown/index.spec.ts +489 -0
  64. package/src/helpers/formats/markdown/htmlToMarkdown/index.ts +425 -0
  65. package/src/helpers/formats/markdown/index.ts +7 -0
  66. package/src/helpers/formats/markdown/linePatterns.spec.ts +498 -0
  67. package/src/helpers/formats/markdown/linePatterns.ts +172 -0
  68. package/src/styles/danx.scss +3 -3
  69. package/src/styles/index.scss +5 -5
  70. package/src/styles/themes/danx/code.scss +257 -1
  71. package/src/styles/themes/danx/index.scss +10 -10
  72. package/src/styles/themes/danx/markdown.scss +59 -0
  73. package/src/test/helpers/editorTestUtils.spec.ts +296 -0
  74. package/src/test/helpers/editorTestUtils.ts +253 -0
  75. package/src/test/helpers/index.ts +1 -0
  76. package/src/test/highlighters.test.ts +153 -0
  77. package/src/test/setup.test.ts +12 -0
  78. package/src/test/setup.ts +12 -0
  79. package/src/types/widgets.d.ts +2 -2
  80. package/vite.config.js +5 -1
  81. package/vitest.config.ts +19 -0
@@ -0,0 +1,444 @@
1
+ import { Ref, ref } from "vue";
2
+ import { ContextMenuContext, ContextMenuItem } from "../../../components/Utility/Markdown/types";
3
+ import { UseMarkdownEditorReturn } from "../useMarkdownEditor";
4
+
5
+ /**
6
+ * Options for useContextMenu composable
7
+ */
8
+ export interface UseContextMenuOptions {
9
+ editor: UseMarkdownEditorReturn;
10
+ readonly?: Ref<boolean>;
11
+ }
12
+
13
+ /**
14
+ * Return type for useContextMenu composable
15
+ */
16
+ export interface UseContextMenuReturn {
17
+ isVisible: Ref<boolean>;
18
+ position: Ref<{ x: number; y: number }>;
19
+ items: Ref<ContextMenuItem[]>;
20
+ show: (event: MouseEvent) => void;
21
+ hide: () => void;
22
+ }
23
+
24
+ /**
25
+ * Composable for managing the context menu in the markdown editor.
26
+ * Handles context detection and menu item building based on cursor position.
27
+ */
28
+ export function useContextMenu(options: UseContextMenuOptions): UseContextMenuReturn {
29
+ const { editor, readonly } = options;
30
+
31
+ // Context menu state
32
+ const isVisible = ref(false);
33
+ const position = ref({ x: 0, y: 0 });
34
+ const items = ref<ContextMenuItem[]>([]);
35
+
36
+ /**
37
+ * Determine the context for the context menu based on cursor position
38
+ */
39
+ function determineContext(): ContextMenuContext {
40
+ if (editor.tables.isInTable()) return "table";
41
+ if (editor.codeBlocks.isInCodeBlock()) return "code";
42
+ if (editor.lists.getCurrentListType()) return "list";
43
+ return "text";
44
+ }
45
+
46
+ /**
47
+ * Build context menu items with nested submenus based on the current context.
48
+ *
49
+ * Menu items are filtered based on markdown spec constraints:
50
+ * - Code blocks: Only show toggle code block (code is verbatim, no formatting allowed)
51
+ * - Tables: Only inline formatting and table operations (no block-level elements)
52
+ * - Lists: Inline formatting, list toggle, and blockquote (no headings, tables, or code blocks)
53
+ * - Text: Full menu with all options
54
+ */
55
+ function buildItems(context: ContextMenuContext): ContextMenuItem[] {
56
+ const menuItems: ContextMenuItem[] = [];
57
+
58
+ // In code blocks, show minimal menu - just exit option
59
+ // Code blocks are literal/verbatim, no formatting is allowed
60
+ // Unnested since this is the only action available in this context
61
+ if (context === "code") {
62
+ menuItems.push({
63
+ id: "code-block",
64
+ label: "Toggle Code Block",
65
+ shortcut: "Ctrl+Shift+K",
66
+ action: () => editor.codeBlocks.toggleCodeBlock()
67
+ });
68
+ return menuItems;
69
+ }
70
+
71
+ // In tables, only show inline formatting and table operations
72
+ // Tables cannot contain block-level elements (headings, code blocks, blockquotes, lists, nested tables)
73
+ // Table operations are unnested since this context is already limited to tables
74
+ if (context === "table") {
75
+ // Format submenu (inline only - keep nested)
76
+ menuItems.push({
77
+ id: "format",
78
+ label: "Format",
79
+ children: [
80
+ {
81
+ id: "bold",
82
+ label: "Bold",
83
+ shortcut: "Ctrl+B",
84
+ action: () => editor.inlineFormatting.toggleBold()
85
+ },
86
+ {
87
+ id: "italic",
88
+ label: "Italic",
89
+ shortcut: "Ctrl+I",
90
+ action: () => editor.inlineFormatting.toggleItalic()
91
+ },
92
+ {
93
+ id: "strikethrough",
94
+ label: "Strikethrough",
95
+ shortcut: "Ctrl+Shift+S",
96
+ action: () => editor.inlineFormatting.toggleStrikethrough()
97
+ },
98
+ {
99
+ id: "inline-code",
100
+ label: "Inline Code",
101
+ shortcut: "Ctrl+E",
102
+ action: () => editor.inlineFormatting.toggleInlineCode()
103
+ },
104
+ {
105
+ id: "link",
106
+ label: "Link",
107
+ shortcut: "Ctrl+K",
108
+ action: () => editor.links.insertLink()
109
+ }
110
+ ]
111
+ });
112
+
113
+ // Table operations - unnested as top-level items with dividers between groups
114
+ // Divider after Format submenu
115
+ menuItems.push({ id: "table-format-divider", label: "", divider: true });
116
+
117
+ // Insert operations
118
+ menuItems.push({
119
+ id: "insert-row-above",
120
+ label: "Insert Row Above",
121
+ shortcut: "Ctrl+Alt+Shift+Up",
122
+ action: () => editor.tables.insertRowAbove()
123
+ });
124
+ menuItems.push({
125
+ id: "insert-row-below",
126
+ label: "Insert Row Below",
127
+ shortcut: "Ctrl+Alt+Shift+Down",
128
+ action: () => editor.tables.insertRowBelow()
129
+ });
130
+ menuItems.push({
131
+ id: "insert-col-left",
132
+ label: "Insert Column Left",
133
+ shortcut: "Ctrl+Alt+Shift+Left",
134
+ action: () => editor.tables.insertColumnLeft()
135
+ });
136
+ menuItems.push({
137
+ id: "insert-col-right",
138
+ label: "Insert Column Right",
139
+ shortcut: "Ctrl+Alt+Shift+Right",
140
+ action: () => editor.tables.insertColumnRight()
141
+ });
142
+
143
+ // Divider before delete operations
144
+ menuItems.push({ id: "table-divider-1", label: "", divider: true });
145
+
146
+ // Delete operations
147
+ menuItems.push({
148
+ id: "delete-row",
149
+ label: "Delete Row",
150
+ shortcut: "Ctrl+Alt+Backspace",
151
+ action: () => editor.tables.deleteCurrentRow()
152
+ });
153
+ menuItems.push({
154
+ id: "delete-col",
155
+ label: "Delete Column",
156
+ shortcut: "Ctrl+Shift+Backspace",
157
+ action: () => editor.tables.deleteCurrentColumn()
158
+ });
159
+ menuItems.push({
160
+ id: "delete-table",
161
+ label: "Delete Table",
162
+ action: () => editor.tables.deleteTable()
163
+ });
164
+
165
+ // Divider before alignment
166
+ menuItems.push({ id: "table-divider-2", label: "", divider: true });
167
+
168
+ // Alignment submenu
169
+ menuItems.push({
170
+ id: "alignment",
171
+ label: "Alignment",
172
+ children: [
173
+ {
174
+ id: "align-left",
175
+ label: "Align Left",
176
+ shortcut: "Ctrl+Alt+L",
177
+ action: () => editor.tables.setColumnAlignmentLeft()
178
+ },
179
+ {
180
+ id: "align-center",
181
+ label: "Align Center",
182
+ shortcut: "Ctrl+Alt+C",
183
+ action: () => editor.tables.setColumnAlignmentCenter()
184
+ },
185
+ {
186
+ id: "align-right",
187
+ label: "Align Right",
188
+ shortcut: "Ctrl+Alt+R",
189
+ action: () => editor.tables.setColumnAlignmentRight()
190
+ }
191
+ ]
192
+ });
193
+
194
+ return menuItems;
195
+ }
196
+
197
+ // In lists, show inline formatting, list operations, and blockquote
198
+ // Lists cannot contain headings, tables, or code blocks inside list items
199
+ if (context === "list") {
200
+ // Format submenu (inline formatting)
201
+ menuItems.push({
202
+ id: "format",
203
+ label: "Format",
204
+ children: [
205
+ {
206
+ id: "bold",
207
+ label: "Bold",
208
+ shortcut: "Ctrl+B",
209
+ action: () => editor.inlineFormatting.toggleBold()
210
+ },
211
+ {
212
+ id: "italic",
213
+ label: "Italic",
214
+ shortcut: "Ctrl+I",
215
+ action: () => editor.inlineFormatting.toggleItalic()
216
+ },
217
+ {
218
+ id: "strikethrough",
219
+ label: "Strikethrough",
220
+ shortcut: "Ctrl+Shift+S",
221
+ action: () => editor.inlineFormatting.toggleStrikethrough()
222
+ },
223
+ {
224
+ id: "inline-code",
225
+ label: "Inline Code",
226
+ shortcut: "Ctrl+E",
227
+ action: () => editor.inlineFormatting.toggleInlineCode()
228
+ },
229
+ {
230
+ id: "link",
231
+ label: "Link",
232
+ shortcut: "Ctrl+K",
233
+ action: () => editor.links.insertLink()
234
+ }
235
+ ]
236
+ });
237
+
238
+ // Lists submenu (toggle between bullet/numbered)
239
+ menuItems.push({
240
+ id: "lists",
241
+ label: "Lists",
242
+ children: [
243
+ {
244
+ id: "bullet-list",
245
+ label: "Bullet List",
246
+ shortcut: "Ctrl+Shift+[",
247
+ action: () => editor.lists.toggleUnorderedList()
248
+ },
249
+ {
250
+ id: "numbered-list",
251
+ label: "Numbered List",
252
+ shortcut: "Ctrl+Shift+]",
253
+ action: () => editor.lists.toggleOrderedList()
254
+ }
255
+ ]
256
+ });
257
+
258
+ // Blocks submenu (only blockquote, no code blocks or tables)
259
+ menuItems.push({
260
+ id: "blocks",
261
+ label: "Blocks",
262
+ children: [
263
+ {
264
+ id: "blockquote",
265
+ label: "Blockquote",
266
+ shortcut: "Ctrl+Shift+Q",
267
+ action: () => editor.blockquotes.toggleBlockquote()
268
+ }
269
+ ]
270
+ });
271
+
272
+ return menuItems;
273
+ }
274
+
275
+ // Text/Paragraph context - show everything (full menu)
276
+ // Headings submenu
277
+ menuItems.push({
278
+ id: "headings",
279
+ label: "Headings",
280
+ children: [
281
+ {
282
+ id: "paragraph",
283
+ label: "Paragraph",
284
+ shortcut: "Ctrl+0",
285
+ action: () => editor.headings.setHeadingLevel(0)
286
+ },
287
+ {
288
+ id: "h1",
289
+ label: "Heading 1",
290
+ shortcut: "Ctrl+1",
291
+ action: () => editor.headings.setHeadingLevel(1)
292
+ },
293
+ {
294
+ id: "h2",
295
+ label: "Heading 2",
296
+ shortcut: "Ctrl+2",
297
+ action: () => editor.headings.setHeadingLevel(2)
298
+ },
299
+ {
300
+ id: "h3",
301
+ label: "Heading 3",
302
+ shortcut: "Ctrl+3",
303
+ action: () => editor.headings.setHeadingLevel(3)
304
+ },
305
+ {
306
+ id: "h4",
307
+ label: "Heading 4",
308
+ shortcut: "Ctrl+4",
309
+ action: () => editor.headings.setHeadingLevel(4)
310
+ },
311
+ {
312
+ id: "h5",
313
+ label: "Heading 5",
314
+ shortcut: "Ctrl+5",
315
+ action: () => editor.headings.setHeadingLevel(5)
316
+ },
317
+ {
318
+ id: "h6",
319
+ label: "Heading 6",
320
+ shortcut: "Ctrl+6",
321
+ action: () => editor.headings.setHeadingLevel(6)
322
+ }
323
+ ]
324
+ });
325
+
326
+ // Format submenu
327
+ menuItems.push({
328
+ id: "format",
329
+ label: "Format",
330
+ children: [
331
+ {
332
+ id: "bold",
333
+ label: "Bold",
334
+ shortcut: "Ctrl+B",
335
+ action: () => editor.inlineFormatting.toggleBold()
336
+ },
337
+ {
338
+ id: "italic",
339
+ label: "Italic",
340
+ shortcut: "Ctrl+I",
341
+ action: () => editor.inlineFormatting.toggleItalic()
342
+ },
343
+ {
344
+ id: "strikethrough",
345
+ label: "Strikethrough",
346
+ shortcut: "Ctrl+Shift+S",
347
+ action: () => editor.inlineFormatting.toggleStrikethrough()
348
+ },
349
+ {
350
+ id: "inline-code",
351
+ label: "Inline Code",
352
+ shortcut: "Ctrl+E",
353
+ action: () => editor.inlineFormatting.toggleInlineCode()
354
+ },
355
+ {
356
+ id: "link",
357
+ label: "Link",
358
+ shortcut: "Ctrl+K",
359
+ action: () => editor.links.insertLink()
360
+ }
361
+ ]
362
+ });
363
+
364
+ // Lists submenu
365
+ menuItems.push({
366
+ id: "lists",
367
+ label: "Lists",
368
+ children: [
369
+ {
370
+ id: "bullet-list",
371
+ label: "Bullet List",
372
+ shortcut: "Ctrl+Shift+[",
373
+ action: () => editor.lists.toggleUnorderedList()
374
+ },
375
+ {
376
+ id: "numbered-list",
377
+ label: "Numbered List",
378
+ shortcut: "Ctrl+Shift+]",
379
+ action: () => editor.lists.toggleOrderedList()
380
+ }
381
+ ]
382
+ });
383
+
384
+ // Blocks submenu (with all block options)
385
+ menuItems.push({
386
+ id: "blocks",
387
+ label: "Blocks",
388
+ children: [
389
+ {
390
+ id: "code-block",
391
+ label: "Code Block",
392
+ shortcut: "Ctrl+Shift+K",
393
+ action: () => editor.codeBlocks.toggleCodeBlock()
394
+ },
395
+ {
396
+ id: "blockquote",
397
+ label: "Blockquote",
398
+ shortcut: "Ctrl+Shift+Q",
399
+ action: () => editor.blockquotes.toggleBlockquote()
400
+ },
401
+ {
402
+ id: "insert-table",
403
+ label: "Insert Table",
404
+ shortcut: "Ctrl+Alt+Shift+T",
405
+ action: () => editor.tables.insertTable()
406
+ }
407
+ ]
408
+ });
409
+
410
+ return menuItems;
411
+ }
412
+
413
+ /**
414
+ * Show the context menu at the event position
415
+ */
416
+ function show(event: MouseEvent): void {
417
+ // Don't show context menu in readonly mode
418
+ if (readonly?.value) return;
419
+
420
+ event.preventDefault();
421
+
422
+ const context = determineContext();
423
+ const menuItems = buildItems(context);
424
+
425
+ position.value = { x: event.clientX, y: event.clientY };
426
+ items.value = menuItems;
427
+ isVisible.value = true;
428
+ }
429
+
430
+ /**
431
+ * Hide the context menu
432
+ */
433
+ function hide(): void {
434
+ isVisible.value = false;
435
+ }
436
+
437
+ return {
438
+ isVisible,
439
+ position,
440
+ items,
441
+ show,
442
+ hide
443
+ };
444
+ }
@@ -0,0 +1,116 @@
1
+ import { onMounted, onUnmounted, Ref, ref, watch } from "vue";
2
+
3
+ /**
4
+ * Options for useFocusTracking composable
5
+ */
6
+ export interface UseFocusTrackingOptions {
7
+ /** Reference to the main content element */
8
+ contentRef: Ref<HTMLElement | null>;
9
+ /** Reference to the menu container (optional, for focus retention when clicking menu) */
10
+ menuContainerRef?: Ref<HTMLElement | null>;
11
+ /** Callback when selection changes in the document */
12
+ onSelectionChange?: () => void;
13
+ }
14
+
15
+ /**
16
+ * Return type for useFocusTracking composable
17
+ */
18
+ export interface UseFocusTrackingReturn {
19
+ /** Whether the editor content area currently has focus */
20
+ isEditorFocused: Ref<boolean>;
21
+ }
22
+
23
+ /**
24
+ * Composable for tracking focus state within a contenteditable editor
25
+ *
26
+ * This handles:
27
+ * - Focus in/out tracking for the content area
28
+ * - Optional focus retention when clicking associated UI elements (like menus)
29
+ * - Document-level selection change monitoring
30
+ * - Proper listener cleanup on unmount
31
+ */
32
+ export function useFocusTracking(options: UseFocusTrackingOptions): UseFocusTrackingReturn {
33
+ const { contentRef, menuContainerRef, onSelectionChange } = options;
34
+
35
+ // Track whether the editor has focus
36
+ const isEditorFocused = ref(false);
37
+
38
+ // Track which element has listeners attached (for cleanup)
39
+ let boundContentEl: HTMLElement | null = null;
40
+
41
+ /**
42
+ * Handle focus entering the content area
43
+ */
44
+ function handleFocusIn(event: FocusEvent): void {
45
+ const contentEl = contentRef.value;
46
+ if (contentEl && contentEl.contains(event.target as Node)) {
47
+ isEditorFocused.value = true;
48
+ // Notify selection change callback if provided
49
+ onSelectionChange?.();
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Handle focus leaving the content area
55
+ *
56
+ * Note: We check if focus is moving to the menu container to avoid
57
+ * prematurely marking the editor as unfocused when clicking menu items.
58
+ */
59
+ function handleFocusOut(event: FocusEvent): void {
60
+ const contentEl = contentRef.value;
61
+ const menuEl = menuContainerRef?.value;
62
+ const relatedTarget = event.relatedTarget as Node | null;
63
+
64
+ // Check if focus is moving outside the editor
65
+ if (contentEl && !contentEl.contains(relatedTarget)) {
66
+ // Also check if focus is moving to the menu container (if provided)
67
+ // Keep focused if moving to menu - allows clicking menu without losing focus state
68
+ if (!menuEl || !menuEl.contains(relatedTarget)) {
69
+ isEditorFocused.value = false;
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Setup or cleanup focus listeners on content element
76
+ */
77
+ function setupContentListeners(el: HTMLElement | null): void {
78
+ // Cleanup previous listeners if element changed
79
+ if (boundContentEl && boundContentEl !== el) {
80
+ boundContentEl.removeEventListener("focusin", handleFocusIn);
81
+ boundContentEl.removeEventListener("focusout", handleFocusOut);
82
+ boundContentEl = null;
83
+ }
84
+
85
+ // Setup new listeners
86
+ if (el && el !== boundContentEl) {
87
+ el.addEventListener("focusin", handleFocusIn);
88
+ el.addEventListener("focusout", handleFocusOut);
89
+ boundContentEl = el;
90
+ }
91
+ }
92
+
93
+ // Watch for content element to become available
94
+ watch(contentRef, (newEl) => {
95
+ setupContentListeners(newEl);
96
+ }, { immediate: true });
97
+
98
+ // Listen for document-level selection changes
99
+ onMounted(() => {
100
+ if (onSelectionChange) {
101
+ document.addEventListener("selectionchange", onSelectionChange);
102
+ }
103
+ });
104
+
105
+ onUnmounted(() => {
106
+ if (onSelectionChange) {
107
+ document.removeEventListener("selectionchange", onSelectionChange);
108
+ }
109
+ // Cleanup content listeners
110
+ setupContentListeners(null);
111
+ });
112
+
113
+ return {
114
+ isEditorFocused
115
+ };
116
+ }