Skip to content

Commit

Permalink
feat: allow taps for looking up words
Browse files Browse the repository at this point in the history
Fixes #845.
  • Loading branch information
birtles committed Dec 7, 2021
1 parent 68c599c commit 0b1352e
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 2 deletions.
26 changes: 24 additions & 2 deletions src/content/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import {
SafeAreaProviderRenderOptions,
} from './safe-area-provider';
import { isForeignObjectElement, isSvgDoc, isSvgSvgElement } from './svg';
import { TapTracker } from './tap-tracker';
import {
getBestFitSize,
getTargetProps,
Expand Down Expand Up @@ -172,6 +173,8 @@ export class ContentHandler {
//
// We don't show the popup when the mouse is moving at speed because it's
// mostly distracting and introduces unnecessary work.
//
// (At some point we should roll all this up into a little helper class.)
private static MOUSE_SPEED_SAMPLES = 2;
private static MOUSE_SPEED_THRESHOLD = 0.5;

Expand All @@ -192,6 +195,10 @@ export class ContentHandler {
// events.
private typingMode: boolean = false;

// Detect if taps we get are clicks or long-presses so we know if we should
// clear the popup or not.
private tapTracker: TapTracker = new TapTracker();

//
// Top-most window concerns
//
Expand Down Expand Up @@ -240,6 +247,7 @@ export class ContentHandler {

this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onFocusIn = this.onFocusIn.bind(this);
Expand All @@ -248,6 +256,7 @@ export class ContentHandler {

window.addEventListener('mousemove', this.onMouseMove);
window.addEventListener('mousedown', this.onMouseDown);
window.addEventListener('mouseup', this.onMouseUp);
window.addEventListener('keydown', this.onKeyDown, { capture: true });
window.addEventListener('keyup', this.onKeyUp, { capture: true });
window.addEventListener('focusin', this.onFocusIn);
Expand Down Expand Up @@ -345,6 +354,7 @@ export class ContentHandler {
detach() {
window.removeEventListener('mousemove', this.onMouseMove);
window.removeEventListener('mousedown', this.onMouseDown);
window.removeEventListener('mouseup', this.onMouseUp);
window.removeEventListener('keydown', this.onKeyDown, { capture: true });
window.removeEventListener('keyup', this.onKeyUp, { capture: true });
window.removeEventListener('focusin', this.onFocusIn);
Expand Down Expand Up @@ -550,8 +560,20 @@ export class ContentHandler {
return;
}

// Clear the highlight since it interferes with selection.
this.clearResult({ currentElement: ev.target as Element });
// If this is a long-press, clear the highlight since it interferes with
// selection.
//
// For a short tap, however, we don't want to clear the popup since it's
// useful to be able to tap words on touch devices in order to look them up.
this.tapTracker.mouseDown((isTap: boolean) => {
if (!isTap) {
this.clearResult({ currentElement: ev.target as Element });
}
});
}

onMouseUp(ev: MouseEvent) {
this.tapTracker.mouseUp();
}

onKeyDown(ev: KeyboardEvent) {
Expand Down
45 changes: 45 additions & 0 deletions src/content/tap-tracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
type IsTapCallback = (isTap: boolean) => void;

type TapState =
| { kind: 'idle' }
| {
kind: 'mousedown';
timeout: ReturnType<typeof setTimeout>;
cb?: IsTapCallback;
}
| { kind: 'longpress' };

// A little utility function to track mouseup/down events so we can distinguish
// between a tap and long-press.
//
// The caller notifies on each mouseDown / mouseUp event.
//
// Along with each call to `mouseDown`, the caller may pass a callback that
// will be called once with a flag indicating if the mousedown resulted in a
// tap (true) or a long-press (false).
export class TapTracker {
private tapState: TapState = { kind: 'idle' };

mouseDown(cb?: (isTap: boolean) => void) {
// This shouldn't happen, but if it does, make sure we clean up.
if (this.tapState.kind === 'mousedown') {
clearTimeout(this.tapState.timeout);
this.tapState.cb?.(true);
}

const timeout = setTimeout(() => {
this.tapState = { kind: 'longpress' };
cb?.(false);
}, 100);

this.tapState = { kind: 'mousedown', timeout, cb };
}

mouseUp() {
if (this.tapState.kind === 'mousedown') {
clearTimeout(this.tapState.timeout);
this.tapState.cb?.(true);
}
this.tapState = { kind: 'idle' };
}
}

0 comments on commit 0b1352e

Please sign in to comment.