- Converting *Handlers into plugins.

- Keep resolving errors.
development
Junsik Shim 2021-08-09 09:34:19 +09:00
parent c89ce4cea4
commit 648e324cc0
37 changed files with 1945 additions and 1973 deletions

View File

@ -1,9 +1,9 @@
import type Cell from './view/cell/datatypes/Cell'; import type Cell from './view/cell/datatypes/Cell';
import CellState from './view/cell/datatypes/CellState'; import type CellState from './view/cell/datatypes/CellState';
import RectangleShape from './view/geometry/shape/node/RectangleShape'; import type InternalMouseEvent from './view/event/InternalMouseEvent';
import type Shape from './view/geometry/shape/Shape'; import type Shape from './view/geometry/shape/Shape';
import type Graph from './view/Graph'; import type { MaxGraph } from './view/Graph';
import ImageBox from './view/image/ImageBox'; import type ImageBox from './view/image/ImageBox';
export type CellMap = { export type CellMap = {
[id: string]: Cell; [id: string]: Cell;
@ -19,10 +19,6 @@ export type UndoableChange = {
export type StyleValue = string | number; export type StyleValue = string | number;
export type StyleProperties = {
[k: string]: StyleValue;
};
export type Properties = { export type Properties = {
[k: string]: any; [k: string]: any;
}; };
@ -30,6 +26,7 @@ export type Properties = {
export type CellStateStyles = { export type CellStateStyles = {
absoluteArcSize: number; absoluteArcSize: number;
align: AlignValue; align: AlignValue;
anchorPointDirection: boolean;
arcSize: number; arcSize: number;
aspect: string; aspect: string;
autosize: boolean; autosize: boolean;
@ -49,8 +46,14 @@ export type CellStateStyles = {
endArrow: ArrowType; endArrow: ArrowType;
endFill: boolean; endFill: boolean;
endSize: number; endSize: number;
entryDx: number;
entryDy: number;
entryPerimeter: boolean;
entryX: number; entryX: number;
entryY: number; entryY: number;
exitDx: number;
exitDy: number;
exitPerimeter: boolean;
exitX: number; exitX: number;
exitY: number; exitY: number;
fillColor: ColorValue; fillColor: ColorValue;
@ -75,11 +78,15 @@ export type CellStateStyles = {
imageHeight: number; imageHeight: number;
imageWidth: number; imageWidth: number;
indicatorColor: ColorValue; indicatorColor: ColorValue;
indicatorDirection: DirectionValue;
indicatorHeight: number; indicatorHeight: number;
indicatorImage: string; indicatorImage: string;
indicatorShape: Shape; indicatorShape: string;
indicatorStrokeColor: ColorValue;
indicatorWidth: number; indicatorWidth: number;
labelBackgroundColor: ColorValue;
labelBorderColor: ColorValue; labelBorderColor: ColorValue;
labelPadding: number;
labelPosition: AlignValue; labelPosition: AlignValue;
loop: Function; loop: Function;
margin: number; margin: number;
@ -206,8 +213,12 @@ export type GradientMap = {
[k: string]: Gradient; [k: string]: Gradient;
}; };
export interface GraphPluginConstructor {
new (graph: MaxGraph): GraphPlugin;
pluginId: string;
}
export interface GraphPlugin { export interface GraphPlugin {
onInit: (graph: Graph) => void;
onDestroy: () => void; onDestroy: () => void;
} }
@ -215,14 +226,16 @@ export interface GraphPlugin {
export type Listener = { export type Listener = {
name: string; name: string;
f: EventListener; f: MouseEventListener;
}; };
export type ListenerTarget = { export type ListenerTarget = {
mxListenerList?: Listener[]; mxListenerList?: Listener[];
}; };
export type Listenable = (Node | Window) & ListenerTarget; export type Listenable = (EventSource | EventTarget) & ListenerTarget;
export type MouseEventListener = (me: MouseEvent) => void;
export type GestureEvent = Event & export type GestureEvent = Event &
MouseEvent & { MouseEvent & {
@ -230,6 +243,12 @@ export type GestureEvent = Event &
pointerId?: number; pointerId?: number;
}; };
export type MouseListenerSet = {
mouseDown: (sender: EventSource, me: InternalMouseEvent) => void;
mouseMove: (sender: EventSource, me: InternalMouseEvent) => void;
mouseUp: (sender: EventSource, me: InternalMouseEvent) => void;
};
export type EventCache = GestureEvent[]; export type EventCache = GestureEvent[];
export interface CellHandle { export interface CellHandle {
@ -237,5 +256,12 @@ export interface CellHandle {
cursor: string; cursor: string;
image: ImageBox | null; image: ImageBox | null;
shape: Shape | null; shape: Shape | null;
active: boolean;
setVisible: (v: boolean) => void; setVisible: (v: boolean) => void;
processEvent: (me: InternalMouseEvent) => void;
positionChanged: () => void;
execute: (me: InternalMouseEvent) => void;
reset: () => void;
redraw: () => void;
destroy: () => void;
} }

View File

@ -1,204 +0,0 @@
/**
* Returns the touch or mouse event that contains the mouse coordinates.
*/
import mxClient from "../mxClient";
// static getMainEvent(e: MouseEvent): MouseEvent;
export const getMainEvent = (e) => {
if (
(e.type == 'touchstart' || e.type == 'touchmove') &&
e.touches != null &&
e.touches[0] != null
) {
e = e.touches[0];
} else if (
e.type == 'touchend' &&
e.changedTouches != null &&
e.changedTouches[0] != null
) {
e = e.changedTouches[0];
}
return e;
};
/**
* Returns true if the meta key is pressed for the given event.
*/
// static getClientX(e: TouchEvent | MouseEvent): number;
export const getClientX = (e) => {
return getMainEvent(e).clientX;
};
/**
* Returns true if the meta key is pressed for the given event.
*/
// static getClientY(e: TouchEvent | MouseEvent): number;
export const getClientY = (e) => {
return getMainEvent(e).clientY;
};
/**
* Function: getSource
*
* Returns the event's target or srcElement depending on the browser.
*/
export const getSource = (evt) => {
return evt.srcElement != null ? evt.srcElement : evt.target;
};
/**
* Returns true if the event has been consumed using {@link consume}.
*/
// static isConsumed(evt: mxEventObject | mxMouseEvent | Event): boolean;
export const isConsumed = (evt) => {
return evt.isConsumed != null && evt.isConsumed;
};
/**
* Returns true if the event was generated using a touch device (not a pen or mouse).
*/
// static isTouchEvent(evt: Event): boolean;
export const isTouchEvent = (evt) => {
return evt.pointerType != null
? evt.pointerType == 'touch' ||
evt.pointerType === evt.MSPOINTER_TYPE_TOUCH
: evt.mozInputSource != null
? evt.mozInputSource == 5
: evt.type.indexOf('touch') == 0;
};
/**
* Returns true if the event was generated using a pen (not a touch device or mouse).
*/
// static isPenEvent(evt: Event): boolean;
export const isPenEvent = (evt) => {
return evt.pointerType != null
? evt.pointerType == 'pen' || evt.pointerType === evt.MSPOINTER_TYPE_PEN
: evt.mozInputSource != null
? evt.mozInputSource == 2
: evt.type.indexOf('pen') == 0;
};
/**
* Returns true if the event was generated using a touch device (not a pen or mouse).
*/
// static isMultiTouchEvent(evt: Event): boolean;
export const isMultiTouchEvent = (evt) => {
return (
evt.type != null &&
evt.type.indexOf('touch') == 0 &&
evt.touches != null &&
evt.touches.length > 1
);
};
/**
* Returns true if the event was generated using a mouse (not a pen or touch device).
*/
// static isMouseEvent(evt: Event): boolean;
export const isMouseEvent = (evt) => {
return evt.pointerType != null
? evt.pointerType == 'mouse' ||
evt.pointerType === evt.MSPOINTER_TYPE_MOUSE
: evt.mozInputSource != null
? evt.mozInputSource == 1
: evt.type.indexOf('mouse') == 0;
};
/**
* Returns true if the left mouse button is pressed for the given event.
* To check if a button is pressed during a mouseMove you should use the
* {@link mxGraph.isMouseDown} property. Note that this returns true in Firefox
* for control+left-click on the Mac.
*/
// static isLeftMouseButton(evt: MouseEvent): boolean;
export const isLeftMouseButton = (evt) => {
// Special case for mousemove and mousedown we check the buttons
// if it exists because which is 0 even if no button is pressed
if (
'buttons' in evt &&
(evt.type == 'mousedown' || evt.type == 'mousemove')
) {
return evt.buttons == 1;
}
if ('which' in evt) {
return evt.which === 1;
}
return evt.button === 1;
};
/**
* Returns true if the middle mouse button is pressed for the given event.
* To check if a button is pressed during a mouseMove you should use the
* {@link mxGraph.isMouseDown} property.
*/
// static isMiddleMouseButton(evt: MouseEvent): boolean;
export const isMiddleMouseButton = (evt) => {
if ('which' in evt) {
return evt.which === 2;
}
return evt.button === 4;
};
/**
* Returns true if the right mouse button was pressed. Note that this
* button might not be available on some systems. For handling a popup
* trigger {@link isPopupTrigger} should be used.
*/
// static isRightMouseButton(evt: MouseEvent): boolean;
export const isRightMouseButton = (evt) => {
if ('which' in evt) {
return evt.which === 3;
}
return evt.button === 2;
};
/**
* Returns true if the event is a popup trigger. This implementation
* returns true if the right button or the left button and control was
* pressed on a Mac.
*/
// static isPopupTrigger(evt: Event): boolean;
export const isPopupTrigger = (evt) => {
return (
isRightMouseButton(evt) ||
(mxClient.IS_MAC &&
isControlDown(evt) &&
!isShiftDown(evt) &&
!isMetaDown(evt) &&
!isAltDown(evt))
);
};
/**
* Returns true if the shift key is pressed for the given event.
*/
// static isShiftDown(evt: MouseEvent): boolean;
export const isShiftDown = (evt) => {
return evt != null ? evt.shiftKey : false;
};
/**
* Returns true if the alt key is pressed for the given event.
*/
// static isAltDown(evt: MouseEvent): boolean;
export const isAltDown = (evt) => {
return evt != null ? evt.altKey : false;
};
/**
* Returns true if the control key is pressed for the given event.
*/
// static isControlDown(evt: MouseEvent): boolean;
export const isControlDown = (evt) => {
return evt != null ? evt.ctrlKey : false;
};
/**
* Returns true if the meta key is pressed for the given event.
*/
// static isMetaDown(evt: MouseEvent): boolean;
export const isMetaDown = (evt) => {
return evt != null ? evt.metaKey : false;
};

View File

@ -0,0 +1,189 @@
/**
* Returns the touch or mouse event that contains the mouse coordinates.
*/
import mxClient from '../mxClient';
export const getMainEvent = (evt: MouseEvent) => {
let t = evt as any;
if ((t.type === 'touchstart' || t.type === 'touchmove') && t.touches && t.touches[0]) {
t = t.touches[0];
} else if (t.type === 'touchend' && t.changedTouches && t.changedTouches[0]) {
t = t.changedTouches[0];
}
return t as MouseEvent;
};
/**
* Returns true if the meta key is pressed for the given event.
*/
export const getClientX = (evt: MouseEvent) => {
return getMainEvent(evt).clientX;
};
/**
* Returns true if the meta key is pressed for the given event.
*/
// static getClientY(e: TouchEvent | MouseEvent): number;
export const getClientY = (evt: MouseEvent) => {
return getMainEvent(evt).clientY;
};
/**
* Function: getSource
*
* Returns the event's target or srcElement depending on the browser.
*/
export const getSource = (evt: MouseEvent) => {
return evt.srcElement !== undefined ? evt.srcElement : evt.target;
};
/**
* Returns true if the event has been consumed using {@link consume}.
*/
export const isConsumed = (evt: MouseEvent) => {
const t = evt as any;
return t.isConsumed !== undefined && t.isConsumed;
};
/**
* Returns true if the event was generated using a touch device (not a pen or mouse).
*/
export const isTouchEvent = (evt: MouseEvent) => {
const t = evt as any;
return t.pointerType
? t.pointerType === 'touch' || t.pointerType === t.MSPOINTER_TYPE_TOUCH
: t.mozInputSource !== undefined
? t.mozInputSource === 5
: t.type.indexOf('touch') === 0;
};
/**
* Returns true if the event was generated using a pen (not a touch device or mouse).
*/
export const isPenEvent = (evt: MouseEvent) => {
const t = evt as any;
return t.pointerType
? t.pointerType == 'pen' || t.pointerType === t.MSPOINTER_TYPE_PEN
: t.mozInputSource !== undefined
? t.mozInputSource === 2
: t.type.indexOf('pen') === 0;
};
/**
* Returns true if the event was generated using a touch device (not a pen or mouse).
*/
export const isMultiTouchEvent = (evt: MouseEvent) => {
const t = evt as any;
return (
t.type &&
t.type.indexOf('touch') == 0 &&
t.touches !== undefined &&
t.touches.length > 1
);
};
/**
* Returns true if the event was generated using a mouse (not a pen or touch device).
*/
export const isMouseEvent = (evt: Event) => {
const t = evt as any;
return t.pointerType
? t.pointerType == 'mouse' || t.pointerType === t.MSPOINTER_TYPE_MOUSE
: t.mozInputSource !== undefined
? t.mozInputSource === 1
: t.type.indexOf('mouse') === 0;
};
/**
* Returns true if the left mouse button is pressed for the given event.
* To check if a button is pressed during a mouseMove you should use the
* {@link mxGraph.isMouseDown} property. Note that this returns true in Firefox
* for control+left-click on the Mac.
*/
// static isLeftMouseButton(evt: MouseEvent): boolean;
export const isLeftMouseButton = (evt: MouseEvent) => {
// Special case for mousemove and mousedown we check the buttons
// if it exists because which is 0 even if no button is pressed
if ('buttons' in evt && (evt.type === 'mousedown' || evt.type === 'mousemove')) {
return evt.buttons === 1;
}
if ('which' in evt) {
return evt.which === 1;
}
return evt.button === 1;
};
/**
* Returns true if the middle mouse button is pressed for the given event.
* To check if a button is pressed during a mouseMove you should use the
* {@link mxGraph.isMouseDown} property.
*/
export const isMiddleMouseButton = (evt: MouseEvent) => {
if ('which' in evt) {
return evt.which === 2;
}
return evt.button === 4;
};
/**
* Returns true if the right mouse button was pressed. Note that this
* button might not be available on some systems. For handling a popup
* trigger {@link isPopupTrigger} should be used.
*/
export const isRightMouseButton = (evt: MouseEvent) => {
if ('which' in evt) {
return evt.which === 3;
}
return evt.button === 2;
};
/**
* Returns true if the event is a popup trigger. This implementation
* returns true if the right button or the left button and control was
* pressed on a Mac.
*/
export const isPopupTrigger = (evt: MouseEvent) => {
return (
isRightMouseButton(evt) ||
(mxClient.IS_MAC &&
isControlDown(evt) &&
!isShiftDown(evt) &&
!isMetaDown(evt) &&
!isAltDown(evt))
);
};
/**
* Returns true if the shift key is pressed for the given event.
*/
export const isShiftDown = (evt: MouseEvent) => {
return evt.shiftKey;
};
/**
* Returns true if the alt key is pressed for the given event.
*/
export const isAltDown = (evt: MouseEvent) => {
return evt.altKey;
};
/**
* Returns true if the control key is pressed for the given event.
*/
export const isControlDown = (evt: MouseEvent) => {
return evt.ctrlKey;
};
/**
* Returns true if the meta key is pressed for the given event.
*/
export const isMetaDown = (evt: MouseEvent) => {
return evt.metaKey;
};

View File

@ -41,7 +41,7 @@ import Cell from '../view/cell/datatypes/Cell';
import Model from '../view/model/Model'; import Model from '../view/model/Model';
import graph from '../view/Graph'; import graph from '../view/Graph';
import type { CellStateStyles, Properties, StyleProperties, StyleValue } from '../types'; import type { CellStateStyles, Properties, StyleValue } from '../types';
import CellArray from '../view/cell/datatypes/CellArray'; import CellArray from '../view/cell/datatypes/CellArray';
/** /**
@ -159,7 +159,11 @@ export const parseCssNumber = (value: string) => {
* mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%'); * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');
* (end) * (end)
*/ */
export const setPrefixedStyle = (style: StyleProperties, name: string, value: string) => { export const setPrefixedStyle = (
style: CSSStyleDeclaration,
name: string,
value: string
) => {
let prefix = null; let prefix = null;
if (mxClient.IS_SF || mxClient.IS_GC) { if (mxClient.IS_SF || mxClient.IS_GC) {
@ -189,7 +193,7 @@ export const setPrefixedStyle = (style: StyleProperties, name: string, value: st
export const hasScrollbars = (node: HTMLElement) => { export const hasScrollbars = (node: HTMLElement) => {
const style = getCurrentStyle(node); const style = getCurrentStyle(node);
return style && (style.overflow === 'scroll' || style.overflow === 'auto'); return !!style && (style.overflow === 'scroll' || style.overflow === 'auto');
}; };
/** /**
@ -402,14 +406,16 @@ export const getColor = (array: any, key: string, defaultValue: any) => {
* a - Array of <mxPoints> to be compared. * a - Array of <mxPoints> to be compared.
* b - Array of <mxPoints> to be compared. * b - Array of <mxPoints> to be compared.
*/ */
export const equalPoints = (a: Point[] | null, b: Point[] | null) => { export const equalPoints = (a: (Point | null)[] | null, b: (Point | null)[] | null) => {
if ((!a && b) || (a && !b) || (a && b && a.length != b.length)) { if ((!a && b) || (a && !b) || (a && b && a.length != b.length)) {
return false; return false;
} }
if (a && b) { if (a && b) {
for (let i = 0; i < a.length; i += 1) { for (let i = 0; i < a.length; i += 1) {
if (!a[i] || !a[i].equals(b[i])) return false; const p = a[i];
if (!p || (p && !p.equals(b[i]))) return false;
} }
} }

View File

@ -24,16 +24,14 @@ import Point from './geometry/Point';
import { import {
applyMixins, applyMixins,
autoImplement, autoImplement,
getBoundingBox,
getCurrentStyle, getCurrentStyle,
getValue,
hasScrollbars, hasScrollbars,
parseCssNumber, parseCssNumber,
} from '../util/Utils'; } from '../util/Utils';
import Cell from './cell/datatypes/Cell'; import Cell from './cell/datatypes/Cell';
import Model from './model/Model'; import Model from './model/Model';
import Stylesheet from './style/Stylesheet'; import Stylesheet from './style/Stylesheet';
import { DIALECT_SVG, PAGE_FORMAT_A4_PORTRAIT } from '../util/Constants'; import { PAGE_FORMAT_A4_PORTRAIT } from '../util/Constants';
import ChildChange from './model/ChildChange'; import ChildChange from './model/ChildChange';
import GeometryChange from './geometry/GeometryChange'; import GeometryChange from './geometry/GeometryChange';
@ -49,23 +47,27 @@ import EdgeHandler from './cell/edge/EdgeHandler';
import VertexHandler from './cell/vertex/VertexHandler'; import VertexHandler from './cell/vertex/VertexHandler';
import EdgeSegmentHandler from './cell/edge/EdgeSegmentHandler'; import EdgeSegmentHandler from './cell/edge/EdgeSegmentHandler';
import ElbowEdgeHandler from './cell/edge/ElbowEdgeHandler'; import ElbowEdgeHandler from './cell/edge/ElbowEdgeHandler';
import GraphEvents from './event/GraphEvents';
import GraphImage from './image/GraphImage';
import GraphCells from './cell/GraphCells';
import GraphSelection from './selection/GraphSelection';
import GraphConnections from './connection/GraphConnections';
import GraphEdge from './cell/edge/GraphEdge';
import GraphVertex from './cell/vertex/GraphVertex';
import GraphOverlays from './layout/GraphOverlays';
import GraphEditing from './editing/GraphEditing';
import GraphFolding from './folding/GraphFolding';
import GraphLabel from './label/GraphLabel';
import GraphValidation from './validation/GraphValidation';
import GraphSnap from './snap/GraphSnap';
import type { GraphPlugin } from '../types'; import type { GraphPlugin, GraphPluginConstructor } from '../types';
import GraphTooltip from './tooltip/GraphTooltip'; import type GraphPorts from './ports/GraphPorts';
import GraphTerminal from './terminal/GraphTerminal'; import type Dictionary from '../util/Dictionary';
import type GraphPanning from './panning/GraphPanning';
import type GraphZoom from './zoom/GraphZoom';
import type GraphEvents from './event/GraphEvents';
import type GraphImage from './image/GraphImage';
import type GraphCells from './cell/GraphCells';
import type GraphSelection from './selection/GraphSelection';
import type GraphConnections from './connection/GraphConnections';
import type GraphEdge from './cell/edge/GraphEdge';
import type GraphVertex from './cell/vertex/GraphVertex';
import type GraphOverlays from './layout/GraphOverlays';
import type GraphEditing from './editing/GraphEditing';
import type GraphFolding from './folding/GraphFolding';
import type GraphLabel from './label/GraphLabel';
import type GraphValidation from './validation/GraphValidation';
import type GraphSnap from './snap/GraphSnap';
import type GraphTooltip from './tooltip/GraphTooltip';
import type GraphTerminal from './terminal/GraphTerminal';
type PartialEvents = Pick< type PartialEvents = Pick<
GraphEvents, GraphEvents,
@ -81,7 +83,9 @@ type PartialEvents = Pick<
| 'isIgnoreTerminalEvent' | 'isIgnoreTerminalEvent'
| 'isCloneEvent' | 'isCloneEvent'
| 'isToggleEvent' | 'isToggleEvent'
| 'getClickTolerance' | 'getEventTolerance'
| 'isInvokesStopCellEditing'
| 'getPointForEvent'
>; >;
type PartialSelection = Pick< type PartialSelection = Pick<
GraphSelection, GraphSelection,
@ -90,6 +94,13 @@ type PartialSelection = Pick<
| 'getSelectionCount' | 'getSelectionCount'
| 'selectCellForEvent' | 'selectCellForEvent'
| 'setSelectionCell' | 'setSelectionCell'
| 'getSelectionCells'
| 'updateSelection'
| 'selectRegion'
| 'cellAdded'
| 'cellRemoved'
| 'getUpdatingSelectionResource'
| 'getDoneResource'
>; >;
type PartialCells = Pick< type PartialCells = Pick<
GraphCells, GraphCells,
@ -100,6 +111,14 @@ type PartialCells = Pick<
| 'isCellsCloneable' | 'isCellsCloneable'
| 'cloneCell' | 'cloneCell'
| 'setCellStyles' | 'setCellStyles'
| 'isCellMovable'
| 'isCellResizable'
| 'getChildCells'
| 'isCellRotatable'
| 'getCellContainmentArea'
| 'getCurrentCellStyle'
| 'resizeCell'
| 'removeStateForCell'
>; >;
type PartialConnections = Pick< type PartialConnections = Pick<
GraphConnections, GraphConnections,
@ -108,17 +127,44 @@ type PartialConnections = Pick<
| 'isCellDisconnectable' | 'isCellDisconnectable'
| 'getOutlineConstraint' | 'getOutlineConstraint'
| 'connectCell' | 'connectCell'
| 'getConnections'
| 'isConstrainChild'
| 'isValidSource'
>; >;
type PartialEditing = Pick<GraphEditing, 'isEditing'>; type PartialEditing = Pick<GraphEditing, 'isEditing' | 'stopEditing'>;
type PartialTooltip = Pick<GraphTooltip, 'getTooltip'>; type PartialTooltip = Pick<GraphTooltip, 'getTooltip'>;
type PartialValidation = Pick< type PartialValidation = Pick<
GraphValidation, GraphValidation,
'getEdgeValidationError' | 'validationAlert' 'getEdgeValidationError' | 'validationAlert'
>; >;
type PartialLabel = Pick<GraphLabel, 'isLabelMovable'>; type PartialLabel = Pick<
GraphLabel,
'isLabelMovable' | 'isHtmlLabel' | 'isWrapping' | 'isLabelClipped' | 'getLabel'
>;
type PartialTerminal = Pick<GraphTerminal, 'isTerminalPointMovable'>; type PartialTerminal = Pick<GraphTerminal, 'isTerminalPointMovable'>;
type PartialSnap = Pick<GraphSnap, 'snap' | 'getGridSize'>; type PartialSnap = Pick<
type PartialEdge = Pick<GraphEdge, 'isAllowDanglingEdges' | 'isResetEdgesOnConnect'>; GraphSnap,
'snap' | 'getGridSize' | 'isGridEnabled' | 'getSnapTolerance'
>;
type PartialEdge = Pick<
GraphEdge,
'isAllowDanglingEdges' | 'isResetEdgesOnConnect' | 'getEdges' | 'insertEdge' | 'addEdge'
>;
type PartialOverlays = Pick<GraphOverlays, 'getCellOverlays'>;
type PartialFolding = Pick<
GraphFolding,
'getFoldingImage' | 'isFoldingEnabled' | 'foldCells'
>;
type PartialPanning = Pick<
GraphPanning,
| 'panGraph'
| 'isUseScrollbarsForPanning'
| 'getPanDx'
| 'setPanDx'
| 'getPanDy'
| 'setPanDy'
>;
type PartialZoom = Pick<GraphZoom, 'zoomTo'>;
type PartialClass = PartialEvents & type PartialClass = PartialEvents &
PartialSelection & PartialSelection &
PartialCells & PartialCells &
@ -130,10 +176,22 @@ type PartialClass = PartialEvents &
PartialTerminal & PartialTerminal &
PartialSnap & PartialSnap &
PartialEdge & PartialEdge &
PartialOverlays &
PartialFolding &
PartialPanning &
PartialZoom &
EventSource; EventSource;
export type MaxGraph = Graph & PartialClass; export type MaxGraph = Graph & PartialClass;
const defaultPlugins: GraphPluginConstructor[] = [
TooltipHandler,
SelectionCellsHandler,
PopupMenuHandler,
ConnectionHandler,
GraphHandler,
];
/** /**
* Extends {@link EventSource} to implement a graph component for * Extends {@link EventSource} to implement a graph component for
* the browser. This is the main class of the package. To activate * the browser. This is the main class of the package. To activate
@ -151,12 +209,12 @@ export type MaxGraph = Graph & PartialClass;
* @class graph * @class graph
* @extends {EventSource} * @extends {EventSource}
*/ */
// @ts-ignore // @ts-ignore recursive reference error
class Graph extends autoImplement<PartialClass>() { class Graph extends autoImplement<PartialClass>() {
constructor( constructor(
container: HTMLElement, container: HTMLElement,
model: Model, model: Model,
plugins: GraphPlugin[] = [], plugins: GraphPluginConstructor[] = defaultPlugins,
stylesheet: Stylesheet | null = null stylesheet: Stylesheet | null = null
) { ) {
super(); super();
@ -165,7 +223,6 @@ class Graph extends autoImplement<PartialClass>() {
this.model = model; this.model = model;
this.plugins = plugins; this.plugins = plugins;
this.cellRenderer = this.createCellRenderer(); this.cellRenderer = this.createCellRenderer();
this.setSelectionModel(this.createSelectionModel());
this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet()); this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet());
this.view = this.createGraphView(); this.view = this.createGraphView();
@ -176,21 +233,6 @@ class Graph extends autoImplement<PartialClass>() {
this.getModel().addListener(InternalEvent.CHANGE, this.graphModelChangeListener); this.getModel().addListener(InternalEvent.CHANGE, this.graphModelChangeListener);
// Installs basic event handlers with disabled default settings.
this.createHandlers();
// Initializes the display if a container was specified
this.init();
this.view.revalidate();
}
/**
* Initializes the {@link container} and creates the respective datastructures.
*
* @param container DOM node that will contain the graph display.
*/
init() {
// Initializes the in-place editor // Initializes the in-place editor
this.cellEditor = this.createCellEditor(); this.cellEditor = this.createCellEditor();
@ -200,22 +242,14 @@ class Graph extends autoImplement<PartialClass>() {
// Updates the size of the container for the current graph // Updates the size of the container for the current graph
this.sizeDidChange(); this.sizeDidChange();
// Hides tooltips and resets tooltip timer if mouse leaves container // Initiailzes plugins
InternalEvent.addListener(this.container, 'mouseleave', (evt: Event) => { this.plugins.forEach((p: GraphPluginConstructor) => {
if ( this.pluginsMap.put(p.pluginId, new p(this));
this.tooltipHandler.div &&
this.tooltipHandler.div !== (<MouseEvent>evt).relatedTarget
) {
this.tooltipHandler.hide();
}
}); });
// Initiailzes plugins this.view.revalidate();
this.plugins.forEach((p) => p.onInit(this));
} }
// TODO: Document me!
container: HTMLElement; container: HTMLElement;
getContainer = () => this.container; getContainer = () => this.container;
@ -224,21 +258,23 @@ class Graph extends autoImplement<PartialClass>() {
// Handlers // Handlers
// @ts-ignore Cannot be null. // @ts-ignore Cannot be null.
tooltipHandler: TooltipHandler; // tooltipHandler: TooltipHandler;
// @ts-ignore Cannot be null. // @ts-ignore Cannot be null.
selectionCellsHandler: SelectionCellsHandler; // selectionCellsHandler: SelectionCellsHandler;
// @ts-ignore Cannot be null. // @ts-ignore Cannot be null.
popupMenuHandler: PopupMenuHandler; // popupMenuHandler: PopupMenuHandler;
// @ts-ignore Cannot be null. // @ts-ignore Cannot be null.
connectionHandler: ConnectionHandler; // connectionHandler: ConnectionHandler;
// @ts-ignore Cannot be null. // @ts-ignore Cannot be null.
graphHandler: GraphHandler; // graphHandler: GraphHandler;
getTooltipHandler = () => this.tooltipHandler; getPlugin = (id: string) => this.pluginsMap.get(id) as unknown;
getSelectionCellsHandler = () => this.selectionCellsHandler;
getPopupMenuHandler = () => this.popupMenuHandler; // getTooltipHandler = () => this.pluginsMap.get('TooltipHandler');
getConnectionHandler = () => this.connectionHandler; // getSelectionCellsHandler = () => this.selectionCellsHandler;
getGraphHandler = () => this.graphHandler; // getPopupMenuHandler = () => this.popupMenuHandler;
// getConnectionHandler = () => this.connectionHandler;
// getGraphHandler = () => this.graphHandler;
graphModelChangeListener: Function | null = null; graphModelChangeListener: Function | null = null;
paintBackground: Function | null = null; paintBackground: Function | null = null;
@ -252,7 +288,8 @@ class Graph extends autoImplement<PartialClass>() {
*/ */
model: Model; model: Model;
plugins: GraphPlugin[]; plugins: GraphPluginConstructor[];
pluginsMap: Dictionary<string, GraphPlugin> = new Dictionary();
/** /**
* Holds the {@link GraphView} that caches the {@link CellState}s for the cells. * Holds the {@link GraphView} that caches the {@link CellState}s for the cells.
@ -275,11 +312,6 @@ class Graph extends autoImplement<PartialClass>() {
// @ts-ignore // @ts-ignore
stylesheet: Stylesheet; stylesheet: Stylesheet;
/**
* Holds the {@link mxGraphSelectionModel} that models the current selection.
*/
selectionModel: mxGraphSelectionModel | null = null;
/** /**
* Holds the {@link CellEditor} that is used as the in-place editing. * Holds the {@link CellEditor} that is used as the in-place editing.
*/ */
@ -591,62 +623,6 @@ class Graph extends autoImplement<PartialClass>() {
} }
} }
/**
* Creates the tooltip-, panning-, connection- and graph-handler (in this
* order). This is called in the constructor before {@link init} is called.
*/
createHandlers(): void {
this.tooltipHandler = this.createTooltipHandler();
this.tooltipHandler.setEnabled(false);
this.selectionCellsHandler = this.createSelectionCellsHandler();
this.connectionHandler = this.createConnectionHandler();
this.connectionHandler.setEnabled(false);
this.graphHandler = this.createGraphHandler();
this.popupMenuHandler = this.createPopupMenuHandler();
}
/**
* Creates and returns a new {@link TooltipHandler} to be used in this graph.
*/
createTooltipHandler() {
return new TooltipHandler(this);
}
/**
* Creates and returns a new {@link TooltipHandler} to be used in this graph.
*/
createSelectionCellsHandler(): SelectionCellsHandler {
return new SelectionCellsHandler(this);
}
/**
* Creates and returns a new {@link ConnectionHandler} to be used in this graph.
*/
createConnectionHandler(): ConnectionHandler {
return new ConnectionHandler(this);
}
/**
* Creates and returns a new {@link GraphHandler} to be used in this graph.
*/
createGraphHandler(): GraphHandler {
return new GraphHandler(this);
}
/**
* Creates and returns a new {@link PopupMenuHandler} to be used in this graph.
*/
createPopupMenuHandler(): PopupMenuHandler {
return new PopupMenuHandler(this);
}
/**
* Creates a new {@link mxGraphSelectionModel} to be used in this graph.
*/
createSelectionModel(): mxGraphSelectionModel {
return new mxGraphSelectionModel(this);
}
/** /**
* Creates a new {@link mxGraphSelectionModel} to be used in this graph. * Creates a new {@link mxGraphSelectionModel} to be used in this graph.
*/ */
@ -732,7 +708,7 @@ class Graph extends autoImplement<PartialClass>() {
if (change instanceof RootChange) { if (change instanceof RootChange) {
this.clearSelection(); this.clearSelection();
this.setDefaultParent(null); this.setDefaultParent(null);
this.cells.removeStateForCell(change.previous); this.removeStateForCell(change.previous);
if (this.resetViewOnRootChange) { if (this.resetViewOnRootChange) {
this.view.scale = 1; this.view.scale = 1;
@ -752,7 +728,7 @@ class Graph extends autoImplement<PartialClass>() {
if (!this.getModel().contains(newParent) || newParent.isCollapsed()) { if (!this.getModel().contains(newParent) || newParent.isCollapsed()) {
this.view.invalidate(change.child, true, true); this.view.invalidate(change.child, true, true);
this.cells.removeStateForCell(change.child); this.removeStateForCell(change.child);
// Handles special case of current root of view being removed // Handles special case of current root of view being removed
if (this.view.currentRoot == change.child) { if (this.view.currentRoot == change.child) {
@ -803,7 +779,7 @@ class Graph extends autoImplement<PartialClass>() {
// Removes the state from the cache by default // Removes the state from the cache by default
else if (change.cell != null && change.cell instanceof Cell) { else if (change.cell != null && change.cell instanceof Cell) {
this.cells.removeStateForCell(change.cell); this.removeStateForCell(change.cell);
} }
} }
@ -1109,7 +1085,7 @@ class Graph extends autoImplement<PartialClass>() {
* *
* @param state {@link mxCellState} whose handler should be created. * @param state {@link mxCellState} whose handler should be created.
*/ */
createHandler(state: CellState): EdgeHandler | VertexHandler | null { createHandler(state: CellState) {
let result: EdgeHandler | VertexHandler | null = null; let result: EdgeHandler | VertexHandler | null = null;
if (state.cell.isEdge()) { if (state.cell.isEdge()) {

View File

@ -31,6 +31,7 @@ import CellHighlight from './selection/CellHighlight';
import Rectangle from './geometry/Rectangle'; import Rectangle from './geometry/Rectangle';
import { getClientX, getClientY, isAltDown, isMultiTouchEvent } from '../util/EventUtils'; import { getClientX, getClientY, isAltDown, isMultiTouchEvent } from '../util/EventUtils';
import { MaxGraph } from './Graph'; import { MaxGraph } from './Graph';
import { GraphPlugin, GraphPluginConstructor } from '../types';
/** /**
* Class: mxGraphHandler * Class: mxGraphHandler
@ -52,7 +53,9 @@ import { MaxGraph } from './Graph';
* *
* graph - Reference to the enclosing <mxGraph>. * graph - Reference to the enclosing <mxGraph>.
*/ */
class GraphHandler { class GraphHandler implements GraphPlugin {
static pluginId = 'GraphHandler';
constructor(graph: MaxGraph) { constructor(graph: MaxGraph) {
this.graph = graph; this.graph = graph;
this.graph.addMouseListener(this); this.graph.addMouseListener(this);
@ -1798,7 +1801,7 @@ class GraphHandler {
* Destroys the handler and all its resources and DOM nodes. * Destroys the handler and all its resources and DOM nodes.
*/ */
// destroy(): void; // destroy(): void;
destroy() { onDestroy() {
this.graph.removeMouseListener(this); this.graph.removeMouseListener(this);
this.graph.removeListener(this.panHandler); this.graph.removeListener(this.panHandler);

View File

@ -11,6 +11,7 @@ import {
DEFAULT_VALID_COLOR, DEFAULT_VALID_COLOR,
MAX_HOTSPOT_SIZE, MAX_HOTSPOT_SIZE,
MIN_HOTSPOT_SIZE, MIN_HOTSPOT_SIZE,
NONE,
} from '../../util/Constants'; } from '../../util/Constants';
import CellHighlight from '../selection/CellHighlight'; import CellHighlight from '../selection/CellHighlight';
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
@ -273,11 +274,7 @@ class CellMarker extends EventSource {
* *
* Sets and marks the current valid state. * Sets and marks the current valid state.
*/ */
setCurrentState( setCurrentState(state: CellState | null, me: InternalMouseEvent, color?: ColorValue) {
state: CellState | null,
me: InternalMouseEvent,
color: ColorValue | null = null
) {
const isValid = state ? this.isValidState(state) : false; const isValid = state ? this.isValidState(state) : false;
color = color ?? this.getMarkerColor(me.getEvent(), state, isValid); color = color ?? this.getMarkerColor(me.getEvent(), state, isValid);

View File

@ -71,6 +71,8 @@ import Model from '../model/Model';
import CellOverlay from './CellOverlay'; import CellOverlay from './CellOverlay';
import { getClientX, getClientY, getSource } from '../../util/EventUtils'; import { getClientX, getClientY, getSource } from '../../util/EventUtils';
import { isNode } from '../../util/DomUtils'; import { isNode } from '../../util/DomUtils';
import { CellStateStyles } from '../../types';
import CellArray from './datatypes/CellArray';
/** /**
* Class: mxCellRenderer * Class: mxCellRenderer
@ -119,6 +121,7 @@ class CellRenderer {
* *
* Defines the default shape for edges. Default is <mxConnector>. * Defines the default shape for edges. Default is <mxConnector>.
*/ */
// @ts-expect-error The constructors for Shape and Connector are different.
defaultEdgeShape: typeof Shape = Connector; defaultEdgeShape: typeof Shape = Connector;
/** /**
@ -264,12 +267,11 @@ class CellRenderer {
let ctor = this.getShape(state.style.shape); let ctor = this.getShape(state.style.shape);
if (!ctor) { if (!ctor) {
ctor = <typeof Shape>( // @ts-expect-error The various Shape constructors are not compatible.
(state.cell.isEdge() ? this.defaultEdgeShape : this.defaultVertexShape) ctor = state.cell.isEdge() ? this.defaultEdgeShape : this.defaultVertexShape;
);
} }
return ctor; return ctor as typeof Shape;
} }
/** /**
@ -286,12 +288,12 @@ class CellRenderer {
if (shape) { if (shape) {
shape.apply(state); shape.apply(state);
shape.imageSrc = state.getImage(); shape.imageSrc = state.getImageSrc();
shape.indicatorColor = state.getIndicatorColor(); shape.indicatorColor = state.getIndicatorColor();
shape.indicatorStrokeColor = state.style.indicatorStrokeColor; shape.indicatorStrokeColor = state.style.indicatorStrokeColor;
shape.indicatorGradientColor = state.getIndicatorGradientColor(); shape.indicatorGradientColor = state.getIndicatorGradientColor();
shape.indicatorDirection = state.style.indicatorDirection; shape.indicatorDirection = state.style.indicatorDirection;
shape.indicatorImage = state.getIndicatorImage(); shape.indicatorImageSrc = state.getIndicatorImageSrc();
this.postConfigureShape(state); this.postConfigureShape(state);
} }
} }
@ -304,8 +306,8 @@ class CellRenderer {
* This implementation resolves these keywords on the fill, stroke * This implementation resolves these keywords on the fill, stroke
* and gradient color keys. * and gradient color keys.
*/ */
postConfigureShape(state: CellState): void { postConfigureShape(state: CellState) {
if (state.shape != null) { if (state.shape) {
this.resolveColor(state, 'indicatorGradientColor', 'gradientColor'); this.resolveColor(state, 'indicatorGradientColor', 'gradientColor');
this.resolveColor(state, 'indicatorColor', 'fillColor'); this.resolveColor(state, 'indicatorColor', 'fillColor');
this.resolveColor(state, 'gradient', 'gradientColor'); this.resolveColor(state, 'gradient', 'gradientColor');
@ -320,14 +322,19 @@ class CellRenderer {
* Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
* the respective color on the shape. * the respective color on the shape.
*/ */
checkPlaceholderStyles(state: CellState): boolean { checkPlaceholderStyles(state: CellState) {
// LATER: Check if the color has actually changed // LATER: Check if the color has actually changed
if (state.style != null) { if (state.style) {
const values = ['inherit', 'swimlane', 'indicated']; const values = ['inherit', 'swimlane', 'indicated'];
const styles = ['fillColor', 'strokeColor', 'gradientColor', 'fontColor']; const styles: (keyof CellStateStyles)[] = [
'fillColor',
'strokeColor',
'gradientColor',
'fontColor',
];
for (let i = 0; i < styles.length; i += 1) { for (let i = 0; i < styles.length; i += 1) {
if (values.indexOf(state.style[styles[i]]) >= 0) { if (values.indexOf(state.style[styles[i]] as string) >= 0) {
return true; return true;
} }
} }
@ -341,26 +348,25 @@ class CellRenderer {
* Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
* the respective color on the shape. * the respective color on the shape.
*/ */
resolveColor(state: CellState, field: string, key: string): void { resolveColor(state: CellState, field: string, key: string) {
const shape = key === 'fontColor' ? state.text : state.shape; const shape: Shape | null = key === 'fontColor' ? state.text : state.shape;
if (shape != null) { if (shape) {
const { graph } = state.view; const { graph } = state.view;
// @ts-ignore // @ts-ignore
const value = shape[field]; const value = shape[field];
let referenced = null; let referenced = null;
if (value === 'inherit') { if (value === 'inherit') {
// @ts-ignore
referenced = state.cell.getParent(); referenced = state.cell.getParent();
/* disable swimlane for now
} else if (value === 'swimlane') { } else if (value === 'swimlane') {
// @ts-ignore // @ts-ignore
shape[field] = shape[field] =
key === 'strokeColor' || key === 'fontColor' ? '#000000' : '#ffffff'; key === 'strokeColor' || key === 'fontColor' ? '#000000' : '#ffffff';
// @ts-ignore if (state.cell.getTerminal(false)) {
if (state.cell.getTerminal(false) != null) {
// @ts-ignore
referenced = state.cell.getTerminal(false); referenced = state.cell.getTerminal(false);
} else { } else {
referenced = state.cell; referenced = state.cell;
@ -368,30 +374,27 @@ class CellRenderer {
referenced = graph.swimlane.getSwimlane(<Cell>referenced); referenced = graph.swimlane.getSwimlane(<Cell>referenced);
key = graph.swimlane.swimlaneIndicatorColorAttribute; key = graph.swimlane.swimlaneIndicatorColorAttribute;
} else if (value === 'indicated' && state.shape != null) { */
} else if (value === 'indicated' && state.shape) {
// @ts-ignore // @ts-ignore
shape[field] = state.shape.indicatorColor; shape[field] = state.shape.indicatorColor;
} else if (key !== 'fillColor' && value === 'fillColor' && state.shape != null) { } else if (key !== 'fillColor' && value === 'fillColor' && state.shape) {
// @ts-ignore // @ts-ignore
shape[field] = state.style.fillColor; shape[field] = state.style.fillColor;
} else if ( } else if (key !== 'strokeColor' && value === 'strokeColor' && state.shape) {
key !== 'strokeColor' &&
value === 'strokeColor' &&
state.shape != null
) {
// @ts-ignore // @ts-ignore
shape[field] = state.style.strokeColor; shape[field] = state.style.strokeColor;
} }
if (referenced != null) { if (referenced) {
const rstate = graph.getView().getState(referenced); const rstate = graph.getView().getState(referenced);
// @ts-ignore // @ts-ignore
shape[field] = null; shape[field] = null;
if (rstate != null) { if (rstate) {
const rshape = key === 'fontColor' ? rstate.text : rstate.shape; const rshape = key === 'fontColor' ? rstate.text : rstate.shape;
if (rshape != null && field !== 'indicatorColor') { if (rshape && field !== 'indicatorColor') {
// @ts-ignore // @ts-ignore
shape[field] = rshape[field]; shape[field] = rshape[field];
} else { } else {
@ -412,7 +415,6 @@ class CellRenderer {
* *
* state - <mxCellState> for which the label should be created. * state - <mxCellState> for which the label should be created.
*/ */
// getLabelValue(state: mxCellState): string;
getLabelValue(state: CellState) { getLabelValue(state: CellState) {
return state.view.graph.getLabel(state.cell); return state.view.graph.getLabel(state.cell);
} }
@ -426,15 +428,12 @@ class CellRenderer {
* *
* state - <mxCellState> for which the label should be created. * state - <mxCellState> for which the label should be created.
*/ */
// createLabel(state: mxCellState, value: string): void; createLabel(state: CellState, value: string) {
createLabel(state: CellState, value: any) {
const { graph } = state.view; const { graph } = state.view;
const isEdge = state.cell.isEdge();
if (state.style.fontSize > 0 || state.style.fontSize == null) { if (state.style.fontSize > 0 || state.style.fontSize == null) {
// Avoids using DOM node for empty labels // Avoids using DOM node for empty labels
const isForceHtml = const isForceHtml = graph.isHtmlLabel(state.cell) || isNode(value);
graph.isHtmlLabel(state.cell) || (value != null && isNode(value));
state.text = new this.defaultTextShape( state.text = new this.defaultTextShape(
value, value,
@ -459,8 +458,7 @@ class CellRenderer {
state.style.labelPadding, state.style.labelPadding,
state.style.textDirection || DEFAULT_TEXT_DIRECTION state.style.textDirection || DEFAULT_TEXT_DIRECTION
); );
state.text.opacity = state.text.opacity = state.style.textOpacity;
state.style.textOpacity == null ? 100 : state.style.textOpacity;
state.text.dialect = isForceHtml ? DIALECT_STRICTHTML : state.view.graph.dialect; state.text.dialect = isForceHtml ? DIALECT_STRICTHTML : state.view.graph.dialect;
state.text.style = state.style; state.text.style = state.style;
state.text.state = state; state.text.state = state;
@ -474,7 +472,7 @@ class CellRenderer {
let forceGetCell = false; let forceGetCell = false;
const getState = (evt: Event | InternalMouseEvent) => { const getState = (evt: Event | InternalMouseEvent) => {
let result = state; let result: CellState | null = state;
if (mxClient.IS_TOUCH || forceGetCell) { if (mxClient.IS_TOUCH || forceGetCell) {
const x = getClientX(evt); const x = getClientX(evt);
@ -483,7 +481,7 @@ class CellRenderer {
// Dispatches the drop event to the graph which // Dispatches the drop event to the graph which
// consumes and executes the source function // consumes and executes the source function
const pt = convertPoint(graph.container, x, y); const pt = convertPoint(graph.container, x, y);
result = <CellState>graph.view.getState(graph.getCellAt(pt.x, pt.y)); result = graph.view.getState(graph.getCellAt(pt.x, pt.y) as Cell);
} }
return result; return result;
}; };
@ -491,29 +489,29 @@ class CellRenderer {
// TODO: Add handling for special touch device gestures // TODO: Add handling for special touch device gestures
InternalEvent.addGestureListeners( InternalEvent.addGestureListeners(
state.text.node, state.text.node,
(evt: MouseEvent) => { (evt: Event) => {
if (this.isLabelEvent(state, evt)) { if (this.isLabelEvent(state, evt as MouseEvent)) {
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN, InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, state) new InternalMouseEvent(evt as MouseEvent, state)
); );
forceGetCell = forceGetCell =
graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG'; graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG';
} }
}, },
(evt: MouseEvent) => { (evt: Event) => {
if (this.isLabelEvent(state, evt)) { if (this.isLabelEvent(state, evt as MouseEvent)) {
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, getState(evt)) new InternalMouseEvent(evt as MouseEvent, getState(evt))
); );
} }
}, },
(evt: MouseEvent) => { (evt: Event) => {
if (this.isLabelEvent(state, evt)) { if (this.isLabelEvent(state, evt as MouseEvent)) {
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_UP, InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt, getState(evt)) new InternalMouseEvent(evt as MouseEvent, getState(evt))
); );
forceGetCell = false; forceGetCell = false;
} }
@ -521,10 +519,10 @@ class CellRenderer {
); );
// Uses double click timeout in mxGraph for quirks mode // Uses double click timeout in mxGraph for quirks mode
if (graph.event.nativeDblClickEnabled) { if (graph.isNativeDblClickEnabled()) {
InternalEvent.addListener(state.text.node, 'dblclick', (evt: MouseEvent) => { InternalEvent.addListener(state.text.node, 'dblclick', (evt: Event) => {
if (this.isLabelEvent(state, evt)) { if (this.isLabelEvent(state, evt as MouseEvent)) {
graph.event.dblClick(evt, state.cell); graph.dblClick(evt as MouseEvent, state.cell);
InternalEvent.consume(evt); InternalEvent.consume(evt);
} }
}); });
@ -558,47 +556,37 @@ class CellRenderer {
* *
* state - <mxCellState> for which the overlay should be created. * state - <mxCellState> for which the overlay should be created.
*/ */
createCellOverlays(state: CellState): void { createCellOverlays(state: CellState) {
const { graph } = state.view; const { graph } = state.view;
const overlays = graph.getCellOverlays(state.cell); const overlays = graph.getCellOverlays(state.cell);
let dict = null; const dict = new Dictionary<CellOverlay, Shape>();
if (overlays != null) { for (let i = 0; i < overlays.length; i += 1) {
dict = new Dictionary(); const shape = state.overlays.remove(overlays[i]);
for (let i = 0; i < overlays.length; i += 1) { if (!shape) {
const shape = state.overlays != null ? state.overlays.remove(overlays[i]) : null; const tmp = new ImageShape(new Rectangle(), overlays[i].image.src);
tmp.dialect = state.view.graph.dialect;
tmp.preserveImageAspect = false;
tmp.overlay = overlays[i];
this.initializeOverlay(state, tmp);
this.installCellOverlayListeners(state, overlays[i], tmp);
if (shape == null) { if (overlays[i].cursor) {
const tmp = new ImageShape( tmp.node.style.cursor = overlays[i].cursor;
new Rectangle(),
// @ts-ignore
overlays[i].image.src
);
tmp.dialect = state.view.graph.dialect;
tmp.preserveImageAspect = false;
tmp.overlay = overlays[i];
this.initializeOverlay(state, tmp);
this.installCellOverlayListeners(state, overlays[i], tmp);
if (overlays[i].cursor != null) {
// @ts-ignore
tmp.node.style.cursor = overlays[i].cursor;
}
dict.put(overlays[i], tmp);
} else {
dict.put(overlays[i], shape);
} }
dict.put(overlays[i], tmp);
} else {
dict.put(overlays[i], shape);
} }
} }
// Removes unused // Removes unused
if (state.overlays != null) { state.overlays.visit((id: any, shape: { destroy: () => void }) => {
state.overlays.visit((id: any, shape: { destroy: () => void }) => { shape.destroy();
shape.destroy(); });
});
}
state.overlays = dict; state.overlays = dict;
} }
@ -612,7 +600,7 @@ class CellRenderer {
* state - <mxCellState> for which the overlay should be created. * state - <mxCellState> for which the overlay should be created.
* overlay - <mxImageShape> that represents the overlay. * overlay - <mxImageShape> that represents the overlay.
*/ */
initializeOverlay(state: CellState, overlay: ImageShape): void { initializeOverlay(state: CellState, overlay: ImageShape) {
overlay.init(state.view.getOverlayPane()); overlay.init(state.view.getOverlayPane());
} }
@ -622,16 +610,12 @@ class CellRenderer {
* Installs the listeners for the given <mxCellState>, <mxCellOverlay> and * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
* <mxShape> that represents the overlay. * <mxShape> that represents the overlay.
*/ */
installCellOverlayListeners( installCellOverlayListeners(state: CellState, overlay: CellOverlay, shape: Shape) {
state: CellState,
overlay: CellOverlay,
shape: Shape
): void {
const { graph } = state.view; const { graph } = state.view;
InternalEvent.addListener(shape.node, 'click', (evt: Event) => { InternalEvent.addListener(shape.node, 'click', (evt: Event) => {
if (graph.editing.isEditing()) { if (graph.isEditing()) {
graph.editing.stopEditing(!graph.editing.isInvokesStopCellEditing()); graph.stopEditing(!graph.isInvokesStopCellEditing());
} }
overlay.fireEvent( overlay.fireEvent(
@ -645,9 +629,9 @@ class CellRenderer {
InternalEvent.consume(evt); InternalEvent.consume(evt);
}, },
(evt: Event) => { (evt: Event) => {
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, state) new InternalMouseEvent(evt as MouseEvent, state)
); );
} }
); );
@ -670,12 +654,12 @@ class CellRenderer {
* *
* state - <mxCellState> for which the control should be created. * state - <mxCellState> for which the control should be created.
*/ */
createControl(state: CellState): void { createControl(state: CellState) {
const { graph } = state.view; const { graph } = state.view;
const image = graph.getFoldingImage(state); const image = graph.getFoldingImage(state);
if (graph.foldingEnabled && image != null) { if (graph.isFoldingEnabled() && image) {
if (state.control == null) { if (!state.control) {
const b = new Rectangle(0, 0, image.width, image.height); const b = new Rectangle(0, 0, image.width, image.height);
state.control = new ImageShape(b, image.src); state.control = new ImageShape(b, image.src);
state.control.preserveImageAspect = false; state.control.preserveImageAspect = false;
@ -688,7 +672,7 @@ class CellRenderer {
this.createControlClickHandler(state) this.createControlClickHandler(state)
); );
} }
} else if (state.control != null) { } else if (state.control) {
state.control.destroy(); state.control.destroy();
state.control = null; state.control = null;
} }
@ -703,13 +687,13 @@ class CellRenderer {
* *
* state - <mxCellState> whose control click handler should be returned. * state - <mxCellState> whose control click handler should be returned.
*/ */
createControlClickHandler(state: CellState): Function { createControlClickHandler(state: CellState) {
const { graph } = state.view; const { graph } = state.view;
return (evt: EventObject) => { return (evt: Event) => {
if (this.forceControlClickHandler || graph.isEnabled()) { if (this.forceControlClickHandler || graph.isEnabled()) {
const collapse = !state.cell.isCollapsed(); const collapse = !state.cell.isCollapsed();
graph.foldCells(collapse, false, [state.cell], false, evt); graph.foldCells(collapse, false, new CellArray(state.cell), false, evt);
InternalEvent.consume(evt); InternalEvent.consume(evt);
} }
}; };
@ -731,7 +715,7 @@ class CellRenderer {
state: CellState, state: CellState,
control: Shape, control: Shape,
handleEvents: boolean, handleEvents: boolean,
clickHandler: Function clickHandler: EventListener
): Element { ): Element {
const { graph } = state.view; const { graph } = state.view;
@ -744,8 +728,7 @@ class CellRenderer {
if (isForceHtml) { if (isForceHtml) {
control.dialect = DIALECT_PREFERHTML; control.dialect = DIALECT_PREFERHTML;
control.init(graph.container); control.init(graph.container);
// @ts-ignore control.node.style.zIndex = String(1);
control.node.style.zIndex = 1;
} else { } else {
control.init(state.view.getOverlayPane()); control.init(state.view.getOverlayPane());
} }
@ -753,9 +736,8 @@ class CellRenderer {
const node = control.node; const node = control.node;
// Workaround for missing click event on iOS is to check tolerance below // Workaround for missing click event on iOS is to check tolerance below
if (clickHandler != null && !mxClient.IS_IOS) { if (clickHandler && !mxClient.IS_IOS) {
if (graph.isEnabled()) { if (graph.isEnabled()) {
// @ts-ignore
node.style.cursor = 'pointer'; node.style.cursor = 'pointer';
} }
@ -769,35 +751,34 @@ class CellRenderer {
node, node,
(evt: Event) => { (evt: Event) => {
first = new Point(getClientX(evt), getClientY(evt)); first = new Point(getClientX(evt), getClientY(evt));
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN, InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, state) new InternalMouseEvent(evt as MouseEvent, state)
); );
InternalEvent.consume(evt); InternalEvent.consume(evt);
}, },
(evt: Event) => { (evt: Event) => {
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, state) new InternalMouseEvent(evt as MouseEvent, state)
); );
}, },
(evt: Event) => { (evt: Event) => {
graph.event.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_UP, InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt, state) new InternalMouseEvent(evt as MouseEvent, state)
); );
InternalEvent.consume(evt); InternalEvent.consume(evt);
} }
); );
// Uses capture phase for event interception to stop bubble phase // Uses capture phase for event interception to stop bubble phase
if (clickHandler != null && mxClient.IS_IOS) { if (clickHandler && mxClient.IS_IOS) {
// @ts-ignore
node.addEventListener( node.addEventListener(
'touchend', 'touchend',
(evt) => { (evt) => {
if (first != null) { if (first) {
const tol = graph.tolerance; const tol = graph.getEventTolerance();
if ( if (
Math.abs(first.x - getClientX(evt)) < tol && Math.abs(first.x - getClientX(evt)) < tol &&
@ -827,7 +808,7 @@ class CellRenderer {
* state - <mxCellState> whose shape fired the event. * state - <mxCellState> whose shape fired the event.
* evt - Mouse event which was fired. * evt - Mouse event which was fired.
*/ */
isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent): boolean { isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) {
return true; return true;
} }
@ -842,7 +823,7 @@ class CellRenderer {
* state - <mxCellState> whose label fired the event. * state - <mxCellState> whose label fired the event.
* evt - Mouse event which was fired. * evt - Mouse event which was fired.
*/ */
isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent): boolean { isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) {
return true; return true;
} }
@ -855,14 +836,14 @@ class CellRenderer {
* *
* state - <mxCellState> for which the event listeners should be isntalled. * state - <mxCellState> for which the event listeners should be isntalled.
*/ */
installListeners(state: CellState): void { installListeners(state: CellState) {
const { graph } = state.view; const { graph } = state.view;
// Workaround for touch devices routing all events for a mouse // Workaround for touch devices routing all events for a mouse
// gesture (down, move, up) via the initial DOM node. Same for // gesture (down, move, up) via the initial DOM node. Same for
// HTML images in all IE versions (VML images are working). // HTML images in all IE versions (VML images are working).
const getState = (evt: Event) => { const getState = (evt: Event) => {
let result = state; let result: CellState | null = state;
if ( if (
(graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG') || (graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG') ||
@ -874,7 +855,7 @@ class CellRenderer {
// Dispatches the drop event to the graph which // Dispatches the drop event to the graph which
// consumes and executes the source function // consumes and executes the source function
const pt = convertPoint(graph.container, x, y); const pt = convertPoint(graph.container, x, y);
result = <CellState>graph.view.getState(graph.getCellAt(pt.x, pt.y)); result = graph.view.getState(graph.getCellAt(pt.x, pt.y) as Cell);
} }
return result; return result;
@ -883,38 +864,38 @@ class CellRenderer {
InternalEvent.addGestureListeners( InternalEvent.addGestureListeners(
// @ts-ignore // @ts-ignore
state.shape.node, state.shape.node,
(evt: MouseEvent) => { (evt: Event) => {
if (this.isShapeEvent(state, evt)) { if (this.isShapeEvent(state, evt as MouseEvent)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN, InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt, state) new InternalMouseEvent(evt as MouseEvent, state)
); );
} }
}, },
(evt: MouseEvent) => { (evt: Event) => {
if (this.isShapeEvent(state, evt)) { if (this.isShapeEvent(state, evt as MouseEvent)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, getState(evt)) new InternalMouseEvent(evt as MouseEvent, getState(evt))
); );
} }
}, },
(evt: MouseEvent) => { (evt: Event) => {
if (this.isShapeEvent(state, evt)) { if (this.isShapeEvent(state, evt as MouseEvent)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_UP, InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt, getState(evt)) new InternalMouseEvent(evt as MouseEvent, getState(evt))
); );
} }
} }
); );
// Uses double click timeout in mxGraph for quirks mode // Uses double click timeout in mxGraph for quirks mode
if (graph.nativeDblClickEnabled) { if (graph.isNativeDblClickEnabled()) {
// @ts-ignore // @ts-ignore
InternalEvent.addListener(state.shape.node, 'dblclick', (evt) => { InternalEvent.addListener(state.shape.node, 'dblclick', (evt) => {
if (this.isShapeEvent(state, evt)) { if (this.isShapeEvent(state, evt as MouseEvent)) {
graph.dblClick(evt, state.cell); graph.dblClick(evt as MouseEvent, state.cell);
InternalEvent.consume(evt); InternalEvent.consume(evt);
} }
}); });
@ -930,22 +911,22 @@ class CellRenderer {
* *
* state - <mxCellState> whose label should be redrawn. * state - <mxCellState> whose label should be redrawn.
*/ */
redrawLabel(state: CellState, forced: boolean): void { redrawLabel(state: CellState, forced: boolean) {
const { graph } = state.view; const { graph } = state.view;
const value = this.getLabelValue(state); const value = this.getLabelValue(state);
const wrapping = graph.isWrapping(state.cell); const wrapping = graph.isWrapping(state.cell);
const clipping = graph.isLabelClipped(state.cell); const clipping = graph.isLabelClipped(state.cell);
const isForceHtml = const isForceHtml =
state.view.graph.isHtmlLabel(state.cell) || (value != null && isNode(value)); state.view.graph.isHtmlLabel(state.cell) || (value && isNode(value));
const dialect = isForceHtml ? DIALECT_STRICTHTML : state.view.graph.dialect; const dialect = isForceHtml ? DIALECT_STRICTHTML : state.view.graph.dialect;
const overflow = state.style.overflow || 'visible'; const overflow = state.style.overflow || 'visible';
if ( if (
state.text != null && state.text &&
(state.text.wrap != wrapping || (state.text.wrap !== wrapping ||
state.text.clipped != clipping || state.text.clipped !== clipping ||
state.text.overflow != overflow || state.text.overflow !== overflow ||
state.text.dialect != dialect) state.text.dialect !== dialect)
) { ) {
state.text.destroy(); state.text.destroy();
state.text = null; state.text = null;
@ -972,7 +953,7 @@ class CellRenderer {
state.text.apply(state); state.text.apply(state);
// Special case where value is obtained via hook in graph // Special case where value is obtained via hook in graph
state.text.valign = <string>state.getVerticalAlign(); state.text.valign = state.getVerticalAlign();
} }
const bounds = this.getLabelBounds(state); const bounds = this.getLabelBounds(state);
@ -1597,23 +1578,23 @@ class CellRenderer {
if (state.shape.indicatorShape != null) { if (state.shape.indicatorShape != null) {
state.shape.indicator = new state.shape.indicatorShape(); state.shape.indicator = new state.shape.indicatorShape();
state.shape.indicator.dialect = state.shape.dialect; state.shape.indicator.dialect = state.shape.dialect;
state.shape.indicator.init(state.node); state.shape.indicator.init(state.node as HTMLElement);
force = true; force = true;
} }
} }
if (state.shape != null) { if (state.shape) {
// Handles changes of the collapse icon // Handles changes of the collapse icon
this.createControl(state); this.createControl(state);
// Redraws the cell if required, ignores changes to bounds if points are // Redraws the cell if required, ignores changes to bounds if points are
// defined as the bounds are updated for the given points inside the shape // defined as the bounds are updated for the given points inside the shape
if (force || this.isShapeInvalid(state, state.shape)) { if (force || this.isShapeInvalid(state, state.shape)) {
if (state.absolutePoints != null) { if (state.absolutePoints.length > 0) {
state.shape.points = state.absolutePoints.slice(); state.shape.points = state.absolutePoints.slice();
state.shape.bounds = null; state.shape.bounds = null;
} else { } else {
state.shape.points = null; state.shape.points = [];
state.shape.bounds = new Rectangle(state.x, state.y, state.width, state.height); state.shape.bounds = new Rectangle(state.x, state.y, state.width, state.height);
} }
@ -1664,22 +1645,20 @@ class CellRenderer {
* *
* state - <mxCellState> for which the shapes should be destroyed. * state - <mxCellState> for which the shapes should be destroyed.
*/ */
destroy(state: CellState): void { destroy(state: CellState) {
if (state.shape != null) { if (state.shape) {
if (state.text != null) { if (state.text) {
state.text.destroy(); state.text.destroy();
state.text = null; state.text = null;
} }
if (state.overlays != null) { state.overlays.visit((id: string, shape: Shape) => {
state.overlays.visit((id: string, shape: Shape) => { shape.destroy();
shape.destroy(); });
});
state.overlays = null; state.overlays = new Dictionary();
}
if (state.control != null) { if (state.control) {
state.control.destroy(); state.control.destroy();
state.control = null; state.control = null;
} }
@ -1699,6 +1678,7 @@ CellRenderer.registerShape(SHAPE_ELLIPSE, EllipseShape);
CellRenderer.registerShape(SHAPE_RHOMBUS, RhombusShape); CellRenderer.registerShape(SHAPE_RHOMBUS, RhombusShape);
// @ts-ignore // @ts-ignore
CellRenderer.registerShape(SHAPE_CYLINDER, CylinderShape); CellRenderer.registerShape(SHAPE_CYLINDER, CylinderShape);
// @ts-ignore
CellRenderer.registerShape(SHAPE_CONNECTOR, Connector); CellRenderer.registerShape(SHAPE_CONNECTOR, Connector);
// @ts-ignore // @ts-ignore
CellRenderer.registerShape(SHAPE_ACTOR, Actor); CellRenderer.registerShape(SHAPE_ACTOR, Actor);
@ -1706,14 +1686,19 @@ CellRenderer.registerShape(SHAPE_TRIANGLE, TriangleShape);
CellRenderer.registerShape(SHAPE_HEXAGON, HexagonShape); CellRenderer.registerShape(SHAPE_HEXAGON, HexagonShape);
// @ts-ignore // @ts-ignore
CellRenderer.registerShape(SHAPE_CLOUD, CloudShape); CellRenderer.registerShape(SHAPE_CLOUD, CloudShape);
// @ts-ignore
CellRenderer.registerShape(SHAPE_LINE, Line); CellRenderer.registerShape(SHAPE_LINE, Line);
// @ts-ignore
CellRenderer.registerShape(SHAPE_ARROW, Arrow); CellRenderer.registerShape(SHAPE_ARROW, Arrow);
// @ts-ignore
CellRenderer.registerShape(SHAPE_ARROW_CONNECTOR, ArrowConnector); CellRenderer.registerShape(SHAPE_ARROW_CONNECTOR, ArrowConnector);
// @ts-ignore // @ts-ignore
CellRenderer.registerShape(SHAPE_DOUBLE_ELLIPSE, DoubleEllipseShape); CellRenderer.registerShape(SHAPE_DOUBLE_ELLIPSE, DoubleEllipseShape);
// @ts-ignore
CellRenderer.registerShape(SHAPE_SWIMLANE, SwimlaneShape); CellRenderer.registerShape(SHAPE_SWIMLANE, SwimlaneShape);
// @ts-ignore // @ts-ignore
CellRenderer.registerShape(SHAPE_IMAGE, ImageShape); CellRenderer.registerShape(SHAPE_IMAGE, ImageShape);
// @ts-ignore
CellRenderer.registerShape(SHAPE_LABEL, Label); CellRenderer.registerShape(SHAPE_LABEL, Label);
export default CellRenderer; export default CellRenderer;

View File

@ -97,7 +97,7 @@ class TemporaryCellStates {
/** /**
* Holds the states of the rectangle. * Holds the states of the rectangle.
*/ */
oldStates: Dictionary<string, CellState>; oldStates: Dictionary<Cell, CellState>;
/** /**
* Holds the bounds of the rectangle. * Holds the bounds of the rectangle.

View File

@ -1,7 +1,7 @@
import Cell from "./datatypes/Cell"; import Cell from './datatypes/Cell';
import CellArray from "./datatypes/CellArray"; import CellArray from './datatypes/CellArray';
import Dictionary from "../../util/Dictionary"; import Dictionary from '../../util/Dictionary';
import Graph from "../Graph"; import Graph from '../Graph';
class TreeTraversal { class TreeTraversal {
dependencies = ['connections']; dependencies = ['connections'];
@ -42,7 +42,7 @@ class TreeTraversal {
for (const cell of parent.getChildren()) { for (const cell of parent.getChildren()) {
if (cell.isVertex() && cell.isVisible()) { if (cell.isVertex() && cell.isVisible()) {
const conns = this.graph.connection.getConnections(cell, isolate ? parent : null); const conns = this.graph.getConnections(cell, isolate ? parent : null);
let fanOut = 0; let fanOut = 0;
let fanIn = 0; let fanIn = 0;
@ -117,13 +117,13 @@ class TreeTraversal {
directed: boolean = true, directed: boolean = true,
func: Function | null = null, func: Function | null = null,
edge: Cell | null = null, edge: Cell | null = null,
visited: Dictionary | null = null, visited: Dictionary<Cell, boolean> | null = null,
inverse: boolean = false inverse: boolean = false
): void { ): void {
if (func != null && vertex != null) { if (func != null && vertex != null) {
directed = directed != null ? directed : true; directed = directed != null ? directed : true;
inverse = inverse != null ? inverse : false; inverse = inverse != null ? inverse : false;
visited = visited || new Dictionary(); visited = visited || new Dictionary<Cell, boolean>();
if (!visited.get(vertex)) { if (!visited.get(vertex)) {
visited.put(vertex, true); visited.put(vertex, true);

View File

@ -15,6 +15,7 @@ import Dictionary from '../../../util/Dictionary';
import { NONE } from '../../../util/Constants'; import { NONE } from '../../../util/Constants';
import { CellStateStyles } from 'packages/core/src/types'; import { CellStateStyles } from 'packages/core/src/types';
import RectangleShape from '../../geometry/shape/node/RectangleShape'; import RectangleShape from '../../geometry/shape/node/RectangleShape';
import CellOverlay from '../CellOverlay';
/** /**
* Class: mxCellState * Class: mxCellState
@ -67,7 +68,7 @@ class CellState extends Rectangle {
control: Shape | null = null; control: Shape | null = null;
// Used by mxCellRenderer's createCellOverlays() // Used by mxCellRenderer's createCellOverlays()
overlays: Dictionary<Cell, Shape> | null = null; overlays: Dictionary<CellOverlay, Shape> = new Dictionary();
/** /**
* Variable: view * Variable: view
@ -197,6 +198,8 @@ class CellState extends Rectangle {
parentHighlight: RectangleShape | null = null; parentHighlight: RectangleShape | null = null;
point: Point | null = null;
/** /**
* Function: getPerimeterBounds * Function: getPerimeterBounds
* *

View File

@ -63,6 +63,7 @@ import InternalMouseEvent from '../../event/InternalMouseEvent';
import Cell from '../datatypes/Cell'; import Cell from '../datatypes/Cell';
import ImageBox from '../../image/ImageBox'; import ImageBox from '../../image/ImageBox';
import Marker from '../../geometry/shape/edge/Marker'; import Marker from '../../geometry/shape/edge/Marker';
import EventSource from '../../event/EventSource';
/** /**
* Graph event handler that reconnects edges and modifies control points and the edge * Graph event handler that reconnects edges and modifies control points and the edge
@ -381,8 +382,12 @@ class EdgeHandler {
customHandles: CellHandle[] = []; customHandles: CellHandle[] = [];
startX: number = 0; startX = 0;
startY: number = 0; startY = 0;
outline = true;
active = true;
/** /**
* Function: isParentHighlightVisible * Function: isParentHighlightVisible
@ -461,7 +466,7 @@ class EdgeHandler {
* <virtualBendsEnabled> is true and the current style allows and * <virtualBendsEnabled> is true and the current style allows and
* renders custom waypoints. * renders custom waypoints.
*/ */
isVirtualBendsEnabled(evt: Event) { isVirtualBendsEnabled(evt?: Event) {
return ( return (
this.virtualBendsEnabled && this.virtualBendsEnabled &&
(this.state.style.edge == null || (this.state.style.edge == null ||
@ -970,7 +975,7 @@ class EdgeHandler {
* control point. The source and target points are used for reconnecting * control point. The source and target points are used for reconnecting
* the edge. * the edge.
*/ */
mouseDown(sender: Listenable, me: InternalMouseEvent) { mouseDown(sender: EventSource, me: InternalMouseEvent) {
const handle = this.getHandleForEvent(me); const handle = this.getHandleForEvent(me);
if (handle !== null && this.bends[handle]) { if (handle !== null && this.bends[handle]) {
@ -1284,7 +1289,7 @@ class EdgeHandler {
// Removes point if user tries to straighten a segment // Removes point if user tries to straighten a segment
if (!result && this.straightRemoveEnabled && (!me || !isAltDown(me.getEvent()))) { if (!result && this.straightRemoveEnabled && (!me || !isAltDown(me.getEvent()))) {
const tol = this.graph.getClickTolerance() * this.graph.getClickTolerance(); const tol = this.graph.getEventTolerance() * this.graph.getEventTolerance();
const abs = this.state.absolutePoints.slice(); const abs = this.state.absolutePoints.slice();
abs[this.index] = pt; abs[this.index] = pt;
@ -1517,8 +1522,7 @@ class EdgeHandler {
* *
* Handles the event by updating the preview. * Handles the event by updating the preview.
*/ */
// mouseMove(sender: any, me: mxMouseEvent): void; mouseMove(sender: EventSource, me: InternalMouseEvent) {
mouseMove(sender: Listenable, me: InternalMouseEvent) {
if (this.index != null && this.marker != null) { if (this.index != null && this.marker != null) {
this.currentPoint = this.getPointForEvent(me); this.currentPoint = this.getPointForEvent(me);
this.error = null; this.error = null;
@ -1628,7 +1632,7 @@ class EdgeHandler {
* Handles the event to applying the previewed changes on the edge by * Handles the event to applying the previewed changes on the edge by
* using <moveLabel>, <connect> or <changePoints>. * using <moveLabel>, <connect> or <changePoints>.
*/ */
mouseUp(sender: Listenable, me: InternalMouseEvent) { mouseUp(sender: EventSource, me: InternalMouseEvent) {
// Workaround for wrong event source in Webkit // Workaround for wrong event source in Webkit
if (this.index != null && this.marker != null) { if (this.index != null && this.marker != null) {
if (this.shape != null && this.shape.node != null) { if (this.shape != null && this.shape.node != null) {
@ -1675,7 +1679,7 @@ class EdgeHandler {
} else if (this.isLabel && this.label) { } else if (this.isLabel && this.label) {
this.moveLabel(this.state, this.label.x, this.label.y); this.moveLabel(this.state, this.label.x, this.label.y);
} else if (this.isSource || this.isTarget) { } else if (this.isSource || this.isTarget) {
let terminal = null; let terminal: Cell | null = null;
if ( if (
this.constraintHandler.currentConstraint != null && this.constraintHandler.currentConstraint != null &&
@ -1685,17 +1689,17 @@ class EdgeHandler {
} }
if ( if (
terminal == null && !terminal &&
this.marker.hasValidState() && this.marker.hasValidState() &&
this.marker.highlight != null && this.marker.highlight != null &&
this.marker.highlight.shape != null && this.marker.highlight.shape != null &&
this.marker.highlight.shape.stroke !== 'transparent' && this.marker.highlight.shape.stroke !== 'transparent' &&
this.marker.highlight.shape.stroke !== 'white' this.marker.highlight.shape.stroke !== 'white'
) { ) {
terminal = this.marker.validState.cell; terminal = this.marker.validState!.cell;
} }
if (terminal != null) { if (terminal) {
const model = this.graph.getModel(); const model = this.graph.getModel();
const parent = edge.getParent(); const parent = edge.getParent();
@ -1704,18 +1708,18 @@ class EdgeHandler {
// Clones and adds the cell // Clones and adds the cell
if (clone) { if (clone) {
let geo = edge.getGeometry(); let geo = edge.getGeometry();
clone = this.graph.cloneCell(edge); const cloned = this.graph.cloneCell(edge);
model.add(parent, clone, parent.getChildCount()); model.add(parent, cloned, parent.getChildCount());
if (geo != null) { if (geo != null) {
geo = geo.clone(); geo = geo.clone();
model.setGeometry(clone, geo); model.setGeometry(cloned, geo);
} }
const other = edge.getTerminal(!this.isSource); const other = edge.getTerminal(!this.isSource);
this.graph.connectCell(clone, other, !this.isSource); this.graph.connectCell(cloned, other, !this.isSource);
edge = clone; edge = cloned;
} }
edge = this.connect(edge, terminal, this.isSource, clone, me); edge = this.connect(edge, terminal, this.isSource, clone, me);
@ -2127,7 +2131,7 @@ class EdgeHandler {
* *
* Redraws the preview, and the bends- and label control points. * Redraws the preview, and the bends- and label control points.
*/ */
redraw(ignoreHandles: boolean) { redraw(ignoreHandles?: boolean) {
this.abspoints = this.state.absolutePoints.slice(); this.abspoints = this.state.absolutePoints.slice();
const g = this.state.cell.getGeometry(); const g = this.state.cell.getGeometry();

View File

@ -69,6 +69,8 @@ class VertexHandle implements CellHandle {
*/ */
ignoreGrid = false; ignoreGrid = false;
active = true;
/** /**
* Hook for subclassers to return the current position of the handle. * Hook for subclassers to return the current position of the handle.
*/ */

File diff suppressed because it is too large Load Diff

View File

@ -13,17 +13,17 @@ import Point from '../geometry/Point';
*/ */
class ConnectionConstraint { class ConnectionConstraint {
constructor( constructor(
point: Point | null = null, point: Point | null,
perimeter: boolean = true, perimeter = true,
name: string | null = null, name: string | null = null,
dx: number | null = null, dx = 0,
dy: number | null = null dy = 0
) { ) {
this.point = point; this.point = point;
this.perimeter = perimeter != null ? perimeter : true; this.perimeter = perimeter;
this.name = name; this.name = name;
this.dx = dx || 0; this.dx = dx;
this.dy = dy || 0; this.dy = dy;
} }
/** /**
@ -31,7 +31,7 @@ class ConnectionConstraint {
* *
* <mxPoint> that specifies the fixed location of the connection point. * <mxPoint> that specifies the fixed location of the connection point.
*/ */
point: Point | null = null; point: Point | null;
/** /**
* Variable: perimeter * Variable: perimeter
@ -39,7 +39,7 @@ class ConnectionConstraint {
* Boolean that specifies if the point should be projected onto the perimeter * Boolean that specifies if the point should be projected onto the perimeter
* of the terminal. * of the terminal.
*/ */
perimeter: boolean = true; perimeter = true;
/** /**
* Variable: name * Variable: name
@ -53,14 +53,14 @@ class ConnectionConstraint {
* *
* Optional float that specifies the horizontal offset of the constraint. * Optional float that specifies the horizontal offset of the constraint.
*/ */
dx: number | null = null; dx = 0;
/** /**
* Variable: dy * Variable: dy
* *
* Optional float that specifies the vertical offset of the constraint. * Optional float that specifies the vertical offset of the constraint.
*/ */
dy: number | null = null; dy = 0;
} }
export default ConnectionConstraint; export default ConnectionConstraint;

View File

@ -14,6 +14,7 @@ import {
DIALECT_SVG, DIALECT_SVG,
HIGHLIGHT_STROKEWIDTH, HIGHLIGHT_STROKEWIDTH,
INVALID_COLOR, INVALID_COLOR,
NONE,
OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_COLOR,
OUTLINE_HIGHLIGHT_STROKEWIDTH, OUTLINE_HIGHLIGHT_STROKEWIDTH,
TOOLTIP_VERTICAL_OFFSET, TOOLTIP_VERTICAL_OFFSET,
@ -41,12 +42,13 @@ import {
isConsumed, isConsumed,
isShiftDown, isShiftDown,
} from '../../util/EventUtils'; } from '../../util/EventUtils';
import graph from '../Graph'; import graph, { MaxGraph } from '../Graph';
import Image from '../image/ImageBox'; import Image from '../image/ImageBox';
import CellState from '../cell/datatypes/CellState'; import CellState from '../cell/datatypes/CellState';
import Graph from '../Graph'; import Graph from '../Graph';
import ConnectionConstraint from './ConnectionConstraint'; import ConnectionConstraint from './ConnectionConstraint';
import Shape from '../geometry/shape/Shape'; import Shape from '../geometry/shape/Shape';
import { Listenable } from '../../types';
type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell; type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell;
@ -207,12 +209,45 @@ type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell;
* the <mxCell> that represents the new edge. * the <mxCell> that represents the new edge.
*/ */
class ConnectionHandler extends EventSource { class ConnectionHandler extends EventSource {
constructor(graph: Graph, factoryMethod: FactoryMethod | null = null) { constructor(graph: MaxGraph, factoryMethod: FactoryMethod | null = null) {
super(); super();
this.graph = graph; this.graph = graph;
this.factoryMethod = factoryMethod; this.factoryMethod = factoryMethod;
this.init();
this.graph.addMouseListener(this);
this.marker = <CellMarker>this.createMarker();
this.constraintHandler = new ConstraintHandler(this.graph);
// Redraws the icons if the graph changes
this.changeHandler = (sender: Listenable) => {
if (this.iconState) {
this.iconState = this.graph.getView().getState(this.iconState.cell);
}
if (this.iconState) {
this.redrawIcons(this.icons, this.iconState);
this.constraintHandler.reset();
} else if (this.previous && !this.graph.view.getState(this.previous.cell)) {
this.reset();
}
};
this.graph.getModel().addListener(InternalEvent.CHANGE, this.changeHandler);
this.graph.getView().addListener(InternalEvent.SCALE, this.changeHandler);
this.graph.getView().addListener(InternalEvent.TRANSLATE, this.changeHandler);
this.graph
.getView()
.addListener(InternalEvent.SCALE_AND_TRANSLATE, this.changeHandler);
// Removes the icon if we step into/up or start editing
this.drillHandler = (sender: Listenable) => {
this.reset();
};
this.graph.addListener(InternalEvent.START_EDITING, this.drillHandler);
this.graph.getView().addListener(InternalEvent.DOWN, this.drillHandler);
this.graph.getView().addListener(InternalEvent.UP, this.drillHandler);
// Handles escape keystrokes // Handles escape keystrokes
this.escapeHandler = () => { this.escapeHandler = () => {
@ -225,7 +260,7 @@ class ConnectionHandler extends EventSource {
// TODO: Document me! // TODO: Document me!
previous: CellState | null = null; previous: CellState | null = null;
iconState: CellState | null = null; iconState: CellState | null = null;
icons: ImageShape[] | null = null; icons: ImageShape[] = [];
cell: Cell | null = null; cell: Cell | null = null;
currentPoint: Point | null = null; currentPoint: Point | null = null;
sourceConstraint: ConnectionConstraint | null = null; sourceConstraint: ConnectionConstraint | null = null;
@ -241,7 +276,7 @@ class ConnectionHandler extends EventSource {
* *
* Reference to the enclosing <mxGraph>. * Reference to the enclosing <mxGraph>.
*/ */
graph: Graph; graph: MaxGraph;
/** /**
* Variable: factoryMethod * Variable: factoryMethod
@ -319,7 +354,6 @@ class ConnectionHandler extends EventSource {
* *
* Holds the <mxTerminalMarker> used for finding source and target cells. * Holds the <mxTerminalMarker> used for finding source and target cells.
*/ */
// @ts-ignore
marker: CellMarker; marker: CellMarker;
/** /**
@ -328,14 +362,14 @@ class ConnectionHandler extends EventSource {
* Holds the <mxConstraintHandler> used for drawing and highlighting * Holds the <mxConstraintHandler> used for drawing and highlighting
* constraints. * constraints.
*/ */
constraintHandler: ConstraintHandler | null = null; constraintHandler: ConstraintHandler;
/** /**
* Variable: error * Variable: error
* *
* Holds the current validation error while connections are being created. * Holds the current validation error while connections are being created.
*/ */
error: any = null; error: string | null = null;
/** /**
* Variable: waypointsEnabled * Variable: waypointsEnabled
@ -385,14 +419,14 @@ class ConnectionHandler extends EventSource {
* *
* Holds the change event listener for later removal. * Holds the change event listener for later removal.
*/ */
changeHandler: any = null; changeHandler: (sender: Listenable) => void;
/** /**
* Variable: drillHandler * Variable: drillHandler
* *
* Holds the drill event listener for later removal. * Holds the drill event listener for later removal.
*/ */
drillHandler: any = null; drillHandler: (sender: Listenable) => void;
/** /**
* Variable: mouseDownCounter * Variable: mouseDownCounter
@ -539,52 +573,6 @@ class ConnectionHandler extends EventSource {
return shape; return shape;
} }
/**
* Function: init
*
* Initializes the shapes required for this connection handler. This should
* be invoked if <mxGraph.container> is assigned after the connection
* handler has been created.
*/
init(): void {
this.graph.event.addMouseListener(this);
this.marker = <CellMarker>this.createMarker();
this.constraintHandler = new ConstraintHandler(this.graph);
// Redraws the icons if the graph changes
this.changeHandler = (sender) => {
if (this.iconState != null) {
this.iconState = this.graph.getView().getState(this.iconState.cell);
}
if (this.iconState != null) {
this.redrawIcons(this.icons, this.iconState);
this.constraintHandler.reset();
} else if (
this.previous != null &&
this.graph.view.getState(this.previous.cell) == null
) {
this.reset();
}
};
this.graph.getModel().addListener(InternalEvent.CHANGE, this.changeHandler);
this.graph.getView().addListener(InternalEvent.SCALE, this.changeHandler);
this.graph.getView().addListener(InternalEvent.TRANSLATE, this.changeHandler);
this.graph
.getView()
.addListener(InternalEvent.SCALE_AND_TRANSLATE, this.changeHandler);
// Removes the icon if we step into/up or start editing
this.drillHandler = (sender) => {
this.reset();
};
this.graph.addListener(InternalEvent.START_EDITING, this.drillHandler);
this.graph.getView().addListener(InternalEvent.DOWN, this.drillHandler);
this.graph.getView().addListener(InternalEvent.UP, this.drillHandler);
}
/** /**
* Function: isConnectableCell * Function: isConnectableCell
* *
@ -600,7 +588,7 @@ class ConnectionHandler extends EventSource {
* *
* Creates and returns the <mxCellMarker> used in <marker>. * Creates and returns the <mxCellMarker> used in <marker>.
*/ */
createMarker(): CellMarker { createMarker() {
const self = this; const self = this;
class MyCellMarker extends CellMarker { class MyCellMarker extends CellMarker {
@ -613,12 +601,12 @@ class ConnectionHandler extends EventSource {
self.error = null; self.error = null;
// Checks for cell at preview point (with grid) // Checks for cell at preview point (with grid)
if (cell == null && self.currentPoint != null) { if (!cell && self.currentPoint) {
cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y); cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);
} }
// Uses connectable parent vertex if one exists // Uses connectable parent vertex if one exists
if (cell != null && !cell.isConnectable()) { if (cell && !cell.isConnectable() && self.cell) {
const parent = self.cell.getParent(); const parent = self.cell.getParent();
if (parent.isVertex() && parent.isConnectable()) { if (parent.isVertex() && parent.isConnectable()) {
@ -626,6 +614,7 @@ class ConnectionHandler extends EventSource {
} }
} }
/* disable swimlane for now
if ( if (
(self.graph.swimlane.isSwimlane(cell) && (self.graph.swimlane.isSwimlane(cell) &&
self.currentPoint != null && self.currentPoint != null &&
@ -638,13 +627,14 @@ class ConnectionHandler extends EventSource {
) { ) {
cell = null; cell = null;
} }
*/
if (cell != null) { if (cell) {
if (self.isConnecting()) { if (self.isConnecting()) {
if (self.previous != null) { if (self.previous) {
self.error = self.validateConnection(self.previous.cell, cell); self.error = self.validateConnection(self.previous.cell, cell);
if (self.error != null && self.error.length === 0) { if (self.error && self.error.length === 0) {
cell = null; cell = null;
// Enables create target inside groups // Enables create target inside groups
@ -659,7 +649,7 @@ class ConnectionHandler extends EventSource {
} else if ( } else if (
self.isConnecting() && self.isConnecting() &&
!self.isCreateTarget(me.getEvent()) && !self.isCreateTarget(me.getEvent()) &&
!self.graph.allowDanglingEdges !self.graph.isAllowDanglingEdges()
) { ) {
self.error = ''; self.error = '';
} }
@ -670,30 +660,30 @@ class ConnectionHandler extends EventSource {
// Sets the highlight color according to validateConnection // Sets the highlight color according to validateConnection
isValidState(state: CellState) { isValidState(state: CellState) {
if (self.isConnecting()) { if (self.isConnecting()) {
return self.error == null; return !self.error;
} }
return super.isValidState(state); return super.isValidState(state);
} }
// Overrides to use marker color only in highlight mode or for // Overrides to use marker color only in highlight mode or for
// target selection // target selection
getMarkerColor(evt: Event, state: CellState, isValid: boolean): string | null { getMarkerColor(evt: Event, state: CellState, isValid: boolean) {
return self.connectImage == null || self.isConnecting() return !self.connectImage || self.isConnecting()
? super.getMarkerColor(evt, state, isValid) ? super.getMarkerColor(evt, state, isValid)
: null; : NONE;
} }
// Overrides to use hotspot only for source selection otherwise // Overrides to use hotspot only for source selection otherwise
// intersects always returns true when over a cell // intersects always returns true when over a cell
intersects(state: CellState, evt: InternalMouseEvent) { intersects(state: CellState, evt: InternalMouseEvent) {
if (self.connectImage != null || self.isConnecting()) { if (self.connectImage || self.isConnecting()) {
return true; return true;
} }
return super.intersects(state, evt); return super.intersects(state, evt);
} }
} }
return <CellMarker>new MyCellMarker(this.graph); return new MyCellMarker(this.graph);
} }
/** /**
@ -701,10 +691,10 @@ class ConnectionHandler extends EventSource {
* *
* Starts a new connection for the given state and coordinates. * Starts a new connection for the given state and coordinates.
*/ */
start(state: CellState, x: number, y: number, edgeState: CellState): void { start(state: CellState, x: number, y: number, edgeState: CellState) {
this.previous = state; this.previous = state;
this.first = new Point(x, y); this.first = new Point(x, y);
this.edgeState = edgeState != null ? edgeState : this.createEdgeState(null); this.edgeState = edgeState ?? this.createEdgeState();
// Marks the source state // Marks the source state
this.marker.currentColor = this.marker.validColor; this.marker.currentColor = this.marker.validColor;
@ -720,8 +710,8 @@ class ConnectionHandler extends EventSource {
* Returns true if the source terminal has been clicked and a new * Returns true if the source terminal has been clicked and a new
* connection is currently being previewed. * connection is currently being previewed.
*/ */
isConnecting(): boolean { isConnecting() {
return this.first != null && this.shape != null; return !!this.first && !!this.shape;
} }
/** /**
@ -734,7 +724,7 @@ class ConnectionHandler extends EventSource {
* cell - <mxCell> that represents the source terminal. * cell - <mxCell> that represents the source terminal.
* me - <mxMouseEvent> that is associated with this call. * me - <mxMouseEvent> that is associated with this call.
*/ */
isValidSource(cell: Cell, me: InternalMouseEvent): boolean { isValidSource(cell: Cell, me: InternalMouseEvent) {
return this.graph.isValidSource(cell); return this.graph.isValidSource(cell);
} }
@ -749,7 +739,7 @@ class ConnectionHandler extends EventSource {
* *
* cell - <mxCell> that represents the target terminal. * cell - <mxCell> that represents the target terminal.
*/ */
isValidTarget(cell: Cell): boolean { isValidTarget(cell: Cell) {
return true; return true;
} }
@ -765,7 +755,7 @@ class ConnectionHandler extends EventSource {
* source - <mxCell> that represents the source terminal. * source - <mxCell> that represents the source terminal.
* target - <mxCell> that represents the target terminal. * target - <mxCell> that represents the target terminal.
*/ */
validateConnection(source: Cell, target: Cell): string { validateConnection(source: Cell, target: Cell) {
if (!this.isValidTarget(target)) { if (!this.isValidTarget(target)) {
return ''; return '';
} }
@ -782,7 +772,7 @@ class ConnectionHandler extends EventSource {
* *
* state - <mxCellState> whose connect image should be returned. * state - <mxCellState> whose connect image should be returned.
*/ */
getConnectImage(state: CellState): Image | null { getConnectImage(state: CellState) {
return this.connectImage; return this.connectImage;
} }
@ -796,8 +786,8 @@ class ConnectionHandler extends EventSource {
* *
* state - <mxCellState> whose connect icons should be returned. * state - <mxCellState> whose connect icons should be returned.
*/ */
isMoveIconToFrontForState(state: CellState): boolean { isMoveIconToFrontForState(state: CellState) {
if (state.text != null && state.text.node.parentNode === this.graph.container) { if (state.text && state.text.node.parentNode === this.graph.container) {
return true; return true;
} }
return this.moveIconFront; return this.moveIconFront;
@ -813,10 +803,10 @@ class ConnectionHandler extends EventSource {
* *
* state - <mxCellState> whose connect icons should be returned. * state - <mxCellState> whose connect icons should be returned.
*/ */
createIcons(state: CellState): ImageShape[] | null { createIcons(state: CellState) {
const image = this.getConnectImage(state); const image = this.getConnectImage(state);
if (image != null && state != null) { if (image) {
this.iconState = state; this.iconState = state;
const icons = []; const icons = [];
@ -825,7 +815,7 @@ class ConnectionHandler extends EventSource {
// connect-icon appears behind the selection border and the selection // connect-icon appears behind the selection border and the selection
// border consumes the events before the icon gets a chance // border consumes the events before the icon gets a chance
const bounds = new Rectangle(0, 0, image.width, image.height); const bounds = new Rectangle(0, 0, image.width, image.height);
const icon = new ImageShape(bounds, image.src, null, null, 0); const icon = new ImageShape(bounds, image.src, undefined, undefined, 0);
icon.preserveImageAspect = false; icon.preserveImageAspect = false;
if (this.isMoveIconToFrontForState(state)) { if (this.isMoveIconToFrontForState(state)) {
@ -836,7 +826,7 @@ class ConnectionHandler extends EventSource {
icon.init(this.graph.getView().getOverlayPane()); icon.init(this.graph.getView().getOverlayPane());
// Move the icon back in the overlay pane // Move the icon back in the overlay pane
if (this.moveIconBack && icon.node.previousSibling != null) { if (this.moveIconBack && icon.node.parentNode && icon.node.previousSibling) {
icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
} }
} }
@ -845,11 +835,11 @@ class ConnectionHandler extends EventSource {
// Events transparency // Events transparency
const getState = () => { const getState = () => {
return this.currentState != null ? this.currentState : state; return this.currentState ?? state;
}; };
// Updates the local icon before firing the mouse down event. // Updates the local icon before firing the mouse down event.
const mouseDown = (evt) => { const mouseDown = (evt: MouseEvent) => {
if (!isConsumed(evt)) { if (!isConsumed(evt)) {
this.icon = icon; this.icon = icon;
this.graph.fireMouseEvent( this.graph.fireMouseEvent(
@ -877,10 +867,10 @@ class ConnectionHandler extends EventSource {
* *
* Parameters: * Parameters:
* *
* icons - Optional array of <mxImageShapes> to be redrawn. * icons - Array of <mxImageShapes> to be redrawn.
*/ */
redrawIcons(icons?: ImageShape[] | null, state?: CellState): void { redrawIcons(icons: ImageShape[], state: CellState) {
if (icons != null && icons[0] != null && state != null) { if (icons[0] && icons[0].bounds) {
const pos = this.getIconPosition(icons[0], state); const pos = this.getIconPosition(icons[0], state);
icons[0].bounds.x = pos.x; icons[0].bounds.x = pos.x;
icons[0].bounds.y = pos.y; icons[0].bounds.y = pos.y;
@ -889,11 +879,12 @@ class ConnectionHandler extends EventSource {
} }
// TODO: Document me! =========================================================================================================== // TODO: Document me! ===========================================================================================================
getIconPosition(icon: ImageShape, state: CellState): Point { getIconPosition(icon: ImageShape, state: CellState) {
const { scale } = this.graph.getView(); // const { scale } = this.graph.getView();
let cx = state.getCenterX(); let cx = state.getCenterX();
let cy = state.getCenterY(); let cy = state.getCenterY();
/* disable swimlane for now
if (this.graph.isSwimlane(state.cell)) { if (this.graph.isSwimlane(state.cell)) {
const size = this.graph.getStartSize(state.cell); const size = this.graph.getStartSize(state.cell);
@ -910,8 +901,9 @@ class ConnectionHandler extends EventSource {
cx = pt.x; cx = pt.x;
cy = pt.y; cy = pt.y;
} }
} }*/
return new Point(cx - icon.bounds.width / 2, cy - icon.bounds.height / 2);
return new Point(cx - icon.bounds!.width / 2, cy - icon.bounds!.height / 2);
} }
/** /**
@ -919,17 +911,14 @@ class ConnectionHandler extends EventSource {
* *
* Destroys the connect icons and resets the respective state. * Destroys the connect icons and resets the respective state.
*/ */
destroyIcons(): void { destroyIcons() {
if (this.icons != null) { for (let i = 0; i < this.icons.length; i += 1) {
for (let i = 0; i < this.icons.length; i += 1) { this.icons[i].destroy();
this.icons[i].destroy();
}
this.icons = null;
this.icon = null;
this.selectedIcon = null;
this.iconState = null;
} }
this.icon = null;
this.selectedIcon = null;
this.iconState = null;
} }
/** /**
@ -941,7 +930,7 @@ class ConnectionHandler extends EventSource {
* <constraintHandler> are not null, or <previous> and <error> are not null and * <constraintHandler> are not null, or <previous> and <error> are not null and
* <icons> is null or <icons> and <icon> are not null. * <icons> is null or <icons> and <icon> are not null.
*/ */
isStartEvent(me: InternalMouseEvent): boolean { isStartEvent(me: InternalMouseEvent) {
return ( return (
(this.constraintHandler.currentFocus !== null && (this.constraintHandler.currentFocus !== null &&
this.constraintHandler.currentConstraint !== null) || this.constraintHandler.currentConstraint !== null) ||
@ -956,7 +945,7 @@ class ConnectionHandler extends EventSource {
* *
* Handles the event by initiating a new connection. * Handles the event by initiating a new connection.
*/ */
mouseDown(sender: any, me: InternalMouseEvent): void { mouseDown(sender: Listenable, me: InternalMouseEvent) {
this.mouseDownCounter += 1; this.mouseDownCounter += 1;
if ( if (
@ -967,9 +956,9 @@ class ConnectionHandler extends EventSource {
this.isStartEvent(me) this.isStartEvent(me)
) { ) {
if ( if (
this.constraintHandler.currentConstraint != null && this.constraintHandler.currentConstraint &&
this.constraintHandler.currentFocus != null && this.constraintHandler.currentFocus &&
this.constraintHandler.currentPoint != null this.constraintHandler.currentPoint
) { ) {
this.sourceConstraint = this.constraintHandler.currentConstraint; this.sourceConstraint = this.constraintHandler.currentConstraint;
this.previous = this.constraintHandler.currentFocus; this.previous = this.constraintHandler.currentFocus;
@ -982,17 +971,17 @@ class ConnectionHandler extends EventSource {
this.edgeState = this.createEdgeState(me); this.edgeState = this.createEdgeState(me);
this.mouseDownCounter = 1; this.mouseDownCounter = 1;
if (this.waypointsEnabled && this.shape == null) { if (this.waypointsEnabled && !this.shape) {
this.waypoints = null; this.waypoints = null;
this.shape = this.createShape(); this.shape = this.createShape();
if (this.edgeState != null) { if (this.edgeState) {
this.shape.apply(this.edgeState); this.shape.apply(this.edgeState);
} }
} }
// Stores the starting point in the geometry of the preview // Stores the starting point in the geometry of the preview
if (this.previous == null && this.edgeState != null) { if (!this.previous && this.edgeState && this.edgeState.cell.geometry) {
const pt = this.graph.getPointForEvent(me.getEvent()); const pt = this.graph.getPointForEvent(me.getEvent());
this.edgeState.cell.geometry.setTerminalPoint(pt, true); this.edgeState.cell.geometry.setTerminalPoint(pt, true);
} }
@ -1013,7 +1002,7 @@ class ConnectionHandler extends EventSource {
* connecting. This implementation returns true if the state is not movable * connecting. This implementation returns true if the state is not movable
* in the graph. * in the graph.
*/ */
isImmediateConnectSource(state: CellState): boolean { isImmediateConnectSource(state: CellState) {
return !this.graph.isCellMovable(state.cell); return !this.graph.isCellMovable(state.cell);
} }
@ -1034,7 +1023,7 @@ class ConnectionHandler extends EventSource {
* }; * };
* (end) * (end)
*/ */
createEdgeState(me: InternalMouseEvent): CellState | null { createEdgeState(me?: InternalMouseEvent): CellState | null {
return null; return null;
} }
@ -1044,7 +1033,7 @@ class ConnectionHandler extends EventSource {
* Returns true if <outlineConnect> is true and the source of the event is the outline shape * Returns true if <outlineConnect> is true and the source of the event is the outline shape
* or shift is pressed. * or shift is pressed.
*/ */
isOutlineConnectEvent(me: InternalMouseEvent): boolean { isOutlineConnectEvent(me: InternalMouseEvent) {
const offset = getOffset(this.graph.container); const offset = getOffset(this.graph.container);
const evt = me.getEvent(); const evt = me.getEvent();
@ -1143,7 +1132,7 @@ class ConnectionHandler extends EventSource {
// Handles special case where mouse is on outline away from actual end point // Handles special case where mouse is on outline away from actual end point
// in which case the grid is ignored and mouse point is used instead // in which case the grid is ignored and mouse point is used instead
if (me.isSource(this.marker.highlight.shape)) { if (me.isSource(this.marker.highlight.shape)) {
point = new point(me.getGraphX(), me.getGraphY()); point = new Point(me.getGraphX(), me.getGraphY());
} }
const constraint = this.graph.getOutlineConstraint(point, this.currentState, me); const constraint = this.graph.getOutlineConstraint(point, this.currentState, me);
@ -1191,7 +1180,7 @@ class ConnectionHandler extends EventSource {
* *
* Returns true if the given cell does not allow new connections to be created. * Returns true if the given cell does not allow new connections to be created.
*/ */
isCellEnabled(cell: Cell): boolean { isCellEnabled(cell: Cell) {
return true; return true;
} }
@ -1200,7 +1189,7 @@ class ConnectionHandler extends EventSource {
* *
* Converts the given point from screen coordinates to model coordinates. * Converts the given point from screen coordinates to model coordinates.
*/ */
convertWaypoint(point: Point): void { convertWaypoint(point: Point) {
const scale = this.graph.getView().getScale(); const scale = this.graph.getView().getScale();
const tr = this.graph.getView().getTranslate(); const tr = this.graph.getView().getTranslate();
@ -1214,13 +1203,13 @@ class ConnectionHandler extends EventSource {
* Called to snap the given point to the current preview. This snaps to the * Called to snap the given point to the current preview. This snaps to the
* first point of the preview if alt is not pressed. * first point of the preview if alt is not pressed.
*/ */
snapToPreview(me: MouseEvent, point: Point): void { snapToPreview(me: InternalMouseEvent, point: Point) {
if (!isAltDown(me.getEvent()) && this.previous != null) { if (!isAltDown(me.getEvent()) && this.previous) {
const tol = (this.graph.gridSize * this.graph.view.scale) / 2; const tol = (this.graph.getGridSize() * this.graph.view.scale) / 2;
const tmp = const tmp =
this.sourceConstraint != null this.sourceConstraint && this.first
? this.first ? this.first
: new point(this.previous.getCenterX(), this.previous.getCenterY()); : new Point(this.previous.getCenterX(), this.previous.getCenterY());
if (Math.abs(tmp.x - me.getGraphX()) < tol) { if (Math.abs(tmp.x - me.getGraphX()) < tol) {
point.x = tmp.x; point.x = tmp.x;
@ -1238,13 +1227,13 @@ class ConnectionHandler extends EventSource {
* Handles the event by updating the preview edge or by highlighting * Handles the event by updating the preview edge or by highlighting
* a possible source or target terminal. * a possible source or target terminal.
*/ */
mouseMove(sender: MouseEvent, me: InternalMouseEvent): void { mouseMove(sender: MouseEvent, me: InternalMouseEvent) {
if ( if (
!me.isConsumed() && !me.isConsumed() &&
(this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown) (this.ignoreMouseDown || this.first || !this.graph.isMouseDown)
) { ) {
// Handles special case when handler is disabled during highlight // Handles special case when handler is disabled during highlight
if (!this.isEnabled() && this.currentState != null) { if (!this.isEnabled() && this.currentState) {
this.destroyIcons(); this.destroyIcons();
this.currentState = null; this.currentState = null;
} }
@ -1255,10 +1244,10 @@ class ConnectionHandler extends EventSource {
let point = new Point(me.getGraphX(), me.getGraphY()); let point = new Point(me.getGraphX(), me.getGraphY());
this.error = null; this.error = null;
if (this.graph.grid.isGridEnabledEvent(me.getEvent())) { if (this.graph.isGridEnabledEvent(me.getEvent())) {
point = new point( point = new Point(
(this.graph.grid.snap(point.x / scale - tr.x) + tr.x) * scale, (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,
(this.graph.grid.snap(point.y / scale - tr.y) + tr.y) * scale (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale
); );
} }
@ -1266,29 +1255,29 @@ class ConnectionHandler extends EventSource {
this.currentPoint = point; this.currentPoint = point;
if ( if (
(this.first != null || (this.isEnabled() && this.graph.isEnabled())) && (this.first || (this.isEnabled() && this.graph.isEnabled())) &&
(this.shape != null || (this.shape ||
this.first == null || !this.first ||
Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance || Math.abs(me.getGraphX() - this.first.x) > this.graph.getEventTolerance() ||
Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance) Math.abs(me.getGraphY() - this.first.y) > this.graph.getEventTolerance())
) { ) {
this.updateCurrentState(me, point); this.updateCurrentState(me, point);
} }
if (this.first != null) { if (this.first) {
let constraint = null; let constraint = null;
let current = point; let current = point;
// Uses the current point from the constraint handler if available // Uses the current point from the constraint handler if available
if ( if (
this.constraintHandler.currentConstraint != null && this.constraintHandler.currentConstraint &&
this.constraintHandler.currentFocus != null && this.constraintHandler.currentFocus &&
this.constraintHandler.currentPoint != null this.constraintHandler.currentPoint
) { ) {
constraint = this.constraintHandler.currentConstraint; constraint = this.constraintHandler.currentConstraint;
current = this.constraintHandler.currentPoint.clone(); current = this.constraintHandler.currentPoint.clone();
} else if ( } else if (
this.previous != null && this.previous &&
!this.graph.isIgnoreTerminalEvent(me.getEvent()) && !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
isShiftDown(me.getEvent()) isShiftDown(me.getEvent())
) { ) {
@ -1305,11 +1294,11 @@ class ConnectionHandler extends EventSource {
let pt2 = this.first; let pt2 = this.first;
// Moves the connect icon with the mouse // Moves the connect icon with the mouse
if (this.selectedIcon != null) { if (this.selectedIcon && this.selectedIcon.bounds) {
const w = this.selectedIcon.bounds.width; const w = this.selectedIcon.bounds.width;
const h = this.selectedIcon.bounds.height; const h = this.selectedIcon.bounds.height;
if (this.currentState != null && this.targetConnectImage) { if (this.currentState && this.targetConnectImage) {
const pos = this.getIconPosition(this.selectedIcon, this.currentState); const pos = this.getIconPosition(this.selectedIcon, this.currentState);
this.selectedIcon.bounds.x = pos.x; this.selectedIcon.bounds.x = pos.x;
this.selectedIcon.bounds.y = pos.y; this.selectedIcon.bounds.y = pos.y;
@ -1327,7 +1316,7 @@ class ConnectionHandler extends EventSource {
} }
// Uses edge state to compute the terminal points // Uses edge state to compute the terminal points
if (this.edgeState != null) { if (this.edgeState) {
this.updateEdgeState(current, constraint); this.updateEdgeState(current, constraint);
current = this.edgeState.absolutePoints[ current = this.edgeState.absolutePoints[
this.edgeState.absolutePoints.length - 1 this.edgeState.absolutePoints.length - 1
@ -1397,7 +1386,10 @@ class ConnectionHandler extends EventSource {
const dx = Math.abs(me.getGraphX() - this.first.x); const dx = Math.abs(me.getGraphX() - this.first.x);
const dy = Math.abs(me.getGraphY() - this.first.y); const dy = Math.abs(me.getGraphY() - this.first.y);
if (dx > this.graph.tolerance || dy > this.graph.tolerance) { if (
dx > this.graph.getEventTolerance() ||
dy > this.graph.getEventTolerance()
) {
this.shape = this.createShape(); this.shape = this.createShape();
if (this.edgeState != null) { if (this.edgeState != null) {
@ -1487,28 +1479,33 @@ class ConnectionHandler extends EventSource {
* *
* Updates <edgeState>. * Updates <edgeState>.
*/ */
updateEdgeState(current: CellState, constraint: CellState): void { updateEdgeState(current: Point, constraint: ConnectionConstraint) {
if (!this.edgeState) return;
// TODO: Use generic method for writing constraint to style // TODO: Use generic method for writing constraint to style
if (this.sourceConstraint != null && this.sourceConstraint.point != null) { if (this.sourceConstraint && this.sourceConstraint.point) {
this.edgeState.style.exitX = this.sourceConstraint.point.x; this.edgeState.style.exitX = this.sourceConstraint.point.x;
this.edgeState.style.exitY = this.sourceConstraint.point.y; this.edgeState.style.exitY = this.sourceConstraint.point.y;
} }
if (constraint != null && constraint.point != null) { if (constraint && constraint.point) {
this.edgeState.style.entryX = constraint.point.x; this.edgeState.style.entryX = constraint.point.x;
this.edgeState.style.entryY = constraint.point.y; this.edgeState.style.entryY = constraint.point.y;
} else { } else {
delete this.edgeState.style.entryX; this.edgeState.style.entryX = 0;
delete this.edgeState.style.entryY; this.edgeState.style.entryY = 0;
} }
this.edgeState.absolutePoints = [null, this.currentState != null ? null : current]; this.edgeState.absolutePoints = [null, this.currentState != null ? null : current];
this.graph.view.updateFixedTerminalPoint(
this.edgeState, if (this.sourceConstraint) {
this.previous, this.graph.view.updateFixedTerminalPoint(
true, this.edgeState,
this.sourceConstraint this.previous,
); true,
this.sourceConstraint
);
}
if (this.currentState != null) { if (this.currentState != null) {
if (constraint == null) { if (constraint == null) {
@ -1683,7 +1680,7 @@ class ConnectionHandler extends EventSource {
const addPoint = const addPoint =
this.waypoints != null || this.waypoints != null ||
(this.mouseDownCounter > 1 && (this.mouseDownCounter > 1 &&
(dx > this.graph.tolerance || dy > this.graph.tolerance)); (dx > this.graph.getEventTolerance() || dy > this.graph.getEventTolerance()));
if (addPoint) { if (addPoint) {
if (this.waypoints == null) { if (this.waypoints == null) {
@ -1691,7 +1688,7 @@ class ConnectionHandler extends EventSource {
} }
const { scale } = this.graph.view; const { scale } = this.graph.view;
point = new point( point = new Point(
this.graph.snap(me.getGraphX() / scale) * scale, this.graph.snap(me.getGraphX() / scale) * scale,
this.graph.snap(me.getGraphY() / scale) * scale this.graph.snap(me.getGraphY() / scale) * scale
); );
@ -1823,9 +1820,9 @@ class ConnectionHandler extends EventSource {
* Redraws the preview edge using the color and width returned by * Redraws the preview edge using the color and width returned by
* <getEdgeColor> and <getEdgeWidth>. * <getEdgeColor> and <getEdgeWidth>.
*/ */
drawPreview(): void { drawPreview() {
this.updatePreview(this.error == null); this.updatePreview(this.error === null);
this.shape.redraw(); if (this.shape) this.shape.redraw();
} }
/** /**
@ -1839,9 +1836,11 @@ class ConnectionHandler extends EventSource {
* valid - Boolean indicating if the color for a valid edge should be * valid - Boolean indicating if the color for a valid edge should be
* returned. * returned.
*/ */
updatePreview(valid: boolean): void { updatePreview(valid: boolean) {
this.shape.strokeWidth = this.getEdgeWidth(valid); if (this.shape) {
this.shape.stroke = this.getEdgeColor(valid); this.shape.strokeWidth = this.getEdgeWidth(valid);
this.shape.stroke = this.getEdgeColor(valid);
}
} }
/** /**
@ -1855,7 +1854,7 @@ class ConnectionHandler extends EventSource {
* valid - Boolean indicating if the color for a valid edge should be * valid - Boolean indicating if the color for a valid edge should be
* returned. * returned.
*/ */
getEdgeColor(valid: boolean): string { getEdgeColor(valid: boolean) {
return valid ? VALID_COLOR : INVALID_COLOR; return valid ? VALID_COLOR : INVALID_COLOR;
} }
@ -1889,7 +1888,7 @@ class ConnectionHandler extends EventSource {
* released. * released.
*/ */
connect(source: Cell, target: Cell, evt: MouseEvent, dropTarget: Cell): void { connect(source: Cell, target: Cell, evt: MouseEvent, dropTarget: Cell): void {
if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges) { if (target != null || this.isCreateTarget(evt) || this.graph.isAllowDanglingEdges()) {
// Uses the common parent of source and target or // Uses the common parent of source and target or
// the default parent to insert the edge // the default parent to insert the edge
const model = this.graph.getModel(); const model = this.graph.getModel();
@ -2068,7 +2067,7 @@ class ConnectionHandler extends EventSource {
* Selects the given edge after adding a new connection. The target argument * Selects the given edge after adding a new connection. The target argument
* contains the target vertex if one has been inserted. * contains the target vertex if one has been inserted.
*/ */
selectCells(edge: Cell, target: Cell): void { selectCells(edge: Cell, target: Cell) {
this.graph.setSelectionCell(edge); this.graph.setSelectionCell(edge);
} }
@ -2087,7 +2086,7 @@ class ConnectionHandler extends EventSource {
target: Cell, target: Cell,
style: string style: string
): Cell { ): Cell {
if (this.factoryMethod == null) { if (!this.factoryMethod) {
return this.graph.insertEdge(parent, id, value, source, target, style); return this.graph.insertEdge(parent, id, value, source, target, style);
} }
let edge = this.createEdge(value, source, target, style); let edge = this.createEdge(value, source, target, style);
@ -2160,9 +2159,9 @@ class ConnectionHandler extends EventSource {
* Returns the tolerance for aligning new targets to sources. This returns the grid size / 2. * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2.
*/ */
getAlignmentTolerance(evt: MouseEvent): number { getAlignmentTolerance(evt: MouseEvent): number {
return this.graph.grid.isGridEnabled() return this.graph.isGridEnabled()
? this.graph.grid.gridSize / 2 ? this.graph.getGridSize() / 2
: this.graph.grid.tolerance; : this.graph.getSnapTolerance();
} }
/** /**

View File

@ -4,7 +4,12 @@ import InternalMouseEvent from '../event/InternalMouseEvent';
import ConnectionConstraint from './ConnectionConstraint'; import ConnectionConstraint from './ConnectionConstraint';
import Rectangle from '../geometry/Rectangle'; import Rectangle from '../geometry/Rectangle';
import { DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_WEST } from '../../util/Constants'; import { DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_WEST } from '../../util/Constants';
import utils, { getRotatedPoint, getValue, toRadians } from '../../util/Utils'; import utils, {
autoImplement,
getRotatedPoint,
getValue,
toRadians,
} from '../../util/Utils';
import Cell from '../cell/datatypes/Cell'; import Cell from '../cell/datatypes/Cell';
import CellArray from '../cell/datatypes/CellArray'; import CellArray from '../cell/datatypes/CellArray';
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
@ -13,28 +18,53 @@ import Dictionary from '../../util/Dictionary';
import Geometry from '../geometry/Geometry'; import Geometry from '../geometry/Geometry';
import Graph from '../Graph'; import Graph from '../Graph';
import ConnectionHandler from './ConnectionHandler'; import ConnectionHandler from './ConnectionHandler';
import GraphCells from '../cell/GraphCells';
import GraphPorts from '../ports/GraphPorts';
import GraphEdge from '../cell/edge/GraphEdge';
class GraphConnections { type PartialGraph = Pick<Graph, 'getView' | 'getModel' | 'fireEvent'>;
constructor(graph: Graph) { type PartialCells = Pick<GraphCells, 'setCellStyles' | 'isCellLocked'>;
this.graph = graph; type PartialPorts = Pick<GraphPorts, 'isPortsEnabled' | 'isPort' | 'getTerminalForPort'>;
} type PartialEdge = Pick<
GraphEdge,
graph: Graph; | 'isResetEdgesOnConnect'
| 'resetEdge'
| 'getEdges'
| 'isAllowDanglingEdges'
| 'isConnectableEdges'
>;
type PartialClass = PartialGraph & PartialCells & PartialPorts & PartialEdge;
// @ts-ignore recursive reference error
class GraphConnections extends autoImplement<PartialClass>() {
/***************************************************************************** /*****************************************************************************
* Group: Cell connecting and connection constraints * Group: Cell connecting and connection constraints
*****************************************************************************/ *****************************************************************************/
connectionHandler: ConnectionHandler | null = null;
getConnectionHandler() {
return this.connectionHandler;
}
setConnectionHandler(connectionHandler: ConnectionHandler) {
this.connectionHandler = connectionHandler;
}
constrainChildren = false;
constrainRelativeChildren = false;
disconnectOnMove = false;
cellsDisconnectable = true;
/** /**
* Returns the constraint used to connect to the outline of the given state. * Returns the constraint used to connect to the outline of the given state.
*/ */
getOutlineConstraint( getOutlineConstraint(point: Point, terminalState: CellState, me: InternalMouseEvent) {
point: Point, if (terminalState.shape) {
terminalState: CellState, const bounds = <Rectangle>this.getView().getPerimeterBounds(terminalState);
me: InternalMouseEvent
): ConnectionConstraint | null {
if (terminalState.shape != null) {
const bounds = <Rectangle>this.graph.view.getPerimeterBounds(terminalState);
const direction = terminalState.style.direction; const direction = terminalState.style.direction;
if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) { if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) {
@ -51,7 +81,7 @@ class GraphConnections {
const cos = Math.cos(-alpha); const cos = Math.cos(-alpha);
const sin = Math.sin(-alpha); const sin = Math.sin(-alpha);
const ct = new point(bounds.getCenterX(), bounds.getCenterY()); const ct = new Point(bounds.getCenterX(), bounds.getCenterY());
point = getRotatedPoint(point, cos, sin, ct); point = getRotatedPoint(point, cos, sin, ct);
} }
@ -61,16 +91,10 @@ class GraphConnections {
let dy = 0; let dy = 0;
// LATER: Add flipping support for image shapes // LATER: Add flipping support for image shapes
if ((<Cell>terminalState.cell).isVertex()) { if (terminalState.cell.isVertex()) {
let flipH = terminalState.style.flipH; let flipH = terminalState.style.flipH;
let flipV = terminalState.style.flipV; let flipV = terminalState.style.flipV;
// Legacy support for stencilFlipH/V
if (terminalState.shape != null && terminalState.shape.stencil != null) {
flipH = getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;
flipV = getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;
}
if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) { if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) {
const tmp = flipH; const tmp = flipH;
flipH = flipV; flipH = flipV;
@ -88,7 +112,7 @@ class GraphConnections {
} }
} }
point = new point( point = new Point(
(point.x - bounds.x) * sx - dx + bounds.x, (point.x - bounds.x) * sx - dx + bounds.x,
(point.y - bounds.y) * sy - dy + bounds.y (point.y - bounds.y) * sy - dy + bounds.y
); );
@ -102,7 +126,7 @@ class GraphConnections {
? 0 ? 0
: Math.round(((point.y - bounds.y) * 1000) / bounds.height) / 1000; : Math.round(((point.y - bounds.y) * 1000) / bounds.height) / 1000;
return new ConnectionConstraint(new point(x, y), false); return new ConnectionConstraint(new Point(x, y), false);
} }
return null; return null;
} }
@ -115,11 +139,8 @@ class GraphConnections {
* @param terminal {@link mxCellState} that represents the terminal. * @param terminal {@link mxCellState} that represents the terminal.
* @param source Boolean that specifies if the terminal is the source or target. * @param source Boolean that specifies if the terminal is the source or target.
*/ */
getAllConnectionConstraints( getAllConnectionConstraints(terminal: CellState | null, source: boolean) {
terminal: CellState, if (terminal && terminal.shape && terminal.shape.stencil) {
source: boolean
): ConnectionConstraint[] | null {
if (terminal != null && terminal.shape != null && terminal.shape.stencil != null) {
return terminal.shape.stencil.constraints; return terminal.shape.stencil.constraints;
} }
return null; return null;
@ -135,19 +156,18 @@ class GraphConnections {
*/ */
getConnectionConstraint( getConnectionConstraint(
edge: CellState, edge: CellState,
terminal: CellState | null = null, terminal: CellState | null,
source: boolean = false source: boolean = false
): ConnectionConstraint { ) {
let point = null; let point: Point | null = null;
// @ts-ignore
const x = <string>edge.style[source ? 'exitX' : 'entryX'];
if (x != null) { const x = edge.style[source ? 'exitX' : 'entryX'];
// @ts-ignore
const y = <string>edge.style[source ? 'exitY' : 'entryY'];
if (y != null) { if (x !== undefined) {
point = new point(parseFloat(x), parseFloat(y)); const y = edge.style[source ? 'exitY' : 'entryY'];
if (y !== undefined) {
point = new Point(x, y);
} }
} }
@ -155,14 +175,12 @@ class GraphConnections {
let dx = 0; let dx = 0;
let dy = 0; let dy = 0;
if (point != null) { if (point) {
perimeter = getValue(edge.style, source ? 'exitPerimeter' : 'entryPerimeter', true); perimeter = edge.style[source ? 'exitPerimeter' : 'entryPerimeter'];
// Add entry/exit offset // Add entry/exit offset
// @ts-ignore dx = edge.style[source ? 'exitDx' : 'entryDx'];
dx = parseFloat(<string>edge.style[source ? 'exitDx' : 'entryDx']); dy = edge.style[source ? 'exitDy' : 'entryDy'];
// @ts-ignore
dy = parseFloat(<string>edge.style[source ? 'exitDy' : 'entryDy']);
dx = Number.isFinite(dx) ? dx : 0; dx = Number.isFinite(dx) ? dx : 0;
dy = Number.isFinite(dy) ? dy : 0; dy = Number.isFinite(dy) ? dy : 0;
@ -187,12 +205,12 @@ class GraphConnections {
terminal: Cell, terminal: Cell,
source: boolean = false, source: boolean = false,
constraint: ConnectionConstraint | null = null constraint: ConnectionConstraint | null = null
): void { ) {
if (constraint != null) { if (constraint) {
this.getModel().beginUpdate(); this.getModel().beginUpdate();
try { try {
if (constraint == null || constraint.point == null) { if (!constraint || !constraint.point) {
this.setCellStyles(source ? 'exitX' : 'entryX', null, new CellArray(edge)); this.setCellStyles(source ? 'exitX' : 'entryX', null, new CellArray(edge));
this.setCellStyles(source ? 'exitY' : 'entryY', null, new CellArray(edge)); this.setCellStyles(source ? 'exitY' : 'entryY', null, new CellArray(edge));
this.setCellStyles(source ? 'exitDx' : 'entryDx', null, new CellArray(edge)); this.setCellStyles(source ? 'exitDx' : 'entryDx', null, new CellArray(edge));
@ -202,7 +220,7 @@ class GraphConnections {
null, null,
new CellArray(edge) new CellArray(edge)
); );
} else if (constraint.point != null) { } else if (constraint.point) {
this.setCellStyles( this.setCellStyles(
source ? 'exitX' : 'entryX', source ? 'exitX' : 'entryX',
constraint.point.x, constraint.point.x,
@ -257,17 +275,17 @@ class GraphConnections {
vertex: CellState, vertex: CellState,
constraint: ConnectionConstraint, constraint: ConnectionConstraint,
round: boolean = true round: boolean = true
): Point { ) {
let point = null; let point: Point | null = null;
if (vertex != null && constraint.point != null) { if (constraint.point) {
const bounds = <Rectangle>this.graph.view.getPerimeterBounds(vertex); const bounds = <Rectangle>this.getView().getPerimeterBounds(vertex);
const cx = new point(bounds.getCenterX(), bounds.getCenterY()); const cx = new Point(bounds.getCenterX(), bounds.getCenterY());
const direction = vertex.style.direction; const direction = vertex.style.direction;
let r1 = 0; let r1 = 0;
// Bounds need to be rotated by 90 degrees for further computation // Bounds need to be rotated by 90 degrees for further computation
if (direction != null && getValue(vertex.style, 'anchorPointDirection', 1) == 1) { if (vertex.style.anchorPointDirection) {
if (direction === DIRECTION_NORTH) { if (direction === DIRECTION_NORTH) {
r1 += 270; r1 += 270;
} else if (direction === DIRECTION_WEST) { } else if (direction === DIRECTION_WEST) {
@ -282,8 +300,8 @@ class GraphConnections {
} }
} }
const { scale } = this.view; const { scale } = this.getView();
point = new point( point = new Point(
bounds.x + constraint.point.x * bounds.width + <number>constraint.dx * scale, bounds.x + constraint.point.x * bounds.width + <number>constraint.dx * scale,
bounds.y + constraint.point.y * bounds.height + <number>constraint.dy * scale bounds.y + constraint.point.y * bounds.height + <number>constraint.dy * scale
); );
@ -308,19 +326,13 @@ class GraphConnections {
point = getRotatedPoint(point, cos, sin, cx); point = getRotatedPoint(point, cos, sin, cx);
} }
point = this.graph.view.getPerimeterPoint(vertex, point, false); point = this.getView().getPerimeterPoint(vertex, point, false);
} else { } else {
r2 += r1; r2 += r1;
if ((<Cell>vertex.cell).isVertex()) { if (vertex.cell.isVertex()) {
let flipH = vertex.style.flipH == 1; let flipH = vertex.style.flipH;
let flipV = vertex.style.flipV == 1; let flipV = vertex.style.flipV;
// Legacy support for stencilFlipH/V
if (vertex.shape != null && vertex.shape.stencil != null) {
flipH = getValue(vertex.style, 'stencilFlipH', 0) == 1 || flipH;
flipV = getValue(vertex.style, 'stencilFlipV', 0) == 1 || flipV;
}
if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) { if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) {
const temp = flipH; const temp = flipH;
@ -339,7 +351,7 @@ class GraphConnections {
} }
// Generic rotation after projection on perimeter // Generic rotation after projection on perimeter
if (r2 !== 0 && point != null) { if (r2 !== 0 && point) {
const rad = toRadians(r2); const rad = toRadians(r2);
const cos = Math.cos(rad); const cos = Math.cos(rad);
const sin = Math.sin(rad); const sin = Math.sin(rad);
@ -348,7 +360,7 @@ class GraphConnections {
} }
} }
if (round && point != null) { if (round && point) {
point.x = Math.round(point.x); point.x = Math.round(point.x);
point.y = Math.round(point.y); point.y = Math.round(point.y);
} }
@ -371,7 +383,7 @@ class GraphConnections {
terminal: Cell, terminal: Cell,
source: boolean = false, source: boolean = false,
constraint: ConnectionConstraint | null = null constraint: ConnectionConstraint | null = null
): Cell { ) {
this.getModel().beginUpdate(); this.getModel().beginUpdate();
try { try {
const previous = edge.getTerminal(source); const previous = edge.getTerminal(source);
@ -410,52 +422,50 @@ class GraphConnections {
terminal: Cell, terminal: Cell,
source: boolean = false, source: boolean = false,
constraint: ConnectionConstraint | null = null constraint: ConnectionConstraint | null = null
): void { ) {
if (edge != null) { this.getModel().beginUpdate();
this.getModel().beginUpdate(); try {
try { const previous = edge.getTerminal(source);
const previous = edge.getTerminal(source);
// Updates the constraint // Updates the constraint
this.setConnectionConstraint(edge, terminal, source, constraint); this.setConnectionConstraint(edge, terminal, source, constraint);
// Checks if the new terminal is a port, uses the ID of the port in the // Checks if the new terminal is a port, uses the ID of the port in the
// style and the parent of the port as the actual terminal of the edge. // style and the parent of the port as the actual terminal of the edge.
if (this.isPortsEnabled()) { if (this.isPortsEnabled()) {
let id = null; let id = null;
if (this.isPort(terminal)) { if (this.isPort(terminal)) {
id = terminal.getId(); id = terminal.getId();
terminal = <Cell>this.getTerminalForPort(terminal, source); terminal = <Cell>this.getTerminalForPort(terminal, source);
}
// Sets or resets all previous information for connecting to a child port
const key = source ? 'sourcePort' : 'targetPort';
this.setCellStyles(key, id, new CellArray(edge));
} }
this.getModel().setTerminal(edge, terminal, source); // Sets or resets all previous information for connecting to a child port
const key = source ? 'sourcePort' : 'targetPort';
if (this.resetEdgesOnConnect) { this.setCellStyles(key, id, new CellArray(edge));
this.resetEdge(edge);
}
this.fireEvent(
new EventObject(
InternalEvent.CELL_CONNECTED,
'edge',
edge,
'terminal',
terminal,
'source',
source,
'previous',
previous
)
);
} finally {
this.getModel().endUpdate();
} }
this.getModel().setTerminal(edge, terminal, source);
if (this.isResetEdgesOnConnect()) {
this.resetEdge(edge);
}
this.fireEvent(
new EventObject(
InternalEvent.CELL_CONNECTED,
'edge',
edge,
'terminal',
terminal,
'source',
source,
'previous',
previous
)
);
} finally {
this.getModel().endUpdate();
} }
} }
@ -465,84 +475,77 @@ class GraphConnections {
* *
* @param cells Array of {@link Cell} to be disconnected. * @param cells Array of {@link Cell} to be disconnected.
*/ */
disconnectGraph(cells: CellArray | null) { disconnectGraph(cells: CellArray) {
if (cells != null) { this.getModel().beginUpdate();
this.getModel().beginUpdate(); try {
try { const { scale, translate: tr } = this.getView();
const { scale } = this.view;
const tr = this.graph.view.translate;
// Fast lookup for finding cells in array // Fast lookup for finding cells in array
const dict = new Dictionary(); const dict = new Dictionary<Cell, boolean>();
for (let i = 0; i < cells.length; i += 1) { for (let i = 0; i < cells.length; i += 1) {
dict.put(cells[i], true); dict.put(cells[i], true);
} }
for (const cell of cells) { for (const cell of cells) {
if (cell.isEdge()) { if (cell.isEdge()) {
let geo = <Geometry>cell.getGeometry(); let geo = cell.getGeometry();
if (geo != null) { if (geo) {
const state = this.graph.view.getState(cell); const state = this.getView().getState(cell);
const pstate = <CellState>this.graph.view.getState(cell.getParent()); const pstate = <CellState>this.getView().getState(cell.getParent());
if (state != null && pstate != null) { if (state && pstate) {
geo = geo.clone(); geo = geo.clone();
// @ts-ignore const dx = -pstate.origin.x;
const dx = -pstate.origin.x; const dy = -pstate.origin.y;
// @ts-ignore const pts = state.absolutePoints;
const dy = -pstate.origin.y;
const pts = <Point[]>state.absolutePoints;
let src = cell.getTerminal(true); let src = cell.getTerminal(true);
if (src != null && this.isCellDisconnectable(cell, src, true)) { if (src && this.isCellDisconnectable(cell, src, true)) {
while (src != null && !dict.get(src)) { while (src && !dict.get(src)) {
src = src.getParent(); src = src.getParent();
}
if (src == null) {
geo.setTerminalPoint(
new Point(
pts[0].x / scale - tr.x + dx,
pts[0].y / scale - tr.y + dy
),
true
);
this.getModel().setTerminal(cell, null, true);
}
} }
let trg = cell.getTerminal(false); if (!src && pts[0]) {
geo.setTerminalPoint(
new Point(pts[0].x / scale - tr.x + dx, pts[0].y / scale - tr.y + dy),
true
);
this.getModel().setTerminal(cell, null, true);
}
}
if (trg != null && this.isCellDisconnectable(cell, trg, false)) { let trg = cell.getTerminal(false);
while (trg != null && !dict.get(trg)) {
trg = trg.getParent();
}
if (trg == null) { if (trg && this.isCellDisconnectable(cell, trg, false)) {
const n = pts.length - 1; while (trg && !dict.get(trg)) {
trg = trg.getParent();
}
if (!trg) {
const n = pts.length - 1;
const p = pts[n];
if (p) {
geo.setTerminalPoint( geo.setTerminalPoint(
new Point( new Point(p.x / scale - tr.x + dx, p.y / scale - tr.y + dy),
<number>(<Point>pts[n]).x / scale - tr.x + dx,
<number>(<Point>pts[n]).y / scale - tr.y + dy
),
false false
); );
this.getModel().setTerminal(cell, null, false); this.getModel().setTerminal(cell, null, false);
} }
} }
this.getModel().setGeometry(cell, geo);
} }
this.getModel().setGeometry(cell, geo);
} }
} }
} }
} finally {
this.getModel().endUpdate();
} }
} finally {
this.getModel().endUpdate();
} }
} }
@ -553,7 +556,7 @@ class GraphConnections {
* @param parent Optional parent of the opposite end for a connection to be * @param parent Optional parent of the opposite end for a connection to be
* returned. * returned.
*/ */
getConnections(cell: Cell, parent: Cell | null = null): CellArray { getConnections(cell: Cell, parent: Cell | null = null) {
return this.getEdges(cell, parent, true, true, false); return this.getEdges(cell, parent, true, true, false);
} }
@ -565,7 +568,7 @@ class GraphConnections {
* *
* @param cell {@link mxCell} that should be constrained. * @param cell {@link mxCell} that should be constrained.
*/ */
isConstrainChild(cell: Cell): boolean { isConstrainChild(cell: Cell) {
return ( return (
this.isConstrainChildren() && this.isConstrainChildren() &&
!!cell.getParent() && !!cell.getParent() &&
@ -576,28 +579,28 @@ class GraphConnections {
/** /**
* Returns {@link constrainChildren}. * Returns {@link constrainChildren}.
*/ */
isConstrainChildren(): boolean { isConstrainChildren() {
return this.constrainChildren; return this.constrainChildren;
} }
/** /**
* Sets {@link constrainChildren}. * Sets {@link constrainChildren}.
*/ */
setConstrainChildren(value: boolean): void { setConstrainChildren(value: boolean) {
this.constrainChildren = value; this.constrainChildren = value;
} }
/** /**
* Returns {@link constrainRelativeChildren}. * Returns {@link constrainRelativeChildren}.
*/ */
isConstrainRelativeChildren(): boolean { isConstrainRelativeChildren() {
return this.constrainRelativeChildren; return this.constrainRelativeChildren;
} }
/** /**
* Sets {@link constrainRelativeChildren}. * Sets {@link constrainRelativeChildren}.
*/ */
setConstrainRelativeChildren(value: boolean): void { setConstrainRelativeChildren(value: boolean) {
this.constrainRelativeChildren = value; this.constrainRelativeChildren = value;
} }
@ -608,7 +611,7 @@ class GraphConnections {
/** /**
* Returns {@link disconnectOnMove} as a boolean. * Returns {@link disconnectOnMove} as a boolean.
*/ */
isDisconnectOnMove(): boolean { isDisconnectOnMove() {
return this.disconnectOnMove; return this.disconnectOnMove;
} }
@ -619,7 +622,7 @@ class GraphConnections {
* @param value Boolean indicating if edges should be disconnected * @param value Boolean indicating if edges should be disconnected
* when moved. * when moved.
*/ */
setDisconnectOnMove(value: boolean): void { setDisconnectOnMove(value: boolean) {
this.disconnectOnMove = value; this.disconnectOnMove = value;
} }
@ -637,21 +640,21 @@ class GraphConnections {
cell: Cell, cell: Cell,
terminal: Cell | null = null, terminal: Cell | null = null,
source: boolean = false source: boolean = false
): boolean { ) {
return this.isCellsDisconnectable() && !this.isCellLocked(cell); return this.isCellsDisconnectable() && !this.isCellLocked(cell);
} }
/** /**
* Returns {@link cellsDisconnectable}. * Returns {@link cellsDisconnectable}.
*/ */
isCellsDisconnectable(): boolean { isCellsDisconnectable() {
return this.cellsDisconnectable; return this.cellsDisconnectable;
} }
/** /**
* Sets {@link cellsDisconnectable}. * Sets {@link cellsDisconnectable}.
*/ */
setCellsDisconnectable(value: boolean): void { setCellsDisconnectable(value: boolean) {
this.cellsDisconnectable = value; this.cellsDisconnectable = value;
} }
@ -662,10 +665,12 @@ class GraphConnections {
* *
* @param cell {@link mxCell} that represents a possible source or null. * @param cell {@link mxCell} that represents a possible source or null.
*/ */
isValidSource(cell: Cell | null): boolean { isValidSource(cell: Cell | null) {
return ( return (
(cell == null && this.allowDanglingEdges) || (cell == null && this.isAllowDanglingEdges()) ||
(cell != null && (!cell.isEdge() || this.connectableEdges) && cell.isConnectable()) (cell != null &&
(!cell.isEdge() || this.isConnectableEdges()) &&
cell.isConnectable())
); );
} }
@ -699,14 +704,14 @@ class GraphConnections {
* *
* @param connectable Boolean indicating if new connections should be allowed. * @param connectable Boolean indicating if new connections should be allowed.
*/ */
setConnectable(connectable: boolean): void { setConnectable(connectable: boolean) {
(<ConnectionHandler>this.connectionHandler).setEnabled(connectable); (<ConnectionHandler>this.connectionHandler).setEnabled(connectable);
} }
/** /**
* Returns true if the {@link connectionHandler} is enabled. * Returns true if the {@link connectionHandler} is enabled.
*/ */
isConnectable(): boolean { isConnectable() {
return (<ConnectionHandler>this.connectionHandler).isEnabled(); return (<ConnectionHandler>this.connectionHandler).isEnabled();
} }
} }

View File

@ -22,6 +22,7 @@ type PartialCells = Pick<
>; >;
type PartialClass = PartialGraph & PartialSelection & PartialEvents & PartialCells; type PartialClass = PartialGraph & PartialSelection & PartialEvents & PartialCells;
// @ts-ignore recursive reference error
class GraphEditing extends autoImplement<PartialClass>() { class GraphEditing extends autoImplement<PartialClass>() {
/** /**
* Specifies the return value for {@link isCellEditable}. * Specifies the return value for {@link isCellEditable}.

View File

@ -7,7 +7,7 @@
import EventObject from './EventObject'; import EventObject from './EventObject';
type EventListener = { type EventListenerObject = {
funct: Function; funct: Function;
name: string; name: string;
}; };
@ -46,7 +46,7 @@ class EventSource {
* contains the event name followed by the respective listener for each * contains the event name followed by the respective listener for each
* registered listener. * registered listener.
*/ */
eventListeners: EventListener[] = []; eventListeners: EventListenerObject[] = [];
/** /**
* Variable: eventsEnabled * Variable: eventsEnabled
@ -60,7 +60,7 @@ class EventSource {
* *
* Optional source for events. Default is null. * Optional source for events. Default is null.
*/ */
eventSource: EventSource | null; eventSource: EventSource | EventTarget | null;
/** /**
* Function: isEventsEnabled * Function: isEventsEnabled
@ -94,7 +94,7 @@ class EventSource {
* *
* Sets <eventSource>. * Sets <eventSource>.
*/ */
setEventSource(value: EventSource | null) { setEventSource(value: EventSource | EventTarget | null) {
this.eventSource = value; this.eventSource = value;
} }
@ -106,7 +106,7 @@ class EventSource {
* *
* The parameters of the listener are the sender and an <mxEventObject>. * The parameters of the listener are the sender and an <mxEventObject>.
*/ */
addListener(name: string, funct: (...args: any[]) => any) { addListener(name: string, funct: Function) {
this.eventListeners.push({ name, funct }); this.eventListeners.push({ name, funct });
} }
@ -115,7 +115,7 @@ class EventSource {
* *
* Removes all occurrences of the given listener from <eventListeners>. * Removes all occurrences of the given listener from <eventListeners>.
*/ */
removeListener(funct: (...args: any[]) => any) { removeListener(funct: Function) {
let i = 0; let i = 0;
while (i < this.eventListeners.length) { while (i < this.eventListeners.length) {
@ -146,7 +146,7 @@ class EventSource {
* sender - Optional sender to be passed to the listener. Default value is * sender - Optional sender to be passed to the listener. Default value is
* the return value of <getEventSource>. * the return value of <getEventSource>.
*/ */
fireEvent(evt: EventObject, sender: any = null) { fireEvent(evt: EventObject, sender: EventSource | EventTarget | null = null) {
if (this.isEventsEnabled()) { if (this.isEventsEnabled()) {
if (!evt) { if (!evt) {
evt = new EventObject(''); evt = new EventObject('');

View File

@ -32,6 +32,7 @@ import type GraphCells from '../cell/GraphCells';
import type GraphSelection from '../selection/GraphSelection'; import type GraphSelection from '../selection/GraphSelection';
import GraphEditing from '../editing/GraphEditing'; import GraphEditing from '../editing/GraphEditing';
import GraphSnap from '../snap/GraphSnap'; import GraphSnap from '../snap/GraphSnap';
import { MouseEventListener } from '../../types';
type PartialGraph = Pick< type PartialGraph = Pick<
Graph, Graph,
@ -59,21 +60,15 @@ type PartialClass = PartialGraph &
PartialSnap & PartialSnap &
EventSource; EventSource;
type MouseListener = {
mouseDown: Function;
mouseMove: Function;
mouseUp: Function;
};
// @ts-ignore recursive reference error // @ts-ignore recursive reference error
class GraphEvents extends autoImplement<PartialClass>() { class GraphEvents extends autoImplement<PartialClass>() {
/** /**
* Holds the mouse event listeners. See {@link fireMouseEvent}. * Holds the mouse event listeners. See {@link fireMouseEvent}.
*/ */
mouseListeners: MouseListener[] = []; mouseListeners: MouseListenerSet[] = [];
// TODO: Document me! // TODO: Document me!
lastTouchEvent: InternalMouseEvent | null = null; lastTouchEvent: MouseEvent | null = null;
doubleClickCounter: number = 0; doubleClickCounter: number = 0;
lastTouchCell: Cell | null = null; lastTouchCell: Cell | null = null;
fireDoubleClick: boolean | null = null; fireDoubleClick: boolean | null = null;
@ -82,8 +77,8 @@ class GraphEvents extends autoImplement<PartialClass>() {
lastMouseY: number | null = null; lastMouseY: number | null = null;
isMouseTrigger: boolean | null = null; isMouseTrigger: boolean | null = null;
ignoreMouseEvents: boolean | null = null; ignoreMouseEvents: boolean | null = null;
mouseMoveRedirect: EventListener | null = null; mouseMoveRedirect: MouseEventListener | null = null;
mouseUpRedirect: EventListener | null = null; mouseUpRedirect: MouseEventListener | null = null;
lastEvent: any; // FIXME: Check if this can be more specific - DOM events or mxEventObjects! lastEvent: any; // FIXME: Check if this can be more specific - DOM events or mxEventObjects!
/** /**
@ -199,7 +194,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
*/ */
tolerance: number = 4; tolerance: number = 4;
getClickTolerance = () => this.tolerance; getEventTolerance = () => this.tolerance;
/***************************************************************************** /*****************************************************************************
* Group: Event processing * Group: Event processing
@ -210,7 +205,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* *
* @param evt Mouseevent that represents the keystroke. * @param evt Mouseevent that represents the keystroke.
*/ */
escape(evt: InternalMouseEvent): void { escape(evt: Event) {
this.fireEvent(new EventObject(InternalEvent.ESCAPE, 'event', evt)); this.fireEvent(new EventObject(InternalEvent.ESCAPE, 'event', evt));
} }
@ -355,7 +350,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param evt Mouseevent that represents the doubleclick. * @param evt Mouseevent that represents the doubleclick.
* @param cell Optional {@link Cell} under the mousepointer. * @param cell Optional {@link Cell} under the mousepointer.
*/ */
dblClick(evt: MouseEvent, cell?: Cell): void { dblClick(evt: MouseEvent, cell?: Cell) {
const mxe = new EventObject(InternalEvent.DOUBLE_CLICK, { event: evt, cell: cell }); const mxe = new EventObject(InternalEvent.DOUBLE_CLICK, { event: evt, cell: cell });
this.fireEvent(mxe); this.fireEvent(mxe);
@ -364,7 +359,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
this.isEnabled() && this.isEnabled() &&
!isConsumed(evt) && !isConsumed(evt) &&
!mxe.isConsumed() && !mxe.isConsumed() &&
cell != null && cell &&
this.isCellEditable(cell) && this.isCellEditable(cell) &&
!this.isEditing(cell) !this.isEditing(cell)
) { ) {
@ -379,7 +374,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param me {@link mxMouseEvent} that represents the touch event. * @param me {@link mxMouseEvent} that represents the touch event.
* @param state Optional {@link CellState} that is associated with the event. * @param state Optional {@link CellState} that is associated with the event.
*/ */
tapAndHold(me: InternalMouseEvent): void { tapAndHold(me: InternalMouseEvent) {
const evt = me.getEvent(); const evt = me.getEvent();
const mxe = new EventObject( const mxe = new EventObject(
InternalEvent.TAP_AND_HOLD, InternalEvent.TAP_AND_HOLD,
@ -434,8 +429,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* *
* @param listener Listener to be added to the graph event listeners. * @param listener Listener to be added to the graph event listeners.
*/ */
// addMouseListener(listener: { [key: string]: (sender: mxEventSource, me: mxMouseEvent) => void }): void; addMouseListener(listener: MouseListenerSet) {
addMouseListener(listener: MouseListener): void {
this.mouseListeners.push(listener); this.mouseListeners.push(listener);
} }
@ -444,8 +438,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* *
* @param listener Listener to be removed from the graph event listeners. * @param listener Listener to be removed from the graph event listeners.
*/ */
// removeMouseListener(listener: { [key: string]: (sender: mxEventSource, me: mxMouseEvent) => void }): void; removeMouseListener(listener: MouseListenerSet) {
removeMouseListener(listener: MouseListener) {
for (let i = 0; i < this.mouseListeners.length; i += 1) { for (let i = 0; i < this.mouseListeners.length; i += 1) {
if (this.mouseListeners[i] === listener) { if (this.mouseListeners[i] === listener) {
this.mouseListeners.splice(i, 1); this.mouseListeners.splice(i, 1);
@ -461,30 +454,28 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param me {@link mxMouseEvent} to be updated. * @param me {@link mxMouseEvent} to be updated.
* @param evtName Name of the mouse event. * @param evtName Name of the mouse event.
*/ */
updateMouseEvent(me: InternalMouseEvent, evtName: string): InternalMouseEvent { updateMouseEvent(me: InternalMouseEvent, evtName: string) {
if (me.graphX == null || me.graphY == null) { const pt = convertPoint(this.getContainer(), me.getX(), me.getY());
const pt = convertPoint(this.getContainer(), me.getX(), me.getY());
me.graphX = pt.x - this.panning.panDx; me.graphX = pt.x - this.panning.panDx;
me.graphY = pt.y - this.panning.panDy; me.graphY = pt.y - this.panning.panDy;
// Searches for rectangles using method if native hit detection is disabled on shape // Searches for rectangles using method if native hit detection is disabled on shape
if ( if (
me.getCell() == null && me.getCell() == null &&
this.isMouseDown && this.isMouseDown &&
evtName === InternalEvent.MOUSE_MOVE evtName === InternalEvent.MOUSE_MOVE
) { ) {
me.state = this.getView().getState( me.state = this.getView().getState(
this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => { this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => {
return ( return (
state.shape == null || state.shape == null ||
state.shape.paintBackground !== this.paintBackground || state.shape.paintBackground !== this.paintBackground ||
getValue(state.style, 'pointerEvents', '1') == '1' || getValue(state.style, 'pointerEvents', '1') == '1' ||
(state.shape.fill != null && state.shape.fill !== NONE) (state.shape.fill != null && state.shape.fill !== NONE)
); );
}) })
); );
}
} }
return me; return me;
@ -493,8 +484,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
/** /**
* Returns the state for the given touch event. * Returns the state for the given touch event.
*/ */
// getStateForTouchEvent(evt: MouseEvent | TouchEvent): mxCellState; getStateForTouchEvent(evt: MouseEvent) {
getStateForTouchEvent(evt: InternalMouseEvent) {
const x = getClientX(evt); const x = getClientX(evt);
const y = getClientY(evt); const y = getClientY(evt);
@ -508,8 +498,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
/** /**
* Returns true if the event should be ignored in {@link fireMouseEvent}. * Returns true if the event should be ignored in {@link fireMouseEvent}.
*/ */
// isEventIgnored(evtName: string, me: mxMouseEvent, sender: mxEventSource): boolean; isEventIgnored(evtName: string, me: InternalMouseEvent, sender: EventSource) {
isEventIgnored(evtName: string, me: InternalMouseEvent, sender: any): boolean {
const mouseEvent = isMouseEvent(me.getEvent()); const mouseEvent = isMouseEvent(me.getEvent());
let result = false; let result = false;
@ -545,13 +534,13 @@ class GraphEvents extends autoImplement<PartialClass>() {
) { ) {
this.setEventSource(me.getSource()); this.setEventSource(me.getSource());
this.mouseMoveRedirect = (evt: InternalMouseEvent) => { this.mouseMoveRedirect = (evt: MouseEvent) => {
this.fireMouseEvent( this.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt, this.getStateForTouchEvent(evt)) new InternalMouseEvent(evt, this.getStateForTouchEvent(evt))
); );
}; };
this.mouseUpRedirect = (evt: InternalMouseEvent) => { this.mouseUpRedirect = (evt: MouseEvent) => {
this.fireMouseEvent( this.fireMouseEvent(
InternalEvent.MOUSE_UP, InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt, this.getStateForTouchEvent(evt)) new InternalMouseEvent(evt, this.getStateForTouchEvent(evt))
@ -675,11 +664,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param me {@link mxMouseEvent} to be fired. * @param me {@link mxMouseEvent} to be fired.
* @param sender Optional sender argument. Default is `this`. * @param sender Optional sender argument. Default is `this`.
*/ */
fireMouseEvent( fireMouseEvent(evtName: string, me: InternalMouseEvent, sender: EventSource = this) {
evtName: string,
me: InternalMouseEvent,
sender: EventSource = this
): void {
if (this.isEventSourceIgnored(evtName, me)) { if (this.isEventSourceIgnored(evtName, me)) {
if (this.tooltipHandler != null) { if (this.tooltipHandler != null) {
this.tooltipHandler.hide(); this.tooltipHandler.hide();
@ -687,10 +672,6 @@ class GraphEvents extends autoImplement<PartialClass>() {
return; return;
} }
if (sender == null) {
sender = this;
}
// Updates the graph coordinates in the event // Updates the graph coordinates in the event
me = this.updateMouseEvent(me, evtName); me = this.updateMouseEvent(me, evtName);
@ -992,7 +973,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* Returns true if the given event is a clone event. This implementation * Returns true if the given event is a clone event. This implementation
* returns true if control is pressed. * returns true if control is pressed.
*/ */
isCloneEvent(evt: MouseEvent): boolean { isCloneEvent(evt: MouseEvent) {
return isControlDown(evt); return isControlDown(evt);
} }
@ -1001,7 +982,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* returns true the cell behind the selected cell will be selected. This * returns true the cell behind the selected cell will be selected. This
* implementation returns false; * implementation returns false;
*/ */
isTransparentClickEvent(evt: MouseEvent): boolean { isTransparentClickEvent(evt: MouseEvent) {
return false; return false;
} }
@ -1010,21 +991,21 @@ class GraphEvents extends autoImplement<PartialClass>() {
* returns true if the meta key (Cmd) is pressed on Macs or if control is * returns true if the meta key (Cmd) is pressed on Macs or if control is
* pressed on any other platform. * pressed on any other platform.
*/ */
isToggleEvent(evt: MouseEvent): boolean { isToggleEvent(evt: MouseEvent) {
return mxClient.IS_MAC ? isMetaDown(evt) : isControlDown(evt); return mxClient.IS_MAC ? isMetaDown(evt) : isControlDown(evt);
} }
/** /**
* Returns true if the given mouse event should be aligned to the grid. * Returns true if the given mouse event should be aligned to the grid.
*/ */
isGridEnabledEvent(evt: MouseEvent): boolean { isGridEnabledEvent(evt: MouseEvent) {
return evt != null && !isAltDown(evt); return !isAltDown(evt);
} }
/** /**
* Returns true if the given mouse event should be aligned to the grid. * Returns true if the given mouse event should be aligned to the grid.
*/ */
isConstrainedEvent(evt: MouseEvent): boolean { isConstrainedEvent(evt: MouseEvent) {
return isShiftDown(evt); return isShiftDown(evt);
} }
@ -1032,7 +1013,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* Returns true if the given mouse event should not allow any connections to be * Returns true if the given mouse event should not allow any connections to be
* made. This implementation returns false. * made. This implementation returns false.
*/ */
isIgnoreTerminalEvent(evt: MouseEvent): boolean { isIgnoreTerminalEvent(evt: MouseEvent) {
return false; return false;
} }
@ -1044,7 +1025,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param addOffset Optional boolean that specifies if the position should be * @param addOffset Optional boolean that specifies if the position should be
* offset by half of the {@link gridSize}. Default is `true`. * offset by half of the {@link gridSize}. Default is `true`.
*/ */
getPointForEvent(evt: InternalMouseEvent, addOffset: boolean = true): Point { getPointForEvent(evt: MouseEvent, addOffset: boolean = true) {
const p = convertPoint(this.getContainer(), getClientX(evt), getClientY(evt)); const p = convertPoint(this.getContainer(), getClientX(evt), getClientY(evt));
const s = this.getView().scale; const s = this.getView().scale;
const tr = this.getView().translate; const tr = this.getView().translate;
@ -1052,6 +1033,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
p.x = this.snap(p.x / s - tr.x - off); p.x = this.snap(p.x / s - tr.x - off);
p.y = this.snap(p.y / s - tr.y - off); p.y = this.snap(p.y / s - tr.y - off);
return p; return p;
} }

View File

@ -9,7 +9,7 @@ import mxClient from '../../mxClient';
import { isConsumed, isMouseEvent } from '../../util/EventUtils'; import { isConsumed, isMouseEvent } from '../../util/EventUtils';
import graph from '../Graph'; import graph from '../Graph';
import CellState from '../cell/datatypes/CellState'; import CellState from '../cell/datatypes/CellState';
import { EventCache, GestureEvent, Listenable } from '../../types'; import { EventCache, GestureEvent, Listenable, MouseEventListener } from '../../types';
// Checks if passive event listeners are supported // Checks if passive event listeners are supported
// see https://github.com/Modernizr/Modernizr/issues/1894 // see https://github.com/Modernizr/Modernizr/issues/1894
@ -50,11 +50,10 @@ class InternalEvent {
* {@link mxUtils.bind} in order to bind the "this" keyword inside the function * {@link mxUtils.bind} in order to bind the "this" keyword inside the function
* to a given execution scope. * to a given execution scope.
*/ */
// static addListener(element: Node | Window, eventName: string, funct: Function): void; static addListener(element: Listenable, eventName: string, funct: MouseEventListener) {
static addListener(element: Listenable, eventName: string, funct: EventListener) {
element.addEventListener( element.addEventListener(
eventName, eventName,
funct, funct as EventListener,
supportsPassive ? { passive: false } : false supportsPassive ? { passive: false } : false
); );
@ -69,9 +68,12 @@ class InternalEvent {
/** /**
* Removes the specified listener from the given element. * Removes the specified listener from the given element.
*/ */
// static removeListener(element: Node | Window, eventName: string, funct: Function): void; static removeListener(
static removeListener(element: Listenable, eventName: string, funct: EventListener) { element: Listenable,
element.removeEventListener(eventName, funct, false); eventName: string,
funct: MouseEventListener
) {
element.removeEventListener(eventName, funct as EventListener, false);
if (element.mxListenerList) { if (element.mxListenerList) {
const listenerCount = element.mxListenerList.length; const listenerCount = element.mxListenerList.length;
@ -90,7 +92,6 @@ class InternalEvent {
/** /**
* Removes all listeners from the given element. * Removes all listeners from the given element.
*/ */
// static removeAllListeners(element: Node | Window): void;
static removeAllListeners(element: Listenable) { static removeAllListeners(element: Listenable) {
const list = element.mxListenerList; const list = element.mxListenerList;
@ -112,10 +113,10 @@ class InternalEvent {
* will be registered as well as the mouse events. * will be registered as well as the mouse events.
*/ */
static addGestureListeners( static addGestureListeners(
node: Listenable, node: EventSource | EventTarget,
startListener: EventListener | null = null, startListener: MouseEventListener | null = null,
moveListener: EventListener | null = null, moveListener: MouseEventListener | null = null,
endListener: EventListener | null = null endListener: MouseEventListener | null = null
) { ) {
if (startListener) { if (startListener) {
InternalEvent.addListener( InternalEvent.addListener(
@ -164,9 +165,9 @@ class InternalEvent {
*/ */
static removeGestureListeners( static removeGestureListeners(
node: Listenable, node: Listenable,
startListener: EventListener | null, startListener: MouseEventListener | null,
moveListener: EventListener | null, moveListener: MouseEventListener | null,
endListener: EventListener | null endListener: MouseEventListener | null
) { ) {
if (startListener) { if (startListener) {
InternalEvent.removeListener( InternalEvent.removeListener(
@ -221,10 +222,10 @@ class InternalEvent {
node: Listenable, node: Listenable,
graph: graph, graph: graph,
state: CellState | ((evt: Event) => CellState) | null = null, state: CellState | ((evt: Event) => CellState) | null = null,
down: EventListener | null = null, down: MouseEventListener | null = null,
move: EventListener | null = null, move: MouseEventListener | null = null,
up: EventListener | null = null, up: MouseEventListener | null = null,
dblClick: EventListener | null = null dblClick: MouseEventListener | null = null
) { ) {
const getState = (evt: Event) => { const getState = (evt: Event) => {
return typeof state === 'function' ? state(evt) : state; return typeof state === 'function' ? state(evt) : state;
@ -238,7 +239,7 @@ class InternalEvent {
} else if (!isConsumed(evt)) { } else if (!isConsumed(evt)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN, InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt as MouseEvent, getState(evt)) new InternalMouseEvent(evt, getState(evt))
); );
} }
}, },
@ -248,7 +249,7 @@ class InternalEvent {
} else if (!isConsumed(evt)) { } else if (!isConsumed(evt)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent, getState(evt)) new InternalMouseEvent(evt, getState(evt))
); );
} }
}, },
@ -258,7 +259,7 @@ class InternalEvent {
} else if (!isConsumed(evt)) { } else if (!isConsumed(evt)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_UP, InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent, getState(evt)) new InternalMouseEvent(evt, getState(evt))
); );
} }
} }
@ -269,7 +270,7 @@ class InternalEvent {
dblClick(evt); dblClick(evt);
} else if (!isConsumed(evt)) { } else if (!isConsumed(evt)) {
const tmp = getState(evt); const tmp = getState(evt);
graph.dblClick(evt as MouseEvent, tmp?.cell); graph.dblClick(evt, tmp?.cell);
} }
}); });
} }
@ -279,17 +280,17 @@ class InternalEvent {
* *
* @param element DOM node to remove the listeners from. * @param element DOM node to remove the listeners from.
*/ */
static release(element: Listenable | null) { static release(element: Listenable) {
try { try {
if (element) { InternalEvent.removeAllListeners(element);
InternalEvent.removeAllListeners(element);
if ('childNodes' in element) { // @ts-ignore
const children = element.childNodes; const children = element.childNodes;
const childCount = children.length;
for (let i = 0; i < childCount; i += 1) { if (children !== undefined) {
InternalEvent.release(children[i]); const childCount = children.length;
} for (let i = 0; i < childCount; i += 1) {
InternalEvent.release(children[i]);
} }
} }
} catch (e) { } catch (e) {

View File

@ -66,6 +66,8 @@ class GraphFolding extends autoImplement<PartialClass>() {
getCollapseExpandResource = () => this.collapseExpandResource; getCollapseExpandResource = () => this.collapseExpandResource;
isFoldingEnabled = () => this.options.foldingEnabled;
/** /**
* *
* @default true * @default true
@ -133,7 +135,7 @@ class GraphFolding extends autoImplement<PartialClass>() {
recurse: boolean = false, recurse: boolean = false,
cells: CellArray | null = null, cells: CellArray | null = null,
checkFoldable: boolean = false, checkFoldable: boolean = false,
evt: EventObject | null = null evt: Event | null = null
): CellArray | null { ): CellArray | null {
if (cells == null) { if (cells == null) {
cells = this.getFoldableCells(this.getSelectionCells(), collapse); cells = this.getFoldableCells(this.getSelectionCells(), collapse);

View File

@ -326,6 +326,8 @@ class Shape {
image: ImageBox | null = null; image: ImageBox | null = null;
imageSrc: string | null = null;
indicatorColor: ColorValue = NONE; indicatorColor: ColorValue = NONE;
indicatorStrokeColor: ColorValue = NONE; indicatorStrokeColor: ColorValue = NONE;
@ -334,6 +336,8 @@ class Shape {
indicatorDirection: DirectionValue = DIRECTION_EAST; indicatorDirection: DirectionValue = DIRECTION_EAST;
indicatorImageSrc: string | null = null;
/** /**
* Function: isHtmlAllowed * Function: isHtmlAllowed
* *

View File

@ -63,7 +63,7 @@ import SvgCanvas2D from 'packages/core/src/util/canvas/SvgCanvas2D';
*/ */
class TextShape extends Shape { class TextShape extends Shape {
constructor( constructor(
value: string, value: string | HTMLElement | SVGGElement,
bounds: Rectangle, bounds: Rectangle,
align: AlignValue = ALIGN_CENTER, align: AlignValue = ALIGN_CENTER,
valign: VAlignValue = ALIGN_MIDDLE, valign: VAlignValue = ALIGN_MIDDLE,
@ -112,8 +112,7 @@ class TextShape extends Shape {
this.updateMargin(); this.updateMargin();
} }
// TODO: Document me! value: string | HTMLElement | SVGGElement;
value: string | HTMLElement | SVGGElement | null;
bounds: Rectangle; bounds: Rectangle;
align: AlignValue; align: AlignValue;
valign: VAlignValue; valign: VAlignValue;

View File

@ -61,7 +61,7 @@ class GraphLabel extends autoImplement<PartialClass>() {
* *
* @param cell {@link mxCell} whose label should be returned. * @param cell {@link mxCell} whose label should be returned.
*/ */
getLabel(cell: Cell): string | Node | null { getLabel(cell: Cell) {
let result: string | null = ''; let result: string | null = '';
if (this.isLabelsVisible() && cell != null) { if (this.isLabelsVisible() && cell != null) {
@ -71,6 +71,7 @@ class GraphLabel extends autoImplement<PartialClass>() {
result = this.convertValueToString(cell); result = this.convertValueToString(cell);
} }
} }
return result; return result;
} }

View File

@ -1,23 +1,19 @@
import {hasScrollbars} from "../../util/Utils"; import { autoImplement, hasScrollbars } from '../../util/Utils';
import EventObject from "../event/EventObject"; import EventObject from '../event/EventObject';
import InternalEvent from "../event/InternalEvent"; import InternalEvent from '../event/InternalEvent';
import PanningManager from './PanningManager'; import PanningHandler from './PanningHandler';
import PanningHandler from "./PanningHandler"; import Graph from '../Graph';
import Graph from "../Graph"; import Cell from '../cell/datatypes/Cell';
import Cell from "../cell/datatypes/Cell"; import Rectangle from '../geometry/Rectangle';
import Rectangle from "../geometry/Rectangle"; import Point from '../geometry/Point';
import Point from "../geometry/Point"; import GraphEvents from '../event/GraphEvents';
import SelectionCellsHandler from '../selection/SelectionCellsHandler';
class GraphPanning { type PartialGraph = Pick<Graph, 'getContainer' | 'getView' | 'getPlugin'>;
constructor(graph: Graph) { type PartialEvents = Pick<GraphEvents, 'fireEvent'>;
this.graph = graph; type PartialClass = PartialGraph & PartialEvents;
this.createHandlers()
}
graph: Graph; class GraphPanning extends autoImplement<PartialClass>() {
panningHandler: PanningHandler | null = null;
panningManager: PanningManager | null = null;
shiftPreview1: HTMLElement | null = null; shiftPreview1: HTMLElement | null = null;
shiftPreview2: HTMLElement | null = null; shiftPreview2: HTMLElement | null = null;
@ -28,7 +24,9 @@ class GraphPanning {
* then no panning occurs if this is `true`. * then no panning occurs if this is `true`.
* @default true * @default true
*/ */
useScrollbarsForPanning: boolean = true; useScrollbarsForPanning = true;
isUseScrollbarsForPanning = () => this.useScrollbarsForPanning;
/** /**
* Specifies if autoscrolling should be carried out via mxPanningManager even * Specifies if autoscrolling should be carried out via mxPanningManager even
@ -38,7 +36,7 @@ class GraphPanning {
* are visible and scrollable in all directions. * are visible and scrollable in all directions.
* @default false * @default false
*/ */
timerAutoScroll: boolean = false; timerAutoScroll = false;
/** /**
* Specifies if panning via {@link panGraph} should be allowed to implement autoscroll * Specifies if panning via {@link panGraph} should be allowed to implement autoscroll
@ -47,38 +45,25 @@ class GraphPanning {
* positive value. * positive value.
* @default false * @default false
*/ */
allowAutoPanning: boolean = false; allowAutoPanning = false;
/** /**
* Current horizontal panning value. * Current horizontal panning value.
* @default 0 * @default 0
*/ */
panDx: number = 0; panDx = 0;
getPanDx = () => this.panDx;
setPanDx = (dx: number) => (this.panDx = dx);
/** /**
* Current vertical panning value. * Current vertical panning value.
* @default 0 * @default 0
*/ */
panDy: number = 0; panDy = 0;
createHandlers() { getPanDy = () => this.panDy;
this.panningHandler = this.createPanningHandler(); setPanDy = (dy: number) => (this.panDy = dy);
this.panningHandler.panningEnabled = false;
}
/**
* Creates and returns a new {@link PanningHandler} to be used in this graph.
*/
createPanningHandler(): PanningHandler {
return new PanningHandler(this);
}
/**
* Creates and returns an {@link PanningManager}.
*/
createPanningManager(): PanningManager {
return new PanningManager(this);
}
/** /**
* Shifts the graph display by the given amount. This is used to preview * Shifts the graph display by the given amount. This is used to preview
@ -88,30 +73,30 @@ class GraphPanning {
* @param dx Amount to shift the graph along the x-axis. * @param dx Amount to shift the graph along the x-axis.
* @param dy Amount to shift the graph along the y-axis. * @param dy Amount to shift the graph along the y-axis.
*/ */
panGraph(dx: number, dy: number): void { panGraph(dx: number, dy: number) {
const container = <HTMLElement>this.graph.container; const container = this.getContainer();
if (this.useScrollbarsForPanning && hasScrollbars(container)) { if (this.useScrollbarsForPanning && hasScrollbars(container)) {
container.scrollLeft = -dx; container.scrollLeft = -dx;
container.scrollTop = -dy; container.scrollTop = -dy;
} else { } else {
const canvas = <SVGElement>this.graph.view.getCanvas(); const canvas = this.getView().getCanvas();
// Puts everything inside the container in a DIV so that it // Puts everything inside the container in a DIV so that it
// can be moved without changing the state of the container // can be moved without changing the state of the container
if (dx === 0 && dy === 0) { if (dx === 0 && dy === 0) {
canvas.removeAttribute('transform'); canvas.removeAttribute('transform');
if (this.shiftPreview1 != null) { if (this.shiftPreview1) {
let child = this.shiftPreview1.firstChild; let child = this.shiftPreview1.firstChild;
while (child != null) { while (child) {
const next = child.nextSibling; const next = child.nextSibling;
container.appendChild(child); container.appendChild(child);
child = next; child = next;
} }
if (this.shiftPreview1.parentNode != null) { if (this.shiftPreview1.parentNode) {
this.shiftPreview1.parentNode.removeChild(this.shiftPreview1); this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
} }
@ -121,13 +106,13 @@ class GraphPanning {
const shiftPreview2 = <HTMLElement>this.shiftPreview2; const shiftPreview2 = <HTMLElement>this.shiftPreview2;
child = shiftPreview2.firstChild; child = shiftPreview2.firstChild;
while (child != null) { while (child) {
const next = child.nextSibling; const next = child.nextSibling;
container.appendChild(child); container.appendChild(child);
child = next; child = next;
} }
if (shiftPreview2.parentNode != null) { if (shiftPreview2.parentNode) {
shiftPreview2.parentNode.removeChild(shiftPreview2); shiftPreview2.parentNode.removeChild(shiftPreview2);
} }
this.shiftPreview2 = null; this.shiftPreview2 = null;
@ -135,7 +120,7 @@ class GraphPanning {
} else { } else {
canvas.setAttribute('transform', `translate(${dx},${dy})`); canvas.setAttribute('transform', `translate(${dx},${dy})`);
if (this.shiftPreview1 == null) { if (!this.shiftPreview1) {
// Needs two divs for stuff before and after the SVG element // Needs two divs for stuff before and after the SVG element
this.shiftPreview1 = document.createElement('div'); this.shiftPreview1 = document.createElement('div');
this.shiftPreview1.style.position = 'absolute'; this.shiftPreview1.style.position = 'absolute';
@ -148,7 +133,7 @@ class GraphPanning {
let current = this.shiftPreview1; let current = this.shiftPreview1;
let child = container.firstChild; let child = container.firstChild;
while (child != null) { while (child) {
const next = child.nextSibling; const next = child.nextSibling;
// SVG element is moved via transform attribute // SVG element is moved via transform attribute
@ -163,11 +148,11 @@ class GraphPanning {
} }
// Inserts elements only if not empty // Inserts elements only if not empty
if (this.shiftPreview1.firstChild != null) { if (this.shiftPreview1.firstChild) {
container.insertBefore(this.shiftPreview1, canvas.parentNode); container.insertBefore(this.shiftPreview1, canvas.parentNode);
} }
if (this.shiftPreview2.firstChild != null) { if (this.shiftPreview2.firstChild) {
container.appendChild(this.shiftPreview2); container.appendChild(this.shiftPreview2);
} }
} }
@ -184,7 +169,7 @@ class GraphPanning {
this.panDx = dx; this.panDx = dx;
this.panDy = dy; this.panDy = dy;
this.graph.fireEvent(new EventObject(InternalEvent.PAN)); this.fireEvent(new EventObject(InternalEvent.PAN));
} }
} }
@ -203,23 +188,18 @@ class GraphPanning {
* @param cell {@link mxCell} to be made visible. * @param cell {@link mxCell} to be made visible.
* @param center Optional boolean flag. Default is `false`. * @param center Optional boolean flag. Default is `false`.
*/ */
scrollCellToVisible(cell: Cell, center: boolean = false): void { scrollCellToVisible(cell: Cell, center = false) {
const x = -this.graph.view.translate.x; const x = -this.getView().translate.x;
const y = -this.graph.view.translate.y; const y = -this.getView().translate.y;
const state = this.graph.view.getState(cell); const state = this.getView().getState(cell);
if (state != null) { if (state) {
const bounds = new Rectangle( const bounds = new Rectangle(x + state.x, y + state.y, state.width, state.height);
x + state.x,
y + state.y,
state.width,
state.height
);
if (center && this.graph.container != null) { if (center && this.getContainer()) {
const w = this.graph.container.clientWidth; const w = this.getContainer().clientWidth;
const h = this.graph.container.clientHeight; const h = this.getContainer().clientHeight;
bounds.x = bounds.getCenterX() - w / 2; bounds.x = bounds.getCenterX() - w / 2;
bounds.width = w; bounds.width = w;
@ -227,20 +207,14 @@ class GraphPanning {
bounds.height = h; bounds.height = h;
} }
const tr = new Point( const tr = new Point(this.getView().translate.x, this.getView().translate.y);
this.graph.view.translate.x,
this.graph.view.translate.y
);
if (this.scrollRectToVisible(bounds)) { if (this.scrollRectToVisible(bounds)) {
// Triggers an update via the view's event source // Triggers an update via the view's event source
const tr2 = new Point( const tr2 = new Point(this.getView().translate.x, this.getView().translate.y);
this.graph.view.translate.x, this.getView().translate.x = tr.x;
this.graph.view.translate.y this.getView().translate.y = tr.y;
); this.getView().setTranslate(tr2.x, tr2.y);
this.graph.view.translate.x = tr.x;
this.graph.view.translate.y = tr.y;
this.graph.view.setTranslate(tr2.x, tr2.y);
} }
} }
} }
@ -250,84 +224,84 @@ class GraphPanning {
* *
* @param rect {@link mxRectangle} to be made visible. * @param rect {@link mxRectangle} to be made visible.
*/ */
scrollRectToVisible(rect: Rectangle): boolean { scrollRectToVisible(rect: Rectangle) {
let isChanged = false; let isChanged = false;
if (rect != null) { const container = <HTMLElement>this.getContainer();
const container = <HTMLElement>this.graph.container; const w = container.offsetWidth;
const w = container.offsetWidth; const h = container.offsetHeight;
const h = container.offsetHeight;
const widthLimit = Math.min(w, rect.width); const widthLimit = Math.min(w, rect.width);
const heightLimit = Math.min(h, rect.height); const heightLimit = Math.min(h, rect.height);
if (hasScrollbars(container)) { if (hasScrollbars(container)) {
rect.x += this.graph.view.translate.x; rect.x += this.getView().translate.x;
rect.y += this.graph.view.translate.y; rect.y += this.getView().translate.y;
let dx = container.scrollLeft - rect.x; let dx = container.scrollLeft - rect.x;
const ddx = Math.max(dx - container.scrollLeft, 0); const ddx = Math.max(dx - container.scrollLeft, 0);
if (dx > 0) {
container.scrollLeft -= dx + 2;
} else {
dx = rect.x + widthLimit - container.scrollLeft - container.clientWidth;
if (dx > 0) { if (dx > 0) {
container.scrollLeft -= dx + 2; container.scrollLeft += dx + 2;
} else {
dx =
rect.x + widthLimit - container.scrollLeft - container.clientWidth;
if (dx > 0) {
container.scrollLeft += dx + 2;
}
} }
}
let dy = container.scrollTop - rect.y; let dy = container.scrollTop - rect.y;
const ddy = Math.max(0, dy - container.scrollTop); const ddy = Math.max(0, dy - container.scrollTop);
if (dy > 0) {
container.scrollTop -= dy + 2;
} else {
dy = rect.y + heightLimit - container.scrollTop - container.clientHeight;
if (dy > 0) { if (dy > 0) {
container.scrollTop -= dy + 2; container.scrollTop += dy + 2;
} else {
dy =
rect.y + heightLimit - container.scrollTop - container.clientHeight;
if (dy > 0) {
container.scrollTop += dy + 2;
}
} }
}
if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0)) { if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0)) {
this.graph.view.setTranslate(ddx, ddy); this.getView().setTranslate(ddx, ddy);
} }
} else { } else {
const x = -this.graph.view.translate.x; const x = -this.getView().translate.x;
const y = -this.graph.view.translate.y; const y = -this.getView().translate.y;
const s = this.graph.view.scale; const s = this.getView().scale;
if (rect.x + widthLimit > x + w) { if (rect.x + widthLimit > x + w) {
this.graph.view.translate.x -= (rect.x + widthLimit - w - x) / s; this.getView().translate.x -= (rect.x + widthLimit - w - x) / s;
isChanged = true; isChanged = true;
} }
if (rect.y + heightLimit > y + h) { if (rect.y + heightLimit > y + h) {
this.graph.view.translate.y -= (rect.y + heightLimit - h - y) / s; this.getView().translate.y -= (rect.y + heightLimit - h - y) / s;
isChanged = true; isChanged = true;
} }
if (rect.x < x) { if (rect.x < x) {
this.graph.view.translate.x += (x - rect.x) / s; this.getView().translate.x += (x - rect.x) / s;
isChanged = true; isChanged = true;
} }
if (rect.y < y) { if (rect.y < y) {
this.graph.view.translate.y += (y - rect.y) / s; this.getView().translate.y += (y - rect.y) / s;
isChanged = true; isChanged = true;
} }
if (isChanged) { if (isChanged) {
this.graph.view.refresh(); this.getView().refresh();
// Repaints selection marker (ticket 18) const selectionCellsHandler = this.getPlugin(
if (this.selectionCellsHandler != null) { 'SelectionCellsHandler'
this.selectionCellsHandler.refresh(); ) as SelectionCellsHandler;
}
// Repaints selection marker (ticket 18)
if (selectionCellsHandler) {
selectionCellsHandler.refresh();
} }
} }
} }
@ -345,10 +319,11 @@ class GraphPanning {
* *
* @param enabled Boolean indicating if panning should be enabled. * @param enabled Boolean indicating if panning should be enabled.
*/ */
setPanning(enabled: boolean): void { setPanning(enabled: boolean) {
(<PanningHandler>this.panningHandler).panningEnabled = enabled; const panningHandler = this.getPlugin('PanningHandler') as PanningHandler;
}
if (panningHandler) panningHandler.panningEnabled = enabled;
}
} }
export default GraphPanning; export default GraphPanning;

View File

@ -5,10 +5,22 @@
* Type definitions from the typed-mxgraph project * Type definitions from the typed-mxgraph project
*/ */
import EventSource from '../event/EventSource'; import EventSource from '../event/EventSource';
import utils, { hasScrollbars } from '../../util/Utils'; import { hasScrollbars } from '../../util/Utils';
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent'; import InternalEvent from '../event/InternalEvent';
import { isConsumed, isControlDown, isLeftMouseButton, isMultiTouchEvent, isPopupTrigger, isShiftDown } from '../../util/EventUtils'; import {
isConsumed,
isControlDown,
isLeftMouseButton,
isMultiTouchEvent,
isPopupTrigger,
isShiftDown,
} from '../../util/EventUtils';
import PanningManager from './PanningManager';
import InternalMouseEvent from '../event/InternalMouseEvent';
import type { GraphPlugin, MouseEventListener } from '../../types';
import type { MaxGraph } from '../Graph';
/** /**
* Class: mxPanningHandler * Class: mxPanningHandler
@ -39,66 +51,65 @@ import { isConsumed, isControlDown, isLeftMouseButton, isMultiTouchEvent, isPopu
* Fires when the panning handler changes its <active> state to false. The * Fires when the panning handler changes its <active> state to false. The
* <code>event</code> property contains the corresponding <mxMouseEvent>. * <code>event</code> property contains the corresponding <mxMouseEvent>.
*/ */
class PanningHandler extends EventSource { class PanningHandler extends EventSource implements GraphPlugin {
constructor(graph) { static pluginId = 'PanningHandler';
constructor(graph: MaxGraph) {
super(); super();
if (graph != null) { this.graph = graph;
this.graph = graph; this.graph.addMouseListener(this);
this.graph.addMouseListener(this);
// Handles force panning event // Handles force panning event
this.forcePanningHandler = (sender, evt) => { this.forcePanningHandler = (sender: EventSource, eo: EventObject) => {
const evtName = evt.getProperty('eventName'); const evtName = eo.getProperty('eventName');
const me = evt.getProperty('event'); const me = eo.getProperty('event');
if (evtName === InternalEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) { if (evtName === InternalEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) {
this.start(me); this.start(me);
this.active = true; this.active = true;
this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me)); this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me));
me.consume(); me.consume();
} }
}; };
this.graph.addListener( this.graph.addListener(InternalEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);
InternalEvent.FIRE_MOUSE_EVENT,
this.forcePanningHandler
);
// Handles pinch gestures // Handles pinch gestures
this.gestureHandler = (sender, eo) => { this.gestureHandler = (sender: EventSource, eo: EventObject) => {
if (this.isPinchEnabled()) { if (this.isPinchEnabled()) {
const evt = eo.getProperty('event'); const evt = eo.getProperty('event');
if (!isConsumed(evt) && evt.type === 'gesturestart') { if (!isConsumed(evt) && evt.type === 'gesturestart') {
this.initialScale = this.graph.view.scale; this.initialScale = this.graph.view.scale;
// Forces start of panning when pinch gesture starts // Forces start of panning when pinch gesture starts
if (!this.active && this.mouseDownEvent != null) { if (!this.active && this.mouseDownEvent) {
this.start(this.mouseDownEvent); this.start(this.mouseDownEvent);
this.mouseDownEvent = null; this.mouseDownEvent = null;
}
} else if (evt.type === 'gestureend' && this.initialScale != null) {
this.initialScale = null;
}
if (this.initialScale != null) {
this.zoomGraph(evt);
} }
} else if (evt.type === 'gestureend' && this.initialScale !== 0) {
this.initialScale = 0;
} }
};
this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler); if (this.initialScale !== 0) {
this.zoomGraph(evt);
this.mouseUpListener = () => {
if (this.active) {
this.reset();
} }
}; }
};
// Stops scrolling on every mouseup anywhere in the document this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler);
InternalEvent.addListener(document, 'mouseup', this.mouseUpListener);
} this.mouseUpListener = () => {
if (this.active) {
this.reset();
}
};
// Stops scrolling on every mouseup anywhere in the document
InternalEvent.addListener(document, 'mouseup', this.mouseUpListener);
this.panningManager = new PanningManager(graph);
} }
/** /**
@ -106,8 +117,9 @@ class PanningHandler extends EventSource {
* *
* Reference to the enclosing <mxGraph>. * Reference to the enclosing <mxGraph>.
*/ */
// graph: mxGraph; graph: MaxGraph;
graph = null;
panningManager: PanningManager;
/** /**
* Variable: useLeftButtonForPanning * Variable: useLeftButtonForPanning
@ -115,7 +127,6 @@ class PanningHandler extends EventSource {
* Specifies if panning should be active for the left mouse button. * Specifies if panning should be active for the left mouse button.
* Setting this to true may conflict with <mxRubberband>. Default is false. * Setting this to true may conflict with <mxRubberband>. Default is false.
*/ */
// useLeftButtonForPanning: boolean;
useLeftButtonForPanning = false; useLeftButtonForPanning = false;
/** /**
@ -123,7 +134,6 @@ class PanningHandler extends EventSource {
* *
* Specifies if <mxEvent.isPopupTrigger> should also be used for panning. * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
*/ */
// usePopupTrigger: boolean;
usePopupTrigger = true; usePopupTrigger = true;
/** /**
@ -132,7 +142,6 @@ class PanningHandler extends EventSource {
* Specifies if panning should be active even if there is a cell under the * Specifies if panning should be active even if there is a cell under the
* mousepointer. Default is false. * mousepointer. Default is false.
*/ */
// ignoreCell: boolean;
ignoreCell = false; ignoreCell = false;
/** /**
@ -140,7 +149,6 @@ class PanningHandler extends EventSource {
* *
* Specifies if the panning should be previewed. Default is true. * Specifies if the panning should be previewed. Default is true.
*/ */
// previewEnabled: boolean;
previewEnabled = true; previewEnabled = true;
/** /**
@ -149,7 +157,6 @@ class PanningHandler extends EventSource {
* Specifies if the panning steps should be aligned to the grid size. * Specifies if the panning steps should be aligned to the grid size.
* Default is false. * Default is false.
*/ */
// useGrid: boolean;
useGrid = false; useGrid = false;
/** /**
@ -157,7 +164,6 @@ class PanningHandler extends EventSource {
* *
* Specifies if panning should be enabled. Default is true. * Specifies if panning should be enabled. Default is true.
*/ */
// panningEnabled: boolean;
panningEnabled = true; panningEnabled = true;
/** /**
@ -165,15 +171,15 @@ class PanningHandler extends EventSource {
* *
* Specifies if pinch gestures should be handled as zoom. Default is true. * Specifies if pinch gestures should be handled as zoom. Default is true.
*/ */
// pinchEnabled: boolean;
pinchEnabled = true; pinchEnabled = true;
initialScale = 0;
/** /**
* Variable: maxScale * Variable: maxScale
* *
* Specifies the maximum scale. Default is 8. * Specifies the maximum scale. Default is 8.
*/ */
// maxScale: number;
maxScale = 8; maxScale = 8;
/** /**
@ -181,7 +187,6 @@ class PanningHandler extends EventSource {
* *
* Specifies the minimum scale. Default is 0.01. * Specifies the minimum scale. Default is 0.01.
*/ */
// minScale: number;
minScale = 0.01; minScale = 0.01;
/** /**
@ -189,23 +194,20 @@ class PanningHandler extends EventSource {
* *
* Holds the current horizontal offset. * Holds the current horizontal offset.
*/ */
// dx: number; dx = 0;
dx = null;
/** /**
* Variable: dy * Variable: dy
* *
* Holds the current vertical offset. * Holds the current vertical offset.
*/ */
// dy: number; dy = 0;
dy = null;
/** /**
* Variable: startX * Variable: startX
* *
* Holds the x-coordinate of the start point. * Holds the x-coordinate of the start point.
*/ */
// startX: number;
startX = 0; startX = 0;
/** /**
@ -213,17 +215,29 @@ class PanningHandler extends EventSource {
* *
* Holds the y-coordinate of the start point. * Holds the y-coordinate of the start point.
*/ */
// startY: number;
startY = 0; startY = 0;
dx0 = 0;
dy0 = 0;
panningTrigger = false;
active = false;
forcePanningHandler: (sender: EventSource, evt: EventObject) => void;
gestureHandler: (sender: EventSource, evt: EventObject) => void;
mouseUpListener: MouseEventListener;
mouseDownEvent: InternalMouseEvent | null = null;
/** /**
* Function: isActive * Function: isActive
* *
* Returns true if the handler is currently active. * Returns true if the handler is currently active.
*/ */
// isActive(): boolean;
isActive() { isActive() {
return this.active || this.initialScale != null; return this.active || this.initialScale !== null;
} }
/** /**
@ -231,7 +245,6 @@ class PanningHandler extends EventSource {
* *
* Returns <panningEnabled>. * Returns <panningEnabled>.
*/ */
// isPanningEnabled(): boolean;
isPanningEnabled() { isPanningEnabled() {
return this.panningEnabled; return this.panningEnabled;
} }
@ -241,8 +254,7 @@ class PanningHandler extends EventSource {
* *
* Sets <panningEnabled>. * Sets <panningEnabled>.
*/ */
// setPanningEnabled(value: boolean): void; setPanningEnabled(value: boolean) {
setPanningEnabled(value) {
this.panningEnabled = value; this.panningEnabled = value;
} }
@ -251,7 +263,6 @@ class PanningHandler extends EventSource {
* *
* Returns <pinchEnabled>. * Returns <pinchEnabled>.
*/ */
// isPinchEnabled(): boolean;
isPinchEnabled() { isPinchEnabled() {
return this.pinchEnabled; return this.pinchEnabled;
} }
@ -261,8 +272,7 @@ class PanningHandler extends EventSource {
* *
* Sets <pinchEnabled>. * Sets <pinchEnabled>.
*/ */
// setPinchEnabled(value: boolean): void; setPinchEnabled(value: boolean) {
setPinchEnabled(value) {
this.pinchEnabled = value; this.pinchEnabled = value;
} }
@ -273,14 +283,11 @@ class PanningHandler extends EventSource {
* given cell. This returns true if control-shift is pressed or if * given cell. This returns true if control-shift is pressed or if
* <usePopupTrigger> is true and the event is a popup trigger. * <usePopupTrigger> is true and the event is a popup trigger.
*/ */
// isPanningTrigger(me: mxMouseEvent): boolean; isPanningTrigger(me: InternalMouseEvent) {
isPanningTrigger(me) {
const evt = me.getEvent(); const evt = me.getEvent();
return ( return (
(this.useLeftButtonForPanning && (this.useLeftButtonForPanning && !me.getState() && isLeftMouseButton(evt)) ||
me.getState() == null &&
isLeftMouseButton(evt)) ||
(isControlDown(evt) && isShiftDown(evt)) || (isControlDown(evt) && isShiftDown(evt)) ||
(this.usePopupTrigger && isPopupTrigger(evt)) (this.usePopupTrigger && isPopupTrigger(evt))
); );
@ -293,8 +300,7 @@ class PanningHandler extends EventSource {
* implementation always returns true if <ignoreCell> is true or for * implementation always returns true if <ignoreCell> is true or for
* multi touch events. * multi touch events.
*/ */
// isForcePanningEvent(me: mxMouseEvent): boolean; isForcePanningEvent(me: InternalMouseEvent) {
isForcePanningEvent(me) {
return this.ignoreCell || isMultiTouchEvent(me.getEvent()); return this.ignoreCell || isMultiTouchEvent(me.getEvent());
} }
@ -304,8 +310,7 @@ class PanningHandler extends EventSource {
* Handles the event by initiating the panning. By consuming the event all * Handles the event by initiating the panning. By consuming the event all
* subsequent events of the gesture are redirected to this handler. * subsequent events of the gesture are redirected to this handler.
*/ */
// mouseDown(sender: any, me: mxMouseEvent): void; mouseDown(sender: EventSource, me: InternalMouseEvent) {
mouseDown(sender, me) {
this.mouseDownEvent = me; this.mouseDownEvent = me;
if ( if (
@ -324,16 +329,15 @@ class PanningHandler extends EventSource {
* *
* Starts panning at the given event. * Starts panning at the given event.
*/ */
// start(me: mxMouseEvent): void; start(me: InternalMouseEvent) {
start(me) {
this.dx0 = -this.graph.container.scrollLeft; this.dx0 = -this.graph.container.scrollLeft;
this.dy0 = -this.graph.container.scrollTop; this.dy0 = -this.graph.container.scrollTop;
// Stores the location of the trigger event // Stores the location of the trigger event
this.startX = me.getX(); this.startX = me.getX();
this.startY = me.getY(); this.startY = me.getY();
this.dx = null; this.dx = 0;
this.dy = null; this.dy = 0;
this.panningTrigger = true; this.panningTrigger = true;
} }
@ -366,8 +370,7 @@ class PanningHandler extends EventSource {
* }; * };
* (end) * (end)
*/ */
// consumePanningTrigger(me: mxMouseEvent): void; consumePanningTrigger(me: InternalMouseEvent) {
consumePanningTrigger(me) {
me.consume(); me.consume();
} }
@ -376,8 +379,7 @@ class PanningHandler extends EventSource {
* *
* Handles the event by updating the panning on the graph. * Handles the event by updating the panning on the graph.
*/ */
// mouseMove(sender: any, me: mxMouseEvent): void; mouseMove(sender: EventSource, me: InternalMouseEvent) {
mouseMove(sender, me) {
this.dx = me.getX() - this.startX; this.dx = me.getX() - this.startX;
this.dy = me.getY() - this.startY; this.dy = me.getY() - this.startY;
@ -399,8 +401,8 @@ class PanningHandler extends EventSource {
// Panning is activated only if the mouse is moved // Panning is activated only if the mouse is moved
// beyond the graph tolerance // beyond the graph tolerance
this.active = this.active =
Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dx) > this.graph.getSnapTolerance() ||
Math.abs(this.dy) > this.graph.tolerance; Math.abs(this.dy) > this.graph.getSnapTolerance();
if (!tmp && this.active) { if (!tmp && this.active) {
this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me)); this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me));
@ -418,13 +420,12 @@ class PanningHandler extends EventSource {
* Handles the event by setting the translation on the view or showing the * Handles the event by setting the translation on the view or showing the
* popupmenu. * popupmenu.
*/ */
// mouseUp(sender: any, me: mxMouseEvent): void; mouseUp(sender: EventSource, me: InternalMouseEvent) {
mouseUp(sender, me) {
if (this.active) { if (this.active) {
if (this.dx != null && this.dy != null) { if (this.dx !== 0 && this.dy !== 0) {
// Ignores if scrollbars have been used for panning // Ignores if scrollbars have been used for panning
if ( if (
!this.graph.useScrollbarsForPanning || !this.graph.isUseScrollbarsForPanning() ||
!hasScrollbars(this.graph.container) !hasScrollbars(this.graph.container)
) { ) {
const { scale } = this.graph.getView(); const { scale } = this.graph.getView();
@ -447,16 +448,11 @@ class PanningHandler extends EventSource {
* *
* Zooms the graph to the given value and consumed the event if needed. * Zooms the graph to the given value and consumed the event if needed.
*/ */
zoomGraph(evt) { zoomGraph(evt: Event) {
// @ts-ignore evt may have scale property
let value = Math.round(this.initialScale * evt.scale * 100) / 100; let value = Math.round(this.initialScale * evt.scale * 100) / 100;
value = Math.max(this.minScale, value);
if (this.minScale != null) { value = Math.min(this.maxScale, value);
value = Math.max(this.minScale, value);
}
if (this.maxScale != null) {
value = Math.min(this.maxScale, value);
}
if (this.graph.view.scale !== value) { if (this.graph.view.scale !== value) {
this.graph.zoomTo(value); this.graph.zoomTo(value);
@ -470,13 +466,12 @@ class PanningHandler extends EventSource {
* Handles the event by setting the translation on the view or showing the * Handles the event by setting the translation on the view or showing the
* popupmenu. * popupmenu.
*/ */
// reset(): void;
reset() { reset() {
this.panningTrigger = false; this.panningTrigger = false;
this.mouseDownEvent = null; this.mouseDownEvent = null;
this.active = false; this.active = false;
this.dx = null; this.dx = 0;
this.dy = null; this.dy = 0;
} }
/** /**
@ -484,8 +479,7 @@ class PanningHandler extends EventSource {
* *
* Pans <graph> by the given amount. * Pans <graph> by the given amount.
*/ */
// panGraph(dx: number, dy: number): void; panGraph(dx: number, dy: number) {
panGraph(dx, dy) {
this.graph.getView().setTranslate(dx, dy); this.graph.getView().setTranslate(dx, dy);
} }
@ -494,8 +488,7 @@ class PanningHandler extends EventSource {
* *
* Destroys the handler and all its resources and DOM nodes. * Destroys the handler and all its resources and DOM nodes.
*/ */
// destroy(): void; onDestroy() {
destroy() {
this.graph.removeMouseListener(this); this.graph.removeMouseListener(this);
this.graph.removeListener(this.forcePanningHandler); this.graph.removeListener(this.forcePanningHandler);
this.graph.removeListener(this.gestureHandler); this.graph.removeListener(this.gestureHandler);

View File

@ -5,8 +5,11 @@
* Type definitions from the typed-mxgraph project * Type definitions from the typed-mxgraph project
*/ */
import { MouseEventListener, MouseListenerSet } from '../../types';
import { hasScrollbars } from '../../util/Utils'; import { hasScrollbars } from '../../util/Utils';
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent';
import { MaxGraph } from '../Graph';
/** /**
* Class: mxPanningManager * Class: mxPanningManager
@ -14,7 +17,7 @@ import EventObject from '../event/EventObject';
* Implements a handler for panning. * Implements a handler for panning.
*/ */
class PanningManager { class PanningManager {
constructor(graph) { constructor(graph: MaxGraph) {
this.thread = null; this.thread = null;
this.active = false; this.active = false;
this.tdx = 0; this.tdx = 0;
@ -46,7 +49,7 @@ class PanningManager {
}; };
// Stops scrolling on every mouseup anywhere in the document // Stops scrolling on every mouseup anywhere in the document
mxEvent.addListener(document, 'mouseup', this.mouseUpListener); InternalEvent.addListener(document, 'mouseup', this.mouseUpListener);
const createThread = () => { const createThread = () => {
this.scrollbars = hasScrollbars(graph.container); this.scrollbars = hasScrollbars(graph.container);
@ -61,9 +64,9 @@ class PanningManager {
const left = -graph.container.scrollLeft - Math.ceil(this.dx); const left = -graph.container.scrollLeft - Math.ceil(this.dx);
const top = -graph.container.scrollTop - Math.ceil(this.dy); const top = -graph.container.scrollTop - Math.ceil(this.dy);
graph.panGraph(left, top); graph.panGraph(left, top);
graph.panDx = this.scrollLeft - graph.container.scrollLeft; graph.setPanDx(this.scrollLeft - graph.container.scrollLeft);
graph.panDy = this.scrollTop - graph.container.scrollTop; graph.setPanDy(this.scrollTop - graph.container.scrollTop);
graph.fireEvent(new EventObject(mxEvent.PAN)); graph.fireEvent(new EventObject(InternalEvent.PAN));
// TODO: Implement graph.autoExtend // TODO: Implement graph.autoExtend
} else { } else {
graph.panGraph(this.getDx(), this.getDy()); graph.panGraph(this.getDx(), this.getDy());
@ -171,8 +174,8 @@ class PanningManager {
this.tdy = 0; this.tdy = 0;
if (!this.scrollbars) { if (!this.scrollbars) {
const px = graph.panDx; const px = graph.getPanDx();
const py = graph.panDy; const py = graph.getPanDy();
if (px != 0 || py != 0) { if (px != 0 || py != 0) {
graph.panGraph(0, 0); graph.panGraph(0, 0);
@ -182,16 +185,16 @@ class PanningManager {
); );
} }
} else { } else {
graph.panDx = 0; graph.setPanDx(0);
graph.panDy = 0; graph.setPanDy(0);
graph.fireEvent(new EventObject(mxEvent.PAN)); graph.fireEvent(new EventObject(InternalEvent.PAN));
} }
} }
}; };
this.destroy = () => { this.destroy = () => {
graph.removeMouseListener(this.mouseListener); graph.removeMouseListener(this.mouseListener);
mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); InternalEvent.removeListener(document, 'mouseup', this.mouseUpListener);
}; };
} }
@ -200,7 +203,6 @@ class PanningManager {
* *
* Damper value for the panning. Default is 1/6. * Damper value for the panning. Default is 1/6.
*/ */
// damper: number;
damper = 1 / 6; damper = 1 / 6;
/** /**
@ -208,7 +210,6 @@ class PanningManager {
* *
* Delay in milliseconds for the panning. Default is 10. * Delay in milliseconds for the panning. Default is 10.
*/ */
// delay: number;
delay = 10; delay = 10;
/** /**
@ -216,7 +217,6 @@ class PanningManager {
* *
* Specifies if mouse events outside of the component should be handled. Default is true. * Specifies if mouse events outside of the component should be handled. Default is true.
*/ */
// handleMouseOut: boolean;
handleMouseOut = true; handleMouseOut = true;
/** /**
@ -224,8 +224,33 @@ class PanningManager {
* *
* Border to handle automatic panning inside the component. Default is 0 (disabled). * Border to handle automatic panning inside the component. Default is 0 (disabled).
*/ */
// border: number;
border = 0; border = 0;
thread: number | null = null;
active = false;
tdx = 0;
tdy = 0;
t0x = 0;
t0y = 0;
dx = 0;
dy = 0;
scrollbars = false;
scrollLeft = 0;
scrollTop = 0;
mouseListener: MouseListenerSet;
mouseUpListener: MouseEventListener;
stop: () => void;
isActive: () => boolean;
getDx: () => number;
getDy: () => number;
start: () => void;
panTo: (x: number, y: number, w: number, h: number) => void;
destroy: () => void;
} }
export default PanningManager; export default PanningManager;

View File

@ -11,6 +11,7 @@ import { getMainEvent, isMultiTouchEvent } from '../../util/EventUtils';
import Graph from '../Graph'; import Graph from '../Graph';
import InternalMouseEvent from '../event/InternalMouseEvent'; import InternalMouseEvent from '../event/InternalMouseEvent';
import Cell from '../cell/datatypes/Cell'; import Cell from '../cell/datatypes/Cell';
import { GraphPlugin } from '../../types';
/** /**
* Class: mxPopupMenuHandler * Class: mxPopupMenuHandler
@ -21,7 +22,7 @@ import Cell from '../cell/datatypes/Cell';
* *
* Constructs an event handler that creates a <mxPopupMenu>. * Constructs an event handler that creates a <mxPopupMenu>.
*/ */
class PopupMenuHandler extends mxPopupMenu { class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
constructor(graph: Graph, factoryMethod: any) { constructor(graph: Graph, factoryMethod: any) {
super(); super();

View File

@ -72,7 +72,7 @@ class CellHighlight {
} }
// TODO: Document me!! // TODO: Document me!!
highlightColor: ColorValue | null = null; highlightColor: ColorValue;
strokeWidth: number = 0; strokeWidth: number = 0;
@ -80,7 +80,7 @@ class CellHighlight {
opacity = 100; opacity = 100;
repaintHandler: Function | null = null; repaintHandler: Function;
shape: Shape | null = null; shape: Shape | null = null;
@ -112,14 +112,14 @@ class CellHighlight {
* Holds the handler that automatically invokes reset if the highlight should be hidden. * Holds the handler that automatically invokes reset if the highlight should be hidden.
* @default null * @default null
*/ */
resetHandler: Function | null = null; resetHandler: Function;
/** /**
* Sets the color of the rectangle used to highlight drop targets. * Sets the color of the rectangle used to highlight drop targets.
* *
* @param {string} color - String that represents the new highlight color. * @param {string} color - String that represents the new highlight color.
*/ */
setHighlightColor(color: ColorValue | null) { setHighlightColor(color: ColorValue) {
this.highlightColor = color; this.highlightColor = color;
if (this.shape) { if (this.shape) {
@ -134,10 +134,12 @@ class CellHighlight {
this.shape = this.createShape(); this.shape = this.createShape();
this.repaint(); this.repaint();
const node = this.shape.node; if (this.shape) {
const node = this.shape.node;
if (!this.keepOnTop && node?.parentNode?.firstChild !== node) { if (!this.keepOnTop && node?.parentNode?.firstChild !== node && node.parentNode) {
node.parentNode.insertBefore(node, node.parentNode.firstChild); node.parentNode.insertBefore(node, node.parentNode.firstChild);
}
} }
} }
@ -145,11 +147,11 @@ class CellHighlight {
* Creates and returns the highlight shape for the given state. * Creates and returns the highlight shape for the given state.
*/ */
createShape() { createShape() {
if (!this.state) return; if (!this.state) return null;
const shape = this.graph.cellRenderer.createShape(this.state); const shape = this.graph.cellRenderer.createShape(this.state);
shape.svgStrokeTolerance = this.graph.tolerance; shape.svgStrokeTolerance = this.graph.getEventTolerance();
shape.points = this.state.absolutePoints; shape.points = this.state.absolutePoints;
shape.apply(this.state); shape.apply(this.state);
shape.stroke = this.highlightColor; shape.stroke = this.highlightColor;
@ -173,7 +175,7 @@ class CellHighlight {
/** /**
* Updates the highlight after a change of the model or view. * Updates the highlight after a change of the model or view.
*/ */
getStrokeWidth(state: CellState | null = null): number | null { getStrokeWidth(state: CellState | null = null) {
return this.strokeWidth; return this.strokeWidth;
} }
@ -256,13 +258,13 @@ class CellHighlight {
/** /**
* Destroys the handler and all its resources and DOM nodes. * Destroys the handler and all its resources and DOM nodes.
*/ */
destroy(): void { destroy() {
const graph = <graph>this.graph; const graph = this.graph;
graph.getView().removeListener(this.resetHandler); graph.getView().removeListener(this.resetHandler);
graph.getView().removeListener(this.repaintHandler); graph.getView().removeListener(this.repaintHandler);
graph.getModel().removeListener(this.repaintHandler); graph.getModel().removeListener(this.repaintHandler);
if (this.shape != null) { if (this.shape) {
this.shape.destroy(); this.shape.destroy();
this.shape = null; this.shape = null;
} }

View File

@ -15,6 +15,7 @@ import type GraphCells from '../cell/GraphCells';
import type Graph from '../Graph'; import type Graph from '../Graph';
import type GraphEvents from '../event/GraphEvents'; import type GraphEvents from '../event/GraphEvents';
import type EventSource from '../event/EventSource'; import type EventSource from '../event/EventSource';
import { MaxGraph } from '../Graph';
type PartialGraph = Pick< type PartialGraph = Pick<
Graph, Graph,
@ -36,6 +37,8 @@ class GraphSelection extends autoImplement<PartialClass>() {
*/ */
doneResource: string = mxClient.language !== 'none' ? 'done' : ''; doneResource: string = mxClient.language !== 'none' ? 'done' : '';
getDoneResource = () => this.doneResource;
/** /**
* Specifies the resource key for the status message while the selection is * Specifies the resource key for the status message while the selection is
* being updated. If the resource for this key does not exist then the * being updated. If the resource for this key does not exist then the
@ -44,6 +47,8 @@ class GraphSelection extends autoImplement<PartialClass>() {
updatingSelectionResource: string = updatingSelectionResource: string =
mxClient.language !== 'none' ? 'updatingSelection' : ''; mxClient.language !== 'none' ? 'updatingSelection' : '';
getUpdatingSelectionResource = () => this.updatingSelectionResource;
/** /**
* Specifies if only one selected item at a time is allowed. * Specifies if only one selected item at a time is allowed.
* Default is false. * Default is false.
@ -222,7 +227,7 @@ class GraphSelection extends autoImplement<PartialClass>() {
(removed && removed.length > 0 && removed[0]) (removed && removed.length > 0 && removed[0])
) { ) {
const change = new SelectionChange( const change = new SelectionChange(
this, this as MaxGraph,
added || new CellArray(), added || new CellArray(),
removed || new CellArray() removed || new CellArray()
); );
@ -512,7 +517,7 @@ class GraphSelection extends autoImplement<PartialClass>() {
) { ) {
const filter = (cell: Cell) => { const filter = (cell: Cell) => {
return ( return (
this.getView().getState(cell) && !!this.getView().getState(cell) &&
(((selectGroups || cell.getChildCount() === 0) && (((selectGroups || cell.getChildCount() === 0) &&
cell.isVertex() && cell.isVertex() &&
vertices && vertices &&

View File

@ -17,9 +17,10 @@ import mxClient from '../../mxClient';
import Rectangle from '../geometry/Rectangle'; import Rectangle from '../geometry/Rectangle';
import { isAltDown, isMultiTouchEvent } from '../../util/EventUtils'; import { isAltDown, isMultiTouchEvent } from '../../util/EventUtils';
import { clearSelection } from '../../util/DomUtils'; import { clearSelection } from '../../util/DomUtils';
import Graph from '../Graph'; import { MaxGraph } from '../Graph';
import { GraphPlugin } from '../../types'; import { GraphPlugin } from '../../types';
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
import EventSource from '../event/EventSource';
/** /**
* Event handler that selects rectangular regions. * Event handler that selects rectangular regions.
@ -27,23 +28,14 @@ import EventObject from '../event/EventObject';
* To enable rubberband selection in a graph, use the following code. * To enable rubberband selection in a graph, use the following code.
*/ */
class RubberBand implements GraphPlugin { class RubberBand implements GraphPlugin {
forceRubberbandHandler?: Function; static pluginId = 'RubberBand';
panHandler?: Function;
gestureHandler?: Function;
graph?: Graph;
first: Point | null = null;
destroyed: boolean = false;
dragHandler?: Function;
dropHandler?: Function;
constructor() {} constructor(graph: MaxGraph) {
onInit(graph: Graph) {
this.graph = graph; this.graph = graph;
this.graph.addMouseListener(this); this.graph.addMouseListener(this);
// Handles force rubberband event // Handles force rubberband event
this.forceRubberbandHandler = (sender: any, evt: EventObject) => { this.forceRubberbandHandler = (sender: EventSource, evt: EventObject) => {
const evtName = evt.getProperty('eventName'); const evtName = evt.getProperty('eventName');
const me = evt.getProperty('event'); const me = evt.getProperty('event');
@ -67,8 +59,8 @@ class RubberBand implements GraphPlugin {
this.graph.addListener(InternalEvent.PAN, this.panHandler); this.graph.addListener(InternalEvent.PAN, this.panHandler);
// Does not show menu if any touch gestures take place after the trigger // Does not show menu if any touch gestures take place after the trigger
this.gestureHandler = (sender, eo) => { this.gestureHandler = (sender: EventSource, eo: EventObject) => {
if (this.first != null) { if (this.first) {
this.reset(); this.reset();
} }
}; };
@ -76,6 +68,20 @@ class RubberBand implements GraphPlugin {
this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler); this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler);
} }
forceRubberbandHandler: Function;
panHandler: Function;
gestureHandler: Function;
graph: MaxGraph;
first: Point | null = null;
destroyed: boolean = false;
dragHandler: ((evt: MouseEvent) => void) | null = null;
dropHandler: ((evt: MouseEvent) => void) | null = null;
x = 0;
y = 0;
width = 0;
height = 0;
/** /**
* Specifies the default opacity to be used for the rubberband div. Default is 20. * Specifies the default opacity to be used for the rubberband div. Default is 20.
*/ */
@ -93,14 +99,14 @@ class RubberBand implements GraphPlugin {
* *
* Holds the DIV element which is currently visible. * Holds the DIV element which is currently visible.
*/ */
div = null; div: HTMLElement | null = null;
/** /**
* Variable: sharedDiv * Variable: sharedDiv
* *
* Holds the DIV element which is used to display the rubberband. * Holds the DIV element which is used to display the rubberband.
*/ */
sharedDiv = null; sharedDiv: HTMLElement | null = null;
/** /**
* Variable: currentX * Variable: currentX
@ -119,12 +125,12 @@ class RubberBand implements GraphPlugin {
/** /**
* Optional fade out effect. Default is false. * Optional fade out effect. Default is false.
*/ */
fadeOut: boolean = false; fadeOut = false;
/** /**
* Creates the rubberband selection shape. * Creates the rubberband selection shape.
*/ */
isEnabled(): boolean { isEnabled() {
return this.enabled; return this.enabled;
} }
@ -155,12 +161,12 @@ class RubberBand implements GraphPlugin {
* event all subsequent events of the gesture are redirected to this * event all subsequent events of the gesture are redirected to this
* handler. * handler.
*/ */
mouseDown(sender: any, me: InternalMouseEvent): void { mouseDown(sender: EventSource, me: InternalMouseEvent) {
if ( if (
!me.isConsumed() && !me.isConsumed() &&
this.isEnabled() && this.isEnabled() &&
this.graph.isEnabled() && this.graph.isEnabled() &&
me.getState() == null && !me.getState() &&
!isMultiTouchEvent(me.getEvent()) !isMultiTouchEvent(me.getEvent())
) { ) {
const offset = getOffset(this.graph.container); const offset = getOffset(this.graph.container);
@ -181,12 +187,12 @@ class RubberBand implements GraphPlugin {
/** /**
* Creates the rubberband selection shape. * Creates the rubberband selection shape.
*/ */
start(x: number, y: number): void { start(x: number, y: number) {
this.first = new Point(x, y); this.first = new Point(x, y);
const { container } = this.graph; const { container } = this.graph;
function createMouseEvent(evt) { function createMouseEvent(evt: MouseEvent) {
const me = new InternalMouseEvent(evt); const me = new InternalMouseEvent(evt);
const pt = convertPoint(container, me.getX(), me.getY()); const pt = convertPoint(container, me.getX(), me.getY());
@ -196,11 +202,11 @@ class RubberBand implements GraphPlugin {
return me; return me;
} }
this.dragHandler = (evt) => { this.dragHandler = (evt: MouseEvent) => {
this.mouseMove(this.graph, createMouseEvent(evt)); this.mouseMove(this.graph, createMouseEvent(evt));
}; };
this.dropHandler = (evt) => { this.dropHandler = (evt: MouseEvent) => {
this.mouseUp(this.graph, createMouseEvent(evt)); this.mouseUp(this.graph, createMouseEvent(evt));
}; };
@ -220,8 +226,8 @@ class RubberBand implements GraphPlugin {
* *
* Handles the event by updating therubberband selection. * Handles the event by updating therubberband selection.
*/ */
mouseMove(sender: any, me: InternalMouseEvent): void { mouseMove(sender: EventSource, me: InternalMouseEvent) {
if (!me.isConsumed() && this.first != null) { if (!me.isConsumed() && this.first) {
const origin = getScrollOrigin(this.graph.container); const origin = getScrollOrigin(this.graph.container);
const offset = getOffset(this.graph.container); const offset = getOffset(this.graph.container);
origin.x -= offset.x; origin.x -= offset.x;
@ -230,10 +236,10 @@ class RubberBand implements GraphPlugin {
const y = me.getY() + origin.y; const y = me.getY() + origin.y;
const dx = this.first.x - x; const dx = this.first.x - x;
const dy = this.first.y - y; const dy = this.first.y - y;
const tol = this.graph.tolerance; const tol = this.graph.getEventTolerance();
if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) { if (this.div || Math.abs(dx) > tol || Math.abs(dy) > tol) {
if (this.div == null) { if (!this.div) {
this.div = this.createShape(); this.div = this.createShape();
} }
@ -250,8 +256,8 @@ class RubberBand implements GraphPlugin {
/** /**
* Creates the rubberband selection shape. * Creates the rubberband selection shape.
*/ */
createShape(): HTMLElement | null { createShape() {
if (this.sharedDiv == null) { if (!this.sharedDiv) {
this.sharedDiv = document.createElement('div'); this.sharedDiv = document.createElement('div');
this.sharedDiv.className = 'mxRubberband'; this.sharedDiv.className = 'mxRubberband';
setOpacity(this.sharedDiv, this.defaultOpacity); setOpacity(this.sharedDiv, this.defaultOpacity);
@ -272,8 +278,8 @@ class RubberBand implements GraphPlugin {
* *
* Returns true if this handler is active. * Returns true if this handler is active.
*/ */
isActive(sender: any, me: InternalMouseEvent): boolean { isActive(sender?: EventSource, me?: InternalMouseEvent) {
return this.div != null && this.div.style.display !== 'none'; return this.div && this.div.style.display !== 'none';
} }
/** /**
@ -282,7 +288,7 @@ class RubberBand implements GraphPlugin {
* Handles the event by selecting the region of the rubberband using * Handles the event by selecting the region of the rubberband using
* <mxGraph.selectRegion>. * <mxGraph.selectRegion>.
*/ */
mouseUp(sender: any, me: InternalMouseEvent): void { mouseUp(sender: EventSource, me: InternalMouseEvent) {
const active = this.isActive(); const active = this.isActive();
this.reset(); this.reset();
@ -298,7 +304,7 @@ class RubberBand implements GraphPlugin {
* Resets the state of this handler and selects the current region * Resets the state of this handler and selects the current region
* for the given event. * for the given event.
*/ */
execute(evt) { execute(evt: MouseEvent) {
const rect = new Rectangle(this.x, this.y, this.width, this.height); const rect = new Rectangle(this.x, this.y, this.width, this.height);
this.graph.selectRegion(rect, evt); this.graph.selectRegion(rect, evt);
} }
@ -309,18 +315,18 @@ class RubberBand implements GraphPlugin {
* Resets the state of the rubberband selection. * Resets the state of the rubberband selection.
*/ */
reset() { reset() {
if (this.div != null) { if (this.div) {
if (mxClient.IS_SVG && this.fadeOut) { if (mxClient.IS_SVG && this.fadeOut) {
const temp = this.div; const temp = this.div;
setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear'); setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear');
temp.style.pointerEvents = 'none'; temp.style.pointerEvents = 'none';
temp.style.opacity = 0; temp.style.opacity = String(0);
window.setTimeout(() => { window.setTimeout(() => {
temp.parentNode.removeChild(temp); if (temp.parentNode) temp.parentNode.removeChild(temp);
}, 200); }, 200);
} else { } else {
this.div.parentNode.removeChild(this.div); if (this.div.parentNode) this.div.parentNode.removeChild(this.div);
} }
} }
@ -344,7 +350,7 @@ class RubberBand implements GraphPlugin {
* *
* Sets <currentX> and <currentY> and calls <repaint>. * Sets <currentX> and <currentY> and calls <repaint>.
*/ */
update(x, y) { update(x: number, y: number) {
this.currentX = x; this.currentX = x;
this.currentY = y; this.currentY = y;
@ -357,9 +363,9 @@ class RubberBand implements GraphPlugin {
* Computes the bounding box and updates the style of the <div>. * Computes the bounding box and updates the style of the <div>.
*/ */
repaint() { repaint() {
if (this.div != null) { if (this.div && this.first) {
const x = this.currentX - this.graph.panDx; const x = this.currentX - this.graph.getPanDx();
const y = this.currentY - this.graph.panDy; const y = this.currentY - this.graph.getPanDy();
this.x = Math.min(this.first.x, x); this.x = Math.min(this.first.x, x);
this.y = Math.min(this.first.y, y); this.y = Math.min(this.first.y, y);
@ -391,7 +397,7 @@ class RubberBand implements GraphPlugin {
this.graph.removeListener(this.panHandler); this.graph.removeListener(this.panHandler);
this.reset(); this.reset();
if (this.sharedDiv != null) { if (this.sharedDiv) {
this.sharedDiv = null; this.sharedDiv = null;
} }
} }

View File

@ -8,11 +8,14 @@ import EventSource from '../event/EventSource';
import Dictionary from '../../util/Dictionary'; import Dictionary from '../../util/Dictionary';
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
import InternalEvent from '../event/InternalEvent'; import InternalEvent from '../event/InternalEvent';
import utils, { sortCells } from '../../util/Utils'; import { sortCells } from '../../util/Utils';
import Graph from '../Graph'; import { MaxGraph } from '../Graph';
import Cell from '../cell/datatypes/Cell'; import Cell from '../cell/datatypes/Cell';
import CellArray from '../cell/datatypes/CellArray';
import CellState from '../cell/datatypes/CellState'; import CellState from '../cell/datatypes/CellState';
import { GraphPlugin } from '../../types';
import EdgeHandler from '../cell/edge/EdgeHandler';
import VertexHandler from '../cell/vertex/VertexHandler';
import InternalMouseEvent from '../event/InternalMouseEvent';
/** /**
* Class: mxSelectionCellsHandler * Class: mxSelectionCellsHandler
@ -36,21 +39,23 @@ import CellState from '../cell/datatypes/CellState';
* *
* graph - Reference to the enclosing <mxGraph>. * graph - Reference to the enclosing <mxGraph>.
*/ */
class SelectionCellsHandler extends EventSource { class SelectionCellsHandler extends EventSource implements GraphPlugin {
constructor(graph: Graph) { static pluginId = 'SelectionCellsHandler';
constructor(graph: MaxGraph) {
super(); super();
this.graph = graph; this.graph = graph;
this.handlers = new Dictionary(); this.handlers = new Dictionary();
this.graph.addMouseListener(this); this.graph.addMouseListener(this);
this.refreshHandler = (sender, evt) => { this.refreshHandler = (sender: EventSource, evt: EventObject) => {
if (this.isEnabled()) { if (this.isEnabled()) {
this.refresh(); this.refresh();
} }
}; };
this.graph.getSelectionModel().addListener(InternalEvent.CHANGE, this.refreshHandler); this.graph.addListener(InternalEvent.CHANGE, this.refreshHandler);
this.graph.getModel().addListener(InternalEvent.CHANGE, this.refreshHandler); this.graph.getModel().addListener(InternalEvent.CHANGE, this.refreshHandler);
this.graph.getView().addListener(InternalEvent.SCALE, this.refreshHandler); this.graph.getView().addListener(InternalEvent.SCALE, this.refreshHandler);
this.graph.getView().addListener(InternalEvent.TRANSLATE, this.refreshHandler); this.graph.getView().addListener(InternalEvent.TRANSLATE, this.refreshHandler);
@ -66,42 +71,42 @@ class SelectionCellsHandler extends EventSource {
* *
* Reference to the enclosing <mxGraph>. * Reference to the enclosing <mxGraph>.
*/ */
graph: Graph; graph: MaxGraph;
/** /**
* Variable: enabled * Variable: enabled
* *
* Specifies if events are handled. Default is true. * Specifies if events are handled. Default is true.
*/ */
enabled: boolean = true; enabled = true;
/** /**
* Variable: refreshHandler * Variable: refreshHandler
* *
* Keeps a reference to an event listener for later removal. * Keeps a reference to an event listener for later removal.
*/ */
refreshHandler: any = null; refreshHandler: (sender: EventSource, evt: EventObject) => void;
/** /**
* Variable: maxHandlers * Variable: maxHandlers
* *
* Defines the maximum number of handlers to paint individually. Default is 100. * Defines the maximum number of handlers to paint individually. Default is 100.
*/ */
maxHandlers: number = 100; maxHandlers = 100;
/** /**
* Variable: handlers * Variable: handlers
* *
* <mxDictionary> that maps from cells to handlers. * <mxDictionary> that maps from cells to handlers.
*/ */
handlers: Dictionary<string, any>; handlers: Dictionary<Cell, EdgeHandler | VertexHandler>;
/** /**
* Function: isEnabled * Function: isEnabled
* *
* Returns <enabled>. * Returns <enabled>.
*/ */
isEnabled(): boolean { isEnabled() {
return this.enabled; return this.enabled;
} }
@ -110,7 +115,7 @@ class SelectionCellsHandler extends EventSource {
* *
* Sets <enabled>. * Sets <enabled>.
*/ */
setEnabled(value: boolean): void { setEnabled(value: boolean) {
this.enabled = value; this.enabled = value;
} }
@ -119,7 +124,7 @@ class SelectionCellsHandler extends EventSource {
* *
* Returns the handler for the given cell. * Returns the handler for the given cell.
*/ */
getHandler(cell: Cell): any { getHandler(cell: Cell) {
return this.handlers.get(cell); return this.handlers.get(cell);
} }
@ -128,8 +133,8 @@ class SelectionCellsHandler extends EventSource {
* *
* Returns true if the given cell has a handler. * Returns true if the given cell has a handler.
*/ */
isHandled(cell: Cell): boolean { isHandled(cell: Cell) {
return this.getHandler(cell) != null; return !!this.getHandler(cell);
} }
/** /**
@ -137,7 +142,7 @@ class SelectionCellsHandler extends EventSource {
* *
* Resets all handlers. * Resets all handlers.
*/ */
reset(): void { reset() {
this.handlers.visit((key, handler) => { this.handlers.visit((key, handler) => {
handler.reset.apply(handler); handler.reset.apply(handler);
}); });
@ -148,8 +153,8 @@ class SelectionCellsHandler extends EventSource {
* *
* Reloads or updates all handlers. * Reloads or updates all handlers.
*/ */
getHandledSelectionCells(): CellArray { getHandledSelectionCells() {
return this.graph.selection.getSelectionCells(); return this.graph.getSelectionCells();
} }
/** /**
@ -157,7 +162,7 @@ class SelectionCellsHandler extends EventSource {
* *
* Reloads or updates all handlers. * Reloads or updates all handlers.
*/ */
refresh(): void { refresh() {
// Removes all existing handlers // Removes all existing handlers
const oldHandlers = this.handlers; const oldHandlers = this.handlers;
this.handlers = new Dictionary(); this.handlers = new Dictionary();
@ -169,23 +174,22 @@ class SelectionCellsHandler extends EventSource {
for (let i = 0; i < tmp.length; i += 1) { for (let i = 0; i < tmp.length; i += 1) {
const state = this.graph.view.getState(tmp[i]); const state = this.graph.view.getState(tmp[i]);
if (state != null) { if (state) {
let handler = oldHandlers.remove(tmp[i]); let handler = oldHandlers.remove(tmp[i]);
if (handler != null) { if (handler) {
if (handler.state !== state) { if (handler.state !== state) {
handler.destroy(); handler.destroy();
handler = null; handler = null;
} else if (!this.isHandlerActive(handler)) { } else if (!this.isHandlerActive(handler)) {
if (handler.refresh != null) { // @ts-ignore refresh may exist
handler.refresh(); if (handler.refresh) handler.refresh();
}
handler.redraw(); handler.redraw();
} }
} }
if (handler != null) { if (handler) {
this.handlers.put(tmp[i], handler); this.handlers.put(tmp[i], handler);
} }
} }
@ -201,10 +205,10 @@ class SelectionCellsHandler extends EventSource {
for (let i = 0; i < tmp.length; i += 1) { for (let i = 0; i < tmp.length; i += 1) {
const state = this.graph.view.getState(tmp[i]); const state = this.graph.view.getState(tmp[i]);
if (state != null) { if (state) {
let handler = this.handlers.get(tmp[i]); let handler = this.handlers.get(tmp[i]);
if (handler == null) { if (!handler) {
handler = this.graph.createHandler(state); handler = this.graph.createHandler(state);
this.fireEvent(new EventObject(InternalEvent.ADD, 'state', state)); this.fireEvent(new EventObject(InternalEvent.ADD, 'state', state));
this.handlers.put(tmp[i], handler); this.handlers.put(tmp[i], handler);
@ -220,8 +224,8 @@ class SelectionCellsHandler extends EventSource {
* *
* Returns true if the given handler is active and should not be redrawn. * Returns true if the given handler is active and should not be redrawn.
*/ */
isHandlerActive(handler: any): boolean { isHandlerActive(handler: EdgeHandler | VertexHandler) {
return handler.index != null; return handler.index !== null;
} }
/** /**
@ -229,10 +233,10 @@ class SelectionCellsHandler extends EventSource {
* *
* Updates the handler for the given shape if one exists. * Updates the handler for the given shape if one exists.
*/ */
updateHandler(state: CellState): void { updateHandler(state: CellState) {
let handler = this.handlers.remove(state.cell); let handler = this.handlers.remove(state.cell);
if (handler != null) { if (handler) {
// Transfers the current state to the new handler // Transfers the current state to the new handler
const { index } = handler; const { index } = handler;
const x = handler.startX; const x = handler.startX;
@ -241,10 +245,10 @@ class SelectionCellsHandler extends EventSource {
handler.destroy(); handler.destroy();
handler = this.graph.createHandler(state); handler = this.graph.createHandler(state);
if (handler != null) { if (handler) {
this.handlers.put(state.cell, handler); this.handlers.put(state.cell, handler);
if (index != null && x != null && y != null) { if (index !== null) {
handler.start(x, y, index); handler.start(x, y, index);
} }
} }
@ -256,12 +260,10 @@ class SelectionCellsHandler extends EventSource {
* *
* Redirects the given event to the handlers. * Redirects the given event to the handlers.
*/ */
mouseDown(sender: Event, me: Event): void { mouseDown(sender: EventSource, me: InternalMouseEvent) {
if (this.graph.isEnabled() && this.isEnabled()) { if (this.graph.isEnabled() && this.isEnabled()) {
const args = [sender, me];
this.handlers.visit((key, handler) => { this.handlers.visit((key, handler) => {
handler.mouseDown.apply(handler, args); handler.mouseDown(sender, me);
}); });
} }
} }
@ -271,12 +273,10 @@ class SelectionCellsHandler extends EventSource {
* *
* Redirects the given event to the handlers. * Redirects the given event to the handlers.
*/ */
mouseMove(sender: Event, me: Event): void { mouseMove(sender: EventSource, me: InternalMouseEvent) {
if (this.graph.isEnabled() && this.isEnabled()) { if (this.graph.isEnabled() && this.isEnabled()) {
const args = [sender, me];
this.handlers.visit((key, handler) => { this.handlers.visit((key, handler) => {
handler.mouseMove.apply(handler, args); handler.mouseMove(sender, me);
}); });
} }
} }
@ -286,12 +286,10 @@ class SelectionCellsHandler extends EventSource {
* *
* Redirects the given event to the handlers. * Redirects the given event to the handlers.
*/ */
mouseUp(sender: Event, me: Event): void { mouseUp(sender: EventSource, me: InternalMouseEvent) {
if (this.graph.isEnabled() && this.isEnabled()) { if (this.graph.isEnabled() && this.isEnabled()) {
const args = [sender, me];
this.handlers.visit((key, handler) => { this.handlers.visit((key, handler) => {
handler.mouseUp.apply(handler, args); handler.mouseUp(sender, me);
}); });
} }
} }
@ -301,15 +299,11 @@ class SelectionCellsHandler extends EventSource {
* *
* Destroys the handler and all its resources and DOM nodes. * Destroys the handler and all its resources and DOM nodes.
*/ */
destroy(): void { onDestroy() {
this.graph.removeMouseListener(this); this.graph.removeMouseListener(this);
this.graph.removeListener(this.refreshHandler);
if (this.refreshHandler != null) { this.graph.getModel().removeListener(this.refreshHandler);
this.graph.selection.getSelectionModel().removeListener(this.refreshHandler); this.graph.getView().removeListener(this.refreshHandler);
this.graph.getModel().removeListener(this.refreshHandler);
this.graph.getView().removeListener(this.refreshHandler);
this.refreshHandler = null;
}
} }
} }

View File

@ -1,12 +1,10 @@
import EventObject from '../event/EventObject'; import EventObject from '../event/EventObject';
import Resources from '../../util/Resources'; import Resources from '../../util/Resources';
import mxLog from '../../util/gui/mxLog';
import InternalEvent from '../event/InternalEvent'; import InternalEvent from '../event/InternalEvent';
import mxGraphSelectionModel from '../view/selection/mxGraphSelectionModel'; import CellArray from '../cell/datatypes/CellArray';
import Cell from '../cell/datatypes/Cell';
import CellArray from "../cell/datatypes/CellArray";
import type { UndoableChange } from '../../types'; import type { UndoableChange } from '../../types';
import type { MaxGraph } from '../Graph';
/** /**
* @class SelectionChange * @class SelectionChange
@ -14,16 +12,16 @@ import type { UndoableChange } from '../../types';
*/ */
class SelectionChange implements UndoableChange { class SelectionChange implements UndoableChange {
constructor( constructor(
selectionModel: mxGraphSelectionModel, graph: MaxGraph,
added: CellArray = new CellArray(), added: CellArray = new CellArray(),
removed: CellArray = new CellArray() removed: CellArray = new CellArray()
) { ) {
this.selectionModel = selectionModel; this.graph = graph;
this.added = added.slice(); this.added = added.slice();
this.removed = removed.slice(); this.removed = removed.slice();
} }
selectionModel: mxGraphSelectionModel; graph: MaxGraph;
added: CellArray; added: CellArray;
@ -33,35 +31,25 @@ class SelectionChange implements UndoableChange {
* Changes the current root of the view. * Changes the current root of the view.
*/ */
execute() { execute() {
const t0: any = mxLog.enter('mxSelectionChange.execute');
window.status = window.status =
Resources.get(this.selectionModel.updatingSelectionResource) || Resources.get(this.graph.getUpdatingSelectionResource()) ||
this.selectionModel.updatingSelectionResource; this.graph.getUpdatingSelectionResource();
for (const removed of this.removed) { for (const removed of this.removed) {
this.selectionModel.cellRemoved(removed); this.graph.cellRemoved(removed);
} }
for (const added of this.added) { for (const added of this.added) {
this.selectionModel.cellAdded(added); this.graph.cellAdded(added);
} }
[this.added, this.removed] = [this.removed, this.added]; [this.added, this.removed] = [this.removed, this.added];
window.status = window.status =
Resources.get(this.selectionModel.doneResource) || Resources.get(this.graph.getDoneResource()) || this.graph.getDoneResource();
this.selectionModel.doneResource;
mxLog.leave('mxSelectionChange.execute', t0);
this.selectionModel.fireEvent( this.graph.fireEvent(
new EventObject( new EventObject(InternalEvent.CHANGE, 'added', this.added, 'removed', this.removed)
InternalEvent.CHANGE,
'added',
this.added,
'removed',
this.removed
)
); );
} }
} }

View File

@ -11,6 +11,8 @@ class GraphSnap extends autoImplement<PartialClass>() {
// TODO: Document me! // TODO: Document me!
tolerance: number = 0; tolerance: number = 0;
getSnapTolerance = () => this.tolerance;
/** /**
* Specifies the grid size. * Specifies the grid size.
* @default 10 * @default 10

View File

@ -9,10 +9,12 @@ import { fit, getScrollOrigin } from '../../util/Utils';
import { TOOLTIP_VERTICAL_OFFSET } from '../../util/Constants'; import { TOOLTIP_VERTICAL_OFFSET } from '../../util/Constants';
import { getSource, isMouseEvent } from '../../util/EventUtils'; import { getSource, isMouseEvent } from '../../util/EventUtils';
import { isNode } from '../../util/DomUtils'; import { isNode } from '../../util/DomUtils';
import Graph, { MaxGraph } from '../Graph'; import { MaxGraph } from '../Graph';
import CellState from '../cell/datatypes/CellState'; import CellState from '../cell/datatypes/CellState';
import InternalMouseEvent from '../event/InternalMouseEvent'; import InternalMouseEvent from '../event/InternalMouseEvent';
import { Listenable } from '../../types'; import PopupMenuHandler from '../popups_menus/PopupMenuHandler';
import type { GraphPlugin } from '../../types';
/** /**
* Class: mxTooltipHandler * Class: mxTooltipHandler
@ -38,14 +40,41 @@ import { Listenable } from '../../types';
* graph - Reference to the enclosing <mxGraph>. * graph - Reference to the enclosing <mxGraph>.
* delay - Optional delay in milliseconds. * delay - Optional delay in milliseconds.
*/ */
class TooltipHandler { class TooltipHandler implements GraphPlugin {
constructor(graph: MaxGraph, delay: number = 500) { static pluginId = 'TooltipHandler';
constructor(graph: MaxGraph) {
this.graph = graph; this.graph = graph;
this.delay = delay; this.delay = 500;
this.graph.addMouseListener(this); this.graph.addMouseListener(this);
this.div = document.createElement('div');
this.div.className = 'mxTooltip';
this.div.style.visibility = 'hidden';
document.body.appendChild(this.div);
InternalEvent.addGestureListeners(this.div, (evt) => {
const source = getSource(evt);
// @ts-ignore nodeName may exist
if (source && source.nodeName !== 'A') {
this.hideTooltip();
}
});
// Hides tooltips and resets tooltip timer if mouse leaves container
InternalEvent.addListener(
this.graph.getContainer(),
'mouseleave',
(evt: MouseEvent) => {
if (this.div !== evt.relatedTarget) {
this.hide();
}
}
);
} }
// @ts-ignore Cannot be null.
div: HTMLElement; div: HTMLElement;
/** /**
@ -60,7 +89,7 @@ class TooltipHandler {
* *
* Reference to the enclosing <mxGraph>. * Reference to the enclosing <mxGraph>.
*/ */
graph: Graph; graph: MaxGraph;
/** /**
* Variable: delay * Variable: delay
@ -91,10 +120,10 @@ class TooltipHandler {
*/ */
destroyed = false; destroyed = false;
lastX: number = 0; lastX = 0;
lastY: number = 0; lastY = 0;
state: CellState | null = null; state: CellState | null = null;
stateSource: boolean = false; stateSource = false;
node: any; node: any;
thread: number | null = null; thread: number | null = null;
@ -143,27 +172,6 @@ class TooltipHandler {
this.hideOnHover = value; this.hideOnHover = value;
} }
/**
* Function: init
*
* Initializes the DOM nodes required for this tooltip handler.
*/
init() {
this.div = document.createElement('div');
this.div.className = 'mxTooltip';
this.div.style.visibility = 'hidden';
document.body.appendChild(this.div);
InternalEvent.addGestureListeners(this.div, (evt) => {
const source = getSource(evt);
if (source.nodeName !== 'A') {
this.hideTooltip();
}
});
}
/** /**
* Function: getStateForEvent * Function: getStateForEvent
* *
@ -180,7 +188,7 @@ class TooltipHandler {
* event all subsequent events of the gesture are redirected to this * event all subsequent events of the gesture are redirected to this
* handler. * handler.
*/ */
mouseDown(sender: any, me: InternalMouseEvent) { mouseDown(sender: EventSource, me: InternalMouseEvent) {
this.reset(me, false); this.reset(me, false);
this.hideTooltip(); this.hideTooltip();
} }
@ -190,7 +198,7 @@ class TooltipHandler {
* *
* Handles the event by updating the rubberband selection. * Handles the event by updating the rubberband selection.
*/ */
mouseMove(sender: Listenable, me: InternalMouseEvent) { mouseMove(sender: EventSource, me: InternalMouseEvent) {
if (me.getX() !== this.lastX || me.getY() !== this.lastY) { if (me.getX() !== this.lastX || me.getY() !== this.lastY) {
this.reset(me, true); this.reset(me, true);
const state = this.getStateForEvent(me); const state = this.getStateForEvent(me);
@ -218,7 +226,7 @@ class TooltipHandler {
* Handles the event by resetting the tooltip timer or hiding the existing * Handles the event by resetting the tooltip timer or hiding the existing
* tooltip. * tooltip.
*/ */
mouseUp(sender: any, me: InternalMouseEvent): void { mouseUp(sender: EventSource, me: InternalMouseEvent) {
this.reset(me, true); this.reset(me, true);
this.hideTooltip(); this.hideTooltip();
} }
@ -228,8 +236,8 @@ class TooltipHandler {
* *
* Resets the timer. * Resets the timer.
*/ */
resetTimer(): void { resetTimer() {
if (this.thread !== null) { if (this.thread) {
window.clearTimeout(this.thread); window.clearTimeout(this.thread);
this.thread = null; this.thread = null;
} }
@ -255,18 +263,28 @@ class TooltipHandler {
const x = me.getX(); const x = me.getX();
const y = me.getY(); const y = me.getY();
const stateSource = me.isSource(state.shape) || me.isSource(state.text); const stateSource = me.isSource(state.shape) || me.isSource(state.text);
const popupMenuHandler = this.graph.getPlugin(
'PopupMenuHandler'
) as PopupMenuHandler;
this.thread = window.setTimeout(() => { this.thread = window.setTimeout(() => {
if ( if (
state && state &&
node &&
!this.graph.isEditing() && !this.graph.isEditing() &&
!this.graph.popupMenuHandler.isMenuShowing() && popupMenuHandler &&
!popupMenuHandler.isMenuShowing() &&
!this.graph.isMouseDown !this.graph.isMouseDown
) { ) {
// Uses information from inside event cause using the event at // Uses information from inside event cause using the event at
// this (delayed) point in time is not possible in IE as it no // this (delayed) point in time is not possible in IE as it no
// longer contains the required information (member not found) // longer contains the required information (member not found)
const tip = this.graph.getTooltip(state, node, x, y); const tip = this.graph.getTooltip(
state,
node as HTMLElement | SVGElement,
x,
y
);
this.show(tip, x, y); this.show(tip, x, y);
this.state = state; this.state = state;
this.node = node; this.node = node;
@ -328,12 +346,12 @@ class TooltipHandler {
* *
* Destroys the handler and all its resources and DOM nodes. * Destroys the handler and all its resources and DOM nodes.
*/ */
destroy() { onDestroy() {
if (!this.destroyed) { if (!this.destroyed) {
this.graph.removeMouseListener(this); this.graph.removeMouseListener(this);
InternalEvent.release(this.div); InternalEvent.release(this.div);
if (this.div.parentNode != null) { if (this.div.parentNode) {
this.div.parentNode.removeChild(this.div); this.div.parentNode.removeChild(this.div);
} }

View File

@ -51,6 +51,8 @@ import CellArray from '../cell/datatypes/CellArray';
import type { MaxGraph } from '../Graph'; import type { MaxGraph } from '../Graph';
import StyleRegistry from '../style/StyleRegistry'; import StyleRegistry from '../style/StyleRegistry';
import TooltipHandler from '../tooltip/TooltipHandler';
import { MouseEventListener } from '../../types';
/** /**
* @class GraphView * @class GraphView
@ -687,30 +689,26 @@ class GraphView extends EventSource {
// container and finishing the handling of a single gesture // container and finishing the handling of a single gesture
InternalEvent.addGestureListeners( InternalEvent.addGestureListeners(
this.backgroundPageShape.node, this.backgroundPageShape.node,
(evt: Event) => { (evt: MouseEvent) => {
graph.fireMouseEvent( graph.fireMouseEvent(InternalEvent.MOUSE_DOWN, new InternalMouseEvent(evt));
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt as MouseEvent)
);
}, },
(evt: Event) => { (evt: MouseEvent) => {
const tooltipHandler = graph.getPlugin('TooltipHandler') as TooltipHandler;
// Hides the tooltip if mouse is outside container // Hides the tooltip if mouse is outside container
if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) { if (tooltipHandler && tooltipHandler.isHideOnHover()) {
graph.tooltipHandler.hide(); tooltipHandler.hide();
} }
if (graph.isMouseDown && !isConsumed(evt)) { if (graph.isMouseDown && !isConsumed(evt)) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent) new InternalMouseEvent(evt)
); );
} }
}, },
(evt: Event) => { (evt: MouseEvent) => {
graph.fireMouseEvent( graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt));
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent)
);
} }
); );
} }
@ -2034,19 +2032,22 @@ class GraphView extends EventSource {
* Returns true if the event origin is one of the drawing panes or * Returns true if the event origin is one of the drawing panes or
* containers of the view. * containers of the view.
*/ */
isContainerEvent(evt: Event | MouseEvent) { isContainerEvent(evt: MouseEvent) {
const source = getSource(evt); const source = getSource(evt);
return ( return (
source === this.graph.container || source &&
source.parentNode === this.backgroundPane || (source === this.graph.container ||
(source.parentNode && source.parentNode.parentNode === this.backgroundPane) || // @ts-ignore parentNode may exist
source === this.canvas.parentNode || source.parentNode === this.backgroundPane ||
source === this.canvas || // @ts-ignore parentNode may exist
source === this.backgroundPane || (source.parentNode && source.parentNode.parentNode === this.backgroundPane) ||
source === this.drawPane || source === this.canvas.parentNode ||
source === this.overlayPane || source === this.canvas ||
source === this.decoratorPane source === this.backgroundPane ||
source === this.drawPane ||
source === this.overlayPane ||
source === this.decoratorPane)
); );
} }
@ -2125,24 +2126,18 @@ class GraphView extends EventSource {
pointerId = evt.pointerId; pointerId = evt.pointerId;
} }
}) as EventListener, }) as EventListener,
(evt: Event) => { (evt: MouseEvent) => {
if ( if (
this.isContainerEvent(evt) && this.isContainerEvent(evt) &&
// @ts-ignore // @ts-ignore
(pointerId === null || evt.pointerId === pointerId) (pointerId === null || evt.pointerId === pointerId)
) { ) {
graph.fireMouseEvent( graph.fireMouseEvent(InternalEvent.MOUSE_MOVE, new InternalMouseEvent(evt));
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent)
);
} }
}, },
(evt: Event) => { (evt: MouseEvent) => {
if (this.isContainerEvent(evt)) { if (this.isContainerEvent(evt)) {
graph.fireMouseEvent( graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt));
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent)
);
} }
pointerId = null; pointerId = null;
@ -2161,7 +2156,7 @@ class GraphView extends EventSource {
// Workaround for touch events which started on some DOM node // Workaround for touch events which started on some DOM node
// on top of the container, in which case the cells under the // on top of the container, in which case the cells under the
// mouse for the move and up events are not detected. // mouse for the move and up events are not detected.
const getState = (evt: Event) => { const getState = (evt: MouseEvent) => {
let state = null; let state = null;
// Workaround for touch events which started on some DOM node // Workaround for touch events which started on some DOM node
@ -2188,16 +2183,20 @@ class GraphView extends EventSource {
// in Firefox and Chrome // in Firefox and Chrome
graph.addMouseListener({ graph.addMouseListener({
mouseDown: (sender: any, me: InternalMouseEvent) => { mouseDown: (sender: any, me: InternalMouseEvent) => {
(<PopupMenuHandler>graph.popupMenuHandler).hideMenu(); const popupMenuHandler = graph.getPlugin('PopupMenuHandler') as PopupMenuHandler;
if (popupMenuHandler) popupMenuHandler.hideMenu();
}, },
mouseMove: () => {}, mouseMove: () => {},
mouseUp: () => {}, mouseUp: () => {},
}); });
this.moveHandler = (evt: Event) => { this.moveHandler = (evt: MouseEvent) => {
const tooltipHandler = graph.getPlugin('TooltipHandler') as TooltipHandler;
// Hides the tooltip if mouse is outside container // Hides the tooltip if mouse is outside container
if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) { if (tooltipHandler && tooltipHandler.isHideOnHover()) {
graph.tooltipHandler.hide(); tooltipHandler.hide();
} }
if ( if (
@ -2211,12 +2210,12 @@ class GraphView extends EventSource {
) { ) {
graph.fireMouseEvent( graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE, InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent, getState(evt)) new InternalMouseEvent(evt, getState(evt))
); );
} }
}; };
this.endHandler = (evt: Event) => { this.endHandler = (evt: MouseEvent) => {
if ( if (
this.captureDocumentGesture && this.captureDocumentGesture &&
graph.isMouseDown && graph.isMouseDown &&
@ -2225,10 +2224,7 @@ class GraphView extends EventSource {
graph.container.style.display !== 'none' && graph.container.style.display !== 'none' &&
graph.container.style.visibility !== 'hidden' graph.container.style.visibility !== 'hidden'
) { ) {
graph.fireMouseEvent( graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt));
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent)
);
} }
}; };
@ -2320,8 +2316,8 @@ class GraphView extends EventSource {
} }
} }
endHandler: EventListener | null = null; endHandler: MouseEventListener | null = null;
moveHandler: EventListener | null = null; moveHandler: MouseEventListener | null = null;
} }
export default GraphView; export default GraphView;