🐛 Handle middle mouse click on feed list items (#1469)

* 🐛 Handle middle mouse click on feed list items

* ♻️ Refactor the event listener and turn it into a dedicated hook for web

* 🧹 Cleanup unnecessary Link changes

* Fix import

* Create native version of useAuxClick

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
zio/stable
Foysal Ahamed 2023-09-18 23:34:12 +02:00 committed by GitHub
parent 255beb0c1f
commit 3c4899b3c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 2 deletions

View File

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

View File

@ -0,0 +1,43 @@
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

@ -59,6 +59,7 @@ export const Link = observer(function Link({
}: Props) { }: Props) {
const store = useStores() const store = useStores()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const anchorHref = asAnchor ? sanitizeUrl(href) : undefined
const onPress = React.useCallback( const onPress = React.useCallback(
(e?: Event) => { (e?: Event) => {
@ -96,7 +97,7 @@ export const Link = observer(function Link({
accessibilityRole="link" accessibilityRole="link"
{...props}> {...props}>
{/* @ts-ignore web only -prf */} {/* @ts-ignore web only -prf */}
<View style={style} href={asAnchor ? sanitizeUrl(href) : undefined}> <View style={style} href={anchorHref}>
{children ? children : <Text>{title || 'link'}</Text>} {children ? children : <Text>{title || 'link'}</Text>}
</View> </View>
</TouchableWithoutFeedback> </TouchableWithoutFeedback>
@ -123,7 +124,7 @@ export const Link = observer(function Link({
accessible={accessible} accessible={accessible}
accessibilityRole="link" accessibilityRole="link"
// @ts-ignore web only -prf // @ts-ignore web only -prf
href={asAnchor ? sanitizeUrl(href) : undefined} href={anchorHref}
{...props}> {...props}>
{children ? children : <Text>{title || 'link'}</Text>} {children ? children : <Text>{title || 'link'}</Text>}
</Com> </Com>

View File

@ -16,11 +16,13 @@ import {useWebMediaQueries} from '../../lib/hooks/useWebMediaQueries'
import {BottomBarWeb} from './bottom-bar/BottomBarWeb' import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
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'
const ShellInner = observer(function ShellInnerImpl() { const ShellInner = observer(function ShellInnerImpl() {
const store = useStores() const store = useStores()
const {isDesktop, isMobile} = useWebMediaQueries() const {isDesktop, isMobile} = useWebMediaQueries()
const navigator = useNavigation<NavigationProp>() const navigator = useNavigation<NavigationProp>()
useAuxClick()
useEffect(() => { useEffect(() => {
navigator.addListener('state', () => { navigator.addListener('state', () => {