From 74e1b733a9f3ab215666b95b07e9ee6a84fb0624 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 7 Jul 2025 20:57:04 +0800 Subject: [PATCH] layout: dismiss system context menu popup when selecting text on iPad, closes #1485 (#1549) --- .../src/app/library/components/BookItem.tsx | 2 +- .../reader/components/annotator/Annotator.tsx | 4 ++- .../src/app/reader/hooks/useTextSelector.ts | 32 +++++++++++++++---- apps/readest-app/src/components/Popup.tsx | 16 +++++----- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/apps/readest-app/src/app/library/components/BookItem.tsx b/apps/readest-app/src/app/library/components/BookItem.tsx index 42acd803..058d2c0b 100644 --- a/apps/readest-app/src/app/library/components/BookItem.tsx +++ b/apps/readest-app/src/app/library/components/BookItem.tsx @@ -122,7 +122,7 @@ const BookItem: React.FC = ({ onPointerLeave={(e) => stopEvent(e)} onClick={() => showBookDetailsModal(book)} > -
+
diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index 8a001f69..722a2f5a 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -120,7 +120,9 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { detail.doc?.addEventListener('touchstart', handleTouchStart); detail.doc?.addEventListener('touchmove', handleTouchmove); detail.doc?.addEventListener('touchend', handleTouchEnd); - detail.doc?.addEventListener('pointerup', () => handlePointerup(doc, index)); + detail.doc?.addEventListener('pointerup', (ev: PointerEvent) => + handlePointerup(doc, index, ev), + ); detail.doc?.addEventListener('selectionchange', () => handleSelectionchange(doc, index)); // Disable the default context menu on mobile devices, diff --git a/apps/readest-app/src/app/reader/hooks/useTextSelector.ts b/apps/readest-app/src/app/reader/hooks/useTextSelector.ts index bb5c0740..7e7b6813 100644 --- a/apps/readest-app/src/app/reader/hooks/useTextSelector.ts +++ b/apps/readest-app/src/app/reader/hooks/useTextSelector.ts @@ -63,14 +63,14 @@ export const useTextSelector = ( if (!isTextSelected.current) return; sel.addRange(range); setSelection({ key: bookKey, text: await getAnnotationText(range), range, index }); - }, 40); - }, 0); + }, 30); + }, 30); }; const handleSelectionchange = (doc: Document, index: number) => { // Available on iOS, Android and Desktop, fired when the selection is changed // Ideally the popup only shows when the selection is done, const sel = doc.getSelection() as Selection; - if (osPlatform === 'ios') return; + if (osPlatform === 'ios' || appService?.isIOSApp) return; if (!isValidSelection(sel)) { if (!isUpToPopup.current) { handleDismissPopup(); @@ -91,12 +91,30 @@ export const useTextSelector = ( } isUpToPopup.current = true; }; - const handlePointerup = (doc: Document, index: number) => { + const isPointerInsideSelection = (selection: Selection, ev: PointerEvent) => { + if (selection.rangeCount === 0) return false; + const range = selection.getRangeAt(0); + const rects = range.getClientRects(); + const padding = 80; + for (let i = 0; i < rects.length; i++) { + const rect = rects[i]!; + if ( + ev.clientX >= rect.left - padding && + ev.clientX <= rect.right + padding && + ev.clientY >= rect.top - padding && + ev.clientY <= rect.bottom + padding + ) { + return true; + } + } + return false; + }; + const handlePointerup = (doc: Document, index: number, ev: PointerEvent) => { // Available on iOS and Desktop, fired at touchend or mouseup // Note that on Android, pointerup event is fired after an additional touch event const sel = doc.getSelection() as Selection; - if (isValidSelection(sel)) { - if (osPlatform === 'ios') { + if (isValidSelection(sel) && isPointerInsideSelection(sel, ev)) { + if (osPlatform === 'ios' || appService?.isIOSApp) { makeSelectionOnIOS(sel, index); } else { makeSelection(sel, index, true); @@ -134,7 +152,7 @@ export const useTextSelector = ( } isPopuped.current = showPopup; }, - ['android', 'ios'].includes(osPlatform) ? 0 : 500, + ['android', 'ios'].includes(osPlatform) || appService?.isIOSApp ? 0 : 500, ); }; diff --git a/apps/readest-app/src/components/Popup.tsx b/apps/readest-app/src/components/Popup.tsx index d44be8f9..69f963a9 100644 --- a/apps/readest-app/src/components/Popup.tsx +++ b/apps/readest-app/src/components/Popup.tsx @@ -115,26 +115,26 @@ const Popup = ({ trianglePosition?.dir === 'right' ? 'none' : trianglePosition?.dir === 'left' - ? `6px solid` - : '6px solid transparent', + ? `7px solid` + : '7px solid transparent', borderRight: trianglePosition?.dir === 'left' ? 'none' : trianglePosition?.dir === 'right' - ? `6px solid` - : '6px solid transparent', + ? `7px solid` + : '7px solid transparent', borderTop: trianglePosition?.dir === 'down' ? 'none' : trianglePosition?.dir === 'up' - ? `6px solid` - : '6px solid transparent', + ? `7px solid` + : '7px solid transparent', borderBottom: trianglePosition?.dir === 'up' ? 'none' : trianglePosition?.dir === 'down' - ? `6px solid` - : '6px solid transparent', + ? `7px solid` + : '7px solid transparent', transform: trianglePosition?.dir === 'left' || trianglePosition?.dir === 'right' ? 'translateY(-50%)'