Kundenansicht neu: Leiste am iPhone wieder ganz unten (iOS-Höhenfehler)
In der App vom Home-Bildschirm bleibt auf iOS nach dem Schließen der Tastatur die Fensterhöhe oft rund 60 px kleiner (WebKit-Fehler). Leiste und Chat-Eingabe stehen dann zu hoch, darunter ein leerer Streifen, bis die App neu startet. hoehe.js merkt sich die volle Höhe je Breite und blendet die Hülle nach dem Verlassen eines Feldes, nach einem resize oder wenn die App wieder vorne ist kurz aus und ein, wenn die Höhe hängt. Das zwingt WebKit zum Neu-Messen. Scrollstände und Fokus bleiben, höchstens zwei Versuche je Anlass, nur in der iOS-App vom Home-Bildschirm am Handy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
99d9b280f5
commit
0fa8fdbbac
4 files changed
+207
-2
No files matched your search
@@ -0,0 +1,111 @@
|
||||
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: 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 \}\)/)
|
||||
})
|
||||
Reference in new issue
Block a user