fix web aux click on all browsers (#2633)

zio/stable
Hailey 2024-02-06 09:00:16 -08:00 committed by GitHub
parent 2f1ce117d7
commit 065a094087
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 44 additions and 58 deletions

View File

@ -1,2 +0,0 @@
// does nothing in native
export const useAuxClick = () => {}

View File

@ -1,43 +0,0 @@
import {useEffect} from 'react'
// This is the handler for the middle mouse button click on the feed.
// Normally, we would do this via `onAuxClick` handler on each link element
// However, that handler is not supported on react-native-web and there are some
// discrepancies between various browsers (i.e: safari doesn't trigger it and routes through click event)
// So, this temporary alternative is meant to bridge the gap in an efficient way until the support improves.
export const useAuxClick = () => {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
useEffect(() => {
// On the web, it should always be there but in case it gets accidentally included in native builds
const wrapperEl = document?.body
// Safari already handles auxclick event as click+metaKey so we need to avoid doing this there in case it becomes recursive
if (wrapperEl && !isSafari) {
const handleAuxClick = (e: MouseEvent & {target: HTMLElement}) => {
// Only handle the middle mouse button click
// Only handle if the clicked element itself or one of its ancestors is a link
if (
e.button !== 1 ||
e.target.closest('a') ||
e.target.tagName === 'A'
) {
return
}
// On the original element, trigger a click event with metaKey set to true so that it triggers
// the browser's default behavior of opening the link in a new tab
e.target.dispatchEvent(
new MouseEvent('click', {metaKey: true, bubbles: true}),
)
}
// @ts-ignore For web only
wrapperEl.addEventListener('auxclick', handleAuxClick)
return () => {
// @ts-ignore For web only
wrapperEl?.removeEventListener('auxclick', handleAuxClick)
}
}
}, [isSafari])
}

View File

@ -31,6 +31,7 @@ import {PressableWithHover} from './PressableWithHover'
import FixedTouchableHighlight from '../pager/FixedTouchableHighlight' import FixedTouchableHighlight from '../pager/FixedTouchableHighlight'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useOpenLink} from '#/state/preferences/in-app-browser' import {useOpenLink} from '#/state/preferences/in-app-browser'
import {WebAuxClickWrapper} from 'view/com/util/WebAuxClickWrapper'
type Event = type Event =
| React.MouseEvent<HTMLAnchorElement, MouseEvent> | React.MouseEvent<HTMLAnchorElement, MouseEvent>
@ -104,17 +105,19 @@ export const Link = memo(function Link({
) )
} }
return ( return (
<TouchableWithoutFeedback <WebAuxClickWrapper>
testID={testID} <TouchableWithoutFeedback
onPress={onPress} testID={testID}
accessible={accessible} onPress={onPress}
accessibilityRole="link" accessible={accessible}
{...props}> accessibilityRole="link"
{/* @ts-ignore web only -prf */} {...props}>
<View style={style} href={anchorHref}> {/* @ts-ignore web only -prf */}
{children ? children : <Text>{title || 'link'}</Text>} <View style={style} href={anchorHref}>
</View> {children ? children : <Text>{title || 'link'}</Text>}
</TouchableWithoutFeedback> </View>
</TouchableWithoutFeedback>
</WebAuxClickWrapper>
) )
} }

View File

@ -0,0 +1,30 @@
import React from 'react'
import {Platform} from 'react-native'
const onMouseUp = (e: React.MouseEvent & {target: HTMLElement}) => {
// Only handle whenever it is the middle button
if (e.button !== 1 || e.target.closest('a') || e.target.tagName === 'A') {
return
}
e.target.dispatchEvent(
new MouseEvent('click', {metaKey: true, bubbles: true}),
)
}
const onMouseDown = (e: React.MouseEvent) => {
// Prevents the middle click scroll from enabling
if (e.button !== 1) return
e.preventDefault()
}
export function WebAuxClickWrapper({children}: React.PropsWithChildren<{}>) {
if (Platform.OS !== 'web') return children
return (
// @ts-ignore web only
<div onMouseDown={onMouseDown} onMouseUp={onMouseUp}>
{children}
</div>
)
}

View File

@ -11,7 +11,6 @@ import {DrawerContent} from './Drawer'
import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {useAuxClick} from 'lib/hooks/useAuxClick'
import {t} from '@lingui/macro' import {t} from '@lingui/macro'
import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell' import {useIsDrawerOpen, useSetDrawerOpen} from '#/state/shell'
import {useCloseAllActiveElements} from '#/state/util' import {useCloseAllActiveElements} from '#/state/util'
@ -26,7 +25,6 @@ function ShellInner() {
const closeAllActiveElements = useCloseAllActiveElements() const closeAllActiveElements = useCloseAllActiveElements()
useWebBodyScrollLock(isDrawerOpen) useWebBodyScrollLock(isDrawerOpen)
useAuxClick()
useEffect(() => { useEffect(() => {
const unsubscribe = navigator.addListener('state', () => { const unsubscribe = navigator.addListener('state', () => {