From 41606a6ad8cf08c80f3fd3ce3118307041eb8f7c Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 23 Nov 2022 22:11:26 +1100
Subject: [PATCH 01/14] add start of experimental new pinch touch support
---
packages/core/src/types.ts | 3 +-
packages/core/src/view/event/InternalEvent.ts | 137 +++++++-----------
2 files changed, 54 insertions(+), 86 deletions(-)
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index f5ff261ffa..d442fb8bc9 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -266,7 +266,7 @@ export interface GraphPlugin {
export type Listener = {
name: string;
- f: MouseEventListener | KeyboardEventListener;
+ f: MouseEventListener | TouchEventListener | KeyboardEventListener;
};
export type ListenerTarget = {
@@ -276,6 +276,7 @@ export type ListenerTarget = {
export type Listenable = (EventTarget | (Window & typeof globalThis)) & ListenerTarget;
export type MouseEventListener = (me: MouseEvent) => void;
+export type TouchEventListener = (me: TouchEvent) => void;
export type KeyboardEventListener = (ke: KeyboardEvent) => void;
export type GestureEvent = Event &
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index 409e684a1e..5423061ade 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -26,6 +26,7 @@ import {
KeyboardEventListener,
Listenable,
MouseEventListener,
+ TouchEventListener,
} from '../../types';
import { Graph } from '../Graph';
@@ -73,7 +74,7 @@ class InternalEvent {
static addListener(
element: Listenable,
eventName: string,
- funct: MouseEventListener | KeyboardEventListener
+ funct: MouseEventListener | TouchEventListener | KeyboardEventListener
) {
element.addEventListener(
eventName,
@@ -95,7 +96,7 @@ class InternalEvent {
static removeListener(
element: Listenable,
eventName: string,
- funct: MouseEventListener | KeyboardEventListener
+ funct: MouseEventListener | TouchEventListener | KeyboardEventListener
) {
element.removeEventListener(eventName, funct as EventListener, false);
@@ -321,9 +322,6 @@ class InternalEvent {
* function has two arguments: the mouse event and a boolean that specifies
* if the wheel was moved up or down.
*
- * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
- * Safari. It does currently not work on Safari for Mac.
- *
* ### Example
*
* @example
@@ -345,99 +343,68 @@ class InternalEvent {
target: Listenable
) {
if (funct != null) {
- const wheelHandler = (evt: WheelEvent) => {
- // To prevent window zoom on trackpad pinch
- if (evt.ctrlKey) {
- evt.preventDefault();
- }
-
- // Handles the event using the given function
- if (Math.abs(evt.deltaX) > 0.5 || Math.abs(evt.deltaY) > 0.5) {
- funct(evt, evt.deltaY == 0 ? -evt.deltaX > 0 : -evt.deltaY > 0);
- }
- };
+ let touches: TouchList | null = null;
+ let startTouches: TouchList | null = null;
target = target != null ? target : window;
- if (Client.IS_SF && !Client.IS_TOUCH) {
- let scale = 1;
+ const getTouchDistance = (touches: TouchList) => {
+ var a = touches[0].clientX - touches[1].clientX;
+ var b = touches[0].clientY - touches[1].clientY;
+ return Math.sqrt(a * a + b * b);
+ }
- InternalEvent.addListener(target, 'gesturestart', (evt: GestureEvent) => {
- InternalEvent.consume(evt);
- scale = 1;
+ // Adds basic mouse listeners for graph event dispatching
+ if (Client.IS_TOUCH) {
+ // If a touch device, use the touch events
+ // TODO: Should this only happen on mobile?
+ // What if a user prefers using their mouse on touch-capable devices?
+ InternalEvent.addListener(target, 'touchstart', (evt: TouchEvent) => {
+ if (evt.touches && evt.touches.length > 1) {
+ InternalEvent.consume(evt);
+ startTouches = evt.touches;
+ }
});
+ InternalEvent.addListener(target, 'touchmove', (evt: TouchEvent) => {
+ if (!startTouches && evt.touches && evt.touches.length > 1) {
+ startTouches = evt.touches;
+ }
+ if (startTouches && evt.touches && evt.touches.length > 1) {
+ InternalEvent.consume(evt);
+ touches = evt.touches;
- InternalEvent.addListener(target, 'gesturechange', ((evt: GestureEvent) => {
- InternalEvent.consume(evt);
-
- if (typeof evt.scale === 'number') {
- const diff = scale - evt.scale;
-
- if (Math.abs(diff) > 0.2) {
+ const diff = getTouchDistance(touches) - getTouchDistance(startTouches);
+ if (Math.abs(diff) > InternalEvent.PINCH_THRESHOLD) {
funct(evt, diff < 0, true);
- scale = evt.scale;
+ startTouches = evt.touches;
}
}
- }) as EventListener);
-
- InternalEvent.addListener(target, 'gestureend', (evt: GestureEvent) => {
+ })
+ InternalEvent.addListener(target, 'touchend', (evt: TouchEvent) => {
InternalEvent.consume(evt);
+ touches = null;
+ startTouches = null;
});
- } else {
- let evtCache: EventCache = [];
- let dx0 = 0;
- let dy0 = 0;
-
- // Adds basic listeners for graph event dispatching
- InternalEvent.addGestureListeners(
- target,
- ((evt: GestureEvent) => {
- if (!isMouseEvent(evt) && evt.pointerId != null) {
- evtCache.push(evt);
- }
- }) as EventListener,
- ((evt: GestureEvent) => {
- if (!isMouseEvent(evt) && evtCache.length == 2) {
- // Find this event in the cache and update its record with this event
- for (let i = 0; i < evtCache.length; i += 1) {
- if (evt.pointerId == evtCache[i].pointerId) {
- evtCache[i] = evt;
- break;
- }
- }
-
- // Calculate the distance between the two pointers
- const dx = Math.abs(evtCache[0].clientX - evtCache[1].clientX);
- const dy = Math.abs(evtCache[0].clientY - evtCache[1].clientY);
- const tx = Math.abs(dx - dx0);
- const ty = Math.abs(dy - dy0);
-
- if (
- tx > InternalEvent.PINCH_THRESHOLD ||
- ty > InternalEvent.PINCH_THRESHOLD
- ) {
- const cx =
- evtCache[0].clientX + (evtCache[1].clientX - evtCache[0].clientX) / 2;
- const cy =
- evtCache[0].clientY + (evtCache[1].clientY - evtCache[0].clientY) / 2;
-
- funct(evtCache[0], tx > ty ? dx > dx0 : dy > dy0, true, cx, cy);
-
- // Cache the distance for the next move event
- dx0 = dx;
- dy0 = dy;
- }
- }
- }) as EventListener,
- (evt) => {
- evtCache = [];
- dx0 = 0;
- dy0 = 0;
- }
- );
}
- InternalEvent.addListener(target, 'wheel', wheelHandler as EventListener);
+ // Fall back to standard mouse wheel if touch events not in progress, or not a touch device
+ InternalEvent.addListener(target, 'wheel', ((evt: WheelEvent) => {
+ if (startTouches) {
+ // If being handled by touch events, ignore
+ evt.preventDefault();
+ return;
+ }
+
+ // To prevent window zoom on trackpad pinch
+ if (evt.ctrlKey) {
+ evt.preventDefault();
+ }
+
+ // Handles the event using the given function
+ if (Math.abs(evt.deltaX) > 0.5 || Math.abs(evt.deltaY) > 0.5) {
+ funct(evt, evt.deltaY == 0 ? -evt.deltaX > 0 : -evt.deltaY > 0);
+ }
+ }) as EventListener);
}
}
From 76e001850b2f354286ca0b2faa2f84f969c77e3e Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Mon, 28 Nov 2022 22:25:40 +1100
Subject: [PATCH 02/14] start conversion of touch story
---
packages/html/stories/Touch.stories.js | 435 +++++++++++++++++++++++++
packages/html/stories/stashed/Touch.js | 379 ---------------------
2 files changed, 435 insertions(+), 379 deletions(-)
create mode 100644 packages/html/stories/Touch.stories.js
delete mode 100644 packages/html/stories/stashed/Touch.js
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
new file mode 100644
index 0000000000..d15caf8c92
--- /dev/null
+++ b/packages/html/stories/Touch.stories.js
@@ -0,0 +1,435 @@
+/**
+ * Copyright (c) 2006-2013, JGraph Ltd
+ *
+ * Touch
+ *
+ * This example demonstrates handling of touch,
+ * mouse and pointer events.
+ */
+
+import { convertPoint } from '@maxgraph/core/src/util/styleUtils';
+import { createImage } from '@maxgraph/core/src/util/domUtils';
+import { getRotatedPoint, toRadians } from '@maxgraph/core/src/util/mathUtils';
+import { getValue } from '@maxgraph/core/src/util/Utils';
+import {
+ ConnectionHandler, EdgeHandler,
+ Graph, Outline,
+ PanningHandler,
+ Point,
+ PopupMenuHandler,
+ SelectionHandler, Translations,
+ VertexHandler,
+ Client,
+ RubberBandHandler,
+ InternalEvent
+} from "@maxgraph/core/src";
+import * as Constants from "@maxgraph/core/src/util/Constants";
+import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler";
+import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
+import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
+import {globalTypes} from "../.storybook/preview";
+
+export default {
+ title: 'DnD_CopyPaste/Touch',
+ argTypes: {
+ ...globalTypes,
+ /*rubberBand: {
+ type: 'boolean',
+ defaultValue: true,
+ },*/
+ },
+};
+
+const HTML_TEMPLATE = `
+
+
+
+
+
+
+
+
+
+`
+
+const Template = ({ label, ...args }) => {
+ // To detect if touch events are actually supported, the following condition is recommended:
+ // Client.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
+
+ // Disables built-in text selection and context menu while not editing text
+ let textEditing = (evt) => {
+ return graph.isEditing();
+ };
+
+ const container = document.createElement('div');
+ container.onselectstart = textEditing;
+ container.onmousedown = textEditing;
+ container.oncontextmenu = textEditing;
+
+ // Rounded edge and vertex handles
+ let touchHandle = new Image('images/handle-main.png', 17, 17);
+ Outline.prototype.sizerImage = touchHandle;
+
+ // Adds connect icon to selected vertex
+ let connectorSrc = 'images/handle-connect.png';
+
+ // Sets constants for touch style
+ // TODO: Find a means of altering these constants (ts conversion)
+ //Constants.HANDLE_SIZE = 16;
+ //Constants.LABEL_HANDLE_SIZE = 7;
+
+ // Context menu trigger implementation depending on current selection state
+ // combined with support for normal popup trigger.
+ let cellSelected = false;
+ let selectionEmpty = false;
+ let menuShowing = false;
+
+ // Larger tolerance and grid for real touch devices
+ let vertexHandlerTolerance,
+ edgeHandlerTolerance,
+ graphTolerance;
+
+ if (Client.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) {
+ //Shape.prototype.svgStrokeTolerance = 18; // TODO: Find if there's a way to replicate the previous behaviour - having this value for all shapes (ts conversion)
+ vertexHandlerTolerance = 12;
+ edgeHandlerTolerance = 12;
+ graphTolerance = 12;
+ } else {
+ vertexHandlerTolerance = 0;
+ edgeHandlerTolerance = 0;
+ graphTolerance = 0;
+ }
+
+ class MyEdgeHandler extends EdgeHandler {
+ tolerance = edgeHandlerTolerance;
+ handleImage = touchHandle
+ }
+
+ class MyPopupMenuHandler extends PopupMenuHandler {
+ autoExpand = true;
+
+ isSelectOnPopup(me) {
+ return InternalEvent.isMouseEvent(me.getEvent());
+ };
+
+ // Installs context menu
+ factoryMethod(menu, cell, evt) {
+ menu.addItem('Item 1', null, function () {
+ alert('Item 1');
+ });
+ menu.addSeparator();
+
+ var submenu1 = menu.addItem('Submenu 1', null, null);
+ menu.addItem('Subitem 1', null, function () {
+ alert('Subitem 1');
+ }, submenu1);
+ menu.addItem('Subitem 1', null, function () {
+ alert('Subitem 2');
+ }, submenu1);
+ };
+
+ // Shows popup menu if cell was selected or selection was empty and background was clicked
+ mouseUp(sender, me) {
+ this.popupTrigger = !graph.isEditing() && (this.popupTrigger || (!menuShowing &&
+ !graph.isEditing() && !InternalEvent.isMouseEvent(me.getEvent()) &&
+ ((selectionEmpty && me.getCell() == null && graph.isSelectionEmpty()) ||
+ (cellSelected && graph.isCellSelected(me.getCell())))));
+ super.apply(this, arguments);
+ };
+ }
+
+ class MyVertexHandler extends VertexHandler {
+ rotationEnabled = true; // Enables rotation handle
+ manageSizers = true; // Enables managing of sizers
+ livePreview = true; // Enables live preview
+ handleImage = touchHandle;
+ tolerance = vertexHandlerTolerance;
+
+ init() {
+ // TODO: Use 4 sizers, move outside of shape
+ //this.singleSizer = this.state.width < 30 && this.state.height < 30;
+ super.apply(this, arguments);
+
+ // Only show connector image on one cell and do not show on containers
+ if (
+ this.graph.getPlugin('ConnectionHandler').isEnabled() &&
+ this.state.cell.isConnectable() &&
+ this.graph.getSelectionCount() === 1
+ ) {
+ this.connectorImg = createImage(connectorSrc);
+ this.connectorImg.style.cursor = 'pointer';
+ this.connectorImg.style.width = '29px';
+ this.connectorImg.style.height = '29px';
+ this.connectorImg.style.position = 'absolute';
+
+ if (!Client.IS_TOUCH) {
+ this.connectorImg.setAttribute('title', Translations.get('connect'));
+ InternalEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
+ }
+
+ // Starts connecting on touch/mouse down
+ InternalEvent.addGestureListeners(this.connectorImg,
+ ((evt) => {
+ this.graph.getPlugin('PopupMenuHandler').hideMenu();
+ this.graph.stopEditing(false);
+
+ let pt = convertPoint(this.graph.container,
+ InternalEvent.getClientX(evt), InternalEvent.getClientY(evt));
+ this.graph.getPlugin('ConnectionHandler').start(this.state, pt.x, pt.y);
+ this.graph.isMouseDown = true;
+ this.graph.isMouseTrigger = InternalEvent.isMouseEvent(evt);
+ InternalEvent.consume(evt);
+ })
+ );
+
+ this.graph.container.appendChild(this.connectorImg);
+ }
+
+ this.redrawHandles();
+ };
+
+ hideSizers() {
+ super.apply(this, arguments);
+
+ if (this.connectorImg != null) {
+ this.connectorImg.style.visibility = 'hidden';
+ }
+ };
+
+ reset() {
+ super.apply(this, arguments);
+
+ if (this.connectorImg != null) {
+ this.connectorImg.style.visibility = '';
+ }
+ };
+
+ redrawHandles() {
+ super.apply(this);
+
+ if (this.state != null && this.connectorImg != null) {
+ let pt = new Point();
+ let s = this.state;
+
+ // Top right for single-sizer
+ if (this.singleSizer) {
+ pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2;
+ pt.y = s.y - this.connectorImg.offsetHeight / 2;
+ } else {
+ pt.x = s.x + s.width + Constants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2;
+ pt.y = s.y + s.height / 2;
+ }
+
+ let alpha = toRadians(getValue(s.style, 'rotation', 0));
+ if (alpha != 0) {
+ let cos = Math.cos(alpha);
+ let sin = Math.sin(alpha);
+
+ let ct = new Point(s.getCenterX(), s.getCenterY());
+ pt = getRotatedPoint(pt, cos, sin, ct);
+ }
+
+ this.connectorImg.style.left = (pt.x - this.connectorImg.offsetWidth / 2) + 'px';
+ this.connectorImg.style.top = (pt.y - this.connectorImg.offsetHeight / 2) + 'px';
+ }
+ };
+
+ destroy(sender, me) {
+ super.apply(this, arguments);
+
+ if (this.connectorImg != null) {
+ this.connectorImg.parentNode.removeChild(this.connectorImg);
+ this.connectorImg = null;
+ }
+ };
+ }
+
+ class MyPanningHandler extends PanningHandler {
+ // One finger pans (no rubberband selection) must start regardless of mouse button
+ isPanningTrigger(me) {
+ let evt = me.getEvent();
+
+ return (me.getState() == null && !InternalEvent.isMouseEvent(evt)) ||
+ (InternalEvent.isPopupTrigger(evt) && (me.getState() == null ||
+ InternalEvent.isControlDown(evt) || InternalEvent.isShiftDown(evt)));
+ };
+ }
+
+ class MySelectionHandler extends SelectionHandler {
+ // Don't clear selection if multiple cells selected
+ mouseDown = function (sender, me) {
+ super.apply(this, arguments);
+
+ if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1) {
+ this.delayedSelection = false;
+ }
+ };
+ }
+
+ class MyConnectionHandler extends ConnectionHandler {
+ createMarker() {
+ class MyMarker extends ConnectionHandlerCellMarker { // TODO: export this currently private class (ts conversion)
+ // Disable new connections via "hotspot"
+ isEnabled() {
+ return this.graph.getPlugin('ConnectionHandler').first != null;
+ };
+ }
+ return new MyMarker(this.graph, this);
+ }
+
+ // On connect the target is selected and we clone the cell of the preview edge for insert
+ selectCells(edge, target) {
+ if (target != null) {
+ this.graph.setSelectionCell(target);
+ } else {
+ this.graph.setSelectionCell(edge);
+ }
+ };
+ }
+
+ class MyCustomGraph extends Graph {
+ tolerance = graphTolerance; // TODO: Check this works with the mixins (ts conversion)
+
+ createVertexHandler(state) {
+ return new MyVertexHandler(state);
+ }
+
+ fireMouseEvent(evtName, me, sender) {
+ if (evtName === InternalEvent.MOUSE_DOWN) {
+ // For hit detection on edges
+ me = this.updateMouseEvent(me);
+
+ cellSelected = this.isCellSelected(me.getCell());
+ selectionEmpty = this.isSelectionEmpty();
+ menuShowing = graph.getPlugin('PopupMenuHandler').isMenuShowing();
+ }
+ this.fireMouseEvent.apply(this, arguments);
+ };
+
+ // Adds custom hit detection if native hit detection found no cell
+ updateMouseEvent(me) {
+ me = super.apply(this, arguments);
+
+ if (me.getState() == null) {
+ let cell = this.getCellAt(me.graphX, me.graphY);
+ if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY)) {
+ cell = null;
+ } else {
+ me.state = this.view.getState(cell);
+
+ if (me.state != null && me.state.shape != null) {
+ this.container.style.cursor = me.state.shape.node.style.cursor;
+ }
+ }
+ }
+
+ if (me.getState() == null) {
+ this.container.style.cursor = 'default';
+ }
+ return me;
+ };
+
+ // Overrides double click handling to use the tolerance
+ dblClick(evt, cell) {
+ if (cell == null) {
+ let pt = convertPoint(this.container,
+ InternalEvent.getClientX(evt), InternalEvent.getClientY(evt));
+ cell = this.getCellAt(pt.x, pt.y);
+ }
+ super.call(this, evt, cell);
+ };
+ }
+
+ // Creates the graph inside the given container
+ let graph = new MyCustomGraph(container, null, [
+ CellEditorHandler,
+ TooltipHandler,
+ SelectionCellsHandler,
+ MyPopupMenuHandler,
+ MyConnectionHandler,
+ MySelectionHandler,
+ MyPanningHandler,
+ ]);
+
+ graph.centerZoom = false;
+ graph.setConnectable(true);
+ graph.setPanning(true);
+
+ // Creates rubberband selection
+ let rubberband = new RubberBandHandler(graph);
+
+ // Tap and hold on background starts rubberband for multiple selected
+ // cells the cell associated with the event is deselected
+ graph.addListener(InternalEvent.TAP_AND_HOLD, function (sender, evt) {
+ if (!InternalEvent.isMultiTouchEvent(evt)) {
+ let me = evt.getProperty('event');
+ let cell = evt.getProperty('cell');
+
+ if (cell == null) {
+ let pt = convertPoint(this.container,
+ InternalEvent.getClientX(me), InternalEvent.getClientY(me));
+ rubberband.start(pt.x, pt.y);
+ } else if (graph.getSelectionCount() > 1 && graph.isCellSelected(cell)) {
+ graph.removeSelectionCell(cell);
+ }
+
+ // Blocks further processing of the event
+ evt.consume();
+ }
+ });
+
+ // Adds mouse wheel handling for zoom
+ InternalEvent.addMouseWheelListener(function (evt, up) {
+ if (up) {
+ graph.zoomIn();
+ } else {
+ graph.zoomOut();
+ }
+ InternalEvent.consume(evt);
+ });
+
+ graph.batchUpdate(() => {
+ // Get the default parent for inserting new cells. This
+ // is normally the first child of the root (ie. layer 0).
+ let parent = graph.getDefaultParent();
+
+ var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
+ var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
+ var e1 = graph.insertEdge(parent, null, '', v1, v2);
+ });
+
+ // Pre-fetches touch handle+connector image
+ new Image().src = touchHandle.src;
+ new Image().src = connectorSrc;
+
+ return container;
+};
+
+export const Default = Template.bind({});
diff --git a/packages/html/stories/stashed/Touch.js b/packages/html/stories/stashed/Touch.js
deleted file mode 100644
index a822e41a4c..0000000000
--- a/packages/html/stories/stashed/Touch.js
+++ /dev/null
@@ -1,379 +0,0 @@
-import { error } from '../../packages/core/src/util/gui/MaxWindow';
-
-/**
- * Copyright (c) 2006-2013, JGraph Ltd
- *
- * Touch
- *
- * This example demonstrates handling of touch,
- * mouse and pointer events.
- */
-
-import React from 'react';
-import mxEvent from '../mxgraph/util/mxEvent';
-import mxGraph from '../mxgraph/view/mxGraph';
-import mxRubberband from '../mxgraph/handler/mxRubberband';
-import { convertPoint, createImage, getRotatedPoint, getValue, toRadians } from '../../packages/core/src/util/utils';
-
-
-const HTML_TEMPLATE = `
-
-
-
-
-
-
-
-
-
-`
-
-
-// To detect if touch events are actually supported, the following condition is recommended:
-// Client.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
-
-// Disables built-in text selection and context menu while not editing text
-let textEditing = (evt) => {
- return graph.isEditing();
-};
-
-container.onselectstart = textEditing;
-container.onmousedown = textEditing;
-container.oncontextmenu = textEditing;
-
-// Creates the graph inside the given container
-let graph = new mxGraph(container);
-graph.centerZoom = false;
-graph.setConnectable(true);
-graph.setPanning(true);
-
-// Creates rubberband selection
- let rubberband = new mxRubberband(graph);
-
-graph.getPlugin('PopupMenuHandler').autoExpand = true;
-
-graph.getPlugin('PopupMenuHandler').isSelectOnPopup = function(me) {
- return mxEvent.isMouseEvent(me.getEvent());
-};
-
- // Installs context menu
-graph.getPlugin('PopupMenuHandler').factoryMethod = function(menu, cell, evt) {
- menu.addItem('Item 1', null, function() {
- alert('Item 1');
- });
- menu.addSeparator();
-
- var submenu1 = menu.addItem('Submenu 1', null, null);
- menu.addItem('Subitem 1', null, function() {
- alert('Subitem 1');
- }, submenu1);
- menu.addItem('Subitem 1', null, function() {
- alert('Subitem 2');
- }, submenu1);
-};
-
-// Context menu trigger implementation depending on current selection state
-// combined with support for normal popup trigger.
-let cellSelected = false;
-let selectionEmpty = false;
-let menuShowing = false;
-
-graph.fireMouseEvent = function(evtName, me, sender) {
- if (evtName == mxEvent.MOUSE_DOWN) {
- // For hit detection on edges
- me = this.updateMouseEvent(me);
-
- cellSelected = this.isCellSelected(me.getCell());
- selectionEmpty = this.isSelectionEmpty();
- menuShowing = graph.getPlugin('PopupMenuHandler').isMenuShowing();
- }
- mxGraph.prototype.fireMouseEvent.apply(this, arguments);
-};
-
-// Shows popup menu if cell was selected or selection was empty and background was clicked
-graph.getPlugin('PopupMenuHandler').mouseUp = function(sender, me) {
- this.popupTrigger = !graph.isEditing() && (this.popupTrigger || (!menuShowing &&
- !graph.isEditing() && !mxEvent.isMouseEvent(me.getEvent()) &&
- ((selectionEmpty && me.getCell() == null && graph.isSelectionEmpty()) ||
- (cellSelected && graph.isCellSelected(me.getCell())))));
- PopupMenuHandler.prototype.mouseUp.apply(this, arguments);
-};
-
-// Tap and hold on background starts rubberband for multiple selected
-// cells the cell associated with the event is deselected
-graph.addListener(mxEvent.TAP_AND_HOLD, function(sender, evt){
- if (!mxEvent.isMultiTouchEvent(evt)) {
- let me = evt.getProperty('event');
- let cell = evt.getProperty('cell');
-
- if (cell == null) {
- let pt = convertPoint(this.container,
- mxEvent.getClientX(me), mxEvent.getClientY(me));
- rubberband.start(pt.x, pt.y);
- } else if (graph.getSelectionCount() > 1 && graph.isCellSelected(cell)) {
- graph.removeSelectionCell(cell);
- }
-
- // Blocks further processing of the event
- evt.consume();
- }
-});
-
-// Adds mouse wheel handling for zoom
-mxEvent.addMouseWheelListener(function(evt, up) {
- if (up) {
- graph.zoomIn();
- } else {
- graph.zoomOut();
- }
- mxEvent.consume(evt);
-});
-
-// Gets the default parent for inserting new cells. This
-// is normally the first child of the root (ie. layer 0).
-let parent = graph.getDefaultParent();
-
-// Adds cells to the model in a single step
-graph.getDataModel().beginUpdate();
-try {
- var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
- var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
- var e1 = graph.insertEdge(parent, null, '', v1, v2);
-} finally {
- // Updates the display
- graph.getDataModel().endUpdate();
-}
-
-// Disables new connections via "hotspot"
-graph.getPlugin('ConnectionHandler').marker.isEnabled = function() {
- return this.graph.getPlugin('ConnectionHandler').first != null;
-};
-
-// Adds custom hit detection if native hit detection found no cell
-graph.updateMouseEvent = function(me) {
- let me = mxGraph.prototype.updateMouseEvent.apply(this, arguments);
-
- if (me.getState() == null) {
- let cell = this.getCellAt(me.graphX, me.graphY);
- if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY)){
- cell = null;
- } else {
- me.state = this.view.getState(cell);
-
- if (me.state != null && me.state.shape != null) {
- this.container.style.cursor = me.state.shape.node.style.cursor;
- }
- }
- }
-
- if (me.getState() == null) {
- this.container.style.cursor = 'default';
- }
- return me;
-};
-
-// Enables rotation handle
-VertexHandler.prototype.rotationEnabled = true;
-
-// Enables managing of sizers
-VertexHandler.prototype.manageSizers = true;
-
-// Enables live preview
-VertexHandler.prototype.livePreview = true;
-
-// Sets constants for touch style
-mxConstants.HANDLE_SIZE = 16;
-mxConstants.LABEL_HANDLE_SIZE = 7;
-
-// Larger tolerance and grid for real touch devices
-if (Client.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) {
- Shape.prototype.svgStrokeTolerance = 18;
- VertexHandler.prototype.tolerance = 12;
- mxEdgeHandler.prototype.tolerance = 12;
- mxGraph.prototype.tolerance = 12;
-}
-
-// One finger pans (no rubberband selection) must start regardless of mouse button
-PanningHandler.prototype.isPanningTrigger = function(me) {
- let evt = me.getEvent();
-
- return (me.getState() == null && !mxEvent.isMouseEvent(evt)) ||
- (mxEvent.isPopupTrigger(evt) && (me.getState() == null ||
- mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)));
-};
-
-// Don't clear selection if multiple cells selected
-let graphHandlerMouseDown = SelectionHandler.prototype.mouseDown;
-SelectionHandler.prototype.mouseDown = function(sender, me) {
- graphHandlerMouseDown.apply(this, arguments);
-
- if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1) {
- this.delayedSelection = false;
- }
-};
-
-// On connect the target is selected and we clone the cell of the preview edge for insert
-ConnectionHandler.prototype.selectCells = function(edge, target) {
- if (target != null) {
- this.graph.setSelectionCell(target);
- } else {
- this.graph.setSelectionCell(edge);
- }
-};
-
-// Overrides double click handling to use the tolerance
-let graphDblClick = mxGraph.prototype.dblClick;
-mxGraph.prototype.dblClick = function(evt, cell) {
- if (cell == null) {
- let pt = convertPoint(this.container,
- mxEvent.getClientX(evt), mxEvent.getClientY(evt));
- cell = this.getCellAt(pt.x, pt.y);
- }
- graphDblClick.call(this, evt, cell);
-};
-
-// Rounded edge and vertex handles
-let touchHandle = new Image('images/handle-main.png', 17, 17);
-VertexHandler.prototype.handleImage = touchHandle;
-mxEdgeHandler.prototype.handleImage = touchHandle;
-Outline.prototype.sizerImage = touchHandle;
-
-// Pre-fetches touch handle
-new Image().src = touchHandle.src;
-
-// Adds connect icon to selected vertex
-let connectorSrc = 'images/handle-connect.png';
-
-let vertexHandlerInit = VertexHandler.prototype.init;
-VertexHandler.prototype.init = function() {
- // TODO: Use 4 sizers, move outside of shape
- //this.singleSizer = this.state.width < 30 && this.state.height < 30;
- vertexHandlerInit.apply(this, arguments);
-
- // Only show connector image on one cell and do not show on containers
- if (
- this.graph.getPlugin('ConnectionHandler').isEnabled() &&
- this.state.cell.isConnectable() &&
- this.graph.getSelectionCount() == 1
- ) {
- this.connectorImg = createImage(connectorSrc);
- this.connectorImg.style.cursor = 'pointer';
- this.connectorImg.style.width = '29px';
- this.connectorImg.style.height = '29px';
- this.connectorImg.style.position = 'absolute';
-
- if (!Client.IS_TOUCH) {
- this.connectorImg.setAttribute('title', Translations.get('connect'));
- mxEvent.redirectMouseEvents(this.connectorImg, this.graph, this.state);
- }
-
- // Starts connecting on touch/mouse down
- mxEvent.addGestureListeners(this.connectorImg,
- ((evt) => {
- this.graph.getPlugin('PopupMenuHandler').hideMenu();
- this.graph.stopEditing(false);
-
- let pt = convertPoint(this.graph.container,
- mxEvent.getClientX(evt), mxEvent.getClientY(evt));
- this.graph.getPlugin('ConnectionHandler').start(this.state, pt.x, pt.y);
- this.graph.isMouseDown = true;
- this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
- mxEvent.consume(evt);
- })
- );
-
- this.graph.container.appendChild(this.connectorImg);
- }
-
- this.redrawHandles();
-};
-
-let vertexHandlerHideSizers = VertexHandler.prototype.hideSizers;
-VertexHandler.prototype.hideSizers = function() {
- vertexHandlerHideSizers.apply(this, arguments);
-
- if (this.connectorImg != null) {
- this.connectorImg.style.visibility = 'hidden';
- }
-};
-
-let vertexHandlerReset = VertexHandler.prototype.reset;
-VertexHandler.prototype.reset = function() {
- vertexHandlerReset.apply(this, arguments);
-
- if (this.connectorImg != null) {
- this.connectorImg.style.visibility = '';
- }
-};
-
-let vertexHandlerRedrawHandles = VertexHandler.prototype.redrawHandles;
-VertexHandler.prototype.redrawHandles = function() {
- vertexHandlerRedrawHandles.apply(this);
-
- if (this.state != null && this.connectorImg != null) {
- let pt = new Point();
- let s = this.state;
-
- // Top right for single-sizer
- if (VertexHandler.prototype.singleSizer) {
- pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2;
- pt.y = s.y - this.connectorImg.offsetHeight / 2;
- } else {
- pt.x = s.x + s.width + mxConstants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2;
- pt.y = s.y + s.height / 2;
- }
-
- let alpha = toRadians(getValue(s.style, 'rotation', 0));
- if (alpha != 0) {
- let cos = Math.cos(alpha);
- let sin = Math.sin(alpha);
-
- let ct = new Point(s.getCenterX(), s.getCenterY());
- pt = getRotatedPoint(pt, cos, sin, ct);
- }
-
- this.connectorImg.style.left = (pt.x - this.connectorImg.offsetWidth / 2) + 'px';
- this.connectorImg.style.top = (pt.y - this.connectorImg.offsetHeight / 2) + 'px';
- }
-};
-
-let vertexHandlerDestroy = VertexHandler.prototype.destroy;
-VertexHandler.prototype.destroy = function(sender, me) {
- vertexHandlerDestroy.apply(this, arguments);
-
- if (this.connectorImg != null) {
- this.connectorImg.parentNode.removeChild(this.connectorImg);
- this.connectorImg = null;
- }
-};
-
-// Pre-fetches touch connector
-new Image().src = connectorSrc;
-
From 8a3eaa839aa1b8f5e82e29095adfeebe397be7e7 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Mon, 28 Nov 2022 22:28:44 +1100
Subject: [PATCH 03/14] move ConnectionHandlerCellMarker to external file from
ConnectionHandler to allow use in touch story example
---
.../src/view/handler/ConnectionHandler.ts | 112 +---------------
.../handler/ConnectionHandlerCellMarker.ts | 120 ++++++++++++++++++
packages/html/stories/Touch.stories.js | 1 +
3 files changed, 122 insertions(+), 111 deletions(-)
create mode 100644 packages/core/src/view/handler/ConnectionHandlerCellMarker.ts
diff --git a/packages/core/src/view/handler/ConnectionHandler.ts b/packages/core/src/view/handler/ConnectionHandler.ts
index 5afc879a25..d2c65371c1 100644
--- a/packages/core/src/view/handler/ConnectionHandler.ts
+++ b/packages/core/src/view/handler/ConnectionHandler.ts
@@ -58,6 +58,7 @@ import { Graph } from '../Graph';
import ConnectionConstraint from '../other/ConnectionConstraint';
import Shape from '../geometry/Shape';
import { CellStyle, ColorValue, GraphPlugin, Listenable } from '../../types';
+import ConnectionHandlerCellMarker from './ConnectionHandlerCellMarker';
type FactoryMethod = (
source: Cell | null,
@@ -1986,115 +1987,4 @@ class ConnectionHandler extends EventSource implements GraphPlugin {
}
}
-class ConnectionHandlerCellMarker extends CellMarker {
- connectionHandler: ConnectionHandler;
-
- hotspotEnabled = true;
-
- constructor(
- graph: Graph,
- connectionHandler: ConnectionHandler,
- validColor: ColorValue = DEFAULT_VALID_COLOR,
- invalidColor: ColorValue = DEFAULT_INVALID_COLOR,
- hotspot: number = DEFAULT_HOTSPOT
- ) {
- super(graph, validColor, invalidColor, hotspot);
- this.connectionHandler = connectionHandler;
- }
-
- // Overrides to return cell at location only if valid (so that
- // there is no highlight for invalid cells)
- getCell(me: InternalMouseEvent) {
- let cell = super.getCell(me);
- this.connectionHandler.error = null;
-
- // Checks for cell at preview point (with grid)
- if (!cell && this.connectionHandler.currentPoint) {
- cell = this.connectionHandler.graph.getCellAt(
- this.connectionHandler.currentPoint.x,
- this.connectionHandler.currentPoint.y
- );
- }
-
- // Uses connectable parent vertex if one exists
- if (cell && !cell.isConnectable() && this.connectionHandler.cell) {
- const parent = this.connectionHandler.cell.getParent();
-
- if (parent && parent.isVertex() && parent.isConnectable()) {
- cell = parent;
- }
- }
-
- if (cell) {
- if (
- (this.connectionHandler.graph.isSwimlane(cell) &&
- this.connectionHandler.currentPoint != null &&
- this.connectionHandler.graph.hitsSwimlaneContent(
- cell,
- this.connectionHandler.currentPoint.x,
- this.connectionHandler.currentPoint.y
- )) ||
- !this.connectionHandler.isConnectableCell(cell)
- ) {
- cell = null;
- }
- }
-
- if (cell) {
- if (this.connectionHandler.isConnecting()) {
- if (this.connectionHandler.previous) {
- this.connectionHandler.error = this.connectionHandler.validateConnection(
- this.connectionHandler.previous.cell,
- cell
- );
-
- if (this.connectionHandler.error && this.connectionHandler.error.length === 0) {
- cell = null;
-
- // Enables create target inside groups
- if (this.connectionHandler.isCreateTarget(me.getEvent())) {
- this.connectionHandler.error = null;
- }
- }
- }
- } else if (!this.connectionHandler.isValidSource(cell, me)) {
- cell = null;
- }
- } else if (
- this.connectionHandler.isConnecting() &&
- !this.connectionHandler.isCreateTarget(me.getEvent()) &&
- !this.connectionHandler.graph.isAllowDanglingEdges()
- ) {
- this.connectionHandler.error = '';
- }
-
- return cell;
- }
-
- // Sets the highlight color according to validateConnection
- isValidState(state: CellState) {
- if (this.connectionHandler.isConnecting()) {
- return !this.connectionHandler.error;
- }
- return super.isValidState(state);
- }
-
- // Overrides to use marker color only in highlight mode or for
- // target selection
- getMarkerColor(evt: Event, state: CellState, isValid: boolean) {
- return !this.connectionHandler.connectImage || this.connectionHandler.isConnecting()
- ? super.getMarkerColor(evt, state, isValid)
- : NONE;
- }
-
- // Overrides to use hotspot only for source selection otherwise
- // intersects always returns true when over a cell
- intersects(state: CellState, evt: InternalMouseEvent) {
- if (this.connectionHandler.connectImage || this.connectionHandler.isConnecting()) {
- return true;
- }
- return super.intersects(state, evt);
- }
-}
-
export default ConnectionHandler;
diff --git a/packages/core/src/view/handler/ConnectionHandlerCellMarker.ts b/packages/core/src/view/handler/ConnectionHandlerCellMarker.ts
new file mode 100644
index 0000000000..8640572c33
--- /dev/null
+++ b/packages/core/src/view/handler/ConnectionHandlerCellMarker.ts
@@ -0,0 +1,120 @@
+import CellMarker from "../cell/CellMarker";
+import {Graph} from "../Graph";
+import {ColorValue} from "../../types";
+import {DEFAULT_HOTSPOT, DEFAULT_INVALID_COLOR, DEFAULT_VALID_COLOR, NONE} from "../../util/Constants";
+import InternalMouseEvent from "../event/InternalMouseEvent";
+import CellState from "../cell/CellState";
+import ConnectionHandler from "./ConnectionHandler";
+
+class ConnectionHandlerCellMarker extends CellMarker {
+ connectionHandler: ConnectionHandler;
+
+ hotspotEnabled = true;
+
+ constructor(
+ graph: Graph,
+ connectionHandler: ConnectionHandler,
+ validColor: ColorValue = DEFAULT_VALID_COLOR,
+ invalidColor: ColorValue = DEFAULT_INVALID_COLOR,
+ hotspot: number = DEFAULT_HOTSPOT
+ ) {
+ super(graph, validColor, invalidColor, hotspot);
+ this.connectionHandler = connectionHandler;
+ }
+
+ // Overrides to return cell at location only if valid (so that
+ // there is no highlight for invalid cells)
+ getCell(me: InternalMouseEvent) {
+ let cell = super.getCell(me);
+ this.connectionHandler.error = null;
+
+ // Checks for cell at preview point (with grid)
+ if (!cell && this.connectionHandler.currentPoint) {
+ cell = this.connectionHandler.graph.getCellAt(
+ this.connectionHandler.currentPoint.x,
+ this.connectionHandler.currentPoint.y
+ );
+ }
+
+ // Uses connectable parent vertex if one exists
+ if (cell && !cell.isConnectable() && this.connectionHandler.cell) {
+ const parent = this.connectionHandler.cell.getParent();
+
+ if (parent && parent.isVertex() && parent.isConnectable()) {
+ cell = parent;
+ }
+ }
+
+ if (cell) {
+ if (
+ (this.connectionHandler.graph.isSwimlane(cell) &&
+ this.connectionHandler.currentPoint != null &&
+ this.connectionHandler.graph.hitsSwimlaneContent(
+ cell,
+ this.connectionHandler.currentPoint.x,
+ this.connectionHandler.currentPoint.y
+ )) ||
+ !this.connectionHandler.isConnectableCell(cell)
+ ) {
+ cell = null;
+ }
+ }
+
+ if (cell) {
+ if (this.connectionHandler.isConnecting()) {
+ if (this.connectionHandler.previous) {
+ this.connectionHandler.error = this.connectionHandler.validateConnection(
+ this.connectionHandler.previous.cell,
+ cell
+ );
+
+ if (this.connectionHandler.error && this.connectionHandler.error.length === 0) {
+ cell = null;
+
+ // Enables create target inside groups
+ if (this.connectionHandler.isCreateTarget(me.getEvent())) {
+ this.connectionHandler.error = null;
+ }
+ }
+ }
+ } else if (!this.connectionHandler.isValidSource(cell, me)) {
+ cell = null;
+ }
+ } else if (
+ this.connectionHandler.isConnecting() &&
+ !this.connectionHandler.isCreateTarget(me.getEvent()) &&
+ !this.connectionHandler.graph.isAllowDanglingEdges()
+ ) {
+ this.connectionHandler.error = '';
+ }
+
+ return cell;
+ }
+
+ // Sets the highlight color according to validateConnection
+ isValidState(state: CellState) {
+ if (this.connectionHandler.isConnecting()) {
+ return !this.connectionHandler.error;
+ }
+ return super.isValidState(state);
+ }
+
+ // Overrides to use marker color only in highlight mode or for
+ // target selection
+ getMarkerColor(evt: Event, state: CellState, isValid: boolean) {
+ return !this.connectionHandler.connectImage || this.connectionHandler.isConnecting()
+ ? super.getMarkerColor(evt, state, isValid)
+ : NONE;
+ }
+
+ // Overrides to use hotspot only for source selection otherwise
+ // intersects always returns true when over a cell
+ intersects(state: CellState, evt: InternalMouseEvent) {
+ if (this.connectionHandler.connectImage || this.connectionHandler.isConnecting()) {
+ return true;
+ }
+ return super.intersects(state, evt);
+ }
+}
+
+export default ConnectionHandlerCellMarker;
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index d15caf8c92..492a27822a 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -28,6 +28,7 @@ import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler
import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
import {globalTypes} from "../.storybook/preview";
+import ConnectionHandlerCellMarker from "@maxgraph/core/src/view/handler/ConnectionHandlerCellMarker";
export default {
title: 'DnD_CopyPaste/Touch',
From fa6b76e3e8bc74b53b47a3c60bdeaf3cf74166cc Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Mon, 28 Nov 2022 22:32:27 +1100
Subject: [PATCH 04/14] minor style updates
---
packages/html/stories/Touch.stories.js | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index 492a27822a..db98a384b2 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -7,10 +7,16 @@
* mouse and pointer events.
*/
-import { convertPoint } from '@maxgraph/core/src/util/styleUtils';
+import { globalTypes } from "../.storybook/preview";
+import { getValue } from '@maxgraph/core/src/util/Utils';
+import * as Constants from "@maxgraph/core/src/util/Constants";
import { createImage } from '@maxgraph/core/src/util/domUtils';
+import { convertPoint } from '@maxgraph/core/src/util/styleUtils';
+import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
import { getRotatedPoint, toRadians } from '@maxgraph/core/src/util/mathUtils';
-import { getValue } from '@maxgraph/core/src/util/Utils';
+import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler";
+import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
+import ConnectionHandlerCellMarker from "@maxgraph/core/src/view/handler/ConnectionHandlerCellMarker";
import {
ConnectionHandler, EdgeHandler,
Graph, Outline,
@@ -19,16 +25,10 @@ import {
PopupMenuHandler,
SelectionHandler, Translations,
VertexHandler,
- Client,
+ Client,
RubberBandHandler,
InternalEvent
} from "@maxgraph/core/src";
-import * as Constants from "@maxgraph/core/src/util/Constants";
-import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler";
-import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
-import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
-import {globalTypes} from "../.storybook/preview";
-import ConnectionHandlerCellMarker from "@maxgraph/core/src/view/handler/ConnectionHandlerCellMarker";
export default {
title: 'DnD_CopyPaste/Touch',
@@ -249,7 +249,7 @@ const Template = ({ label, ...args }) => {
}
let alpha = toRadians(getValue(s.style, 'rotation', 0));
- if (alpha != 0) {
+ if (alpha !== 0) {
let cos = Math.cos(alpha);
let sin = Math.sin(alpha);
@@ -305,7 +305,7 @@ const Template = ({ label, ...args }) => {
return new MyMarker(this.graph, this);
}
- // On connect the target is selected and we clone the cell of the preview edge for insert
+ // On connect the target is selected, and we clone the cell of the preview edge for insert
selectCells(edge, target) {
if (target != null) {
this.graph.setSelectionCell(target);
From dfdc8c459cccec77bc4490faa85db12a4dd19325 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Mon, 28 Nov 2022 22:36:41 +1100
Subject: [PATCH 05/14] update comments+style changes for touch story
---
packages/html/stories/Touch.stories.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index db98a384b2..817adc7e8b 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -129,6 +129,7 @@ const Template = ({ label, ...args }) => {
}
class MyEdgeHandler extends EdgeHandler {
+ // TODO: Integrate this, potentially with the other cases in Graph.ts's createEdgeHandler (ts conversion)
tolerance = edgeHandlerTolerance;
handleImage = touchHandle
}
@@ -296,7 +297,7 @@ const Template = ({ label, ...args }) => {
class MyConnectionHandler extends ConnectionHandler {
createMarker() {
- class MyMarker extends ConnectionHandlerCellMarker { // TODO: export this currently private class (ts conversion)
+ class MyMarker extends ConnectionHandlerCellMarker {
// Disable new connections via "hotspot"
isEnabled() {
return this.graph.getPlugin('ConnectionHandler').first != null;
@@ -316,7 +317,7 @@ const Template = ({ label, ...args }) => {
}
class MyCustomGraph extends Graph {
- tolerance = graphTolerance; // TODO: Check this works with the mixins (ts conversion)
+ tolerance = graphTolerance;
createVertexHandler(state) {
return new MyVertexHandler(state);
From 74f7e85f0c5d439b0f741805906b59b41fff6779 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 20:32:01 +1100
Subject: [PATCH 06/14] touch event story fixes
---
packages/core/src/index.ts | 1 +
packages/core/src/view/event/InternalEvent.ts | 4 +-
packages/html/stories/Touch.stories.js | 86 ++++++++++---------
3 files changed, 48 insertions(+), 43 deletions(-)
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index a083d0d81f..3391270118 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -78,6 +78,7 @@ export { default as CellHighlight } from './view/cell/CellHighlight';
export { default as CellMarker } from './view/cell/CellMarker';
export { default as CellTracker } from './view/cell/CellTracker';
export { default as ConnectionHandler } from './view/handler/ConnectionHandler';
+export { default as ConnectionHandlerCellMarker } from './view/handler/ConnectionHandlerCellMarker';
export { default as ConstraintHandler } from './view/handler/ConstraintHandler';
export { default as EdgeHandler } from './view/handler/EdgeHandler';
export { default as EdgeSegmentHandler } from './view/handler/EdgeSegmentHandler';
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index 5423061ade..5a7bb520e7 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -357,8 +357,6 @@ class InternalEvent {
// Adds basic mouse listeners for graph event dispatching
if (Client.IS_TOUCH) {
// If a touch device, use the touch events
- // TODO: Should this only happen on mobile?
- // What if a user prefers using their mouse on touch-capable devices?
InternalEvent.addListener(target, 'touchstart', (evt: TouchEvent) => {
if (evt.touches && evt.touches.length > 1) {
InternalEvent.consume(evt);
@@ -375,7 +373,7 @@ class InternalEvent {
const diff = getTouchDistance(touches) - getTouchDistance(startTouches);
if (Math.abs(diff) > InternalEvent.PINCH_THRESHOLD) {
- funct(evt, diff < 0, true);
+ funct(evt, diff > 0, true);
startTouches = evt.touches;
}
}
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index 817adc7e8b..7ed74215ae 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -7,17 +7,21 @@
* mouse and pointer events.
*/
-import { globalTypes } from "../.storybook/preview";
-import { getValue } from '@maxgraph/core/src/util/Utils';
-import * as Constants from "@maxgraph/core/src/util/Constants";
-import { createImage } from '@maxgraph/core/src/util/domUtils';
-import { convertPoint } from '@maxgraph/core/src/util/styleUtils';
-import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
-import { getRotatedPoint, toRadians } from '@maxgraph/core/src/util/mathUtils';
-import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler";
-import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
-import ConnectionHandlerCellMarker from "@maxgraph/core/src/view/handler/ConnectionHandlerCellMarker";
+//import { getValue } from '@maxgraph/core/src/util/Utils';
+//import * as constants from "@maxgraph/core/src/util/constants";
+//import { createImage } from '@maxgraph/core/src/util/domUtils';
+//import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
+//import { getRotatedPoint, toRadians } from '@maxgraph/core/src/util/mathUtils';
+//import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler";
+//import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
+//import ConnectionHandlerCellMarker from "@maxgraph/core/src/view/handler/ConnectionHandlerCellMarker";
import {
+ utils,
+ domUtils,
+ styleUtils,
+ mathUtils,
+ eventUtils,
+ constants,
ConnectionHandler, EdgeHandler,
Graph, Outline,
PanningHandler,
@@ -27,17 +31,19 @@ import {
VertexHandler,
Client,
RubberBandHandler,
- InternalEvent
-} from "@maxgraph/core/src";
+ InternalEvent,
+ CellEditorHandler,
+ TooltipHandler,
+ SelectionCellsHandler,
+ ConnectionHandlerCellMarker,
+} from "@maxgraph/core";
+
+import { globalTypes } from "../.storybook/preview";
export default {
title: 'DnD_CopyPaste/Touch',
argTypes: {
...globalTypes,
- /*rubberBand: {
- type: 'boolean',
- defaultValue: true,
- },*/
},
};
@@ -103,8 +109,8 @@ const Template = ({ label, ...args }) => {
// Sets constants for touch style
// TODO: Find a means of altering these constants (ts conversion)
- //Constants.HANDLE_SIZE = 16;
- //Constants.LABEL_HANDLE_SIZE = 7;
+ //constants.HANDLE_SIZE = 16;
+ //constants.LABEL_HANDLE_SIZE = 7;
// Context menu trigger implementation depending on current selection state
// combined with support for normal popup trigger.
@@ -138,7 +144,7 @@ const Template = ({ label, ...args }) => {
autoExpand = true;
isSelectOnPopup(me) {
- return InternalEvent.isMouseEvent(me.getEvent());
+ return eventUtils.isMouseEvent(me.getEvent());
};
// Installs context menu
@@ -160,10 +166,10 @@ const Template = ({ label, ...args }) => {
// Shows popup menu if cell was selected or selection was empty and background was clicked
mouseUp(sender, me) {
this.popupTrigger = !graph.isEditing() && (this.popupTrigger || (!menuShowing &&
- !graph.isEditing() && !InternalEvent.isMouseEvent(me.getEvent()) &&
+ !graph.isEditing() && !eventUtils.isMouseEvent(me.getEvent()) &&
((selectionEmpty && me.getCell() == null && graph.isSelectionEmpty()) ||
(cellSelected && graph.isCellSelected(me.getCell())))));
- super.apply(this, arguments);
+ super.mouseUp.apply(this, arguments);
};
}
@@ -177,7 +183,7 @@ const Template = ({ label, ...args }) => {
init() {
// TODO: Use 4 sizers, move outside of shape
//this.singleSizer = this.state.width < 30 && this.state.height < 30;
- super.apply(this, arguments);
+ super.init.apply(this, arguments);
// Only show connector image on one cell and do not show on containers
if (
@@ -185,7 +191,7 @@ const Template = ({ label, ...args }) => {
this.state.cell.isConnectable() &&
this.graph.getSelectionCount() === 1
) {
- this.connectorImg = createImage(connectorSrc);
+ this.connectorImg = domUtils.createImage(connectorSrc);
this.connectorImg.style.cursor = 'pointer';
this.connectorImg.style.width = '29px';
this.connectorImg.style.height = '29px';
@@ -202,11 +208,11 @@ const Template = ({ label, ...args }) => {
this.graph.getPlugin('PopupMenuHandler').hideMenu();
this.graph.stopEditing(false);
- let pt = convertPoint(this.graph.container,
+ let pt = styleUtils.convertPoint(this.graph.container,
InternalEvent.getClientX(evt), InternalEvent.getClientY(evt));
this.graph.getPlugin('ConnectionHandler').start(this.state, pt.x, pt.y);
this.graph.isMouseDown = true;
- this.graph.isMouseTrigger = InternalEvent.isMouseEvent(evt);
+ this.graph.isMouseTrigger = eventUtils.isMouseEvent(evt);
InternalEvent.consume(evt);
})
);
@@ -218,7 +224,7 @@ const Template = ({ label, ...args }) => {
};
hideSizers() {
- super.apply(this, arguments);
+ super.hideSizers.apply(this, arguments);
if (this.connectorImg != null) {
this.connectorImg.style.visibility = 'hidden';
@@ -226,7 +232,7 @@ const Template = ({ label, ...args }) => {
};
reset() {
- super.apply(this, arguments);
+ super.reset.apply(this, arguments);
if (this.connectorImg != null) {
this.connectorImg.style.visibility = '';
@@ -234,7 +240,7 @@ const Template = ({ label, ...args }) => {
};
redrawHandles() {
- super.apply(this);
+ super.redrawHandles.apply(this);
if (this.state != null && this.connectorImg != null) {
let pt = new Point();
@@ -245,17 +251,17 @@ const Template = ({ label, ...args }) => {
pt.x = s.x + s.width - this.connectorImg.offsetWidth / 2;
pt.y = s.y - this.connectorImg.offsetHeight / 2;
} else {
- pt.x = s.x + s.width + Constants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2;
+ pt.x = s.x + s.width + constants.HANDLE_SIZE / 2 + 4 + this.connectorImg.offsetWidth / 2;
pt.y = s.y + s.height / 2;
}
- let alpha = toRadians(getValue(s.style, 'rotation', 0));
+ let alpha = mathUtils.toRadians(utils.getValue(s.style, 'rotation', 0));
if (alpha !== 0) {
let cos = Math.cos(alpha);
let sin = Math.sin(alpha);
let ct = new Point(s.getCenterX(), s.getCenterY());
- pt = getRotatedPoint(pt, cos, sin, ct);
+ pt = mathUtils.getRotatedPoint(pt, cos, sin, ct);
}
this.connectorImg.style.left = (pt.x - this.connectorImg.offsetWidth / 2) + 'px';
@@ -264,7 +270,7 @@ const Template = ({ label, ...args }) => {
};
destroy(sender, me) {
- super.apply(this, arguments);
+ super.destroy.apply(this, arguments);
if (this.connectorImg != null) {
this.connectorImg.parentNode.removeChild(this.connectorImg);
@@ -278,7 +284,7 @@ const Template = ({ label, ...args }) => {
isPanningTrigger(me) {
let evt = me.getEvent();
- return (me.getState() == null && !InternalEvent.isMouseEvent(evt)) ||
+ return (me.getState() == null && !eventUtils.isMouseEvent(evt)) ||
(InternalEvent.isPopupTrigger(evt) && (me.getState() == null ||
InternalEvent.isControlDown(evt) || InternalEvent.isShiftDown(evt)));
};
@@ -286,8 +292,8 @@ const Template = ({ label, ...args }) => {
class MySelectionHandler extends SelectionHandler {
// Don't clear selection if multiple cells selected
- mouseDown = function (sender, me) {
- super.apply(this, arguments);
+ mouseDown(sender, me) {
+ super.mouseDown.apply(this, arguments);
if (this.graph.isCellSelected(me.getCell()) && this.graph.getSelectionCount() > 1) {
this.delayedSelection = false;
@@ -332,12 +338,12 @@ const Template = ({ label, ...args }) => {
selectionEmpty = this.isSelectionEmpty();
menuShowing = graph.getPlugin('PopupMenuHandler').isMenuShowing();
}
- this.fireMouseEvent.apply(this, arguments);
+ super.fireMouseEvent.apply(this, arguments);
};
// Adds custom hit detection if native hit detection found no cell
updateMouseEvent(me) {
- me = super.apply(this, arguments);
+ me = super.updateMouseEvent.apply(this, arguments);
if (me.getState() == null) {
let cell = this.getCellAt(me.graphX, me.graphY);
@@ -361,11 +367,11 @@ const Template = ({ label, ...args }) => {
// Overrides double click handling to use the tolerance
dblClick(evt, cell) {
if (cell == null) {
- let pt = convertPoint(this.container,
+ let pt = styleUtils.convertPoint(this.container,
InternalEvent.getClientX(evt), InternalEvent.getClientY(evt));
cell = this.getCellAt(pt.x, pt.y);
}
- super.call(this, evt, cell);
+ super.dblClick.call(this, evt, cell);
};
}
@@ -395,7 +401,7 @@ const Template = ({ label, ...args }) => {
let cell = evt.getProperty('cell');
if (cell == null) {
- let pt = convertPoint(this.container,
+ let pt = styleUtils.convertPoint(this.container,
InternalEvent.getClientX(me), InternalEvent.getClientY(me));
rubberband.start(pt.x, pt.y);
} else if (graph.getSelectionCount() > 1 && graph.isCellSelected(cell)) {
From d7f2a7fa343180d04340d0e02476eac67b51c215 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 20:34:16 +1100
Subject: [PATCH 07/14] removed unneeded imports from touch event story
---
packages/html/stories/Touch.stories.js | 8 --------
1 file changed, 8 deletions(-)
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index 7ed74215ae..96157db79e 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -7,14 +7,6 @@
* mouse and pointer events.
*/
-//import { getValue } from '@maxgraph/core/src/util/Utils';
-//import * as constants from "@maxgraph/core/src/util/constants";
-//import { createImage } from '@maxgraph/core/src/util/domUtils';
-//import TooltipHandler from "@maxgraph/core/src/view/handler/TooltipHandler";
-//import { getRotatedPoint, toRadians } from '@maxgraph/core/src/util/mathUtils';
-//import CellEditorHandler from "@maxgraph/core/src/view/handler/CellEditorHandler";
-//import SelectionCellsHandler from "@maxgraph/core/src/view/handler/SelectionCellsHandler";
-//import ConnectionHandlerCellMarker from "@maxgraph/core/src/view/handler/ConnectionHandlerCellMarker";
import {
utils,
domUtils,
From a75156de39043b0654d3e6a1f118a4f189e60ca0 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 20:48:22 +1100
Subject: [PATCH 08/14] add ability to use capture events to InternalEvent's
addListener and made it so mousemove/pointermove events are ignored when
there's multitouch and addMouseWheelListener enabled
---
packages/core/src/view/event/InternalEvent.ts | 22 ++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index 5a7bb520e7..b2b8260fdb 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -74,12 +74,13 @@ class InternalEvent {
static addListener(
element: Listenable,
eventName: string,
- funct: MouseEventListener | TouchEventListener | KeyboardEventListener
+ funct: MouseEventListener | TouchEventListener | KeyboardEventListener,
+ capture: boolean = false,
) {
element.addEventListener(
eventName,
funct as EventListener,
- supportsPassive ? { passive: false } : false
+ supportsPassive ? { passive: false, capture: capture } : capture
);
if (!element.mxListenerList) {
@@ -362,7 +363,7 @@ class InternalEvent {
InternalEvent.consume(evt);
startTouches = evt.touches;
}
- });
+ }, true);
InternalEvent.addListener(target, 'touchmove', (evt: TouchEvent) => {
if (!startTouches && evt.touches && evt.touches.length > 1) {
startTouches = evt.touches;
@@ -377,12 +378,23 @@ class InternalEvent {
startTouches = evt.touches;
}
}
- })
+ }, true)
InternalEvent.addListener(target, 'touchend', (evt: TouchEvent) => {
InternalEvent.consume(evt);
touches = null;
startTouches = null;
- });
+ }, true);
+
+ InternalEvent.addListener(target, 'mousemove', (evt: TouchEvent) => {
+ if (startTouches) {
+ InternalEvent.consume(evt);
+ }
+ }, true);
+ InternalEvent.addListener(target, 'pointermove', (evt: TouchEvent) => {
+ if (startTouches) {
+ InternalEvent.consume(evt);
+ }
+ }, true);
}
// Fall back to standard mouse wheel if touch events not in progress, or not a touch device
From e8f85e17dcac562df863ccd5bd9680bada7f3ad6 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 21:24:40 +1100
Subject: [PATCH 09/14] add styles to touch story container
---
packages/html/stories/Touch.stories.js | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index 96157db79e..4568adfaa0 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -39,7 +39,8 @@ export default {
},
};
-const HTML_TEMPLATE = `
+// TODO: Centralize to global MaxGraph CSS or assign these styles explicitly
+const MENU_STYLES = `
-
-
-
-
-
-
-
-
`
const Template = ({ label, ...args }) => {
@@ -92,6 +84,13 @@ const Template = ({ label, ...args }) => {
container.onmousedown = textEditing;
container.oncontextmenu = textEditing;
+ container.style.position = 'relative';
+ container.style.overflow = 'hidden';
+ container.style.width = `${args.width}px`;
+ container.style.height = `${args.height}px`;
+ container.style.background = 'url(/images/grid.gif)';
+ container.style.cursor = 'default';
+
// Rounded edge and vertex handles
let touchHandle = new Image('images/handle-main.png', 17, 17);
Outline.prototype.sizerImage = touchHandle;
From 1b14f1d87abf37d5dac30694b5f7b002e2465765 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 21:32:46 +1100
Subject: [PATCH 10/14] replace remaining var's with let
---
packages/html/stories/Touch.stories.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index 4568adfaa0..4760696743 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -145,7 +145,7 @@ const Template = ({ label, ...args }) => {
});
menu.addSeparator();
- var submenu1 = menu.addItem('Submenu 1', null, null);
+ let submenu1 = menu.addItem('Submenu 1', null, null);
menu.addItem('Subitem 1', null, function () {
alert('Subitem 1');
}, submenu1);
@@ -419,9 +419,9 @@ const Template = ({ label, ...args }) => {
// is normally the first child of the root (ie. layer 0).
let parent = graph.getDefaultParent();
- var v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
- var v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
- var e1 = graph.insertEdge(parent, null, '', v1, v2);
+ let v1 = graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30);
+ let v2 = graph.insertVertex(parent, null, 'World!', 200, 150, 80, 30);
+ let e1 = graph.insertEdge(parent, null, '', v1, v2);
});
// Pre-fetches touch handle+connector image
From ee48906eedbdba52c35cf13ad85ecf2140539492 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 21:34:46 +1100
Subject: [PATCH 11/14] replace remaining var's with let
---
packages/core/src/view/event/InternalEvent.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index b2b8260fdb..1809aeaaa3 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -350,8 +350,8 @@ class InternalEvent {
target = target != null ? target : window;
const getTouchDistance = (touches: TouchList) => {
- var a = touches[0].clientX - touches[1].clientX;
- var b = touches[0].clientY - touches[1].clientY;
+ let a = touches[0].clientX - touches[1].clientX;
+ let b = touches[0].clientY - touches[1].clientY;
return Math.sqrt(a * a + b * b);
}
From b957841744aaafd70a621d7cb16d1904f9a80867 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Wed, 30 Nov 2022 21:38:44 +1100
Subject: [PATCH 12/14] fix eslint errors
---
packages/core/src/view/event/InternalEvent.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index 1809aeaaa3..9ed52c00a0 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -75,7 +75,7 @@ class InternalEvent {
element: Listenable,
eventName: string,
funct: MouseEventListener | TouchEventListener | KeyboardEventListener,
- capture: boolean = false,
+ capture = false,
) {
element.addEventListener(
eventName,
@@ -350,8 +350,8 @@ class InternalEvent {
target = target != null ? target : window;
const getTouchDistance = (touches: TouchList) => {
- let a = touches[0].clientX - touches[1].clientX;
- let b = touches[0].clientY - touches[1].clientY;
+ const a = touches[0].clientX - touches[1].clientX;
+ const b = touches[0].clientY - touches[1].clientY;
return Math.sqrt(a * a + b * b);
}
From ddec85adf86c0b09e7b6827d66975b46d308505c Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Thu, 1 Dec 2022 11:58:45 +1100
Subject: [PATCH 13/14] remove support for safari-specific gestures api and
fixed touch events for iOS/safari
---
packages/core/src/view/GraphView.ts | 21 +---------
packages/core/src/view/event/InternalEvent.ts | 35 +++++++++++------
.../core/src/view/handler/PanningHandler.ts | 25 ------------
.../core/src/view/handler/PopupMenuHandler.ts | 9 -----
.../src/view/handler/RubberBandHandler.ts | 10 -----
packages/core/src/view/mixins/EventsMixin.ts | 39 -------------------
packages/html/stories/Touch.stories.js | 15 ++++---
7 files changed, 35 insertions(+), 119 deletions(-)
diff --git a/packages/core/src/view/GraphView.ts b/packages/core/src/view/GraphView.ts
index dfa043dc7e..435ec0b60c 100644
--- a/packages/core/src/view/GraphView.ts
+++ b/packages/core/src/view/GraphView.ts
@@ -2071,25 +2071,6 @@ export class GraphView extends EventSource {
const graph = this.graph;
const { container } = graph;
- // Support for touch device gestures (eg. pinch to zoom)
- // Double-tap handling is implemented in mxGraph.fireMouseEvent
- if (Client.IS_TOUCH) {
- InternalEvent.addListener(container, 'gesturestart', ((evt: MouseEvent) => {
- graph.fireGestureEvent(evt);
- InternalEvent.consume(evt);
- }) as EventListener);
-
- InternalEvent.addListener(container, 'gesturechange', ((evt: MouseEvent) => {
- graph.fireGestureEvent(evt);
- InternalEvent.consume(evt);
- }) as EventListener);
-
- InternalEvent.addListener(container, 'gestureend', ((evt: MouseEvent) => {
- graph.fireGestureEvent(evt);
- InternalEvent.consume(evt);
- }) as EventListener);
- }
-
// Fires event only for one pointer per gesture
let pointerId: number | null = null;
@@ -2100,7 +2081,7 @@ export class GraphView extends EventSource {
// Condition to avoid scrollbar events starting a rubberband selection
if (
this.isContainerEvent(evt) &&
- ((!Client.IS_GC && !Client.IS_SF) || !this.isScrollEvent(evt))
+ (!Client.IS_GC && !Client.IS_SF)
) {
graph.fireMouseEvent(InternalEvent.MOUSE_DOWN, new InternalMouseEvent(evt));
// @ts-ignore
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index 9ed52c00a0..919dcac87c 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -341,51 +341,64 @@ class InternalEvent {
*/
static addMouseWheelListener(
funct: (event: Event, up: boolean, force?: boolean, cx?: number, cy?: number) => void,
- target: Listenable
+ target?: Listenable
) {
if (funct != null) {
- let touches: TouchList | null = null;
- let startTouches: TouchList | null = null;
+ type TouchArray = { clientX: number, clientY: number }[];
+ let touches: TouchArray | null = null;
+ let startTouches: TouchArray | null = null;
target = target != null ? target : window;
- const getTouchDistance = (touches: TouchList) => {
+ const getTouchDistance = (touches: TouchArray) => {
const a = touches[0].clientX - touches[1].clientX;
const b = touches[0].clientY - touches[1].clientY;
return Math.sqrt(a * a + b * b);
}
+ const touchesToArray = (touches: TouchList): TouchArray => {
+ // Safari seems to use the same TouchList object unless
+ // the values are copied, so copy+output the values we need
+ const out = [];
+ for (let i=0; i {
if (evt.touches && evt.touches.length > 1) {
InternalEvent.consume(evt);
- startTouches = evt.touches;
+ startTouches = touchesToArray(evt.touches);
}
}, true);
InternalEvent.addListener(target, 'touchmove', (evt: TouchEvent) => {
if (!startTouches && evt.touches && evt.touches.length > 1) {
- startTouches = evt.touches;
+ startTouches = touchesToArray(evt.touches);
}
if (startTouches && evt.touches && evt.touches.length > 1) {
InternalEvent.consume(evt);
- touches = evt.touches;
+ touches = touchesToArray(evt.touches);
const diff = getTouchDistance(touches) - getTouchDistance(startTouches);
if (Math.abs(diff) > InternalEvent.PINCH_THRESHOLD) {
funct(evt, diff > 0, true);
- startTouches = evt.touches;
+ startTouches = touchesToArray(evt.touches);
}
}
}, true)
InternalEvent.addListener(target, 'touchend', (evt: TouchEvent) => {
- InternalEvent.consume(evt);
+ if (startTouches) {
+ InternalEvent.consume(evt);
+ }
touches = null;
startTouches = null;
}, true);
- InternalEvent.addListener(target, 'mousemove', (evt: TouchEvent) => {
+ /*InternalEvent.addListener(target, 'mousemove', (evt: TouchEvent) => {
if (startTouches) {
InternalEvent.consume(evt);
}
@@ -394,7 +407,7 @@ class InternalEvent {
if (startTouches) {
InternalEvent.consume(evt);
}
- }, true);
+ }, true);*/
}
// Fall back to standard mouse wheel if touch events not in progress, or not a touch device
diff --git a/packages/core/src/view/handler/PanningHandler.ts b/packages/core/src/view/handler/PanningHandler.ts
index dd90ff5129..ae7483eae7 100644
--- a/packages/core/src/view/handler/PanningHandler.ts
+++ b/packages/core/src/view/handler/PanningHandler.ts
@@ -85,31 +85,6 @@ class PanningHandler extends EventSource implements GraphPlugin {
this.graph.addListener(InternalEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);
- // Handles pinch gestures
- this.gestureHandler = (sender: EventSource, eo: EventObject) => {
- if (this.isPinchEnabled()) {
- const evt = eo.getProperty('event');
-
- if (!isConsumed(evt) && evt.type === 'gesturestart') {
- this.initialScale = this.graph.view.scale;
-
- // Forces start of panning when pinch gesture starts
- if (!this.active && this.mouseDownEvent) {
- this.start(this.mouseDownEvent);
- this.mouseDownEvent = null;
- }
- } else if (evt.type === 'gestureend' && this.initialScale !== 0) {
- this.initialScale = 0;
- }
-
- if (this.initialScale !== 0) {
- this.zoomGraph(evt);
- }
- }
- };
-
- this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler);
-
this.mouseUpListener = () => {
if (this.active) {
this.reset();
diff --git a/packages/core/src/view/handler/PopupMenuHandler.ts b/packages/core/src/view/handler/PopupMenuHandler.ts
index 4cbc065240..8b72d13ad3 100644
--- a/packages/core/src/view/handler/PopupMenuHandler.ts
+++ b/packages/core/src/view/handler/PopupMenuHandler.ts
@@ -43,18 +43,9 @@ class PopupMenuHandler extends MaxPopupMenu implements GraphPlugin {
this.graph = graph;
this.graph.addMouseListener(this);
- // Does not show menu if any touch gestures take place after the trigger
- this.gestureHandler = (sender: EventSource, eo: EventObject) => {
- this.inTolerance = false;
- };
-
- this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler);
-
this.init();
}
- gestureHandler: (sender: EventSource, eo: EventObject) => void;
-
inTolerance = false;
popupTrigger = false;
diff --git a/packages/core/src/view/handler/RubberBandHandler.ts b/packages/core/src/view/handler/RubberBandHandler.ts
index bf7c5dcafa..2565a0daee 100644
--- a/packages/core/src/view/handler/RubberBandHandler.ts
+++ b/packages/core/src/view/handler/RubberBandHandler.ts
@@ -70,20 +70,10 @@ class RubberBandHandler implements GraphPlugin {
};
this.graph.addListener(InternalEvent.PAN, this.panHandler);
-
- // Does not show menu if any touch gestures take place after the trigger
- this.gestureHandler = (sender: EventSource, eo: EventObject) => {
- if (this.first) {
- this.reset();
- }
- };
-
- this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler);
}
forceRubberbandHandler: Function;
panHandler: Function;
- gestureHandler: Function;
graph: Graph;
first: Point | null = null;
destroyed = false;
diff --git a/packages/core/src/view/mixins/EventsMixin.ts b/packages/core/src/view/mixins/EventsMixin.ts
index 2939203ff8..ea91674872 100644
--- a/packages/core/src/view/mixins/EventsMixin.ts
+++ b/packages/core/src/view/mixins/EventsMixin.ts
@@ -115,7 +115,6 @@ declare module '../Graph' {
me: InternalMouseEvent,
sender: EventSource
) => void;
- fireGestureEvent: (evt: MouseEvent, cell?: Cell | null) => void;
sizeDidChange: () => void;
isCloneEvent: (evt: MouseEvent) => boolean;
isTransparentClickEvent: (evt: MouseEvent) => boolean;
@@ -230,7 +229,6 @@ type PartialEvents = Pick<
| 'getEventState'
| 'fireMouseEvent'
| 'consumeMouseEvent'
- | 'fireGestureEvent'
| 'sizeDidChange'
| 'isCloneEvent'
| 'isTransparentClickEvent'
@@ -1084,43 +1082,6 @@ const EventsMixin: PartialType = {
}
},
- /**
- * Dispatches a {@link InternalEvent.GESTURE} event. The following example will resize the
- * cell under the mouse based on the scale property of the native touch event.
- *
- * ```javascript
- * graph.addListener(mxEvent.GESTURE, function(sender, eo)
- * {
- * var evt = eo.getProperty('event');
- * var state = graph.view.getState(eo.getProperty('cell'));
- *
- * if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2)
- * {
- * var scale = graph.view.scale;
- * var tr = graph.view.translate;
- *
- * var w = state.width * evt.scale;
- * var h = state.height * evt.scale;
- * var x = state.x - (w - state.width) / 2;
- * var y = state.y - (h - state.height) / 2;
- *
- * var bounds = new mxRectangle(graph.snap(x / scale) - tr.x,
- * graph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale));
- * graph.resizeCell(state.cell, bounds);
- * eo.consume();
- * }
- * });
- * ```
- *
- * @param evt Gestureend event that represents the gesture.
- * @param cell Optional {@link Cell} associated with the gesture.
- */
- fireGestureEvent(evt, cell = null) {
- // Resets double tap event handling when gestures take place
- this.lastTouchTime = 0;
- this.fireEvent(new EventObject(InternalEvent.GESTURE, { event: evt, cell }));
- },
-
/**
* Called when the size of the graph has changed. This implementation fires
* a {@link size} event after updating the clipping region of the SVG element in
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index 4760696743..fb5d5a34f7 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -167,7 +167,8 @@ const Template = ({ label, ...args }) => {
class MyVertexHandler extends VertexHandler {
rotationEnabled = true; // Enables rotation handle
manageSizers = true; // Enables managing of sizers
- livePreview = true; // Enables live preview
+ // TODO: It appears live preview is broken on Safari/iOS (iPhone) when resizing nodes!
+ //livePreview = true; // Enables live preview
handleImage = touchHandle;
tolerance = vertexHandlerTolerance;
@@ -276,8 +277,8 @@ const Template = ({ label, ...args }) => {
let evt = me.getEvent();
return (me.getState() == null && !eventUtils.isMouseEvent(evt)) ||
- (InternalEvent.isPopupTrigger(evt) && (me.getState() == null ||
- InternalEvent.isControlDown(evt) || InternalEvent.isShiftDown(evt)));
+ (eventUtils.isPopupTrigger(evt) && (me.getState() == null ||
+ eventUtils.isControlDown(evt) || eventUtils.isShiftDown(evt)));
};
}
@@ -387,13 +388,13 @@ const Template = ({ label, ...args }) => {
// Tap and hold on background starts rubberband for multiple selected
// cells the cell associated with the event is deselected
graph.addListener(InternalEvent.TAP_AND_HOLD, function (sender, evt) {
- if (!InternalEvent.isMultiTouchEvent(evt)) {
+ if (!eventUtils.isMultiTouchEvent(evt)) {
let me = evt.getProperty('event');
let cell = evt.getProperty('cell');
if (cell == null) {
let pt = styleUtils.convertPoint(this.container,
- InternalEvent.getClientX(me), InternalEvent.getClientY(me));
+ eventUtils.getClientX(me), eventUtils.getClientY(me));
rubberband.start(pt.x, pt.y);
} else if (graph.getSelectionCount() > 1 && graph.isCellSelected(cell)) {
graph.removeSelectionCell(cell);
@@ -431,4 +432,8 @@ const Template = ({ label, ...args }) => {
return container;
};
+window.onerror = function(a, b, c, d, e) {
+ alert(a+'\n'+b+'\n'+c+'\n'+d+'\n'+e+'\n'+e.stack)
+}
+
export const Default = Template.bind({});
From c15512792eb5e0fa5d9f35c969c18ff7b00fbbc7 Mon Sep 17 00:00:00 2001
From: Dave Morrissey <20507948+mcyph@users.noreply.github.com>
Date: Sat, 3 Dec 2022 15:25:59 +1100
Subject: [PATCH 14/14] remove references to gestureHandler and eslint fixes
---
packages/core/src/view/event/InternalEvent.ts | 17 ++++++++---------
.../core/src/view/handler/PanningHandler.ts | 4 +---
.../core/src/view/handler/PopupMenuHandler.ts | 1 -
packages/html/stories/Touch.stories.js | 4 ++--
4 files changed, 11 insertions(+), 15 deletions(-)
diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts
index 919dcac87c..7a19b2bf76 100644
--- a/packages/core/src/view/event/InternalEvent.ts
+++ b/packages/core/src/view/event/InternalEvent.ts
@@ -18,11 +18,9 @@ limitations under the License.
import InternalMouseEvent from './InternalMouseEvent';
import Client from '../../Client';
-import { isConsumed, isMouseEvent } from '../../util/EventUtils';
+import { isConsumed } from '../../util/EventUtils';
import CellState from '../cell/CellState';
import {
- EventCache,
- GestureEvent,
KeyboardEventListener,
Listenable,
MouseEventListener,
@@ -34,6 +32,7 @@ import { Graph } from '../Graph';
// see https://github.com/Modernizr/Modernizr/issues/1894
let supportsPassive = false;
+
try {
document.addEventListener(
'test',
@@ -55,7 +54,7 @@ try {
* @class InternalEvent
*
* Cross-browser DOM event support. For internal event handling,
- * {@link mxEventSource} and the graph event dispatch loop in {@link graph} are used.
+ * {@link EventSource} and the graph event dispatch loop in {@link graph} are used.
*
* ### Memory Leaks:
*
@@ -398,7 +397,7 @@ class InternalEvent {
startTouches = null;
}, true);
- /*InternalEvent.addListener(target, 'mousemove', (evt: TouchEvent) => {
+ InternalEvent.addListener(target, 'mousemove', (evt: TouchEvent) => {
if (startTouches) {
InternalEvent.consume(evt);
}
@@ -407,27 +406,27 @@ class InternalEvent {
if (startTouches) {
InternalEvent.consume(evt);
}
- }, true);*/
+ }, true);
}
// Fall back to standard mouse wheel if touch events not in progress, or not a touch device
InternalEvent.addListener(target, 'wheel', ((evt: WheelEvent) => {
if (startTouches) {
// If being handled by touch events, ignore
- evt.preventDefault();
+ InternalEvent.consume(evt);
return;
}
// To prevent window zoom on trackpad pinch
if (evt.ctrlKey) {
- evt.preventDefault();
+ InternalEvent.consume(evt);
}
// Handles the event using the given function
if (Math.abs(evt.deltaX) > 0.5 || Math.abs(evt.deltaY) > 0.5) {
funct(evt, evt.deltaY == 0 ? -evt.deltaX > 0 : -evt.deltaY > 0);
}
- }) as EventListener);
+ }) as EventListener, true);
}
}
diff --git a/packages/core/src/view/handler/PanningHandler.ts b/packages/core/src/view/handler/PanningHandler.ts
index ae7483eae7..8223ae385c 100644
--- a/packages/core/src/view/handler/PanningHandler.ts
+++ b/packages/core/src/view/handler/PanningHandler.ts
@@ -108,7 +108,7 @@ class PanningHandler extends EventSource implements GraphPlugin {
/**
* Specifies if panning should be active for the left mouse button.
- * Setting this to true may conflict with {@link Rubberband}. Default is false.
+ * Setting this to true may conflict with {@link RubberbandHandler}. Default is false.
*/
useLeftButtonForPanning = false;
@@ -184,7 +184,6 @@ class PanningHandler extends EventSource implements GraphPlugin {
active = false;
forcePanningHandler: (sender: EventSource, evt: EventObject) => void;
- gestureHandler: (sender: EventSource, evt: EventObject) => void;
mouseUpListener: MouseEventListener;
@@ -418,7 +417,6 @@ class PanningHandler extends EventSource implements GraphPlugin {
onDestroy() {
this.graph.removeMouseListener(this);
this.graph.removeListener(this.forcePanningHandler);
- this.graph.removeListener(this.gestureHandler);
InternalEvent.removeListener(document, 'mouseup', this.mouseUpListener);
}
}
diff --git a/packages/core/src/view/handler/PopupMenuHandler.ts b/packages/core/src/view/handler/PopupMenuHandler.ts
index 8b72d13ad3..99df389c25 100644
--- a/packages/core/src/view/handler/PopupMenuHandler.ts
+++ b/packages/core/src/view/handler/PopupMenuHandler.ts
@@ -192,7 +192,6 @@ class PopupMenuHandler extends MaxPopupMenu implements GraphPlugin {
*/
onDestroy() {
this.graph.removeMouseListener(this);
- this.graph.removeListener(this.gestureHandler);
// Supercall
super.destroy();
diff --git a/packages/html/stories/Touch.stories.js b/packages/html/stories/Touch.stories.js
index fb5d5a34f7..da87c404f5 100644
--- a/packages/html/stories/Touch.stories.js
+++ b/packages/html/stories/Touch.stories.js
@@ -168,7 +168,7 @@ const Template = ({ label, ...args }) => {
rotationEnabled = true; // Enables rotation handle
manageSizers = true; // Enables managing of sizers
// TODO: It appears live preview is broken on Safari/iOS (iPhone) when resizing nodes!
- //livePreview = true; // Enables live preview
+ livePreview = true; // Enables live preview
handleImage = touchHandle;
tolerance = vertexHandlerTolerance;
@@ -413,7 +413,7 @@ const Template = ({ label, ...args }) => {
graph.zoomOut();
}
InternalEvent.consume(evt);
- });
+ }, container);
graph.batchUpdate(() => {
// Get the default parent for inserting new cells. This