Files
eveAI/eveai_chat_client/static/assets/js/composables/useChatViewport.js

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;