/* Kundenansicht neu: iOS-Fehler der App vom Home-Bildschirm. Nach dem Schließen der Tastatur bleibt die Fensterhöhe oft kleiner (WebKit, bekannt seit iOS 17, in iOS 26 wieder da): alles, was unten fest steht (Leiste, Chat-Eingabe), sitzt dann rund 60 px zu hoch, darunter bleibt ein leerer weißer Streifen, bis die App neu startet. Abhilfe: die Hülle (mindestens volle Fensterhöhe) kurz aus- und wieder einblenden, das zwingt WebKit, die Höhe neu zu messen. Scrollstände und Fokus bleiben. Nur in der App vom Home-Bildschirm auf iOS (das entscheidet `aktiv`) und nur, wenn die Höhe wirklich hängt. */ export const LUFT = 4 // so viel darf die Höhe schwanken, ohne dass sie als hängend gilt export const PRUEF_MS = [150, 600] // nach dem Schließen der Tastatur: früh und noch einmal, wenn die Animation sicher durch ist export const MAX_VERSUCHE = 2 // je Anlass (Tastatur zu, App wieder vorne); hilft es nicht, bleibt es bis zum nächsten Anlass dabei /** Reiner Zustand { voll, breite }: größte innere Höhe bei dieser Breite. Ein Breitenwechsel (Drehen, iPad-Fenster) fängt neu an. */ export function hoeheZustand(alt, { hoehe, breite }) { if (!alt || Math.abs(breite - alt.breite) > 1) return { voll: hoehe, breite } return { voll: Math.max(alt.voll, hoehe), breite: alt.breite } } /** Hängt die Höhe? Kleiner als die größte bei gleicher Breite und kein Textfeld im Fokus (eine offene Tastatur ist kein Hängen). */ export function haengt(zustand, { hoehe, breite, textfeldAktiv }) { if (!zustand || textfeldAktiv) return false if (Math.abs(breite - zustand.breite) > 1) return false return zustand.voll - hoehe > LUFT } /** * Verdrahtung: merkt sich die volle Höhe (resize), prüft nach dem Verlassen eines Feldes (focusout), nach einem resize ohne Textfeld * und wenn die App wieder nach vorne kommt (visibilitychange), jeweils zu den Zeiten PRUEF_MS. Rückgabe { pruefe, stand, beende }. */ export function beobachteHoehe({ win = window, doc = document, element, aktiv = () => true, istTextfeld = () => false }) { const messe = () => ({ hoehe: win.innerHeight, breite: win.innerWidth }) let z = hoeheZustand(null, messe()) let versuche = 0 let timer = [] const merke = () => { z = hoeheZustand(z, messe()) } /** Hülle aus- und einblenden, mit synchronem Umbruch dazwischen; vorher gescrollte Kästen, Fensterscroll und Fokus zurücksetzen. */ function heile() { const fensterX = win.scrollX ?? 0 const fensterY = win.scrollY ?? 0 const staende = [] for (const el of element.querySelectorAll('*')) { if (el.scrollTop || el.scrollLeft) staende.push([el, el.scrollTop, el.scrollLeft]) } const fokus = doc.activeElement && doc.activeElement !== doc.body && element.contains(doc.activeElement) ? doc.activeElement : null const vorher = element.style.display element.style.display = 'none' void element.offsetHeight element.style.display = vorher void element.offsetHeight for (const [el, top, left] of staende) { el.scrollTop = top; el.scrollLeft = left } if (fensterX || fensterY) win.scrollTo(fensterX, fensterY) if (fokus && doc.activeElement !== fokus) fokus.focus?.({ preventScroll: true }) } function pruefe() { if (!aktiv() || !element) return false const m = messe() if (!haengt(z, { ...m, textfeldAktiv: istTextfeld(doc.activeElement) })) { merke(); return false } if (versuche >= MAX_VERSUCHE) return false versuche += 1 heile() merke() return true } const warte = (fn, ms) => (win.setTimeout ? win.setTimeout(fn, ms) : setTimeout(fn, ms)) const stoppe = (id) => (win.clearTimeout ? win.clearTimeout(id) : clearTimeout(id)) const plane = () => { for (const id of timer) stoppe(id) timer = PRUEF_MS.map((ms) => warte(pruefe, ms)) } const neuerAnlass = () => { versuche = 0; plane() } const beiResize = () => { merke(); if (!istTextfeld(doc.activeElement)) plane() } const beiUnfokus = () => neuerAnlass() const beiSicht = () => { if (doc.visibilityState === 'visible') neuerAnlass() } win.addEventListener('resize', beiResize) doc.addEventListener('focusout', beiUnfokus) doc.addEventListener('visibilitychange', beiSicht) return { pruefe, stand: () => ({ ...z, versuche }), beende() { for (const id of timer) stoppe(id) win.removeEventListener('resize', beiResize) doc.removeEventListener('focusout', beiUnfokus) doc.removeEventListener('visibilitychange', beiSicht) }, } }