@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
JavaScript
"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