- Converting *Handlers into plugins.

- Keep resolving errors.
development
Junsik Shim 2021-08-30 18:45:01 +09:00
parent 648e324cc0
commit 61648e43ce
41 changed files with 1851 additions and 1884 deletions

View File

@ -1,5 +1,6 @@
import type Cell from './view/cell/datatypes/Cell';
import type CellState from './view/cell/datatypes/CellState';
import EventSource from './view/event/EventSource';
import type InternalMouseEvent from './view/event/InternalMouseEvent';
import type Shape from './view/geometry/shape/Shape';
import type { MaxGraph } from './view/Graph';
@ -43,6 +44,7 @@ export type CellStateStyles = {
direction: DirectionValue;
edge: string;
editable: boolean;
elbow: string;
endArrow: ArrowType;
endFill: boolean;
endSize: number;
@ -93,6 +95,7 @@ export type CellStateStyles = {
movable: boolean;
noEdgeStyle: boolean;
opacity: number;
orthogonal: boolean | null;
overflow: OverflowValue;
perimeter: Function | string | null;
perimeterSpacing: number;
@ -226,7 +229,7 @@ export interface GraphPlugin {
export type Listener = {
name: string;
f: MouseEventListener;
f: MouseEventListener | KeyboardEventListener;
};
export type ListenerTarget = {
@ -236,6 +239,7 @@ export type ListenerTarget = {
export type Listenable = (EventSource | EventTarget) & ListenerTarget;
export type MouseEventListener = (me: MouseEvent) => void;
export type KeyboardEventListener = (ke: KeyboardEvent) => void;
export type GestureEvent = Event &
MouseEvent & {
@ -265,3 +269,13 @@ export interface CellHandle {
redraw: () => void;
destroy: () => void;
}
export interface PopupMenuItem extends HTMLElement {
table: HTMLElement;
tbody: HTMLElement;
div: HTMLElement;
willAddSeparator: boolean;
containsItems: boolean;
activeRow: PopupMenuItem | null;
eventReceiver: HTMLElement | null;
}

View File

@ -43,7 +43,7 @@ export const getSource = (evt: MouseEvent) => {
/**
* Returns true if the event has been consumed using {@link consume}.
*/
export const isConsumed = (evt: MouseEvent) => {
export const isConsumed = (evt: MouseEvent | KeyboardEvent) => {
const t = evt as any;
return t.isConsumed !== undefined && t.isConsumed;
};

View File

@ -11,9 +11,10 @@ import Polyline from '../view/geometry/shape/edge/Polyline';
import CellState from '../view/cell/datatypes/CellState';
import Shape from '../view/geometry/shape/Shape';
import Rectangle from '../view/geometry/Rectangle';
import graph from '../view/Graph';
import { MaxGraph } from '../view/Graph';
import EventObject from '../view/event/EventObject';
import GraphView from '../view/view/GraphView';
import { ColorValue } from '../types';
/**
* Class: mxGuide
@ -25,7 +26,7 @@ import GraphView from '../view/view/GraphView';
* Constructs a new guide object.
*/
class Guide {
constructor(graph: graph, states: CellState[]) {
constructor(graph: MaxGraph, states: CellState[]) {
this.graph = graph;
this.setStates(states);
}
@ -35,39 +36,34 @@ class Guide {
*
* Reference to the enclosing <mxGraph> instance.
*/
// graph: mxGraph;
graph: graph;
graph: MaxGraph;
/**
* Variable: states
*
* Contains the <mxCellStates> that are used for alignment.
*/
// states: mxCellState[];
states: CellState[] | null = null;
states: CellState[] = [];
/**
* Variable: horizontal
*
* Specifies if horizontal guides are enabled. Default is true.
*/
// horizontal: boolean;
horizontal: boolean = true;
horizontal = true;
/**
* Variable: vertical
*
* Specifies if vertical guides are enabled. Default is true.
*/
// vertical: boolean;
vertical: boolean = true;
vertical = true;
/**
* Variable: vertical
*
* Holds the <mxShape> for the horizontal guide.
*/
// guideX: mxShape;
guideX: Shape | null = null;
/**
@ -75,7 +71,6 @@ class Guide {
*
* Holds the <mxShape> for the vertical guide.
*/
// guideY: mxShape;
guideY: Shape | null = null;
/**
@ -83,22 +78,21 @@ class Guide {
*
* Specifies if rounded coordinates should be used. Default is false.
*/
rounded: boolean = false;
rounded = false;
/**
* Variable: tolerance
*
* Default tolerance in px if grid is disabled. Default is 2.
*/
tolerance: number = 2;
tolerance = 2;
/**
* Function: setStates
*
* Sets the <mxCellStates> that should be used for alignment.
*/
// setStates(states: mxCellState[]): void;
setStates(states: CellState[]): void {
setStates(states: CellState[]) {
this.states = states;
}
@ -108,8 +102,7 @@ class Guide {
* Returns true if the guide should be enabled for the given native event. This
* implementation always returns true.
*/
// isEnabledForEvent(evt: Event): boolean;
isEnabledForEvent(evt: EventObject | null = null): boolean {
isEnabledForEvent(evt: MouseEvent) {
return true;
}
@ -118,9 +111,9 @@ class Guide {
*
* Returns the tolerance for the guides. Default value is gridSize / 2.
*/
getGuideTolerance(gridEnabled: boolean = false): number {
return gridEnabled && this.graph.gridEnabled
? this.graph.gridSize / 2
getGuideTolerance(gridEnabled = false) {
return gridEnabled && this.graph.isGridEnabled()
? this.graph.getGridSize() / 2
: this.tolerance;
}
@ -135,7 +128,7 @@ class Guide {
*
* horizontal - Boolean that specifies which guide should be created.
*/
createGuideShape(horizontal: boolean = false): Polyline {
createGuideShape(horizontal = false) {
// TODO: Should vertical guides be supported here?? ============================
const guide = new Polyline([], GUIDE_COLOR, GUIDE_STROKEWIDTH);
guide.isDashed = true;
@ -146,7 +139,7 @@ class Guide {
* Returns true if the given state should be ignored.
* @param state
*/
isStateIgnored(state: CellState | null = null): boolean {
isStateIgnored(state: CellState) {
return false;
}
@ -158,15 +151,10 @@ class Guide {
move(
bounds: Rectangle | null = null,
delta: Point,
gridEnabled: boolean = false,
clone: boolean = false
): Point {
if (
this.states != null &&
(this.horizontal || this.vertical) &&
bounds != null &&
delta != null
gridEnabled = false,
clone = false
) {
if ((this.horizontal || this.vertical) && bounds) {
const { scale } = this.graph.getView();
const tt = this.getGuideTolerance(gridEnabled) * scale;
const b = bounds.clone();
@ -174,10 +162,10 @@ class Guide {
b.y += delta.y;
let overrideX = false;
let stateX: CellState | null = null;
let valueX = null;
let valueX: number | null = null;
let overrideY = false;
let stateY: CellState | null = null;
let valueY = null;
let valueY: number | null = null;
let ttX = tt;
let ttY = tt;
const left = b.x;
@ -211,7 +199,7 @@ class Guide {
stateX = state;
valueX = x;
if (this.guideX == null) {
if (!this.guideX) {
this.guideX = this.createGuideShape(true);
// Makes sure to use SVG shapes in order to implement
@ -250,7 +238,7 @@ class Guide {
stateY = state;
valueY = y;
if (this.guideY == null) {
if (!this.guideY) {
this.guideY = this.createGuideShape(false);
// Makes sure to use SVG shapes in order to implement
@ -268,7 +256,7 @@ class Guide {
for (let i = 0; i < this.states.length; i += 1) {
const state = this.states[i];
if (state != null && !this.isStateIgnored(state)) {
if (state && !this.isStateIgnored(state)) {
// Align x
if (this.horizontal) {
snapX(state.getCenterX(), state, true);
@ -276,7 +264,7 @@ class Guide {
snapX(state.x + state.width, state, false);
// Aligns left and right of shape to center of page
if (state.cell == null) {
if (!state.cell) {
snapX(state.getCenterX(), state, false);
}
}
@ -288,7 +276,7 @@ class Guide {
snapY(state.y + state.height, state, false);
// Aligns left and right of shape to center of page
if (state.cell == null) {
if (!state.cell) {
snapY(state.getCenterY(), state, false);
}
}
@ -300,69 +288,63 @@ class Guide {
delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y);
// Redraws the guides
const c = <HTMLElement>this.graph.container;
const c = this.graph.container;
if (!overrideX && this.guideX != null) {
(<SVGElement>this.guideX.node).style.visibility = 'hidden';
} else if (this.guideX != null) {
let minY = null;
let maxY = null;
if (!overrideX && this.guideX) {
this.guideX.node.style.visibility = 'hidden';
} else if (this.guideX) {
let minY: number | null = null;
let maxY: number | null = null;
if (stateX != null) {
minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y);
if (stateX) {
minY = Math.min(bounds.y + delta.y - this.graph.getPanDy(), stateX!.y);
maxY = Math.max(
bounds.y + bounds.height + delta.y - this.graph.panDy,
// @ts-ignore
stateX.y + stateX.height
bounds.y + bounds.height + delta.y - this.graph.getPanDy(),
// @ts-ignore stateX! doesn't work for some reason...
stateX!.y + stateX!.height
);
}
if (minY != null && maxY != null) {
this.guideX.points = [
new Point(valueX, minY),
new Point(valueX, maxY),
];
if (minY !== null && maxY !== null) {
this.guideX.points = [new Point(valueX!, minY), new Point(valueX!, maxY)];
} else {
this.guideX.points = [
new Point(valueX, -this.graph.panDy),
new Point(valueX, c.scrollHeight - 3 - this.graph.panDy),
new Point(valueX!, -this.graph.getPanDy()),
new Point(valueX!, c.scrollHeight - 3 - this.graph.getPanDy()),
];
}
this.guideX.stroke = this.getGuideColor(stateX, true);
(<SVGElement>this.guideX.node).style.visibility = 'visible';
this.guideX.stroke = this.getGuideColor(stateX!, true);
this.guideX.node.style.visibility = 'visible';
this.guideX.redraw();
}
if (!overrideY && this.guideY != null) {
(<SVGElement>this.guideY.node).style.visibility = 'hidden';
this.guideY.node.style.visibility = 'hidden';
} else if (this.guideY != null) {
let minX = null;
let maxX = null;
if (stateY != null && bounds != null) {
minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x);
minX = Math.min(bounds.x + delta.x - this.graph.getPanDx(), stateY!.x);
maxX = Math.max(
bounds.x + bounds.width + delta.x - this.graph.panDx,
bounds.x + bounds.width + delta.x - this.graph.getPanDx(),
// @ts-ignore
stateY.x + stateY.width
);
}
if (minX != null && maxX != null) {
if (minX != null && maxX != null && valueY !== null) {
this.guideY.points = [new Point(minX, valueY), new Point(maxX, valueY)];
} else if (valueY !== null) {
this.guideY.points = [
new Point(minX, valueY),
new Point(maxX, valueY),
];
} else {
this.guideY.points = [
new Point(-this.graph.panDx, valueY),
new Point(c.scrollWidth - 3 - this.graph.panDx, valueY),
new Point(-this.graph.getPanDx(), valueY),
new Point(c.scrollWidth - 3 - this.graph.getPanDx(), valueY),
];
}
this.guideY.stroke = this.getGuideColor(stateY, false);
(<SVGElement>this.guideY.node).style.visibility = 'visible';
this.guideY.stroke = this.getGuideColor(stateY!, false);
this.guideY.node.style.visibility = 'visible';
this.guideY.redraw();
}
}
@ -382,7 +364,7 @@ class Guide {
stateY: CellState | null = null,
dy: number
): Point {
const s = (<GraphView>this.graph.view).scale;
const s = this.graph.view.scale;
if (this.rounded || (stateX != null && stateX.cell == null)) {
dx = Math.round((bounds.x + dx) / s) * s - bounds.x;
}
@ -397,9 +379,7 @@ class Guide {
*
* Hides all current guides.
*/
// getGuideColor(state: mxCellState, horizontal: any): string;
getGuideColor(state: CellState | null,
horizontal: boolean | null): string {
getGuideColor(state: CellState, horizontal: boolean) {
return GUIDE_COLOR;
}
@ -408,7 +388,7 @@ class Guide {
*
* Hides all current guides.
*/
hide(): void {
hide() {
this.setVisible(false);
}
@ -417,16 +397,12 @@ class Guide {
*
* Shows or hides the current guides.
*/
setVisible(visible: boolean): void {
if (this.guideX != null) {
(<SVGElement>this.guideX.node).style.visibility = visible
? 'visible'
: 'hidden';
setVisible(visible: boolean) {
if (this.guideX) {
this.guideX.node.style.visibility = visible ? 'visible' : 'hidden';
}
if (this.guideY != null) {
(<SVGElement>this.guideY.node).style.visibility = visible
? 'visible'
: 'hidden';
if (this.guideY) {
this.guideY.node.style.visibility = visible ? 'visible' : 'hidden';
}
}
@ -435,12 +411,12 @@ class Guide {
*
* Destroys all resources that this object uses.
*/
destroy(): void {
if (this.guideX != null) {
destroy() {
if (this.guideX) {
this.guideX.destroy();
this.guideX = null;
}
if (this.guideY != null) {
if (this.guideY) {
this.guideY.destroy();
this.guideY = null;
}

View File

@ -172,11 +172,11 @@ export const setPrefixedStyle = (
prefix = 'Moz';
}
style[name] = value;
style.setProperty(name, value);
if (prefix !== null && name.length > 0) {
name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
style[name] = value;
style.setProperty(name, value);
}
};

View File

@ -5,12 +5,15 @@
* Type definitions from the typed-mxgraph project
*/
import EventSource from '../../view/event/EventSource';
import utils, { fit, getDocumentScrollOrigin } from '../Utils';
import { fit, getDocumentScrollOrigin } from '../Utils';
import EventObject from '../../view/event/EventObject';
import mxClient from '../../mxClient';
import InternalEvent from '../../view/event/InternalEvent';
import { write } from '../DomUtils';
import { isLeftMouseButton } from '../EventUtils';
import Cell from '../../view/cell/datatypes/Cell';
import InternalMouseEvent from '../../view/event/InternalMouseEvent';
import { PopupMenuItem } from '../../types';
/**
* Class: mxPopupMenu
@ -38,23 +41,14 @@ import { isLeftMouseButton } from '../EventUtils';
*
* Fires after the menu has been shown in <popup>.
*/
class mxPopupMenu extends EventSource {
constructor(factoryMethod) {
class PopupMenu extends EventSource implements Partial<PopupMenuItem> {
constructor(
factoryMethod?: (handler: PopupMenuItem, cell: Cell | null, me: MouseEvent) => void
) {
super();
this.factoryMethod = factoryMethod;
if (factoryMethod != null) {
this.init();
}
}
/**
* Function: init
*
* Initializes the shapes required for this vertex handler.
*/
// init(): void;
init() {
// Adds the inner table
this.table = document.createElement('table');
this.table.className = 'mxPopupMenu';
@ -66,19 +60,24 @@ class mxPopupMenu extends EventSource {
this.div = document.createElement('div');
this.div.className = 'mxPopupMenu';
this.div.style.display = 'inline';
this.div.style.zIndex = this.zIndex;
this.div.style.zIndex = String(this.zIndex);
this.div.appendChild(this.table);
// Disables the context menu on the outer div
InternalEvent.disableContextMenu(this.div);
}
div: HTMLElement;
table: HTMLElement;
tbody: HTMLElement;
activeRow: PopupMenuItem | null = null;
eventReceiver: HTMLElement | null = null;
/**
* Variable: submenuImage
*
* URL of the image to be used for the submenu icon.
*/
// submenuImage: string;
submenuImage = `${mxClient.imageBasePath}/submenu.gif`;
/**
@ -86,7 +85,6 @@ class mxPopupMenu extends EventSource {
*
* Specifies the zIndex for the popupmenu and its shadow. Default is 1006.
*/
// zIndex: number;
zIndex = 10006;
/**
@ -96,8 +94,7 @@ class mxPopupMenu extends EventSource {
* current panning handler, the <mxCell> under the mouse and the mouse
* event that triggered the call as arguments.
*/
// factoryMethod: (handler: mxPopupMenuHandler, cell: mxCell, me: mxMouseEvent) => any;
factoryMethod = null;
factoryMethod?: (handler: PopupMenuItem, cell: Cell | null, me: MouseEvent) => void;
/**
* Variable: useLeftButtonForPopup
@ -105,7 +102,6 @@ class mxPopupMenu extends EventSource {
* Specifies if popupmenus should be activated by clicking the left mouse
* button. Default is false.
*/
// useLeftButtonForPopup: boolean;
useLeftButtonForPopup = false;
/**
@ -113,7 +109,6 @@ class mxPopupMenu extends EventSource {
*
* Specifies if events are handled. Default is true.
*/
// enabled: boolean;
enabled = true;
/**
@ -121,7 +116,6 @@ class mxPopupMenu extends EventSource {
*
* Contains the number of times <addItem> has been called for a new menu.
*/
// itemCount: number;
itemCount = 0;
/**
@ -129,7 +123,6 @@ class mxPopupMenu extends EventSource {
*
* Specifies if submenus should be expanded on mouseover. Default is false.
*/
// autoExpand: boolean;
autoExpand = false;
/**
@ -138,7 +131,6 @@ class mxPopupMenu extends EventSource {
* Specifies if separators should only be added if a menu item follows them.
* Default is false.
*/
// smartSeparators: boolean;
smartSeparators = false;
/**
@ -146,16 +138,17 @@ class mxPopupMenu extends EventSource {
*
* Specifies if any labels should be visible. Default is true.
*/
// labels: boolean;
labels = true;
willAddSeparator = false;
containsItems = false;
/**
* Function: isEnabled
*
* Returns true if events are handled. This implementation
* returns <enabled>.
*/
// isEnabled(): boolean;
isEnabled() {
return this.enabled;
}
@ -166,8 +159,7 @@ class mxPopupMenu extends EventSource {
* Enables or disables event handling. This implementation
* updates <enabled>.
*/
// setEnabled(enabled: boolean): void;
setEnabled(enabled) {
setEnabled(enabled: boolean) {
this.enabled = enabled;
}
@ -181,8 +173,7 @@ class mxPopupMenu extends EventSource {
*
* me - <mxMouseEvent> that represents the mouse event.
*/
// isPopupTrigger(me: mxMouseEvent): boolean;
isPopupTrigger(me) {
isPopupTrigger(me: InternalMouseEvent) {
return (
me.isPopupTrigger() ||
(this.useLeftButtonForPopup && isLeftMouseButton(me.getEvent()))
@ -210,8 +201,17 @@ class mxPopupMenu extends EventSource {
* Default is true.
* noHover - Optional boolean to disable hover state.
*/
addItem(title, image, funct, parent, iconCls, enabled, active, noHover) {
parent = parent || this;
addItem(
title: string,
image: string | null,
funct: Function,
parent: PopupMenuItem | null = null,
iconCls?: string,
enabled?: boolean,
active?: boolean,
noHover?: boolean
) {
parent = (parent ?? this) as PopupMenuItem;
this.itemCount++;
// Smart separators only added if element contains items
@ -224,17 +224,18 @@ class mxPopupMenu extends EventSource {
}
parent.containsItems = true;
const tr = document.createElement('tr');
const tr = <PopupMenuItem>(<unknown>document.createElement('tr'));
tr.className = 'mxPopupMenuItem';
const col1 = document.createElement('td');
col1.className = 'mxPopupMenuIcon';
// Adds the given image into the first column
if (image != null) {
if (image) {
const img = document.createElement('img');
img.src = image;
col1.appendChild(img);
} else if (iconCls != null) {
} else if (iconCls) {
const div = document.createElement('div');
div.className = iconCls;
col1.appendChild(div);
@ -244,18 +245,14 @@ class mxPopupMenu extends EventSource {
if (this.labels) {
const col2 = document.createElement('td');
col2.className = `mxPopupMenuItem${
enabled != null && !enabled ? ' mxDisabled' : ''
}`;
col2.className = `mxPopupMenuItem${!enabled ? ' mxDisabled' : ''}`;
write(col2, title);
col2.align = 'left';
tr.appendChild(col2);
const col3 = document.createElement('td');
col3.className = `mxPopupMenuItem${
enabled != null && !enabled ? ' mxDisabled' : ''
}`;
col3.className = `mxPopupMenuItem${!enabled ? ' mxDisabled' : ''}`;
col3.style.paddingRight = '6px';
col3.style.textAlign = 'right';
@ -266,21 +263,16 @@ class mxPopupMenu extends EventSource {
}
}
parent.tbody.appendChild(tr);
if (active != false && enabled != false) {
let currentSelection = null;
parent.tbody?.appendChild(tr);
if (active && enabled) {
InternalEvent.addGestureListeners(
tr,
evt => {
(evt) => {
this.eventReceiver = tr;
if (parent.activeRow != tr && parent.activeRow != parent) {
if (
parent.activeRow != null &&
parent.activeRow.div.parentNode != null
) {
if (parent && parent.activeRow != tr && parent.activeRow != parent) {
if (parent.activeRow != null && parent.activeRow.div.parentNode != null) {
this.hideSubmenu(parent);
}
@ -292,12 +284,9 @@ class mxPopupMenu extends EventSource {
InternalEvent.consume(evt);
},
evt => {
if (parent.activeRow != tr && parent.activeRow != parent) {
if (
parent.activeRow != null &&
parent.activeRow.div.parentNode != null
) {
(evt) => {
if (parent && parent.activeRow != tr && parent.activeRow != parent) {
if (parent.activeRow != null && parent.activeRow.div.parentNode != null) {
this.hideSubmenu(parent);
}
@ -312,26 +301,14 @@ class mxPopupMenu extends EventSource {
tr.className = 'mxPopupMenuItemHover';
}
},
evt => {
(evt) => {
// EventReceiver avoids clicks on a submenu item
// which has just been shown in the mousedown
if (this.eventReceiver == tr) {
if (parent.activeRow != tr) {
if (parent && parent.activeRow != tr) {
this.hideMenu();
}
// Workaround for lost current selection in page because of focus in IE
if (currentSelection != null) {
// Workaround for "unspecified error" in IE8 standards
try {
currentSelection.select();
} catch (e) {
// ignore
}
currentSelection = null;
}
if (funct != null) {
funct(evt);
}
@ -344,7 +321,7 @@ class mxPopupMenu extends EventSource {
// Resets hover style because TR in IE doesn't have hover
if (!noHover) {
InternalEvent.addListener(tr, 'mouseout', evt => {
InternalEvent.addListener(tr, 'mouseout', (evt: MouseEvent) => {
tr.className = 'mxPopupMenuItem';
});
}
@ -356,13 +333,14 @@ class mxPopupMenu extends EventSource {
/**
* Adds a checkmark to the given menuitem.
*/
// addCheckmark(item: Element, img: string): void;
addCheckmark(item, img) {
const td = item.firstChild.nextSibling;
addCheckmark(item: HTMLElement, img: string) {
if (item.firstChild) {
const td = item.firstChild.nextSibling as HTMLElement;
td.style.backgroundImage = `url('${img}')`;
td.style.backgroundRepeat = 'no-repeat';
td.style.backgroundPosition = '2px 50%';
}
}
/**
* Function: createSubmenu
@ -375,8 +353,7 @@ class mxPopupMenu extends EventSource {
*
* parent - An item returned by <addItem>.
*/
// createSubmenu(parent: Element): void;
createSubmenu(parent) {
createSubmenu(parent: PopupMenuItem) {
parent.table = document.createElement('table');
parent.table.className = 'mxPopupMenu';
@ -388,7 +365,7 @@ class mxPopupMenu extends EventSource {
parent.div.style.position = 'absolute';
parent.div.style.display = 'inline';
parent.div.style.zIndex = this.zIndex;
parent.div.style.zIndex = String(this.zIndex);
parent.div.appendChild(parent.table);
@ -396,9 +373,11 @@ class mxPopupMenu extends EventSource {
img.setAttribute('src', this.submenuImage);
// Last column of the submenu item in the parent menu
td = parent.firstChild.nextSibling.nextSibling;
if (parent.firstChild?.nextSibling?.nextSibling) {
const td = parent.firstChild.nextSibling.nextSibling;
td.appendChild(img);
}
}
/**
* Function: showSubmenu
@ -406,18 +385,17 @@ class mxPopupMenu extends EventSource {
* Shows the submenu inside the given parent row.
*/
// showSubmenu(parent: Element, row: Element): void;
showSubmenu(parent, row) {
showSubmenu(parent: PopupMenuItem, row: PopupMenuItem) {
if (row.div != null) {
row.div.style.left = `${parent.div.offsetLeft +
row.offsetLeft +
row.offsetWidth -
1}px`;
row.div.style.left = `${
parent.div.offsetLeft + row.offsetLeft + row.offsetWidth - 1
}px`;
row.div.style.top = `${parent.div.offsetTop + row.offsetTop}px`;
document.body.appendChild(row.div);
// Moves the submenu to the left side if there is no space
const left = parseInt(row.div.offsetLeft);
const width = parseInt(row.div.offsetWidth);
const left = row.div.offsetLeft;
const width = row.div.offsetWidth;
const offset = getDocumentScrollOrigin(document);
const b = document.body;
@ -426,10 +404,7 @@ class mxPopupMenu extends EventSource {
const right = offset.x + (b.clientWidth || d.clientWidth);
if (left + width > right) {
row.div.style.left = `${Math.max(
0,
parent.div.offsetLeft - width - 6
)}px`;
row.div.style.left = `${Math.max(0, parent.div.offsetLeft - width - 6)}px`;
}
fit(row.div);
@ -447,13 +422,12 @@ class mxPopupMenu extends EventSource {
* parent - Optional item returned by <addItem>.
* force - Optional boolean to ignore <smartSeparators>. Default is false.
*/
// addSeparator(parent?: Element, force?: boolean): void;
addSeparator(parent, force) {
addSeparator(parent: PopupMenuItem, force = false) {
parent = parent || this;
if (this.smartSeparators && !force) {
parent.willAddSeparator = true;
} else if (parent.tbody != null) {
} else if (parent.tbody) {
parent.willAddSeparator = false;
const tr = document.createElement('tr');
@ -491,8 +465,7 @@ class mxPopupMenu extends EventSource {
* }
* (end)
*/
// popup(x: number, y: number, cell: mxCell, evt: Event): void;
popup(x, y, cell, evt) {
popup(x: number, y: number, cell: Cell | null, evt: MouseEvent) {
if (this.div != null && this.tbody != null && this.factoryMethod != null) {
this.div.style.left = `${x}px`;
this.div.style.top = `${y}px`;
@ -504,7 +477,7 @@ class mxPopupMenu extends EventSource {
}
this.itemCount = 0;
this.factoryMethod(this, cell, evt);
this.factoryMethod(<PopupMenuItem>(<unknown>this), cell, evt);
if (this.itemCount > 0) {
this.showMenu();
@ -547,7 +520,7 @@ class mxPopupMenu extends EventSource {
this.div.parentNode.removeChild(this.div);
}
this.hideSubmenu(this);
this.hideSubmenu(<PopupMenuItem>(<unknown>this));
this.containsItems = false;
this.fireEvent(new EventObject(InternalEvent.HIDE));
}
@ -562,8 +535,7 @@ class mxPopupMenu extends EventSource {
*
* parent - An item returned by <addItem>.
*/
// hideSubmenu(parent: Element): void;
hideSubmenu(parent) {
hideSubmenu(parent: PopupMenuItem) {
if (parent.activeRow != null) {
this.hideSubmenu(parent.activeRow);
@ -580,7 +552,6 @@ class mxPopupMenu extends EventSource {
*
* Destroys the handler and all its resources and DOM nodes.
*/
// destroy(): void;
destroy() {
if (this.div != null) {
InternalEvent.release(this.div);
@ -588,10 +559,8 @@ class mxPopupMenu extends EventSource {
if (this.div.parentNode != null) {
this.div.parentNode.removeChild(this.div);
}
this.div = null;
}
}
}
export default mxPopupMenu;
export default PopupMenu;

View File

@ -5,9 +5,8 @@
* Type definitions from the typed-mxgraph project
*/
import graph from '../../view/Graph';
import Model from "../../view/model/Model";
import CellArray from "../../view/cell/datatypes/CellArray";
import { MaxGraph } from '../../view/Graph';
import CellArray from '../../view/cell/datatypes/CellArray';
/**
* @class
@ -18,8 +17,8 @@ import CellArray from "../../view/cell/datatypes/CellArray";
*
* @example
* ```javascript
* mxClipboard.copy(graph);
* mxClipboard.paste(graph2);
* Clipboard.copy(graph);
* Clipboard.paste(graph2);
* ```
*
* This copies the selection cells from the graph to the clipboard and
@ -33,30 +32,30 @@ import CellArray from "../../view/cell/datatypes/CellArray";
*
* @example
* ```javascript
* mxClipboard.copy = function(graph, cells)
* Clipboard.copy = function(graph, cells)
* {
* cells = cells || graph.getSelectionCells();
* var result = graph.getExportableCells(cells);
*
* mxClipboard.parents = new Object();
* Clipboard.parents = new Object();
*
* for (var i = 0; i < result.length; i++)
* {
* mxClipboard.parents[i] = graph.model.getParent(cells[i]);
* Clipboard.parents[i] = graph.model.getParent(cells[i]);
* }
*
* mxClipboard.insertCount = 1;
* mxClipboard.setCells(graph.cloneCells(result));
* Clipboard.insertCount = 1;
* Clipboard.setCells(graph.cloneCells(result));
*
* return result;
* };
*
* mxClipboard.paste = function(graph)
* Clipboard.paste = function(graph)
* {
* if (!mxClipboard.isEmpty())
* if (!Clipboard.isEmpty())
* {
* var cells = graph.getImportableCells(mxClipboard.getCells());
* var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
* var cells = graph.getImportableCells(Clipboard.getCells());
* var delta = Clipboard.insertCount * Clipboard.STEPSIZE;
* var parent = graph.getDefaultParent();
*
* graph.model.beginUpdate();
@ -64,8 +63,8 @@ import CellArray from "../../view/cell/datatypes/CellArray";
* {
* for (var i = 0; i < cells.length; i++)
* {
* var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
* mxClipboard.parents[i] : parent;
* var tmp = (Clipboard.parents != null && graph.model.contains(Clipboard.parents[i])) ?
* Clipboard.parents[i] : parent;
* cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
* }
* }
@ -75,48 +74,48 @@ import CellArray from "../../view/cell/datatypes/CellArray";
* }
*
* // Increments the counter and selects the inserted cells
* mxClipboard.insertCount++;
* Clipboard.insertCount++;
* graph.setSelectionCells(cells);
* }
* };
* ```
*/
class mxClipboard {
class Clipboard {
/**
* Defines the step size to offset the cells after each paste operation.
* Default is 10.
*/
static STEPSIZE: number = 10;
static STEPSIZE = 10;
/**
* Counts the number of times the clipboard data has been inserted.
*/
static insertCount: number = 1;
static insertCount = 1;
/**
* Holds the array of {@link mxCell} currently in the clipboard.
*/
static cells: CellArray | null = null;
static cells: CellArray;
/**
* Sets the cells in the clipboard. Fires a {@link mxEvent.CHANGE} event.
*/
static setCells(cells: CellArray | null): void {
mxClipboard.cells = cells;
static setCells(cells: CellArray) {
Clipboard.cells = cells;
}
/**
* Returns the cells in the clipboard.
*/
static getCells(): CellArray | null {
return mxClipboard.cells;
static getCells() {
return Clipboard.cells;
}
/**
* Returns true if the clipboard currently has not data stored.
*/
static isEmpty(): boolean {
return mxClipboard.getCells() == null;
static isEmpty() {
return !Clipboard.getCells();
}
/**
@ -127,10 +126,10 @@ class mxClipboard {
* @param graph - {@link graph} that contains the cells to be cut.
* @param cells - Optional array of {@link mxCell} to be cut.
*/
static cut(graph: graph, cells?: CellArray | null): CellArray | null {
cells = mxClipboard.copy(graph, cells);
mxClipboard.insertCount = 0;
mxClipboard.removeCells(graph, cells);
static cut(graph: MaxGraph, cells?: CellArray) {
cells = Clipboard.copy(graph, cells);
Clipboard.insertCount = 0;
Clipboard.removeCells(graph, cells);
return cells;
}
@ -142,7 +141,7 @@ class mxClipboard {
* @param graph - {@link graph} that contains the cells to be cut.
* @param cells - Array of {@link mxCell} to be cut.
*/
static removeCells(graph: graph, cells: CellArray | null): void {
static removeCells(graph: MaxGraph, cells: CellArray) {
graph.removeCells(cells);
}
@ -154,11 +153,11 @@ class mxClipboard {
* @param graph - {@link graph} that contains the cells to be copied.
* @param cells - Optional array of {@link mxCell} to be copied.
*/
static copy(graph: graph, cells?: CellArray | null): CellArray | null {
static copy(graph: MaxGraph, cells?: CellArray) {
cells = cells || graph.getSelectionCells();
const result = (<CellArray>graph.getExportableCells(cells)).getTopmostCells();
mxClipboard.insertCount = 1;
mxClipboard.setCells(graph.cloneCells(<CellArray>result));
const result = graph.getExportableCells(cells).getTopmostCells();
Clipboard.insertCount = 1;
Clipboard.setCells(graph.cloneCells(result));
return result;
}
@ -174,18 +173,17 @@ class mxClipboard {
*
* @param graph - {@link graph} to paste the {@link cells} into.
*/
static paste(graph: graph): CellArray | null {
static paste(graph: MaxGraph) {
let cells = null;
if (!mxClipboard.isEmpty()) {
// @ts-ignore
cells = graph.getImportableCells(mxClipboard.getCells());
const delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
if (!Clipboard.isEmpty() && Clipboard.getCells()) {
cells = graph.getImportableCells(Clipboard.getCells());
const delta = Clipboard.insertCount * Clipboard.STEPSIZE;
const parent = graph.getDefaultParent();
cells = graph.importCells(cells, delta, delta, parent);
// Increments the counter and selects the inserted cells
mxClipboard.insertCount++;
Clipboard.insertCount++;
graph.setSelectionCells(<CellArray>cells);
}
@ -193,4 +191,4 @@ class mxClipboard {
}
}
export default mxClipboard;
export default Clipboard;

View File

@ -47,27 +47,29 @@ import EdgeHandler from './cell/edge/EdgeHandler';
import VertexHandler from './cell/vertex/VertexHandler';
import EdgeSegmentHandler from './cell/edge/EdgeSegmentHandler';
import ElbowEdgeHandler from './cell/edge/ElbowEdgeHandler';
import Dictionary from '../util/Dictionary';
import type { GraphPlugin, GraphPluginConstructor } from '../types';
import type GraphPorts from './ports/GraphPorts';
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';
import GraphPorts from './ports/GraphPorts';
import GraphPanning from './panning/GraphPanning';
import GraphZoom from './zoom/GraphZoom';
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 GraphTooltip from './tooltip/GraphTooltip';
import GraphTerminal from './terminal/GraphTerminal';
import GraphDragDrop from './drag_drop/GraphDragDrop';
import GraphSwimlane from './swimlane/GraphSwimlane';
type PartialEvents = Pick<
GraphEvents,
@ -86,6 +88,10 @@ type PartialEvents = Pick<
| 'getEventTolerance'
| 'isInvokesStopCellEditing'
| 'getPointForEvent'
| 'isConstrainedEvent'
| 'isMouseTrigger'
| 'isEnterStopsCellEditing'
| 'getCursorForMouseEvent'
>;
type PartialSelection = Pick<
GraphSelection,
@ -101,6 +107,8 @@ type PartialSelection = Pick<
| 'cellRemoved'
| 'getUpdatingSelectionResource'
| 'getDoneResource'
| 'isSiblingSelected'
| 'setSelectionCells'
>;
type PartialCells = Pick<
GraphCells,
@ -119,10 +127,22 @@ type PartialCells = Pick<
| 'getCurrentCellStyle'
| 'resizeCell'
| 'removeStateForCell'
| 'getMovableCells'
| 'getCloneableCells'
| 'isCellLocked'
| 'moveCells'
| 'removeCells'
| 'isCellDeletable'
| 'addCell'
| 'getExportableCells'
| 'cloneCells'
| 'importCells'
| 'getImportableCells'
>;
type PartialConnections = Pick<
GraphConnections,
| 'getConnectionConstraint'
| 'setConnectionConstraint'
| 'getConnectionPoint'
| 'isCellDisconnectable'
| 'getOutlineConstraint'
@ -130,25 +150,35 @@ type PartialConnections = Pick<
| 'getConnections'
| 'isConstrainChild'
| 'isValidSource'
| 'getAllConnectionConstraints'
>;
type PartialEditing = Pick<
GraphEditing,
'isEditing' | 'stopEditing' | 'labelChanged' | 'getEditingValue'
>;
type PartialEditing = Pick<GraphEditing, 'isEditing' | 'stopEditing'>;
type PartialTooltip = Pick<GraphTooltip, 'getTooltip'>;
type PartialValidation = Pick<
GraphValidation,
'getEdgeValidationError' | 'validationAlert'
'getEdgeValidationError' | 'validationAlert' | 'isEdgeValid'
>;
type PartialLabel = Pick<
GraphLabel,
'isLabelMovable' | 'isHtmlLabel' | 'isWrapping' | 'isLabelClipped' | 'getLabel'
>;
type PartialTerminal = Pick<GraphTerminal, 'isTerminalPointMovable'>;
type PartialTerminal = Pick<GraphTerminal, 'isTerminalPointMovable' | 'getOpposites'>;
type PartialSnap = Pick<
GraphSnap,
'snap' | 'getGridSize' | 'isGridEnabled' | 'getSnapTolerance'
'snap' | 'getGridSize' | 'isGridEnabled' | 'getSnapTolerance' | 'snapDelta'
>;
type PartialEdge = Pick<
GraphEdge,
'isAllowDanglingEdges' | 'isResetEdgesOnConnect' | 'getEdges' | 'insertEdge' | 'addEdge'
| 'isAllowDanglingEdges'
| 'isResetEdgesOnConnect'
| 'getEdges'
| 'insertEdge'
| 'addEdge'
| 'splitEdge'
| 'flipEdge'
>;
type PartialOverlays = Pick<GraphOverlays, 'getCellOverlays'>;
type PartialFolding = Pick<
@ -163,8 +193,16 @@ type PartialPanning = Pick<
| 'setPanDx'
| 'getPanDy'
| 'setPanDy'
| 'isTimerAutoScroll'
| 'isAllowAutoPanning'
| 'scrollCellToVisible'
>;
type PartialZoom = Pick<GraphZoom, 'zoomTo'>;
type PartialDragDrop = Pick<
GraphDragDrop,
'isDropEnabled' | 'isAutoScroll' | 'isAutoExtend' | 'isSplitEnabled' | 'isSplitTarget'
>;
type PartialSwimlane = Pick<GraphSwimlane, 'getDropTarget'>;
type PartialClass = PartialEvents &
PartialSelection &
PartialCells &
@ -180,16 +218,20 @@ type PartialClass = PartialEvents &
PartialFolding &
PartialPanning &
PartialZoom &
PartialDragDrop &
PartialSwimlane &
EventSource;
export type MaxGraph = Graph & PartialClass;
const defaultPlugins: GraphPluginConstructor[] = [
CellEditor,
TooltipHandler,
SelectionCellsHandler,
PopupMenuHandler,
ConnectionHandler,
GraphHandler,
PanningHandler,
];
/**
@ -233,9 +275,6 @@ class Graph extends autoImplement<PartialClass>() {
this.getModel().addListener(InternalEvent.CHANGE, this.graphModelChangeListener);
// Initializes the in-place editor
this.cellEditor = this.createCellEditor();
// Initializes the container using the view
this.view.init();
@ -256,26 +295,8 @@ class Graph extends autoImplement<PartialClass>() {
destroyed: boolean = false;
// Handlers
// @ts-ignore Cannot be null.
// tooltipHandler: TooltipHandler;
// @ts-ignore Cannot be null.
// selectionCellsHandler: SelectionCellsHandler;
// @ts-ignore Cannot be null.
// popupMenuHandler: PopupMenuHandler;
// @ts-ignore Cannot be null.
// connectionHandler: ConnectionHandler;
// @ts-ignore Cannot be null.
// graphHandler: GraphHandler;
getPlugin = (id: string) => this.pluginsMap.get(id) as unknown;
// getTooltipHandler = () => this.pluginsMap.get('TooltipHandler');
// getSelectionCellsHandler = () => this.selectionCellsHandler;
// getPopupMenuHandler = () => this.popupMenuHandler;
// getConnectionHandler = () => this.connectionHandler;
// getGraphHandler = () => this.graphHandler;
graphModelChangeListener: Function | null = null;
paintBackground: Function | null = null;
@ -338,6 +359,8 @@ class Graph extends autoImplement<PartialClass>() {
*/
dialect: 'svg' | 'mixedHtml' | 'preferHtml' | 'strictHtml' = 'svg';
getDialect = () => this.dialect;
/**
* Value returned by {@link getOverlap} if {@link isAllowOverlapParent} returns
* `true` for the given cell. {@link getOverlap} is used in {@link constrainChild} if
@ -371,7 +394,9 @@ class Graph extends autoImplement<PartialClass>() {
* Not yet implemented.
* @default false
*/
pageVisible: boolean = false;
pageVisible = false;
isPageVisible = () => this.pageVisible;
/**
* Specifies if a dashed line should be drawn between multiple pages.
@ -379,32 +404,42 @@ class Graph extends autoImplement<PartialClass>() {
* should call {@link sizeDidChange} to force an update of the display.
* @default false
*/
pageBreaksVisible: boolean = false;
pageBreaksVisible = false;
isPageBreaksVisible = () => this.pageBreaksVisible;
/**
* Specifies the color for page breaks.
* @default gray
*/
pageBreakColor: string = 'gray';
pageBreakColor = 'gray';
getPageBreakColor = () => this.pageBreakColor;
/**
* Specifies the page breaks should be dashed.
* @default true
*/
pageBreakDashed: boolean = true;
pageBreakDashed = true;
isPageBreakDashed = () => this.pageBreakDashed;
/**
* Specifies the minimum distance in pixels for page breaks to be visible.
* @default 20
*/
minPageBreakDist: number = 20;
minPageBreakDist = 20;
getMinPageBreakDist = () => this.minPageBreakDist;
/**
* Specifies if the graph size should be rounded to the next page number in
* {@link sizeDidChange}. This is only used if the graph container has scrollbars.
* @default false
*/
preferPageSize: boolean = false;
preferPageSize = false;
isPreferPageSize = () => this.preferPageSize;
/**
* Specifies the page format for the background page.
@ -412,26 +447,30 @@ class Graph extends autoImplement<PartialClass>() {
* if {@link pageVisible} is `true` and the page breaks if {@link pageBreaksVisible} is `true`.
* @default {@link mxConstants.PAGE_FORMAT_A4_PORTRAIT}
*/
pageFormat: Rectangle = new Rectangle(...PAGE_FORMAT_A4_PORTRAIT);
pageFormat = new Rectangle(...PAGE_FORMAT_A4_PORTRAIT);
getPageFormat = () => this.pageFormat;
/**
* Specifies the scale of the background page.
* Not yet implemented.
* @default 1.5
*/
pageScale: number = 1.5;
pageScale = 1.5;
getPageScale = () => this.pageScale;
/**
* Specifies the return value for {@link isEnabled}.
* @default true
*/
enabled: boolean = true;
enabled = true;
/**
* Specifies the return value for {@link canExportCell}.
* @default true
*/
exportEnabled: boolean = true;
exportEnabled = true;
isExportEnabled = () => this.exportEnabled;
@ -439,7 +478,7 @@ class Graph extends autoImplement<PartialClass>() {
* Specifies the return value for {@link canImportCell}.
* @default true
*/
importEnabled: boolean = true;
importEnabled = true;
isImportEnabled = () => this.importEnabled;
@ -449,7 +488,9 @@ class Graph extends autoImplement<PartialClass>() {
* scroll positions (ie usually only rightwards and downwards). To avoid
* possible conflicts with panning, set {@link translateToScrollPosition} to `true`.
*/
ignoreScrollbars: boolean = false;
ignoreScrollbars = false;
isIgnoreScrollbars = () => this.ignoreScrollbars;
/**
* Specifies if the graph should automatically convert the current scroll
@ -457,7 +498,9 @@ class Graph extends autoImplement<PartialClass>() {
* This can be used to avoid conflicts when using {@link autoScroll} and
* {@link ignoreScrollbars} with no scrollbars in the container.
*/
translateToScrollPosition: boolean = false;
translateToScrollPosition = false;
isTranslateToScrollPosition = () => this.translateToScrollPosition;
/**
* {@link Rectangle} that specifies the area in which all cells in the diagram
@ -473,12 +516,19 @@ class Graph extends autoImplement<PartialClass>() {
*/
minimumGraphSize: Rectangle | null = null;
getMinimumGraphSize = () => this.minimumGraphSize;
setMinimumGraphSize = (size: Rectangle | null) => (this.minimumGraphSize = size);
/**
* {@link Rectangle} that specifies the minimum size of the {@link container} if
* {@link resizeContainer} is `true`.
*/
minimumContainerSize: Rectangle | null = null;
getMinimumContainerSize = () => this.minimumContainerSize;
setMinimumContainerSize = (size: Rectangle | null) =>
(this.minimumContainerSize = size);
/**
* {@link Rectangle} that specifies the maximum size of the container if
* {@link resizeContainer} is `true`.
@ -490,7 +540,7 @@ class Graph extends autoImplement<PartialClass>() {
* the graph size has changed.
* @default false
*/
resizeContainer: boolean = false;
resizeContainer = false;
/**
* Border to be added to the bottom and right side when the container is
@ -644,13 +694,6 @@ class Graph extends autoImplement<PartialClass>() {
return new CellRenderer();
}
/**
* Creates a new {@link CellEditor} to be used in this graph.
*/
createCellEditor(): CellEditor {
return new CellEditor(this);
}
/**
* Returns the {@link Model} that contains the cells.
*/
@ -708,7 +751,8 @@ class Graph extends autoImplement<PartialClass>() {
if (change instanceof RootChange) {
this.clearSelection();
this.setDefaultParent(null);
this.removeStateForCell(change.previous);
if (change.previous) this.removeStateForCell(change.previous);
if (this.resetViewOnRootChange) {
this.view.scale = 1;
@ -792,9 +836,11 @@ class Graph extends autoImplement<PartialClass>() {
y: number,
extend: boolean = false,
border: number = 20
): void {
) {
const panningHandler = this.getPlugin('PanningHandler') as PanningHandler;
if (
!this.timerAutoScroll &&
!this.isTimerAutoScroll() &&
(this.ignoreScrollbars || hasScrollbars(this.container))
) {
const c = <HTMLElement>this.container;
@ -860,14 +906,8 @@ class Graph extends autoImplement<PartialClass>() {
}
}
}
} else if (
this.allowAutoPanning &&
!(<PanningHandler>this.panningHandler).isActive()
) {
if (this.panningManager == null) {
this.panningManager = this.createPanningManager();
}
this.panningManager.panTo(x + this.panDx, y + this.panDy);
} else if (this.isAllowAutoPanning() && !panningHandler.isActive()) {
panningHandler.getPanningManager().panTo(x + this.getPanDx(), y + this.getPanDy());
}
}
@ -894,8 +934,7 @@ class Graph extends autoImplement<PartialClass>() {
/**
* Returns the preferred size of the background page if {@link preferPageSize} is true.
*/
getPreferredPageSize(bounds: Rectangle, width: number, height: number): Rectangle {
const { scale } = this.view;
getPreferredPageSize(bounds: Rectangle, width: number, height: number) {
const tr = this.view.translate;
const fmt = this.pageFormat;
const ps = this.pageScale;
@ -1091,13 +1130,13 @@ class Graph extends autoImplement<PartialClass>() {
if (state.cell.isEdge()) {
const source = state.getVisibleTerminalState(true);
const target = state.getVisibleTerminalState(false);
const geo = (<Cell>state.cell).getGeometry();
const geo = state.cell.getGeometry();
const edgeStyle = this.getView().getEdgeStyle(
state,
geo != null ? geo.points : null,
<CellState>source,
<CellState>target
geo ? geo.points : undefined,
source,
target
);
result = this.createEdgeHandler(state, edgeStyle);
} else {
@ -1120,7 +1159,7 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param state {@link mxCellState} to create the handler for.
*/
createEdgeHandler(state: CellState, edgeStyle: any): EdgeHandler {
createEdgeHandler(state: CellState, edgeStyle: any) {
let result = null;
if (
edgeStyle == EdgeStyle.Loop ||
@ -1137,7 +1176,8 @@ class Graph extends autoImplement<PartialClass>() {
} else {
result = new EdgeHandler(state);
}
return result;
return result as EdgeHandler;
}
/**
@ -1145,8 +1185,8 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param state {@link mxCellState} to create the handler for.
*/
createEdgeSegmentHandler(state: CellState): mxEdgeSegmentHandler {
return new mxEdgeSegmentHandler(state);
createEdgeSegmentHandler(state: CellState) {
return new EdgeSegmentHandler(state);
}
/**
@ -1154,7 +1194,7 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param state {@link mxCellState} to create the handler for.
*/
createElbowEdgeHandler(state: CellState): ElbowEdgeHandler {
createElbowEdgeHandler(state: CellState) {
return new ElbowEdgeHandler(state);
}
@ -1166,7 +1206,7 @@ class Graph extends autoImplement<PartialClass>() {
* Returns the current root of the displayed cell hierarchy. This is a
* shortcut to {@link GraphView.currentRoot} in {@link GraphView}.
*/
getCurrentRoot(): Cell | null {
getCurrentRoot() {
return this.view.currentRoot;
}
@ -1213,7 +1253,6 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param cell {@link mxCell} whose offset should be returned.
*/
// getChildOffsetForCell(cell: mxCell): number;
getChildOffsetForCell(cell: Cell): Point | null {
return null;
}
@ -1230,7 +1269,7 @@ class Graph extends autoImplement<PartialClass>() {
const state = this.view.getState(current);
if (state != null) {
this.selection.setSelectionCell(current);
this.setSelectionCell(current);
}
}
}
@ -1242,7 +1281,7 @@ class Graph extends autoImplement<PartialClass>() {
* @param cell {@link mxCell} which should be checked as a possible root.
*/
isValidRoot(cell: Cell) {
return cell != null;
return !!cell;
}
/*****************************************************************************
@ -1357,7 +1396,7 @@ class Graph extends autoImplement<PartialClass>() {
*/
const orthogonal = edge.style.orthogonal;
if (orthogonal != null) {
if (orthogonal !== null) {
return orthogonal;
}
@ -1461,7 +1500,7 @@ class Graph extends autoImplement<PartialClass>() {
/**
* Returns {@link resizeContainer}.
*/
isResizeContainer(): boolean {
isResizeContainer() {
return this.resizeContainer;
}
@ -1477,7 +1516,7 @@ class Graph extends autoImplement<PartialClass>() {
/**
* Returns true if the graph is {@link enabled}.
*/
isEnabled(): boolean {
isEnabled() {
return this.enabled;
}
@ -1487,14 +1526,14 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param value Boolean indicating if the graph should be enabled.
*/
setEnabled(value: boolean): void {
setEnabled(value: boolean) {
this.enabled = value;
}
/**
* Returns {@link multigraph} as a boolean.
*/
isMultigraph(): boolean {
isMultigraph() {
return this.multigraph;
}
@ -1505,14 +1544,14 @@ class Graph extends autoImplement<PartialClass>() {
* @param value Boolean indicating if the graph allows multiple connections
* between the same pair of vertices.
*/
setMultigraph(value: boolean): void {
setMultigraph(value: boolean) {
this.multigraph = value;
}
/**
* Returns {@link allowLoops} as a boolean.
*/
isAllowLoops(): boolean {
isAllowLoops() {
return this.allowLoops;
}
@ -1521,7 +1560,7 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param value Boolean indicating if loops are allowed.
*/
setAllowLoops(value: boolean): void {
setAllowLoops(value: boolean) {
this.allowLoops = value;
}
@ -1530,7 +1569,7 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param state {@link mxCellState} that is being resized.
*/
isRecursiveResize(state: CellState | null = null): boolean {
isRecursiveResize(state: CellState | null = null) {
return this.recursiveResize;
}
@ -1539,24 +1578,10 @@ class Graph extends autoImplement<PartialClass>() {
*
* @param value New boolean value for {@link recursiveResize}.
*/
setRecursiveResize(value: boolean): void {
setRecursiveResize(value: boolean) {
this.recursiveResize = value;
}
/**
* Returns {@link allowNegativeCoordinates}.
*/
isAllowNegativeCoordinates(): boolean {
return this.allowNegativeCoordinates;
}
/**
* Sets {@link allowNegativeCoordinates}.
*/
setAllowNegativeCoordinates(value: boolean): void {
this.allowNegativeCoordinates = value;
}
/**
* Returns a decimal number representing the amount of the width and height
* of the given cell that is allowed to overlap its parent. A value of 0
@ -1623,17 +1648,10 @@ class Graph extends autoImplement<PartialClass>() {
destroy(): void {
if (!this.destroyed) {
this.destroyed = true;
// @ts-ignore
this.container = null;
this.tooltipHandler?.destroy?.();
this.selectionCellsHandler?.destroy?.();
this.panningHandler?.destroy?.();
this.popupMenuHandler?.destroy?.();
this.connectionHandler?.destroy?.();
this.graphHandler?.destroy?.();
this.cellEditor?.destroy?.();
this.view?.destroy?.();
Object.values(this.pluginsMap).forEach((p) => p.onDestroy());
this.view.destroy();
if (this.model != null && this.graphModelChangeListener != null) {
this.getModel().removeListener(this.graphModelChangeListener);
@ -1644,19 +1662,20 @@ class Graph extends autoImplement<PartialClass>() {
}
applyMixins(Graph, [
GraphEvents,
GraphImage,
GraphCells,
GraphSelection,
GraphConnections,
GraphEdge,
GraphVertex,
GraphOverlays,
GraphEditing,
GraphEvents,
GraphFolding,
GraphImage,
GraphLabel,
GraphValidation,
GraphOverlays,
GraphSelection,
GraphSnap,
GraphSwimlane,
GraphValidation,
GraphVertex,
]);
export default Graph;

File diff suppressed because it is too large Load Diff

View File

@ -129,7 +129,7 @@ class CellMarker extends EventSource {
*
* Holds the current marker color.
*/
currentColor: ColorValue | null = null;
currentColor: ColorValue = NONE;
/**
* Variable: validState

View File

@ -471,7 +471,7 @@ class CellRenderer {
// getCellAt for the subsequent mouseMoves and the final mouseUp.
let forceGetCell = false;
const getState = (evt: Event | InternalMouseEvent) => {
const getState = (evt: MouseEvent) => {
let result: CellState | null = state;
if (mxClient.IS_TOUCH || forceGetCell) {
@ -489,29 +489,33 @@ class CellRenderer {
// TODO: Add handling for special touch device gestures
InternalEvent.addGestureListeners(
state.text.node,
(evt: Event) => {
if (this.isLabelEvent(state, evt as MouseEvent)) {
(evt: MouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt as MouseEvent, state)
new InternalMouseEvent(evt, state)
);
const source = getSource(evt);
forceGetCell =
graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG';
// @ts-ignore nodeName should exist.
graph.dialect !== DIALECT_SVG && source.nodeName === 'IMG';
}
},
(evt: Event) => {
if (this.isLabelEvent(state, evt as MouseEvent)) {
(evt: MouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent, getState(evt))
new InternalMouseEvent(evt, getState(evt))
);
}
},
(evt: Event) => {
if (this.isLabelEvent(state, evt as MouseEvent)) {
(evt: MouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent, getState(evt))
new InternalMouseEvent(evt, getState(evt))
);
forceGetCell = false;
}
@ -520,9 +524,9 @@ class CellRenderer {
// Uses double click timeout in mxGraph for quirks mode
if (graph.isNativeDblClickEnabled()) {
InternalEvent.addListener(state.text.node, 'dblclick', (evt: Event) => {
if (this.isLabelEvent(state, evt as MouseEvent)) {
graph.dblClick(evt as MouseEvent, state.cell);
InternalEvent.addListener(state.text.node, 'dblclick', (evt: MouseEvent) => {
if (this.isLabelEvent(state, evt)) {
graph.dblClick(evt, state.cell);
InternalEvent.consume(evt);
}
});
@ -749,24 +753,24 @@ class CellRenderer {
InternalEvent.addGestureListeners(
node,
(evt: Event) => {
(evt: MouseEvent) => {
first = new Point(getClientX(evt), getClientY(evt));
graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt as MouseEvent, state)
new InternalMouseEvent(evt, state)
);
InternalEvent.consume(evt);
},
(evt: Event) => {
(evt: MouseEvent) => {
graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent, state)
new InternalMouseEvent(evt, state)
);
},
(evt: Event) => {
(evt: MouseEvent) => {
graph.fireMouseEvent(
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent, state)
new InternalMouseEvent(evt, state)
);
InternalEvent.consume(evt);
}
@ -776,13 +780,13 @@ class CellRenderer {
if (clickHandler && mxClient.IS_IOS) {
node.addEventListener(
'touchend',
(evt) => {
(evt: Event) => {
if (first) {
const tol = graph.getEventTolerance();
if (
Math.abs(first.x - getClientX(evt)) < tol &&
Math.abs(first.y - getClientY(evt)) < tol
Math.abs(first.x - getClientX(evt as MouseEvent)) < tol &&
Math.abs(first.y - getClientY(evt as MouseEvent)) < tol
) {
clickHandler.call(clickHandler, evt);
InternalEvent.consume(evt);
@ -808,7 +812,7 @@ class CellRenderer {
* state - <mxCellState> whose shape fired the event.
* evt - Mouse event which was fired.
*/
isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) {
isShapeEvent(state: CellState, evt: MouseEvent) {
return true;
}
@ -823,7 +827,7 @@ class CellRenderer {
* state - <mxCellState> whose label fired the event.
* evt - Mouse event which was fired.
*/
isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) {
isLabelEvent(state: CellState, evt: MouseEvent) {
return true;
}
@ -842,11 +846,15 @@ class CellRenderer {
// Workaround for touch devices routing all events for a mouse
// gesture (down, move, up) via the initial DOM node. Same for
// HTML images in all IE versions (VML images are working).
const getState = (evt: Event) => {
const getState = (evt: MouseEvent) => {
let result: CellState | null = state;
const source = getSource(evt);
if (
(graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG') ||
(source &&
graph.dialect !== DIALECT_SVG &&
// @ts-ignore nodeName should exist
source.nodeName === 'IMG') ||
mxClient.IS_TOUCH
) {
const x = getClientX(evt);
@ -855,36 +863,38 @@ class CellRenderer {
// Dispatches the drop event to the graph which
// consumes and executes the source function
const pt = convertPoint(graph.container, x, y);
result = graph.view.getState(graph.getCellAt(pt.x, pt.y) as Cell);
const cell = graph.getCellAt(pt.x, pt.y);
result = cell ? graph.view.getState(cell) : null;
}
return result;
};
if (state.shape) {
InternalEvent.addGestureListeners(
// @ts-ignore
state.shape.node,
(evt: Event) => {
if (this.isShapeEvent(state, evt as MouseEvent)) {
(evt: MouseEvent) => {
if (this.isShapeEvent(state, evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_DOWN,
new InternalMouseEvent(evt as MouseEvent, state)
new InternalMouseEvent(evt, state)
);
}
},
(evt: Event) => {
if (this.isShapeEvent(state, evt as MouseEvent)) {
(evt: MouseEvent) => {
if (this.isShapeEvent(state, evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_MOVE,
new InternalMouseEvent(evt as MouseEvent, getState(evt))
new InternalMouseEvent(evt, getState(evt))
);
}
},
(evt: Event) => {
if (this.isShapeEvent(state, evt as MouseEvent)) {
(evt: MouseEvent) => {
if (this.isShapeEvent(state, evt)) {
graph.fireMouseEvent(
InternalEvent.MOUSE_UP,
new InternalMouseEvent(evt as MouseEvent, getState(evt))
new InternalMouseEvent(evt, getState(evt))
);
}
}
@ -892,15 +902,15 @@ class CellRenderer {
// Uses double click timeout in mxGraph for quirks mode
if (graph.isNativeDblClickEnabled()) {
// @ts-ignore
InternalEvent.addListener(state.shape.node, 'dblclick', (evt) => {
if (this.isShapeEvent(state, evt as MouseEvent)) {
graph.dblClick(evt as MouseEvent, state.cell);
InternalEvent.addListener(state.shape.node, 'dblclick', (evt: MouseEvent) => {
if (this.isShapeEvent(state, evt)) {
graph.dblClick(evt, state.cell);
InternalEvent.consume(evt);
}
});
}
}
}
/**
* Function: redrawLabel

View File

@ -45,6 +45,7 @@ import type GraphLabel from '../label/GraphLabel';
import type GraphSnap from '../snap/GraphSnap';
import type { CellStateStyles } from '../../types';
import GraphVertex from './vertex/GraphVertex';
type PartialGraph = Pick<
Graph,
@ -55,8 +56,6 @@ type PartialGraph = Pick<
| 'fireEvent'
| 'getDefaultParent'
| 'getCurrentRoot'
| 'isAllowNegativeCoordinates'
| 'setAllowNegativeCoordinates'
| 'getOverlap'
| 'isRecursiveResize'
| 'getCellRenderer'
@ -91,6 +90,10 @@ type PartialSnap = Pick<
GraphSnap,
'isGridEnabled' | 'snap' | 'getGridSize' | 'getTolerance'
>;
type PartialVertex = Pick<
GraphVertex,
'isAllowNegativeCoordinates' | 'setAllowNegativeCoordinates'
>;
type PartialClass = PartialGraph &
PartialImage &
PartialSelection &
@ -99,7 +102,8 @@ type PartialClass = PartialGraph &
PartialValidation &
PartialFolding &
PartialLabel &
PartialSnap;
PartialSnap &
PartialVertex;
// @ts-ignore recursive reference error
class GraphCells extends autoImplement<PartialClass>() {
@ -1575,7 +1579,7 @@ class GraphCells extends autoImplement<PartialClass>() {
dx: number,
dy: number,
target: Cell | null = null,
evt: InternalMouseEvent | null = null,
evt: MouseEvent | null = null,
mapping: any = {}
) {
return this.moveCells(cells, dx, dy, true, target, evt, mapping);
@ -1612,7 +1616,7 @@ class GraphCells extends autoImplement<PartialClass>() {
dy: number = 0,
clone = false,
target: Cell | null = null,
evt: InternalMouseEvent | null = null,
evt: MouseEvent | null = null,
mapping: any = null
) {
if (dx !== 0 || dy !== 0 || clone || target) {

View File

@ -248,7 +248,7 @@ class CellState extends Rectangle {
* isSource - Boolean that specifies if the first or last point should
* be assigned.
*/
setAbsoluteTerminalPoint(point: Point, isSource = false) {
setAbsoluteTerminalPoint(point: Point | null, isSource = false) {
if (isSource) {
if (this.absolutePoints.length === 0) {
this.absolutePoints.push(point);

View File

@ -34,6 +34,7 @@ import {
import utils, {
contains,
convertPoint,
equalPoints,
findNearestSegment,
getOffset,
intersects,
@ -64,6 +65,7 @@ import Cell from '../datatypes/Cell';
import ImageBox from '../../image/ImageBox';
import Marker from '../../geometry/shape/edge/Marker';
import EventSource from '../../event/EventSource';
import GraphHandler from '../../GraphHandler';
/**
* Graph event handler that reconnects edges and modifies control points and the edge
@ -129,11 +131,13 @@ class EdgeHandler {
}
}
const graphHandler = this.graph.getPlugin('GraphHandler') as GraphHandler;
// Creates bends for the non-routed absolute points
// or bends that don't correspond to points
if (
this.graph.getSelectionCount() < this.graph.graphHandler.maxCells ||
this.graph.graphHandler.maxCells <= 0
this.graph.getSelectionCount() < graphHandler.maxCells ||
graphHandler.maxCells <= 0
) {
this.bends = this.createBends();
@ -193,7 +197,7 @@ class EdgeHandler {
* Holds the <mxConstraintHandler> used for drawing and highlighting
* constraints.
*/
constraintHandler: ConstraintHandler = null;
constraintHandler: ConstraintHandler;
/**
* Variable: error
@ -207,7 +211,7 @@ class EdgeHandler {
*
* Holds the <mxShape> that represents the preview edge.
*/
shape: Shape | null = null;
shape: Shape;
/**
* Variable: bends
@ -223,7 +227,7 @@ class EdgeHandler {
*
* Holds the <mxShape> that represents the label position.
*/
labelShape: Shape | null = null;
labelShape: Shape;
/**
* Variable: cloneEnabled
@ -370,7 +374,7 @@ class EdgeHandler {
isTarget: boolean = false;
label: Point | null = null;
label: Point;
isLabel = false;
@ -492,7 +496,7 @@ class EdgeHandler {
* Returns true if the given event is a trigger to add a new Point. This
* implementation returns true if shift is pressed.
*/
isAddPointEvent(evt: Event) {
isAddPointEvent(evt: MouseEvent) {
return isShiftDown(evt);
}
@ -502,7 +506,7 @@ class EdgeHandler {
* Returns true if the given event is a trigger to remove a point. This
* implementation returns true if shift is pressed.
*/
isRemovePointEvent(evt: Event) {
isRemovePointEvent(evt: MouseEvent) {
return isShiftDown(evt);
}
@ -851,7 +855,7 @@ class EdgeHandler {
*
* bend - <mxShape> that represents the bend to be initialized.
*/
initBend(bend: Shape, dblClick?: EventListener) {
initBend(bend: Shape, dblClick?: (evt: MouseEvent) => void) {
if (this.preferHtml) {
bend.dialect = DIALECT_STRICTHTML;
bend.init(this.graph.container);
@ -1252,7 +1256,7 @@ class EdgeHandler {
* pt - <mxPoint> that contains the current pointer position.
* me - Optional <mxMouseEvent> that contains the current event.
*/
getPreviewPoints(pt: Point, me: InternalMouseEvent) {
getPreviewPoints(pt: Point, me?: InternalMouseEvent) {
const geometry = this.state.cell.getGeometry();
if (!geometry) return null;
@ -1400,7 +1404,7 @@ class EdgeHandler {
point: Point,
terminalState: CellState,
me: InternalMouseEvent,
outline: boolean
outline = false
) {
// Computes the points for the edge style and terminals
const sourceState = this.isSource
@ -1436,7 +1440,7 @@ class EdgeHandler {
this.constraintHandler.currentConstraint = constraint;
this.constraintHandler.currentPoint = point;
} else {
constraint = new ConnectionConstraint();
constraint = new ConnectionConstraint(null);
}
}
@ -1716,7 +1720,7 @@ class EdgeHandler {
model.setGeometry(cloned, geo);
}
const other = edge.getTerminal(!this.isSource);
const other = edge.getTerminal(!this.isSource) as Cell;
this.graph.connectCell(cloned, other, !this.isSource);
edge = cloned;
@ -1744,8 +1748,8 @@ class EdgeHandler {
pt.y -= pstate.origin.y;
}
pt.x -= this.graph.panDx / this.graph.view.scale;
pt.y -= this.graph.panDy / this.graph.view.scale;
pt.x -= this.graph.getPanDx() / this.graph.view.scale;
pt.y -= this.graph.getPanDy() / this.graph.view.scale;
// Destroys and recreates this handler
edge = this.changeTerminalPoint(edge, pt, this.isSource, clone);
@ -1780,7 +1784,6 @@ class EdgeHandler {
*
* Resets the state of this handler.
*/
// reset(): void;
reset() {
if (this.active) {
this.refresh();
@ -1788,35 +1791,22 @@ class EdgeHandler {
this.error = null;
this.index = null;
this.label = null;
this.points = null;
this.points = [];
this.snapPoint = null;
this.isLabel = false;
this.isSource = false;
this.isTarget = false;
this.active = false;
if (this.livePreview && this.sizers != null) {
for (let i = 0; i < this.sizers.length; i += 1) {
if (this.sizers[i] != null) {
this.sizers[i].node.style.display = '';
}
}
}
if (this.marker != null) {
if (this.marker) {
this.marker.reset();
}
if (this.constraintHandler != null) {
this.constraintHandler.reset();
}
if (this.customHandles != null) {
for (let i = 0; i < this.customHandles.length; i += 1) {
this.customHandles[i].reset();
}
}
this.setPreviewColor(EDGE_SELECTION_COLOR);
this.removeHint();
@ -1829,10 +1819,8 @@ class EdgeHandler {
* Sets the color of the preview to the given value.
*/
setPreviewColor(color: ColorValue) {
if (this.shape != null) {
this.shape.stroke = color;
}
}
/**
* Function: convertPoint
@ -1860,7 +1848,7 @@ class EdgeHandler {
const pstate = this.graph.getView().getState(this.state.cell.getParent());
if (pstate != null) {
if (pstate) {
point.x -= pstate.origin.x;
point.y -= pstate.origin.y;
}
@ -1954,7 +1942,7 @@ class EdgeHandler {
let constraint = this.constraintHandler.currentConstraint;
if (constraint == null) {
constraint = new ConnectionConstraint();
constraint = new ConnectionConstraint(null);
}
this.graph.connectCell(edge, terminal, isSource, constraint);
@ -1989,7 +1977,7 @@ class EdgeHandler {
geo = geo.clone();
geo.setTerminalPoint(point, isSource);
model.setGeometry(edge, geo);
this.graph.connectCell(edge, null, isSource, new ConnectionConstraint());
this.graph.connectCell(edge, null, isSource, new ConnectionConstraint(null));
}
} finally {
model.endUpdate();
@ -2064,7 +2052,8 @@ class EdgeHandler {
if (parent.isVertex()) {
const pState = this.graph.view.getState(parent);
offset = new Point(pState.x, pState.y);
if (pState) offset = new Point(pState.x, pState.y);
}
const index = findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);
@ -2169,7 +2158,8 @@ class EdgeHandler {
const { cell } = this.state;
// Updates the handle for the label position
let b = this.labelShape.bounds;
let b = this.labelShape.bounds as Rectangle;
this.label = new Point(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
this.labelShape.bounds = new Rectangle(
Math.round(this.label.x - b.width / 2),
@ -2186,11 +2176,12 @@ class EdgeHandler {
if (this.bends != null && this.bends.length > 0) {
const n = this.abspoints.length - 1;
const p0 = this.abspoints[0];
const p0 = this.abspoints[0] as Point;
const x0 = p0.x;
const y0 = p0.y;
b = this.bends[0].bounds;
b = this.bends[0].bounds as Rectangle;
this.bends[0].bounds = new Rectangle(
Math.floor(x0 - b.width / 2),
Math.floor(y0 - b.height / 2),
@ -2204,12 +2195,12 @@ class EdgeHandler {
this.checkLabelHandle(this.bends[0].bounds);
}
const pe = this.abspoints[n];
const pe = this.abspoints[n] as Point;
const xn = pe.x;
const yn = pe.y;
const bn = this.bends.length - 1;
b = this.bends[bn].bounds;
b = this.bends[bn].bounds as Rectangle;
this.bends[bn].bounds = new Rectangle(
Math.floor(xn - b.width / 2),
Math.floor(yn - b.height / 2),
@ -2220,25 +2211,23 @@ class EdgeHandler {
this.bends[bn].redraw();
if (this.manageLabelHandle) {
this.checkLabelHandle(this.bends[bn].bounds);
this.checkLabelHandle(this.bends[bn].bounds as Rectangle);
}
this.redrawInnerBends(p0, pe);
}
if (
this.abspoints != null &&
this.virtualBends != null &&
this.virtualBends.length > 0
) {
let last = this.abspoints[0];
if (this.virtualBends.length > 0) {
let last = this.abspoints[0] as Point;
for (let i = 0; i < this.virtualBends.length; i += 1) {
if (this.virtualBends[i] != null && this.abspoints[i + 1] != null) {
const pt = this.abspoints[i + 1];
b = this.virtualBends[i];
const pt = this.abspoints[i + 1] as Point;
const b = this.virtualBends[i];
const x = last.x + (pt.x - last.x) / 2;
const y = last.y + (pt.y - last.y) / 2;
if (b.bounds) {
b.bounds = new Rectangle(
Math.floor(x - b.bounds.width / 2),
Math.floor(y - b.bounds.height / 2),
@ -2246,30 +2235,30 @@ class EdgeHandler {
b.bounds.height
);
b.redraw();
}
setOpacity(b.node, this.virtualBendOpacity);
last = pt;
if (this.manageLabelHandle) {
this.checkLabelHandle(b.bounds);
this.checkLabelHandle(b.bounds as Rectangle);
}
}
}
}
if (this.labelShape != null) {
this.labelShape.redraw();
}
if (this.customHandles != null) {
for (let i = 0; i < this.customHandles.length; i += 1) {
const temp = this.customHandles[i].shape.node.style.display;
const shape = this.customHandles[i].shape;
if (shape) {
const temp = shape.node.style.display;
this.customHandles[i].redraw();
this.customHandles[i].shape.node.style.display = temp;
shape.node.style.display = temp;
// Hides custom handles during text editing
this.customHandles[i].shape.node.style.visibility = this.isCustomHandleVisible(
this.customHandles[i]
)
shape.node.style.visibility = this.isCustomHandleVisible(this.customHandles[i])
? ''
: 'hidden';
}
@ -2291,28 +2280,20 @@ class EdgeHandler {
* Shortcut to <hideSizers>.
*/
setHandlesVisible(visible: boolean) {
if (this.bends != null) {
for (let i = 0; i < this.bends.length; i += 1) {
this.bends[i].node.style.display = visible ? '' : 'none';
}
}
if (this.virtualBends != null) {
for (let i = 0; i < this.virtualBends.length; i += 1) {
this.virtualBends[i].node.style.display = visible ? '' : 'none';
}
}
if (this.labelShape != null) {
this.labelShape.node.style.display = visible ? '' : 'none';
}
if (this.customHandles != null) {
for (let i = 0; i < this.customHandles.length; i += 1) {
this.customHandles[i].setVisible(visible);
}
}
}
/**
* Function: redrawInnerBends
@ -2328,10 +2309,10 @@ class EdgeHandler {
for (let i = 1; i < this.bends.length - 1; i += 1) {
if (this.bends[i] != null) {
if (this.abspoints[i] != null) {
const { x } = this.abspoints[i];
const { y } = this.abspoints[i];
const { x } = this.abspoints[i] as Point;
const { y } = this.abspoints[i] as Point;
const b = this.bends[i].bounds;
const b = this.bends[i].bounds as Rectangle;
this.bends[i].node.style.visibility = 'visible';
this.bends[i].bounds = new Rectangle(
Math.round(x - b.width / 2),
@ -2341,11 +2322,14 @@ class EdgeHandler {
);
if (this.manageLabelHandle) {
this.checkLabelHandle(this.bends[i].bounds);
this.checkLabelHandle(this.bends[i].bounds as Rectangle);
} else if (
this.handleImage == null &&
this.labelShape.visible &&
intersects(this.bends[i].bounds, this.labelShape.bounds)
intersects(
this.bends[i].bounds as Rectangle,
this.labelShape.bounds as Rectangle
)
) {
const w = HANDLE_SIZE + 3;
const h = HANDLE_SIZE + 3;
@ -2360,7 +2344,6 @@ class EdgeHandler {
this.bends[i].redraw();
} else {
this.bends[i].destroy();
this.bends[i] = null;
}
}
}
@ -2373,8 +2356,7 @@ class EdgeHandler {
* intersects.
*/
checkLabelHandle(b: Rectangle) {
if (this.labelShape != null) {
const b2 = this.labelShape.bounds;
const b2 = this.labelShape.bounds as Rectangle;
if (intersects(b, b2)) {
if (b.getCenterY() < b2.getCenterY()) {
@ -2384,7 +2366,6 @@ class EdgeHandler {
}
}
}
}
/**
* Function: drawPreview
@ -2394,7 +2375,7 @@ class EdgeHandler {
drawPreview() {
try {
if (this.isLabel) {
const b = this.labelShape.bounds;
const b = this.labelShape.bounds as Rectangle;
const bounds = new Rectangle(
Math.round(this.label.x - b.width / 2),
Math.round(this.label.y - b.height / 2),
@ -2402,7 +2383,7 @@ class EdgeHandler {
b.height
);
if (!this.labelShape.bounds.equals(bounds)) {
if (!b.equals(bounds)) {
this.labelShape.bounds = bounds;
this.labelShape.redraw();
}
@ -2436,20 +2417,14 @@ class EdgeHandler {
this.abspoints = this.getSelectionPoints(this.state);
this.points = [];
if (this.bends != null) {
this.destroyBends(this.bends);
this.bends = this.createBends();
}
if (this.virtualBends != null) {
this.destroyBends(this.virtualBends);
this.virtualBends = this.createVirtualBends();
}
if (this.customHandles != null) {
this.destroyBends(this.customHandles);
this.customHandles = this.createCustomHandles();
}
// Puts label node on top of bends
if (
@ -2476,7 +2451,7 @@ class EdgeHandler {
*
* Destroys all elements in <bends>.
*/
destroyBends(bends: Shape[]) {
destroyBends(bends: Shape[] | CellHandle[]) {
if (bends != null) {
for (let i = 0; i < bends.length; i += 1) {
if (bends[i] != null) {
@ -2495,26 +2470,17 @@ class EdgeHandler {
*/
// destroy(): void;
destroy() {
if (this.escapeHandler != null) {
this.state.view.graph.removeListener(this.escapeHandler);
this.escapeHandler = null;
}
if (this.marker != null) {
this.marker.destroy();
this.marker = null;
}
if (this.shape != null) {
this.shape.destroy();
this.shape = null;
}
if (this.parentHighlight != null) {
if (this.parentHighlight) {
const parent = this.state.cell.getParent();
const pstate = this.graph.view.getState(parent);
if (pstate != null && pstate.parentHighlight === this.parentHighlight) {
if (pstate && pstate.parentHighlight === this.parentHighlight) {
pstate.parentHighlight = null;
}
@ -2522,21 +2488,15 @@ class EdgeHandler {
this.parentHighlight = null;
}
if (this.labelShape != null) {
this.labelShape.destroy();
this.labelShape = null;
}
if (this.constraintHandler != null) {
this.constraintHandler.destroy();
this.constraintHandler = null;
}
this.destroyBends(this.virtualBends);
this.virtualBends = null;
this.virtualBends = [];
this.destroyBends(this.customHandles);
this.customHandles = null;
this.customHandles = [];
this.destroyBends(this.bends);
this.bends = [];

View File

@ -7,49 +7,43 @@
import Point from '../../geometry/Point';
import { CURSOR_TERMINAL_HANDLE } from '../../../util/Constants';
import Rectangle from '../../geometry/Rectangle';
import utils, { contains, setOpacity } from '../../../util/Utils';
import { contains, setOpacity } from '../../../util/Utils';
import ElbowEdgeHandler from './ElbowEdgeHandler';
import CellState from '../datatypes/CellState';
import Cell from '../datatypes/Cell';
import InternalMouseEvent from '../../event/InternalMouseEvent';
class EdgeSegmentHandler extends ElbowEdgeHandler {
constructor(state: CellState) {
// WARNING: should be super of mxEdgeHandler!
super(state);
}
points: Point[] | null = null;
points: Point[] = [];
/**
* Function: getCurrentPoints
*
* Returns the current absolute points.
*/
getCurrentPoints(): Point[] {
getCurrentPoints() {
let pts = this.state.absolutePoints;
if (pts != null) {
// Special case for straight edges where we add a virtual middle handle for moving the edge
const tol = Math.max(1, this.graph.view.scale);
if (
pts.length === 2 ||
(pts.length === 2 && pts[0] && pts[1]) ||
(pts.length === 3 &&
((Math.abs(pts[0].x - pts[1].x) < tol &&
Math.abs(pts[1].x - pts[2].x) < tol) ||
(Math.abs(pts[0].y - pts[1].y) < tol &&
Math.abs(pts[1].y - pts[2].y) < tol)))
pts[0] &&
pts[1] &&
pts[2] &&
((Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol) ||
(Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol)))
) {
const cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
const cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
const cx = pts[0].x + (pts[pts.length - 1]!.x - pts[0].x) / 2;
const cy = pts[0].y + (pts[pts.length - 1]!.y - pts[0].y) / 2;
pts = [
pts[0],
new Point(cx, cy),
new Point(cx, cy),
pts[pts.length - 1],
];
}
pts = [pts[0], new Point(cx, cy), new Point(cx, cy), pts[pts.length - 1]];
}
return pts;
@ -60,17 +54,17 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
*
* Updates the given preview state taking into account the state of the constraint handler.
*/
getPreviewPoints(point: Point): Point[] {
getPreviewPoints(point: Point) {
if (this.isSource || this.isTarget) {
return super.getPreviewPoints(point);
}
const pts = this.getCurrentPoints();
let last = this.convertPoint(pts[0].clone(), false);
let last = this.convertPoint(pts[0]!.clone(), false);
point = this.convertPoint(point.clone(), false);
let result = [];
let result: Point[] = [];
for (let i = 1; i < pts.length; i += 1) {
const pt = this.convertPoint(pts[i].clone(), false);
const pt = this.convertPoint(pts[i]!.clone(), false);
if (i === this.index) {
if (Math.round(last.x - pt.x) === 0) {
@ -117,29 +111,29 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
*
* Overridden to perform optimization of the edge style result.
*/
updatePreviewState(edge: Cell,
updatePreviewState(
edge: CellState,
point: Point,
terminalState: CellState,
me: MouseEvent): void {
me: InternalMouseEvent
): void {
super.updatePreviewState(edge, point, terminalState, me);
// Checks and corrects preview by running edge style again
if (!this.isSource && !this.isTarget) {
point = this.convertPoint(point.clone(), false);
const pts = edge.absolutePoints;
let pt0 = pts[0];
let pt1 = pts[1];
let pt0 = pts[0] as Point;
let pt1 = pts[1] as Point;
let result = [];
for (let i = 2; i < pts.length; i += 1) {
const pt2 = pts[i];
const pt2 = pts[i] as Point;
// Merges adjacent segments only if more than 2 to allow for straight edges
if (
(Math.round(pt0.x - pt1.x) !== 0 ||
Math.round(pt1.x - pt2.x) !== 0) &&
(Math.round(pt0.x - pt1.x) !== 0 || Math.round(pt1.x - pt2.x) !== 0) &&
(Math.round(pt0.y - pt1.y) !== 0 || Math.round(pt1.y - pt2.y) !== 0)
) {
result.push(this.convertPoint(pt1.clone(), false));
@ -153,11 +147,14 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
const target = this.state.getVisibleTerminalState(false);
const rpts = this.state.absolutePoints;
const end = pts[pts.length - 1];
// A straight line is represented by 3 handles
if (
result.length === 0 &&
(Math.round(pts[0].x - pts[pts.length - 1].x) === 0 ||
Math.round(pts[0].y - pts[pts.length - 1].y) === 0)
pts[0] &&
end &&
(Math.round(pts[0].x - end.x) === 0 || Math.round(pts[0].y - end.y) === 0)
) {
result = [point, point];
}
@ -168,7 +165,7 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
source != null &&
target != null &&
rpts != null &&
Math.round(rpts[0].x - rpts[rpts.length - 1].x) === 0
Math.round(rpts[0]!.x - rpts[rpts.length - 1]!.x) === 0
) {
const view = this.graph.getView();
const scale = view.getScale();
@ -191,10 +188,10 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
let ye = view.getRoutingCenterY(target) / scale - tr.y;
// Use fixed connection point y-coordinate if one exists
const tc = this.graph.connection.getConnectionConstraint(edge, target, false);
const tc = this.graph.getConnectionConstraint(edge, target, false);
if (tc) {
const pt = this.graph.connection.getConnectionPoint(target, tc);
const pt = this.graph.getConnectionPoint(target, tc);
if (pt != null) {
this.convertPoint(pt, false);
@ -217,15 +214,16 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
/**
* Overriden to merge edge segments.
*/
connect(edge: Cell,
connect(
edge: Cell,
terminal: Cell,
isSource: boolean,
isClone: boolean,
me: MouseEvent): Cell {
me: InternalMouseEvent
) {
const model = this.graph.getModel();
let geo = edge.getGeometry();
let result = null;
let result: Point[] | null = null;
// Merges adjacent edge segments
if (geo != null && geo.points != null && geo.points.length > 0) {
@ -239,8 +237,10 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
// Merges adjacent segments only if more than 2 to allow for straight edges
if (
(Math.round(pt0.x - pt1.x) !== 0 ||
Math.round(pt1.x - pt2.x) !== 0) &&
pt0 &&
pt1 &&
pt2 &&
(Math.round(pt0.x - pt1.x) !== 0 || Math.round(pt1.x - pt2.x) !== 0) &&
(Math.round(pt0.y - pt1.y) !== 0 || Math.round(pt1.y - pt2.y) !== 0)
) {
result.push(this.convertPoint(pt1.clone(), false));
@ -273,7 +273,7 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
*
* Returns no tooltips.
*/
getTooltipForNode(node: any): string {
getTooltipForNode(node: Element): string | null {
return null;
}
@ -282,8 +282,7 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
*
* Adds custom bends for the center of each segment.
*/
// start(x: number, y: number, index: number): void;
start(x: number, y: number, index: number): void {
start(x: number, y: number, index: number) {
super.start(x, y, index);
if (
@ -321,11 +320,11 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
for (let i = 0; i < pts.length - 1; i += 1) {
bend = this.createVirtualBend();
bends.push(bend);
let horizontal = Math.round(pts[i].x - pts[i + 1].x) === 0;
let horizontal = Math.round(pts[i]!.x - pts[i + 1]!.x) === 0;
// Special case where dy is 0 as well
if (Math.round(pts[i].y - pts[i + 1].y) === 0 && i < pts.length - 2) {
horizontal = Math.round(pts[i].x - pts[i + 2].x) === 0;
if (Math.round(pts[i]!.y - pts[i + 1]!.y) === 0 && i < pts.length - 2) {
horizontal = Math.round(pts[i]!.x - pts[i + 2]!.x) === 0;
}
bend.setCursor(horizontal ? 'col-resize' : 'row-resize');
@ -347,7 +346,7 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
*
* Overridden to invoke <refresh> before the redraw.
*/
redraw(): void {
redraw() {
this.refresh();
super.redraw();
}
@ -357,7 +356,7 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
*
* Updates the position of the custom bends.
*/
redrawInnerBends(p0: Point, pe: Point): void {
redrawInnerBends(p0: Point, pe: Point) {
if (this.graph.isCellBendable(this.state.cell)) {
const pts = this.getCurrentPoints();
@ -367,17 +366,21 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
// Puts handle in the center of straight edges
if (
pts.length === 4 &&
pts[0] &&
pts[1] &&
pts[2] &&
pts[3] &&
Math.round(pts[1].x - pts[2].x) === 0 &&
Math.round(pts[1].y - pts[2].y) === 0
) {
straight = true;
if (Math.round(pts[0].y - pts[pts.length - 1].y) === 0) {
const cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
if (Math.round(pts[0].y - pts[pts.length - 1]!.y) === 0) {
const cx = pts[0].x + (pts[pts.length - 1]!.x - pts[0].x) / 2;
pts[1] = new Point(cx, pts[1].y);
pts[2] = new Point(cx, pts[2].y);
} else {
const cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
const cy = pts[0].y + (pts[pts.length - 1]!.y - pts[0].y) / 2;
pts[1] = new Point(pts[1].x, cy);
pts[2] = new Point(pts[2].x, cy);
}
@ -385,13 +388,10 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
for (let i = 0; i < pts.length - 1; i += 1) {
if (this.bends[i + 1] != null) {
p0 = pts[i];
pe = pts[i + 1];
const pt = new Point(
p0.x + (pe.x - p0.x) / 2,
p0.y + (pe.y - p0.y) / 2
);
const b = this.bends[i + 1].bounds;
p0 = pts[i] as Point;
pe = pts[i + 1] as Point;
const pt = new Point(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
const b = this.bends[i + 1].bounds as Rectangle;
this.bends[i + 1].bounds = new Rectangle(
Math.floor(pt.x - b.width / 2),
Math.floor(pt.y - b.height / 2),
@ -401,7 +401,7 @@ class EdgeSegmentHandler extends ElbowEdgeHandler {
this.bends[i + 1].redraw();
if (this.manageLabelHandle) {
this.checkLabelHandle(this.bends[i + 1].bounds);
this.checkLabelHandle(this.bends[i + 1].bounds as Rectangle);
}
}
}

View File

@ -20,6 +20,8 @@ import Rectangle from '../../geometry/Rectangle';
import utils, { intersects } from '../../../util/Utils';
import mxClient from '../../../mxClient';
import { isConsumed } from '../../../util/EventUtils';
import CellState from '../datatypes/CellState';
import InternalMouseEvent from '../../event/InternalMouseEvent';
/**
* Class: mxElbowEdgeHandler
@ -38,7 +40,7 @@ import { isConsumed } from '../../../util/EventUtils';
* state - <mxCellState> of the cell to be modified.
*/
class ElbowEdgeHandler extends EdgeHandler {
constructor(state) {
constructor(state: CellState) {
super(state);
}
@ -46,7 +48,6 @@ class ElbowEdgeHandler extends EdgeHandler {
* Specifies if a double click on the middle handle should call
* <mxGraph.flipEdge>. Default is true.
*/
// flipEnabled: boolean;
flipEnabled = true;
/**
@ -77,9 +78,9 @@ class ElbowEdgeHandler extends EdgeHandler {
// Virtual
bends.push(
this.createVirtualBend((evt) => {
this.createVirtualBend((evt: MouseEvent) => {
if (!isConsumed(evt) && this.flipEnabled) {
this.graph.flipEdge(this.state.cell, evt);
this.graph.flipEdge(this.state.cell);
InternalEvent.consume(evt);
}
})
@ -102,8 +103,7 @@ class ElbowEdgeHandler extends EdgeHandler {
* Creates a virtual bend that supports double clicking and calls
* <mxGraph.flipEdge>.
*/
// createVirtualBend(dblClickHandler: (evt: Event) => void): mxRectangleShape;
createVirtualBend(dblClickHandler) {
createVirtualBend(dblClickHandler?: (evt: MouseEvent) => void) {
const bend = this.createHandleShape();
this.initBend(bend, dblClickHandler);
@ -121,12 +121,9 @@ class ElbowEdgeHandler extends EdgeHandler {
*
* Returns the cursor to be used for the bend.
*/
// getCursorForBend(): string;
getCursorForBend() {
return this.state.style.edge === mxEdgeStyle.TopToBottom ||
this.state.style.edge === EDGESTYLE_TOPTOBOTTOM ||
((this.state.style.edge === mxEdgeStyle.ElbowConnector ||
this.state.style.edge === EDGESTYLE_ELBOW) &&
return this.state.style.edge === EDGESTYLE_TOPTOBOTTOM ||
(this.state.style.edge === EDGESTYLE_ELBOW &&
this.state.style.elbow === ELBOW_VERTICAL)
? 'row-resize'
: 'col-resize';
@ -137,8 +134,7 @@ class ElbowEdgeHandler extends EdgeHandler {
*
* Returns the tooltip for the given node.
*/
// getTooltipForNode(node: Element): string;
getTooltipForNode(node) {
getTooltipForNode(node: Element) {
let tip = null;
if (
@ -164,8 +160,7 @@ class ElbowEdgeHandler extends EdgeHandler {
* point - <mxPoint> to be converted.
* gridEnabled - Boolean that specifies if the grid should be applied.
*/
// convertPoint(point: mxPoint, gridEnabled: boolean): mxPoint;
convertPoint(point, gridEnabled) {
convertPoint(point: Point, gridEnabled: boolean) {
const scale = this.graph.getView().getScale();
const tr = this.graph.getView().getTranslate();
const { origin } = this.state;
@ -191,17 +186,16 @@ class ElbowEdgeHandler extends EdgeHandler {
* p0 - <mxPoint> that represents the location of the first point.
* pe - <mxPoint> that represents the location of the last point.
*/
// redrawInnerBends(p0: mxPoint, pe: mxPoint): void;
redrawInnerBends(p0, pe) {
redrawInnerBends(p0: Point, pe: Point) {
const g = this.state.cell.getGeometry();
const pts = this.state.absolutePoints;
let pt = null;
// Keeps the virtual bend on the edge shape
if (pts.length > 1) {
p0 = pts[1];
pe = pts[pts.length - 2];
} else if (g.points != null && g.points.length > 0) {
p0 = pts[1] as Point;
pe = pts[pts.length - 2] as Point;
} else if (g!.points != null && g!.points.length > 0) {
pt = pts[0];
}
@ -219,30 +213,21 @@ class ElbowEdgeHandler extends EdgeHandler {
// Makes handle slightly bigger if the yellow label handle
// exists and intersects this green handle
const b = this.bends[1].bounds;
let w = b.width;
let h = b.height;
let bounds = new Rectangle(
Math.round(pt.x - w / 2),
Math.round(pt.y - h / 2),
w,
h
);
let w = b!.width;
let h = b!.height;
let bounds = new Rectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h);
if (this.manageLabelHandle) {
this.checkLabelHandle(bounds);
} else if (
this.handleImage == null &&
this.labelShape.visible &&
this.labelShape.bounds &&
intersects(bounds, this.labelShape.bounds)
) {
w = HANDLE_SIZE + 3;
h = HANDLE_SIZE + 3;
bounds = new Rectangle(
Math.floor(pt.x - w / 2),
Math.floor(pt.y - h / 2),
w,
h
);
bounds = new Rectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h);
}
this.bends[1].bounds = bounds;

View File

@ -232,7 +232,7 @@ class GraphEdge extends autoImplement<PartialClass>() {
splitEdge(
edge: Cell,
cells: CellArray,
newEdge: Cell,
newEdge: Cell | null,
dx = 0,
dy = 0,
x: number,
@ -311,7 +311,7 @@ class GraphEdge extends autoImplement<PartialClass>() {
let value: any; // note me - can be a string or a class instance!!!
let source: Cell;
let target: Cell;
let style: string; // TODO: Also allow for an object or class instance??
let style: string = ''; // TODO: Also allow for an object or class instance??
if (args.length === 1) {
// If only a single parameter, treat as an object

View File

@ -13,13 +13,27 @@ class GraphVertex extends autoImplement<PartialClass>() {
* Specifies the return value for vertices in {@link isLabelMovable}.
* @default false
*/
vertexLabelsMovable: boolean = false;
vertexLabelsMovable = false;
/**
* Specifies if negative coordinates for vertices are allowed.
* @default true
*/
allowNegativeCoordinates: boolean = true;
allowNegativeCoordinates = true;
/**
* Returns {@link allowNegativeCoordinates}.
*/
isAllowNegativeCoordinates() {
return this.allowNegativeCoordinates;
}
/**
* Sets {@link allowNegativeCoordinates}.
*/
setAllowNegativeCoordinates(value: boolean) {
this.allowNegativeCoordinates = value;
}
/**
* Function: insertVertex
@ -64,7 +78,7 @@ class GraphVertex extends autoImplement<PartialClass>() {
* geometryClass - Optional class reference to a class derived from mxGeometry.
* This can be useful for defining custom constraints.
*/
insertVertex = (...args: any[]): Cell => {
insertVertex = (...args: any[]) => {
let parent;
let id;
let value;
@ -147,7 +161,7 @@ class GraphVertex extends autoImplement<PartialClass>() {
*
* @param parent {@link mxCell} whose children should be returned.
*/
getChildVertices(parent: Cell): CellArray {
getChildVertices(parent: Cell) {
return this.getChildCells(parent, true, false);
}
@ -158,14 +172,14 @@ class GraphVertex extends autoImplement<PartialClass>() {
/**
* Returns {@link vertexLabelsMovable}.
*/
isVertexLabelsMovable(): boolean {
isVertexLabelsMovable() {
return this.vertexLabelsMovable;
}
/**
* Sets {@link vertexLabelsMovable}.
*/
setVertexLabelsMovable(value: boolean): void {
setVertexLabelsMovable(value: boolean) {
this.vertexLabelsMovable = value;
}
}

View File

@ -41,6 +41,8 @@ import CellArray from '../datatypes/CellArray';
import EdgeHandler from '../edge/EdgeHandler';
import CellHighlight from '../../selection/CellHighlight';
import EventSource from '../../event/EventSource';
import GraphHandler from '../../GraphHandler';
import SelectionCellsHandler from '../../selection/SelectionCellsHandler';
/**
* Class: mxVertexHandler
@ -82,10 +84,12 @@ class VertexHandler {
this.selectionBorder.setCursor(CURSOR_MOVABLE_VERTEX);
}
const graphHandler = this.graph.getPlugin('GraphHandler') as GraphHandler;
// Adds the sizer handles
if (
this.graph.graphHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < this.graph.graphHandler.maxCells
graphHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < graphHandler.maxCells
) {
const resizable = this.graph.isCellResizable(this.state.cell);
this.sizers = [];
@ -374,12 +378,14 @@ class VertexHandler {
* Returns true if the rotation handle should be showing.
*/
isRotationHandleVisible() {
const graphHandler = this.graph.getPlugin('GraphHandler') as GraphHandler;
return (
this.graph.isEnabled() &&
this.rotationEnabled &&
this.graph.isCellRotatable(this.state.cell) &&
(this.graph.graphHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < this.graph.graphHandler.maxCells)
(graphHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < graphHandler.maxCells)
);
}
@ -790,11 +796,15 @@ class VertexHandler {
const edges = this.graph.getEdges(this.state.cell);
this.edgeHandlers = [];
for (let i = 0; i < edges.length; i += 1) {
const handler = this.graph.selectionCellsHandler.getHandler(edges[i]);
const selectionCellsHandler = this.graph.getPlugin(
'SelectionCellsHandler'
) as SelectionCellsHandler;
if (handler != null) {
this.edgeHandlers.push(handler);
for (let i = 0; i < edges.length; i += 1) {
const handler = selectionCellsHandler.getHandler(edges[i]);
if (handler) {
this.edgeHandlers.push(handler as EdgeHandler);
}
}
}

View File

@ -48,9 +48,10 @@ import CellState from '../cell/datatypes/CellState';
import Graph from '../Graph';
import ConnectionConstraint from './ConnectionConstraint';
import Shape from '../geometry/shape/Shape';
import { Listenable } from '../../types';
import { GraphPlugin, Listenable } from '../../types';
import CellArray from '../cell/datatypes/CellArray';
type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell;
type FactoryMethod = (source: Cell | null, target: Cell | null, style?: string) => Cell;
/**
* Class: mxConnectionHandler
@ -208,7 +209,9 @@ type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell;
* optional cell style from the preview as the third argument. It returns
* the <mxCell> that represents the new edge.
*/
class ConnectionHandler extends EventSource {
class ConnectionHandler extends EventSource implements GraphPlugin {
static pluginId = 'ConnectionHandler';
constructor(graph: MaxGraph, factoryMethod: FactoryMethod | null = null) {
super();
@ -216,7 +219,7 @@ class ConnectionHandler extends EventSource {
this.factoryMethod = factoryMethod;
this.graph.addMouseListener(this);
this.marker = <CellMarker>this.createMarker();
this.marker = this.createMarker();
this.constraintHandler = new ConstraintHandler(this.graph);
// Redraws the icons if the graph changes
@ -269,7 +272,7 @@ class ConnectionHandler extends EventSource {
originalPoint: Point | null = null;
currentState: CellState | null = null;
selectedIcon: ImageShape | null = null;
waypoints: Point[] | null = null;
waypoints: Point[] = [];
/**
* Variable: graph
@ -519,10 +522,10 @@ class ConnectionHandler extends EventSource {
*/
isInsertBefore(
edge: Cell,
source: Cell,
target: Cell,
source: Cell | null,
target: Cell | null,
evt: MouseEvent,
dropTarget: Cell
dropTarget: Cell | null
) {
return this.insertBeforeSource && source !== target;
}
@ -857,7 +860,7 @@ class ConnectionHandler extends EventSource {
return icons;
}
return null;
return [];
}
/**
@ -945,7 +948,7 @@ class ConnectionHandler extends EventSource {
*
* Handles the event by initiating a new connection.
*/
mouseDown(sender: Listenable, me: InternalMouseEvent) {
mouseDown(sender: EventSource, me: InternalMouseEvent) {
this.mouseDownCounter += 1;
if (
@ -972,7 +975,7 @@ class ConnectionHandler extends EventSource {
this.mouseDownCounter = 1;
if (this.waypointsEnabled && !this.shape) {
this.waypoints = null;
this.waypoints = [];
this.shape = this.createShape();
if (this.edgeState) {
@ -1034,6 +1037,8 @@ class ConnectionHandler extends EventSource {
* or shift is pressed.
*/
isOutlineConnectEvent(me: InternalMouseEvent) {
if (!this.currentPoint) return false;
const offset = getOffset(this.graph.container);
const evt = me.getEvent();
@ -1068,9 +1073,9 @@ class ConnectionHandler extends EventSource {
updateCurrentState(me: InternalMouseEvent, point: Point): void {
this.constraintHandler.update(
me,
this.first == null,
!this.first,
false,
this.first == null || me.isSource(this.marker.highlight.shape) ? null : point
!this.first || me.isSource(this.marker.highlight.shape) ? null : point
);
if (
@ -1080,9 +1085,10 @@ class ConnectionHandler extends EventSource {
// Handles special case where grid is large and connection point is at actual point in which
// case the outline is not followed as long as we're < gridSize / 2 away from that point
if (
this.marker.highlight != null &&
this.marker.highlight.state != null &&
this.marker.highlight.state.cell === this.constraintHandler.currentFocus.cell
this.marker.highlight &&
this.marker.highlight.state &&
this.marker.highlight.state.cell === this.constraintHandler.currentFocus.cell &&
this.marker.highlight.shape
) {
// Direct repaint needed if cell already highlighted
if (this.marker.highlight.shape.stroke !== 'transparent') {
@ -1094,19 +1100,19 @@ class ConnectionHandler extends EventSource {
}
// Updates validation state
if (this.previous != null) {
if (this.previous) {
this.error = this.validateConnection(
this.previous.cell,
this.constraintHandler.currentFocus.cell
);
if (this.error == null) {
if (!this.error) {
this.currentState = this.constraintHandler.currentFocus;
}
if (
this.error != null ||
(this.currentState != null && !this.isCellEnabled(this.currentState.cell))
this.error ||
(this.currentState && !this.isCellEnabled(this.currentState.cell))
) {
this.constraintHandler.reset();
}
@ -1154,11 +1160,14 @@ class ConnectionHandler extends EventSource {
OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
this.marker.highlight.repaint();
} else if (this.marker.hasValidState()) {
const cell = me.getCell();
// Handles special case where actual end point of edge and current mouse point
// are not equal (due to grid snapping) and there is no hit on shape or highlight
// but ignores cases where parent is used for non-connectable child cells
if (
me.getCell().isConnectable() &&
cell &&
cell.isConnectable() &&
this.marker.getValidState() !== me.getState()
) {
this.marker.highlight.shape.stroke = 'transparent';
@ -1227,7 +1236,7 @@ class ConnectionHandler extends EventSource {
* Handles the event by updating the preview edge or by highlighting
* a possible source or target terminal.
*/
mouseMove(sender: MouseEvent, me: InternalMouseEvent) {
mouseMove(sender: EventSource, me: InternalMouseEvent) {
if (
!me.isConsumed() &&
(this.ignoreMouseDown || this.first || !this.graph.isMouseDown)
@ -1266,7 +1275,7 @@ class ConnectionHandler extends EventSource {
if (this.first) {
let constraint = null;
let current = point;
let current: Point | null = point;
// Uses the current point from the constraint handler if available
if (
@ -1291,7 +1300,7 @@ class ConnectionHandler extends EventSource {
}
}
let pt2 = this.first;
let pt2: Point | null = this.first;
// Moves the connect icon with the mouse
if (this.selectedIcon && this.selectedIcon.bounds) {
@ -1323,8 +1332,8 @@ class ConnectionHandler extends EventSource {
];
pt2 = this.edgeState.absolutePoints[0];
} else {
if (this.currentState != null) {
if (this.constraintHandler.currentConstraint == null) {
if (this.currentState) {
if (!this.constraintHandler.currentConstraint) {
const tmp = this.getTargetPerimeterPoint(this.currentState, me);
if (tmp != null) {
@ -1334,14 +1343,11 @@ class ConnectionHandler extends EventSource {
}
// Computes the source perimeter point
if (this.sourceConstraint == null && this.previous != null) {
const next =
this.waypoints != null && this.waypoints.length > 0
? this.waypoints[0]
: current;
const tmp = this.getSourcePerimeterPoint(this.previous, next, me);
if (!this.sourceConstraint && this.previous) {
const next = this.waypoints.length > 0 ? this.waypoints[0] : current;
const tmp = this.getSourcePerimeterPoint(this.previous, next as Point, me);
if (tmp != null) {
if (tmp) {
pt2 = tmp;
}
}
@ -1351,19 +1357,20 @@ class ConnectionHandler extends EventSource {
// by moving the preview shape away from the mouse. This
// makes sure the preview shape does not prevent the detection
// of the cell under the mousepointer even for slow gestures.
if (this.currentState == null && this.movePreviewAway) {
if (!this.currentState && this.movePreviewAway && current) {
let tmp = pt2;
if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2) {
if (this.edgeState && this.edgeState.absolutePoints.length >= 2) {
const tmp2 = this.edgeState.absolutePoints[
this.edgeState.absolutePoints.length - 2
];
if (tmp2 != null) {
if (tmp2) {
tmp = tmp2;
}
}
if (tmp) {
const dx = current.x - tmp.x;
const dy = current.y - tmp.y;
@ -1377,12 +1384,13 @@ class ConnectionHandler extends EventSource {
this.originalPoint = current.clone();
current.x -= (dx * 4) / len;
current.y -= (dy * 4) / len;
}
} else {
this.originalPoint = null;
}
// Creates the preview shape (lazy)
if (this.shape == null) {
if (!this.shape) {
const dx = Math.abs(me.getGraphX() - this.first.x);
const dy = Math.abs(me.getGraphY() - this.first.y);
@ -1392,7 +1400,7 @@ class ConnectionHandler extends EventSource {
) {
this.shape = this.createShape();
if (this.edgeState != null) {
if (this.edgeState) {
this.shape.apply(this.edgeState);
}
@ -1402,13 +1410,13 @@ class ConnectionHandler extends EventSource {
}
// Updates the points in the preview edge
if (this.shape != null) {
if (this.edgeState != null) {
if (this.shape) {
if (this.edgeState) {
this.shape.points = this.edgeState.absolutePoints;
} else {
let pts = [pt2];
if (this.waypoints != null) {
if (this.waypoints.length > 0) {
pts = pts.concat(this.waypoints);
}
@ -1420,7 +1428,7 @@ class ConnectionHandler extends EventSource {
}
// Makes sure endpoint of edge is visible during connect
if (this.cursor != null) {
if (this.cursor) {
this.graph.container.style.cursor = this.cursor;
}
@ -1428,14 +1436,14 @@ class ConnectionHandler extends EventSource {
me.consume();
} else if (!this.isEnabled() || !this.graph.isEnabled()) {
this.constraintHandler.reset();
} else if (this.previous !== this.currentState && this.edgeState == null) {
} else if (this.previous !== this.currentState && !this.edgeState) {
this.destroyIcons();
// Sets the cursor on the current shape
if (
this.currentState != null &&
this.error == null &&
this.constraintHandler.currentConstraint == null
this.currentState &&
!this.error &&
!this.constraintHandler.currentConstraint
) {
this.icons = this.createIcons(this.currentState);
@ -1462,7 +1470,9 @@ class ConnectionHandler extends EventSource {
for (let i = 0; i < this.icons.length && !hitsIcon; i += 1) {
hitsIcon =
target === this.icons[i].node || target.parentNode === this.icons[i].node;
target === this.icons[i].node ||
// @ts-ignore parentNode should exist.
(!!target && target.parentNode === this.icons[i].node);
}
if (!hitsIcon) {
@ -1479,7 +1489,7 @@ class ConnectionHandler extends EventSource {
*
* Updates <edgeState>.
*/
updateEdgeState(current: Point, constraint: ConnectionConstraint) {
updateEdgeState(current: Point | null, constraint: ConnectionConstraint | null) {
if (!this.edgeState) return;
// TODO: Use generic method for writing constraint to style
@ -1526,17 +1536,13 @@ class ConnectionHandler extends EventSource {
}
// Scales and translates the waypoints to the model
let realPoints = null;
if (this.waypoints != null) {
realPoints = [];
let realPoints = [];
for (let i = 0; i < this.waypoints.length; i += 1) {
const pt = this.waypoints[i].clone();
this.convertWaypoint(pt);
realPoints[i] = pt;
}
}
this.graph.view.updatePoints(
this.edgeState,
@ -1561,14 +1567,14 @@ class ConnectionHandler extends EventSource {
* state - <mxCellState> that represents the target cell state.
* me - <mxMouseEvent> that represents the mouse move.
*/
getTargetPerimeterPoint(state: CellState, me: MouseEvent): Point {
let result = null;
getTargetPerimeterPoint(state: CellState, me: InternalMouseEvent) {
let result: Point | null = null;
const { view } = state;
const targetPerimeter = view.getPerimeterFunction(state);
if (targetPerimeter != null) {
if (targetPerimeter && this.previous) {
const next =
this.waypoints != null && this.waypoints.length > 0
this.waypoints.length > 0
? this.waypoints[this.waypoints.length - 1]
: new Point(this.previous.getCenterX(), this.previous.getCenterY());
const tmp = targetPerimeter(
@ -1578,7 +1584,7 @@ class ConnectionHandler extends EventSource {
false
);
if (tmp != null) {
if (tmp) {
result = tmp;
}
} else {
@ -1600,13 +1606,13 @@ class ConnectionHandler extends EventSource {
* next - <mxPoint> that represents the next point along the previewed edge.
* me - <mxMouseEvent> that represents the mouse move.
*/
getSourcePerimeterPoint(state: CellState, next: Point, me: MouseEvent): Point {
getSourcePerimeterPoint(state: CellState, next: Point, me: InternalMouseEvent) {
let result = null;
const { view } = state;
const sourcePerimeter = view.getPerimeterFunction(state);
const c = new Point(state.getCenterX(), state.getCenterY());
if (sourcePerimeter != null) {
if (sourcePerimeter) {
const theta = getValue(state.style, 'rotation', 0);
const rad = -theta * (Math.PI / 180);
@ -1621,7 +1627,7 @@ class ConnectionHandler extends EventSource {
let tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);
if (tmp != null) {
if (tmp) {
if (theta !== 0) {
tmp = getRotatedPoint(
new Point(tmp.x, tmp.y),
@ -1652,7 +1658,7 @@ class ConnectionHandler extends EventSource {
* icons - Array of currently displayed icons.
* me - <mxMouseEvent> that contains the mouse event.
*/
updateIcons(state: CellState, icons: string[], me: InternalMouseEvent): void {
updateIcons(state: CellState, icons: ImageShape[], me: InternalMouseEvent) {
// empty
}
@ -1664,8 +1670,8 @@ class ConnectionHandler extends EventSource {
* called if <waypointsEnabled> is true. This implemtation returns true
* if there is a cell state in the given event.
*/
isStopEvent(me: InternalMouseEvent): boolean {
return me.getState() != null;
isStopEvent(me: InternalMouseEvent) {
return !!me.getState();
}
/**
@ -1673,20 +1679,18 @@ class ConnectionHandler extends EventSource {
*
* Adds the waypoint for the given event to <waypoints>.
*/
addWaypointForEvent(me: InternalMouseEvent): void {
addWaypointForEvent(me: InternalMouseEvent) {
if (!this.first) return;
let point = convertPoint(this.graph.container, me.getX(), me.getY());
const dx = Math.abs(point.x - this.first.x);
const dy = Math.abs(point.y - this.first.y);
const addPoint =
this.waypoints != null ||
this.waypoints.length > 0 ||
(this.mouseDownCounter > 1 &&
(dx > this.graph.getEventTolerance() || dy > this.graph.getEventTolerance()));
if (addPoint) {
if (this.waypoints == null) {
this.waypoints = [];
}
const { scale } = this.graph.view;
point = new Point(
this.graph.snap(me.getGraphX() / scale) * scale,
@ -1703,12 +1707,12 @@ class ConnectionHandler extends EventSource {
* implementation returns true if the constraints are not pointing to the
* same fixed connection point.
*/
checkConstraints(c1, c2) {
checkConstraints(c1: ConnectionConstraint | null, c2: ConnectionConstraint | null) {
return (
c1 == null ||
c2 == null ||
c1.point == null ||
c2.point == null ||
!c1 ||
!c2 ||
!c1.point ||
!c2.point ||
!c1.point.equals(c2.point) ||
c1.dx !== c2.dx ||
c1.dy !== c2.dy ||
@ -1721,7 +1725,7 @@ class ConnectionHandler extends EventSource {
*
* Handles the event by inserting the new connection.
*/
mouseUp(sender: InternalMouseEvent, me: InternalMouseEvent): void {
mouseUp(sender: EventSource, me: InternalMouseEvent) {
if (!me.isConsumed() && this.isConnecting()) {
if (this.waypointsEnabled && !this.isStopEvent(me)) {
this.addWaypointForEvent(me);
@ -1733,27 +1737,24 @@ class ConnectionHandler extends EventSource {
const c1 = this.sourceConstraint;
const c2 = this.constraintHandler.currentConstraint;
const source = this.previous != null ? this.previous.cell : null;
const source = this.previous ? this.previous.cell : null;
let target = null;
if (
this.constraintHandler.currentConstraint != null &&
this.constraintHandler.currentFocus != null
this.constraintHandler.currentConstraint &&
this.constraintHandler.currentFocus
) {
target = this.constraintHandler.currentFocus.cell;
}
if (target == null && this.currentState != null) {
if (!target && this.currentState) {
target = this.currentState.cell;
}
// Inserts the edge if no validation error exists and if constraints differ
if (
this.error == null &&
(source == null ||
target == null ||
source !== target ||
this.checkConstraints(c1, c2))
!this.error &&
(!source || !target || source !== target || this.checkConstraints(c1, c2))
) {
this.connect(source, target, me.getEvent(), me.getCell());
} else {
@ -1763,7 +1764,7 @@ class ConnectionHandler extends EventSource {
this.marker.validState != null &&
this.previous.cell === this.marker.validState.cell
) {
this.graph.selectCellForEvent(this.marker.source, me.getEvent());
this.graph.selectCellForEvent(this.marker.validState.cell, me.getEvent());
}
// Displays the error message if it is not an empty string,
@ -1887,38 +1888,48 @@ class ConnectionHandler extends EventSource {
* dropTarget - <mxCell> that represents the cell under the mouse when it was
* released.
*/
connect(source: Cell, target: Cell, evt: MouseEvent, dropTarget: Cell): void {
if (target != null || this.isCreateTarget(evt) || this.graph.isAllowDanglingEdges()) {
connect(
source: Cell | null,
target: Cell | null,
evt: MouseEvent,
dropTarget: Cell | null = null
) {
if (target || this.isCreateTarget(evt) || this.graph.isAllowDanglingEdges()) {
// Uses the common parent of source and target or
// the default parent to insert the edge
const model = this.graph.getModel();
let terminalInserted = false;
let edge = null;
let edge: Cell | null = null;
model.beginUpdate();
try {
if (
source != null &&
target == null &&
source &&
!target &&
!this.graph.isIgnoreTerminalEvent(evt) &&
this.isCreateTarget(evt)
) {
target = this.createTargetVertex(evt, source);
if (target != null) {
dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
if (target) {
dropTarget = this.graph.getDropTarget(new CellArray(target), evt, dropTarget);
terminalInserted = true;
// Disables edges as drop targets if the target cell was created
// FIXME: Should not shift if vertex was aligned (same in Java)
if (dropTarget == null || !dropTarget.isEdge()) {
const pstate = this.graph.getView().getState(dropTarget);
const pstate = dropTarget
? this.graph.getView().getState(dropTarget)
: null;
if (pstate != null) {
if (pstate) {
const tmp = target.getGeometry();
if (tmp) {
tmp.x -= pstate.origin.x;
tmp.y -= pstate.origin.y;
}
}
} else {
dropTarget = this.graph.getDefaultParent();
}
@ -1930,17 +1941,17 @@ class ConnectionHandler extends EventSource {
let parent = this.graph.getDefaultParent();
if (
source != null &&
target != null &&
source &&
target &&
source.getParent() === target.getParent() &&
source.getParent().getParent() !== model.getRoot()
) {
parent = source.getParent();
if (
source.geometry != null &&
source.geometry &&
source.geometry.relative &&
target.geometry != null &&
target.geometry &&
target.geometry.relative
) {
parent = parent.getParent();
@ -1950,16 +1961,16 @@ class ConnectionHandler extends EventSource {
// Uses the value of the preview edge state for inserting
// the new edge into the graph
let value = null;
let style = null;
let style = '';
if (this.edgeState != null) {
if (this.edgeState) {
value = this.edgeState.cell.value;
style = this.edgeState.cell.style;
style = this.edgeState.cell.style ?? '';
}
edge = this.insertEdge(parent, null, value, source, target, style);
edge = this.insertEdge(parent, '', value, source, target, style);
if (edge != null) {
if (edge && source) {
// Updates the connection constraints
this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
this.graph.setConnectionConstraint(
@ -1970,7 +1981,7 @@ class ConnectionHandler extends EventSource {
);
// Uses geometry of the preview edge state
if (this.edgeState != null) {
if (this.edgeState && this.edgeState.cell && this.edgeState.cell.geometry) {
model.setGeometry(edge, this.edgeState.cell.geometry);
}
@ -2006,7 +2017,7 @@ class ConnectionHandler extends EventSource {
}
// Uses scaled waypoints in geometry
if (this.waypoints != null && this.waypoints.length > 0) {
if (this.waypoints.length > 0) {
const s = this.graph.view.scale;
const tr = this.graph.view.translate;
geo.points = [];
@ -2017,7 +2028,7 @@ class ConnectionHandler extends EventSource {
}
}
if (target == null) {
if (!target && this.currentPoint) {
const t = this.graph.view.translate;
const s = this.graph.view.scale;
const pt =
@ -2027,8 +2038,8 @@ class ConnectionHandler extends EventSource {
this.originalPoint.y / s - t.y
)
: new Point(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
pt.x -= this.graph.panDx / this.graph.view.scale;
pt.y -= this.graph.panDy / this.graph.view.scale;
pt.x -= this.graph.getPanDx() / this.graph.view.scale;
pt.y -= this.graph.getPanDy() / this.graph.view.scale;
geo.setTerminalPoint(pt, false);
}
@ -2067,7 +2078,7 @@ class ConnectionHandler extends EventSource {
* Selects the given edge after adding a new connection. The target argument
* contains the target vertex if one has been inserted.
*/
selectCells(edge: Cell, target: Cell) {
selectCells(edge: Cell | null, target: Cell | null) {
this.graph.setSelectionCell(edge);
}
@ -2082,8 +2093,8 @@ class ConnectionHandler extends EventSource {
parent: Cell,
id: string,
value: any,
source: Cell,
target: Cell,
source: Cell | null,
target: Cell | null,
style: string
): Cell {
if (!this.factoryMethod) {
@ -2107,11 +2118,11 @@ class ConnectionHandler extends EventSource {
* evt - Mousedown event of the connect gesture.
* source - <mxCell> that represents the source terminal.
*/
createTargetVertex(evt: MouseEvent, source: Cell): Cell {
createTargetVertex(evt: MouseEvent, source: Cell) {
// Uses the first non-relative source
let geo = source.getGeometry();
while (geo != null && geo.relative) {
while (geo && geo.relative) {
source = source.getParent();
geo = source.getGeometry();
}
@ -2119,15 +2130,15 @@ class ConnectionHandler extends EventSource {
const clone = this.graph.cloneCell(source);
geo = clone.getGeometry();
if (geo != null) {
if (geo && this.currentPoint) {
const t = this.graph.view.translate;
const s = this.graph.view.scale;
const point = new Point(
this.currentPoint.x / s - t.x,
this.currentPoint.y / s - t.y
);
geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s);
geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s);
geo.x = Math.round(point.x - geo.width / 2 - this.graph.getPanDx() / s);
geo.y = Math.round(point.y - geo.height / 2 - this.graph.getPanDy() / s);
// Aligns with source if within certain tolerance
const tol = this.getAlignmentTolerance();
@ -2158,7 +2169,7 @@ class ConnectionHandler extends EventSource {
*
* 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.isGridEnabled()
? this.graph.getGridSize() / 2
: this.graph.getSnapTolerance();
@ -2179,7 +2190,7 @@ class ConnectionHandler extends EventSource {
* target - <mxCell> that represents the target terminal.
* style - Optional style from the preview edge.
*/
createEdge(value?: any, source?: Cell, target?: Cell, style?: string): Cell {
createEdge(value: any, source: Cell | null, target: Cell | null, style: string = '') {
let edge = null;
// Creates a new edge using the factoryMethod
@ -2207,7 +2218,7 @@ class ConnectionHandler extends EventSource {
* called on all instances. It is called automatically for the built-in
* instance created for each <mxGraph>.
*/
destroy(): void {
onDestroy() {
this.graph.removeMouseListener(this);
if (this.shape != null) {
@ -2217,29 +2228,24 @@ class ConnectionHandler extends EventSource {
if (this.marker != null) {
this.marker.destroy();
this.marker = null;
}
if (this.constraintHandler != null) {
this.constraintHandler.destroy();
this.constraintHandler = null;
}
if (this.changeHandler != null) {
this.graph.getModel().removeListener(this.changeHandler);
this.graph.getView().removeListener(this.changeHandler);
this.changeHandler = null;
}
if (this.drillHandler != null) {
this.graph.removeListener(this.drillHandler);
this.graph.getView().removeListener(this.drillHandler);
this.drillHandler = null;
}
if (this.escapeHandler != null) {
this.graph.removeListener(this.escapeHandler);
this.escapeHandler = null;
}
}
}

View File

@ -15,11 +15,17 @@ import {
HIGHLIGHT_STROKEWIDTH,
} from '../../util/Constants';
import InternalEvent from '../event/InternalEvent';
import utils, { intersects } from '../../util/Utils';
import { intersects } from '../../util/Utils';
import Rectangle from '../geometry/Rectangle';
import ImageShape from '../geometry/shape/node/ImageShape';
import RectangleShape from '../geometry/shape/node/RectangleShape';
import { isShiftDown } from '../../util/EventUtils';
import { MaxGraph } from '../Graph';
import CellState from '../cell/datatypes/CellState';
import InternalMouseEvent from '../event/InternalMouseEvent';
import ConnectionConstraint from './ConnectionConstraint';
import Point from '../geometry/Point';
import Cell from '../cell/datatypes/Cell';
/**
* Handles constraints on connection targets. This class is in charge of
@ -29,11 +35,11 @@ import { isShiftDown } from '../../util/EventUtils';
* @class ConstraintHandler
*/
class ConstraintHandler {
constructor(graph) {
constructor(graph: MaxGraph) {
this.graph = graph;
// Adds a graph model listener to update the current focus on changes
this.resetHandler = (sender, evt) => {
this.resetHandler = () => {
if (
this.currentFocus != null &&
this.graph.view.getState(this.currentFocus.cell) == null
@ -60,26 +66,42 @@ class ConstraintHandler {
/**
* Reference to the enclosing {@link mxGraph}.
*/
// graph: mxGraph;
graph = null;
graph: MaxGraph;
resetHandler: () => void;
currentFocus: CellState | null = null;
currentFocusArea: Rectangle | null = null;
focusIcons: ImageShape[] = [];
constraints: ConnectionConstraint[] = [];
currentConstraint: ConnectionConstraint | null = null;
focusHighlight: RectangleShape | null = null;
focusPoints: Point[] = [];
currentPoint: Point | null = null;
/**
* Specifies if events are handled. Default is true.
*/
// enabled: boolean;
enabled = true;
/**
* Specifies the color for the highlight. Default is {@link DEFAULT_VALID_COLOR}.
*/
// highlightColor: string;
highlightColor = DEFAULT_VALID_COLOR;
mouseleaveHandler: (() => void) | null = null;
/**
* Returns true if events are handled. This implementation
* returns {@link enabled}.
*/
// isEnabled(): boolean;
isEnabled() {
return this.enabled;
}
@ -92,22 +114,20 @@ class ConstraintHandler {
*
* @param {boolean} enabled - Boolean that specifies the new enabled state.
*/
// setEnabled(enabled: boolean): void;
setEnabled(enabled) {
setEnabled(enabled: boolean) {
this.enabled = enabled;
}
/**
* Resets the state of this handler.
*/
// reset(): void;
reset() {
if (this.focusIcons != null) {
for (let i = 0; i < this.focusIcons.length; i += 1) {
this.focusIcons[i].destroy();
}
this.focusIcons = null;
this.focusIcons = [];
}
if (this.focusHighlight != null) {
@ -119,7 +139,7 @@ class ConstraintHandler {
this.currentFocusArea = null;
this.currentPoint = null;
this.currentFocus = null;
this.focusPoints = null;
this.focusPoints = [];
}
/**
@ -130,16 +150,18 @@ class ConstraintHandler {
*
* me - {@link mxMouseEvent} whose tolerance should be returned.
*/
// getTolerance(me: mxMouseEvent): number;
getTolerance(me) {
return this.graph.getTolerance();
getTolerance(me: InternalMouseEvent) {
return this.graph.getEventTolerance();
}
/**
* Returns the tolerance to be used for intersecting connection points.
*/
// getImageForConstraint(state: mxCellState, constraint: mxConnectionConstraint, point: mxPoint): mxImage;
getImageForConstraint(state, constraint, point) {
getImageForConstraint(
state: CellState,
constraint: ConnectionConstraint,
point: Point
) {
return this.pointImage;
}
@ -147,16 +169,14 @@ class ConstraintHandler {
* Returns true if the given {@link mxMouseEvent} should be ignored in {@link update}. This
* implementation always returns false.
*/
// isEventIgnored(me: mxMouseEvent, source: boolean): boolean;
isEventIgnored(me, source) {
isEventIgnored(me: InternalMouseEvent, source = false) {
return false;
}
/**
* Returns true if the given state should be ignored. This always returns false.
*/
// isStateIgnored(state: mxCellState, source: boolean): boolean;
isStateIgnored(state, source) {
isStateIgnored(state: CellState, source = false) {
return false;
}
@ -170,8 +190,8 @@ class ConstraintHandler {
this.focusIcons[i].destroy();
}
this.focusIcons = null;
this.focusPoints = null;
this.focusIcons = [];
this.focusPoints = [];
}
}
@ -190,16 +210,14 @@ class ConstraintHandler {
* Returns true if the current focused state should not be changed for the given event.
* This returns true if shift and alt are pressed.
*/
// isKeepFocusEvent(me: mxMouseEvent): boolean;
isKeepFocusEvent(me) {
isKeepFocusEvent(me: InternalMouseEvent) {
return isShiftDown(me.getEvent());
}
/**
* Returns the cell for the given event.
*/
// getCellForEvent(me: mxMouseEvent, point: mxPoint): mxCell;
getCellForEvent(me, point) {
getCellForEvent(me: InternalMouseEvent, point: Point | null) {
let cell = me.getCell();
// Gets cell under actual point if different from event location
@ -231,8 +249,12 @@ class ConstraintHandler {
* Updates the state of this handler based on the given {@link mxMouseEvent}.
* Source is a boolean indicating if the cell is a source or target.
*/
// update(me: mxMouseEvent, source: mxCell, existingEdge: mxCell, point: mxPoint): void;
update(me, source, existingEdge, point) {
update(
me: InternalMouseEvent,
source: boolean,
existingEdge: boolean,
point: Point | null
) {
if (this.isEnabled() && !this.isEventIgnored(me)) {
// Lazy installation of mouseleave handler
if (this.mouseleaveHandler == null && this.graph.container != null) {
@ -253,21 +275,21 @@ class ConstraintHandler {
2 * tol,
2 * tol
);
const state = this.graph.view.getState(this.getCellForEvent(me, point));
const state = this.graph.view.getState(this.getCellForEvent(me, point) as Cell);
// Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed
if (
!this.isKeepFocusEvent(me) &&
(this.currentFocusArea == null ||
this.currentFocus == null ||
state != null ||
state ||
!this.currentFocus.cell.isVertex() ||
!intersects(this.currentFocusArea, mouse)) &&
state !== this.currentFocus
) {
this.currentFocusArea = null;
this.currentFocus = null;
this.setFocus(me, state, source);
this.setFocus(me, state!, source);
}
this.currentConstraint = null;
@ -285,8 +307,8 @@ class ConstraintHandler {
const cy = mouse.getCenterY();
for (let i = 0; i < this.focusIcons.length; i += 1) {
const dx = cx - this.focusIcons[i].bounds.getCenterX();
const dy = cy - this.focusIcons[i].bounds.getCenterY();
const dx = cx - this.focusIcons[i].bounds!.getCenterX();
const dy = cy - this.focusIcons[i].bounds!.getCenterY();
tmp = dx * dx + dy * dy;
if (
@ -299,7 +321,7 @@ class ConstraintHandler {
this.currentPoint = this.focusPoints[i];
minDistSq = tmp;
tmp = this.focusIcons[i].bounds.clone();
tmp = this.focusIcons[i].bounds!.clone();
tmp.grow(HIGHLIGHT_SIZE + 1);
tmp.width -= 1;
tmp.height -= 1;
@ -347,12 +369,12 @@ class ConstraintHandler {
this.constraints != null &&
this.focusIcons != null
) {
const state = this.graph.view.getState(this.currentFocus.cell);
const state = this.graph.view.getState(this.currentFocus.cell) as CellState;
this.currentFocus = state;
this.currentFocusArea = new Rectangle(state.x, state.y, state.width, state.height);
for (let i = 0; i < this.constraints.length; i += 1) {
const cp = this.graph.getConnectionPoint(state, this.constraints[i]);
const cp = this.graph.getConnectionPoint(state, this.constraints[i]) as Point;
const img = this.getImageForConstraint(state, this.constraints[i], cp);
const bounds = new Rectangle(
@ -363,7 +385,7 @@ class ConstraintHandler {
);
this.focusIcons[i].bounds = bounds;
this.focusIcons[i].redraw();
this.currentFocusArea.add(this.focusIcons[i].bounds);
this.currentFocusArea.add(this.focusIcons[i].bounds as Rectangle);
this.focusPoints[i] = cp;
}
}
@ -374,14 +396,13 @@ class ConstraintHandler {
* the handler is not enabled then the outline is painted, but the constraints
* are ignored.
*/
// setFocus(me: mxMouseEvent, state: mxCellState, source: mxCell): void;
setFocus(me, state, source) {
setFocus(me: InternalMouseEvent, state: CellState, source: boolean) {
this.constraints =
state != null && !this.isStateIgnored(state, source) && state.cell.isConnectable()
? this.isEnabled()
? this.graph.getAllConnectionConstraints(state, source) || []
: []
: null;
: [];
// Only uses cells which have constraints
if (this.constraints != null) {
@ -393,15 +414,15 @@ class ConstraintHandler {
this.focusIcons[i].destroy();
}
this.focusIcons = null;
this.focusPoints = null;
this.focusIcons = [];
this.focusPoints = [];
}
this.focusPoints = [];
this.focusIcons = [];
for (let i = 0; i < this.constraints.length; i += 1) {
const cp = this.graph.getConnectionPoint(state, this.constraints[i]);
const cp = this.graph.getConnectionPoint(state, this.constraints[i]) as Point;
const img = this.getImageForConstraint(state, this.constraints[i], cp);
const { src } = img;
@ -419,7 +440,7 @@ class ConstraintHandler {
// Move the icon behind all other overlays
if (icon.node.previousSibling != null) {
icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
icon.node.parentNode?.insertBefore(icon.node, icon.node.parentNode.firstChild);
}
const getState = () => {
@ -429,7 +450,7 @@ class ConstraintHandler {
icon.redraw();
InternalEvent.redirectMouseEvents(icon.node, this.graph, getState);
this.currentFocusArea.add(icon.bounds);
this.currentFocusArea.add(icon.bounds as Rectangle);
this.focusIcons.push(icon);
this.focusPoints.push(cp);
}
@ -446,10 +467,9 @@ class ConstraintHandler {
*
* Returns true if the given icon intersects the given point.
*/
// createHighlightShape(): mxShape;
createHighlightShape() {
const hl = new RectangleShape(
null,
new Rectangle(),
this.highlightColor,
this.highlightColor,
HIGHLIGHT_STROKEWIDTH
@ -462,9 +482,8 @@ class ConstraintHandler {
/**
* Returns true if the given icon intersects the given rectangle.
*/
// intersects(icon: mxShape, mouse: mxRectangle, source: mxCell, existingEdge: mxCell): boolean;
intersects(icon, mouse, source, existingEdge) {
return intersects(icon.bounds, mouse);
intersects(icon: ImageShape, mouse: Rectangle, source: boolean, existingEdge: boolean) {
return intersects(icon.bounds as Rectangle, mouse);
}
/**
@ -474,12 +493,9 @@ class ConstraintHandler {
destroy() {
this.reset();
if (this.resetHandler != null) {
this.graph.model.removeListener(this.resetHandler);
this.graph.view.removeListener(this.resetHandler);
this.graph.removeListener(this.resetHandler);
this.resetHandler = null;
}
if (this.mouseleaveHandler != null && this.graph.container != null) {
InternalEvent.removeListener(
@ -492,4 +508,4 @@ class ConstraintHandler {
}
}
export default mxConstraintHandler;
export default ConstraintHandler;

View File

@ -202,7 +202,7 @@ class GraphConnections extends autoImplement<PartialClass>() {
*/
setConnectionConstraint(
edge: Cell,
terminal: Cell,
terminal: Cell | null,
source: boolean = false,
constraint: ConnectionConstraint | null = null
) {
@ -380,7 +380,7 @@ class GraphConnections extends autoImplement<PartialClass>() {
*/
connectCell(
edge: Cell,
terminal: Cell,
terminal: Cell | null = null,
source: boolean = false,
constraint: ConnectionConstraint | null = null
) {
@ -419,7 +419,7 @@ class GraphConnections extends autoImplement<PartialClass>() {
*/
cellConnected(
edge: Cell,
terminal: Cell,
terminal: Cell | null,
source: boolean = false,
constraint: ConnectionConstraint | null = null
) {
@ -435,9 +435,9 @@ class GraphConnections extends autoImplement<PartialClass>() {
if (this.isPortsEnabled()) {
let id = null;
if (this.isPort(terminal)) {
if (terminal && this.isPort(terminal)) {
id = terminal.getId();
terminal = <Cell>this.getTerminalForPort(terminal, source);
terminal = this.getTerminalForPort(terminal, source);
}
// Sets or resets all previous information for connecting to a child port

View File

@ -6,7 +6,12 @@
*/
import Rectangle from '../geometry/Rectangle';
import CellHighlight from '../selection/CellHighlight';
import utils, { getDocumentScrollOrigin, getOffset, getScrollOrigin, setOpacity } from '../../util/Utils';
import utils, {
getDocumentScrollOrigin,
getOffset,
getScrollOrigin,
setOpacity,
} from '../../util/Utils';
import InternalEvent from '../event/InternalEvent';
import mxClient from '../../mxClient';
import mxGuide from '../../util/Guide';
@ -21,6 +26,22 @@ import {
isPenEvent,
isTouchEvent,
} from '../../util/EventUtils';
import EventSource from '../event/EventSource';
import EventObject from '../event/EventObject';
import { MaxGraph } from '../Graph';
import InternalMouseEvent from '../event/InternalMouseEvent';
import Guide from '../../util/Guide';
import Cell from '../cell/datatypes/Cell';
import { GraphPlugin } from '../../types';
import GraphHandler from '../GraphHandler';
type DropHandler = (
graph: MaxGraph,
evt: MouseEvent,
cell: Cell | null,
x?: number,
y?: number
) => void;
/**
* @class DragSource
@ -33,7 +54,7 @@ import {
*
*/
class DragSource {
constructor(element, dropHandler) {
constructor(element: EventTarget, dropHandler: DropHandler) {
this.element = element;
this.dropHandler = dropHandler;
@ -43,11 +64,11 @@ class DragSource {
});
// Prevents native drag and drop
InternalEvent.addListener(element, 'dragstart', (evt) => {
InternalEvent.addListener(element, 'dragstart', (evt: MouseEvent) => {
InternalEvent.consume(evt);
});
this.eventConsumer = (sender, evt) => {
this.eventConsumer = (sender: EventSource, evt: EventObject) => {
const evtName = evt.getProperty('eventName');
const me = evt.getProperty('event');
@ -60,126 +81,115 @@ class DragSource {
/**
* Reference to the DOM node which was made draggable.
*/
// element: HTMLElement;
element = null;
element: EventTarget;
/**
* Holds the DOM node that is used to represent the drag preview. If this is
* null then the source element will be cloned and used for the drag preview.
*/
// dropHandler: Function;
dropHandler = null;
dropHandler: DropHandler;
eventConsumer: (sender: EventSource, evt: EventObject) => void;
/**
* {@link Point} that specifies the offset of the {@link dragElement}. Default is null.
*/
// dragOffset: mxPoint;
dragOffset = null;
dragOffset: Point | null = null;
/**
* Holds the DOM node that is used to represent the drag preview. If this is
* null then the source element will be cloned and used for the drag preview.
*/
// dragElement: HTMLElement;
dragElement = null;
dragElement: HTMLElement | null = null;
/**
* TODO - wrong description
* Optional {@link Rectangle} that specifies the unscaled size of the preview.
*/
// previewElement: mxRectangle;
previewElement = null;
previewElement: HTMLElement | null = null;
/**
* Variable: previewOffset
*
* Optional <mxPoint> that specifies the offset of the preview in pixels.
*/
previewOffset = null;
previewOffset: Point | null = null;
/**
* Specifies if this drag source is enabled. Default is true.
*/
// enabled: boolean;
enabled = true;
/**
* Reference to the {@link mxGraph} that is the current drop target.
*/
// currentGraph: mxGraph;
currentGraph = null;
currentGraph: MaxGraph | null = null;
/**
* Holds the current drop target under the mouse.
*/
// currentDropTarget: mxCell;
currentDropTarget = null;
currentDropTarget: Cell | null = null;
/**
* Holds the current drop location.
*/
// currentPoint: mxPoint;
currentPoint = null;
currentPoint: Point | null = null;
/**
* Holds an {@link mxGuide} for the {@link currentGraph} if {@link dragPreview} is not null.
*/
// currentGuide: mxGuide;
currentGuide = null;
currentGuide: Guide | null = null;
/**
* Holds an {@link mxGuide} for the {@link currentGraph} if {@link dragPreview} is not null.
* @note wrong doc
*/
// currentHighlight: mxCellHighlight;
currentHighlight = null;
currentHighlight: CellHighlight | null = null;
/**
* Specifies if the graph should scroll automatically. Default is true.
*/
// autoscroll: boolean;
autoscroll = true;
/**
* Specifies if {@link mxGuide} should be enabled. Default is true.
*/
// guidesEnabled: boolean;
guidesEnabled = true;
/**
* Specifies if the grid should be allowed. Default is true.
*/
// gridEnabled: boolean;
gridEnabled = true;
/**
* Specifies if drop targets should be highlighted. Default is true.
*/
// highlightDropTargets: boolean;
highlightDropTargets = true;
/**
* ZIndex for the drag element. Default is 100.
*/
// dragElementZIndex: number;
dragElementZIndex = 100;
/**
* Opacity of the drag element in %. Default is 70.
*/
// dragElementOpacity: number;
dragElementOpacity = 70;
/**
* Whether the event source should be checked in {@link graphContainerEvent}. Default
* is true.
*/
// checkEventSource: boolean;
checkEventSource = true;
mouseMoveHandler: ((evt: MouseEvent) => void) | null = null;
mouseUpHandler: ((evt: MouseEvent) => void) | null = null;
eventSource: EventTarget | null = null;
/**
* Returns {@link enabled}.
*/
// isEnabled(): boolean;
isEnabled() {
return this.enabled;
}
@ -187,15 +197,13 @@ class DragSource {
/**
* Sets {@link enabled}.
*/
// setEnabled(value: boolean): void;
setEnabled(value) {
setEnabled(value: boolean) {
this.enabled = value;
}
/**
* Returns {@link guidesEnabled}.
*/
// isGuidesEnabled(): boolean;
isGuidesEnabled() {
return this.guidesEnabled;
}
@ -203,15 +211,13 @@ class DragSource {
/**
* Sets {@link guidesEnabled}.
*/
// setGuidesEnabled(value: boolean): void;
setGuidesEnabled(value) {
setGuidesEnabled(value: boolean) {
this.guidesEnabled = value;
}
/**
* Returns {@link gridEnabled}.
*/
// isGridEnabled(): boolean;
isGridEnabled() {
return this.gridEnabled;
}
@ -219,8 +225,7 @@ class DragSource {
/**
* Sets {@link gridEnabled}.
*/
// setGridEnabled(value: boolean): void;
setGridEnabled(value) {
setGridEnabled(value: boolean) {
this.gridEnabled = value;
}
@ -228,8 +233,7 @@ class DragSource {
* Returns the graph for the given mouse event. This implementation returns
* null.
*/
// getGraphForEvent(evt: MouseEvent): mxGraph;
getGraphForEvent(evt) {
getGraphForEvent(evt: MouseEvent) {
return null;
}
@ -237,8 +241,7 @@ class DragSource {
* Returns the drop target for the given graph and coordinates. This
* implementation uses {@link mxGraph.getCellAt}.
*/
// getDropTarget(graph: mxGraph, x: number, y: number, evt: PointerEvent): mxCell;
getDropTarget(graph, x, y, evt) {
getDropTarget(graph: MaxGraph, x: number, y: number, evt: MouseEvent) {
return graph.getCellAt(x, y);
}
@ -246,34 +249,30 @@ class DragSource {
* Creates and returns a clone of the {@link dragElementPrototype} or the {@link element}
* if the former is not defined.
*/
// createDragElement(evt: Event): Node;
createDragElement(evt) {
return this.element.cloneNode(true);
createDragElement(evt: MouseEvent) {
return (this.element as HTMLElement).cloneNode(true) as HTMLElement;
}
/**
* Creates and returns an element which can be used as a preview in the given
* graph.
*/
// createPreviewElement(graph: mxGraph): HTMLElement;
createPreviewElement(graph) {
createPreviewElement(graph: MaxGraph): HTMLElement | null {
return null;
}
/**
* Returns true if this drag source is active.
*/
// isActive(): boolean;
isActive() {
return this.mouseMoveHandler != null;
return !!this.mouseMoveHandler;
}
/**
* Stops and removes everything and restores the state of the object.
*/
// reset(): void;
reset() {
if (this.currentGraph != null) {
if (this.currentGraph) {
this.dragExit(this.currentGraph);
this.currentGraph = null;
}
@ -303,8 +302,7 @@ class DragSource {
* };
* ```
*/
// mouseDown(evt: mxMouseEvent): void;
mouseDown(evt) {
mouseDown(evt: MouseEvent) {
if (this.enabled && !isConsumed(evt) && this.mouseMoveHandler == null) {
this.startDrag(evt);
this.mouseMoveHandler = this.mouseMove.bind(this);
@ -318,6 +316,8 @@ class DragSource {
if (mxClient.IS_TOUCH && !isMouseEvent(evt)) {
this.eventSource = getSource(evt);
if (this.eventSource) {
InternalEvent.addGestureListeners(
this.eventSource,
null,
@ -327,15 +327,15 @@ class DragSource {
}
}
}
}
/**
* Creates the {@link dragElement} using {@link createDragElement}.
*/
// startDrag(evt: mxMouseEvent): void;
startDrag(evt) {
startDrag(evt: MouseEvent) {
this.dragElement = this.createDragElement(evt);
this.dragElement.style.position = 'absolute';
this.dragElement.style.zIndex = this.dragElementZIndex;
this.dragElement.style.zIndex = String(this.dragElementZIndex);
setOpacity(this.dragElement, this.dragElementOpacity);
if (this.checkEventSource && mxClient.IS_SVG) {
@ -346,7 +346,6 @@ class DragSource {
/**
* Invokes {@link removeDragElement}.
*/
// stopDrag(): void;
stopDrag() {
// LATER: This used to have a mouse event. If that is still needed we need to add another
// final call to the DnD protocol to add a cleanup step in the case of escape press, which
@ -357,10 +356,9 @@ class DragSource {
/**
* Removes and destroys the {@link dragElement}.
*/
// removeDragElement(): void;
removeDragElement() {
if (this.dragElement != null) {
if (this.dragElement.parentNode != null) {
if (this.dragElement) {
if (this.dragElement.parentNode) {
this.dragElement.parentNode.removeChild(this.dragElement);
}
@ -371,8 +369,7 @@ class DragSource {
/**
* Returns the topmost element under the given event.
*/
// getElementForEvent(evt: Event): Element;
getElementForEvent(evt) {
getElementForEvent(evt: MouseEvent) {
return isTouchEvent(evt) || isPenEvent(evt)
? document.elementFromPoint(getClientX(evt), getClientY(evt))
: getSource(evt);
@ -381,8 +378,7 @@ class DragSource {
/**
* Returns true if the given graph contains the given event.
*/
// graphContainsEvent(graph: mxGraph, evt: Event): boolean;
graphContainsEvent(graph, evt) {
graphContainsEvent(graph: MaxGraph, evt: MouseEvent) {
const x = getClientX(evt);
const y = getClientY(evt);
const offset = getOffset(graph.container);
@ -390,14 +386,15 @@ class DragSource {
let elt = this.getElementForEvent(evt);
if (this.checkEventSource) {
while (elt != null && elt !== graph.container) {
while (elt && elt !== graph.container) {
// @ts-ignore parentNode may exist
elt = elt.parentNode;
}
}
// Checks if event is inside the bounds of the graph container
return (
elt != null &&
!!elt &&
x >= offset.x - origin.x &&
y >= offset.y - origin.y &&
x <= offset.x - origin.x + graph.container.offsetWidth &&
@ -410,35 +407,33 @@ class DragSource {
* {@link currentGraph}, calling {@link dragEnter} and {@link dragExit} on the new and old graph,
* respectively, and invokes {@link dragOver} if {@link currentGraph} is not null.
*/
// mouseMove(evt: MouseEvent): void;
mouseMove(evt) {
mouseMove(evt: MouseEvent) {
let graph = this.getGraphForEvent(evt);
// Checks if event is inside the bounds of the graph container
if (graph != null && !this.graphContainsEvent(graph, evt)) {
if (graph && !this.graphContainsEvent(graph, evt)) {
graph = null;
}
if (graph !== this.currentGraph) {
if (this.currentGraph != null) {
if (this.currentGraph) {
this.dragExit(this.currentGraph, evt);
}
this.currentGraph = graph;
if (this.currentGraph != null) {
if (this.currentGraph) {
this.dragEnter(this.currentGraph, evt);
}
}
if (this.currentGraph != null) {
if (this.currentGraph) {
this.dragOver(this.currentGraph, evt);
}
if (
this.dragElement != null &&
(this.previewElement == null ||
this.previewElement.style.visibility !== 'visible')
this.dragElement &&
(!this.previewElement || this.previewElement.style.visibility !== 'visible')
) {
let x = getClientX(evt);
let y = getClientY(evt);
@ -449,7 +444,7 @@ class DragSource {
this.dragElement.style.visibility = 'visible';
if (this.dragOffset != null) {
if (this.dragOffset) {
x += this.dragOffset.x;
y += this.dragOffset.y;
}
@ -458,7 +453,7 @@ class DragSource {
this.dragElement.style.left = `${x + offset.x}px`;
this.dragElement.style.top = `${y + offset.y}px`;
} else if (this.dragElement != null) {
} else if (this.dragElement) {
this.dragElement.style.visibility = 'hidden';
}
@ -469,13 +464,11 @@ class DragSource {
* Processes the mouse up event and invokes {@link drop}, {@link dragExit} and {@link stopDrag}
* as required.
*/
// mouseUp(evt: MouseEvent): void;
mouseUp(evt) {
if (this.currentGraph != null) {
mouseUp(evt: MouseEvent) {
if (this.currentGraph) {
if (
this.currentPoint != null &&
(this.previewElement == null ||
this.previewElement.style.visibility !== 'hidden')
this.currentPoint &&
(!this.previewElement || this.previewElement.style.visibility !== 'hidden')
) {
const { scale } = this.currentGraph.view;
const tr = this.currentGraph.view.translate;
@ -500,7 +493,7 @@ class DragSource {
*/
// removeListeners(): void;
removeListeners() {
if (this.eventSource != null) {
if (this.eventSource) {
InternalEvent.removeGestureListeners(
this.eventSource,
null,
@ -523,26 +516,19 @@ class DragSource {
/**
* Actives the given graph as a drop target.
*/
// dragEnter(graph: mxGraph, evt: Event): void;
dragEnter(graph, evt) {
dragEnter(graph: MaxGraph, evt: MouseEvent) {
graph.isMouseDown = true;
graph.isMouseTrigger = isMouseEvent(evt);
this.previewElement = this.createPreviewElement(graph);
if (
this.previewElement != null &&
this.checkEventSource &&
mxClient.IS_SVG
) {
if (this.previewElement && this.checkEventSource && mxClient.IS_SVG) {
this.previewElement.style.pointerEvents = 'none';
}
// Guide is only needed if preview element is used
if (this.isGuidesEnabled() && this.previewElement != null) {
this.currentGuide = new mxGuide(
graph,
graph.graphHandler.getGuideStates()
);
if (this.isGuidesEnabled() && this.previewElement) {
const graphHandler = graph.getPlugin('GraphHandler') as GraphHandler;
this.currentGuide = new mxGuide(graph, graphHandler.getGuideStates());
}
if (this.highlightDropTargets) {
@ -556,8 +542,7 @@ class DragSource {
/**
* Deactivates the given graph as a drop target.
*/
// dragExit(graph: mxGraph, evt: Event): void;
dragExit(graph, evt) {
dragExit(graph: MaxGraph, evt?: MouseEvent) {
this.currentDropTarget = null;
this.currentPoint = null;
graph.isMouseDown = false;
@ -565,20 +550,20 @@ class DragSource {
// Consumes all events in the current graph before they are fired
graph.removeListener(this.eventConsumer);
if (this.previewElement != null) {
if (this.previewElement.parentNode != null) {
if (this.previewElement) {
if (this.previewElement.parentNode) {
this.previewElement.parentNode.removeChild(this.previewElement);
}
this.previewElement = null;
}
if (this.currentGuide != null) {
if (this.currentGuide) {
this.currentGuide.destroy();
this.currentGuide = null;
}
if (this.currentHighlight != null) {
if (this.currentHighlight) {
this.currentHighlight.destroy();
this.currentHighlight = null;
}
@ -588,27 +573,29 @@ class DragSource {
* Implements autoscroll, updates the {@link currentPoint}, highlights any drop
* targets and updates the preview.
*/
// dragOver(graph: mxGraph, evt: Event): void;
dragOver(graph, evt) {
dragOver(graph: MaxGraph, evt: MouseEvent) {
const offset = getOffset(graph.container);
const origin = getScrollOrigin(graph.container);
let x = getClientX(evt) - offset.x + origin.x - graph.panDx;
let y = getClientY(evt) - offset.y + origin.y - graph.panDy;
let x = getClientX(evt) - offset.x + origin.x - graph.getPanDx();
let y = getClientY(evt) - offset.y + origin.y - graph.getPanDy();
if (graph.autoScroll && (this.autoscroll == null || this.autoscroll)) {
graph.scrollPointToVisible(x, y, graph.autoExtend);
if (graph.isAutoScroll() && (!this.autoscroll || this.autoscroll)) {
graph.scrollPointToVisible(x, y, graph.isAutoExtend());
}
// Highlights the drop target under the mouse
if (this.currentHighlight != null && graph.isDropEnabled()) {
if (this.currentHighlight && graph.isDropEnabled()) {
this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
if (this.currentDropTarget) {
const state = graph.getView().getState(this.currentDropTarget);
this.currentHighlight.highlight(state);
}
}
// Updates the location of the preview
if (this.previewElement != null) {
if (this.previewElement.parentNode == null) {
if (this.previewElement) {
if (!this.previewElement.parentNode) {
graph.container.appendChild(this.previewElement);
this.previewElement.style.zIndex = '3';
@ -619,10 +606,7 @@ class DragSource {
let hideGuide = true;
// Grid and guides
if (
this.currentGuide != null &&
this.currentGuide.isEnabledForEvent(evt)
) {
if (this.currentGuide && this.currentGuide.isEnabledForEvent(evt)) {
// LATER: HTML preview appears smaller than SVG preview
const w = parseInt(this.previewElement.style.width);
const h = parseInt(this.previewElement.style.height);
@ -635,16 +619,16 @@ class DragSource {
} else if (gridEnabled) {
const { scale } = graph.view;
const tr = graph.view.translate;
const off = graph.gridSize / 2;
const off = graph.getGridSize() / 2;
x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
}
if (this.currentGuide != null && hideGuide) {
if (this.currentGuide && hideGuide) {
this.currentGuide.hide();
}
if (this.previewOffset != null) {
if (this.previewOffset) {
x += this.previewOffset.x;
y += this.previewOffset.y;
}
@ -661,8 +645,13 @@ class DragSource {
* Returns the drop target for the given graph and coordinates. This
* implementation uses {@link mxGraph.getCellAt}.
*/
// drop(graph: mxGraph, evt: Event, dropTarget: mxCell, x: number, y: number): void;
drop(graph, evt, dropTarget, x, y) {
drop(
graph: MaxGraph,
evt: MouseEvent,
dropTarget: Cell | null = null,
x: number,
y: number
) {
this.dropHandler(graph, evt, dropTarget, x, y);
// Had to move this to after the insert because it will

View File

@ -1,13 +1,19 @@
import { autoImplement } from '../../util/Utils';
import Cell from '../cell/datatypes/Cell';
import CellArray from '../cell/datatypes/CellArray';
import InternalMouseEvent from '../event/InternalMouseEvent';
class GraphDragDrop {
import type GraphValidation from '../validation/GraphValidation';
type PartialValidation = Pick<GraphValidation, 'getEdgeValidationError'>;
type PartialClass = PartialValidation;
class GraphDragDrop extends autoImplement<PartialClass>() {
/**
* Specifies the return value for {@link isDropEnabled}.
* @default false
*/
dropEnabled: boolean = false;
dropEnabled = false;
/**
* Specifies if dropping onto edges should be enabled. This is ignored if
@ -15,7 +21,7 @@ class GraphDragDrop {
* out the drop operation.
* @default true
*/
splitEnabled: boolean = true;
splitEnabled = true;
/**
* Specifies if the graph should automatically scroll if the mouse goes near
@ -27,7 +33,9 @@ class GraphDragDrop {
* no scrollbars, the use of {@link allowAutoPanning} is recommended.
* @default true
*/
autoScroll: boolean = true;
autoScroll = true;
isAutoScroll = () => this.autoScroll;
/**
* Specifies if the size of the graph should be automatically extended if the
@ -35,8 +43,9 @@ class GraphDragDrop {
* account if the container has scrollbars. See {@link autoScroll}.
* @default true
*/
autoExtend: boolean = true;
autoExtend = true;
isAutoExtend = () => this.autoExtend;
/*****************************************************************************
* Group: Graph behaviour
@ -45,7 +54,7 @@ class GraphDragDrop {
/**
* Returns {@link dropEnabled} as a boolean.
*/
isDropEnabled(): boolean {
isDropEnabled() {
return this.dropEnabled;
}
@ -56,7 +65,7 @@ class GraphDragDrop {
* @param dropEnabled Boolean indicating if the graph should allow dropping
* of cells into other cells.
*/
setDropEnabled(value: boolean): void {
setDropEnabled(value: boolean) {
this.dropEnabled = value;
}
@ -67,7 +76,7 @@ class GraphDragDrop {
/**
* Returns {@link splitEnabled} as a boolean.
*/
isSplitEnabled(): boolean {
isSplitEnabled() {
return this.splitEnabled;
}
@ -78,7 +87,7 @@ class GraphDragDrop {
* @param dropEnabled Boolean indicating if the graph should allow dropping
* of cells into other cells.
*/
setSplitEnabled(value: boolean): void {
setSplitEnabled(value: boolean) {
this.splitEnabled = value;
}
@ -90,23 +99,17 @@ class GraphDragDrop {
* @param cells {@link mxCell} that should split the edge.
* @param evt Mouseevent that triggered the invocation.
*/
// isSplitTarget(target: mxCell, cells: mxCellArray, evt: Event): boolean;
isSplitTarget(target: Cell, cells: CellArray, evt: InternalMouseEvent): boolean {
isSplitTarget(target: Cell, cells: CellArray, evt: MouseEvent) {
if (
target.isEdge() &&
cells != null &&
cells.length == 1 &&
cells.length === 1 &&
cells[0].isConnectable() &&
this.getEdgeValidationError(target, target.getTerminal(true), cells[0]) ==
null
!this.getEdgeValidationError(target, target.getTerminal(true), cells[0])
) {
const src = <Cell>target.getTerminal(true);
const trg = <Cell>target.getTerminal(false);
const src = target.getTerminal(true);
const trg = target.getTerminal(false);
return (
!cells[0].isAncestor(src) &&
!cells[0].isAncestor(trg)
);
return !cells[0].isAncestor(src) && !cells[0].isAncestor(trg);
}
return false;
}

View File

@ -5,7 +5,12 @@
* Type definitions from the typed-mxgraph project
*/
import { getAlignmentAsPoint, getStringValue, getValue, setPrefixedStyle } from '../../util/Utils';
import {
getAlignmentAsPoint,
getStringValue,
getValue,
setPrefixedStyle,
} from '../../util/Utils';
import Rectangle from '../geometry/Rectangle';
import InternalEvent from '../event/InternalEvent';
import mxClient from '../../mxClient';
@ -23,20 +28,17 @@ import {
FONT_STRIKETHROUGH,
FONT_UNDERLINE,
LINE_HEIGHT,
NONE,
WORD_WRAP,
} from '../../util/Constants';
import TextShape from '../geometry/shape/node/TextShape';
import graph from '../Graph';
import Cell from '../cell/datatypes/Cell';
import InternalMouseEvent from '../event/InternalMouseEvent';
import CellState from '../cell/datatypes/CellState';
import Shape from '../geometry/shape/Shape';
import EventObject from '../event/EventObject';
import { extractTextWithWhitespace, isNode } from '../../util/DomUtils';
import {
htmlEntities,
replaceTrailingNewlines,
} from '../../util/StringUtils';
import { htmlEntities, replaceTrailingNewlines } from '../../util/StringUtils';
import {
getSource,
isConsumed,
@ -44,6 +46,12 @@ import {
isMetaDown,
isShiftDown,
} from '../../util/EventUtils';
import EventSource from '../event/EventSource';
import type { MaxGraph } from '../Graph';
import type { GraphPlugin } from '../../types';
import CellArray from '../cell/datatypes/CellArray';
import TooltipHandler from '../tooltip/TooltipHandler';
/**
* Class: mxCellEditor
@ -151,8 +159,10 @@ import {
*
* graph - Reference to the enclosing <mxGraph>.
*/
class CellEditor {
constructor(graph: graph) {
class CellEditor implements GraphPlugin {
static pluginId = 'CellEditor';
constructor(graph: MaxGraph) {
this.graph = graph;
// Stops editing after zoom changes
@ -163,24 +173,21 @@ class CellEditor {
};
// Handling of deleted cells while editing
this.changeHandler = (sender: any) => {
if (
this.editingCell != null &&
this.graph.getView().getState(this.editingCell, false) == null
) {
this.changeHandler = (sender: EventSource) => {
if (this.editingCell && !this.graph.getView().getState(this.editingCell, false)) {
this.stopEditing(true);
}
};
this.graph.view.addListener(InternalEvent.SCALE, this.zoomHandler);
this.graph.view.addListener(InternalEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
this.graph.getView().addListener(InternalEvent.SCALE, this.zoomHandler);
this.graph.getView().addListener(InternalEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
this.graph.getModel().addListener(InternalEvent.CHANGE, this.changeHandler);
}
// TODO: Document me!
changeHandler: Function | null;
changeHandler: (sender: EventSource) => void;
zoomHandler: Function | null;
zoomHandler: () => void;
clearOnChange: boolean = false;
@ -195,8 +202,7 @@ class CellEditor {
*
* Reference to the enclosing <mxGraph>.
*/
// graph: mxGraph;
graph: graph;
graph: MaxGraph;
/**
* Variable: textarea
@ -204,7 +210,6 @@ class CellEditor {
* Holds the DIV that is used for text editing. Note that this may be null before the first
* edit. Instantiated in <init>.
*/
// textarea: Element;
textarea: HTMLElement | null = null;
/**
@ -335,8 +340,7 @@ class CellEditor {
* Creates the <textarea> and installs the event listeners. The key handler
* updates the <modified> state.
*/
// init(): void;
init(): void {
init() {
this.textarea = document.createElement('div');
this.textarea.className = 'mxCellEditor mxPlainTextEditor';
this.textarea.contentEditable = String(true);
@ -356,7 +360,7 @@ class CellEditor {
* Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
*/
// applyValue(state: mxCellState, value: string): void;
applyValue(state: CellState, value: any): void {
applyValue(state: CellState, value: any) {
this.graph.labelChanged(state.cell, value, <InternalMouseEvent>this.trigger);
}
@ -365,8 +369,8 @@ class CellEditor {
*
* Sets the temporary horizontal alignment for the current editing session.
*/
setAlign(align: string): void {
if (this.textarea != null) {
setAlign(align: string) {
if (this.textarea) {
this.textarea.style.textAlign = align;
}
@ -379,12 +383,8 @@ class CellEditor {
*
* Gets the initial editing value for the given cell.
*/
// getInitialValue(state: mxCellState, trigger: Event): string;
getInitialValue(state: CellState, trigger: EventObject | InternalMouseEvent) {
let result = htmlEntities(
<string>this.graph.getEditingValue(state.cell, trigger),
false
);
getInitialValue(state: CellState, trigger: MouseEvent | null) {
let result = htmlEntities(this.graph.getEditingValue(state.cell, trigger), false);
result = replaceTrailingNewlines(result, '<div><br></div>');
return result.replace(/\n/g, '<br>');
}
@ -394,9 +394,9 @@ class CellEditor {
*
* Returns the current editing value.
*/
// getCurrentValue(state: mxCellState): string;
getCurrentValue(state: CellState) {
// @ts-ignore
if (!this.textarea) return null;
return extractTextWithWhitespace(this.textarea.childNodes);
}
@ -407,12 +407,12 @@ class CellEditor {
* are not pressed.
*/
// isCancelEditingKeyEvent(evt: Event): boolean;
isCancelEditingKeyEvent(evt: KeyboardEvent) {
isCancelEditingKeyEvent(evt: MouseEvent | KeyboardEvent) {
return (
this.escapeCancelsEditing ||
isShiftDown(evt) ||
isControlDown(evt) ||
isMetaDown(evt)
isShiftDown(<MouseEvent>(<unknown>evt)) ||
isControlDown(<MouseEvent>(<unknown>evt)) ||
isMetaDown(<MouseEvent>(<unknown>evt))
);
}
@ -459,8 +459,7 @@ class CellEditor {
this.clearOnChange &&
elt.innerHTML === this.getEmptyLabelText() &&
(!mxClient.IS_FF ||
(evt.keyCode !== 8 /* Backspace */ &&
evt.keyCode !== 46)) /* Delete */
(evt.keyCode !== 8 /* Backspace */ && evt.keyCode !== 46)) /* Delete */
) {
this.clearOnChange = false;
elt.innerHTML = '';
@ -496,7 +495,7 @@ class CellEditor {
// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
const evtName = 'input';
const resizeHandler = (evt: Event) => {
const resizeHandler = (evt: MouseEvent) => {
if (this.editingCell != null && this.autoSize && !isConsumed(evt)) {
// Asynchronous is needed for keydown and shows better results for input events overall
// (ie non-blocking and cases where the offsetWidth/-Height was wrong at this time)
@ -524,14 +523,13 @@ class CellEditor {
* returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
* and enter is pressed without control or shift.
*/
// isStopEditingEvent(evt: Event): boolean;
isStopEditingEvent(evt: KeyboardEvent) {
return (
evt.keyCode === 113 /* F2 */ ||
(this.graph.isEnterStopsCellEditing() &&
evt.keyCode === 13 /* Enter */ &&
!isControlDown(evt) &&
!isShiftDown(evt))
!isControlDown(<MouseEvent>(<unknown>evt)) &&
!isShiftDown(<MouseEvent>(<unknown>evt)))
);
}
@ -540,7 +538,7 @@ class CellEditor {
*
* Returns true if this editor is the source for the given native event.
*/
isEventSource(evt: Event): boolean {
isEventSource(evt: MouseEvent) {
return getSource(evt) === this.textarea;
}
@ -549,10 +547,12 @@ class CellEditor {
*
* Returns <modified>.
*/
resize(): void {
const state = this.graph.getView().getState(this.editingCell);
resize() {
const state = this.editingCell
? this.graph.getView().getState(this.editingCell)
: null;
if (state == null) {
if (!state) {
this.stopEditing(true);
} else if (this.textarea != null) {
const isEdge = state.cell.isEdge();
@ -562,22 +562,12 @@ class CellEditor {
if (!this.autoSize || state.style.overflow === 'fill') {
// Specifies the bounds of the editor box
this.bounds = <Rectangle>this.getEditorBounds(state);
this.textarea.style.width = `${Math.round(
this.bounds.width / scale
)}px`;
this.textarea.style.height = `${Math.round(
this.bounds.height / scale
)}px`;
this.textarea.style.width = `${Math.round(this.bounds.width / scale)}px`;
this.textarea.style.height = `${Math.round(this.bounds.height / scale)}px`;
// FIXME: Offset when scaled
this.textarea.style.left = `${Math.max(
0,
Math.round(this.bounds.x + 1)
)}px`;
this.textarea.style.top = `${Math.max(
0,
Math.round(this.bounds.y + 1)
)}px`;
this.textarea.style.left = `${Math.max(0, Math.round(this.bounds.x + 1))}px`;
this.textarea.style.top = `${Math.max(0, Math.round(this.bounds.y + 1))}px`;
// Installs native word wrapping and avoids word wrap for empty label placeholder
if (
@ -606,8 +596,7 @@ class CellEditor {
if (m == null) {
m = getAlignmentAsPoint(
this.align ||
getValue(state.style, 'align', ALIGN_CENTER),
this.align || getValue(state.style, 'align', ALIGN_CENTER),
getValue(state.style, 'verticalAlign', ALIGN_MIDDLE)
);
}
@ -627,16 +616,8 @@ class CellEditor {
}
} else {
let bounds = Rectangle.fromRectangle(state);
let hpos = getValue(
state.style,
'labelPosition',
ALIGN_CENTER
);
let vpos = getValue(
state.style,
'verticalLabelPosition',
ALIGN_MIDDLE
);
let hpos = getValue(state.style, 'labelPosition', ALIGN_CENTER);
let vpos = getValue(state.style, 'verticalLabelPosition', ALIGN_MIDDLE);
bounds =
state.shape != null && hpos === 'center' && vpos === 'middle'
@ -653,30 +634,20 @@ class CellEditor {
) {
// @ts-ignore
const dummy = new TextShape(); // FIXME!!!! ===================================================================================================
const spacing = parseInt(state.style.spacing || 2) * scale;
const spacing = (state.style.spacing ?? 2) * scale;
const spacingTop =
(parseInt(state.style.spacingTop || 0) + dummy.baseSpacingTop) *
scale +
spacing;
((state.style.spacingTop ?? 0) + dummy.baseSpacingTop) * scale + spacing;
const spacingRight =
(parseInt(state.style.spacingRight || 0) +
dummy.baseSpacingRight) *
scale +
((state.style.spacingRight ?? 0) + dummy.baseSpacingRight) * scale +
spacing;
const spacingBottom =
(parseInt(state.style.spacingBottom || 0) +
dummy.baseSpacingBottom) *
scale +
((state.style.spacingBottom ?? 0) + dummy.baseSpacingBottom) * scale +
spacing;
const spacingLeft =
(parseInt(state.style.spacingLeft || 0) + dummy.baseSpacingLeft) *
scale +
spacing;
((state.style.spacingLeft ?? 0) + dummy.baseSpacingLeft) * scale + spacing;
hpos =
state.style.labelPosition != null
? state.style.labelPosition
: 'center';
state.style.labelPosition != null ? state.style.labelPosition : 'center';
vpos =
state.style.verticalLabelPosition != null
? state.style.verticalLabelPosition
@ -686,11 +657,8 @@ class CellEditor {
bounds.x + spacingLeft,
bounds.y + spacingTop,
bounds.width -
(hpos === ALIGN_CENTER && lw == null
? spacingLeft + spacingRight
: 0),
bounds.height -
(vpos === ALIGN_MIDDLE ? spacingTop + spacingBottom : 0)
(hpos === ALIGN_CENTER && lw == null ? spacingLeft + spacingRight : 0),
bounds.height - (vpos === ALIGN_MIDDLE ? spacingTop + spacingBottom : 0)
);
}
@ -714,8 +682,7 @@ class CellEditor {
this.textarea.style.whiteSpace = 'normal';
// Forces automatic reflow if text is removed from an oversize label and normal word wrap
const tmp =
Math.round(this.bounds.width / scale) + this.wordWrapPadding;
const tmp = Math.round(this.bounds.width / scale) + this.wordWrapPadding;
if (this.textarea.style.position !== 'relative') {
this.textarea.style.width = `${tmp}px`;
@ -750,18 +717,12 @@ class CellEditor {
this.textarea.style.top = `${Math.max(
0,
Math.round(
this.bounds.y -
m.y * (this.bounds.height - 4) +
(m.y === -1 ? 3 : 0)
this.bounds.y - m.y * (this.bounds.height - 4) + (m.y === -1 ? 3 : 0)
) + 1
)}px`;
}
setPrefixedStyle(
this.textarea.style,
'transformOrigin',
'0px 0px'
);
setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px');
setPrefixedStyle(
this.textarea.style,
'transform',
@ -777,8 +738,7 @@ class CellEditor {
*
* Called if the textarea has lost focus.
*/
// focusLost(): void;
focusLost(): void {
focusLost() {
this.stopEditing(!this.graph.isInvokesStopCellEditing());
}
@ -786,11 +746,10 @@ class CellEditor {
* Function: getBackgroundColor
*
* Returns the background color for the in-place editor. This implementation
* always returns null.
* always returns NONE.
*/
// getBackgroundColor(state: mxCellState): string;
getBackgroundColor(state: CellState): string | null {
return null;
getBackgroundColor(state: CellState) {
return NONE;
}
/**
@ -803,11 +762,7 @@ class CellEditor {
* cell - <mxCell> to start editing.
* trigger - Optional mouse event that triggered the editor.
*/
// startEditing(cell: mxCell, trigger?: MouseEvent): void;
startEditing(
cell: Cell,
trigger: InternalMouseEvent | MouseEvent | null = null
): void {
startEditing(cell: Cell, trigger: MouseEvent | null = null) {
this.stopEditing(true);
this.align = null;
@ -816,23 +771,21 @@ class CellEditor {
this.init();
}
if (this.graph.tooltipHandler != null) {
this.graph.tooltipHandler.hideTooltip();
const tooltipHandler = this.graph.getPlugin('TooltipHandler') as TooltipHandler;
if (tooltipHandler) {
tooltipHandler.hideTooltip();
}
const state = this.graph.getView().getState(cell);
if (state != null) {
if (state) {
// Configures the style of the in-place editor
const { scale } = this.graph.getView();
const size =
state.style.fontSize != null ? state.style.fontSize : DEFAULT_FONTSIZE;
const family =
state.style.fontFamily != null
? state.style.fontFamily
: DEFAULT_FONTFAMILY;
const color = getValue(state.style, 'fontColor', 'black');
const align = getValue(state.style, 'align', ALIGN_LEFT);
const size = state.style.fontSize ?? DEFAULT_FONTSIZE;
const family = state.style.fontFamily ?? DEFAULT_FONTFAMILY;
const color = state.style.fontColor ?? 'black';
const align = state.style.align ?? ALIGN_LEFT;
const bold = (state.style.fontStyle || 0) & FONT_BOLD;
const italic = (state.style.fontStyle || 0) & FONT_ITALIC;
@ -848,8 +801,7 @@ class CellEditor {
textarea.style.lineHeight = ABSOLUTE_LINE_HEIGHT
? `${Math.round(size * LINE_HEIGHT)}px`
: String(LINE_HEIGHT);
textarea.style.backgroundColor =
this.getBackgroundColor(state) || 'transparent';
textarea.style.backgroundColor = this.getBackgroundColor(state) || 'transparent';
textarea.style.textDecoration = txtDecor.join(' ');
textarea.style.fontWeight = bold ? 'bold' : 'normal';
textarea.style.fontStyle = italic ? 'italic' : '';
@ -861,13 +813,11 @@ class CellEditor {
textarea.style.color = color;
let dir = (this.textDirection =
state.style.textDirection != null
? state.style.textDirection
: DEFAULT_TEXT_DIRECTION);
state.style.textDirection ?? DEFAULT_TEXT_DIRECTION);
if (dir === 'auto') {
if (
state.text != null &&
state.text !== null &&
state.text.dialect !== DIALECT_STRICTHTML &&
!isNode(state.text.value)
) {
@ -882,8 +832,7 @@ class CellEditor {
}
// Sets the initial editing value
textarea.innerHTML =
this.getInitialValue(state, <InternalMouseEvent>trigger) || '';
textarea.innerHTML = this.getInitialValue(state, trigger) || '';
this.initialValue = textarea.innerHTML;
// Uses an optional text value for empty labels which is cleared
@ -904,7 +853,7 @@ class CellEditor {
this.trigger = trigger;
this.textNode = null;
if (state.text != null && this.isHideLabel(state)) {
if (state.text !== null && this.isHideLabel(state)) {
this.textNode = <SVGGElement>state.text.node;
this.textNode.style.visibility = 'hidden';
}
@ -913,8 +862,7 @@ class CellEditor {
if (
this.autoSize &&
// @ts-ignore
(this.graph.model.isEdge(state.cell) ||
state.style.overflow !== 'fill')
(this.graph.model.isEdge(state.cell) || state.style.overflow !== 'fill')
) {
window.setTimeout(() => {
this.resize();
@ -931,8 +879,7 @@ class CellEditor {
if (
this.isSelectText() &&
textarea.innerHTML.length > 0 &&
(textarea.innerHTML !== this.getEmptyLabelText() ||
!this.clearOnChange)
(textarea.innerHTML !== this.getEmptyLabelText() || !this.clearOnChange)
) {
document.execCommand('selectAll', false);
}
@ -947,7 +894,6 @@ class CellEditor {
*
* Returns <selectText>.
*/
// isSelectText(): boolean;
isSelectText() {
return this.selectText;
}
@ -955,11 +901,10 @@ class CellEditor {
/**
* Function: clearSelection
*/
// clearSelection(): void;
clearSelection() {
const selection = window.getSelection();
if (selection != null) {
if (selection) {
if (selection.empty) {
selection.empty();
} else if (selection.removeAllRanges) {
@ -973,10 +918,9 @@ class CellEditor {
*
* Stops the editor and applies the value if cancel is false.
*/
// stopEditing(cancel: boolean): void;
stopEditing(cancel: boolean = false) {
if (this.editingCell != null) {
if (this.textNode != null) {
if (this.editingCell) {
if (this.textNode) {
this.textNode.style.visibility = 'visible';
this.textNode = null;
}
@ -992,33 +936,27 @@ class CellEditor {
textarea.blur();
this.clearSelection();
if (textarea.parentNode != null) {
if (textarea.parentNode) {
textarea.parentNode.removeChild(textarea);
}
if (
this.clearOnChange &&
textarea.innerHTML === this.getEmptyLabelText()
) {
if (this.clearOnChange && textarea.innerHTML === this.getEmptyLabelText()) {
textarea.innerHTML = '';
this.clearOnChange = false;
}
if (
state != null &&
(textarea.innerHTML !== initial || this.align != null)
) {
if (state && (textarea.innerHTML !== initial || this.align !== null)) {
this.prepareTextarea();
const value = this.getCurrentValue(state);
this.graph.getModel().beginUpdate();
try {
if (value != null) {
if (value !== null) {
this.applyValue(state, value);
}
if (this.align != null) {
this.graph.setCellStyles('align', this.align, [state.cell]);
if (this.align !== null) {
this.graph.setCellStyles('align', this.align, new CellArray(state.cell));
}
} finally {
this.graph.getModel().endUpdate();
@ -1026,7 +964,8 @@ class CellEditor {
}
// Forces new instance on next edit for undo history reset
InternalEvent.release(this.textarea);
if (this.textarea) InternalEvent.release(this.textarea);
this.textarea = null;
this.align = null;
}
@ -1038,10 +977,9 @@ class CellEditor {
* Prepares the textarea for getting its value in <stopEditing>.
* This implementation removes the extra trailing linefeed in Firefox.
*/
// prepareTextarea(): void;
prepareTextarea(): void {
prepareTextarea() {
const textarea = <HTMLElement>this.textarea;
if (textarea.lastChild != null && textarea.lastChild.nodeName === 'BR') {
if (textarea.lastChild && textarea.lastChild.nodeName === 'BR') {
textarea.removeChild(textarea.lastChild);
}
}
@ -1052,8 +990,7 @@ class CellEditor {
* Returns true if the label should be hidden while the cell is being
* edited.
*/
// isHideLabel(state: mxCellState): boolean;
isHideLabel(state: CellState | null = null): boolean {
isHideLabel(state: CellState | null = null) {
return true;
}
@ -1062,15 +999,14 @@ class CellEditor {
*
* Returns the minimum width and height for editing the given state.
*/
// getMinimumSize(state: mxCellState): mxRectangle;
getMinimumSize(state: CellState): Rectangle {
getMinimumSize(state: CellState) {
const { scale } = this.graph.getView();
const textarea = <HTMLElement>this.textarea;
return new Rectangle(
0,
0,
state.text == null ? 30 : state.text.size * scale + 20,
state.text === null ? 30 : state.text.size * scale + 20,
textarea.style.textAlign === 'left' ? 120 : 40
);
}
@ -1080,8 +1016,7 @@ class CellEditor {
*
* Returns the <mxRectangle> that defines the bounds of the editor.
*/
// getEditorBounds(state: mxCellState): mxRectangle;
getEditorBounds(state: CellState): Rectangle | null {
getEditorBounds(state: CellState) {
const isEdge = state.cell.isEdge();
const { scale } = this.graph.getView();
const minSize = this.getMinimumSize(state);
@ -1094,28 +1029,19 @@ class CellEditor {
state.view.graph.cellRenderer.legacySpacing &&
state.style.overflow === 'fill'
) {
result = (<Shape>state.shape).getLabelBounds(
Rectangle.fromRectangle(state)
);
result = (<Shape>state.shape).getLabelBounds(Rectangle.fromRectangle(state));
} else {
// @ts-ignore
const dummy = new TextShape(); // FIXME!!!! ===================================================================================================
const spacing: number = parseInt(state.style.spacing || 0) * scale;
const spacing: number = (state.style.spacing ?? 0) * scale;
const spacingTop: number =
(parseInt(state.style.spacingTop || 0) + dummy.baseSpacingTop) * scale +
spacing;
((state.style.spacingTop ?? 0) + dummy.baseSpacingTop) * scale + spacing;
const spacingRight: number =
(parseInt(state.style.spacingRight || 0) + dummy.baseSpacingRight) *
scale +
spacing;
((state.style.spacingRight ?? 0) + dummy.baseSpacingRight) * scale + spacing;
const spacingBottom: number =
(parseInt(state.style.spacingBottom || 0) + dummy.baseSpacingBottom) *
scale +
spacing;
((state.style.spacingBottom ?? 0) + dummy.baseSpacingBottom) * scale + spacing;
const spacingLeft: number =
(parseInt(state.style.spacingLeft || 0) + dummy.baseSpacingLeft) *
scale +
spacing;
((state.style.spacingLeft ?? 0) + dummy.baseSpacingLeft) * scale + spacing;
result = new Rectangle(
state.x,
@ -1124,9 +1050,7 @@ class CellEditor {
Math.max(minHeight, state.height - spacingTop - spacingBottom)
);
const hpos: string =
state.style.labelPosition != null
? state.style.labelPosition
: 'center';
state.style.labelPosition != null ? state.style.labelPosition : 'center';
const vpos: string =
state.style.verticalLabelPosition != null
? state.style.verticalLabelPosition
@ -1163,10 +1087,7 @@ class CellEditor {
if (state.text != null && state.text.boundingBox != null) {
if (!isEdge) {
result.width = Math.max(result.width, state.text.boundingBox.width);
result.height = Math.max(
result.height,
state.text.boundingBox.height
);
result.height = Math.max(result.height, state.text.boundingBox.height);
} else {
result.width = Math.max(minWidth, state.text.boundingBox.width);
result.height = Math.max(minHeight, state.text.boundingBox.height);
@ -1176,11 +1097,7 @@ class CellEditor {
// Applies the horizontal and vertical label positions
if (state.cell.isVertex()) {
const horizontal: string = <string>(
getStringValue(
state.style,
'labelPosition',
ALIGN_CENTER
)
getStringValue(state.style, 'labelPosition', ALIGN_CENTER)
);
if (horizontal === 'left') {
@ -1222,9 +1139,8 @@ class CellEditor {
* cell - <mxCell> for which a text for an empty editing box should be
* returned.
*/
// getEmptyLabelText(cell: mxCell): string;
getEmptyLabelText(cell: Cell | null = null): string {
return this.emptyLabelText || '';
getEmptyLabelText(cell: Cell | null = null) {
return this.emptyLabelText ?? '';
}
/**
@ -1233,8 +1149,7 @@ class CellEditor {
* Returns the cell that is currently being edited or null if no cell is
* being edited.
*/
// getEditingCell(): mxCell;
getEditingCell(): Cell | null {
getEditingCell() {
return this.editingCell;
}
@ -1243,25 +1158,17 @@ class CellEditor {
*
* Destroys the editor and removes all associated resources.
*/
// destroy(): void;
destroy(): void {
if (this.textarea != null) {
onDestroy() {
if (this.textarea) {
InternalEvent.release(this.textarea);
if (this.textarea.parentNode != null) {
if (this.textarea.parentNode) {
this.textarea.parentNode.removeChild(this.textarea);
}
this.textarea = null;
}
if (this.changeHandler != null) {
this.graph.getModel().removeListener(this.changeHandler);
this.changeHandler = null;
}
if (this.zoomHandler) {
this.graph.view.removeListener(this.zoomHandler);
this.zoomHandler = null;
}
this.graph.getView().removeListener(this.zoomHandler);
}
}

View File

@ -81,7 +81,7 @@ class GraphEditing extends autoImplement<PartialClass>() {
* @param cell {@link mxCell} for which the initial editing value should be returned.
* @param evt Optional mouse event that triggered the editor.
*/
getEditingValue(cell: Cell, evt: EventObject) {
getEditingValue(cell: Cell, evt: MouseEvent | null) {
return this.convertValueToString(cell);
}

View File

@ -6,8 +6,9 @@
*/
import CellMarker from '../cell/CellMarker';
import InternalMouseEvent from './InternalMouseEvent';
import Graph from '../Graph';
import Graph, { MaxGraph } from '../Graph';
import Cell from '../cell/datatypes/Cell';
import EventSource from './EventSource';
/**
* Event handler that highlights cells
@ -66,31 +67,31 @@ import Cell from '../cell/datatypes/Cell';
*/
class CellTracker extends CellMarker {
constructor(
graph: Graph,
graph: MaxGraph,
color: string,
funct: null | ((me: InternalMouseEvent) => Cell)=null
funct: ((me: InternalMouseEvent) => Cell) | null = null
) {
super(graph, color);
this.graph.event.addMouseListener(this);
this.graph.addMouseListener(this);
if (funct != null) {
if (funct) {
this.getCell = funct;
}
}
destroyed: boolean = false;
destroyed = false;
/**
* Ignores the event. The event is not consumed.
*/
mouseDown(sender: any, me: InternalMouseEvent): void {}
mouseDown(sender: EventSource, me: InternalMouseEvent) {}
/**
* Handles the event by highlighting the cell under the mousepointer if it
* is over the hotspot region of the cell.
*/
mouseMove(sender: any, me: InternalMouseEvent): void {
mouseMove(sender: EventSource, me: InternalMouseEvent) {
if (this.isEnabled()) {
this.process(me);
}
@ -99,7 +100,7 @@ class CellTracker extends CellMarker {
/**
* Handles the event by resetting the highlight.
*/
mouseUp(sender: any, me: InternalMouseEvent): void {}
mouseUp(sender: EventSource, me: InternalMouseEvent) {}
/**
* Function: destroy
@ -108,11 +109,11 @@ class CellTracker extends CellMarker {
* normally need to be called. It is called automatically when the window
* unloads.
*/
destroy(): void {
destroy() {
if (!this.destroyed) {
this.destroyed = true;
this.graph.event.removeMouseListener(this);
this.graph.removeMouseListener(this);
super.destroy();
}
}

View File

@ -34,8 +34,9 @@ type EventListenerObject = {
*
* Constructs a new event source.
*/
class EventSource {
class EventSource extends EventTarget {
constructor(eventSource: EventSource | null = null) {
super();
this.eventSource = eventSource;
}

View File

@ -30,9 +30,12 @@ import CellEditor from '../editing/CellEditor';
import type Graph from '../Graph';
import type GraphCells from '../cell/GraphCells';
import type GraphSelection from '../selection/GraphSelection';
import GraphEditing from '../editing/GraphEditing';
import GraphSnap from '../snap/GraphSnap';
import { MouseEventListener } from '../../types';
import type GraphEditing from '../editing/GraphEditing';
import type GraphSnap from '../snap/GraphSnap';
import { MouseEventListener, MouseListenerSet } from '../../types';
import TooltipHandler from '../tooltip/TooltipHandler';
import GraphDragDrop from '../drag_drop/GraphDragDrop';
import GraphPageBreaks from '../page_breaks/GraphPageBreaks';
type PartialGraph = Pick<
Graph,
@ -42,6 +45,21 @@ type PartialGraph = Pick<
| 'getGraphBounds'
| 'getContainer'
| 'paintBackground'
| 'getPlugin'
| 'getPanDx'
| 'getPanDy'
| 'getMinimumContainerSize'
| 'scrollPointToVisible'
| 'isIgnoreScrollbars'
| 'isTranslateToScrollPosition'
| 'getBorder'
| 'isResizeContainer'
| 'doResizeContainer'
| 'isPreferPageSize'
| 'isPageVisible'
| 'getPreferredPageSize'
| 'getMinimumGraphSize'
| 'isPageBreaksVisible'
>;
type PartialCells = Pick<GraphCells, 'getCellAt' | 'getCursorForCell'>;
type PartialSelection = Pick<
@ -53,11 +71,15 @@ type PartialEditing = Pick<
'isCellEditable' | 'isEditing' | 'startEditingAtCell' | 'stopEditing'
>;
type PartialSnap = Pick<GraphSnap, 'getGridSize' | 'snap'>;
type PartialDragDrop = Pick<GraphDragDrop, 'isAutoScroll' | 'isAutoExtend'>;
type PartialPageBreaks = Pick<GraphPageBreaks, 'updatePageBreaks'>;
type PartialClass = PartialGraph &
PartialCells &
PartialSelection &
PartialEditing &
PartialSnap &
PartialDragDrop &
PartialPageBreaks &
EventSource;
// @ts-ignore recursive reference error
@ -86,7 +108,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* is pressed.
* @default true
*/
escapeEnabled: boolean = true;
escapeEnabled = true;
/**
* If `true`, when editing is to be stopped by way of selection changing,
@ -95,7 +117,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* {@link CellEditor}.
* @default true
*/
invokesStopCellEditing: boolean = true;
invokesStopCellEditing = true;
/**
* If `true`, pressing the enter key without pressing control or shift will stop
@ -103,18 +125,18 @@ class GraphEvents extends autoImplement<PartialClass>() {
* cell editing. Note: You can always use F2 and escape to stop editing.
* @default false
*/
enterStopsCellEditing: boolean = false;
enterStopsCellEditing = false;
/**
* Holds the state of the mouse button.
*/
isMouseDown: boolean = false;
isMouseDown = false;
/**
* Specifies if native double click events should be detected.
* @default true
*/
nativeDblClickEnabled: boolean = true;
nativeDblClickEnabled = true;
isNativeDblClickEnabled = () => this.nativeDblClickEnabled;
@ -123,76 +145,76 @@ class GraphEvents extends autoImplement<PartialClass>() {
* double click.
* @default true
*/
doubleTapEnabled: boolean = true;
doubleTapEnabled = true;
/**
* Specifies the timeout in milliseconds for double taps and non-native double clicks.
* @default 500
*/
doubleTapTimeout: number = 500;
doubleTapTimeout = 500;
/**
* Specifies the tolerance in pixels for double taps and double clicks in quirks mode.
* @default 25
*/
doubleTapTolerance: number = 25;
doubleTapTolerance = 25;
/**
* Variable: lastTouchX
*
* Holds the x-coordinate of the last touch event for double tap detection.
*/
lastTouchX: number = 0;
lastTouchX = 0;
/**
* Holds the x-coordinate of the last touch event for double tap detection.
*/
lastTouchY: number = 0;
lastTouchY = 0;
/**
* Holds the time of the last touch event for double click detection.
*/
lastTouchTime: number = 0;
lastTouchTime = 0;
/**
* Specifies if tap and hold should be used for starting connections on touch-based
* devices.
* @default true
*/
tapAndHoldEnabled: boolean = true;
tapAndHoldEnabled = true;
/**
* Specifies the time in milliseconds for a tap and hold.
* @default 500
*/
tapAndHoldDelay: number = 500;
tapAndHoldDelay = 500;
/**
* `True` if the timer for tap and hold events is running.
*/
tapAndHoldInProgress: boolean = false;
tapAndHoldInProgress = false;
/**
* `True` as long as the timer is running and the touch events
* stay within the given {@link tapAndHoldTolerance}.
*/
tapAndHoldValid: boolean = false;
tapAndHoldValid = false;
/**
* Holds the x-coordinate of the initial touch event for tap and hold.
*/
initialTouchX: number = 0;
initialTouchX = 0;
/**
* Holds the y-coordinate of the initial touch event for tap and hold.
*/
initialTouchY: number = 0;
initialTouchY = 0;
/**
* Tolerance in pixels for a move to be handled as a single click.
* @default 4
*/
tolerance: number = 4;
tolerance = 4;
getEventTolerance = () => this.tolerance;
@ -350,7 +372,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param evt Mouseevent that represents the doubleclick.
* @param cell Optional {@link Cell} under the mousepointer.
*/
dblClick(evt: MouseEvent, cell?: Cell) {
dblClick(evt: MouseEvent, cell: Cell | null = null) {
const mxe = new EventObject(InternalEvent.DOUBLE_CLICK, { event: evt, cell: cell });
this.fireEvent(mxe);
@ -383,8 +405,9 @@ class GraphEvents extends autoImplement<PartialClass>() {
'cell',
me.getCell()
);
const panningHandler = <PanningHandler>this.panning.panningHandler;
const connectionHandler = <ConnectionHandler>this.connectionHandler;
const panningHandler = this.getPlugin('PanningHandler') as PanningHandler;
const connectionHandler = this.getPlugin('ConnectionHandler') as ConnectionHandler;
// LATER: Check if event should be consumed if me is consumed
this.fireEvent(mxe);
@ -401,9 +424,12 @@ class GraphEvents extends autoImplement<PartialClass>() {
!mxe.isConsumed() &&
connectionHandler.isEnabled()
) {
const state = this.getView().getState(connectionHandler.marker.getCell(me));
const cell = connectionHandler.marker.getCell(me);
if (state != null) {
if (cell) {
const state = this.getView().getState(cell);
if (state) {
connectionHandler.marker.currentColor = connectionHandler.marker.validColor;
connectionHandler.marker.markedState = state;
connectionHandler.marker.mark();
@ -417,6 +443,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
}
}
}
}
/*****************************************************************************
* Group: Graph events
@ -457,25 +484,21 @@ class GraphEvents extends autoImplement<PartialClass>() {
updateMouseEvent(me: InternalMouseEvent, evtName: string) {
const pt = convertPoint(this.getContainer(), me.getX(), me.getY());
me.graphX = pt.x - this.panning.panDx;
me.graphY = pt.y - this.panning.panDy;
me.graphX = pt.x - this.getPanDx();
me.graphY = pt.y - this.getPanDy();
// Searches for rectangles using method if native hit detection is disabled on shape
if (
me.getCell() == null &&
this.isMouseDown &&
evtName === InternalEvent.MOUSE_MOVE
) {
me.state = this.getView().getState(
this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => {
if (!me.getCell() && this.isMouseDown && evtName === InternalEvent.MOUSE_MOVE) {
const cell = this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => {
return (
state.shape == null ||
!state.shape ||
state.shape.paintBackground !== this.paintBackground ||
getValue(state.style, 'pointerEvents', '1') == '1' ||
(state.shape.fill != null && state.shape.fill !== NONE)
);
})
state.style.pointerEvents ||
state.shape.fill !== NONE
);
});
me.state = cell ? this.getView().getState(cell) : null;
}
return me;
@ -491,8 +514,9 @@ class GraphEvents extends autoImplement<PartialClass>() {
// Dispatches the drop event to the graph which
// consumes and executes the source function
const pt = convertPoint(this.getContainer(), x, y);
const cell = this.getCellAt(pt.x, pt.y);
return this.getView().getState(this.getCellAt(pt.x, pt.y));
return cell ? this.getView().getState(cell) : null;
}
/**
@ -527,6 +551,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
} else if (!mxClient.IS_GC && eventSource && me.getSource() !== eventSource) {
result = true;
} else if (
eventSource &&
mxClient.IS_TOUCH &&
evtName === InternalEvent.MOUSE_DOWN &&
!mouseEvent &&
@ -625,9 +650,13 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param evtName The name of the event.
* @param me {@link mxMouseEvent} that should be ignored.
*/
isEventSourceIgnored(evtName: string, me: InternalMouseEvent): boolean {
isEventSourceIgnored(evtName: string, me: InternalMouseEvent) {
const source = me.getSource();
const name = source.nodeName != null ? source.nodeName.toLowerCase() : '';
if (!source) return true;
// @ts-ignore nodeName could exist
const name = source.nodeName ? source.nodeName.toLowerCase() : '';
const candidate = !isMouseEvent(me.getEvent()) || isLeftMouseButton(me.getEvent());
return (
@ -636,10 +665,15 @@ class GraphEvents extends autoImplement<PartialClass>() {
(name === 'select' ||
name === 'option' ||
(name === 'input' &&
// @ts-ignore type could exist
source.type !== 'checkbox' &&
// @ts-ignore type could exist
source.type !== 'radio' &&
// @ts-ignore type could exist
source.type !== 'button' &&
// @ts-ignore type could exist
source.type !== 'submit' &&
// @ts-ignore type could exist
source.type !== 'file'))
);
}
@ -650,7 +684,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
*
* {@link CellState} - State whose event source should be returned.
*/
getEventState(state: CellState): CellState {
getEventState(state: CellState) {
return state;
}
@ -666,9 +700,12 @@ class GraphEvents extends autoImplement<PartialClass>() {
*/
fireMouseEvent(evtName: string, me: InternalMouseEvent, sender: EventSource = this) {
if (this.isEventSourceIgnored(evtName, me)) {
if (this.tooltipHandler != null) {
this.tooltipHandler.hide();
const tooltipHandler = this.getPlugin('TooltipHandler') as TooltipHandler;
if (tooltipHandler) {
tooltipHandler.hide();
}
return;
}
@ -690,7 +727,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
if (evtName === InternalEvent.MOUSE_DOWN) {
if (
this.lastTouchEvent != null &&
this.lastTouchEvent &&
this.lastTouchEvent !== me.getEvent() &&
currentTime - this.lastTouchTime < this.doubleTapTimeout &&
Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
@ -701,7 +738,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
let doubleClickFired = false;
if (evtName === InternalEvent.MOUSE_UP) {
if (me.getCell() === this.lastTouchCell && this.lastTouchCell !== null) {
if (me.getCell() === this.lastTouchCell && this.lastTouchCell) {
this.lastTouchTime = 0;
const cell = this.lastTouchCell;
this.lastTouchCell = null;
@ -718,7 +755,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
InternalEvent.consume(me.getEvent());
return;
}
} else if (this.lastTouchEvent == null || this.lastTouchEvent !== me.getEvent()) {
} else if (!this.lastTouchEvent || this.lastTouchEvent !== me.getEvent()) {
this.lastTouchCell = me.getCell();
this.lastTouchX = me.getX();
this.lastTouchY = me.getY();
@ -737,7 +774,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
// Workaround for Chrome/Safari not firing native double click events for double touch on background
const valid =
cell != null ||
cell ||
((isTouchEvent(me.getEvent()) || isPenEvent(me.getEvent())) &&
(mxClient.IS_GC || mxClient.IS_SF));
@ -756,26 +793,32 @@ class GraphEvents extends autoImplement<PartialClass>() {
}
if (!this.isEventIgnored(evtName, me, sender)) {
const state = me.getState();
// Updates the event state via getEventState
me.state = this.getEventState(me.getState());
me.state = state ? this.getEventState(state) : null;
this.fireEvent(
new EventObject(InternalEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me)
);
if (mxClient.IS_SF || mxClient.IS_GC || me.getEvent().target !== this.container) {
const container = <HTMLElement>this.container;
if (
mxClient.IS_SF ||
mxClient.IS_GC ||
me.getEvent().target !== this.getContainer()
) {
const container = this.getContainer();
if (
evtName === InternalEvent.MOUSE_MOVE &&
this.isMouseDown &&
this.autoScroll &&
!isMultiTouchEvent(me.getEvent)
this.isAutoScroll() &&
!isMultiTouchEvent(me.getEvent())
) {
this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.isAutoExtend());
} else if (
evtName === InternalEvent.MOUSE_UP &&
this.ignoreScrollbars &&
this.translateToScrollPosition &&
this.isIgnoreScrollbars() &&
this.isTranslateToScrollPosition() &&
(container.scrollLeft !== 0 || container.scrollTop !== 0)
) {
const s = this.getView().scale;
@ -788,7 +831,6 @@ class GraphEvents extends autoImplement<PartialClass>() {
container.scrollTop = 0;
}
const args = [sender, me];
const mouseListeners = this.mouseListeners;
// Does not change returnValue in Opera
@ -798,11 +840,11 @@ class GraphEvents extends autoImplement<PartialClass>() {
for (const l of mouseListeners) {
if (evtName === InternalEvent.MOUSE_DOWN) {
l.mouseDown.apply(l, args);
l.mouseDown(sender, me);
} else if (evtName === InternalEvent.MOUSE_MOVE) {
l.mouseMove.apply(l, args);
l.mouseMove(sender, me);
} else if (evtName === InternalEvent.MOUSE_UP) {
l.mouseUp.apply(l, args);
l.mouseUp(sender, me);
}
}
@ -847,11 +889,13 @@ class GraphEvents extends autoImplement<PartialClass>() {
Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;
}
const cellEditor = this.getPlugin('CellEditor') as CellEditor;
// Stops editing for all events other than from cellEditor
if (
evtName === InternalEvent.MOUSE_DOWN &&
this.isEditing() &&
!(<CellEditor>this.cellEditor).isEventSource(me.getEvent())
!cellEditor.isEventSource(me.getEvent())
) {
this.stopEditing(!this.isInvokesStopCellEditing());
}
@ -863,7 +907,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
/**
* Consumes the given {@link InternalMouseEvent} if it's a touchStart event.
*/
consumeMouseEvent(evtName: string, me: InternalMouseEvent, sender: any = this): void {
consumeMouseEvent(evtName: string, me: InternalMouseEvent, sender: EventSource = this) {
// Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch
if (evtName === InternalEvent.MOUSE_DOWN && isTouchEvent(me.getEvent())) {
me.consume(false);
@ -912,7 +956,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* a {@link size} event after updating the clipping region of the SVG element in
* SVG-bases browsers.
*/
sizeDidChange(): void {
sizeDidChange() {
const bounds = this.getGraphBounds();
const border = this.getBorder();
@ -920,31 +964,33 @@ class GraphEvents extends autoImplement<PartialClass>() {
let width = Math.max(0, bounds.x) + bounds.width + 2 * border;
let height = Math.max(0, bounds.y) + bounds.height + 2 * border;
if (this.minimumContainerSize != null) {
width = Math.max(width, this.minimumContainerSize.width);
height = Math.max(height, this.minimumContainerSize.height);
const minimumContainerSize = this.getMinimumContainerSize();
if (minimumContainerSize) {
width = Math.max(width, minimumContainerSize.width);
height = Math.max(height, minimumContainerSize.height);
}
if (this.resizeContainer) {
if (this.isResizeContainer()) {
this.doResizeContainer(width, height);
}
if (this.preferPageSize || this.pageVisible) {
if (this.isPreferPageSize() || this.isPageVisible()) {
const size = this.getPreferredPageSize(
bounds,
Math.max(1, width),
Math.max(1, height)
);
if (size != null) {
width = size.width * this.getView().scale;
height = size.height * this.getView().scale;
}
}
if (this.minimumGraphSize != null) {
width = Math.max(width, this.minimumGraphSize.width * this.getView().scale);
height = Math.max(height, this.minimumGraphSize.height * this.getView().scale);
const minimumGraphSize = this.getMinimumGraphSize();
if (minimumGraphSize) {
width = Math.max(width, minimumGraphSize.width * this.getView().scale);
height = Math.max(height, minimumGraphSize.height * this.getView().scale);
}
width = Math.ceil(width);
@ -953,14 +999,14 @@ class GraphEvents extends autoImplement<PartialClass>() {
// @ts-ignore
const root = this.getView().getDrawPane().ownerSVGElement;
if (root != null) {
if (root) {
root.style.minWidth = `${Math.max(1, width)}px`;
root.style.minHeight = `${Math.max(1, height)}px`;
root.style.width = '100%';
root.style.height = '100%';
}
this.pageBreaks.updatePageBreaks(this.pageBreaksVisible, width, height);
this.updatePageBreaks(this.isPageBreaksVisible(), width, height);
this.fireEvent(new EventObject(InternalEvent.SIZE, 'bounds', bounds));
}
@ -1025,7 +1071,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
* @param addOffset Optional boolean that specifies if the position should be
* offset by half of the {@link gridSize}. Default is `true`.
*/
getPointForEvent(evt: MouseEvent, addOffset: boolean = true) {
getPointForEvent(evt: MouseEvent, addOffset = true) {
const p = convertPoint(this.getContainer(), getClientX(evt), getClientY(evt));
const s = this.getView().scale;
const tr = this.getView().translate;
@ -1044,7 +1090,7 @@ class GraphEvents extends autoImplement<PartialClass>() {
/**
* Returns {@link escapeEnabled}.
*/
isEscapeEnabled(): boolean {
isEscapeEnabled() {
return this.escapeEnabled;
}
@ -1053,35 +1099,35 @@ class GraphEvents extends autoImplement<PartialClass>() {
*
* @param enabled Boolean indicating if escape should be enabled.
*/
setEscapeEnabled(value: boolean): void {
setEscapeEnabled(value: boolean) {
this.escapeEnabled = value;
}
/**
* Returns {@link invokesStopCellEditing}.
*/
isInvokesStopCellEditing(): boolean {
isInvokesStopCellEditing() {
return this.invokesStopCellEditing;
}
/**
* Sets {@link invokesStopCellEditing}.
*/
setInvokesStopCellEditing(value: boolean): void {
setInvokesStopCellEditing(value: boolean) {
this.invokesStopCellEditing = value;
}
/**
* Returns {@link enterStopsCellEditing}.
*/
isEnterStopsCellEditing(): boolean {
isEnterStopsCellEditing() {
return this.enterStopsCellEditing;
}
/**
* Sets {@link enterStopsCellEditing}.
*/
setEnterStopsCellEditing(value: boolean): void {
setEnterStopsCellEditing(value: boolean) {
this.enterStopsCellEditing = value;
}
@ -1095,8 +1141,9 @@ class GraphEvents extends autoImplement<PartialClass>() {
*
* @param me {@link mxMouseEvent} whose cursor should be returned.
*/
getCursorForMouseEvent(me: InternalMouseEvent): string | null {
return this.getCursorForCell(me.getCell());
getCursorForMouseEvent(me: InternalMouseEvent) {
const cell = me.getCell();
return cell ? this.getCursorForCell(cell) : null;
}
}

View File

@ -9,7 +9,13 @@ import mxClient from '../../mxClient';
import { isConsumed, isMouseEvent } from '../../util/EventUtils';
import graph from '../Graph';
import CellState from '../cell/datatypes/CellState';
import { EventCache, GestureEvent, Listenable, MouseEventListener } from '../../types';
import {
EventCache,
GestureEvent,
KeyboardEventListener,
Listenable,
MouseEventListener,
} from '../../types';
// Checks if passive event listeners are supported
// see https://github.com/Modernizr/Modernizr/issues/1894
@ -50,7 +56,11 @@ class InternalEvent {
* {@link mxUtils.bind} in order to bind the "this" keyword inside the function
* to a given execution scope.
*/
static addListener(element: Listenable, eventName: string, funct: MouseEventListener) {
static addListener(
element: Listenable,
eventName: string,
funct: MouseEventListener | KeyboardEventListener
) {
element.addEventListener(
eventName,
funct as EventListener,
@ -71,7 +81,7 @@ class InternalEvent {
static removeListener(
element: Listenable,
eventName: string,
funct: MouseEventListener
funct: MouseEventListener | KeyboardEventListener
) {
element.removeEventListener(eventName, funct as EventListener, false);
@ -221,7 +231,7 @@ class InternalEvent {
static redirectMouseEvents(
node: Listenable,
graph: graph,
state: CellState | ((evt: Event) => CellState) | null = null,
state: CellState | ((evt: Event) => CellState | null) | null = null,
down: MouseEventListener | null = null,
move: MouseEventListener | null = null,
up: MouseEventListener | null = null,
@ -265,7 +275,7 @@ class InternalEvent {
}
);
InternalEvent.addListener(node, 'dblclick', (evt) => {
InternalEvent.addListener(node, 'dblclick', (evt: MouseEvent) => {
if (dblClick) {
dblClick(evt);
} else if (!isConsumed(evt)) {
@ -344,7 +354,7 @@ class InternalEvent {
if (mxClient.IS_SF && !mxClient.IS_TOUCH) {
let scale = 1;
InternalEvent.addListener(target, 'gesturestart', (evt) => {
InternalEvent.addListener(target, 'gesturestart', (evt: GestureEvent) => {
InternalEvent.consume(evt);
scale = 1;
});
@ -362,7 +372,7 @@ class InternalEvent {
}
}) as EventListener);
InternalEvent.addListener(target, 'gestureend', (evt) => {
InternalEvent.addListener(target, 'gestureend', (evt: GestureEvent) => {
InternalEvent.consume(evt);
});
} else {
@ -427,7 +437,7 @@ class InternalEvent {
* Disables the context menu for the given element.
*/
static disableContextMenu(element: Listenable) {
InternalEvent.addListener(element, 'contextmenu', (evt) => {
InternalEvent.addListener(element, 'contextmenu', (evt: MouseEvent) => {
if (evt.preventDefault) {
evt.preventDefault();
}

View File

@ -276,6 +276,8 @@ class Shape {
*/
pointerEvents = true;
originalPointerEvents: boolean | null = null;
/**
* Variable: svgPointerEvents
*

View File

@ -1,8 +1,24 @@
import Rectangle from "../geometry/Rectangle";
import Point from "../geometry/Point";
import Polyline from "../geometry/shape/edge/Polyline";
import Rectangle from '../geometry/Rectangle';
import Point from '../geometry/Point';
import Polyline from '../geometry/shape/edge/Polyline';
import { autoImplement } from '../../util/Utils';
class GraphPageBreaks {
import type Graph from '../Graph';
type PartialGraph = Pick<
Graph,
| 'getView'
| 'getGraphBounds'
| 'getPageFormat'
| 'getPageScale'
| 'getMinPageBreakDist'
| 'getPageBreakColor'
| 'getDialect'
| 'isPageBreakDashed'
>;
type PartialClass = PartialGraph;
class GraphPageBreaks extends autoImplement<PartialClass>() {
horizontalPageBreaks: any[] | null = null;
verticalPageBreaks: any[] | null = null;
@ -14,10 +30,9 @@ class GraphPageBreaks {
* @param height Specifies the height of the container in pixels.
*/
updatePageBreaks(visible: boolean, width: number, height: number): void {
const { scale } = this.view;
const tr = this.getView().translate;
const fmt = this.pageFormat;
const ps = scale * this.pageScale;
const { scale, translate: tr } = this.getView();
const fmt = this.getPageFormat();
const ps = scale * this.getPageScale();
const bounds = new Rectangle(0, 0, fmt.width * ps, fmt.height * ps);
const gb = Rectangle.fromRectangle(this.getGraphBounds());
@ -25,25 +40,19 @@ class GraphPageBreaks {
gb.height = Math.max(1, gb.height);
bounds.x =
Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width +
tr.x * scale;
Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale;
bounds.y =
Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height +
tr.y * scale;
Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale;
gb.width =
Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
gb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
gb.height =
Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) *
bounds.height;
Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height;
// Does not show page breaks if the scale is too small
visible =
visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
visible && Math.min(bounds.width, bounds.height) > this.getMinPageBreakDist();
const horizontalCount = visible
? Math.ceil(gb.height / bounds.height) + 1
: 0;
const horizontalCount = visible ? Math.ceil(gb.height / bounds.height) + 1 : 0;
const verticalCount = visible ? Math.ceil(gb.width / bounds.width) + 1 : 0;
const right = (verticalCount - 1) * bounds.width;
const bottom = (horizontalCount - 1) * bounds.height;
@ -59,9 +68,7 @@ class GraphPageBreaks {
const drawPageBreaks = (breaks: any) => {
if (breaks != null) {
const count =
breaks === this.horizontalPageBreaks
? horizontalCount
: verticalCount;
breaks === this.horizontalPageBreaks ? horizontalCount : verticalCount;
for (let i = 0; i <= count; i += 1) {
const pts =
@ -91,10 +98,10 @@ class GraphPageBreaks {
breaks[i].points = pts;
breaks[i].redraw();
} else {
const pageBreak = new Polyline(pts, this.pageBreakColor);
pageBreak.dialect = this.dialect;
const pageBreak = new Polyline(pts, this.getPageBreakColor());
pageBreak.dialect = this.getDialect();
pageBreak.pointerEvents = false;
pageBreak.isDashed = this.pageBreakDashed;
pageBreak.isDashed = this.isPageBreakDashed();
pageBreak.init(this.getView().backgroundPane);
pageBreak.redraw();
@ -116,4 +123,3 @@ class GraphPageBreaks {
}
export default GraphPageBreaks;

View File

@ -38,6 +38,8 @@ class GraphPanning extends autoImplement<PartialClass>() {
*/
timerAutoScroll = false;
isTimerAutoScroll = () => this.timerAutoScroll;
/**
* Specifies if panning via {@link panGraph} should be allowed to implement autoscroll
* if no scrollbars are available in {@link scrollPointToVisible}. To enable panning
@ -47,6 +49,8 @@ class GraphPanning extends autoImplement<PartialClass>() {
*/
allowAutoPanning = false;
isAllowAutoPanning = () => this.allowAutoPanning;
/**
* Current horizontal panning value.
* @default 0

View File

@ -121,6 +121,8 @@ class PanningHandler extends EventSource implements GraphPlugin {
panningManager: PanningManager;
getPanningManager = () => this.panningManager;
/**
* Variable: useLeftButtonForPanning
*

View File

@ -8,7 +8,9 @@
import { MouseEventListener, MouseListenerSet } from '../../types';
import { hasScrollbars } from '../../util/Utils';
import EventObject from '../event/EventObject';
import EventSource from '../event/EventSource';
import InternalEvent from '../event/InternalEvent';
import InternalMouseEvent from '../event/InternalMouseEvent';
import { MaxGraph } from '../Graph';
/**
@ -31,9 +33,9 @@ class PanningManager {
this.scrollTop = 0;
this.mouseListener = {
mouseDown: (sender, me) => {},
mouseMove: (sender, me) => {},
mouseUp: (sender, me) => {
mouseDown: (sender: EventSource, me: InternalMouseEvent) => {},
mouseMove: (sender: EventSource, me: InternalMouseEvent) => {},
mouseUp: (sender: EventSource, me: InternalMouseEvent) => {
if (this.active) {
this.stop();
}
@ -92,7 +94,7 @@ class PanningManager {
this.active = true;
};
this.panTo = (x, y, w, h) => {
this.panTo = (x, y, w = 0, h = 0) => {
if (!this.active) {
this.start();
}
@ -100,9 +102,6 @@ class PanningManager {
this.scrollLeft = graph.container.scrollLeft;
this.scrollTop = graph.container.scrollTop;
w = w != null ? w : 0;
h = h != null ? h : 0;
const c = graph.container;
this.dx = x + w - c.scrollLeft - c.clientWidth;
@ -249,7 +248,7 @@ class PanningManager {
getDx: () => number;
getDy: () => number;
start: () => void;
panTo: (x: number, y: number, w: number, h: number) => void;
panTo: (x: number, y: number, w?: number, h?: number) => void;
destroy: () => void;
}

View File

@ -4,14 +4,16 @@
* Updated to ES9 syntax by David Morrissey 2021
* Type definitions from the typed-mxgraph project
*/
import mxPopupMenu from '../../util/gui/mxPopupMenu';
import PopupMenu from '../../util/gui/PopupMenu';
import InternalEvent from '../event/InternalEvent';
import utils, { getScrollOrigin } from '../../util/Utils';
import { getScrollOrigin } from '../../util/Utils';
import { getMainEvent, isMultiTouchEvent } from '../../util/EventUtils';
import Graph from '../Graph';
import { MaxGraph } from '../Graph';
import InternalMouseEvent from '../event/InternalMouseEvent';
import Cell from '../cell/datatypes/Cell';
import { GraphPlugin } from '../../types';
import { GraphPlugin, PopupMenuItem } from '../../types';
import TooltipHandler from '../tooltip/TooltipHandler';
import EventSource from '../event/EventSource';
import EventObject from '../event/EventObject';
/**
* Class: mxPopupMenuHandler
@ -22,17 +24,17 @@ import { GraphPlugin } from '../../types';
*
* Constructs an event handler that creates a <mxPopupMenu>.
*/
class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
constructor(graph: Graph, factoryMethod: any) {
class PopupMenuHandler extends PopupMenu implements GraphPlugin {
static pluginId = 'PopupMenuHandler';
constructor(graph: MaxGraph) {
super();
if (graph != null) {
this.graph = graph;
this.factoryMethod = factoryMethod;
this.graph.event.addMouseListener(this);
this.graph.addMouseListener(this);
// Does not show menu if any touch gestures take place after the trigger
this.gestureHandler = (sender, eo) => {
this.gestureHandler = (sender: EventSource, eo: EventObject) => {
this.inTolerance = false;
};
@ -40,22 +42,18 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
this.init();
}
}
// @ts-ignore
gestureHandler: (...args: any[]) => any;
// @ts-ignore
inTolerance: boolean;
// @ts-ignore
popupTrigger: boolean;
gestureHandler: (sender: EventSource, eo: EventObject) => void;
inTolerance = false;
popupTrigger = false;
/**
* Variable: graph
*
* Reference to the enclosing <mxGraph>.
*/
// @ts-ignore
graph: Graph;
graph: MaxGraph;
/**
* Variable: selectOnPopup
@ -63,7 +61,7 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
* Specifies if cells should be selected if a popupmenu is displayed for
* them. Default is true.
*/
selectOnPopup: boolean = true;
selectOnPopup = true;
/**
* Variable: clearSelectionOnBackground
@ -71,7 +69,7 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
* Specifies if cells should be deselected if a popupmenu is displayed for
* the diagram background. Default is true.
*/
clearSelectionOnBackground: boolean = true;
clearSelectionOnBackground = true;
/**
* Variable: triggerX
@ -106,14 +104,12 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
*
* Initializes the shapes required for this vertex handler.
*/
init(): void {
// Supercall
super.init();
init() {
// Hides the tooltip if the mouse is over
// the context menu
InternalEvent.addGestureListeners(this.div, (evt) => {
this.graph.tooltipHandler.hide();
const tooltipHandler = this.graph.getPlugin('TooltipHandler') as TooltipHandler;
tooltipHandler.hide();
});
}
@ -133,7 +129,7 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
* Handles the event by initiating the panning. By consuming the event all
* subsequent events of the gesture are redirected to this handler.
*/
mouseDown(sender: any, me: InternalMouseEvent): void {
mouseDown(sender: EventSource, me: InternalMouseEvent) {
if (this.isEnabled() && !isMultiTouchEvent(me.getEvent())) {
// Hides the popupmenu if is is being displayed
this.hideMenu();
@ -151,14 +147,14 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
*
* Handles the event by updating the panning on the graph.
*/
mouseMove(sender: any, me: InternalMouseEvent): void {
mouseMove(sender: EventSource, me: InternalMouseEvent) {
// Popup trigger may change on mouseUp so ignore it
if (this.inTolerance && this.screenX != null && this.screenY != null) {
if (
Math.abs(getMainEvent(me.getEvent()).screenX - this.screenX) >
this.graph.tolerance ||
this.graph.getEventTolerance() ||
Math.abs(getMainEvent(me.getEvent()).screenY - this.screenY) >
this.graph.tolerance
this.graph.getEventTolerance()
) {
this.inTolerance = false;
}
@ -171,7 +167,7 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
* Handles the event by setting the translation on the view or showing the
* popupmenu.
*/
mouseUp(sender: any, me: InternalMouseEvent): void {
mouseUp(sender: EventSource, me: InternalMouseEvent) {
if (
this.popupTrigger &&
this.inTolerance &&
@ -185,15 +181,16 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
this.graph.isEnabled() &&
this.isSelectOnPopup(me) &&
cell != null &&
!this.graph.selection.isCellSelected(cell)
!this.graph.isCellSelected(cell)
) {
this.graph.selection.setSelectionCell(cell);
this.graph.setSelectionCell(cell);
} else if (this.clearSelectionOnBackground && cell == null) {
this.graph.selection.clearSelection();
this.graph.clearSelection();
}
// Hides the tooltip if there is one
this.graph.tooltipHandler.hide();
const tooltipHandler = this.graph.getPlugin('TooltipHandler') as TooltipHandler;
tooltipHandler.hide();
// Menu is shifted by 1 pixel so that the mouse up event
// is routed via the underlying shape instead of the DIV
@ -211,7 +208,7 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
*
* Hook to return the cell for the mouse up popup trigger handling.
*/
getCellForPopupEvent(me: InternalMouseEvent): Cell {
getCellForPopupEvent(me: InternalMouseEvent) {
return me.getCell();
}
@ -220,9 +217,9 @@ class PopupMenuHandler extends mxPopupMenu implements GraphPlugin {
*
* Destroys the handler and all its resources and DOM nodes.
*/
destroy(): void {
this.graph.event.removeMouseListener(this);
this.graph.event.removeListener(this.gestureHandler);
onDestroy() {
this.graph.removeMouseListener(this);
this.graph.removeListener(this.gestureHandler);
// Supercall
super.destroy();

View File

@ -1,4 +1,4 @@
import Cell from "../cell/datatypes/Cell";
import Cell from '../cell/datatypes/Cell';
import Graph from '../Graph';
class GraphPorts {
@ -40,7 +40,7 @@ class GraphPorts {
*
* @param cell {@link mxCell} that represents the port.
*/
isPort(cell: Cell): boolean {
isPort(cell: Cell | null) {
return false;
}

View File

@ -116,8 +116,8 @@ class GraphSelection extends autoImplement<PartialClass>() {
*
* @param cell {@link mxCell} to be selected.
*/
setCell(cell: Cell) {
this.setCells(new CellArray(cell));
setCell(cell: Cell | null) {
this.setCells(cell ? new CellArray(cell) : new CellArray());
}
/**
@ -318,7 +318,7 @@ class GraphSelection extends autoImplement<PartialClass>() {
*
* @param cell {@link mxCell} to be selected.
*/
setSelectionCell(cell: Cell) {
setSelectionCell(cell: Cell | null) {
this.setCell(cell);
}

View File

@ -1,6 +1,6 @@
import Cell from '../cell/datatypes/Cell';
import Rectangle from '../geometry/Rectangle';
import utils, { convertPoint, getValue, mod } from '../../util/Utils';
import utils, { autoImplement, convertPoint, getValue, mod } from '../../util/Utils';
import {
DEFAULT_STARTSIZE,
DIRECTION_EAST,
@ -12,34 +12,42 @@ import {
import CellArray from '../cell/datatypes/CellArray';
import InternalMouseEvent from '../event/InternalMouseEvent';
import { getClientX, getClientY } from '../../util/EventUtils';
import Graph from '../Graph';
class GraphSwimlane {
constructor(graph: Graph) {
this.graph = graph;
}
import type Graph from '../Graph';
import GraphCells from '../cell/GraphCells';
import { CellStateStyles } from '../../types';
import GraphDragDrop from '../drag_drop/GraphDragDrop';
import GraphPanning from '../panning/GraphPanning';
graph: Graph;
type PartialGraph = Pick<
Graph,
'getDefaultParent' | 'getCurrentRoot' | 'getModel' | 'getView' | 'getContainer'
>;
type PartialCells = Pick<GraphCells, 'getCurrentCellStyle' | 'intersects'>;
type PartialDragDrop = Pick<GraphDragDrop, 'isSplitEnabled' | 'isSplitTarget'>;
type PartialPanning = Pick<GraphPanning, 'getPanDx' | 'getPanDy'>;
type PartialClass = PartialGraph & PartialCells & PartialDragDrop & PartialPanning;
class GraphSwimlane extends autoImplement<PartialClass>() {
/**
* Specifies if swimlanes should be selectable via the content if the
* mouse is released.
* @default true
*/
swimlaneSelectionEnabled: boolean = true;
swimlaneSelectionEnabled = true;
/**
* Specifies if nesting of swimlanes is allowed.
* @default true
*/
swimlaneNesting: boolean = true;
swimlaneNesting = true;
/**
* The attribute used to find the color for the indicator if the indicator
* color is set to 'swimlane'.
* @default {@link 'fillColor'}
*/
swimlaneIndicatorColorAttribute: string = 'fillColor';
swimlaneIndicatorColorAttribute = 'fillColor';
/**
* Returns the nearest ancestor of the given cell which is a swimlane, or
@ -47,10 +55,9 @@ class GraphSwimlane {
*
* @param cell {@link mxCell} for which the ancestor swimlane should be returned.
*/
// getSwimlane(cell: mxCell): mxCell;
getSwimlane(cell: Cell | null = null): Cell | null {
while (cell != null && !this.isSwimlane(cell)) {
cell = <Cell>cell.getParent();
getSwimlane(cell: Cell | null = null) {
while (cell && !this.isSwimlane(cell)) {
cell = cell.getParent();
}
return cell;
}
@ -64,26 +71,22 @@ class GraphSwimlane {
* @param parent {@link mxCell} that should be used as the root of the recursion.
* Default is {@link defaultParent}.
*/
getSwimlaneAt(
x: number,
y: number,
parent: Cell = this.getDefaultParent()
): Cell | null {
if (parent == null) {
parent = <Cell>this.getCurrentRoot();
getSwimlaneAt(x: number, y: number, parent?: Cell | null): Cell | null {
if (!parent) {
parent = this.getCurrentRoot();
if (parent == null) {
parent = <Cell>this.getModel().getRoot();
if (!parent) {
parent = this.getModel().getRoot();
}
}
if (parent != null) {
if (parent) {
const childCount = parent.getChildCount();
for (let i = 0; i < childCount; i += 1) {
const child = parent.getChildAt(i);
if (child != null) {
if (child) {
const result = this.getSwimlaneAt(x, y, child);
if (result != null) {
@ -92,7 +95,7 @@ class GraphSwimlane {
if (child.isVisible() && this.isSwimlane(child)) {
const state = this.getView().getState(child);
if (this.intersects(state, x, y)) {
if (state && this.intersects(state, x, y)) {
return child;
}
}
@ -110,12 +113,12 @@ class GraphSwimlane {
* @param x X-coordinate of the mouse event.
* @param y Y-coordinate of the mouse event.
*/
hitsSwimlaneContent(swimlane: Cell, x: number, y: number): boolean {
const state = this.graph.view.getState(swimlane);
hitsSwimlaneContent(swimlane: Cell, x: number, y: number) {
const state = this.getView().getState(swimlane);
const size = this.getStartSize(swimlane);
if (state != null) {
const scale = this.graph.view.getScale();
if (state) {
const scale = this.getView().getScale();
x -= state.x;
y -= state.y;
@ -142,12 +145,12 @@ class GraphSwimlane {
* @param swimlane {@link mxCell} whose start size should be returned.
* @param ignoreState Optional boolean that specifies if cell state should be ignored.
*/
getStartSize(swimlane: Cell, ignoreState: boolean = false): Rectangle {
getStartSize(swimlane: Cell, ignoreState = false) {
const result = new Rectangle();
const style = this.graph.cell.getCurrentCellStyle(swimlane, ignoreState);
const style = this.getCurrentCellStyle(swimlane, ignoreState);
const size = parseInt(getValue(style, 'startSize', DEFAULT_STARTSIZE));
if (getValue(style, 'horizontal', true)) {
if (style.horizontal === true) {
result.height = size;
} else {
result.width = size;
@ -158,11 +161,11 @@ class GraphSwimlane {
/**
* Returns the direction for the given swimlane style.
*/
getSwimlaneDirection(style: any): string {
const dir = getValue(style, 'direction', DIRECTION_EAST);
const flipH = getValue(style, 'flipH', 0) == 1;
const flipV = getValue(style, 'flipV', 0) == 1;
const h = getValue(style, 'horizontal', true);
getSwimlaneDirection(style: CellStateStyles) {
const dir = style.direction ?? DIRECTION_EAST;
const flipH = style.flipH;
const flipV = style.flipV;
const h = style.horizontal;
let n = h ? 0 : 3;
if (dir === DIRECTION_NORTH) {
@ -195,11 +198,11 @@ class GraphSwimlane {
* @param swimlane {@link mxCell} whose start size should be returned.
* @param ignoreState Optional boolean that specifies if cell state should be ignored.
*/
getActualStartSize(swimlane: Cell, ignoreState: boolean = false): Rectangle {
getActualStartSize(swimlane: Cell, ignoreState = false) {
const result = new Rectangle();
if (this.isSwimlane(swimlane, ignoreState)) {
const style = this.graph.cell.getCurrentCellStyle(swimlane, ignoreState);
const style = this.getCurrentCellStyle(swimlane, ignoreState);
const size = parseInt(getValue(style, 'startSize', DEFAULT_STARTSIZE));
const dir = this.getSwimlaneDirection(style);
@ -224,13 +227,9 @@ class GraphSwimlane {
* @param cell {@link mxCell} to be checked.
* @param ignoreState Optional boolean that specifies if the cell state should be ignored.
*/
isSwimlane(cell: Cell, ignoreState: boolean = false): boolean {
if (
cell != null &&
cell.getParent() !== this.graph.model.getRoot() &&
!cell.isEdge()
) {
return this.graph.cell.getCurrentCellStyle(cell, ignoreState).shape === SHAPE_SWIMLANE;
isSwimlane(cell: Cell, ignoreState = false) {
if (cell && cell.getParent() !== this.getModel().getRoot() && !cell.isEdge()) {
return this.getCurrentCellStyle(cell, ignoreState).shape === SHAPE_SWIMLANE;
}
return false;
}
@ -249,10 +248,10 @@ class GraphSwimlane {
* @param cells {@link mxCell} that should be dropped into the target.
* @param evt Mouseevent that triggered the invocation.
*/
isValidDropTarget(cell: Cell, cells: CellArray, evt: InternalMouseEvent): boolean {
isValidDropTarget(cell: Cell, cells: CellArray, evt: MouseEvent) {
return (
cell != null &&
((this.graph.isSplitEnabled() && this.graph.isSplitTarget(cell, cells, evt)) ||
cell &&
((this.isSplitEnabled() && this.isSplitTarget(cell, cells, evt)) ||
(!cell.isEdge() &&
(this.isSwimlane(cell) || (cell.getChildCount() > 0 && !cell.isCollapsed()))))
);
@ -274,10 +273,10 @@ class GraphSwimlane {
*/
getDropTarget(
cells: CellArray,
evt: InternalMouseEvent,
evt: MouseEvent,
cell: Cell | null = null,
clone: boolean = false
): Cell | null {
clone = false
) {
if (!this.isSwimlaneNesting()) {
for (let i = 0; i < cells.length; i += 1) {
if (this.isSwimlane(cells[i])) {
@ -286,31 +285,31 @@ class GraphSwimlane {
}
}
const pt = convertPoint(this.graph.container, getClientX(evt), getClientY(evt));
pt.x -= this.graph.panning.panDx;
pt.y -= this.graph.panning.panDy;
const pt = convertPoint(this.getContainer(), getClientX(evt), getClientY(evt));
pt.x -= this.getPanDx();
pt.y -= this.getPanDy();
const swimlane = this.getSwimlaneAt(pt.x, pt.y);
if (cell == null) {
if (!cell) {
cell = swimlane;
} else if (swimlane != null) {
} else if (swimlane) {
// Checks if the cell is an ancestor of the swimlane
// under the mouse and uses the swimlane in that case
let tmp = swimlane.getParent();
while (tmp != null && this.isSwimlane(tmp) && tmp != cell) {
while (tmp && this.isSwimlane(tmp) && tmp !== cell) {
tmp = tmp.getParent();
}
if (tmp == cell) {
if (tmp === cell) {
cell = swimlane;
}
}
while (
cell != null &&
cell &&
!this.isValidDropTarget(cell, cells, evt) &&
!this.graph.model.isLayer(cell)
!this.getModel().isLayer(cell)
) {
cell = cell.getParent();
}
@ -318,18 +317,18 @@ class GraphSwimlane {
// Checks if parent is dropped into child if not cloning
if (!clone) {
let parent = cell;
while (parent != null && cells.indexOf(parent) < 0) {
while (parent && cells.indexOf(parent) < 0) {
parent = parent.getParent();
}
}
return !this.graph.model.isLayer(<Cell>cell) && parent == null ? cell : null;
return !this.getModel().isLayer(<Cell>cell) && !parent ? cell : null;
}
/**
* Returns {@link swimlaneNesting} as a boolean.
*/
isSwimlaneNesting(): boolean {
isSwimlaneNesting() {
return this.swimlaneNesting;
}
@ -339,14 +338,14 @@ class GraphSwimlane {
*
* @param value Boolean indicating if swimlanes can be nested.
*/
setSwimlaneNesting(value: boolean): void {
setSwimlaneNesting(value: boolean) {
this.swimlaneNesting = value;
}
/**
* Returns {@link swimlaneSelectionEnabled} as a boolean.
*/
isSwimlaneSelectionEnabled(): boolean {
isSwimlaneSelectionEnabled() {
return this.swimlaneSelectionEnabled;
}
@ -357,7 +356,7 @@ class GraphSwimlane {
* @param value Boolean indicating if swimlanes content areas
* should be selected when the mouse is released over them.
*/
setSwimlaneSelectionEnabled(value: boolean): void {
setSwimlaneSelectionEnabled(value: boolean) {
this.swimlaneSelectionEnabled = value;
}
}

View File

@ -7,11 +7,10 @@ import { autoImplement } from '../../util/Utils';
import type Graph from '../Graph';
import type GraphFolding from '../folding/GraphFolding';
import SelectionCellsHandler from '../selection/SelectionCellsHandler';
import TooltipHandler from './TooltipHandler';
type PartialGraph = Pick<
Graph,
'getSelectionCellsHandler' | 'convertValueToString' | 'getTooltipHandler'
>;
type PartialGraph = Pick<Graph, 'convertValueToString' | 'getPlugin'>;
type PartialFolding = Pick<GraphFolding, 'getCollapseExpandResource'>;
type PartialClass = PartialGraph & PartialFolding;
@ -53,8 +52,15 @@ class GraphTooltip extends autoImplement<PartialClass>() {
}
if (!tip) {
const handler = this.getSelectionCellsHandler().getHandler(state.cell);
const selectionCellsHandler = this.getPlugin(
'SelectionCellsHandler'
) as SelectionCellsHandler;
const handler = selectionCellsHandler.getHandler(state.cell);
// @ts-ignore Guarded against undefined error already.
if (handler && typeof handler.getTooltipForNode === 'function') {
// @ts-ignore Guarded against undefined error already.
tip = handler.getTooltipForNode(node);
}
}
@ -108,7 +114,9 @@ class GraphTooltip extends autoImplement<PartialClass>() {
* @param enabled Boolean indicating if tooltips should be enabled.
*/
setTooltips(enabled: boolean) {
this.getTooltipHandler().setEnabled(enabled);
const tooltipHandler = this.getPlugin('TooltipHandler') as TooltipHandler;
tooltipHandler.setEnabled(enabled);
}
}

View File

@ -15,6 +15,7 @@ import InternalMouseEvent from '../event/InternalMouseEvent';
import PopupMenuHandler from '../popups_menus/PopupMenuHandler';
import type { GraphPlugin } from '../../types';
import EventSource from '../event/EventSource';
/**
* Class: mxTooltipHandler

View File

@ -52,7 +52,7 @@ class GraphValidation extends autoImplement<PartialClass>() {
* @param source {@link mxCell} that represents the source terminal.
* @param target {@link mxCell} that represents the target terminal.
*/
isEdgeValid(edge: Cell, source: Cell, target: Cell) {
isEdgeValid(edge: Cell | null, source: Cell, target: Cell) {
return !this.getEdgeValidationError(edge, source, target);
}
@ -97,7 +97,7 @@ class GraphValidation extends autoImplement<PartialClass>() {
edge: Cell | null = null,
source: Cell | null = null,
target: Cell | null = null
): string | null {
) {
if (edge && !this.isAllowDanglingEdges() && (!source || !target)) {
return '';
}