Prevent native mobile context menu

This commit is contained in:
schroda
2025-06-10 22:09:02 +02:00
parent 3b0d91e4a6
commit 781d89a75a
4 changed files with 37 additions and 4 deletions

View File

@@ -39,6 +39,7 @@ import {
ChapterReadInfo, ChapterReadInfo,
ChapterScanlatorInfo, ChapterScanlatorInfo,
} from '@/modules/chapter/Chapter.types.ts'; } from '@/modules/chapter/Chapter.types.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
type TChapter = ChapterIdInfo & type TChapter = ChapterIdInfo &
ChapterMangaInfo & ChapterMangaInfo &
@@ -62,6 +63,7 @@ interface IProps {
export const ChapterCard = memo((props: IProps) => { export const ChapterCard = memo((props: IProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const preventMobileContextMenu = MediaQuery.usePreventMobileContextMenu();
const menuButtonRef = useRef<HTMLButtonElement>(null); const menuButtonRef = useRef<HTMLButtonElement>(null);
@@ -120,9 +122,10 @@ export const ChapterCard = memo((props: IProps) => {
<CardActionArea <CardActionArea
component={Link} component={Link}
to={Chapters.getReaderUrl(chapter)} to={Chapters.getReaderUrl(chapter)}
onContextMenu={preventMobileContextMenu}
sx={MediaQuery.preventMobileContextMenuSx()}
style={{ style={{
color: theme.palette.text[chapter.isRead ? 'disabled' : 'primary'], color: theme.palette.text[chapter.isRead ? 'disabled' : 'primary'],
userSelect: 'none',
}} }}
state={Chapters.getReaderOpenChapterLocationState(chapter, true)} state={Chapters.getReaderOpenChapterLocationState(chapter, true)}
replace={mode === 'reader'} replace={mode === 'reader'}

View File

@@ -7,7 +7,7 @@
*/ */
import useMediaQuery from '@mui/material/useMediaQuery'; import useMediaQuery from '@mui/material/useMediaQuery';
import { Breakpoint } from '@mui/material/styles'; import { Breakpoint, SxProps, Theme } from '@mui/material/styles';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { getCurrentTheme } from '@/modules/theme/services/ThemeCreator.ts'; import { getCurrentTheme } from '@/modules/theme/services/ThemeCreator.ts';
import { ThemeMode } from '@/modules/theme/contexts/AppThemeContext.tsx'; import { ThemeMode } from '@/modules/theme/contexts/AppThemeContext.tsx';
@@ -112,4 +112,24 @@ export class MediaQuery {
return () => matchSystemThemeMode.removeEventListener('change', handleSystemThemeModeChange); return () => matchSystemThemeMode.removeEventListener('change', handleSystemThemeModeChange);
} }
static usePreventMobileContextMenu() {
const isTouchDevice = MediaQuery.useIsTouchDevice();
return useCallback(
(e: React.MouseEvent<any, MouseEvent>) => {
if (isTouchDevice) {
e.preventDefault();
}
},
[isTouchDevice],
);
}
static preventMobileContextMenuSx(): SxProps<Theme> {
return {
userSelect: 'none',
'-webkit-touch-callout': 'none',
};
}
} }

View File

@@ -22,6 +22,7 @@ import { SpecificMangaCardProps } from '@/modules/manga/Manga.types.ts';
import { TypographyMaxLines } from '@/modules/core/components/texts/TypographyMaxLines.tsx'; import { TypographyMaxLines } from '@/modules/core/components/texts/TypographyMaxLines.tsx';
import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts'; import { MANGA_COVER_ASPECT_RATIO } from '@/modules/manga/Manga.constants.ts';
import { GridLayout } from '@/modules/core/Core.types.ts'; import { GridLayout } from '@/modules/core/Core.types.ts';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
const BottomGradient = styled('div')({ const BottomGradient = styled('div')({
position: 'absolute', position: 'absolute',
@@ -55,6 +56,7 @@ export const MangaGridCard = memo(
mangaBadges, mangaBadges,
mode, mode,
}: SpecificMangaCardProps) => { }: SpecificMangaCardProps) => {
const preventMobileContextMenu = MediaQuery.usePreventMobileContextMenu();
const optionButtonRef = useRef<HTMLButtonElement>(null); const optionButtonRef = useRef<HTMLButtonElement>(null);
const { id, title } = manga; const { id, title } = manga;
@@ -66,7 +68,11 @@ export const MangaGridCard = memo(
onClick={handleClick} onClick={handleClick}
to={mangaLinkTo} to={mangaLinkTo}
state={Mangas.createLocationState(manga, mode)} state={Mangas.createLocationState(manga, mode)}
sx={{ textDecoration: 'none', userSelect: 'none' }} onContextMenu={preventMobileContextMenu}
sx={{
...MediaQuery.preventMobileContextMenuSx(),
textDecoration: 'none',
}}
> >
<Box <Box
sx={{ sx={{

View File

@@ -19,6 +19,7 @@ import { Mangas } from '@/modules/manga/services/Mangas.ts';
import { MangaOptionButton } from '@/modules/manga/components/MangaOptionButton.tsx'; import { MangaOptionButton } from '@/modules/manga/components/MangaOptionButton.tsx';
import { ListCardAvatar } from '@/modules/core/components/lists/cards/ListCardAvatar.tsx'; import { ListCardAvatar } from '@/modules/core/components/lists/cards/ListCardAvatar.tsx';
import { ListCardContent } from '@/modules/core/components/lists/cards/ListCardContent'; import { ListCardContent } from '@/modules/core/components/lists/cards/ListCardContent';
import { MediaQuery } from '@/modules/core/utils/MediaQuery.tsx';
export const MangaListCard = memo( export const MangaListCard = memo(
({ ({
@@ -35,6 +36,8 @@ export const MangaListCard = memo(
mangaBadges, mangaBadges,
mode, mode,
}: SpecificMangaCardProps) => { }: SpecificMangaCardProps) => {
const preventMobileContextMenu = MediaQuery.usePreventMobileContextMenu();
const optionButtonRef = useRef<HTMLButtonElement>(null); const optionButtonRef = useRef<HTMLButtonElement>(null);
const { id, title } = manga; const { id, title } = manga;
@@ -47,8 +50,9 @@ export const MangaListCard = memo(
state={Mangas.createLocationState(manga, mode)} state={Mangas.createLocationState(manga, mode)}
onClick={handleClick} onClick={handleClick}
{...longPressBind(() => popupState.open(optionButtonRef.current))} {...longPressBind(() => popupState.open(optionButtonRef.current))}
onContextMenu={preventMobileContextMenu}
sx={{ sx={{
userSelect: 'none', ...MediaQuery.preventMobileContextMenuSx(),
'@media (hover: hover) and (pointer: fine)': { '@media (hover: hover) and (pointer: fine)': {
'&:hover .manga-option-button': { '&:hover .manga-option-button': {
visibility: 'visible', visibility: 'visible',