Kundenansicht neu: Leiste im Chat am iPhone wieder ganz unten
Die Messung am iPhone (iOS 26.2, App vom Home-Bildschirm) zeigt die
Ursache: Auf der Startseite ist das Fenster 1002 px hoch. Im Chat
schrumpft die Spalte auf den Kopf (alles andere steht fest), die Seite
wird kürzer als der Bildschirm, und iOS hält unten rund 60 pt für eine
Safari-Leiste frei, die es in der App nicht gibt: 932 px, Leiste und
Eingabe stehen zu hoch, darunter ein weißer Streifen. Mit der Tastatur
hat das nichts zu tun.
Am Handy hat die Spalte im Chat jetzt wie in den anderen Bereichen
mindestens volle Bildschirmhöhe (100vh, dann 100lvh). Ohne
Browserleisten scrollt die Seite dadurch nicht; in Chromium sind alle
Chat-Bilder am Handy und Desktop pixelgleich zu main.
Der Höhen-Wächter aus 0fa8fdb (Hülle aus- und einblenden) hat nicht
geholfen und ist wieder raus. Die Messung bleibt für die Kontrolle am
iPhone und wird danach entfernt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cca29a4aef
commit
17bce304eb
6 files changed
+28
-224
No files matched your search
@@ -1,12 +1,12 @@
|
|||||||
/* VORÜBERGEHENDE MESSUNG (2026-10-29), wird nach der Klärung wieder entfernt: Am iPhone (App vom Home-Bildschirm) steht die Leiste zu hoch.
|
/* VORÜBERGEHENDE MESSUNG (2026-10-29), wird nach der Klärung wieder entfernt: Am iPhone (App vom Home-Bildschirm) steht die Leiste zu hoch.
|
||||||
Schickt Bildschirmwerte (Fenster, visualViewport, Bildschirm, Safe-Area, vh-Einheiten, Lage der Leiste, Zustand von hoehe.js) an
|
Schickt Bildschirmwerte (Fenster, visualViewport, Bildschirm, Safe-Area, vh-Einheiten, Lage der Leiste) an
|
||||||
/api/diag/hoehe; der Server schreibt sie ins Log (server/routes/diagHoehe.js). Keine Inhalte, keine Eingaben. Höchstens MAX Meldungen je Seite. */
|
/api/diag/hoehe; der Server schreibt sie ins Log (server/routes/diagHoehe.js). Keine Inhalte, keine Eingaben. Höchstens MAX Meldungen je Seite. */
|
||||||
|
|
||||||
const MAX = 80
|
const MAX = 80
|
||||||
const ZIEL = '/api/diag/hoehe'
|
const ZIEL = '/api/diag/hoehe'
|
||||||
const rund = (x) => (Number.isFinite(x) ? Math.round(x * 10) / 10 : null)
|
const rund = (x) => (Number.isFinite(x) ? Math.round(x * 10) / 10 : null)
|
||||||
|
|
||||||
export function starteDiagHoehe({ win = window, doc = document, hoehe = null }) {
|
export function starteDiagHoehe({ win = window, doc = document }) {
|
||||||
const t0 = Date.now()
|
const t0 = Date.now()
|
||||||
let n = 0
|
let n = 0
|
||||||
const probe = (stil) => {
|
const probe = (stil) => {
|
||||||
@@ -38,7 +38,6 @@ export function starteDiagHoehe({ win = window, doc = document, hoehe = null })
|
|||||||
unten: rund(unten.getBoundingClientRect().bottom),
|
unten: rund(unten.getBoundingClientRect().bottom),
|
||||||
leiste: leiste ? [rund(leiste.top), rund(leiste.bottom), rund(leiste.height)] : null,
|
leiste: leiste ? [rund(leiste.top), rund(leiste.bottom), rund(leiste.height)] : null,
|
||||||
huelle: huelle ? [rund(huelle.top), rund(huelle.height)] : null,
|
huelle: huelle ? [rund(huelle.top), rund(huelle.height)] : null,
|
||||||
hoehe: hoehe?.stand?.() ?? null,
|
|
||||||
app: Boolean(win.matchMedia?.('(display-mode: standalone)').matches), ns: win.navigator?.standalone ?? null,
|
app: Boolean(win.matchMedia?.('(display-mode: standalone)').matches), ns: win.navigator?.standalone ?? null,
|
||||||
ori: win.screen?.orientation?.type ?? null,
|
ori: win.screen?.orientation?.type ?? null,
|
||||||
fokus: aktiv && aktiv !== doc.body ? aktiv.tagName : null,
|
fokus: aktiv && aktiv !== doc.body ? aktiv.tagName : null,
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
/* 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. onHeile({ vorher, nachher, voll, versuche }) nach
|
|
||||||
* jedem Eingriff. Rückgabe { pruefe, stand, beende }.
|
|
||||||
*/
|
|
||||||
export function beobachteHoehe({ win = window, doc = document, element, aktiv = () => true, istTextfeld = () => false, onHeile = () => {} }) {
|
|
||||||
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
|
|
||||||
const vorher = win.innerHeight
|
|
||||||
heile()
|
|
||||||
merke()
|
|
||||||
try { onHeile({ vorher, nachher: win.innerHeight, voll: z.voll, versuche }) } catch { /* nur Beobachter */ }
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -185,7 +185,10 @@
|
|||||||
#chat-body { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }
|
#chat-body { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }
|
||||||
|
|
||||||
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
|
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
|
||||||
.kn-spalte:has(#bereich-chat:not([hidden])) { display: block; height: auto; }
|
/* Mindestens volle Bildschirmhöhe wie die anderen Bereiche (.kn-spalte, 100vh): ist die Seite kürzer, hält iOS 26 in der App vom Home-Bildschirm
|
||||||
|
unten rund 60 pt für eine Safari-Leiste frei, die es dort nicht gibt. Das Fenster wird kleiner (gemessen am iPhone 2026-10-29: 1002 → 932 px),
|
||||||
|
Leiste und Eingabe stehen zu hoch, darunter ein weißer Streifen. lvh = Höhe ohne Browserleisten; die Seite scrollt dadurch nicht. */
|
||||||
|
.kn-spalte:has(#bereich-chat:not([hidden])) { display: block; height: auto; min-height: 100vh; min-height: 100lvh; }
|
||||||
.kn-haupt:has(#bereich-chat:not([hidden])) { position: fixed; inset: calc(var(--kn-banner-h) + var(--kn-kopf-h) + env(safe-area-inset-top)) 0 var(--kn-unten, var(--kn-zeile-h)); padding: 0 max(16px, env(safe-area-inset-right)) 6px max(16px, env(safe-area-inset-left)); background: var(--kn-bg); }
|
.kn-haupt:has(#bereich-chat:not([hidden])) { position: fixed; inset: calc(var(--kn-banner-h) + var(--kn-kopf-h) + env(safe-area-inset-top)) 0 var(--kn-unten, var(--kn-zeile-h)); padding: 0 max(16px, env(safe-area-inset-right)) 6px max(16px, env(safe-area-inset-left)); background: var(--kn-bg); }
|
||||||
#bereich-chat > .kn-titel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } /* bleibt für Screenreader die Überschrift der Seite */
|
#bereich-chat > .kn-titel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } /* bleibt für Screenreader die Überschrift der Seite */
|
||||||
.kn:has(#bereich-chat:not([hidden])) .kn-kopf { height: calc(var(--kn-kopf-h) + env(safe-area-inset-top)); border-bottom-color: var(--kn-line); } /* feste Höhe wie der Rand oben des Inhalts: im Querformat wäre der Kopf mit Haarlinie 1 px höher */
|
.kn:has(#bereich-chat:not([hidden])) .kn-kopf { height: calc(var(--kn-kopf-h) + env(safe-area-inset-top)); border-bottom-color: var(--kn-line); } /* feste Höhe wie der Rand oben des Inhalts: im Querformat wäre der Kopf mit Haarlinie 1 px höher */
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import { h, leeren, text } from './dom.js'
|
|||||||
import { api as apiStandard } from './api.js'
|
import { api as apiStandard } from './api.js'
|
||||||
import * as routerStandard from './router.js'
|
import * as routerStandard from './router.js'
|
||||||
import { BEREICHE, FUNKTIONS_ETAGEN, hashFuerAnsicht, hashFuerRoute, elternVon, titelFuer, zeigtWiderrufsstreifen } from './routen.js'
|
import { BEREICHE, FUNKTIONS_ETAGEN, hashFuerAnsicht, hashFuerRoute, elternVon, titelFuer, zeigtWiderrufsstreifen } from './routen.js'
|
||||||
import { HANDY_ABFRAGE, baueNavigation, beobachteTastatur, istTextfeld, leisteSichtbar } from './leiste.js'
|
import { HANDY_ABFRAGE, baueNavigation, beobachteTastatur, leisteSichtbar } from './leiste.js'
|
||||||
import { beobachteHoehe } from './hoehe.js'
|
|
||||||
import { starteDiagHoehe } from './diag-hoehe.js'
|
import { starteDiagHoehe } from './diag-hoehe.js'
|
||||||
import { baueEtage } from './etage.js'
|
import { baueEtage } from './etage.js'
|
||||||
import { erzeugeZaehler } from './zaehler.js'
|
import { erzeugeZaehler } from './zaehler.js'
|
||||||
@@ -279,11 +278,8 @@ export async function starteHuelle({ win = window, doc = document, api = apiStan
|
|||||||
if (handyMq.addEventListener) handyMq.addEventListener('change', beiMedien)
|
if (handyMq.addEventListener) handyMq.addEventListener('change', beiMedien)
|
||||||
else handyMq.addListener?.(beiMedien)
|
else handyMq.addListener?.(beiMedien)
|
||||||
beobachteTastatur({ win, doc, onChange: (z) => doc.body.classList.toggle('kn-tastatur', !leisteSichtbar(z)) })
|
beobachteTastatur({ win, doc, onChange: (z) => doc.body.classList.toggle('kn-tastatur', !leisteSichtbar(z)) })
|
||||||
// iOS-App vom Home-Bildschirm: hängt nach der Tastatur die Fensterhöhe, steht die Leiste zu hoch (hoehe.js); dann einmal neu vermessen
|
|
||||||
let diag = null
|
|
||||||
const hoehe = beobachteHoehe({ win, doc, element: doc.querySelector('.kn-huelle'), aktiv: () => istIos() && standalone() && istHandy(), istTextfeld, onHeile: (werte) => diag?.sende('heile', werte) })
|
|
||||||
// VORÜBERGEHENDE MESSUNG (2026-10-29, diag-hoehe.js): Bildschirmwerte am iPhone ins Server-Log, wird nach der Klärung entfernt
|
// VORÜBERGEHENDE MESSUNG (2026-10-29, diag-hoehe.js): Bildschirmwerte am iPhone ins Server-Log, wird nach der Klärung entfernt
|
||||||
if (istIos() && istHandy() && !viewAs) diag = starteDiagHoehe({ win, doc, hoehe })
|
if (istIos() && istHandy() && !viewAs) starteDiagHoehe({ win, doc })
|
||||||
|
|
||||||
// 7. Fristen und Zähler laden parallel und brechen den Start nie ab
|
// 7. Fristen und Zähler laden parallel und brechen den Start nie ab
|
||||||
ladeFristen(api).then((daten) => { fristen = daten; streifen.setze(daten); aktualisiereKonto() })
|
ladeFristen(api).then((daten) => { fristen = daten; streifen.setze(daten); aktualisiereKonto() })
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { lies, handyTeil } from './support/knHuelle.js'
|
||||||
|
|
||||||
|
// iPhone-App vom Home-Bildschirm (Messung 2026-10-29): ist die Seite im Chat kürzer als der Bildschirm, macht iOS 26 das Fenster rund 60 pt kleiner
|
||||||
|
// und die Leiste steht zu hoch. Am Handy muss die Spalte im Chat daher mindestens volle Bildschirmhöhe haben, wie in den anderen Bereichen.
|
||||||
|
test('huelle.css: am Handy hat die Spalte im Chat mindestens volle Höhe (100vh, dann 100lvh), am Desktop bleibt min-height 0', () => {
|
||||||
|
const css = lies('huelle.css')
|
||||||
|
const handy = handyTeil(css)
|
||||||
|
assert.match(handy, /\.kn-spalte:has\(#bereich-chat:not\(\[hidden\]\)\) \{ display: block; height: auto; min-height: 100vh; min-height: 100lvh; \}/)
|
||||||
|
const desktop = css.slice(0, css.indexOf('.kn-spalte:has(#bereich-chat:not([hidden])) { display: block'))
|
||||||
|
assert.match(desktop, /\.kn-spalte:has\(#bereich-chat:not\(\[hidden\]\)\) \{ display: flex;[^}]*min-height: 0; \}/)
|
||||||
|
assert.match(css, /\.kn-spalte \{ position: relative; min-height: 100vh;/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('huelle.js: der alte Höhen-Wächter (Hülle aus- und einblenden) ist weg, die Messung läuft nur am iPhone und nicht in der Mitarbeiter-Ansicht', () => {
|
||||||
|
const js = lies('huelle.js')
|
||||||
|
assert.doesNotMatch(js, /beobachteHoehe|'\.\/hoehe\.js'/)
|
||||||
|
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) starteDiagHoehe\(\{ win, doc \}\)/)
|
||||||
|
})
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
import { test } from 'node:test'
|
|
||||||
import assert from 'node:assert/strict'
|
|
||||||
import { LUFT, MAX_VERSUCHE, PRUEF_MS, beobachteHoehe, haengt, hoeheZustand } from '../public/neu/hoehe.js'
|
|
||||||
import { lies } from './support/knHuelle.js'
|
|
||||||
|
|
||||||
test('hoeheZustand: größte Höhe je Breite, Drehen fängt neu an', () => {
|
|
||||||
let z = hoeheZustand(null, { hoehe: 932, breite: 430 })
|
|
||||||
z = hoeheZustand(z, { hoehe: 873, breite: 430 }); assert.deepEqual(z, { voll: 932, breite: 430 })
|
|
||||||
z = hoeheZustand(z, { hoehe: 430, breite: 932 }); assert.deepEqual(z, { voll: 430, breite: 932 })
|
|
||||||
})
|
|
||||||
|
|
||||||
test('haengt: nur bei gleicher Breite, ohne Textfeld und mehr als LUFT kleiner (iOS-Fall 932 → 873)', () => {
|
|
||||||
const z = { voll: 932, breite: 430 }
|
|
||||||
assert.equal(haengt(z, { hoehe: 873, breite: 430, textfeldAktiv: false }), true)
|
|
||||||
assert.equal(haengt(z, { hoehe: 873, breite: 430, textfeldAktiv: true }), false) // Tastatur offen
|
|
||||||
assert.equal(haengt(z, { hoehe: 932 - LUFT, breite: 430, textfeldAktiv: false }), false)
|
|
||||||
assert.equal(haengt(z, { hoehe: 400, breite: 932, textfeldAktiv: false }), false) // gedreht
|
|
||||||
assert.equal(haengt(null, { hoehe: 1, breite: 1 }), false)
|
|
||||||
})
|
|
||||||
|
|
||||||
/** Kleine Attrappe: Fenster, Dokument und eine Hülle, die mitschreibt, wann sie aus- und eingeblendet wird. */
|
|
||||||
function aufbau({ aktiv = () => true } = {}) {
|
|
||||||
const sender = (felder = {}) => {
|
|
||||||
const hoerer = {}
|
|
||||||
return Object.assign({
|
|
||||||
addEventListener(typ, fn) { (hoerer[typ] ??= []).push(fn) },
|
|
||||||
removeEventListener(typ, fn) { hoerer[typ] = (hoerer[typ] ?? []).filter((x) => x !== fn) },
|
|
||||||
senden(typ) { for (const fn of hoerer[typ] ?? []) fn({ type: typ }) },
|
|
||||||
anzahl: (typ) => (hoerer[typ] ?? []).length,
|
|
||||||
}, felder)
|
|
||||||
}
|
|
||||||
const spaeter = []
|
|
||||||
const protokoll = []
|
|
||||||
const win = sender({
|
|
||||||
innerHeight: 932, innerWidth: 430, scrollX: 0, scrollY: 0,
|
|
||||||
setTimeout: (fn, ms) => { spaeter.push({ fn, ms }); return spaeter.length },
|
|
||||||
clearTimeout: (id) => { if (spaeter[id - 1]) spaeter[id - 1].fn = () => {} },
|
|
||||||
scrollTo: (x, y) => { win.scrollX = x; win.scrollY = y; protokoll.push(['scrollTo', x, y]) },
|
|
||||||
})
|
|
||||||
const verlauf = { scrollTop: 1200, scrollLeft: 0 }
|
|
||||||
const haken = {}
|
|
||||||
const style = {}
|
|
||||||
let display = ''
|
|
||||||
Object.defineProperty(style, 'display', { get: () => display, set: (v) => { display = v; protokoll.push(['display', v]); if (v === 'none') { verlauf.scrollTop = 0; win.scrollY = 0; haken.beimAusblenden?.() } } })
|
|
||||||
const knopf = { focus: () => protokoll.push(['focus']) }
|
|
||||||
const element = {
|
|
||||||
style,
|
|
||||||
get offsetHeight() { protokoll.push(['umbruch']); return 100 },
|
|
||||||
querySelectorAll: () => [verlauf, { scrollTop: 0, scrollLeft: 0 }],
|
|
||||||
contains: (el) => el === knopf || el === verlauf,
|
|
||||||
}
|
|
||||||
const doc = sender({ activeElement: null, body: {}, visibilityState: 'visible' })
|
|
||||||
const istFeld = (el) => el?.feld === true
|
|
||||||
const h = beobachteHoehe({ win, doc, element, aktiv, istTextfeld: istFeld })
|
|
||||||
const laufe = () => { while (spaeter.length) spaeter.shift().fn() }
|
|
||||||
return { win, doc, element, verlauf, knopf, protokoll, spaeter, h, laufe, haken }
|
|
||||||
}
|
|
||||||
|
|
||||||
test('beobachteHoehe: Tastatur zu und Höhe hängt → Hülle einmal aus und ein, Scrollstände zurück; danach Ruhe', () => {
|
|
||||||
const t = aufbau()
|
|
||||||
t.win.scrollY = 40
|
|
||||||
t.doc.activeElement = { feld: true }
|
|
||||||
t.win.innerHeight = 873; t.win.senden('resize') // iOS: die Höhe fällt mit der Tastatur und bleibt unten
|
|
||||||
assert.equal(t.spaeter.length, 0) // solange das Feld den Fokus hat, wird nicht geprüft
|
|
||||||
t.doc.activeElement = t.doc.body; t.doc.senden('focusout')
|
|
||||||
assert.deepEqual(t.spaeter.map((s) => s.ms), PRUEF_MS)
|
|
||||||
t.spaeter.shift().fn()
|
|
||||||
assert.deepEqual(t.protokoll, [['display', 'none'], ['umbruch'], ['display', ''], ['umbruch'], ['scrollTo', 0, 40]])
|
|
||||||
assert.equal(t.verlauf.scrollTop, 1200)
|
|
||||||
t.win.innerHeight = 932 // WebKit hat neu gemessen
|
|
||||||
t.protokoll.length = 0; t.laufe()
|
|
||||||
assert.deepEqual(t.protokoll, [])
|
|
||||||
assert.deepEqual(t.h.stand(), { voll: 932, breite: 430, versuche: 1 })
|
|
||||||
})
|
|
||||||
|
|
||||||
test('beobachteHoehe: onHeile meldet vorher, nachher, voll und Versuch; ein Fehler im Beobachter stört nicht', () => {
|
|
||||||
const t = aufbau()
|
|
||||||
const meldungen = []
|
|
||||||
t.h.beende()
|
|
||||||
const h = beobachteHoehe({ win: t.win, doc: t.doc, element: t.element, istTextfeld: () => false, onHeile: (w) => { meldungen.push(w); throw new Error('kaputt') } })
|
|
||||||
t.win.innerHeight = 873
|
|
||||||
assert.equal(h.pruefe(), true)
|
|
||||||
assert.deepEqual(meldungen, [{ vorher: 873, nachher: 873, voll: 932, versuche: 1 }])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('beobachteHoehe: hilft es nicht, höchstens MAX_VERSUCHE je Anlass; App wieder vorne ist ein neuer Anlass', () => {
|
|
||||||
const t = aufbau()
|
|
||||||
t.win.innerHeight = 873
|
|
||||||
t.doc.senden('focusout'); t.laufe()
|
|
||||||
t.win.senden('resize'); t.laufe()
|
|
||||||
assert.equal(t.protokoll.filter((p) => p[0] === 'display' && p[1] === 'none').length, MAX_VERSUCHE)
|
|
||||||
t.doc.visibilityState = 'hidden'; t.doc.senden('visibilitychange'); assert.equal(t.spaeter.length, 0)
|
|
||||||
t.doc.visibilityState = 'visible'; t.doc.senden('visibilitychange'); t.laufe()
|
|
||||||
assert.equal(t.protokoll.filter((p) => p[0] === 'display' && p[1] === 'none').length, 2 * MAX_VERSUCHE)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('beobachteHoehe: Fokus auf einem Knopf in der Hülle bleibt, ohne Scrollen', () => {
|
|
||||||
const t = aufbau()
|
|
||||||
t.win.innerHeight = 873
|
|
||||||
t.doc.activeElement = t.knopf
|
|
||||||
t.haken.beimAusblenden = () => { t.doc.activeElement = t.doc.body } // beim Ausblenden verliert der Knopf den Fokus (wie im Browser)
|
|
||||||
assert.equal(t.h.pruefe(), true)
|
|
||||||
assert.deepEqual(t.protokoll.at(-1), ['focus'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('beobachteHoehe: nicht aktiv (kein iOS-App-Modus), keine Hülle oder Höhe passt → nichts passiert; beende räumt auf', () => {
|
|
||||||
const aus = aufbau({ aktiv: () => false })
|
|
||||||
aus.win.innerHeight = 873; assert.equal(aus.h.pruefe(), false); assert.deepEqual(aus.protokoll, [])
|
|
||||||
const ok = aufbau()
|
|
||||||
assert.equal(ok.h.pruefe(), false)
|
|
||||||
ok.win.innerHeight = 932 - LUFT; assert.equal(ok.h.pruefe(), false); assert.deepEqual(ok.protokoll, [])
|
|
||||||
ok.doc.senden('focusout'); ok.h.beende(); ok.win.innerHeight = 873; ok.laufe()
|
|
||||||
assert.deepEqual(ok.protokoll, [])
|
|
||||||
assert.deepEqual([ok.win.anzahl('resize'), ok.doc.anzahl('focusout'), ok.doc.anzahl('visibilitychange')], [0, 0, 0])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('huelle.js: Höhen-Wächter nur in der iOS-App vom Home-Bildschirm am Handy, auf der Hülle', () => {
|
|
||||||
const js = lies('huelle.js')
|
|
||||||
assert.match(js, /import \{ beobachteHoehe \} from '\.\/hoehe\.js'/)
|
|
||||||
assert.match(js, /beobachteHoehe\(\{ win, doc, element: doc\.querySelector\('\.kn-huelle'\), aktiv: \(\) => istIos\(\) && standalone\(\) && istHandy\(\), istTextfeld, onHeile:/)
|
|
||||||
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) diag = starteDiagHoehe\(/)
|
|
||||||
})
|
|
||||||
Reference in new issue
Block a user