gristlabs_grist-core/app/client/lib/dblclick.ts
Dmitry S 7284644313 (core) Add support for editing on mobile.
Summary:
- Add custom handling for dblclick on mobile, to allow focusing editor.
- In place of Clipboard.js, use a FocusLayer with document.body as the default focus element.
- Set maximum-scale on iOS viewport to prevent auto-zoom.
- Reposition the editor on window resize when editing a cell, which is a normal
  occurrence on Android when virtual keyboard is shown.
- Add Save/Cancel icon-buttons next to cell editor on mobile.

Test Plan: Tested manually on Safari / FF on iPhone, and on Chrome on Android emulator.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2721
2021-02-03 23:10:51 -05:00

38 lines
1.6 KiB
TypeScript

import {dom, EventCB} from 'grainjs';
const DOUBLE_TAP_INTERVAL_MS = 500;
/**
* Helper to handle 'dblclick' events on either browser or mobile.
*
* This is equivalent to a 'dblclick' handler when touch events are not supported. When they are,
* the callback will be called on second touch within a short time of a first one. (In that case,
* preventDefault() prevents a 'dblclick' event from being emulated.)
*
* Background: though mobile browsers we care about already generate 'click' and 'dblclick' events
* in response to touch events, it doesn't seem to be treated as a direct user interaction. E.g.
* double-click to edit a cell should focus the editor and open the mobile keyboard, but a
* JS-issued focus() call only works when triggered by a direct user interaction, and synthesized
* dblclick doesn't seem to do that.
*
* Helpful links on emulated (synthesized) events:
* - https://developer.mozilla.org/en-US/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent
* - https://github.com/w3c/pointerevents/issues/171
*/
export function onDblClickMatchElem(elem: EventTarget, selector: string, callback: EventCB): void {
dom.onMatchElem(elem, selector, 'dblclick', (ev, _elem) => {
callback(ev, _elem);
});
let lastTapTime = 0;
dom.onMatchElem(elem, selector, 'touchend', (ev, _elem) => {
const currentTime = Date.now();
const tapLength = currentTime - lastTapTime;
lastTapTime = currentTime;
if (tapLength < DOUBLE_TAP_INTERVAL_MS && tapLength > 0) {
ev.preventDefault();
callback(ev, _elem);
}
});
}