Only lock viewport zoom while text inputs are focused (#10340)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover">
|
||||||
<meta name="referrer" content="origin-when-cross-origin">
|
<meta name="referrer" content="origin-when-cross-origin">
|
||||||
<!--
|
<!--
|
||||||
Preconnect to essential domains
|
Preconnect to essential domains
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import {useEffect} from 'react'
|
||||||
|
|
||||||
|
import {IS_WEB_MOBILE_IOS} from '#/env'
|
||||||
|
|
||||||
|
const ZOOM_LOCKED_VIEWPORT =
|
||||||
|
'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, viewport-fit=cover'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iOS Safari zooms in when a text input with `font-size < 16px` gains focus.
|
||||||
|
* To avoid that while still allowing users to pinch-zoom for accessibility, we
|
||||||
|
* only pin `maximum-scale=1` while a text input is focused and restore the
|
||||||
|
* original viewport on blur.
|
||||||
|
*
|
||||||
|
* Listeners run in the capture phase so we update the viewport before iOS
|
||||||
|
* commits to auto-zooming the input.
|
||||||
|
*/
|
||||||
|
export function useViewportZoomLock({enabled} = {enabled: true}) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!IS_WEB_MOBILE_IOS) return
|
||||||
|
if (!enabled) return
|
||||||
|
|
||||||
|
const meta = document.querySelector('meta[name="viewport"]')
|
||||||
|
if (!(meta instanceof HTMLMetaElement)) return
|
||||||
|
|
||||||
|
const originalContent = meta.content
|
||||||
|
|
||||||
|
const onFocus = (e: FocusEvent) => {
|
||||||
|
if (isTextInput(e.target)) {
|
||||||
|
meta.content = ZOOM_LOCKED_VIEWPORT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlur = (e: FocusEvent) => {
|
||||||
|
if (isTextInput(e.target)) {
|
||||||
|
meta.content = originalContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('focus', onFocus, true)
|
||||||
|
document.addEventListener('blur', onBlur, true)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('focus', onFocus, true)
|
||||||
|
document.removeEventListener('blur', onBlur, true)
|
||||||
|
meta.content = originalContent
|
||||||
|
}
|
||||||
|
}, [enabled])
|
||||||
|
}
|
||||||
|
|
||||||
|
const NON_TEXT_INPUT_TYPES = new Set([
|
||||||
|
'button',
|
||||||
|
'checkbox',
|
||||||
|
'color',
|
||||||
|
'file',
|
||||||
|
'hidden',
|
||||||
|
'image',
|
||||||
|
'radio',
|
||||||
|
'range',
|
||||||
|
'reset',
|
||||||
|
'submit',
|
||||||
|
])
|
||||||
|
|
||||||
|
function isTextInput(target: EventTarget | null): boolean {
|
||||||
|
if (!(target instanceof HTMLElement)) return false
|
||||||
|
if (target.isContentEditable) return true
|
||||||
|
if (target instanceof HTMLTextAreaElement) return true
|
||||||
|
if (target instanceof HTMLInputElement) {
|
||||||
|
return !NON_TEXT_INPUT_TYPES.has(target.type)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack'
|
|||||||
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
import {RemoveScrollBar} from 'react-remove-scroll-bar'
|
||||||
|
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
|
import {useViewportZoomLock} from '#/lib/hooks/useViewportZoomLock'
|
||||||
import {
|
import {
|
||||||
type CommonNavigatorParams,
|
type CommonNavigatorParams,
|
||||||
type NavigationProp,
|
type NavigationProp,
|
||||||
@@ -106,6 +107,8 @@ function Inner({convoId}: {convoId: string}) {
|
|||||||
const {top: topInset} = useSafeAreaInsets()
|
const {top: topInset} = useSafeAreaInsets()
|
||||||
const {data: convoData} = useConvoQuery({convoId})
|
const {data: convoData} = useConvoQuery({convoId})
|
||||||
|
|
||||||
|
useViewportZoomLock({enabled: isFocused})
|
||||||
|
|
||||||
const convo = convoData
|
const convo = convoData
|
||||||
? parseConvoView(convoData, currentAccount?.did)
|
? parseConvoView(convoData, currentAccount?.did)
|
||||||
: null
|
: null
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
-->
|
-->
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, viewport-fit=cover"
|
content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"
|
||||||
/>
|
/>
|
||||||
<!--
|
<!--
|
||||||
Preconnect to essential domains
|
Preconnect to essential domains
|
||||||
|
|||||||
Reference in New Issue
Block a user