mirror of
https://github.com/google/blockly.git
synced 2026-01-11 19:07:08 +01:00
refactor: Migrate to PointerEvents (#6598)
* refactor: Remove checks for PointerEvent support. * refactor: Deprecate and remove calls to splitEventByTouches. * refactor: Deprecate and remove calls to setClientFromTouch(). * refactor: Use PointerEvent in place of Event/MouseEvent/TouchEvent/PseudoEvent. * refactor: Update references to mouse/touch events in code and documentation to reference pointer events. * refactor: Merge Gesture and TouchGesture * chore: clang-format changed files * refactor: Bind and expect PointerEvents instead of MouseEvents. * refactor: Rename TouchGesture to Gesture. * fix: Fix test failures. * chore: clang-format changed files. * fix: Fix errant _ from merging * refactor: Clean up dead code in browser_events.ts. * chore: Update version in deprecation notices to reflect release schedule * fix: Fixed a bug that caused the browser context menu to not be suppressed in Chrome. * fix: Re-export Gesture as TouchGesture for backwards compatibility. * refactor: Deprecate and remove uses of opt_noPreventDefault. * chore: Fix error message in gesture.ts. * chore: Removed obsolete todo.
This commit is contained in:
@@ -176,7 +176,7 @@ export class BlockDragger implements IBlockDragger {
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
*/
|
||||
drag(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
drag(e: PointerEvent, currentDragDeltaXY: Coordinate) {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
this.draggingBlock_.moveDuringDrag(newLoc);
|
||||
@@ -205,11 +205,11 @@ export class BlockDragger implements IBlockDragger {
|
||||
/**
|
||||
* Finish a block drag and put the block back on the workspace.
|
||||
*
|
||||
* @param e The mouseup/touchend event.
|
||||
* @param e The pointerup event.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
*/
|
||||
endDrag(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
endDrag(e: PointerEvent, currentDragDeltaXY: Coordinate) {
|
||||
// Make sure internal state is fresh.
|
||||
this.drag(e, currentDragDeltaXY);
|
||||
this.dragIconData_ = [];
|
||||
|
||||
@@ -227,7 +227,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
this.pathObject.updateMovable(this.isMovable());
|
||||
const svg = this.getSvgRoot();
|
||||
if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
|
||||
browserEvents.conditionalBind(svg, 'mousedown', this, this.onMouseDown_);
|
||||
browserEvents.conditionalBind(
|
||||
svg, 'pointerdown', this, this.onMouseDown_);
|
||||
}
|
||||
this.eventsInit_ = true;
|
||||
|
||||
@@ -673,11 +674,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on an SVG block.
|
||||
* Handle a pointerdown on an SVG block.
|
||||
*
|
||||
* @param e Mouse down event or touch start event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private onMouseDown_(e: Event) {
|
||||
private onMouseDown_(e: PointerEvent) {
|
||||
const gesture = this.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBlockStart(e, this);
|
||||
|
||||
@@ -145,7 +145,6 @@ import {Toolbox} from './toolbox/toolbox.js';
|
||||
import {ToolboxItem} from './toolbox/toolbox_item.js';
|
||||
import * as Tooltip from './tooltip.js';
|
||||
import * as Touch from './touch.js';
|
||||
import {TouchGesture} from './touch_gesture.js';
|
||||
import {Trashcan} from './trashcan.js';
|
||||
import * as utils from './utils.js';
|
||||
import * as colour from './utils/colour.js';
|
||||
@@ -485,9 +484,7 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
|
||||
* @param opt_noCaptureIdentifier True if triggering on this event should not
|
||||
* block execution of other event handlers on this touch or other
|
||||
* simultaneous touches. False by default.
|
||||
* @param opt_noPreventDefault True if triggering on this event should prevent
|
||||
* the default handler. False by default. If opt_noPreventDefault is
|
||||
* provided, opt_noCaptureIdentifier must also be provided.
|
||||
* @param _opt_noPreventDefault No-op, deprecated and will be removed in v10.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @deprecated Use **Blockly.browserEvents.conditionalBind** instead.
|
||||
* @see browserEvents.conditionalBind
|
||||
@@ -496,13 +493,12 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
|
||||
export function bindEventWithChecks_(
|
||||
node: EventTarget, name: string, thisObject: Object|null, func: Function,
|
||||
opt_noCaptureIdentifier?: boolean,
|
||||
opt_noPreventDefault?: boolean): browserEvents.Data {
|
||||
_opt_noPreventDefault?: boolean): browserEvents.Data {
|
||||
deprecation.warn(
|
||||
'Blockly.bindEventWithChecks_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.conditionalBind');
|
||||
return browserEvents.conditionalBind(
|
||||
node, name, thisObject, func, opt_noCaptureIdentifier,
|
||||
opt_noPreventDefault);
|
||||
node, name, thisObject, func, opt_noCaptureIdentifier);
|
||||
}
|
||||
|
||||
// Aliases to allow external code to access these values for legacy reasons.
|
||||
@@ -671,6 +667,7 @@ export {FlyoutMetricsManager};
|
||||
export {CodeGenerator};
|
||||
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
|
||||
export {Gesture};
|
||||
export {Gesture as TouchGesture}; // Remove in v10.
|
||||
export {Grid};
|
||||
export {HorizontalFlyout};
|
||||
export {IASTNodeLocation};
|
||||
@@ -723,7 +720,6 @@ export {Toolbox};
|
||||
export {ToolboxCategory};
|
||||
export {ToolboxItem};
|
||||
export {ToolboxSeparator};
|
||||
export {TouchGesture};
|
||||
export {Trashcan};
|
||||
export {VariableMap};
|
||||
export {VariableModel};
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.browserEvents');
|
||||
|
||||
import * as Touch from './touch.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
|
||||
|
||||
@@ -51,42 +52,36 @@ const PAGE_MODE_MULTIPLIER = 125;
|
||||
* @param opt_noCaptureIdentifier True if triggering on this event should not
|
||||
* block execution of other event handlers on this touch or other
|
||||
* simultaneous touches. False by default.
|
||||
* @param opt_noPreventDefault True if triggering on this event should prevent
|
||||
* the default handler. False by default. If opt_noPreventDefault is
|
||||
* provided, opt_noCaptureIdentifier must also be provided.
|
||||
* @param opt_noPreventDefault No-op, deprecated and will be removed in v10.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @alias Blockly.browserEvents.conditionalBind
|
||||
*/
|
||||
export function conditionalBind(
|
||||
node: EventTarget, name: string, thisObject: Object|null, func: Function,
|
||||
opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean): Data {
|
||||
let handled = false;
|
||||
if (opt_noPreventDefault !== undefined) {
|
||||
deprecation.warn(
|
||||
'The opt_noPreventDefault argument of conditionalBind', 'version 9',
|
||||
'version 10');
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
function wrapFunc(e: Event) {
|
||||
const captureIdentifier = !opt_noCaptureIdentifier;
|
||||
// Handle each touch point separately. If the event was a mouse event, this
|
||||
// will hand back an array with one element, which we're fine handling.
|
||||
const events = Touch.splitEventByTouches(e);
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i];
|
||||
if (captureIdentifier && !Touch.shouldHandleEvent(event)) {
|
||||
continue;
|
||||
}
|
||||
Touch.setClientFromTouch(event);
|
||||
|
||||
if (!(captureIdentifier && !Touch.shouldHandleEvent(e))) {
|
||||
if (thisObject) {
|
||||
func.call(thisObject, event);
|
||||
func.call(thisObject, e);
|
||||
} else {
|
||||
func(event);
|
||||
func(e);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
const bindData: Data = [];
|
||||
if (globalThis['PointerEvent'] && name in Touch.TOUCH_MAP) {
|
||||
if (name in Touch.TOUCH_MAP) {
|
||||
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
|
||||
const type = Touch.TOUCH_MAP[name][i];
|
||||
node.addEventListener(type, wrapFunc, false);
|
||||
@@ -95,24 +90,6 @@ export function conditionalBind(
|
||||
} else {
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
bindData.push([node, name, wrapFunc]);
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Touch.TOUCH_MAP) {
|
||||
const touchWrapFunc = (e: Event) => {
|
||||
wrapFunc(e);
|
||||
// Calling preventDefault stops the browser from scrolling/zooming the
|
||||
// page.
|
||||
const preventDef = !opt_noPreventDefault;
|
||||
if (handled && preventDef) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
|
||||
const type = Touch.TOUCH_MAP[name][i];
|
||||
node.addEventListener(type, touchWrapFunc, false);
|
||||
bindData.push([node, type, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
}
|
||||
@@ -146,7 +123,7 @@ export function bind(
|
||||
}
|
||||
|
||||
const bindData: Data = [];
|
||||
if (globalThis['PointerEvent'] && name in Touch.TOUCH_MAP) {
|
||||
if (name in Touch.TOUCH_MAP) {
|
||||
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
|
||||
const type = Touch.TOUCH_MAP[name][i];
|
||||
node.addEventListener(type, wrapFunc, false);
|
||||
@@ -155,32 +132,6 @@ export function bind(
|
||||
} else {
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
bindData.push([node, name, wrapFunc]);
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Touch.TOUCH_MAP) {
|
||||
const touchWrapFunc = (e: Event) => {
|
||||
// Punt on multitouch events.
|
||||
if (e instanceof TouchEvent && e.changedTouches &&
|
||||
e.changedTouches.length === 1) {
|
||||
// Map the touch event's properties to the event.
|
||||
const touchPoint = e.changedTouches[0];
|
||||
// TODO (6311): We are trying to make a touch event look like a mouse
|
||||
// event, which is not allowed, because it requires adding more
|
||||
// properties to the event. How do we want to deal with this?
|
||||
(e as AnyDuringMigration).clientX = touchPoint.clientX;
|
||||
(e as AnyDuringMigration).clientY = touchPoint.clientY;
|
||||
}
|
||||
wrapFunc(e);
|
||||
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
e.preventDefault();
|
||||
};
|
||||
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
|
||||
const type = Touch.TOUCH_MAP[name][i];
|
||||
node.addEventListener(type, touchWrapFunc, false);
|
||||
bindData.push([node, type, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
}
|
||||
|
||||
@@ -249,10 +249,10 @@ export class Bubble implements IBubble {
|
||||
|
||||
if (!this.workspace_.options.readOnly) {
|
||||
this.onMouseDownBubbleWrapper = browserEvents.conditionalBind(
|
||||
this.bubbleBack, 'mousedown', this, this.bubbleMouseDown);
|
||||
this.bubbleBack, 'pointerdown', this, this.bubbleMouseDown);
|
||||
if (this.resizeGroup) {
|
||||
this.onMouseDownResizeWrapper = browserEvents.conditionalBind(
|
||||
this.resizeGroup, 'mousedown', this, this.resizeMouseDown);
|
||||
this.resizeGroup, 'pointerdown', this, this.resizeMouseDown);
|
||||
}
|
||||
}
|
||||
this.bubbleGroup.appendChild(content);
|
||||
@@ -278,11 +278,11 @@ export class Bubble implements IBubble {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's border.
|
||||
* Handle a pointerdown on bubble's border.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private bubbleMouseDown(e: Event) {
|
||||
private bubbleMouseDown(e: PointerEvent) {
|
||||
const gesture = this.workspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBubbleStart(e, this);
|
||||
@@ -318,11 +318,11 @@ export class Bubble implements IBubble {
|
||||
// NOP if bubble is not deletable.
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's resize corner.
|
||||
* Handle a pointerdown on bubble's resize corner.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private resizeMouseDown(e: MouseEvent) {
|
||||
private resizeMouseDown(e: PointerEvent) {
|
||||
this.promote();
|
||||
Bubble.unbindDragEvents();
|
||||
if (browserEvents.isRightButton(e)) {
|
||||
@@ -337,20 +337,20 @@ export class Bubble implements IBubble {
|
||||
this.workspace_.RTL ? -this.width : this.width, this.height));
|
||||
|
||||
Bubble.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||
document, 'mouseup', this, Bubble.bubbleMouseUp);
|
||||
document, 'pointerup', this, Bubble.bubbleMouseUp);
|
||||
Bubble.onMouseMoveWrapper = browserEvents.conditionalBind(
|
||||
document, 'mousemove', this, this.resizeMouseMove);
|
||||
document, 'pointermove', this, this.resizeMouseMove);
|
||||
this.workspace_.hideChaff();
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize this bubble to follow the mouse.
|
||||
* Resize this bubble to follow the pointer.
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
* @param e Pointer move event.
|
||||
*/
|
||||
private resizeMouseMove(e: MouseEvent) {
|
||||
private resizeMouseMove(e: PointerEvent) {
|
||||
this.autoLayout = false;
|
||||
const newXY = this.workspace_.moveDrag(e);
|
||||
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||
@@ -847,11 +847,11 @@ export class Bubble implements IBubble {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-up event while dragging a bubble's border or resize handle.
|
||||
* Handle a pointerup event while dragging a bubble's border or resize handle.
|
||||
*
|
||||
* @param _e Mouse up event.
|
||||
* @param _e Pointer up event.
|
||||
*/
|
||||
private static bubbleMouseUp(_e: MouseEvent) {
|
||||
private static bubbleMouseUp(_e: PointerEvent) {
|
||||
Touch.clearTouchIdentifier();
|
||||
Bubble.unbindDragEvents();
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export class BubbleDragger {
|
||||
* at the start of the drag, in pixel units.
|
||||
* @internal
|
||||
*/
|
||||
dragBubble(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
dragBubble(e: PointerEvent, currentDragDeltaXY: Coordinate) {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
this.bubble.moveDuringDrag(this.dragSurface_, newLoc);
|
||||
@@ -141,12 +141,12 @@ export class BubbleDragger {
|
||||
/**
|
||||
* Finish a bubble drag and put the bubble back on the workspace.
|
||||
*
|
||||
* @param e The mouseup/touchend event.
|
||||
* @param e The pointerup event.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
* @internal
|
||||
*/
|
||||
endBubbleDrag(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
endBubbleDrag(e: PointerEvent, currentDragDeltaXY: Coordinate) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBubble(e, currentDragDeltaXY);
|
||||
|
||||
|
||||
@@ -149,11 +149,8 @@ export class Comment extends Icon {
|
||||
body.appendChild(textarea);
|
||||
this.foreignObject!.appendChild(body);
|
||||
|
||||
// Ideally this would be hooked to the focus event for the comment.
|
||||
// However doing so in Firefox swallows the cursor for unknown reasons.
|
||||
// So this is hooked to mouseup instead. No big deal.
|
||||
this.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||
textarea, 'mouseup', this, this.startEdit, true, true);
|
||||
textarea, 'focus', this, this.startEdit, true);
|
||||
// Don't zoom with mousewheel.
|
||||
this.onWheelWrapper = browserEvents.conditionalBind(
|
||||
textarea, 'wheel', this, function(e: Event) {
|
||||
@@ -315,7 +312,7 @@ export class Comment extends Icon {
|
||||
*
|
||||
* @param _e Mouse up event.
|
||||
*/
|
||||
private startEdit(_e: Event) {
|
||||
private startEdit(_e: PointerEvent) {
|
||||
if (this.bubble_?.promote()) {
|
||||
// Since the act of moving this node within the DOM causes a loss of
|
||||
// focus, we need to reapply the focus.
|
||||
|
||||
@@ -361,7 +361,7 @@ export abstract class Field<T = unknown> implements IASTNodeLocationSvg,
|
||||
if (!clickTarget) throw new Error('A click target has not been set.');
|
||||
Tooltip.bindMouseEvents(clickTarget);
|
||||
this.mouseDownWrapper_ = browserEvents.conditionalBind(
|
||||
clickTarget, 'mousedown', this, this.onMouseDown_);
|
||||
clickTarget, 'pointerdown', this, this.onMouseDown_);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1076,11 +1076,11 @@ export abstract class Field<T = unknown> implements IASTNodeLocationSvg,
|
||||
// NOP
|
||||
|
||||
/**
|
||||
* Handle a mouse down event on a field.
|
||||
* Handle a pointerdown event on a field.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
protected onMouseDown_(e: Event) {
|
||||
protected onMouseDown_(e: PointerEvent) {
|
||||
if (!this.sourceBlock_ || this.sourceBlock_.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -278,9 +278,9 @@ export class FieldAngle extends FieldInput<number> {
|
||||
// a click handler on the drag surface to update the value if the surface
|
||||
// is clicked.
|
||||
this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
|
||||
circle, 'click', this, this.onMouseMove_, true, true);
|
||||
circle, 'pointerdown', this, this.onMouseMove_, true);
|
||||
this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
|
||||
circle, 'mousemove', this, this.onMouseMove_, true, true);
|
||||
circle, 'pointermove', this, this.onMouseMove_, true);
|
||||
this.editor_ = svg;
|
||||
}
|
||||
|
||||
@@ -313,15 +313,11 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
*/
|
||||
protected onMouseMove_(e: Event) {
|
||||
protected onMouseMove_(e: PointerEvent) {
|
||||
// Calculate angle.
|
||||
const bBox = this.gauge_!.ownerSVGElement!.getBoundingClientRect();
|
||||
// AnyDuringMigration because: Property 'clientX' does not exist on type
|
||||
// 'Event'.
|
||||
const dx = (e as AnyDuringMigration).clientX - bBox.left - FieldAngle.HALF;
|
||||
// AnyDuringMigration because: Property 'clientY' does not exist on type
|
||||
// 'Event'.
|
||||
const dy = (e as AnyDuringMigration).clientY - bBox.top - FieldAngle.HALF;
|
||||
const dx = e.clientX - bBox.left - FieldAngle.HALF;
|
||||
const dy = e.clientY - bBox.top - FieldAngle.HALF;
|
||||
let angle = Math.atan(-dy / dx);
|
||||
if (isNaN(angle)) {
|
||||
// This shouldn't happen, but let's not let this error propagate further.
|
||||
|
||||
@@ -306,7 +306,7 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
private onClick_(e: MouseEvent) {
|
||||
private onClick_(e: PointerEvent) {
|
||||
const cell = e.target as Element;
|
||||
const colour = cell && cell.getAttribute('data-colour');
|
||||
if (colour !== null) {
|
||||
@@ -415,7 +415,7 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
private onMouseMove_(e: MouseEvent) {
|
||||
private onMouseMove_(e: PointerEvent) {
|
||||
const cell = e.target as Element;
|
||||
const index = cell && Number(cell.getAttribute('data-index'));
|
||||
if (index !== null && index !== this.highlightedIndex_) {
|
||||
@@ -534,13 +534,13 @@ export class FieldColour extends Field<string> {
|
||||
|
||||
// Configure event handler on the table to listen for any event in a cell.
|
||||
this.onClickWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'click', this, this.onClick_, true);
|
||||
table, 'pointerdown', this, this.onClick_, true);
|
||||
this.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'mousemove', this, this.onMouseMove_, true);
|
||||
table, 'pointermove', this, this.onMouseMove_, true);
|
||||
this.onMouseEnterWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'mouseenter', this, this.onMouseEnter_, true);
|
||||
table, 'pointerenter', this, this.onMouseEnter_, true);
|
||||
this.onMouseLeaveWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'mouseleave', this, this.onMouseLeave_, true);
|
||||
table, 'pointerleave', this, this.onMouseLeave_, true);
|
||||
this.onKeyDownWrapper_ =
|
||||
browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_);
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
Array.prototype.push.apply(
|
||||
this.eventWrappers_,
|
||||
browserEvents.conditionalBind(
|
||||
(this.svgBackground_ as SVGPathElement), 'mousedown', this,
|
||||
(this.svgBackground_ as SVGPathElement), 'pointerdown', this,
|
||||
this.onMouseDown_));
|
||||
|
||||
// A flyout connected to a workspace doesn't have its own current gesture.
|
||||
@@ -614,7 +614,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
}
|
||||
|
||||
this.listeners_.push(browserEvents.conditionalBind(
|
||||
(this.svgBackground_ as SVGPathElement), 'mouseover', this,
|
||||
(this.svgBackground_ as SVGPathElement), 'pointerover', this,
|
||||
deselectAll));
|
||||
|
||||
if (this.horizontalLayout) {
|
||||
@@ -911,27 +911,27 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
protected addBlockListeners_(
|
||||
root: SVGElement, block: BlockSvg, rect: SVGElement) {
|
||||
this.listeners_.push(browserEvents.conditionalBind(
|
||||
root, 'mousedown', null, this.blockMouseDown_(block)));
|
||||
root, 'pointerdown', null, this.blockMouseDown_(block)));
|
||||
this.listeners_.push(browserEvents.conditionalBind(
|
||||
rect, 'mousedown', null, this.blockMouseDown_(block)));
|
||||
rect, 'pointerdown', null, this.blockMouseDown_(block)));
|
||||
this.listeners_.push(
|
||||
browserEvents.bind(root, 'mouseenter', block, block.addSelect));
|
||||
browserEvents.bind(root, 'pointerenter', block, block.addSelect));
|
||||
this.listeners_.push(
|
||||
browserEvents.bind(root, 'mouseleave', block, block.removeSelect));
|
||||
browserEvents.bind(root, 'pointerleave', block, block.removeSelect));
|
||||
this.listeners_.push(
|
||||
browserEvents.bind(rect, 'mouseenter', block, block.addSelect));
|
||||
browserEvents.bind(rect, 'pointerenter', block, block.addSelect));
|
||||
this.listeners_.push(
|
||||
browserEvents.bind(rect, 'mouseleave', block, block.removeSelect));
|
||||
browserEvents.bind(rect, 'pointerleave', block, block.removeSelect));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on an SVG block in a non-closing flyout.
|
||||
* Handle a pointerdown on an SVG block in a non-closing flyout.
|
||||
*
|
||||
* @param block The flyout block to copy.
|
||||
* @returns Function to call when block is clicked.
|
||||
*/
|
||||
private blockMouseDown_(block: BlockSvg): Function {
|
||||
return (e: MouseEvent) => {
|
||||
return (e: PointerEvent) => {
|
||||
const gesture = this.targetWorkspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartBlock(block);
|
||||
@@ -941,11 +941,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse down on the flyout background. Start a vertical scroll drag.
|
||||
* Pointer down on the flyout background. Start a vertical scroll drag.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private onMouseDown_(e: MouseEvent) {
|
||||
private onMouseDown_(e: PointerEvent) {
|
||||
const gesture = this.targetWorkspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleFlyoutStart(e, this);
|
||||
@@ -1026,7 +1026,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
|
||||
// Clicking on a flyout button or label is a lot like clicking on the
|
||||
// flyout background.
|
||||
this.listeners_.push(browserEvents.conditionalBind(
|
||||
buttonSvg, 'mousedown', this, this.onMouseDown_));
|
||||
buttonSvg, 'pointerdown', this, this.onMouseDown_));
|
||||
|
||||
this.buttons_.push(button);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,8 @@ export class FlyoutButton {
|
||||
// AnyDuringMigration because: Argument of type 'SVGGElement | null' is not
|
||||
// assignable to parameter of type 'EventTarget'.
|
||||
this.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
||||
this.svgGroup_ as AnyDuringMigration, 'mouseup', this, this.onMouseUp_);
|
||||
this.svgGroup_ as AnyDuringMigration, 'pointerup', this,
|
||||
this.onMouseUp_);
|
||||
return this.svgGroup_!;
|
||||
}
|
||||
|
||||
@@ -244,9 +245,9 @@ export class FlyoutButton {
|
||||
/**
|
||||
* Do something when the button is clicked.
|
||||
*
|
||||
* @param e Mouse up event.
|
||||
* @param e Pointer up event.
|
||||
*/
|
||||
private onMouseUp_(e: Event) {
|
||||
private onMouseUp_(e: PointerEvent) {
|
||||
const gesture = this.targetWorkspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.cancel();
|
||||
|
||||
447
core/gesture.ts
447
core/gesture.ts
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* The class representing an in-progress gesture, usually a drag
|
||||
* or a tap.
|
||||
* The class representing an in-progress gesture, e.g. a drag,
|
||||
* tap, or pinch to zoom.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
@@ -38,10 +38,16 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* Note: In this file "start" refers to touchstart, mousedown, and pointerstart
|
||||
* events. "End" refers to touchend, mouseup, and pointerend events.
|
||||
* Note: In this file "start" refers to pointerdown
|
||||
* events. "End" refers to pointerup events.
|
||||
*/
|
||||
// TODO: Consider touchcancel/pointercancel.
|
||||
|
||||
/** A multiplier used to convert the gesture scale to a zoom in delta. */
|
||||
const ZOOM_IN_MULTIPLIER = 5;
|
||||
|
||||
/** A multiplier used to convert the gesture scale to a zoom out delta. */
|
||||
const ZOOM_OUT_MULTIPLIER = 6;
|
||||
|
||||
/**
|
||||
* Class for one gesture.
|
||||
*
|
||||
@@ -49,8 +55,8 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
*/
|
||||
export class Gesture {
|
||||
/**
|
||||
* The position of the mouse when the gesture started. Units are CSS
|
||||
* pixels, with (0, 0) at the top left of the browser window (mouseEvent
|
||||
* The position of the pointer when the gesture started. Units are CSS
|
||||
* pixels, with (0, 0) at the top left of the browser window (pointer event
|
||||
* clientX/Y).
|
||||
*/
|
||||
private mouseDownXY_ = new Coordinate(0, 0);
|
||||
@@ -97,13 +103,13 @@ export class Gesture {
|
||||
private hasExceededDragRadius_ = false;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind a mouse move listener at the end of a drag.
|
||||
* A handle to use to unbind a pointermove listener at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
*/
|
||||
protected onMoveWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind a mouse up listener at the end of a drag.
|
||||
* A handle to use to unbind a pointerup listener at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
*/
|
||||
protected onUpWrapper_: browserEvents.Data|null = null;
|
||||
@@ -134,18 +140,46 @@ export class Gesture {
|
||||
private healStack_: boolean;
|
||||
|
||||
/** The event that most recently updated this gesture. */
|
||||
private mostRecentEvent_: Event;
|
||||
private mostRecentEvent_: PointerEvent;
|
||||
|
||||
/** Boolean for whether or not this gesture is a multi-touch gesture. */
|
||||
private isMultiTouch_ = false;
|
||||
|
||||
/** A map of cached points used for tracking multi-touch gestures. */
|
||||
private cachedPoints = new Map<string, Coordinate|null>();
|
||||
|
||||
/**
|
||||
* This is the ratio between the starting distance between the touch points
|
||||
* and the most recent distance between the touch points.
|
||||
* Scales between 0 and 1 mean the most recent zoom was a zoom out.
|
||||
* Scales above 1.0 mean the most recent zoom was a zoom in.
|
||||
*/
|
||||
private previousScale_ = 0;
|
||||
|
||||
/** The starting distance between two touch points. */
|
||||
private startDistance_ = 0;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind the second pointerdown listener
|
||||
* at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
*/
|
||||
private onStartWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Boolean for whether or not the workspace supports pinch-zoom. */
|
||||
private isPinchZoomEnabled_: boolean|null = null;
|
||||
|
||||
/**
|
||||
* @param e The event that kicked off this gesture.
|
||||
* @param creatorWorkspace The workspace that created this gesture and has a
|
||||
* reference to it.
|
||||
*/
|
||||
constructor(e: Event, private readonly creatorWorkspace: WorkspaceSvg) {
|
||||
constructor(
|
||||
e: PointerEvent, private readonly creatorWorkspace: WorkspaceSvg) {
|
||||
this.mostRecentEvent_ = e;
|
||||
|
||||
/**
|
||||
* How far the mouse has moved during this drag, in pixel units.
|
||||
* How far the pointer has moved during this drag, in pixel units.
|
||||
* (0, 0) is at this.mouseDownXY_.
|
||||
*/
|
||||
this.currentDragDeltaXY_ = new Coordinate(0, 0);
|
||||
@@ -181,19 +215,19 @@ export class Gesture {
|
||||
if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.dispose();
|
||||
}
|
||||
|
||||
if (this.onStartWrapper_) {
|
||||
browserEvents.unbind(this.onStartWrapper_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update internal state based on an event.
|
||||
*
|
||||
* @param e The most recent mouse or touch event.
|
||||
* @param e The most recent pointer event.
|
||||
*/
|
||||
private updateFromEvent_(e: Event) {
|
||||
// AnyDuringMigration because: Property 'clientY' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Property 'clientX' does not exist
|
||||
// on type 'Event'.
|
||||
const currentXY = new Coordinate(
|
||||
(e as AnyDuringMigration).clientX, (e as AnyDuringMigration).clientY);
|
||||
private updateFromEvent_(e: PointerEvent) {
|
||||
const currentXY = new Coordinate(e.clientX, e.clientY);
|
||||
const changed = this.updateDragDelta_(currentXY);
|
||||
// Exceeded the drag radius for the first time.
|
||||
if (changed) {
|
||||
@@ -204,9 +238,10 @@ export class Gesture {
|
||||
}
|
||||
|
||||
/**
|
||||
* DO MATH to set currentDragDeltaXY_ based on the most recent mouse position.
|
||||
* DO MATH to set currentDragDeltaXY_ based on the most recent pointer
|
||||
* position.
|
||||
*
|
||||
* @param currentXY The most recent mouse/pointer position, in pixel units,
|
||||
* @param currentXY The most recent pointer position, in pixel units,
|
||||
* with (0, 0) at the window's top left corner.
|
||||
* @returns True if the drag just exceeded the drag radius for the first time.
|
||||
*/
|
||||
@@ -230,7 +265,7 @@ export class Gesture {
|
||||
/**
|
||||
* Update this gesture to record whether a block is being dragged from the
|
||||
* flyout.
|
||||
* This function should be called on a mouse/touch move event the first time
|
||||
* This function should be called on a pointermove event the first time
|
||||
* the drag radius is exceeded. It should be called no more than once per
|
||||
* gesture. If a block should be dragged from the flyout this function creates
|
||||
* the new block on the main workspace and updates targetBlock_ and
|
||||
@@ -267,7 +302,7 @@ export class Gesture {
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether a bubble is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time
|
||||
* This function should be called on a pointermove event the first time
|
||||
* the drag radius is exceeded. It should be called no more than once per
|
||||
* gesture. If a bubble should be dragged this function creates the necessary
|
||||
* BubbleDragger and starts the drag.
|
||||
@@ -288,7 +323,7 @@ export class Gesture {
|
||||
* from the flyout or in the workspace, create the necessary BlockDragger and
|
||||
* start the drag.
|
||||
*
|
||||
* This function should be called on a mouse/touch move event the first time
|
||||
* This function should be called on a pointermove event the first time
|
||||
* the drag radius is exceeded. It should be called no more than once per
|
||||
* gesture. If a block should be dragged, either from the flyout or in the
|
||||
* workspace, this function creates the necessary BlockDragger and starts the
|
||||
@@ -316,7 +351,7 @@ export class Gesture {
|
||||
* Check whether to start a workspace drag. If a workspace is being dragged,
|
||||
* create the necessary WorkspaceDragger and start the drag.
|
||||
*
|
||||
* This function should be called on a mouse/touch move event the first time
|
||||
* This function should be called on a pointermove event the first time
|
||||
* the drag radius is exceeded. It should be called no more than once per
|
||||
* gesture. If a workspace is being dragged this function creates the
|
||||
* necessary WorkspaceDragger and starts the drag.
|
||||
@@ -340,7 +375,7 @@ export class Gesture {
|
||||
|
||||
/**
|
||||
* Update this gesture to record whether anything is being dragged.
|
||||
* This function should be called on a mouse/touch move event the first time
|
||||
* This function should be called on a pointermove event the first time
|
||||
* the drag radius is exceeded. It should be called no more than once per
|
||||
* gesture.
|
||||
*/
|
||||
@@ -398,23 +433,25 @@ export class Gesture {
|
||||
|
||||
/**
|
||||
* Start a gesture: update the workspace to indicate that a gesture is in
|
||||
* progress and bind mousemove and mouseup handlers.
|
||||
* progress and bind pointermove and pointerup handlers.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @param e A pointerdown event.
|
||||
* @internal
|
||||
*/
|
||||
doStart(e: MouseEvent) {
|
||||
doStart(e: PointerEvent) {
|
||||
if (!this.startWorkspace_) {
|
||||
throw new Error(
|
||||
'Cannot start the touch gesture becauase the start ' +
|
||||
'workspace is undefined');
|
||||
}
|
||||
this.isPinchZoomEnabled_ = this.startWorkspace_.options.zoomOptions &&
|
||||
this.startWorkspace_.options.zoomOptions.pinch;
|
||||
|
||||
if (browserEvents.isTargetInput(e)) {
|
||||
this.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.startWorkspace_) {
|
||||
throw new Error(
|
||||
'Cannot start the gesture because the start ' +
|
||||
'workspace is undefined');
|
||||
}
|
||||
|
||||
this.hasStarted_ = true;
|
||||
|
||||
blockAnimations.disconnectUiStop();
|
||||
@@ -443,106 +480,252 @@ export class Gesture {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(#6097): Make types accurate, possibly by refactoring touch handling.
|
||||
const typelessEvent = e as AnyDuringMigration;
|
||||
if ((e.type.toLowerCase() === 'touchstart' ||
|
||||
e.type.toLowerCase() === 'pointerdown') &&
|
||||
typelessEvent.pointerType !== 'mouse') {
|
||||
Touch.longStart(typelessEvent, this);
|
||||
if (e.type.toLowerCase() === 'pointerdown' && e.pointerType !== 'mouse') {
|
||||
Touch.longStart(e, this);
|
||||
}
|
||||
|
||||
// AnyDuringMigration because: Property 'clientY' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Property 'clientX' does not exist
|
||||
// on type 'Event'.
|
||||
this.mouseDownXY_ = new Coordinate(
|
||||
(e as AnyDuringMigration).clientX, (e as AnyDuringMigration).clientY);
|
||||
// AnyDuringMigration because: Property 'metaKey' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Property 'ctrlKey' does not exist
|
||||
// on type 'Event'. AnyDuringMigration because: Property 'altKey' does not
|
||||
// exist on type 'Event'.
|
||||
this.healStack_ = (e as AnyDuringMigration).altKey ||
|
||||
(e as AnyDuringMigration).ctrlKey || (e as AnyDuringMigration).metaKey;
|
||||
this.mouseDownXY_ = new Coordinate(e.clientX, e.clientY);
|
||||
this.healStack_ = e.altKey || e.ctrlKey || e.metaKey;
|
||||
|
||||
this.bindMouseEvents(e);
|
||||
|
||||
if (!this.isEnding_) {
|
||||
this.handleTouchStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind gesture events.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @param e A pointerdown event.
|
||||
* @internal
|
||||
*/
|
||||
bindMouseEvents(e: Event) {
|
||||
bindMouseEvents(e: PointerEvent) {
|
||||
this.onStartWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'pointerdown', null, this.handleStart.bind(this),
|
||||
/* opt_noCaptureIdentifier */ true);
|
||||
this.onMoveWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mousemove', null, this.handleMove.bind(this));
|
||||
document, 'pointermove', null, this.handleMove.bind(this),
|
||||
/* opt_noCaptureIdentifier */ true);
|
||||
this.onUpWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mouseup', null, this.handleUp.bind(this));
|
||||
document, 'pointerup', null, this.handleUp.bind(this),
|
||||
/* opt_noCaptureIdentifier */ true);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse move or touch move event.
|
||||
* Handle a pointerdown event.
|
||||
*
|
||||
* @param e A mouse move or touch move event.
|
||||
* @param e A pointerdown event.
|
||||
* @internal
|
||||
*/
|
||||
handleMove(e: Event) {
|
||||
this.updateFromEvent_(e);
|
||||
if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.drag(this.currentDragDeltaXY_);
|
||||
} else if (this.blockDragger_) {
|
||||
this.blockDragger_.drag(this.mostRecentEvent_, this.currentDragDeltaXY_);
|
||||
} else if (this.bubbleDragger_) {
|
||||
this.bubbleDragger_.dragBubble(
|
||||
this.mostRecentEvent_, this.currentDragDeltaXY_);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse up or touch end event.
|
||||
*
|
||||
* @param e A mouse up or touch end event.
|
||||
* @internal
|
||||
*/
|
||||
handleUp(e: Event) {
|
||||
this.updateFromEvent_(e);
|
||||
Touch.longStop();
|
||||
|
||||
if (this.isEnding_) {
|
||||
console.log('Trying to end a gesture recursively.');
|
||||
handleStart(e: PointerEvent) {
|
||||
if (this.isDragging()) {
|
||||
// A drag has already started, so this can no longer be a pinch-zoom.
|
||||
return;
|
||||
}
|
||||
this.isEnding_ = true;
|
||||
// The ordering of these checks is important: drags have higher priority
|
||||
// than clicks. Fields have higher priority than blocks; blocks have higher
|
||||
// priority than workspaces.
|
||||
// The ordering within drags does not matter, because the three types of
|
||||
// dragging are exclusive.
|
||||
if (this.bubbleDragger_) {
|
||||
this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_);
|
||||
} else if (this.blockDragger_) {
|
||||
this.blockDragger_.endDrag(e, this.currentDragDeltaXY_);
|
||||
} else if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
|
||||
} else if (this.isBubbleClick_()) {
|
||||
// Bubbles are in front of all fields and blocks.
|
||||
this.doBubbleClick_();
|
||||
} else if (this.isFieldClick_()) {
|
||||
this.doFieldClick_();
|
||||
} else if (this.isBlockClick_()) {
|
||||
this.doBlockClick_();
|
||||
} else if (this.isWorkspaceClick_()) {
|
||||
this.doWorkspaceClick_(e);
|
||||
this.handleTouchStart(e);
|
||||
|
||||
if (this.isMultiTouch()) {
|
||||
Touch.longStop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a pointermove event.
|
||||
*
|
||||
* @param e A pointermove event.
|
||||
* @internal
|
||||
*/
|
||||
handleMove(e: PointerEvent) {
|
||||
if ((this.isDragging() && Touch.shouldHandleEvent(e)) ||
|
||||
!this.isMultiTouch()) {
|
||||
this.updateFromEvent_(e);
|
||||
if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.drag(this.currentDragDeltaXY_);
|
||||
} else if (this.blockDragger_) {
|
||||
this.blockDragger_.drag(
|
||||
this.mostRecentEvent_, this.currentDragDeltaXY_);
|
||||
} else if (this.bubbleDragger_) {
|
||||
this.bubbleDragger_.dragBubble(
|
||||
this.mostRecentEvent_, this.currentDragDeltaXY_);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
} else if (this.isMultiTouch()) {
|
||||
this.handleTouchMove(e);
|
||||
Touch.longStop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a pointerup event.
|
||||
*
|
||||
* @param e A pointerup event.
|
||||
* @internal
|
||||
*/
|
||||
handleUp(e: PointerEvent) {
|
||||
if (!this.isDragging()) {
|
||||
this.handleTouchEnd(e);
|
||||
}
|
||||
if (!this.isMultiTouch() || this.isDragging()) {
|
||||
if (!Touch.shouldHandleEvent(e)) {
|
||||
return;
|
||||
}
|
||||
this.updateFromEvent_(e);
|
||||
Touch.longStop();
|
||||
|
||||
if (this.isEnding_) {
|
||||
console.log('Trying to end a gesture recursively.');
|
||||
return;
|
||||
}
|
||||
this.isEnding_ = true;
|
||||
// The ordering of these checks is important: drags have higher priority
|
||||
// than clicks. Fields have higher priority than blocks; blocks have
|
||||
// higher priority than workspaces. The ordering within drags does not
|
||||
// matter, because the three types of dragging are exclusive.
|
||||
if (this.bubbleDragger_) {
|
||||
this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_);
|
||||
} else if (this.blockDragger_) {
|
||||
this.blockDragger_.endDrag(e, this.currentDragDeltaXY_);
|
||||
} else if (this.workspaceDragger_) {
|
||||
this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
|
||||
} else if (this.isBubbleClick_()) {
|
||||
// Bubbles are in front of all fields and blocks.
|
||||
this.doBubbleClick_();
|
||||
} else if (this.isFieldClick_()) {
|
||||
this.doFieldClick_();
|
||||
} else if (this.isBlockClick_()) {
|
||||
this.doBlockClick_();
|
||||
} else if (this.isWorkspaceClick_()) {
|
||||
this.doWorkspaceClick_(e);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a pointerdown event and keep track of current
|
||||
* pointers.
|
||||
*
|
||||
* @param e A pointerdown event.
|
||||
* @internal
|
||||
*/
|
||||
handleTouchStart(e: PointerEvent) {
|
||||
const pointerId = Touch.getTouchIdentifierFromEvent(e);
|
||||
// store the pointerId in the current list of pointers
|
||||
this.cachedPoints.set(pointerId, this.getTouchPoint(e));
|
||||
const pointers = Array.from(this.cachedPoints.keys());
|
||||
// If two pointers are down, store info
|
||||
if (pointers.length === 2) {
|
||||
const point0 = (this.cachedPoints.get(pointers[0]))!;
|
||||
const point1 = (this.cachedPoints.get(pointers[1]))!;
|
||||
this.startDistance_ = Coordinate.distance(point0, point1);
|
||||
this.isMultiTouch_ = true;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a pointermove event and zoom in/out if two pointers
|
||||
* are on the screen.
|
||||
*
|
||||
* @param e A pointermove event.
|
||||
* @internal
|
||||
*/
|
||||
handleTouchMove(e: PointerEvent) {
|
||||
const pointerId = Touch.getTouchIdentifierFromEvent(e);
|
||||
// Update the cache
|
||||
this.cachedPoints.set(pointerId, this.getTouchPoint(e));
|
||||
|
||||
if (this.isPinchZoomEnabled_ && this.cachedPoints.size === 2) {
|
||||
this.handlePinch_(e);
|
||||
} else {
|
||||
this.handleMove(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pinch zoom gesture.
|
||||
*
|
||||
* @param e A pointermove event.
|
||||
*/
|
||||
private handlePinch_(e: PointerEvent) {
|
||||
const pointers = Array.from(this.cachedPoints.keys());
|
||||
// Calculate the distance between the two pointers
|
||||
const point0 = (this.cachedPoints.get(pointers[0]))!;
|
||||
const point1 = (this.cachedPoints.get(pointers[1]))!;
|
||||
const moveDistance = Coordinate.distance(point0, point1);
|
||||
const scale = moveDistance / this.startDistance_;
|
||||
|
||||
if (this.previousScale_ > 0 && this.previousScale_ < Infinity) {
|
||||
const gestureScale = scale - this.previousScale_;
|
||||
const delta = gestureScale > 0 ? gestureScale * ZOOM_IN_MULTIPLIER :
|
||||
gestureScale * ZOOM_OUT_MULTIPLIER;
|
||||
if (!this.startWorkspace_) {
|
||||
throw new Error(
|
||||
'Cannot handle a pinch because the start workspace ' +
|
||||
'is undefined');
|
||||
}
|
||||
const workspace = this.startWorkspace_;
|
||||
const position = browserEvents.mouseToSvg(
|
||||
e, workspace.getParentSvg(), workspace.getInverseScreenCTM());
|
||||
workspace.zoom(position.x, position.y, delta);
|
||||
}
|
||||
this.previousScale_ = scale;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
this.dispose();
|
||||
/**
|
||||
* Handle a pointerup event and end the gesture.
|
||||
*
|
||||
* @param e A pointerup event.
|
||||
* @internal
|
||||
*/
|
||||
handleTouchEnd(e: PointerEvent) {
|
||||
const pointerId = Touch.getTouchIdentifierFromEvent(e);
|
||||
if (this.cachedPoints.has(pointerId)) {
|
||||
this.cachedPoints.delete(pointerId);
|
||||
}
|
||||
if (this.cachedPoints.size < 2) {
|
||||
this.cachedPoints.clear();
|
||||
this.previousScale_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function returning the current touch point coordinate.
|
||||
*
|
||||
* @param e A pointer event.
|
||||
* @returns The current touch point coordinate
|
||||
* @internal
|
||||
*/
|
||||
getTouchPoint(e: PointerEvent): Coordinate|null {
|
||||
if (!this.startWorkspace_) {
|
||||
return null;
|
||||
}
|
||||
return new Coordinate(e.pageX, e.pageY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this gesture is part of a multi-touch gesture.
|
||||
*
|
||||
* @returns Whether this gesture is part of a multi-touch gesture.
|
||||
* @internal
|
||||
*/
|
||||
isMultiTouch(): boolean {
|
||||
return this.isMultiTouch_;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -574,10 +757,10 @@ export class Gesture {
|
||||
/**
|
||||
* Handle a real or faked right-click event by showing a context menu.
|
||||
*
|
||||
* @param e A mouse move or touch move event.
|
||||
* @param e A pointerdown event.
|
||||
* @internal
|
||||
*/
|
||||
handleRightClick(e: Event) {
|
||||
handleRightClick(e: PointerEvent) {
|
||||
if (this.targetBlock_) {
|
||||
this.bringBlockToFront_();
|
||||
this.targetBlock_.workspace.hideChaff(!!this.flyout_);
|
||||
@@ -597,13 +780,13 @@ export class Gesture {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a workspace.
|
||||
* Handle a pointerdown event on a workspace.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @param e A pointerdown event.
|
||||
* @param ws The workspace the event hit.
|
||||
* @internal
|
||||
*/
|
||||
handleWsStart(e: MouseEvent, ws: WorkspaceSvg) {
|
||||
handleWsStart(e: PointerEvent, ws: WorkspaceSvg) {
|
||||
if (this.hasStarted_) {
|
||||
throw Error(
|
||||
'Tried to call gesture.handleWsStart, ' +
|
||||
@@ -625,13 +808,13 @@ export class Gesture {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a flyout.
|
||||
* Handle a pointerdown event on a flyout.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @param e A pointerdown event.
|
||||
* @param flyout The flyout the event hit.
|
||||
* @internal
|
||||
*/
|
||||
handleFlyoutStart(e: MouseEvent, flyout: IFlyout) {
|
||||
handleFlyoutStart(e: PointerEvent, flyout: IFlyout) {
|
||||
if (this.hasStarted_) {
|
||||
throw Error(
|
||||
'Tried to call gesture.handleFlyoutStart, ' +
|
||||
@@ -642,13 +825,13 @@ export class Gesture {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a block.
|
||||
* Handle a pointerdown event on a block.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @param e A pointerdown event.
|
||||
* @param block The block the event hit.
|
||||
* @internal
|
||||
*/
|
||||
handleBlockStart(e: Event, block: BlockSvg) {
|
||||
handleBlockStart(e: PointerEvent, block: BlockSvg) {
|
||||
if (this.hasStarted_) {
|
||||
throw Error(
|
||||
'Tried to call gesture.handleBlockStart, ' +
|
||||
@@ -659,13 +842,13 @@ export class Gesture {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mousedown/touchstart event on a bubble.
|
||||
* Handle a pointerdown event on a bubble.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @param e A pointerdown event.
|
||||
* @param bubble The bubble the event hit.
|
||||
* @internal
|
||||
*/
|
||||
handleBubbleStart(e: Event, bubble: IBubble) {
|
||||
handleBubbleStart(e: PointerEvent, bubble: IBubble) {
|
||||
if (this.hasStarted_) {
|
||||
throw Error(
|
||||
'Tried to call gesture.handleBubbleStart, ' +
|
||||
@@ -734,9 +917,9 @@ export class Gesture {
|
||||
* Execute a workspace click. When in accessibility mode shift clicking will
|
||||
* move the cursor.
|
||||
*
|
||||
* @param _e A mouse up or touch end event.
|
||||
* @param _e A pointerup event.
|
||||
*/
|
||||
private doWorkspaceClick_(_e: Event) {
|
||||
private doWorkspaceClick_(_e: PointerEvent) {
|
||||
const ws = this.creatorWorkspace;
|
||||
if (common.getSelected()) {
|
||||
common.getSelected()!.unselect();
|
||||
@@ -760,7 +943,7 @@ export class Gesture {
|
||||
}
|
||||
}
|
||||
|
||||
/* Begin functions for populating a gesture at mouse down. */
|
||||
/* Begin functions for populating a gesture at pointerdown. */
|
||||
|
||||
/**
|
||||
* Record the field that a gesture started on.
|
||||
@@ -849,14 +1032,14 @@ export class Gesture {
|
||||
}
|
||||
}
|
||||
|
||||
/* End functions for populating a gesture at mouse down. */
|
||||
/* End functions for populating a gesture at pointerdown. */
|
||||
|
||||
/* Begin helper functions defining types of clicks. Any developer wanting
|
||||
* to change the definition of a click should modify only this code. */
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a bubble. This should only be called
|
||||
* when ending a gesture (mouse up, touch end).
|
||||
* when ending a gesture (pointerup).
|
||||
*
|
||||
* @returns Whether this gesture was a click on a bubble.
|
||||
*/
|
||||
@@ -868,7 +1051,7 @@ export class Gesture {
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a block. This should only be called
|
||||
* when ending a gesture (mouse up, touch end).
|
||||
* when ending a gesture (pointerup).
|
||||
*
|
||||
* @returns Whether this gesture was a click on a block.
|
||||
*/
|
||||
@@ -882,7 +1065,7 @@ export class Gesture {
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a field. This should only be called
|
||||
* when ending a gesture (mouse up, touch end).
|
||||
* when ending a gesture (pointerup).
|
||||
*
|
||||
* @returns Whether this gesture was a click on a field.
|
||||
*/
|
||||
@@ -895,7 +1078,7 @@ export class Gesture {
|
||||
|
||||
/**
|
||||
* Whether this gesture is a click on a workspace. This should only be called
|
||||
* when ending a gesture (mouse up, touch end).
|
||||
* when ending a gesture (pointerup).
|
||||
*
|
||||
* @returns Whether this gesture was a click on a workspace.
|
||||
*/
|
||||
@@ -921,9 +1104,9 @@ export class Gesture {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this gesture has already been started. In theory every mouse down
|
||||
* has a corresponding mouse up, but in reality it is possible to lose a
|
||||
* mouse up, leaving an in-process gesture hanging.
|
||||
* Whether this gesture has already been started. In theory every pointerdown
|
||||
* has a corresponding pointerup, but in reality it is possible to lose a
|
||||
* pointerup, leaving an in-process gesture hanging.
|
||||
*
|
||||
* @returns Whether this gesture was a click on a workspace.
|
||||
* @internal
|
||||
|
||||
@@ -75,7 +75,7 @@ export abstract class Icon {
|
||||
|
||||
this.getBlock().getSvgRoot().appendChild(this.iconGroup_);
|
||||
browserEvents.conditionalBind(
|
||||
this.iconGroup_, 'mouseup', this, this.iconClick_);
|
||||
this.iconGroup_, 'pointerup', this, this.iconClick_);
|
||||
this.updateEditable();
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export abstract class Icon {
|
||||
*
|
||||
* @param e Mouse click event.
|
||||
*/
|
||||
protected iconClick_(e: MouseEvent) {
|
||||
protected iconClick_(e: PointerEvent) {
|
||||
if (this.getBlock().workspace.isDragging()) {
|
||||
// Drag operation is concluding. Don't open the editor.
|
||||
return;
|
||||
|
||||
@@ -393,7 +393,7 @@ function loadSounds(pathToMedia: string, workspace: WorkspaceSvg) {
|
||||
|
||||
// Android ignores any sound not loaded as a result of a user action.
|
||||
soundBinds.push(browserEvents.conditionalBind(
|
||||
document, 'mousemove', null, unbindSounds, true));
|
||||
document, 'pointermove', null, unbindSounds, true));
|
||||
soundBinds.push(browserEvents.conditionalBind(
|
||||
document, 'touchstart', null, unbindSounds, true));
|
||||
}
|
||||
|
||||
26
core/menu.ts
26
core/menu.ts
@@ -105,13 +105,13 @@ export class Menu {
|
||||
|
||||
// Add event handlers.
|
||||
this.mouseOverHandler = browserEvents.conditionalBind(
|
||||
element, 'mouseover', this, this.handleMouseOver, true);
|
||||
element, 'pointerover', this, this.handleMouseOver, true);
|
||||
this.clickHandler = browserEvents.conditionalBind(
|
||||
element, 'click', this, this.handleClick, true);
|
||||
element, 'pointerdown', this, this.handleClick, true);
|
||||
this.mouseEnterHandler = browserEvents.conditionalBind(
|
||||
element, 'mouseenter', this, this.handleMouseEnter, true);
|
||||
element, 'pointerenter', this, this.handleMouseEnter, true);
|
||||
this.mouseLeaveHandler = browserEvents.conditionalBind(
|
||||
element, 'mouseleave', this, this.handleMouseLeave, true);
|
||||
element, 'pointerleave', this, this.handleMouseLeave, true);
|
||||
this.onKeyDownHandler = browserEvents.conditionalBind(
|
||||
element, 'keydown', this, this.handleKeyEvent);
|
||||
|
||||
@@ -310,7 +310,7 @@ export class Menu {
|
||||
*
|
||||
* @param e Mouse event to handle.
|
||||
*/
|
||||
private handleMouseOver(e: Event) {
|
||||
private handleMouseOver(e: PointerEvent) {
|
||||
const menuItem = this.getMenuItem(e.target as Element);
|
||||
|
||||
if (menuItem) {
|
||||
@@ -329,18 +329,12 @@ export class Menu {
|
||||
*
|
||||
* @param e Click event to handle.
|
||||
*/
|
||||
private handleClick(e: Event) {
|
||||
private handleClick(e: PointerEvent) {
|
||||
const oldCoords = this.openingCoords;
|
||||
// Clear out the saved opening coords immediately so they're not used twice.
|
||||
this.openingCoords = null;
|
||||
// AnyDuringMigration because: Property 'clientX' does not exist on type
|
||||
// 'Event'.
|
||||
if (oldCoords && typeof (e as AnyDuringMigration).clientX === 'number') {
|
||||
// AnyDuringMigration because: Property 'clientY' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Property 'clientX' does not exist
|
||||
// on type 'Event'.
|
||||
const newCoords = new Coordinate(
|
||||
(e as AnyDuringMigration).clientX, (e as AnyDuringMigration).clientY);
|
||||
if (oldCoords && typeof e.clientX === 'number') {
|
||||
const newCoords = new Coordinate(e.clientX, e.clientY);
|
||||
if (Coordinate.distance(oldCoords, newCoords) < 1) {
|
||||
// This menu was opened by a mousedown and we're handling the consequent
|
||||
// click event. The coords haven't changed, meaning this was the same
|
||||
@@ -362,7 +356,7 @@ export class Menu {
|
||||
*
|
||||
* @param _e Mouse event to handle.
|
||||
*/
|
||||
private handleMouseEnter(_e: Event) {
|
||||
private handleMouseEnter(_e: PointerEvent) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
@@ -371,7 +365,7 @@ export class Menu {
|
||||
*
|
||||
* @param _e Mouse event to handle.
|
||||
*/
|
||||
private handleMouseLeave(_e: Event) {
|
||||
private handleMouseLeave(_e: PointerEvent) {
|
||||
if (this.getElement()) {
|
||||
this.blur();
|
||||
this.setHighlighted(null);
|
||||
|
||||
@@ -155,7 +155,7 @@ export class Mutator extends Icon {
|
||||
*
|
||||
* @param e Mouse click event.
|
||||
*/
|
||||
protected override iconClick_(e: MouseEvent) {
|
||||
protected override iconClick_(e: PointerEvent) {
|
||||
if (this.getBlock().isEditable()) {
|
||||
super.iconClick_(e);
|
||||
}
|
||||
|
||||
@@ -211,9 +211,9 @@ export class Scrollbar {
|
||||
}
|
||||
|
||||
this.onMouseDownBarWrapper_ = browserEvents.conditionalBind(
|
||||
this.svgBackground, 'mousedown', this, this.onMouseDownBar);
|
||||
this.svgBackground, 'pointerdown', this, this.onMouseDownBar);
|
||||
this.onMouseDownHandleWrapper_ = browserEvents.conditionalBind(
|
||||
this.svgHandle, 'mousedown', this, this.onMouseDownHandle);
|
||||
this.svgHandle, 'pointerdown', this, this.onMouseDownHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -703,7 +703,7 @@ export class Scrollbar {
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
*/
|
||||
private onMouseDownHandle(e: MouseEvent) {
|
||||
private onMouseDownHandle(e: PointerEvent) {
|
||||
this.workspace.markFocused();
|
||||
this.cleanUp();
|
||||
if (browserEvents.isRightButton(e)) {
|
||||
@@ -723,9 +723,9 @@ export class Scrollbar {
|
||||
// Record the current mouse position.
|
||||
this.startDragMouse = this.horizontal ? e.clientX : e.clientY;
|
||||
this.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mouseup', this, this.onMouseUpHandle);
|
||||
document, 'pointerup', this, this.onMouseUpHandle);
|
||||
this.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mousemove', this, this.onMouseMoveHandle);
|
||||
document, 'pointermove', this, this.onMouseMoveHandle);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
@@ -735,7 +735,7 @@ export class Scrollbar {
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
*/
|
||||
private onMouseMoveHandle(e: MouseEvent) {
|
||||
private onMouseMoveHandle(e: PointerEvent) {
|
||||
const currentMouse = this.horizontal ? e.clientX : e.clientY;
|
||||
const mouseDelta = currentMouse - this.startDragMouse;
|
||||
const handlePosition = this.startDragHandle + mouseDelta;
|
||||
|
||||
@@ -229,13 +229,13 @@ export class Toolbox extends DeleteArea implements IAutoHideable,
|
||||
container: HTMLDivElement, contentsContainer: HTMLDivElement) {
|
||||
// Clicking on toolbox closes popups.
|
||||
const clickEvent = browserEvents.conditionalBind(
|
||||
container, 'click', this, this.onClick_,
|
||||
/* opt_noCaptureIdentifier */ false, /* opt_noPreventDefault */ true);
|
||||
container, 'pointerdown', this, this.onClick_,
|
||||
/* opt_noCaptureIdentifier */ false);
|
||||
this.boundEvents_.push(clickEvent);
|
||||
|
||||
const keyDownEvent = browserEvents.conditionalBind(
|
||||
contentsContainer, 'keydown', this, this.onKeyDown_,
|
||||
/* opt_noCaptureIdentifier */ false, /* opt_noPreventDefault */ true);
|
||||
/* opt_noCaptureIdentifier */ false);
|
||||
this.boundEvents_.push(keyDownEvent);
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export class Toolbox extends DeleteArea implements IAutoHideable,
|
||||
*
|
||||
* @param e Click event to handle.
|
||||
*/
|
||||
protected onClick_(e: MouseEvent) {
|
||||
protected onClick_(e: PointerEvent) {
|
||||
if (browserEvents.isRightButton(e) || e.target === this.HtmlDiv) {
|
||||
// Close flyout.
|
||||
(common.getMainWorkspace() as WorkspaceSvg).hideChaff(false);
|
||||
|
||||
@@ -231,14 +231,14 @@ export function createDom() {
|
||||
export function bindMouseEvents(element: Element) {
|
||||
// TODO (#6097): Don't stash wrapper info on the DOM.
|
||||
(element as AnyDuringMigration).mouseOverWrapper_ =
|
||||
browserEvents.bind(element, 'mouseover', null, onMouseOver);
|
||||
browserEvents.bind(element, 'pointerover', null, onMouseOver);
|
||||
(element as AnyDuringMigration).mouseOutWrapper_ =
|
||||
browserEvents.bind(element, 'mouseout', null, onMouseOut);
|
||||
browserEvents.bind(element, 'pointerout', null, onMouseOut);
|
||||
|
||||
// Don't use bindEvent_ for mousemove since that would create a
|
||||
// corresponding touch handler, even though this only makes sense in the
|
||||
// context of a mouseover/mouseout.
|
||||
element.addEventListener('mousemove', onMouseMove, false);
|
||||
element.addEventListener('pointermove', onMouseMove, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,7 +254,7 @@ export function unbindMouseEvents(element: Element|null) {
|
||||
// TODO (#6097): Don't stash wrapper info on the DOM.
|
||||
browserEvents.unbind((element as AnyDuringMigration).mouseOverWrapper_);
|
||||
browserEvents.unbind((element as AnyDuringMigration).mouseOutWrapper_);
|
||||
element.removeEventListener('mousemove', onMouseMove);
|
||||
element.removeEventListener('pointermove', onMouseMove);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +263,7 @@ export function unbindMouseEvents(element: Element|null) {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
function onMouseOver(e: Event) {
|
||||
function onMouseOver(e: PointerEvent) {
|
||||
if (blocked) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
@@ -285,7 +285,7 @@ function onMouseOver(e: Event) {
|
||||
*
|
||||
* @param _e Mouse event.
|
||||
*/
|
||||
function onMouseOut(_e: Event) {
|
||||
function onMouseOut(_e: PointerEvent) {
|
||||
if (blocked) {
|
||||
// Someone doesn't want us to show tooltips.
|
||||
return;
|
||||
|
||||
127
core/touch.ts
127
core/touch.ts
@@ -13,6 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Touch');
|
||||
|
||||
import type {Gesture} from './gesture.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,23 +53,17 @@ let touchIdentifier_: string|null = null;
|
||||
*
|
||||
* @alias Blockly.Touch.TOUCH_MAP
|
||||
*/
|
||||
export const TOUCH_MAP: {[key: string]: string[]} = globalThis['PointerEvent'] ?
|
||||
{
|
||||
'mousedown': ['pointerdown'],
|
||||
'mouseenter': ['pointerenter'],
|
||||
'mouseleave': ['pointerleave'],
|
||||
'mousemove': ['pointermove'],
|
||||
'mouseout': ['pointerout'],
|
||||
'mouseover': ['pointerover'],
|
||||
'mouseup': ['pointerup', 'pointercancel'],
|
||||
'touchend': ['pointerup'],
|
||||
'touchcancel': ['pointercancel'],
|
||||
} :
|
||||
{
|
||||
'mousedown': ['touchstart'],
|
||||
'mousemove': ['touchmove'],
|
||||
'mouseup': ['touchend', 'touchcancel'],
|
||||
};
|
||||
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;
|
||||
@@ -85,29 +80,9 @@ let longPid_: AnyDuringMigration = 0;
|
||||
* @alias Blockly.Touch.longStart
|
||||
* @internal
|
||||
*/
|
||||
export function longStart(e: Event, gesture: Gesture) {
|
||||
export function longStart(e: PointerEvent, gesture: Gesture) {
|
||||
longStop();
|
||||
// Punt on multitouch events.
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'Event'.
|
||||
if ((e as AnyDuringMigration).changedTouches &&
|
||||
(e as AnyDuringMigration).changedTouches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
longPid_ = setTimeout(function() {
|
||||
// TODO(#6097): Make types accurate, possibly by refactoring touch handling.
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'Event'.
|
||||
const typelessEvent = e as AnyDuringMigration;
|
||||
// Additional check to distinguish between touch events and pointer events
|
||||
if (typelessEvent.changedTouches) {
|
||||
// TouchEvent
|
||||
typelessEvent.button = 2; // Simulate a right button click.
|
||||
// e was a touch event. It needs to pretend to be a mouse event.
|
||||
typelessEvent.clientX = typelessEvent.changedTouches[0].clientX;
|
||||
typelessEvent.clientY = typelessEvent.changedTouches[0].clientY;
|
||||
}
|
||||
|
||||
// Let the gesture route the right-click correctly.
|
||||
if (gesture) {
|
||||
gesture.handleRightClick(e);
|
||||
@@ -150,78 +125,46 @@ export function clearTouchIdentifier() {
|
||||
* handler; false if it should be blocked.
|
||||
* @alias Blockly.Touch.shouldHandleEvent
|
||||
*/
|
||||
export function shouldHandleEvent(e: Event|PseudoEvent): boolean {
|
||||
return !isMouseOrTouchEvent(e) || checkTouchIdentifier(e);
|
||||
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 touch identifier from the given event. If it was a mouse event, the
|
||||
* identifier is the string 'mouse'.
|
||||
* Get the pointer identifier from the given event.
|
||||
*
|
||||
* @param e Pointer event, mouse event, or touch event.
|
||||
* @returns The pointerId, or touch identifier from the first changed touch, if
|
||||
* defined. Otherwise 'mouse'.
|
||||
* @param e Pointer event.
|
||||
* @returns The pointerId of the event.
|
||||
* @alias Blockly.Touch.getTouchIdentifierFromEvent
|
||||
*/
|
||||
export function getTouchIdentifierFromEvent(e: Event|PseudoEvent): string {
|
||||
if (e instanceof PointerEvent) {
|
||||
return String(e.pointerId);
|
||||
}
|
||||
|
||||
if (e instanceof MouseEvent) {
|
||||
return 'mouse';
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(#6097): Fix types. This is a catch-all for everything but mouse
|
||||
* and pointer events.
|
||||
*/
|
||||
const pseudoEvent = /** {!PseudoEvent} */ e;
|
||||
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'PseudoEvent | Event'. AnyDuringMigration because: Property
|
||||
// 'changedTouches' does not exist on type 'PseudoEvent | Event'.
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'PseudoEvent | Event'. AnyDuringMigration because: Property
|
||||
// 'changedTouches' does not exist on type 'PseudoEvent | Event'.
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'PseudoEvent | Event'.
|
||||
return (pseudoEvent as AnyDuringMigration).changedTouches &&
|
||||
(pseudoEvent as AnyDuringMigration).changedTouches[0] &&
|
||||
(pseudoEvent as AnyDuringMigration).changedTouches[0].identifier !==
|
||||
undefined &&
|
||||
(pseudoEvent as AnyDuringMigration).changedTouches[0].identifier !==
|
||||
null ?
|
||||
String((pseudoEvent as AnyDuringMigration).changedTouches[0].identifier) :
|
||||
'mouse';
|
||||
export function getTouchIdentifierFromEvent(e: PointerEvent): string {
|
||||
return `${e.pointerId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the touch identifier on the event matches the current saved
|
||||
* identifier. If there is no identifier, that means it's a mouse event and
|
||||
* we'll use the identifier "mouse". This means we won't deal well with
|
||||
* multiple mice being used at the same time. That seems okay.
|
||||
* If the current identifier was unset, save the identifier from the
|
||||
* event. This starts a drag/gesture, during which touch events with other
|
||||
* identifiers will be silently ignored.
|
||||
* 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 Mouse event or touch event.
|
||||
* @param e Pointer event.
|
||||
* @returns Whether the identifier on the event matches the current saved
|
||||
* identifier.
|
||||
* @alias Blockly.Touch.checkTouchIdentifier
|
||||
*/
|
||||
export function checkTouchIdentifier(e: Event|PseudoEvent): boolean {
|
||||
export function checkTouchIdentifier(e: PointerEvent): boolean {
|
||||
const identifier = getTouchIdentifierFromEvent(e);
|
||||
|
||||
// if (touchIdentifier_) is insufficient because Android touch
|
||||
// identifiers may be zero.
|
||||
if (touchIdentifier_ !== undefined && touchIdentifier_ !== null) {
|
||||
if (touchIdentifier_) {
|
||||
// We're already tracking some touch/mouse event. Is this from the same
|
||||
// source?
|
||||
return touchIdentifier_ === identifier;
|
||||
}
|
||||
if (e.type === 'mousedown' || e.type === 'touchstart' ||
|
||||
e.type === 'pointerdown') {
|
||||
if (e.type === 'pointerdown') {
|
||||
// No identifier set yet, and this is the start of a drag. Set it and
|
||||
// return.
|
||||
touchIdentifier_ = identifier;
|
||||
@@ -241,6 +184,7 @@ export function checkTouchIdentifier(e: Event|PseudoEvent): boolean {
|
||||
* @alias Blockly.Touch.setClientFromTouch
|
||||
*/
|
||||
export function setClientFromTouch(e: Event|PseudoEvent) {
|
||||
deprecation.warn('setClientFromTouch()', 'version 9', 'version 10');
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'PseudoEvent | Event'.
|
||||
if (e.type.startsWith('touch') && (e as AnyDuringMigration).changedTouches) {
|
||||
@@ -265,6 +209,7 @@ export function setClientFromTouch(e: Event|PseudoEvent) {
|
||||
* @alias Blockly.Touch.isMouseOrTouchEvent
|
||||
*/
|
||||
export function isMouseOrTouchEvent(e: Event|PseudoEvent): boolean {
|
||||
deprecation.warn('isMouseOrTouchEvent()', 'version 9', 'version 10');
|
||||
return e.type.startsWith('touch') || e.type.startsWith('mouse') ||
|
||||
e.type.startsWith('pointer');
|
||||
}
|
||||
@@ -277,6 +222,7 @@ export function isMouseOrTouchEvent(e: Event|PseudoEvent): boolean {
|
||||
* @alias Blockly.Touch.isTouchEvent
|
||||
*/
|
||||
export function isTouchEvent(e: Event|PseudoEvent): boolean {
|
||||
deprecation.warn('isTouchEvent()', 'version 9', 'version 10');
|
||||
return e.type.startsWith('touch') || e.type.startsWith('pointer');
|
||||
}
|
||||
|
||||
@@ -291,6 +237,7 @@ export function isTouchEvent(e: Event|PseudoEvent): boolean {
|
||||
* @alias Blockly.Touch.splitEventByTouches
|
||||
*/
|
||||
export function splitEventByTouches(e: Event): Array<Event|PseudoEvent> {
|
||||
deprecation.warn('splitEventByTouches()', 'version 9', 'version 10');
|
||||
const events = [];
|
||||
// AnyDuringMigration because: Property 'changedTouches' does not exist on
|
||||
// type 'PseudoEvent | Event'.
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* The class extends Gesture to support pinch to zoom
|
||||
* for both pointer and touch events.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.TouchGesture');
|
||||
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import {Gesture} from './gesture.js';
|
||||
import * as Touch from './touch.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
|
||||
/*
|
||||
* Note: In this file "start" refers to touchstart, mousedown, and pointerstart
|
||||
* events. "End" refers to touchend, mouseup, and pointerend events.
|
||||
*/
|
||||
|
||||
/** A multiplier used to convert the gesture scale to a zoom in delta. */
|
||||
const ZOOM_IN_MULTIPLIER = 5;
|
||||
|
||||
/** A multiplier used to convert the gesture scale to a zoom out delta. */
|
||||
const ZOOM_OUT_MULTIPLIER = 6;
|
||||
|
||||
/**
|
||||
* Class for one gesture.
|
||||
*
|
||||
* @alias Blockly.TouchGesture
|
||||
*/
|
||||
export class TouchGesture extends Gesture {
|
||||
/** Boolean for whether or not this gesture is a multi-touch gesture. */
|
||||
private isMultiTouch_ = false;
|
||||
|
||||
/** A map of cached points used for tracking multi-touch gestures. */
|
||||
private cachedPoints = new Map<string, Coordinate|null>();
|
||||
|
||||
/**
|
||||
* This is the ratio between the starting distance between the touch points
|
||||
* and the most recent distance between the touch points.
|
||||
* Scales between 0 and 1 mean the most recent zoom was a zoom out.
|
||||
* Scales above 1.0 mean the most recent zoom was a zoom in.
|
||||
*/
|
||||
private previousScale_ = 0;
|
||||
|
||||
/** The starting distance between two touch points. */
|
||||
private startDistance_ = 0;
|
||||
|
||||
/**
|
||||
* A handle to use to unbind the second touch start or pointer down listener
|
||||
* at the end of a drag.
|
||||
* Opaque data returned from Blockly.bindEventWithChecks_.
|
||||
*/
|
||||
private onStartWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Boolean for whether or not the workspace supports pinch-zoom. */
|
||||
private isPinchZoomEnabled_: boolean|null = null;
|
||||
override onMoveWrapper_: browserEvents.Data|null = null;
|
||||
override onUpWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* Start a gesture: update the workspace to indicate that a gesture is in
|
||||
* progress and bind mousemove and mouseup handlers.
|
||||
*
|
||||
* @param e A mouse down, touch start or pointer down event.
|
||||
* @internal
|
||||
*/
|
||||
override doStart(e: MouseEvent) {
|
||||
if (!this.startWorkspace_) {
|
||||
throw new Error(
|
||||
'Cannot start the touch event becauase the start ' +
|
||||
'workspace is undefined');
|
||||
}
|
||||
this.isPinchZoomEnabled_ = this.startWorkspace_.options.zoomOptions &&
|
||||
this.startWorkspace_.options.zoomOptions.pinch;
|
||||
super.doStart(e);
|
||||
if (!this.isEnding_ && Touch.isTouchEvent(e)) {
|
||||
this.handleTouchStart(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind gesture events.
|
||||
* Overriding the gesture definition of this function, binding the same
|
||||
* functions for onMoveWrapper_ and onUpWrapper_ but passing
|
||||
* opt_noCaptureIdentifier.
|
||||
* In addition, binding a second mouse down event to detect multi-touch
|
||||
* events.
|
||||
*
|
||||
* @param e A mouse down or touch start event.
|
||||
* @internal
|
||||
*/
|
||||
override bindMouseEvents(e: Event) {
|
||||
this.onStartWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mousedown', null, this.handleStart.bind(this),
|
||||
/* opt_noCaptureIdentifier */ true);
|
||||
this.onMoveWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mousemove', null, this.handleMove.bind(this),
|
||||
/* opt_noCaptureIdentifier */ true);
|
||||
this.onUpWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mouseup', null, this.handleUp.bind(this),
|
||||
/* opt_noCaptureIdentifier */ true);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse down, touch start, or pointer down event.
|
||||
*
|
||||
* @param e A mouse down, touch start, or pointer down event.
|
||||
* @internal
|
||||
*/
|
||||
handleStart(e: Event) {
|
||||
if (this.isDragging()) {
|
||||
// A drag has already started, so this can no longer be a pinch-zoom.
|
||||
return;
|
||||
}
|
||||
if (Touch.isTouchEvent(e)) {
|
||||
this.handleTouchStart(e);
|
||||
|
||||
if (this.isMultiTouch()) {
|
||||
Touch.longStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse move, touch move, or pointer move event.
|
||||
*
|
||||
* @param e A mouse move, touch move, or pointer move event.
|
||||
* @internal
|
||||
*/
|
||||
override handleMove(e: MouseEvent) {
|
||||
if (this.isDragging()) {
|
||||
// We are in the middle of a drag, only handle the relevant events
|
||||
if (Touch.shouldHandleEvent(e)) {
|
||||
super.handleMove(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.isMultiTouch()) {
|
||||
if (Touch.isTouchEvent(e)) {
|
||||
this.handleTouchMove(e);
|
||||
}
|
||||
Touch.longStop();
|
||||
} else {
|
||||
super.handleMove(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse up, touch end, or pointer up event.
|
||||
*
|
||||
* @param e A mouse up, touch end, or pointer up event.
|
||||
* @internal
|
||||
*/
|
||||
override handleUp(e: Event) {
|
||||
if (Touch.isTouchEvent(e) && !this.isDragging()) {
|
||||
this.handleTouchEnd(e);
|
||||
}
|
||||
if (!this.isMultiTouch() || this.isDragging()) {
|
||||
if (!Touch.shouldHandleEvent(e)) {
|
||||
return;
|
||||
}
|
||||
super.handleUp(e);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this gesture is part of a multi-touch gesture.
|
||||
*
|
||||
* @returns Whether this gesture is part of a multi-touch gesture.
|
||||
* @internal
|
||||
*/
|
||||
isMultiTouch(): boolean {
|
||||
return this.isMultiTouch_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
override dispose() {
|
||||
super.dispose();
|
||||
|
||||
if (this.onStartWrapper_) {
|
||||
browserEvents.unbind(this.onStartWrapper_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a touch start or pointer down event and keep track of current
|
||||
* pointers.
|
||||
*
|
||||
* @param e A touch start, or pointer down event.
|
||||
* @internal
|
||||
*/
|
||||
handleTouchStart(e: Event) {
|
||||
const pointerId = Touch.getTouchIdentifierFromEvent(e);
|
||||
// store the pointerId in the current list of pointers
|
||||
this.cachedPoints.set(pointerId, this.getTouchPoint(e));
|
||||
const pointers = Array.from(this.cachedPoints.keys());
|
||||
// If two pointers are down, store info
|
||||
if (pointers.length === 2) {
|
||||
const point0 = (this.cachedPoints.get(pointers[0]))!;
|
||||
const point1 = (this.cachedPoints.get(pointers[1]))!;
|
||||
this.startDistance_ = Coordinate.distance(point0, point1);
|
||||
this.isMultiTouch_ = true;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a touch move or pointer move event and zoom in/out if two pointers
|
||||
* are on the screen.
|
||||
*
|
||||
* @param e A touch move, or pointer move event.
|
||||
* @internal
|
||||
*/
|
||||
handleTouchMove(e: MouseEvent) {
|
||||
const pointerId = Touch.getTouchIdentifierFromEvent(e);
|
||||
// Update the cache
|
||||
this.cachedPoints.set(pointerId, this.getTouchPoint(e));
|
||||
|
||||
if (this.isPinchZoomEnabled_ && this.cachedPoints.size === 2) {
|
||||
this.handlePinch_(e);
|
||||
} else {
|
||||
super.handleMove(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pinch zoom gesture.
|
||||
*
|
||||
* @param e A touch move, or pointer move event.
|
||||
*/
|
||||
private handlePinch_(e: MouseEvent) {
|
||||
const pointers = Array.from(this.cachedPoints.keys());
|
||||
// Calculate the distance between the two pointers
|
||||
const point0 = (this.cachedPoints.get(pointers[0]))!;
|
||||
const point1 = (this.cachedPoints.get(pointers[1]))!;
|
||||
const moveDistance = Coordinate.distance(point0, point1);
|
||||
const scale = moveDistance / this.startDistance_;
|
||||
|
||||
if (this.previousScale_ > 0 && this.previousScale_ < Infinity) {
|
||||
const gestureScale = scale - this.previousScale_;
|
||||
const delta = gestureScale > 0 ? gestureScale * ZOOM_IN_MULTIPLIER :
|
||||
gestureScale * ZOOM_OUT_MULTIPLIER;
|
||||
if (!this.startWorkspace_) {
|
||||
throw new Error(
|
||||
'Cannot handle a pinch because the start workspace ' +
|
||||
'is undefined');
|
||||
}
|
||||
const workspace = this.startWorkspace_;
|
||||
const position = browserEvents.mouseToSvg(
|
||||
e, workspace.getParentSvg(), workspace.getInverseScreenCTM());
|
||||
workspace.zoom(position.x, position.y, delta);
|
||||
}
|
||||
this.previousScale_ = scale;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a touch end or pointer end event and end the gesture.
|
||||
*
|
||||
* @param e A touch end, or pointer end event.
|
||||
* @internal
|
||||
*/
|
||||
handleTouchEnd(e: Event) {
|
||||
const pointerId = Touch.getTouchIdentifierFromEvent(e);
|
||||
if (this.cachedPoints.has(pointerId)) {
|
||||
this.cachedPoints.delete(pointerId);
|
||||
}
|
||||
if (this.cachedPoints.size < 2) {
|
||||
this.cachedPoints.clear();
|
||||
this.previousScale_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function returning the current touch point coordinate.
|
||||
*
|
||||
* @param e A touch or pointer event.
|
||||
* @returns The current touch point coordinate
|
||||
* @internal
|
||||
*/
|
||||
getTouchPoint(e: Event): Coordinate|null {
|
||||
if (!this.startWorkspace_) {
|
||||
return null;
|
||||
}
|
||||
// TODO(#6097): Make types accurate, possibly by refactoring touch handling.
|
||||
const typelessEvent = e as AnyDuringMigration;
|
||||
return new Coordinate(
|
||||
typelessEvent.changedTouches ? typelessEvent.changedTouches[0].pageX :
|
||||
typelessEvent.pageX,
|
||||
typelessEvent.changedTouches ? typelessEvent.changedTouches[0].pageY :
|
||||
typelessEvent.pageY);
|
||||
}
|
||||
}
|
||||
@@ -201,11 +201,11 @@ export class Trashcan extends DeleteArea implements IAutoHideable,
|
||||
// Using bindEventWithChecks_ for blocking mousedown causes issue in mobile.
|
||||
// See #4303
|
||||
browserEvents.bind(
|
||||
this.svgGroup_, 'mousedown', this, this.blockMouseDownWhenOpenable_);
|
||||
browserEvents.bind(this.svgGroup_, 'mouseup', this, this.click);
|
||||
this.svgGroup_, 'pointerdown', this, this.blockMouseDownWhenOpenable_);
|
||||
browserEvents.bind(this.svgGroup_, 'pointerup', this, this.click);
|
||||
// Bind to body instead of this.svgGroup_ so that we don't get lid jitters
|
||||
browserEvents.bind(body, 'mouseover', this, this.mouseOver_);
|
||||
browserEvents.bind(body, 'mouseout', this, this.mouseOut_);
|
||||
browserEvents.bind(body, 'pointerover', this, this.mouseOver_);
|
||||
browserEvents.bind(body, 'pointerout', this, this.mouseOut_);
|
||||
this.animateLid_();
|
||||
return this.svgGroup_;
|
||||
}
|
||||
@@ -513,7 +513,7 @@ export class Trashcan extends DeleteArea implements IAutoHideable,
|
||||
*
|
||||
* @param e A mouse down event.
|
||||
*/
|
||||
private blockMouseDownWhenOpenable_(e: Event) {
|
||||
private blockMouseDownWhenOpenable_(e: PointerEvent) {
|
||||
if (!this.contentsIsOpen() && this.hasContents_()) {
|
||||
// Don't start a workspace scroll.
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -170,10 +170,10 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
if (!this.workspace.options.readOnly && !this.eventsInit_) {
|
||||
browserEvents.conditionalBind(
|
||||
this.svgRectTarget_ as SVGRectElement, 'mousedown', this,
|
||||
this.svgRectTarget_ as SVGRectElement, 'pointerdown', this,
|
||||
this.pathMouseDown_);
|
||||
browserEvents.conditionalBind(
|
||||
this.svgHandleTarget_ as SVGRectElement, 'mousedown', this,
|
||||
this.svgHandleTarget_ as SVGRectElement, 'pointerdown', this,
|
||||
this.pathMouseDown_);
|
||||
}
|
||||
this.eventsInit_ = true;
|
||||
@@ -189,11 +189,11 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on an SVG comment.
|
||||
* Handle a pointerdown on an SVG comment.
|
||||
*
|
||||
* @param e Mouse down event or touch start event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private pathMouseDown_(e: Event) {
|
||||
private pathMouseDown_(e: PointerEvent) {
|
||||
const gesture = this.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBubbleStart(e, this);
|
||||
@@ -203,11 +203,11 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
/**
|
||||
* Show the context menu for this workspace comment.
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @param e Pointer event.
|
||||
* @internal
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
showContextMenu(e: Event) {
|
||||
showContextMenu(e: PointerEvent) {
|
||||
throw new Error(
|
||||
'The implementation of showContextMenu should be ' +
|
||||
'monkey-patched in by blockly.ts');
|
||||
@@ -685,18 +685,18 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
|
||||
if (this.resizeGroup_) {
|
||||
browserEvents.conditionalBind(
|
||||
(this.resizeGroup_), 'mousedown', this, this.resizeMouseDown_);
|
||||
(this.resizeGroup_), 'pointerdown', this, this.resizeMouseDown_);
|
||||
}
|
||||
|
||||
if (this.isDeletable()) {
|
||||
browserEvents.conditionalBind(
|
||||
this.deleteGroup_ as SVGGElement, 'mousedown', this,
|
||||
this.deleteGroup_ as SVGGElement, 'pointerdown', this,
|
||||
this.deleteMouseDown_);
|
||||
browserEvents.conditionalBind(
|
||||
this.deleteGroup_ as SVGGElement, 'mouseout', this,
|
||||
this.deleteGroup_ as SVGGElement, 'pointerout', this,
|
||||
this.deleteMouseOut_);
|
||||
browserEvents.conditionalBind(
|
||||
this.deleteGroup_ as SVGGElement, 'mouseup', this,
|
||||
this.deleteGroup_ as SVGGElement, 'pointerup', this,
|
||||
this.deleteMouseUp_);
|
||||
}
|
||||
}
|
||||
@@ -820,11 +820,11 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on comment's resize corner.
|
||||
* Handle a pointerdown on comment's resize corner.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private resizeMouseDown_(e: MouseEvent) {
|
||||
private resizeMouseDown_(e: PointerEvent) {
|
||||
this.unbindDragEvents_();
|
||||
if (browserEvents.isRightButton(e)) {
|
||||
// No right-click.
|
||||
@@ -838,20 +838,20 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
this.workspace.RTL ? -this.width_ : this.width_, this.height_));
|
||||
|
||||
this.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mouseup', this, this.resizeMouseUp_);
|
||||
document, 'pointerup', this, this.resizeMouseUp_);
|
||||
this.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
||||
document, 'mousemove', this, this.resizeMouseMove_);
|
||||
document, 'pointermove', this, this.resizeMouseMove_);
|
||||
this.workspace.hideChaff();
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on comment's delete icon.
|
||||
* Handle a pointerdown on comment's delete icon.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private deleteMouseDown_(e: Event) {
|
||||
private deleteMouseDown_(e: PointerEvent) {
|
||||
// Highlight the delete icon.
|
||||
if (this.deleteIconBorder_) {
|
||||
dom.addClass(this.deleteIconBorder_, 'blocklyDeleteIconHighlighted');
|
||||
@@ -861,11 +861,11 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-out on comment's delete icon.
|
||||
* Handle a pointerout on comment's delete icon.
|
||||
*
|
||||
* @param _e Mouse out event.
|
||||
* @param _e Pointer out event.
|
||||
*/
|
||||
private deleteMouseOut_(_e: Event) {
|
||||
private deleteMouseOut_(_e: PointerEvent) {
|
||||
// Restore highlight on the delete icon.
|
||||
if (this.deleteIconBorder_) {
|
||||
dom.removeClass(this.deleteIconBorder_, 'blocklyDeleteIconHighlighted');
|
||||
@@ -873,18 +873,18 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-up on comment's delete icon.
|
||||
* Handle a pointerup on comment's delete icon.
|
||||
*
|
||||
* @param e Mouse up event.
|
||||
* @param e Pointer up event.
|
||||
*/
|
||||
private deleteMouseUp_(e: Event) {
|
||||
private deleteMouseUp_(e: PointerEvent) {
|
||||
// Delete this comment.
|
||||
this.dispose();
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
/** Stop binding to the global mouseup and mousemove events. */
|
||||
/** Stop binding to the global pointerup and pointermove events. */
|
||||
private unbindDragEvents_() {
|
||||
if (this.onMouseUpWrapper_) {
|
||||
browserEvents.unbind(this.onMouseUpWrapper_);
|
||||
@@ -897,21 +897,22 @@ export class WorkspaceCommentSvg extends WorkspaceComment implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-up event while dragging a comment's border or resize handle.
|
||||
* Handle a pointerup event while dragging a comment's border or resize
|
||||
* handle.
|
||||
*
|
||||
* @param _e Mouse up event.
|
||||
* @param _e Pointer up event.
|
||||
*/
|
||||
private resizeMouseUp_(_e: Event) {
|
||||
private resizeMouseUp_(_e: PointerEvent) {
|
||||
Touch.clearTouchIdentifier();
|
||||
this.unbindDragEvents_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize this comment to follow the mouse.
|
||||
* Resize this comment to follow the pointer.
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
* @param e Pointer move event.
|
||||
*/
|
||||
private resizeMouseMove_(e: MouseEvent) {
|
||||
private resizeMouseMove_(e: PointerEvent) {
|
||||
this.autoLayout_ = false;
|
||||
const newXY = this.workspace.moveDrag(e);
|
||||
this.setSize_(this.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||
|
||||
@@ -56,7 +56,6 @@ import type {Theme} from './theme.js';
|
||||
import {Classic} from './theme/classic.js';
|
||||
import {ThemeManager} from './theme_manager.js';
|
||||
import * as Tooltip from './tooltip.js';
|
||||
import {TouchGesture} from './touch_gesture.js';
|
||||
import type {Trashcan} from './trashcan.js';
|
||||
import * as arrayUtils from './utils/array.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
@@ -224,7 +223,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
currentGesture_: TouchGesture|null = null;
|
||||
currentGesture_: Gesture|null = null;
|
||||
|
||||
/** This workspace's surface for dragging blocks, if it exists. */
|
||||
private readonly blockDragSurface: BlockDragSurfaceSvg|null = null;
|
||||
@@ -774,7 +773,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
|
||||
if (!this.isFlyout) {
|
||||
browserEvents.conditionalBind(
|
||||
this.svgGroup_, 'mousedown', this, this.onMouseDown_, false, true);
|
||||
this.svgGroup_, 'pointerdown', this, this.onMouseDown_, false);
|
||||
// This no-op works around https://bugs.webkit.org/show_bug.cgi?id=226683,
|
||||
// which otherwise prevents zoom/scroll events from being observed in
|
||||
// Safari. Once that bug is fixed it should be removed.
|
||||
@@ -1594,17 +1593,15 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
/* eslint-enable */
|
||||
|
||||
/**
|
||||
* Returns the drag target the mouse event is over.
|
||||
* Returns the drag target the pointer event is over.
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
* @param e Pointer move event.
|
||||
* @returns Null if not over a drag target, or the drag target the event is
|
||||
* over.
|
||||
*/
|
||||
getDragTarget(e: Event): IDragTarget|null {
|
||||
getDragTarget(e: PointerEvent): IDragTarget|null {
|
||||
for (let i = 0, targetArea; targetArea = this.dragTargetAreas[i]; i++) {
|
||||
if (targetArea.clientRect.contains(
|
||||
(e as AnyDuringMigration).clientX,
|
||||
(e as AnyDuringMigration).clientY)) {
|
||||
if (targetArea.clientRect.contains(e.clientX, e.clientY)) {
|
||||
return targetArea.component;
|
||||
}
|
||||
}
|
||||
@@ -1612,11 +1609,11 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on SVG drawing surface.
|
||||
* Handle a pointerdown on SVG drawing surface.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
*/
|
||||
private onMouseDown_(e: MouseEvent) {
|
||||
private onMouseDown_(e: PointerEvent) {
|
||||
const gesture = this.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleWsStart(e, this);
|
||||
@@ -1626,10 +1623,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
/**
|
||||
* Start tracking a drag of an object on this workspace.
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
* @param e Pointer down event.
|
||||
* @param xy Starting location of object.
|
||||
*/
|
||||
startDrag(e: MouseEvent, xy: Coordinate) {
|
||||
startDrag(e: PointerEvent, xy: Coordinate) {
|
||||
// Record the starting offset between the bubble's location and the mouse.
|
||||
const point = browserEvents.mouseToSvg(
|
||||
e, this.getParentSvg(), this.getInverseScreenCTM());
|
||||
@@ -1642,10 +1639,10 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
/**
|
||||
* Track a drag of an object on this workspace.
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
* @param e Pointer move event.
|
||||
* @returns New location of object.
|
||||
*/
|
||||
moveDrag(e: MouseEvent): Coordinate {
|
||||
moveDrag(e: PointerEvent): Coordinate {
|
||||
const point = browserEvents.mouseToSvg(
|
||||
e, this.getParentSvg(), this.getInverseScreenCTM());
|
||||
// Fix scale of mouse event.
|
||||
@@ -2471,14 +2468,13 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
* Look up the gesture that is tracking this touch stream on this workspace.
|
||||
* May create a new gesture.
|
||||
*
|
||||
* @param e Mouse event or touch event.
|
||||
* @param e Pointer event.
|
||||
* @returns The gesture that is tracking this touch stream, or null if no
|
||||
* valid gesture exists.
|
||||
* @internal
|
||||
*/
|
||||
getGesture(e: Event): TouchGesture|null {
|
||||
const isStart = e.type === 'mousedown' || e.type === 'touchstart' ||
|
||||
e.type === 'pointerdown';
|
||||
getGesture(e: PointerEvent): Gesture|null {
|
||||
const isStart = e.type === 'pointerdown';
|
||||
|
||||
const gesture = this.currentGesture_;
|
||||
if (gesture) {
|
||||
@@ -2495,7 +2491,7 @@ export class WorkspaceSvg extends Workspace implements IASTNodeLocationSvg {
|
||||
// No gesture existed on this workspace, but this looks like the start of a
|
||||
// new gesture.
|
||||
if (isStart) {
|
||||
this.currentGesture_ = new TouchGesture(e, this);
|
||||
this.currentGesture_ = new Gesture(e, this);
|
||||
return this.currentGesture_;
|
||||
}
|
||||
// No gesture existed and this event couldn't be the start of a new gesture.
|
||||
|
||||
@@ -276,7 +276,7 @@ export class ZoomControls implements IPositionable {
|
||||
|
||||
// Attach listener.
|
||||
this.onZoomOutWrapper = browserEvents.conditionalBind(
|
||||
this.zoomOutGroup, 'mousedown', null, this.zoom.bind(this, -1));
|
||||
this.zoomOutGroup, 'pointerdown', null, this.zoom.bind(this, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +322,7 @@ export class ZoomControls implements IPositionable {
|
||||
|
||||
// Attach listener.
|
||||
this.onZoomInWrapper = browserEvents.conditionalBind(
|
||||
this.zoomInGroup, 'mousedown', null, this.zoom.bind(this, 1));
|
||||
this.zoomInGroup, 'pointerdown', null, this.zoom.bind(this, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,7 +333,7 @@ export class ZoomControls implements IPositionable {
|
||||
* positive amount values zoom in.
|
||||
* @param e A mouse down event.
|
||||
*/
|
||||
private zoom(amount: number, e: Event) {
|
||||
private zoom(amount: number, e: PointerEvent) {
|
||||
this.workspace.markFocused();
|
||||
this.workspace.zoomCenter(amount);
|
||||
this.fireZoomEvent();
|
||||
@@ -380,7 +380,7 @@ export class ZoomControls implements IPositionable {
|
||||
|
||||
// Attach event listeners.
|
||||
this.onZoomResetWrapper = browserEvents.conditionalBind(
|
||||
this.zoomResetGroup, 'mousedown', null, this.resetZoom.bind(this));
|
||||
this.zoomResetGroup, 'pointerdown', null, this.resetZoom.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,7 +388,7 @@ export class ZoomControls implements IPositionable {
|
||||
*
|
||||
* @param e A mouse down event.
|
||||
*/
|
||||
private resetZoom(e: Event) {
|
||||
private resetZoom(e: PointerEvent) {
|
||||
this.workspace.markFocused();
|
||||
|
||||
// zoom is passed amount and computes the new scale using the formula:
|
||||
|
||||
@@ -1424,5 +1424,9 @@
|
||||
|
||||
'develop': [
|
||||
// New renamings go here!
|
||||
{
|
||||
oldName: 'Blockly.TouchGesture',
|
||||
newName: 'Blockly.Gesture',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ suite('Toolbox', function() {
|
||||
test('Toolbox clicked -> Should close flyout', function() {
|
||||
const hideChaffStub = sinon.stub(
|
||||
Blockly.WorkspaceSvg.prototype, "hideChaff");
|
||||
const evt = new MouseEvent('click', {});
|
||||
const evt = new PointerEvent('pointerdown', {});
|
||||
this.toolbox.HtmlDiv.dispatchEvent(evt);
|
||||
sinon.assert.calledOnce(hideChaffStub);
|
||||
});
|
||||
|
||||
@@ -55,13 +55,9 @@ suite('Tooltip', function() {
|
||||
this.block.setTooltip('Test Tooltip');
|
||||
|
||||
// Fire pointer events directly on the relevant SVG.
|
||||
// Note the 'pointerover', due to the events registered through
|
||||
// Blockly.browserEvents.bind being registered as pointer events rather
|
||||
// than mouse events. Mousemove event is registered directly on the
|
||||
// element rather than through browserEvents.
|
||||
this.block.pathObject.svgPath.dispatchEvent(
|
||||
new MouseEvent('pointerover'));
|
||||
this.block.pathObject.svgPath.dispatchEvent(new MouseEvent('mousemove'));
|
||||
new PointerEvent('pointerover'));
|
||||
this.block.pathObject.svgPath.dispatchEvent(new PointerEvent('pointermove'));
|
||||
this.clock.runAll();
|
||||
|
||||
chai.assert.isTrue(
|
||||
|
||||
@@ -19,29 +19,29 @@
|
||||
});
|
||||
|
||||
suite('shouldHandleTouch', function() {
|
||||
test('handles mousedown event', function() {
|
||||
const mouseEvent = new MouseEvent('mousedown');
|
||||
chai.assert.isTrue(Blockly.Touch.shouldHandleEvent(mouseEvent));
|
||||
test('handles pointerdown event', function() {
|
||||
const pointerEvent = new PointerEvent('pointerdown');
|
||||
chai.assert.isTrue(Blockly.Touch.shouldHandleEvent(pointerEvent));
|
||||
});
|
||||
|
||||
test('handles multiple mousedown events', function() {
|
||||
const mouseEvent1 = new MouseEvent('mousedown');
|
||||
const mouseEvent2 = new MouseEvent('mousedown');
|
||||
Blockly.Touch.shouldHandleEvent(mouseEvent1);
|
||||
chai.assert.isTrue(Blockly.Touch.shouldHandleEvent(mouseEvent2));
|
||||
test('handles multiple pointerdown events', function() {
|
||||
const pointerEvent1 = new PointerEvent('pointerdown');
|
||||
const pointerEvent2 = new PointerEvent('pointerdown');
|
||||
Blockly.Touch.shouldHandleEvent(pointerEvent1);
|
||||
chai.assert.isTrue(Blockly.Touch.shouldHandleEvent(pointerEvent2));
|
||||
});
|
||||
|
||||
test('does not handle mouseup if not tracking touch', function() {
|
||||
const mouseEvent = new MouseEvent('mouseup');
|
||||
chai.assert.isFalse(Blockly.Touch.shouldHandleEvent(mouseEvent));
|
||||
test('does not handle pointerup if not tracking touch', function() {
|
||||
const pointerEvent = new PointerEvent('pointerup');
|
||||
chai.assert.isFalse(Blockly.Touch.shouldHandleEvent(pointerEvent));
|
||||
});
|
||||
|
||||
test('handles mouseup if already tracking a touch', function() {
|
||||
const mousedown = new MouseEvent('mousedown');
|
||||
const mouseup = new MouseEvent('mouseup');
|
||||
// Register the mousedown event first
|
||||
Blockly.Touch.shouldHandleEvent(mousedown);
|
||||
chai.assert.isTrue(Blockly.Touch.shouldHandleEvent(mouseup));
|
||||
test('handles pointerup if already tracking a touch', function() {
|
||||
const pointerdown = new PointerEvent('pointerdown');
|
||||
const pointerup = new PointerEvent('pointerup');
|
||||
// Register the pointerdown event first
|
||||
Blockly.Touch.shouldHandleEvent(pointerdown);
|
||||
chai.assert.isTrue(Blockly.Touch.shouldHandleEvent(pointerup));
|
||||
});
|
||||
|
||||
test('handles pointerdown if this is a new touch', function() {
|
||||
@@ -56,13 +56,6 @@
|
||||
chai.assert.isFalse(Blockly.Touch.shouldHandleEvent(pointerdown2));
|
||||
});
|
||||
|
||||
test('does not handle pointerdown after a mousedown', function() {
|
||||
const mouseEvent = new MouseEvent('mousedown');
|
||||
const pointerdown = new PointerEvent('pointerdown', {pointerId: 1, pointerType: 'touch'});
|
||||
Blockly.Touch.shouldHandleEvent(mouseEvent);
|
||||
chai.assert.isFalse(Blockly.Touch.shouldHandleEvent(pointerdown));
|
||||
});
|
||||
|
||||
test('does not handle pointerup if not tracking touch', function() {
|
||||
const pointerup = new PointerEvent('pointerup', {pointerId: 1, pointerType: 'touch'});
|
||||
chai.assert.isFalse(Blockly.Touch.shouldHandleEvent(pointerup));
|
||||
@@ -77,11 +70,6 @@
|
||||
});
|
||||
|
||||
suite('getTouchIdentifierFromEvent', function() {
|
||||
test('is mouse for MouseEvents', function() {
|
||||
const mousedown = new MouseEvent('mousedown');
|
||||
chai.assert.equal(Blockly.Touch.getTouchIdentifierFromEvent(mousedown), 'mouse');
|
||||
});
|
||||
|
||||
test('is pointerId for mouse PointerEvents', function() {
|
||||
const pointerdown = new PointerEvent('pointerdown', {pointerId: 7, pointerType: 'mouse'});
|
||||
chai.assert.equal(Blockly.Touch.getTouchIdentifierFromEvent(pointerdown), 7);
|
||||
|
||||
Reference in New Issue
Block a user