Files
blockly/core/touch.ts
Christopher Allen b0a7c004a9 refactor(build): Delete Closure Library (#7415)
* fix(build): Restore erroneously-deleted filter function

  This was deleted in PR #7406 as it was mainly being used to
  filter core/ vs. test/mocha/ deps into separate deps files -
  but it turns out also to be used for filtering error
  messages too.  Oops.

* refactor(tests): Migrate advanced compilation test to ES Modules

* refactor(build): Migrate main.js to TypeScript

  This turns out to be pretty straight forward, even if it would
  cause crashing if one actually tried to import this module
  instead of just feeding it to Closure Compiler.

* chore(build): Remove goog.declareModuleId calls

  Replace goog.declareModuleId calls with a comment recording the
  former module ID for posterity (or at least until we decide
  how to reformat the renamings file.

* chore(tests): Delete closure/goog/*

  For the moment we still need something to serve as base.js for
  the benefit of closure-make-deps, so we keep a vestigial
  base.js around, containing only the @provideGoog declaration.

* refactor(build): Remove vestigial base.js

  By changing slightly the command line arguments to
  closure-make-deps and closure-calculate-chunks the need to have
  any base.js is eliminated.

* chore: Typo fix for PR #7415
2023-08-31 00:24:47 +01:00

156 lines
4.7 KiB
TypeScript

/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
// Former goog.module ID: Blockly.Touch
import type {Gesture} from './gesture.js';
/** Length in ms for a touch to become a long press. */
const LONGPRESS = 750;
/**
* Whether touch is enabled in the browser.
* Copied from Closure's goog.events.BrowserFeature.TOUCH_ENABLED
*/
export const TOUCH_ENABLED =
'ontouchstart' in globalThis ||
!!(
globalThis['document'] &&
document.documentElement &&
'ontouchstart' in document.documentElement
) || // IE10 uses non-standard touch events,
// so it has a different check.
!!(
globalThis['navigator'] &&
(globalThis['navigator']['maxTouchPoints'] ||
(globalThis['navigator'] as any)['msMaxTouchPoints'])
);
/** Which touch events are we currently paying attention to? */
let touchIdentifier_: string | null = null;
/**
* The TOUCH_MAP lookup dictionary specifies additional touch events to fire,
* in conjunction with mouse events.
*/
export const TOUCH_MAP: {[key: string]: string[]} = {
'mousedown': ['pointerdown'],
'mouseenter': ['pointerenter'],
'mouseleave': ['pointerleave'],
'mousemove': ['pointermove'],
'mouseout': ['pointerout'],
'mouseover': ['pointerover'],
'mouseup': ['pointerup', 'pointercancel'],
'touchend': ['pointerup'],
'touchcancel': ['pointercancel'],
};
/** PID of queued long-press task. */
let longPid_: AnyDuringMigration = 0;
/**
* Context menus on touch devices are activated using a long-press.
* Unfortunately the contextmenu touch event is currently (2015) only supported
* by Chrome. This function is fired on any touchstart event, queues a task,
* which after about a second opens the context menu. The tasks is killed
* if the touch event terminates early.
*
* @param e Touch start event.
* @param gesture The gesture that triggered this longStart.
* @internal
*/
export function longStart(e: PointerEvent, gesture: Gesture) {
longStop();
longPid_ = setTimeout(function () {
// Let the gesture route the right-click correctly.
if (gesture) {
gesture.handleRightClick(e);
}
}, LONGPRESS);
}
/**
* Nope, that's not a long-press. Either touchend or touchcancel was fired,
* or a drag hath begun. Kill the queued long-press task.
*
* @internal
*/
export function longStop() {
if (longPid_) {
clearTimeout(longPid_);
longPid_ = 0;
}
}
/**
* Clear the touch identifier that tracks which touch stream to pay attention
* to. This ends the current drag/gesture and allows other pointers to be
* captured.
*/
export function clearTouchIdentifier() {
touchIdentifier_ = null;
}
/**
* Decide whether Blockly should handle or ignore this event.
* Mouse and touch events require special checks because we only want to deal
* with one touch stream at a time. All other events should always be handled.
*
* @param e The event to check.
* @returns True if this event should be passed through to the registered
* handler; false if it should be blocked.
*/
export function shouldHandleEvent(e: Event): boolean {
// Do not replace the startsWith with a check for `instanceof PointerEvent`.
// `click` and `contextmenu` are PointerEvents in some browsers,
// despite not starting with `pointer`, but we want to always handle them
// without worrying about touch identifiers.
return (
!e.type.startsWith('pointer') ||
(e instanceof PointerEvent && checkTouchIdentifier(e))
);
}
/**
* Get the pointer identifier from the given event.
*
* @param e Pointer event.
* @returns The pointerId of the event.
*/
export function getTouchIdentifierFromEvent(e: PointerEvent): string {
return `${e.pointerId}`;
}
/**
* Check whether the pointer identifier on the event matches the current saved
* identifier. If the current identifier was unset, save the identifier from
* the event. This starts a drag/gesture, during which pointer events with
* other identifiers will be silently ignored.
*
* @param e Pointer event.
* @returns Whether the identifier on the event matches the current saved
* identifier.
*/
export function checkTouchIdentifier(e: PointerEvent): boolean {
const identifier = getTouchIdentifierFromEvent(e);
if (touchIdentifier_) {
// We're already tracking some touch/mouse event. Is this from the same
// source?
return touchIdentifier_ === identifier;
}
if (e.type === 'pointerdown') {
// No identifier set yet, and this is the start of a drag. Set it and
// return.
touchIdentifier_ = identifier;
return true;
}
// There was no identifier yet, but this wasn't a start event so we're going
// to ignore it. This probably means that another drag finished while this
// pointer was down.
return false;
}