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
@@ -1,5 +1,5 @@
1
1
  // Code Viewer Theme
2
- // Dark theme with syntax highlighting for JSON/YAML
2
+ // Supports dark (default) and light themes with syntax highlighting for JSON/YAML
3
3
 
4
4
  .dx-code-viewer {
5
5
  width: 100%;
@@ -134,6 +134,58 @@
134
134
  color: #808080;
135
135
  }
136
136
 
137
+ // Comments - Muted green
138
+ .syntax-comment {
139
+ color: #6a9955;
140
+ }
141
+
142
+ // CSS/HTML specific
143
+ .syntax-selector {
144
+ color: #d7ba7d; // Gold/yellow
145
+ }
146
+
147
+ .syntax-property {
148
+ color: #9cdcfe; // Light blue (same as key)
149
+ }
150
+
151
+ .syntax-value {
152
+ color: #ce9178; // Orange (same as number)
153
+ }
154
+
155
+ .syntax-at-rule {
156
+ color: #c586c0; // Purple/magenta
157
+ }
158
+
159
+ // JavaScript specific
160
+ .syntax-keyword {
161
+ color: #569cd6; // Blue
162
+ }
163
+
164
+ .syntax-operator {
165
+ color: #d4d4d4; // Gray/white
166
+ }
167
+
168
+ .syntax-regex {
169
+ color: #d16969; // Red
170
+ }
171
+
172
+ .syntax-template {
173
+ color: #ce9178; // Orange (same as string/number)
174
+ }
175
+
176
+ // HTML specific
177
+ .syntax-tag {
178
+ color: #569cd6; // Blue
179
+ }
180
+
181
+ .syntax-attribute {
182
+ color: #9cdcfe; // Light blue
183
+ }
184
+
185
+ .syntax-doctype {
186
+ color: #808080; // Gray
187
+ }
188
+
137
189
 
138
190
  // ==========================================
139
191
  // Editable mode (contenteditable)
@@ -155,4 +207,208 @@
155
207
  // Caret color
156
208
  caret-color: #d4d4d4;
157
209
  }
210
+
211
+ // ==========================================
212
+ // LIGHT THEME VARIANT
213
+ // ==========================================
214
+ &.theme-light {
215
+ .code-collapsed {
216
+ background-color: #f5f5f5;
217
+
218
+ &:hover {
219
+ background-color: #ebebeb;
220
+ }
221
+
222
+ .code-collapsed-preview {
223
+ color: #333333;
224
+ }
225
+ }
226
+
227
+ .code-content {
228
+ background-color: #f8fafc;
229
+ color: #1e293b;
230
+ border: 1px solid #e2e8f0;
231
+ }
232
+
233
+ .code-footer {
234
+ background-color: #f1f5f9;
235
+ border-top: 1px solid #e2e8f0;
236
+
237
+ &.has-error {
238
+ background-color: #fee2e2;
239
+ border-top: 1px solid #ef4444;
240
+ }
241
+ }
242
+
243
+ .language-badge {
244
+ background-color: #e2e8f0;
245
+ color: #64748b;
246
+ }
247
+
248
+ // Light theme syntax highlighting
249
+ .syntax-key {
250
+ color: #0369a1;
251
+ }
252
+
253
+ .syntax-string {
254
+ color: #15803d;
255
+ }
256
+
257
+ .syntax-number {
258
+ color: #c2410c;
259
+ }
260
+
261
+ .syntax-boolean {
262
+ color: #7c3aed;
263
+ }
264
+
265
+ .syntax-null {
266
+ color: #7c3aed;
267
+ }
268
+
269
+ .syntax-punctuation {
270
+ color: #64748b;
271
+ }
272
+
273
+ // Light theme - Comments
274
+ .syntax-comment {
275
+ color: #6a9955;
276
+ }
277
+
278
+ // Light theme - CSS/HTML specific
279
+ .syntax-selector {
280
+ color: #b45309; // Amber/brown
281
+ }
282
+
283
+ .syntax-property {
284
+ color: #0369a1; // Blue (same as key)
285
+ }
286
+
287
+ .syntax-value {
288
+ color: #c2410c; // Orange (same as number)
289
+ }
290
+
291
+ .syntax-at-rule {
292
+ color: #7c3aed; // Purple
293
+ }
294
+
295
+ // Light theme - JavaScript specific
296
+ .syntax-keyword {
297
+ color: #0369a1; // Blue
298
+ }
299
+
300
+ .syntax-operator {
301
+ color: #64748b; // Gray
302
+ }
303
+
304
+ .syntax-regex {
305
+ color: #be123c; // Red/rose
306
+ }
307
+
308
+ .syntax-template {
309
+ color: #c2410c; // Orange
310
+ }
311
+
312
+ // Light theme - HTML specific
313
+ .syntax-tag {
314
+ color: #0369a1; // Blue
315
+ }
316
+
317
+ .syntax-attribute {
318
+ color: #0891b2; // Cyan
319
+ }
320
+
321
+ .syntax-doctype {
322
+ color: #64748b; // Gray
323
+ }
324
+
325
+ .code-content.is-editable {
326
+ caret-color: #1e293b;
327
+
328
+ &:focus {
329
+ border-color: rgba(14, 165, 233, 0.6);
330
+ }
331
+
332
+ &:hover:not(:focus) {
333
+ border-color: rgba(14, 165, 233, 0.3);
334
+ }
335
+ }
336
+
337
+ // Light theme - Language badge component overrides
338
+ .dx-language-badge-container {
339
+ // Container needs no background changes
340
+ }
341
+
342
+ .dx-language-badge {
343
+ background: rgba(0, 0, 0, 0.08);
344
+ color: #64748b;
345
+
346
+ &.is-active {
347
+ background: rgba(0, 0, 0, 0.12);
348
+ }
349
+ }
350
+
351
+ .dx-language-option {
352
+ background: rgba(0, 0, 0, 0.06);
353
+ color: #64748b;
354
+ border-right: 1px solid rgba(0, 0, 0, 0.1);
355
+
356
+ &:hover {
357
+ background: rgba(0, 0, 0, 0.12);
358
+ color: #1e293b;
359
+ }
360
+ }
361
+
362
+ .dx-language-search-trigger {
363
+ // Inherits from dx-language-option
364
+ }
365
+
366
+ .dx-language-search-panel {
367
+ background: #f8fafc;
368
+ border: 1px solid #e2e8f0;
369
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
370
+ }
371
+
372
+ .dx-language-search-input {
373
+ background: #f1f5f9;
374
+ border-bottom: 1px solid #e2e8f0;
375
+ color: #1e293b;
376
+
377
+ &::placeholder {
378
+ color: #94a3b8;
379
+ }
380
+
381
+ &:focus {
382
+ background: #e2e8f0;
383
+ }
384
+ }
385
+
386
+ .dx-language-search-list {
387
+ &::-webkit-scrollbar-track {
388
+ background: #f1f5f9;
389
+ }
390
+
391
+ &::-webkit-scrollbar-thumb {
392
+ background: #cbd5e1;
393
+
394
+ &:hover {
395
+ background: #94a3b8;
396
+ }
397
+ }
398
+ }
399
+
400
+ .dx-language-search-item {
401
+ color: #64748b;
402
+
403
+ &:hover,
404
+ &.is-selected {
405
+ background: #e2e8f0;
406
+ color: #1e293b;
407
+ }
408
+ }
409
+
410
+ .dx-language-search-empty {
411
+ color: #94a3b8;
412
+ }
413
+ }
158
414
  }
@@ -1,10 +1,10 @@
1
- @import "action-table";
2
- @import "buttons";
3
- @import "carousels";
4
- @import "code";
5
- @import "dialogs";
6
- @import "forms";
7
- @import "markdown";
8
- @import "panels";
9
- @import "sidebar";
10
- @import "toolbar";
1
+ @use "action-table";
2
+ @use "buttons";
3
+ @use "carousels";
4
+ @use "code";
5
+ @use "dialogs";
6
+ @use "forms";
7
+ @use "markdown";
8
+ @use "panels";
9
+ @use "sidebar";
10
+ @use "toolbar";
@@ -238,4 +238,63 @@
238
238
  text-decoration: none;
239
239
  }
240
240
  }
241
+
242
+ // ==========================================
243
+ // LIGHT THEME VARIANT
244
+ // ==========================================
245
+ &.theme-light {
246
+ color: #1e293b;
247
+
248
+ // Inline code - light theme
249
+ code {
250
+ background: #f1f5f9;
251
+ color: #0f172a;
252
+ }
253
+
254
+ // Code blocks - light theme
255
+ pre {
256
+ background: #f8fafc;
257
+ border: 1px solid #e2e8f0;
258
+
259
+ code {
260
+ background: transparent;
261
+ }
262
+ }
263
+
264
+ // Blockquotes - light theme
265
+ blockquote {
266
+ border-left-color: #cbd5e1;
267
+ color: #475569;
268
+ }
269
+
270
+ // Links - light theme
271
+ a {
272
+ color: #0369a1;
273
+ }
274
+
275
+ // Horizontal rules - light theme
276
+ hr {
277
+ border-top-color: #e2e8f0;
278
+ }
279
+
280
+ // Tables - light theme
281
+ table {
282
+ th, td {
283
+ border-color: #e2e8f0;
284
+ }
285
+
286
+ th {
287
+ background: #f1f5f9;
288
+ }
289
+
290
+ tr:nth-child(even) {
291
+ background: #f8fafc;
292
+ }
293
+ }
294
+
295
+ // Highlight - light theme
296
+ mark {
297
+ background: rgba(250, 204, 21, 0.5);
298
+ }
299
+ }
241
300
  }
@@ -0,0 +1,296 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { createTestEditor, TestEditorResult } from './editorTestUtils';
3
+
4
+ describe('editorTestUtils', () => {
5
+ let editor: TestEditorResult;
6
+
7
+ afterEach(() => {
8
+ if (editor) {
9
+ editor.destroy();
10
+ }
11
+ });
12
+
13
+ describe('createTestEditor', () => {
14
+ it('should create a contenteditable container', () => {
15
+ editor = createTestEditor('<p>Hello World</p>');
16
+
17
+ expect(editor.container).toBeInstanceOf(HTMLElement);
18
+ expect(editor.container.getAttribute('contenteditable')).toBe('true');
19
+ expect(editor.container.innerHTML).toBe('<p>Hello World</p>');
20
+ });
21
+
22
+ it('should trim whitespace from initial HTML', () => {
23
+ editor = createTestEditor(' <p>Test</p> ');
24
+
25
+ expect(editor.container.innerHTML).toBe('<p>Test</p>');
26
+ });
27
+ });
28
+
29
+ describe('getHtml', () => {
30
+ it('should return current HTML content', () => {
31
+ editor = createTestEditor('<p>Initial</p>');
32
+
33
+ expect(editor.getHtml()).toBe('<p>Initial</p>');
34
+
35
+ // Modify content
36
+ editor.container.innerHTML = '<p>Modified</p>';
37
+ expect(editor.getHtml()).toBe('<p>Modified</p>');
38
+ });
39
+ });
40
+
41
+ describe('getMarkdown', () => {
42
+ it('should convert HTML to markdown', () => {
43
+ editor = createTestEditor('<p>Hello <strong>bold</strong> text</p>');
44
+
45
+ const markdown = editor.getMarkdown();
46
+ expect(markdown).toBe('Hello **bold** text');
47
+ });
48
+
49
+ it('should handle headings', () => {
50
+ editor = createTestEditor('<h2>Heading Two</h2>');
51
+
52
+ expect(editor.getMarkdown()).toBe('## Heading Two');
53
+ });
54
+
55
+ it('should handle lists', () => {
56
+ editor = createTestEditor('<ul><li>Item 1</li><li>Item 2</li></ul>');
57
+
58
+ const markdown = editor.getMarkdown();
59
+ expect(markdown).toContain('- Item 1');
60
+ expect(markdown).toContain('- Item 2');
61
+ });
62
+ });
63
+
64
+ describe('getBlock and getBlocks', () => {
65
+ beforeEach(() => {
66
+ editor = createTestEditor('<p>First</p><p>Second</p><p>Third</p>');
67
+ });
68
+
69
+ it('should get a block by index', () => {
70
+ const block0 = editor.getBlock(0);
71
+ const block1 = editor.getBlock(1);
72
+ const block2 = editor.getBlock(2);
73
+
74
+ expect(block0?.textContent).toBe('First');
75
+ expect(block1?.textContent).toBe('Second');
76
+ expect(block2?.textContent).toBe('Third');
77
+ });
78
+
79
+ it('should return null for invalid index', () => {
80
+ expect(editor.getBlock(10)).toBeNull();
81
+ expect(editor.getBlock(-1)).toBeNull();
82
+ });
83
+
84
+ it('should get all blocks', () => {
85
+ const blocks = editor.getBlocks();
86
+
87
+ expect(blocks).toHaveLength(3);
88
+ expect(blocks[0].textContent).toBe('First');
89
+ expect(blocks[2].textContent).toBe('Third');
90
+ });
91
+ });
92
+
93
+ describe('setCursor and getCursorPosition', () => {
94
+ beforeEach(() => {
95
+ editor = createTestEditor('<p>Hello World</p>');
96
+ });
97
+
98
+ it('should set cursor position in a text node', () => {
99
+ const textNode = editor.getBlock(0)?.firstChild;
100
+ if (!textNode) throw new Error('Text node not found');
101
+
102
+ editor.setCursor(textNode, 5);
103
+
104
+ const pos = editor.getCursorPosition();
105
+ expect(pos.node).toBe(textNode);
106
+ expect(pos.offset).toBe(5);
107
+ });
108
+
109
+ it('should return null node when no selection', () => {
110
+ // Clear any existing selection
111
+ window.getSelection()?.removeAllRanges();
112
+
113
+ const pos = editor.getCursorPosition();
114
+ expect(pos.node).toBeNull();
115
+ expect(pos.offset).toBe(0);
116
+ });
117
+ });
118
+
119
+ describe('setCursorInBlock and getCursorOffsetInBlock', () => {
120
+ beforeEach(() => {
121
+ editor = createTestEditor('<p>First paragraph</p><p>Second paragraph</p>');
122
+ });
123
+
124
+ it('should set cursor at offset within a block', () => {
125
+ editor.setCursorInBlock(0, 6);
126
+
127
+ const offset = editor.getCursorOffsetInBlock(0);
128
+ expect(offset).toBe(6); // After "First "
129
+ });
130
+
131
+ it('should set cursor in second block', () => {
132
+ editor.setCursorInBlock(1, 7);
133
+
134
+ const offset = editor.getCursorOffsetInBlock(1);
135
+ expect(offset).toBe(7); // After "Second "
136
+ });
137
+
138
+ it('should return -1 for invalid block index', () => {
139
+ editor.setCursorInBlock(0, 0);
140
+
141
+ expect(editor.getCursorOffsetInBlock(10)).toBe(-1);
142
+ });
143
+
144
+ it('should return -1 when cursor is not in the specified block', () => {
145
+ editor.setCursorInBlock(0, 0);
146
+
147
+ expect(editor.getCursorOffsetInBlock(1)).toBe(-1);
148
+ });
149
+ });
150
+
151
+ describe('selectRange and selectInBlock', () => {
152
+ beforeEach(() => {
153
+ editor = createTestEditor('<p>Hello World</p>');
154
+ });
155
+
156
+ it('should select a text range', () => {
157
+ const textNode = editor.getBlock(0)?.firstChild;
158
+ if (!textNode) throw new Error('Text node not found');
159
+
160
+ editor.selectRange(textNode, 0, textNode, 5);
161
+
162
+ const sel = window.getSelection();
163
+ expect(sel?.toString()).toBe('Hello');
164
+ });
165
+
166
+ it('should select text within a block by offsets', () => {
167
+ editor.selectInBlock(0, 6, 11);
168
+
169
+ const sel = window.getSelection();
170
+ expect(sel?.toString()).toBe('World');
171
+ });
172
+ });
173
+
174
+ describe('pressKey', () => {
175
+ beforeEach(() => {
176
+ editor = createTestEditor('<p>Test</p>');
177
+ });
178
+
179
+ it('should dispatch keydown event', () => {
180
+ let receivedEvent: KeyboardEvent | null = null;
181
+ editor.container.addEventListener('keydown', (e) => {
182
+ receivedEvent = e;
183
+ });
184
+
185
+ editor.pressKey('a');
186
+
187
+ expect(receivedEvent).not.toBeNull();
188
+ expect(receivedEvent!.key).toBe('a');
189
+ expect(receivedEvent!.code).toBe('KeyA');
190
+ });
191
+
192
+ it('should include modifier keys', () => {
193
+ let receivedEvent: KeyboardEvent | null = null;
194
+ editor.container.addEventListener('keydown', (e) => {
195
+ receivedEvent = e;
196
+ });
197
+
198
+ editor.pressKey('b', { ctrl: true, shift: true });
199
+
200
+ expect(receivedEvent!.ctrlKey).toBe(true);
201
+ expect(receivedEvent!.shiftKey).toBe(true);
202
+ expect(receivedEvent!.altKey).toBe(false);
203
+ expect(receivedEvent!.metaKey).toBe(false);
204
+ });
205
+
206
+ it('should handle special keys', () => {
207
+ let receivedEvent: KeyboardEvent | null = null;
208
+ editor.container.addEventListener('keydown', (e) => {
209
+ receivedEvent = e;
210
+ });
211
+
212
+ editor.pressKey('Enter');
213
+
214
+ expect(receivedEvent!.key).toBe('Enter');
215
+ expect(receivedEvent!.code).toBe('Enter');
216
+ });
217
+ });
218
+
219
+ describe('type', () => {
220
+ beforeEach(() => {
221
+ editor = createTestEditor('<p>Hello</p>');
222
+ });
223
+
224
+ it('should insert text at cursor position', () => {
225
+ const textNode = editor.getBlock(0)?.firstChild;
226
+ if (!textNode) throw new Error('Text node not found');
227
+
228
+ editor.setCursor(textNode, 5);
229
+ editor.type(' World');
230
+
231
+ expect(editor.container.textContent).toContain('Hello');
232
+ expect(editor.container.textContent).toContain('World');
233
+ });
234
+
235
+ it('should dispatch input event', () => {
236
+ let inputFired = false;
237
+ editor.container.addEventListener('input', () => {
238
+ inputFired = true;
239
+ });
240
+
241
+ const textNode = editor.getBlock(0)?.firstChild;
242
+ if (!textNode) throw new Error('Text node not found');
243
+
244
+ editor.setCursor(textNode, 0);
245
+ editor.type('X');
246
+
247
+ expect(inputFired).toBe(true);
248
+ });
249
+ });
250
+
251
+ describe('contentRef', () => {
252
+ it('should provide a Vue ref to the container', () => {
253
+ editor = createTestEditor('<p>Test</p>');
254
+
255
+ expect(editor.contentRef.value).toBe(editor.container);
256
+ });
257
+ });
258
+
259
+ describe('destroy', () => {
260
+ it('should remove the container from DOM', () => {
261
+ editor = createTestEditor('<p>Test</p>');
262
+ const container = editor.container;
263
+
264
+ expect(document.body.contains(container)).toBe(true);
265
+
266
+ editor.destroy();
267
+
268
+ expect(document.body.contains(container)).toBe(false);
269
+ });
270
+ });
271
+
272
+ describe('complex content handling', () => {
273
+ it('should handle nested inline formatting', () => {
274
+ editor = createTestEditor('<p>This is <strong><em>bold italic</em></strong> text</p>');
275
+
276
+ editor.selectInBlock(0, 8, 19);
277
+
278
+ const sel = window.getSelection();
279
+ expect(sel?.toString()).toBe('bold italic');
280
+ });
281
+
282
+ it('should handle multiple blocks with cursor operations', () => {
283
+ editor = createTestEditor('<p>Line 1</p><p>Line 2</p><p>Line 3</p>');
284
+
285
+ // Set cursor in middle block
286
+ editor.setCursorInBlock(1, 3);
287
+
288
+ const offset = editor.getCursorOffsetInBlock(1);
289
+ expect(offset).toBe(3);
290
+
291
+ // Verify cursor is not in other blocks
292
+ expect(editor.getCursorOffsetInBlock(0)).toBe(-1);
293
+ expect(editor.getCursorOffsetInBlock(2)).toBe(-1);
294
+ });
295
+ });
296
+ });