177 lines
5.3 KiB
JavaScript
177 lines
5.3 KiB
JavaScript
// 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 <html>
|
|
// - 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;
|