Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export interface GraphPlugin {

export type Listener = {
name: string;
f: MouseEventListener | KeyboardEventListener;
f: MouseEventListener | TouchEventListener | KeyboardEventListener;
};

export type ListenerTarget = {
Expand All @@ -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 &
Expand Down
21 changes: 1 addition & 20 deletions packages/core/src/view/GraphView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down
175 changes: 82 additions & 93 deletions packages/core/src/view/event/InternalEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ 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,
TouchEventListener,
} from '../../types';
import { Graph } from '../Graph';

// Checks if passive event listeners are supported
// see https://github.com/Modernizr/Modernizr/issues/1894
let supportsPassive = false;


try {
document.addEventListener(
'test',
Expand All @@ -54,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:
*
Expand All @@ -73,12 +73,13 @@ class InternalEvent {
static addListener(
element: Listenable,
eventName: string,
funct: MouseEventListener | KeyboardEventListener
funct: MouseEventListener | TouchEventListener | KeyboardEventListener,
capture = false,
) {
element.addEventListener(
eventName,
funct as EventListener,
supportsPassive ? { passive: false } : false
supportsPassive ? { passive: false, capture: capture } : capture
);

if (!element.mxListenerList) {
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand All @@ -342,102 +340,93 @@ class InternalEvent {
*/
static addMouseWheelListener(
funct: (event: Event, up: boolean, force?: boolean, cx?: number, cy?: number) => void,
target: Listenable
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);
}
};
type TouchArray = { clientX: number, clientY: number }[];
let touches: TouchArray | null = null;
let startTouches: TouchArray | null = null;

target = target != null ? target : window;

if (Client.IS_SF && !Client.IS_TOUCH) {
let scale = 1;

InternalEvent.addListener(target, 'gesturestart', (evt: GestureEvent) => {
InternalEvent.consume(evt);
scale = 1;
});

InternalEvent.addListener(target, 'gesturechange', ((evt: GestureEvent) => {
InternalEvent.consume(evt);
const getTouchDistance = (touches: TouchArray) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: move this closure out of the enclosing function as it doesn't depend on any state provided by this function

const a = touches[0].clientX - touches[1].clientX;
const b = touches[0].clientY - touches[1].clientY;
return Math.sqrt(a * a + b * b);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: use Math.hypot instead (perform the same calculation)

}

if (typeof evt.scale === 'number') {
const diff = scale - evt.scale;
const touchesToArray = (touches: TouchList): TouchArray => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: move this closure out of the enclosing function as getTouchDistance.

// 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<touches.length; i++) {
out.push({ clientX: touches[i].clientX, clientY: touches[i].clientY });
}
return out;
}

if (Math.abs(diff) > 0.2) {
funct(evt, diff < 0, true);
scale = evt.scale;
// Adds basic mouse listeners for graph event dispatching
if (Client.IS_TOUCH) {
// If a touch device, use the touch events
InternalEvent.addListener(target, 'touchstart', (evt: TouchEvent) => {
if (evt.touches && evt.touches.length > 1) {
InternalEvent.consume(evt);
startTouches = touchesToArray(evt.touches);
}
}, true);
InternalEvent.addListener(target, 'touchmove', (evt: TouchEvent) => {
if (!startTouches && evt.touches && evt.touches.length > 1) {
startTouches = touchesToArray(evt.touches);
}
if (startTouches && evt.touches && evt.touches.length > 1) {
InternalEvent.consume(evt);
touches = touchesToArray(evt.touches);

const diff = getTouchDistance(touches) - getTouchDistance(startTouches);
if (Math.abs(diff) > InternalEvent.PINCH_THRESHOLD) {
funct(evt, diff > 0, true);
startTouches = touchesToArray(evt.touches);
}
}
}) as EventListener);
}, true)
InternalEvent.addListener(target, 'touchend', (evt: TouchEvent) => {
if (startTouches) {
InternalEvent.consume(evt);
}
touches = null;
startTouches = null;
}, true);

InternalEvent.addListener(target, 'gestureend', (evt: GestureEvent) => {
InternalEvent.consume(evt);
});
} 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, 'mousemove', (evt: TouchEvent) => {
if (startTouches) {
InternalEvent.consume(evt);
}
);
}, true);
InternalEvent.addListener(target, 'pointermove', (evt: TouchEvent) => {
if (startTouches) {
InternalEvent.consume(evt);
}
}, true);
}

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
InternalEvent.consume(evt);
return;
}

// To prevent window zoom on trackpad pinch
if (evt.ctrlKey) {
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, true);
}
}

Expand Down
Loading