Skip to content

Commit

Permalink
Mobile fixes (Android in particular)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsimon committed Oct 2, 2024
1 parent 4c5b09d commit 85e351a
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PointerEvent, ReactNode, useCallback, useEffect, useState } from 'react';
import { useAnnotator, useSelection } from '@annotorious/react';
import type { TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
import { isMobile } from './isMobile';
import {
autoUpdate,
flip,
Expand Down Expand Up @@ -46,14 +47,12 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
const [isOpen, setOpen] = useState(selected?.length > 0);

const { refs, floatingStyles, update, context } = useFloating({
placement: 'top',
placement: isMobile() ? 'bottom' : 'top',
open: isOpen,
onOpenChange: (open, _event, reason) => {
setOpen(open);

if (!open) {
if (reason === 'escape-key' || reason === 'focus-out')
r?.cancelSelected();
if (!open && (reason === 'escape-key' || reason === 'focus-out')) {
setOpen(open);
r?.cancelSelected();
}
},
middleware: [
Expand All @@ -77,6 +76,9 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {

useEffect(() => {
if (isOpen && annotation) {
// Extra precaution - shouldn't normally happen
if (!annotation.target.selector || annotation.target.selector.length < 1) return;

const {
target: {
selector: [{ range }]
Expand Down Expand Up @@ -120,8 +122,9 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
closeOnFocusOut={true}
returnFocus={false}
initialFocus={
// Don't shift focus to the floating element when selected via keyboard
event?.type === 'keydown' ? -1 : 0
// Don't shift focus to the floating element if selected via keyboard
// or on iPad/Android.
(event?.type === 'keydown' || isMobile()) ? -1 : 0
}>
<div
className="annotation-popup text-annotation-popup not-annotatable"
Expand Down
17 changes: 17 additions & 0 deletions packages/text-annotator-react/src/TextAnnotatorPopup/isMobile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// https://stackoverflow.com/questions/21741841/detecting-ios-android-operating-system
export const isMobile = () => {
// @ts-ignore
var userAgent: string = navigator.userAgent || navigator.vendor || window.opera;

if (/android/i.test(userAgent))
return true;

// @ts-ignore
// Note: as of recently, this NO LONGER DETECTS FIREFOX ON iOS!
// This means FF/iOS will behave like on the desktop, and loose
// selection handlebars after the popup opens.
if (/iPad|iPhone/.test(userAgent) && !window.MSStream)
return true;

return false;
}
68 changes: 22 additions & 46 deletions packages/text-annotator/src/SelectionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,20 @@ export const SelectionHandler = (
isLeftClick = lastDownEvent.button === 0;
};

// Helper
const upsertCurrentTarget = () => {
const exists = store.getAnnotation(currentTarget.annotation);
if (exists) {
store.updateTarget(currentTarget);
} else {
store.addAnnotation({
id: currentTarget.annotation,
bodies: [],
target: currentTarget
});
}
}

const onPointerUp = (evt: PointerEvent) => {
const annotatable = !(evt.target as Node).parentElement?.closest(NOT_ANNOTATABLE_SELECTOR);
if (!annotatable || !isLeftClick) return;
Expand Down Expand Up @@ -204,23 +218,8 @@ export const SelectionHandler = (
currentTarget = undefined;
clickSelect();
} else if (currentTarget) {
// Proper lifecycle management: clear selection first...
selection.clear();

const exists = store.getAnnotation(currentTarget.annotation);
if (exists) {
// ...then add annotation to store...
store.updateTarget(currentTarget);
} else {
// ...then add annotation to store...
store.addAnnotation({
id: currentTarget.annotation,
bodies: [],
target: currentTarget
});
}

// ...then make the new annotation the current selection
upsertCurrentTarget();
selection.userSelect(currentTarget.annotation, clonePointerEvent(evt));
}
});
Expand All @@ -231,20 +230,12 @@ export const SelectionHandler = (

if (sel?.isCollapsed) return;

const exists = store.getAnnotation(currentTarget.annotation);
if (exists) {
// ...then add annotation to store...
store.updateTarget(currentTarget);
} else {
selection.clear();

// ...then add annotation to store...
store.addAnnotation({
id: currentTarget.annotation,
bodies: [],
target: currentTarget
});
}
// When selecting the initial word, Chrome Android fires `contextmenu`
// before selectionChanged.
if (!currentTarget || currentTarget.selector.length === 0)
onSelectionChange(evt);

upsertCurrentTarget();

selection.userSelect(currentTarget.annotation, clonePointerEvent(evt));
}
Expand All @@ -254,23 +245,8 @@ export const SelectionHandler = (
const sel = document.getSelection();

if (!sel.isCollapsed) {
// Proper lifecycle management: clear selection first...
selection.clear();

const exists = store.getAnnotation(currentTarget.annotation);
if (exists) {
// ...then add annotation to store...
store.updateTarget(currentTarget);
} else {
// ...then add annotation to store...
store.addAnnotation({
id: currentTarget.annotation,
bodies: [],
target: currentTarget
});
}

// ...then make the new annotation the current selection
upsertCurrentTarget();
selection.userSelect(currentTarget.annotation, cloneKeyboardEvent(evt));
}
}
Expand Down

0 comments on commit 85e351a

Please sign in to comment.