import { dispatch as d3_dispatch } from 'd3-dispatch'; import { utilFastMouse } from './util'; import { utilRebind } from './rebind'; import { geoVecLength, type Vec2 } from '../geo/vector'; // A custom double-click / double-tap event detector that works on touch devices // if pointer events are supported. Falls back to default `dblclick` event. export function utilDoubleUp() { var dispatch = d3_dispatch('doubleUp'); var _maxTimespan = 500; // milliseconds var _maxDistance = 20; // web pixels; be somewhat generous to account for touch devices /** object representing the pointer that could trigger double up */ var _pointer: { startLoc: Vec2; startTime: number; upCount: number; pointerId: number; } | undefined; function pointerIsValidFor(loc: Vec2): boolean { // second pointerup must occur within a small timeframe after the first pointerdown return new Date().getTime() - _pointer!.startTime <= _maxTimespan && // all pointer events must occur within a small distance of the first pointerdown geoVecLength(_pointer!.startLoc, loc) <= _maxDistance; } function pointerdown(d3_event: PointerEvent): void { // ignore right-click if (d3_event.ctrlKey || d3_event.button === 2) return; const loc: Vec2 = [d3_event.clientX, d3_event.clientY]; // Don't rely on pointerId here since it can change between pointerdown // events on touch devices if (_pointer && !pointerIsValidFor(loc)) { // if this pointer is no longer valid, clear it so another can be started _pointer = undefined; } if (!_pointer) { _pointer = { startLoc: loc, startTime: new Date().getTime(), upCount: 0, pointerId: d3_event.pointerId }; } else { // double down _pointer.pointerId = d3_event.pointerId; } } function pointerup(this: HTMLElement, d3_event: PointerEvent) { // ignore right-click if (d3_event.ctrlKey || d3_event.button === 2) return; if (!_pointer || _pointer.pointerId !== d3_event.pointerId) return; _pointer.upCount += 1; if (_pointer.upCount === 2) { // double up! const loc: Vec2 = [d3_event.clientX, d3_event.clientY]; if (pointerIsValidFor(loc)) { var locInThis = utilFastMouse(this)(d3_event); dispatch.call('doubleUp', this, d3_event, locInThis); } // clear the pointer info in any case _pointer = undefined; } } function doubleUp(selection: d3.Selection) { if ('PointerEvent' in window) { // dblclick isn't well supported on touch devices so manually use // pointer events if they're available selection .on('pointerdown.doubleUp', pointerdown) .on('pointerup.doubleUp', pointerup); } else { // fallback to dblclick selection .on('dblclick.doubleUp', function(d3_event) { dispatch.call('doubleUp', this, d3_event, utilFastMouse(this)(d3_event)); }); } } doubleUp.off = function(selection: d3.Selection) { selection .on('pointerdown.doubleUp', null) .on('pointerup.doubleUp', null) .on('dblclick.doubleUp', null); }; return utilRebind(doubleUp, dispatch, 'on'); }