layout: dismiss system context menu popup when selecting text on iPad, closes #1485 (#1549)

This commit is contained in:
Huang Xin
2025-07-07 20:57:04 +08:00
committed by GitHub
parent a9e0090275
commit 74e1b733a9
4 changed files with 37 additions and 17 deletions
@@ -122,7 +122,7 @@ const BookItem: React.FC<BookItemProps> = ({
onPointerLeave={(e) => stopEvent(e)}
onClick={() => showBookDetailsModal(book)}
>
<div className='pt-[1px]'>
<div className='pt-[2px] sm:pt-[1px]'>
<LiaInfoCircleSolid size={iconSize15} />
</div>
</button>
@@ -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,
@@ -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,
);
};
+8 -8
View File
@@ -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%)'