UNPKG

rynex

Version:

A minimalist TypeScript framework for building reactive web applications with no virtual DOM

530 lines (521 loc) 15.8 kB
/** * Rynex Browser Compatibility Module * Ensures consistent behavior across all major browsers (Chrome, Firefox, Safari, Edge) * Modern browser support with native APIs only - no external dependencies */ import { debugLog, debugWarn } from './debug.js'; // Browser detection result let isInitialized = false; /** * Native browser detection using userAgent */ function detectBrowserFromUA() { const ua = navigator.userAgent; let name = 'Unknown'; let version = '0'; let engine = 'Unknown'; // Detect browser if (ua.includes('Firefox/')) { name = 'Firefox'; version = ua.match(/Firefox\/(\d+\.\d+)/)?.[1] || '0'; engine = 'Gecko'; } else if (ua.includes('Edg/')) { name = 'Microsoft Edge'; version = ua.match(/Edg\/(\d+\.\d+)/)?.[1] || '0'; engine = 'Blink'; } else if (ua.includes('Chrome/')) { name = 'Chrome'; version = ua.match(/Chrome\/(\d+\.\d+)/)?.[1] || '0'; engine = 'Blink'; } else if (ua.includes('Safari/') && !ua.includes('Chrome')) { name = 'Safari'; version = ua.match(/Version\/(\d+\.\d+)/)?.[1] || '0'; engine = 'WebKit'; } return { name, version, engine }; } /** * Detect platform type */ function detectPlatform() { const ua = navigator.userAgent; if (/Mobile|Android|iPhone|iPad|iPod/.test(ua)) { return /iPad|Tablet/.test(ua) ? 'tablet' : 'mobile'; } return 'desktop'; } /** * Detect browser and capabilities */ export function detectBrowser() { const browserInfo = detectBrowserFromUA(); const platform = detectPlatform(); return { name: browserInfo.name, version: browserInfo.version, platform: platform, engine: browserInfo.engine, isChrome: browserInfo.name === 'Chrome', isFirefox: browserInfo.name === 'Firefox', isSafari: browserInfo.name === 'Safari', isEdge: browserInfo.name === 'Microsoft Edge', isMobile: platform === 'mobile' || platform === 'tablet', supportsProxy: typeof Proxy !== 'undefined', supportsIntersectionObserver: 'IntersectionObserver' in window, supportsResizeObserver: 'ResizeObserver' in window, supportsSmoothScroll: 'scrollBehavior' in document.documentElement.style, supportsFetch: 'fetch' in window, supportsCustomElements: 'customElements' in window, }; } /** * Initialize browser fixes and optimizations * Should be called once at application startup */ export function initializeBrowserSupport(options = {}) { if (isInitialized) { debugWarn('Browser', 'Browser support already initialized'); return detectBrowser(); } const { enableSmoothScroll = true, verbose = false } = options; debugLog('Browser', 'Initializing cross-browser support...'); const capabilities = detectBrowser(); if (verbose) { console.log('🌐 Rynex Browser Detection:', { browser: `${capabilities.name} ${capabilities.version}`, platform: capabilities.platform, engine: capabilities.engine, mobile: capabilities.isMobile }); } // Apply browser-specific fixes applyBrowserFixes(capabilities); isInitialized = true; debugLog('Browser', '✅ Cross-browser support initialized successfully'); return capabilities; } /** * Apply browser-specific fixes and workarounds */ function applyBrowserFixes(capabilities) { // Firefox-specific fixes if (capabilities.isFirefox) { debugLog('Browser', 'Applying Firefox-specific fixes...'); // Fix: Firefox scrollbar width calculation fixFirefoxScrollbar(); // Fix: Firefox flexbox rendering issues fixFirefoxFlexbox(); // Fix: Firefox event handling differences fixFirefoxEvents(); // Fix: Firefox CSS transform issues fixFirefoxTransforms(); } // Safari-specific fixes if (capabilities.isSafari) { debugLog('Browser', 'Applying Safari-specific fixes...'); // Fix: Safari date handling fixSafariDateParsing(); // Fix: Safari flexbox bugs fixSafariFlexbox(); // Fix: Safari scroll momentum fixSafariScrolling(); // Fix: Safari event timing fixSafariEvents(); // Fix: Safari backdrop-filter support fixSafariBackdropFilter(); } // Mobile-specific fixes if (capabilities.isMobile) { debugLog('Browser', 'Applying mobile-specific fixes...'); // Fix: Mobile viewport height (100vh issue) fixMobileViewportHeight(); // Fix: Mobile touch events fixMobileTouchEvents(); // Fix: Mobile input zoom prevention fixMobileInputZoom(); } // General cross-browser fixes applyGeneralFixes(); } /** * Firefox scrollbar width fix */ function fixFirefoxScrollbar() { const style = document.createElement('style'); style.textContent = ` /* Firefox scrollbar normalization */ * { scrollbar-width: thin; scrollbar-color: rgba(0, 0, 0, 0.3) transparent; } `; document.head.appendChild(style); } /** * Firefox flexbox rendering fix */ function fixFirefoxFlexbox() { const style = document.createElement('style'); style.textContent = ` /* Firefox flexbox fixes */ @-moz-document url-prefix() { .flex, [style*="display: flex"], [style*="display:flex"] { min-height: 0; min-width: 0; } } `; document.head.appendChild(style); } /** * Firefox event handling fixes */ function fixFirefoxEvents() { // Firefox wheel event normalization const originalAddEventListener = EventTarget.prototype.addEventListener; EventTarget.prototype.addEventListener = function (type, listener, options) { if (type === 'wheel' && typeof listener === 'function') { const wrappedListener = function (event) { // Normalize deltaMode for Firefox const wheelEvent = event; if (wheelEvent.deltaMode === 1) { // Line mode - convert to pixel mode const lineHeight = parseInt(getComputedStyle(document.documentElement).lineHeight) || 16; Object.defineProperty(wheelEvent, 'deltaY', { value: wheelEvent.deltaY * lineHeight, writable: false }); } return listener.call(this, wheelEvent); }; return originalAddEventListener.call(this, type, wrappedListener, options); } return originalAddEventListener.call(this, type, listener, options); }; } /** * Firefox CSS transform fixes */ function fixFirefoxTransforms() { const style = document.createElement('style'); style.textContent = ` /* Firefox transform rendering fixes */ @-moz-document url-prefix() { [style*="transform"] { will-change: transform; backface-visibility: hidden; } } `; document.head.appendChild(style); } /** * Safari date parsing fix */ function fixSafariDateParsing() { const originalParse = Date.parse; Date.parse = function (dateString) { // Safari doesn't support YYYY-MM-DD format well, convert to YYYY/MM/DD if (typeof dateString === 'string' && /^\d{4}-\d{2}-\d{2}/.test(dateString)) { dateString = dateString.replace(/-/g, '/'); } return originalParse(dateString); }; } /** * Safari flexbox fixes */ function fixSafariFlexbox() { const style = document.createElement('style'); style.textContent = ` /* Safari flexbox fixes */ @supports (-webkit-appearance: none) { .flex, [style*="display: flex"], [style*="display:flex"] { -webkit-box-orient: vertical; -webkit-box-direction: normal; } /* Fix Safari flex shrink bug */ [style*="flex:"] { flex-shrink: 1; } } `; document.head.appendChild(style); } /** * Safari scroll momentum fix */ function fixSafariScrolling() { const style = document.createElement('style'); style.textContent = ` /* Safari smooth scrolling */ * { -webkit-overflow-scrolling: touch; } html { scroll-behavior: smooth; } `; document.head.appendChild(style); } /** * Safari event timing fixes */ function fixSafariEvents() { // Safari requestAnimationFrame timing fix const originalRAF = window.requestAnimationFrame; window.requestAnimationFrame = function (callback) { return originalRAF.call(window, (time) => { // Ensure consistent timing across browsers return callback(time || performance.now()); }); }; } /** * Safari backdrop-filter support */ function fixSafariBackdropFilter() { const style = document.createElement('style'); style.textContent = ` /* Safari backdrop-filter support */ @supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) { [style*="backdrop-filter"] { -webkit-backdrop-filter: inherit; } } `; document.head.appendChild(style); } /** * Mobile viewport height fix (100vh issue) */ function fixMobileViewportHeight() { const setVH = () => { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); }; setVH(); window.addEventListener('resize', setVH); window.addEventListener('orientationchange', setVH); const style = document.createElement('style'); style.textContent = ` /* Mobile viewport height fix */ .h-screen, .min-h-screen, [style*="height: 100vh"] { height: calc(var(--vh, 1vh) * 100); } `; document.head.appendChild(style); debugLog('Browser', '✓ Mobile viewport height fixed (use --vh CSS variable)'); } /** * Mobile touch event fixes */ function fixMobileTouchEvents() { // Prevent double-tap zoom on buttons and links let lastTouchEnd = 0; document.addEventListener('touchend', (event) => { const now = Date.now(); if (now - lastTouchEnd <= 300) { event.preventDefault(); } lastTouchEnd = now; }, { passive: false }); // Fix iOS Safari touch delay const style = document.createElement('style'); style.textContent = ` /* Mobile touch optimization */ * { -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; } button, a, input, select, textarea { touch-action: manipulation; } `; document.head.appendChild(style); } /** * Mobile input zoom prevention */ function fixMobileInputZoom() { const style = document.createElement('style'); style.textContent = ` /* Prevent mobile input zoom */ input, select, textarea { font-size: 16px !important; } `; document.head.appendChild(style); } /** * General cross-browser fixes */ function applyGeneralFixes() { // Fix: Consistent box-sizing const style = document.createElement('style'); style.textContent = ` /* Cross-browser normalization */ *, *::before, *::after { box-sizing: border-box; } /* Consistent rendering */ html { -webkit-text-size-adjust: 100%; -moz-text-size-adjust: 100%; text-size-adjust: 100%; } /* Smooth scrolling for all browsers */ html { scroll-behavior: smooth; } /* Prevent horizontal overflow */ body { overflow-x: hidden; } /* Better font rendering */ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; } /* Fix focus outline */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } `; document.head.appendChild(style); // Fix: Console methods for older browsers if (!window.console) { window.console = { log: () => { }, warn: () => { }, error: () => { }, info: () => { }, debug: () => { } }; } // Fix: Performance API if (!window.performance) { window.performance = { now: () => Date.now() }; } debugLog('Browser', '✓ General cross-browser fixes applied'); } /** * Enhanced DOM operations with cross-browser support */ export const browserDOM = { /** * Cross-browser smooth scroll to element */ scrollToElement(element, options = {}) { element.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest', ...options }); }, /** * Cross-browser smooth scroll to position */ scrollTo(x, y, smooth = true) { window.scrollTo({ top: y, left: x, behavior: smooth ? 'smooth' : 'auto' }); }, /** * Get accurate viewport dimensions */ getViewportSize() { return { width: Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0), height: Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0) }; }, /** * Cross-browser element offset */ getElementOffset(element) { const rect = element.getBoundingClientRect(); const scrollTop = window.pageYOffset || document.documentElement.scrollTop; const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; return { top: rect.top + scrollTop, left: rect.left + scrollLeft }; }, /** * Check if element is in viewport */ isInViewport(element, threshold = 0) { const rect = element.getBoundingClientRect(); const viewport = this.getViewportSize(); return (rect.top >= -threshold && rect.left >= -threshold && rect.bottom <= viewport.height + threshold && rect.right <= viewport.width + threshold); } }; /** * Enhanced state management with cross-browser support */ export const browserState = { /** * Check if Proxy is supported (required for reactive state) */ supportsProxy() { return typeof Proxy !== 'undefined' && typeof Reflect !== 'undefined'; }, /** * Fallback for browsers without Proxy support */ createFallbackState(initialState) { console.warn('Proxy not supported, using fallback state management'); // Return a simple object with getters/setters return new Proxy(initialState, { get(target, prop) { return Reflect.get(target, prop); }, set(target, prop, value) { return Reflect.set(target, prop, value); } }); } }; /** * Check if browser support is initialized */ export function isBrowserSupportInitialized() { return isInitialized; } /** * Auto-initialize on import (can be disabled by calling before import) */ if (typeof window !== 'undefined' && typeof document !== 'undefined') { // Auto-initialize with default options when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { if (!isInitialized) { initializeBrowserSupport({ verbose: false }); } }); } else { // DOM already loaded if (!isInitialized) { initializeBrowserSupport({ verbose: false }); } } } //# sourceMappingURL=browsers.js.map