Merge pull request #31981 from overleaf/mg-context-mobile

[web] Add stricter mobile detection for context menu

GitOrigin-RevId: 9c9e75a14c1f9841b5125cb4fb4ef8c2f3b8a33f
This commit is contained in:
Malik Glossop
2026-03-05 13:10:08 +01:00
committed by Copybot
parent c59ac4cc55
commit 9d58797a04
2 changed files with 23 additions and 20 deletions

View File

@@ -17,6 +17,9 @@ import {
} from '@codemirror/state'
import { closeAllContextMenusEffect } from '../utils/close-all-context-menus-effect'
import { isContextMenuMouseEvent } from '../utils/context-menu-mouse-event'
import { isMobileDevice } from '../utils/isMobileDevice'
const isMobile = isMobileDevice()
export const openContextMenuEffect = StateEffect.define<{
pos: number
@@ -28,11 +31,6 @@ export const closeContextMenuEffect = StateEffect.define()
export const openContextMenuAnnotation = Annotation.define<boolean>()
const isTouchOnlyInput =
typeof window.matchMedia === 'function' &&
window.matchMedia('(pointer: coarse)').matches &&
window.matchMedia('(hover: none)').matches
type ContextMenuState = {
tooltip: Tooltip | null
mousePosition: { x: number; y: number } | null
@@ -236,7 +234,7 @@ function isClickOnGutter(target: HTMLElement): boolean {
// Gutter context menu plugin
const gutterContextMenuPlugin = (): Extension =>
EditorView.updateListener.of(update => {
if (isTouchOnlyInput || !update.view.dom.parentElement) {
if (!update.view.dom.parentElement) {
return
}
@@ -275,12 +273,8 @@ const gutterContextMenuPlugin = (): Extension =>
// Handle right-click on ol-cm-filler (empty line widget)
// domEventHandlers doesn't fire for contenteditable="false" elements, so we use a direct DOM listener
const emptyLineFillerContextMenuPlugin = (): Extension => {
if (isTouchOnlyInput) {
return []
}
return ViewPlugin.define(view => {
const emptyLineFillerContextMenuPlugin = (): Extension =>
ViewPlugin.define(view => {
const contentDOM = view.contentDOM
const handleContextMenu = (event: Event) => {
@@ -313,16 +307,11 @@ const emptyLineFillerContextMenuPlugin = (): Extension => {
},
}
})
}
// Editor view context menu handlers
const editorContextMenuHandlers = (): Extension =>
EditorView.domEventHandlers({
contextmenu(event: MouseEvent, view: EditorView) {
if (isTouchOnlyInput) {
return false
}
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY })
if (pos === null) {
return false
@@ -361,8 +350,7 @@ const editorContextMenuHandlers = (): Extension =>
}
// Prevent default on right-click to preserve selection
// But not on touch devices - they need native selection behavior
if (isRightClick && !isTouchOnlyInput) {
if (isRightClick) {
event.preventDefault()
return true
}
@@ -394,7 +382,7 @@ const contextMenuKeymap = (): Extension =>
)
export const contextMenu = (enabled: boolean): Extension =>
enabled
enabled && !isMobile
? [
contextMenuContainerTheme,
contextMenuStateField,

View File

@@ -0,0 +1,15 @@
import browser from '../extensions/browser'
export function isMobileDevice(): boolean {
const ua = typeof navigator !== 'undefined' ? navigator.userAgent : ''
const isMobileUserAgent = /Android|iPhone|iPad|Mobile/i.test(ua)
// Input-capability fallback.
const isTouchOnlyInput =
typeof window !== 'undefined' &&
typeof window.matchMedia === 'function' &&
window.matchMedia('(pointer: coarse)').matches &&
window.matchMedia('(hover: none)').matches
return browser.ios || browser.android || isMobileUserAgent || isTouchOnlyInput
}