diff --git a/public/app.js b/public/app.js index 9845510..236b8ac 100644 --- a/public/app.js +++ b/public/app.js @@ -1379,6 +1379,22 @@ function onAppViewShown(name) { updateAppBar() } +/* Chat-Eingabe ueber der Tastatur halten: nur nachschieben, wenn sie wirklich verdeckt ist, gemessen am + sichtbaren Bereich (visualViewport). Die Seite nicht nach oben pinnen – das kaempft auf iOS gegen + Safaris eigenes Hochschieben und laesst die Eingabe aus dem Bild springen. */ +function revealChatComposer() { + const composer = document.getElementById('chat-composer') + const vv = window.visualViewport + if (!composer || !vv || !isAppLayout() || !composer.contains(document.activeElement)) return + const rect = composer.getBoundingClientRect() + const visibleTop = vv.offsetTop + const visibleBottom = vv.offsetTop + vv.height + let delta = 0 + if (rect.bottom > visibleBottom - 8) delta = rect.bottom - visibleBottom + 8 + else if (rect.top < visibleTop + 8) delta = rect.top - visibleTop - 8 + if (Number.isFinite(delta) && Math.abs(delta) > 1) window.scrollBy(0, delta) +} + /* Wartungs-Tab: Hinweis bei fehlgeschlagener Zahlung oder offener Rückfrage zur Gutschrift */ function updateWartungBadge() { const card = document.getElementById('abo-card') @@ -1563,9 +1579,16 @@ function initAppShell() { }) }, { passive: true }) - // Tastatur offen (Touch): Tab-Leiste ausblenden, damit Eingabe und Chat Platz haben + // Tastatur offen (Touch): Tab-Leiste ausblenden. Auf Android schrumpft die Seite mit der Tastatur + // (interactive-widget=resizes-content), dort nutzt der Chat den frei werdenden Platz. Auf iOS liegt + // die Tastatur ueber der Seite und Safari schiebt das Feld selbst hoch – dort darf sich beim Fokus + // keine Hoehe aendern (kb-ios, siehe kundenbereich.css), sonst rutscht das Feld wieder aus dem Bild. + if (isIosDevice()) document.body.classList.add('kb-ios') document.addEventListener('focusin', (ev) => { - if (coarsePointer.matches && ev.target.matches?.(TEXT_INPUT)) document.body.classList.add('kb-typing') + if (!coarsePointer.matches || !ev.target.matches?.(TEXT_INPUT)) return + document.body.classList.add('kb-typing') + // Nach der Tastatur-Animation (iOS ca. 350 ms) pruefen, ob die Chat-Eingabe sichtbar ist + if (ev.target.closest('#chat-composer')) setTimeout(revealChatComposer, 350) }) document.addEventListener('focusout', () => { setTimeout(() => { @@ -1573,7 +1596,6 @@ function initAppShell() { }, 80) }) - // Sichtbare Hoehe (ohne Tastatur) fuer den Chat. // Schliesst die Tastatur, ohne dass das Feld den Fokus verliert (Android-Zurueck-Geste), // kommt die Tab-Leiste zurueck; tippt man erneut hinein, verschwindet sie wieder. const vv = window.visualViewport @@ -1581,8 +1603,7 @@ function initAppShell() { let fullHeight = vv.height let width = vv.width let keyboardOpen = false - const syncHeight = () => { - document.documentElement.style.setProperty('--kb-vvh', `${Math.round(vv.height)}px`) + vv.addEventListener('resize', () => { if (Math.abs(vv.width - width) > 1) { width = vv.width fullHeight = vv.height @@ -1594,11 +1615,8 @@ function initAppShell() { if (!open) document.body.classList.remove('kb-typing') else if (coarsePointer.matches && document.activeElement?.matches?.(TEXT_INPUT)) document.body.classList.add('kb-typing') } - // Chat mit Tastatur: iOS schiebt die Seite hoch; App-Leiste, Verlauf und Eingabe passen genau hinein - if (keyboardOpen && appShell.view === 'chat' && isAppLayout()) window.scrollTo(0, 0) - } - vv.addEventListener('resize', syncHeight) - syncHeight() + if (open) requestAnimationFrame(() => requestAnimationFrame(revealChatComposer)) + }) } // Wird der Verlauf kleiner (Tastatur, Tab-Leiste), bleibt die neueste Nachricht sichtbar – sofern // man unten war (data-stick setzt ChatComposer.trackScroll) @@ -1650,6 +1668,16 @@ function initAppShell() { if (isAppLayout() && getQueryParam('abo') === 'success') showView('wartung') } +// iOS zoomt beim Tippen in ein Feld hinein, wenn dessen Schrift effektiv unter 16 px liegt – z. B. bei +// verkleinertem Seitenzoom in Safari, obwohl die Felder 16 px haben. maximum-scale=1 verhindert nur +// diesen automatischen Zoom; mit zwei Fingern zoomen bleibt auf iOS moeglich (WebKit ignoriert die +// Sperre dafuer seit iOS 10). Nur iOS: Android wuerde damit jedes Zoomen sperren. +function preventIosFocusZoom() { + const meta = document.querySelector('meta[name="viewport"]') + if (meta && isIosDevice() && !/maximum-scale/.test(meta.content)) meta.content += ', maximum-scale=1' +} +preventIosFocusZoom() + // App-Installation (Homescreen) und Offline-Seite; zwischengespeichert wird nur die Offline-Seite (sw.js) if ('serviceWorker' in navigator && window.isSecureContext) { window.addEventListener('load', () => { diff --git a/public/kundenbereich.css b/public/kundenbereich.css index 5d6b6f9..aa9954e 100644 --- a/public/kundenbereich.css +++ b/public/kundenbereich.css @@ -441,7 +441,8 @@ button.kb-avatar:disabled { cursor: default; box-shadow: none; } -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; } - .kb-page.kb-typing { --kb-tabbar-h: 0px; } + /* Tastatur offen: auf Android wird der Platz der Tab-Leiste frei; auf iOS keine Hoehenaenderung (app.js) */ + .kb-page.kb-typing:not(.kb-ios) { --kb-tabbar-h: 0px; } html.kb-sheet-open { overflow: hidden; } .kb-desk-only { display: none; } .kb-app-only { display: inline; } @@ -588,10 +589,12 @@ button.kb-avatar:disabled { cursor: default; box-shadow: none; } .kb-invoices .inv-actions .btn-link:first-child { background: rgba(157, 61, 254, 0.18); border-color: var(--kb-purple-line); } .kb-invoices .inv-actions .btn-link:first-child:not(:last-child) { flex: 1.7; } - /* Chat: fuellt den Platz zwischen App-Leiste und Tab-Leiste (bzw. Tastatur) */ + /* Chat: fuellt den Platz zwischen App-Leiste und Tab-Leiste. 100dvh folgt auf Android der Tastatur + (interactive-widget=resizes-content); auf iOS bleibt die Hoehe, Safari schiebt das Feld selbst hoch */ #view-chat.active { display: flex; flex-direction: column; - height: calc(var(--kb-vvh, 100dvh) - var(--kb-appbar-h) - var(--kb-tabbar-h) - 20px); + height: calc(100vh - var(--kb-appbar-h) - var(--kb-tabbar-h) - 20px); + height: calc(100dvh - var(--kb-appbar-h) - var(--kb-tabbar-h) - 20px); } #view-chat .kb-head { flex: none; margin-bottom: 12px; } #view-chat .chat-box { flex: 1 1 auto; min-height: 0; height: auto; padding: 10px; border-radius: 20px; } @@ -631,7 +634,8 @@ button.kb-avatar:disabled { cursor: default; box-shadow: none; } .kb-sheet { inset: auto 0 0 0; width: 100%; max-width: none; margin: 0; } .kb-sheet::backdrop { background: rgba(0, 0, 0, 0.55); } .kb-sheet-panel { - max-height: calc(var(--kb-vvh, 100dvh) - env(safe-area-inset-top) - 24px); overscroll-behavior: contain; + max-height: calc(100vh - env(safe-area-inset-top) - 24px); max-height: calc(100dvh - env(safe-area-inset-top) - 24px); + overscroll-behavior: contain; padding: 8px max(18px, env(safe-area-inset-right)) calc(18px + env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left)); border-radius: 24px 24px 0 0; border-bottom: none; box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.5); transition: transform 0.24s ease-in;