UNPKG

@mvp-factory/holy-editor

Version:

Rich text editor with Bible verse slash commands and PWA keyboard tracking, extracted from Holy Habit project

327 lines 12.3 kB
"use strict"; /** * PWA Keyboard Tracker * * Advanced keyboard tracking for PWA environments * Extracted from Holy Habit holy-editor-pro.js */ Object.defineProperty(exports, "__esModule", { value: true }); exports.PWAKeyboardTracker = void 0; class PWAKeyboardTracker { constructor(settings) { this.toolbar = null; this.editorRoot = null; this.lastKeyboardHeight = 0; this.updateTimeout = null; this.isActive = false; // Debounced update handler this.debouncedUpdate = () => { if (!this.isActive) return; if (this.settings.debounceTime === 0) { requestAnimationFrame(() => this.updateToolbarPosition()); } else { if (this.updateTimeout) { clearTimeout(this.updateTimeout); } this.updateTimeout = setTimeout(() => this.updateToolbarPosition(), this.settings.debounceTime); } }; // Focus out handler this.handleFocusOut = () => { setTimeout(() => { if (!this.isInputFocused() && this.toolbar) { this.toolbar.style.transform = 'translateY(0)'; this.toolbar.style.webkitTransform = 'translateY(0)'; document.body.classList.remove('keyboard-active'); this.toolbar.classList.remove('keyboard-tracking'); this.lastKeyboardHeight = 0; } }, 100); }; this.settings = { threshold: 10, keyboardMin: 150, debounceTime: 0, ...settings }; } /** * Initialize keyboard tracking */ initialize(editorRoot, toolbarSelector = '.vertical-toolbar, .mobile-toolbar, .toolbar') { this.editorRoot = editorRoot; this.toolbar = document.querySelector(toolbarSelector); if (!this.toolbar) { console.warn('⚠️ Toolbar element not found for keyboard tracking'); return; } console.log('📱 PWA keyboard tracking initialization'); // Log environment information const env = this.getEnvironmentInfo(); console.log('📱 Environment info:', env); // Setup tracking based on Visual Viewport API support if (this.hasVisualViewport()) { this.setupEnhancedKeyboardTracking(); } else { this.setupFallbackKeyboardTracking(); } this.isActive = true; } /** * Destroy keyboard tracking */ destroy() { this.isActive = false; if (this.updateTimeout) { clearTimeout(this.updateTimeout); this.updateTimeout = null; } // Remove event listeners const events = ['resize', 'orientationchange', 'scroll']; events.forEach(evt => window.removeEventListener(evt, this.debouncedUpdate)); if (this.hasVisualViewport()) { window.visualViewport?.removeEventListener('resize', this.debouncedUpdate); window.visualViewport?.removeEventListener('scroll', this.debouncedUpdate); } // Reset toolbar position if (this.toolbar) { this.toolbar.style.transform = 'translateY(0)'; this.toolbar.style.webkitTransform = 'translateY(0)'; this.toolbar.classList.remove('keyboard-tracking'); } document.body.classList.remove('keyboard-active'); console.log('📱 PWA keyboard tracking destroyed'); } /** * Get current environment information */ getEnvironmentInfo() { return { isPWA: this.isPWA(), isIOS: this.isIOS(), isAndroid: this.isAndroid(), hasVisualViewport: this.hasVisualViewport(), isAndroidChrome: this.isAndroidChrome(), isSamsungInternet: this.isSamsungInternet() }; } /** * Update keyboard tracking settings */ updateSettings(newSettings) { this.settings = { ...this.settings, ...newSettings }; console.log('📱 Keyboard tracking settings updated:', this.settings); } // Platform detection methods isPWA() { return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true || document.referrer.includes('android-app://'); } isIOS() { return /iP(hone|od|ad)/.test(navigator.userAgent) && !window.MSStream; } isAndroid() { return /Android/.test(navigator.userAgent); } isAndroidChrome() { return /Android/.test(navigator.userAgent) && /Chrome/.test(navigator.userAgent); } isSamsungInternet() { return /SamsungBrowser/.test(navigator.userAgent); } hasVisualViewport() { return 'visualViewport' in window; } // Enhanced keyboard tracking (Visual Viewport API) setupEnhancedKeyboardTracking() { console.log('📱 Enhanced keyboard tracking enabled (Visual Viewport API)'); // Event listener registration const events = ['resize', 'orientationchange', 'scroll']; events.forEach(evt => window.addEventListener(evt, this.debouncedUpdate)); // Visual Viewport events if (window.visualViewport) { window.visualViewport.addEventListener('resize', this.debouncedUpdate); window.visualViewport.addEventListener('scroll', this.debouncedUpdate); } // Focus events document.body.addEventListener('focusin', this.debouncedUpdate); document.body.addEventListener('focusout', this.handleFocusOut); // Initial position setup setTimeout(() => this.updateToolbarPosition(), 100); } // Fallback keyboard tracking setupFallbackKeyboardTracking() { console.log('📱 Fallback keyboard tracking enabled (height estimation)'); let isKeyboardVisible = false; let initialHeight = window.innerHeight; const showKeyboard = () => { if (!isKeyboardVisible && this.toolbar) { isKeyboardVisible = true; document.body.classList.add('keyboard-active'); this.toolbar.classList.add('keyboard-up'); // Estimated keyboard height const estimatedHeight = this.isIOS() ? 260 : 300; this.applyToolbarTransform(this.toolbar, estimatedHeight); console.log('📱 Keyboard shown (estimated):', estimatedHeight); } }; const hideKeyboard = () => { if (isKeyboardVisible && this.toolbar) { isKeyboardVisible = false; document.body.classList.remove('keyboard-active'); this.toolbar.classList.remove('keyboard-up'); this.toolbar.style.transform = 'translateY(0)'; this.toolbar.style.webkitTransform = 'translateY(0)'; console.log('📱 Keyboard hidden'); } }; // Height change detection const checkHeightChange = () => { const currentHeight = window.innerHeight; const heightDiff = initialHeight - currentHeight; if (heightDiff > 100) { showKeyboard(); } else if (heightDiff < 50) { hideKeyboard(); } }; // Editor focus events if (this.editorRoot) { this.editorRoot.addEventListener('focusin', () => { setTimeout(showKeyboard, 300); }); this.editorRoot.addEventListener('focusout', () => { setTimeout(() => { if (!this.isInputFocused()) { hideKeyboard(); } }, 200); }); } // Global focus events window.addEventListener('focusin', (e) => { if (e.target && e.target.matches('input, textarea, [contenteditable]')) { setTimeout(showKeyboard, 300); } }); window.addEventListener('focusout', () => { setTimeout(() => { if (!this.isInputFocused()) { hideKeyboard(); } }, 200); }); // Window resize for keyboard detection window.addEventListener('resize', () => { setTimeout(checkHeightChange, 100); }); // Orientation change window.addEventListener('orientationchange', () => { initialHeight = window.innerHeight; hideKeyboard(); }); } // Update toolbar position updateToolbarPosition() { if (!this.toolbar || !this.isActive) return; const offset = this.calculateKeyboardOffset(); // Apply transform if offset changed significantly if (Math.abs(offset - this.lastKeyboardHeight) > this.settings.threshold) { this.applyToolbarTransform(this.toolbar, offset); this.lastKeyboardHeight = offset; console.log('📏 Toolbar position adjusted:', { offset: offset, visualHeight: window.visualViewport?.height, innerHeight: window.innerHeight }); } // Update keyboard state CSS classes this.updateKeyboardClass(offset); } // Calculate keyboard offset calculateKeyboardOffset() { let offset = 0; if (this.hasVisualViewport() && window.visualViewport) { const vh = window.visualViewport.height; const keyboardHeight = window.innerHeight - vh; if (keyboardHeight > this.settings.keyboardMin) { offset = this.getPlatformSpecificOffset(keyboardHeight); } } else if (this.isInputFocused()) { // Visual Viewport API not supported, use estimation offset = 300; } return Math.max(0, offset); } // Platform-specific offset calculation getPlatformSpecificOffset(keyboardHeight) { // iOS PWA status bar height adjustment (20px) if (this.isIOS() && this.isPWA()) { return keyboardHeight - 20; } // Android PWA navigation bar height detection if (this.isAndroid() && this.isPWA()) { const navBarHeight = window.outerHeight - window.innerHeight; return keyboardHeight - navBarHeight; } // Regular web browser return keyboardHeight; } // Apply toolbar transform applyToolbarTransform(toolbar, offset) { toolbar.style.transform = `translateY(-${offset}px)`; toolbar.style.webkitTransform = `translateY(-${offset}px)`; toolbar.style.transition = 'transform 0.3s ease-out'; } // Update keyboard CSS classes updateKeyboardClass(offset) { if (!this.toolbar) return; if (offset > 100) { document.body.classList.add('keyboard-active'); this.toolbar.classList.add('keyboard-tracking'); } else { document.body.classList.remove('keyboard-active'); this.toolbar.classList.remove('keyboard-tracking'); } } // Check if input field is focused isInputFocused() { return document.activeElement && document.activeElement.matches('input, textarea, [contenteditable]'); } /** * Get current keyboard offset data */ getKeyboardOffsetData() { const offset = this.calculateKeyboardOffset(); return { offset, visualHeight: window.visualViewport?.height, innerHeight: window.innerHeight, keyboardHeight: window.innerHeight - (window.visualViewport?.height || window.innerHeight) }; } /** * Force toolbar position update */ forceUpdate() { this.updateToolbarPosition(); } /** * Check if keyboard tracking is active */ isTrackingActive() { return this.isActive; } } exports.PWAKeyboardTracker = PWAKeyboardTracker; //# sourceMappingURL=PWAKeyboardTracker.js.map