mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
On iOS PWA (black-translucent + viewport-fit=cover), opening the virtual keyboard causes iOS to scroll the page upward, pushing the session header behind the system status bar. Fix by resetting window.scrollTo(0, 0) when the keyboard is detected open, and listening to visualViewport scroll events in addition to resize to catch any deferred scrolling. via [HAPI](https://hapi.run) Co-authored-by: HAPI <noreply@hapi.run>
This commit is contained in:
@@ -60,4 +60,47 @@ describe('useViewportHeight update logic', () => {
|
||||
|
||||
expect(root.style.getPropertyValue('--app-viewport-height')).toBe('')
|
||||
})
|
||||
|
||||
it('resets page scroll when keyboard is open', () => {
|
||||
const scrollToSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {})
|
||||
|
||||
// Simulate: keyboard open AND page has been scrolled by iOS
|
||||
Object.defineProperty(window, 'scrollY', { value: 120, configurable: true })
|
||||
|
||||
const viewportHeight = 400
|
||||
const windowHeight = 800
|
||||
const diff = windowHeight - viewportHeight
|
||||
if (diff > 1) {
|
||||
root.style.setProperty('--app-viewport-height', `${viewportHeight}px`)
|
||||
if (window.scrollY > 0) {
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
expect(scrollToSpy).toHaveBeenCalledWith(0, 0)
|
||||
|
||||
// Cleanup
|
||||
Object.defineProperty(window, 'scrollY', { value: 0, configurable: true })
|
||||
scrollToSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('does not reset scroll when page is not scrolled', () => {
|
||||
const scrollToSpy = vi.spyOn(window, 'scrollTo').mockImplementation(() => {})
|
||||
|
||||
Object.defineProperty(window, 'scrollY', { value: 0, configurable: true })
|
||||
|
||||
const viewportHeight = 400
|
||||
const windowHeight = 800
|
||||
const diff = windowHeight - viewportHeight
|
||||
if (diff > 1) {
|
||||
root.style.setProperty('--app-viewport-height', `${viewportHeight}px`)
|
||||
if (window.scrollY > 0) {
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
expect(scrollToSpy).not.toHaveBeenCalled()
|
||||
|
||||
scrollToSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,15 +32,26 @@ export function useViewportHeight(): void {
|
||||
const diff = window.innerHeight - viewport.height
|
||||
if (diff > 1) {
|
||||
root.style.setProperty('--app-viewport-height', `${viewport.height}px`)
|
||||
// On iOS PWA (black-translucent status bar + viewport-fit=cover),
|
||||
// the browser scrolls the page upward when the keyboard opens to
|
||||
// keep the focused input visible. This pushes the header behind
|
||||
// the iOS status bar. Reset the page scroll so the app stays
|
||||
// pinned to the top — the inner flex layout already handles
|
||||
// keeping the composer visible.
|
||||
if (window.scrollY > 0) {
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
} else {
|
||||
root.style.removeProperty('--app-viewport-height')
|
||||
}
|
||||
}
|
||||
|
||||
viewport.addEventListener('resize', update)
|
||||
viewport.addEventListener('scroll', update)
|
||||
|
||||
return () => {
|
||||
viewport.removeEventListener('resize', update)
|
||||
viewport.removeEventListener('scroll', update)
|
||||
root.style.removeProperty('--app-viewport-height')
|
||||
}
|
||||
}, [])
|
||||
|
||||
Reference in New Issue
Block a user