// useChatViewport.js // Centrale viewport-/keyboard-laag voor de chat client. // - Gebruikt visualViewport + VueUse (via window.VueUse) om veilige hoogte // en keyboard-status te bepalen // - Stelt CSS-variabelen --safe-vh en --safe-bottom-inset in op // - Beheert body-klassen zoals chat-keyboard-open en ios-safari import { ref, computed, watchEffect, onMounted, onBeforeUnmount } from 'vue'; // Haal VueUse-composables uit window.VueUse, die in de chat-bundel // worden geïnitialiseerd via nginx/frontend_src/js/vueuse-setup.js. // Als ze ontbreken (bv. in een testsituatie), vallen we terug op // eenvoudige lokale shims. const vueUse = (typeof window !== 'undefined' && window.VueUse) || {}; let { useEventListener, useWindowSize } = vueUse; // Fallback-shim voor useEventListener if (!useEventListener) { useEventListener = (target, event, handler, options) => { if (!target || !target.addEventListener) return () => {}; const opts = options || { passive: true }; target.addEventListener(event, handler, opts); return () => target.removeEventListener(event, handler, opts); }; } // Fallback-shim voor useWindowSize if (!useWindowSize) { useWindowSize = () => { const height = ref(typeof window !== 'undefined' ? window.innerHeight : 0); if (typeof window !== 'undefined') { window.addEventListener('resize', () => { height.value = window.innerHeight; }, { passive: true }); } return { height }; }; } function detectIosSafari() { if (typeof navigator === 'undefined') return false; const ua = navigator.userAgent || navigator.vendor || (window && window.opera) || ''; const isIOS = /iP(ad|hone|od)/.test(ua); const isSafari = /Safari/.test(ua) && !/Chrome|CriOS|FxiOS/.test(ua); return isIOS && isSafari; } // Drempel voor het detecteren van keyboard-open op basis van hoogteverschil const KEYBOARD_DELTA_THRESHOLD = 120; // px, afgestemd op bestaande viewport.js heuristiek export function useChatViewport() { const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'; const safeHeight = ref(isClient ? window.innerHeight : 0); const baselineHeight = ref(null); // baseline voor keyboard-detectie const keyboardOpen = ref(false); const safeBottomInset = ref(0); const { height: windowHeight } = useWindowSize(); const isIosSafari = ref(isClient ? detectIosSafari() : false); const isMobile = computed(() => { if (!isClient) return false; // Houd de definitie in lijn met bestaande CSS (max-width: 768px) return window.innerWidth <= 768; }); function applyToCss() { if (!isClient) return; const root = document.documentElement; const vhUnit = (safeHeight.value || windowHeight.value || 0) / 100; if (vhUnit > 0) { root.style.setProperty('--safe-vh', `${vhUnit}px`); } root.style.setProperty('--safe-bottom-inset', `${safeBottomInset.value || 0}px`); // Body-klassen document.body.classList.toggle('chat-keyboard-open', !!keyboardOpen.value); if (isIosSafari.value) { root.classList.add('ios-safari'); } else { root.classList.remove('ios-safari'); } } function updateFromVisualViewport() { if (!isClient) return; const vv = window.visualViewport; const currentHeight = vv ? vv.height : window.innerHeight; safeHeight.value = currentHeight; // Bepaal een eenvoudige bottom-inset: verschil tussen layout- en visual viewport const layoutHeight = window.innerHeight || currentHeight; const inset = Math.max(0, layoutHeight - currentHeight); safeBottomInset.value = inset; // Keyboard-detectie: vergelijk met baseline if (baselineHeight.value == null) { baselineHeight.value = currentHeight; } const delta = (baselineHeight.value || currentHeight) - currentHeight; keyboardOpen.value = delta > KEYBOARD_DELTA_THRESHOLD; } onMounted(() => { if (!isClient) return; // Initiale meting updateFromVisualViewport(); applyToCss(); const vv = window.visualViewport; // Luister naar relevante events via VueUse const stopResize = vv ? useEventListener(vv, 'resize', () => { updateFromVisualViewport(); applyToCss(); }) : useEventListener(window, 'resize', () => { updateFromVisualViewport(); applyToCss(); }); const stopScroll = vv ? useEventListener(vv, 'scroll', () => { updateFromVisualViewport(); applyToCss(); }) : () => {}; const stopOrientation = useEventListener(window, 'orientationchange', () => { // Bij oriëntatie-wijziging baseline resetten baselineHeight.value = null; updateFromVisualViewport(); applyToCss(); }); onBeforeUnmount(() => { stopResize && stopResize(); stopScroll && stopScroll(); stopOrientation && stopOrientation(); }); }); // Reageer ook op generieke windowHeight veranderingen (fallback) watchEffect(() => { if (!isClient) return; if (!window.visualViewport) { safeHeight.value = windowHeight.value; safeBottomInset.value = 0; keyboardOpen.value = false; applyToCss(); } }); // Initiale toepassing voor SSR / eerste render if (isClient) { applyToCss(); } return { safeHeight, safeBottomInset, keyboardOpen, isMobile, isIosSafari, }; } export default useChatViewport;