overtype 1.0.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.
@@ -0,0 +1,781 @@
1
+ /**
2
+ * OverType - A lightweight markdown editor library with perfect WYSIWYG alignment
3
+ * @version 1.0.0
4
+ * @license MIT
5
+ */
6
+
7
+ import { MarkdownParser } from './parser.js';
8
+ import { ShortcutsManager } from './shortcuts.js';
9
+ import { generateStyles } from './styles.js';
10
+ import { getTheme, mergeTheme, solar } from './themes.js';
11
+ import { Toolbar } from './toolbar.js';
12
+
13
+ /**
14
+ * OverType Editor Class
15
+ */
16
+ class OverType {
17
+ // Static properties
18
+ static instances = new WeakMap();
19
+ static stylesInjected = false;
20
+ static globalListenersInitialized = false;
21
+ static instanceCount = 0;
22
+
23
+ /**
24
+ * Constructor - Always returns an array of instances
25
+ * @param {string|Element|NodeList|Array} target - Target element(s)
26
+ * @param {Object} options - Configuration options
27
+ * @returns {Array} Array of OverType instances
28
+ */
29
+ constructor(target, options = {}) {
30
+ // Convert target to array of elements
31
+ let elements;
32
+
33
+ if (typeof target === 'string') {
34
+ elements = document.querySelectorAll(target);
35
+ if (elements.length === 0) {
36
+ throw new Error(`No elements found for selector: ${target}`);
37
+ }
38
+ elements = Array.from(elements);
39
+ } else if (target instanceof Element) {
40
+ elements = [target];
41
+ } else if (target instanceof NodeList) {
42
+ elements = Array.from(target);
43
+ } else if (Array.isArray(target)) {
44
+ elements = target;
45
+ } else {
46
+ throw new Error('Invalid target: must be selector string, Element, NodeList, or Array');
47
+ }
48
+
49
+ // Initialize all elements and return array
50
+ const instances = elements.map(element => {
51
+ // Check for existing instance
52
+ if (element.overTypeInstance) {
53
+ // Re-init existing instance
54
+ element.overTypeInstance.reinit(options);
55
+ return element.overTypeInstance;
56
+ }
57
+
58
+ // Create new instance
59
+ const instance = Object.create(OverType.prototype);
60
+ instance._init(element, options);
61
+ element.overTypeInstance = instance;
62
+ OverType.instances.set(element, instance);
63
+ return instance;
64
+ });
65
+
66
+ return instances;
67
+ }
68
+
69
+ /**
70
+ * Internal initialization
71
+ * @private
72
+ */
73
+ _init(element, options = {}) {
74
+ this.element = element;
75
+ this.options = this._mergeOptions(options);
76
+ this.instanceId = ++OverType.instanceCount;
77
+ this.initialized = false;
78
+
79
+ // Inject styles if needed
80
+ OverType.injectStyles();
81
+
82
+ // Initialize global listeners
83
+ OverType.initGlobalListeners();
84
+
85
+ // Check for existing OverType DOM structure
86
+ const container = element.querySelector('.overtype-container');
87
+ const wrapper = element.querySelector('.overtype-wrapper');
88
+ if (container || wrapper) {
89
+ this._recoverFromDOM(container, wrapper);
90
+ } else {
91
+ this._buildFromScratch();
92
+ }
93
+
94
+ // Setup shortcuts manager
95
+ this.shortcuts = new ShortcutsManager(this);
96
+
97
+ // Setup toolbar if enabled
98
+ if (this.options.toolbar) {
99
+ this.toolbar = new Toolbar(this);
100
+ this.toolbar.create();
101
+
102
+ // Update toolbar states on selection change
103
+ this.textarea.addEventListener('selectionchange', () => {
104
+ this.toolbar.updateButtonStates();
105
+ });
106
+ this.textarea.addEventListener('input', () => {
107
+ this.toolbar.updateButtonStates();
108
+ });
109
+ }
110
+
111
+ // Mark as initialized
112
+ this.initialized = true;
113
+
114
+ // Call onChange if provided
115
+ if (this.options.onChange) {
116
+ this.options.onChange(this.getValue(), this);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Merge user options with defaults
122
+ * @private
123
+ */
124
+ _mergeOptions(options) {
125
+ const defaults = {
126
+ // Typography
127
+ fontSize: '14px',
128
+ lineHeight: 1.6,
129
+ fontFamily: "'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace",
130
+ padding: '16px',
131
+
132
+ // Mobile styles
133
+ mobile: {
134
+ fontSize: '16px', // Prevent zoom on iOS
135
+ padding: '12px',
136
+ lineHeight: 1.5
137
+ },
138
+
139
+ // Behavior
140
+ autofocus: false,
141
+ placeholder: 'Start typing...',
142
+ value: '',
143
+
144
+ // Callbacks
145
+ onChange: null,
146
+ onKeydown: null,
147
+
148
+ // Features
149
+ showActiveLineRaw: false,
150
+ showStats: false,
151
+ toolbar: false,
152
+ statsFormatter: null
153
+ };
154
+
155
+ // Remove theme and colors from options - these are now global
156
+ const { theme, colors, ...cleanOptions } = options;
157
+
158
+ return {
159
+ ...defaults,
160
+ ...cleanOptions
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Recover from existing DOM structure
166
+ * @private
167
+ */
168
+ _recoverFromDOM(container, wrapper) {
169
+ // Handle old structure (wrapper only) or new structure (container + wrapper)
170
+ if (container && container.classList.contains('overtype-container')) {
171
+ this.container = container;
172
+ this.wrapper = container.querySelector('.overtype-wrapper');
173
+ } else if (wrapper) {
174
+ // Old structure - just wrapper, no container
175
+ this.wrapper = wrapper;
176
+ // Wrap it in a container for consistency
177
+ this.container = document.createElement('div');
178
+ this.container.className = 'overtype-container';
179
+ const currentTheme = OverType.currentTheme || solar;
180
+ const themeName = typeof currentTheme === 'string' ? currentTheme : currentTheme.name;
181
+ if (themeName) {
182
+ this.container.setAttribute('data-theme', themeName);
183
+ }
184
+ wrapper.parentNode.insertBefore(this.container, wrapper);
185
+ this.container.appendChild(wrapper);
186
+ }
187
+
188
+ if (!this.wrapper) {
189
+ // No valid structure found
190
+ if (container) container.remove();
191
+ if (wrapper) wrapper.remove();
192
+ this._buildFromScratch();
193
+ return;
194
+ }
195
+
196
+ this.textarea = this.wrapper.querySelector('.overtype-input');
197
+ this.preview = this.wrapper.querySelector('.overtype-preview');
198
+
199
+ if (!this.textarea || !this.preview) {
200
+ // Partial DOM - clear and rebuild
201
+ this.container.remove();
202
+ this._buildFromScratch();
203
+ return;
204
+ }
205
+
206
+ // Store reference on wrapper
207
+ this.wrapper._instance = this;
208
+
209
+ // Apply instance-specific styles via CSS custom properties
210
+ if (this.options.fontSize) {
211
+ this.wrapper.style.setProperty('--instance-font-size', this.options.fontSize);
212
+ }
213
+ if (this.options.lineHeight) {
214
+ this.wrapper.style.setProperty('--instance-line-height', String(this.options.lineHeight));
215
+ }
216
+ if (this.options.padding) {
217
+ this.wrapper.style.setProperty('--instance-padding', this.options.padding);
218
+ }
219
+
220
+ // Disable autofill, spellcheck, and extensions
221
+ this._configureTextarea();
222
+
223
+ // Apply any new options
224
+ this._applyOptions();
225
+ }
226
+
227
+ /**
228
+ * Build editor from scratch
229
+ * @private
230
+ */
231
+ _buildFromScratch() {
232
+ // Extract any existing content
233
+ const content = this._extractContent();
234
+
235
+ // Clear element
236
+ this.element.innerHTML = '';
237
+
238
+ // Create DOM structure
239
+ this._createDOM();
240
+
241
+ // Set initial content
242
+ if (content || this.options.value) {
243
+ this.setValue(content || this.options.value);
244
+ }
245
+
246
+ // Apply options
247
+ this._applyOptions();
248
+ }
249
+
250
+ /**
251
+ * Extract content from element
252
+ * @private
253
+ */
254
+ _extractContent() {
255
+ // Look for existing OverType textarea
256
+ const textarea = this.element.querySelector('.overtype-input');
257
+ if (textarea) return textarea.value;
258
+
259
+ // Use element's text content as fallback
260
+ return this.element.textContent || '';
261
+ }
262
+
263
+ /**
264
+ * Create DOM structure
265
+ * @private
266
+ */
267
+ _createDOM() {
268
+ // Create container that will hold toolbar and editor
269
+ this.container = document.createElement('div');
270
+ this.container.className = 'overtype-container';
271
+
272
+ // Set current global theme on container
273
+ const currentTheme = OverType.currentTheme || solar;
274
+ const themeName = typeof currentTheme === 'string' ? currentTheme : currentTheme.name;
275
+ if (themeName) {
276
+ this.container.setAttribute('data-theme', themeName);
277
+ }
278
+
279
+ // Create wrapper for editor
280
+ this.wrapper = document.createElement('div');
281
+ this.wrapper.className = 'overtype-wrapper';
282
+
283
+ // Add stats wrapper class if stats are enabled
284
+ if (this.options.showStats) {
285
+ this.wrapper.classList.add('with-stats');
286
+ }
287
+
288
+ // Apply instance-specific styles via CSS custom properties
289
+ if (this.options.fontSize) {
290
+ this.wrapper.style.setProperty('--instance-font-size', this.options.fontSize);
291
+ }
292
+ if (this.options.lineHeight) {
293
+ this.wrapper.style.setProperty('--instance-line-height', String(this.options.lineHeight));
294
+ }
295
+ if (this.options.padding) {
296
+ this.wrapper.style.setProperty('--instance-padding', this.options.padding);
297
+ }
298
+
299
+ this.wrapper._instance = this;
300
+
301
+ // Create textarea
302
+ this.textarea = document.createElement('textarea');
303
+ this.textarea.className = 'overtype-input';
304
+ this.textarea.placeholder = this.options.placeholder;
305
+ this._configureTextarea();
306
+
307
+ // Create preview div
308
+ this.preview = document.createElement('div');
309
+ this.preview.className = 'overtype-preview';
310
+ this.preview.setAttribute('aria-hidden', 'true');
311
+
312
+ // Assemble DOM
313
+ this.wrapper.appendChild(this.textarea);
314
+ this.wrapper.appendChild(this.preview);
315
+
316
+ // Add stats bar if enabled
317
+ if (this.options.showStats) {
318
+ this.statsBar = document.createElement('div');
319
+ this.statsBar.className = 'overtype-stats';
320
+ this.wrapper.appendChild(this.statsBar);
321
+ this._updateStats();
322
+ }
323
+
324
+ // Add wrapper to container
325
+ this.container.appendChild(this.wrapper);
326
+
327
+ // Add container to element
328
+ this.element.appendChild(this.container);
329
+ }
330
+
331
+ /**
332
+ * Configure textarea attributes
333
+ * @private
334
+ */
335
+ _configureTextarea() {
336
+ this.textarea.setAttribute('autocomplete', 'off');
337
+ this.textarea.setAttribute('autocorrect', 'off');
338
+ this.textarea.setAttribute('autocapitalize', 'off');
339
+ this.textarea.setAttribute('spellcheck', 'false');
340
+ this.textarea.setAttribute('data-gramm', 'false');
341
+ this.textarea.setAttribute('data-gramm_editor', 'false');
342
+ this.textarea.setAttribute('data-enable-grammarly', 'false');
343
+ }
344
+
345
+ /**
346
+ * Apply options to the editor
347
+ * @private
348
+ */
349
+ _applyOptions() {
350
+ // Apply autofocus
351
+ if (this.options.autofocus) {
352
+ this.textarea.focus();
353
+ }
354
+
355
+ // Update preview with initial content
356
+ this.updatePreview();
357
+ }
358
+
359
+ /**
360
+ * Update preview with parsed markdown
361
+ */
362
+ updatePreview() {
363
+ const text = this.textarea.value;
364
+ const cursorPos = this.textarea.selectionStart;
365
+ const activeLine = this._getCurrentLine(text, cursorPos);
366
+
367
+ // Parse markdown
368
+ const html = MarkdownParser.parse(text, activeLine, this.options.showActiveLineRaw);
369
+ this.preview.innerHTML = html || '<span style="color: #808080;">Start typing...</span>';
370
+
371
+ // Apply code block backgrounds
372
+ this._applyCodeBlockBackgrounds();
373
+
374
+ // Update stats if enabled
375
+ if (this.options.showStats && this.statsBar) {
376
+ this._updateStats();
377
+ }
378
+
379
+ // Trigger onChange callback
380
+ if (this.options.onChange && this.initialized) {
381
+ this.options.onChange(text, this);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Apply background styling to code blocks
387
+ * @private
388
+ */
389
+ _applyCodeBlockBackgrounds() {
390
+ // Find all code fence elements
391
+ const codeFences = this.preview.querySelectorAll('.code-fence');
392
+
393
+ // Process pairs of code fences
394
+ for (let i = 0; i < codeFences.length - 1; i += 2) {
395
+ const openFence = codeFences[i];
396
+ const closeFence = codeFences[i + 1];
397
+
398
+ // Get parent divs
399
+ const openParent = openFence.parentElement;
400
+ const closeParent = closeFence.parentElement;
401
+
402
+ if (!openParent || !closeParent) continue;
403
+
404
+ // Make fences display: block
405
+ openFence.style.display = 'block';
406
+ closeFence.style.display = 'block';
407
+
408
+ // Apply class to parent divs
409
+ openParent.classList.add('code-block-line');
410
+ closeParent.classList.add('code-block-line');
411
+
412
+ // Apply class to all divs between the parent divs
413
+ let currentDiv = openParent.nextElementSibling;
414
+ while (currentDiv && currentDiv !== closeParent) {
415
+ // Apply class to divs between the fences
416
+ if (currentDiv.tagName === 'DIV') {
417
+ currentDiv.classList.add('code-block-line');
418
+ }
419
+
420
+ // Move to next sibling
421
+ currentDiv = currentDiv.nextElementSibling;
422
+
423
+ // Safety check to prevent infinite loop
424
+ if (!currentDiv) break;
425
+ }
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Get current line number from cursor position
431
+ * @private
432
+ */
433
+ _getCurrentLine(text, cursorPos) {
434
+ const lines = text.substring(0, cursorPos).split('\n');
435
+ return lines.length - 1;
436
+ }
437
+
438
+ /**
439
+ * Handle input events
440
+ * @private
441
+ */
442
+ handleInput(event) {
443
+ this.updatePreview();
444
+ }
445
+
446
+ /**
447
+ * Handle keydown events
448
+ * @private
449
+ */
450
+ handleKeydown(event) {
451
+ // Let shortcuts manager handle it first
452
+ const handled = this.shortcuts.handleKeydown(event);
453
+
454
+ // Call user callback if provided
455
+ if (!handled && this.options.onKeydown) {
456
+ this.options.onKeydown(event, this);
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Handle scroll events
462
+ * @private
463
+ */
464
+ handleScroll(event) {
465
+ // Sync preview scroll with textarea
466
+ this.preview.scrollTop = this.textarea.scrollTop;
467
+ this.preview.scrollLeft = this.textarea.scrollLeft;
468
+ }
469
+
470
+ /**
471
+ * Get editor content
472
+ * @returns {string} Current markdown content
473
+ */
474
+ getValue() {
475
+ return this.textarea.value;
476
+ }
477
+
478
+ /**
479
+ * Set editor content
480
+ * @param {string} value - Markdown content to set
481
+ */
482
+ setValue(value) {
483
+ this.textarea.value = value;
484
+ this.updatePreview();
485
+ }
486
+
487
+
488
+ /**
489
+ * Focus the editor
490
+ */
491
+ focus() {
492
+ this.textarea.focus();
493
+ }
494
+
495
+ /**
496
+ * Blur the editor
497
+ */
498
+ blur() {
499
+ this.textarea.blur();
500
+ }
501
+
502
+ /**
503
+ * Check if editor is initialized
504
+ * @returns {boolean}
505
+ */
506
+ isInitialized() {
507
+ return this.initialized;
508
+ }
509
+
510
+ /**
511
+ * Re-initialize with new options
512
+ * @param {Object} options - New options to apply
513
+ */
514
+ reinit(options = {}) {
515
+ this.options = this._mergeOptions({ ...this.options, ...options });
516
+ this._applyOptions();
517
+ this.updatePreview();
518
+ }
519
+
520
+ /**
521
+ * Update stats bar
522
+ * @private
523
+ */
524
+ _updateStats() {
525
+ if (!this.statsBar) return;
526
+
527
+ const value = this.textarea.value;
528
+ const lines = value.split('\n');
529
+ const chars = value.length;
530
+ const words = value.split(/\s+/).filter(w => w.length > 0).length;
531
+
532
+ // Calculate line and column
533
+ const selectionStart = this.textarea.selectionStart;
534
+ const beforeCursor = value.substring(0, selectionStart);
535
+ const linesBeforeCursor = beforeCursor.split('\n');
536
+ const currentLine = linesBeforeCursor.length;
537
+ const currentColumn = linesBeforeCursor[linesBeforeCursor.length - 1].length + 1;
538
+
539
+ // Use custom formatter if provided
540
+ if (this.options.statsFormatter) {
541
+ this.statsBar.innerHTML = this.options.statsFormatter({
542
+ chars,
543
+ words,
544
+ lines: lines.length,
545
+ line: currentLine,
546
+ column: currentColumn
547
+ });
548
+ } else {
549
+ // Default format with live dot
550
+ this.statsBar.innerHTML = `
551
+ <div class="overtype-stat">
552
+ <span class="live-dot"></span>
553
+ <span>${chars} chars, ${words} words, ${lines.length} lines</span>
554
+ </div>
555
+ <div class="overtype-stat">Line ${currentLine}, Col ${currentColumn}</div>
556
+ `;
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Show or hide stats bar
562
+ * @param {boolean} show - Whether to show stats
563
+ */
564
+ showStats(show) {
565
+ this.options.showStats = show;
566
+
567
+ if (show && !this.statsBar) {
568
+ // Create stats bar
569
+ this.statsBar = document.createElement('div');
570
+ this.statsBar.className = 'overtype-stats';
571
+ this.wrapper.appendChild(this.statsBar);
572
+ this.wrapper.classList.add('with-stats');
573
+ this._updateStats();
574
+ } else if (!show && this.statsBar) {
575
+ // Remove stats bar
576
+ this.statsBar.remove();
577
+ this.statsBar = null;
578
+ this.wrapper.classList.remove('with-stats');
579
+ }
580
+ }
581
+
582
+ /**
583
+ * Destroy the editor instance
584
+ */
585
+ destroy() {
586
+ // Remove instance reference
587
+ this.element.overTypeInstance = null;
588
+ OverType.instances.delete(this.element);
589
+
590
+ // Cleanup shortcuts
591
+ if (this.shortcuts) {
592
+ this.shortcuts.destroy();
593
+ }
594
+
595
+ // Remove DOM if created by us
596
+ if (this.wrapper) {
597
+ const content = this.getValue();
598
+ this.wrapper.remove();
599
+
600
+ // Restore original content
601
+ this.element.textContent = content;
602
+ }
603
+
604
+ this.initialized = false;
605
+ }
606
+
607
+ // ===== Static Methods =====
608
+
609
+ /**
610
+ * Initialize multiple editors (static convenience method)
611
+ * @param {string|Element|NodeList|Array} target - Target element(s)
612
+ * @param {Object} options - Configuration options
613
+ * @returns {Array} Array of OverType instances
614
+ */
615
+ static init(target, options = {}) {
616
+ return new OverType(target, options);
617
+ }
618
+
619
+ /**
620
+ * Get instance from element
621
+ * @param {Element} element - DOM element
622
+ * @returns {OverType|null} OverType instance or null
623
+ */
624
+ static getInstance(element) {
625
+ return element.overTypeInstance || OverType.instances.get(element) || null;
626
+ }
627
+
628
+ /**
629
+ * Destroy all instances
630
+ */
631
+ static destroyAll() {
632
+ const elements = document.querySelectorAll('[data-overtype-instance]');
633
+ elements.forEach(element => {
634
+ const instance = OverType.getInstance(element);
635
+ if (instance) {
636
+ instance.destroy();
637
+ }
638
+ });
639
+ }
640
+
641
+ /**
642
+ * Inject styles into the document
643
+ * @param {boolean} force - Force re-injection
644
+ */
645
+ static injectStyles(force = false) {
646
+ if (OverType.stylesInjected && !force) return;
647
+
648
+ // Remove any existing OverType styles
649
+ const existing = document.querySelector('style.overtype-styles');
650
+ if (existing) {
651
+ existing.remove();
652
+ }
653
+
654
+ // Generate and inject new styles with current theme
655
+ const theme = OverType.currentTheme || solar;
656
+ const styles = generateStyles({ theme });
657
+ const styleEl = document.createElement('style');
658
+ styleEl.className = 'overtype-styles';
659
+ styleEl.textContent = styles;
660
+ document.head.appendChild(styleEl);
661
+
662
+ OverType.stylesInjected = true;
663
+ }
664
+
665
+ /**
666
+ * Set global theme for all OverType instances
667
+ * @param {string|Object} theme - Theme name or custom theme object
668
+ * @param {Object} customColors - Optional color overrides
669
+ */
670
+ static setTheme(theme, customColors = null) {
671
+ // Process theme
672
+ let themeObj = typeof theme === 'string' ? getTheme(theme) : theme;
673
+
674
+ // Apply custom colors if provided
675
+ if (customColors) {
676
+ themeObj = mergeTheme(themeObj, customColors);
677
+ }
678
+
679
+ // Store as current theme
680
+ OverType.currentTheme = themeObj;
681
+
682
+ // Re-inject styles with new theme
683
+ OverType.injectStyles(true);
684
+
685
+ // Update all existing instances - update container theme attribute
686
+ document.querySelectorAll('.overtype-container').forEach(container => {
687
+ const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
688
+ if (themeName) {
689
+ container.setAttribute('data-theme', themeName);
690
+ }
691
+ });
692
+
693
+ // Also handle any old-style wrappers without containers
694
+ document.querySelectorAll('.overtype-wrapper').forEach(wrapper => {
695
+ if (!wrapper.closest('.overtype-container')) {
696
+ const themeName = typeof themeObj === 'string' ? themeObj : themeObj.name;
697
+ if (themeName) {
698
+ wrapper.setAttribute('data-theme', themeName);
699
+ }
700
+ }
701
+
702
+ // Trigger preview update for the instance
703
+ const instance = wrapper._instance;
704
+ if (instance) {
705
+ instance.updatePreview();
706
+ }
707
+ });
708
+ }
709
+
710
+ /**
711
+ * Initialize global event listeners
712
+ */
713
+ static initGlobalListeners() {
714
+ if (OverType.globalListenersInitialized) return;
715
+
716
+ // Input event
717
+ document.addEventListener('input', (e) => {
718
+ if (e.target && e.target.classList && e.target.classList.contains('overtype-input')) {
719
+ const wrapper = e.target.closest('.overtype-wrapper');
720
+ const instance = wrapper?._instance;
721
+ if (instance) instance.handleInput(e);
722
+ }
723
+ });
724
+
725
+ // Keydown event
726
+ document.addEventListener('keydown', (e) => {
727
+ if (e.target && e.target.classList && e.target.classList.contains('overtype-input')) {
728
+ const wrapper = e.target.closest('.overtype-wrapper');
729
+ const instance = wrapper?._instance;
730
+ if (instance) instance.handleKeydown(e);
731
+ }
732
+ });
733
+
734
+ // Scroll event
735
+ document.addEventListener('scroll', (e) => {
736
+ if (e.target && e.target.classList && e.target.classList.contains('overtype-input')) {
737
+ const wrapper = e.target.closest('.overtype-wrapper');
738
+ const instance = wrapper?._instance;
739
+ if (instance) instance.handleScroll(e);
740
+ }
741
+ }, true);
742
+
743
+ // Selection change event
744
+ document.addEventListener('selectionchange', (e) => {
745
+ const activeElement = document.activeElement;
746
+ if (activeElement && activeElement.classList.contains('overtype-input')) {
747
+ const wrapper = activeElement.closest('.overtype-wrapper');
748
+ const instance = wrapper?._instance;
749
+ if (instance) {
750
+ // Update stats bar for cursor position
751
+ if (instance.options.showStats && instance.statsBar) {
752
+ instance._updateStats();
753
+ }
754
+ // Debounce updates
755
+ clearTimeout(instance._selectionTimeout);
756
+ instance._selectionTimeout = setTimeout(() => {
757
+ instance.updatePreview();
758
+ }, 50);
759
+ }
760
+ }
761
+ });
762
+
763
+ OverType.globalListenersInitialized = true;
764
+ }
765
+ }
766
+
767
+ // Export classes for advanced usage
768
+ OverType.MarkdownParser = MarkdownParser;
769
+ OverType.ShortcutsManager = ShortcutsManager;
770
+
771
+ // Export theme utilities
772
+ OverType.themes = { solar, cave: getTheme('cave') };
773
+ OverType.getTheme = getTheme;
774
+
775
+ // Set default theme
776
+ OverType.currentTheme = solar;
777
+
778
+ // For IIFE builds, esbuild needs the class as the default export
779
+ export default OverType;
780
+ // Also export as named for ESM compatibility
781
+ export { OverType };