From 61648e43ce2c9df3e847e89ea86d72ae0ed83e26 Mon Sep 17 00:00:00 2001 From: Junsik Shim Date: Mon, 30 Aug 2021 18:45:01 +0900 Subject: [PATCH] - Converting *Handlers into plugins. - Keep resolving errors. --- packages/core/src/types.ts | 16 +- packages/core/src/util/EventUtils.ts | 2 +- packages/core/src/util/Guide.ts | 154 ++--- packages/core/src/util/Utils.ts | 4 +- .../util/gui/{mxPopupMenu.js => PopupMenu.ts} | 185 +++--- .../storage/{mxClipboard.ts => Clipboard.ts} | 84 ++- packages/core/src/view/Graph.ts | 283 ++++---- packages/core/src/view/GraphHandler.ts | 616 +++++++++--------- packages/core/src/view/cell/CellMarker.ts | 2 +- packages/core/src/view/cell/CellRenderer.ts | 138 ++-- packages/core/src/view/cell/GraphCells.ts | 14 +- .../core/src/view/cell/datatypes/CellState.ts | 2 +- .../core/src/view/cell/edge/EdgeHandler.ts | 242 +++---- .../src/view/cell/edge/EdgeSegmentHandler.ts | 142 ++-- .../src/view/cell/edge/ElbowEdgeHandler.ts | 53 +- packages/core/src/view/cell/edge/GraphEdge.ts | 4 +- .../core/src/view/cell/vertex/GraphVertex.ts | 26 +- .../src/view/cell/vertex/VertexHandler.ts | 26 +- .../src/view/connection/ConnectionHandler.ts | 288 ++++---- .../src/view/connection/ConstraintHandler.ts | 134 ++-- .../src/view/connection/GraphConnections.ts | 10 +- .../core/src/view/drag_drop/DragSource.ts | 253 ++++--- .../core/src/view/drag_drop/GraphDragDrop.ts | 45 +- packages/core/src/view/editing/CellEditor.ts | 333 ++++------ .../core/src/view/editing/GraphEditing.ts | 2 +- packages/core/src/view/event/CellTracker.ts | 23 +- packages/core/src/view/event/EventSource.ts | 3 +- packages/core/src/view/event/GraphEvents.ts | 247 ++++--- packages/core/src/view/event/InternalEvent.ts | 26 +- .../core/src/view/geometry/shape/Shape.ts | 2 + .../src/view/page_breaks/GraphPageBreaks.ts | 96 +-- .../core/src/view/panning/GraphPanning.ts | 4 + .../core/src/view/panning/PanningHandler.ts | 2 + .../core/src/view/panning/PanningManager.ts | 15 +- .../src/view/popups_menus/PopupMenuHandler.ts | 89 ++- packages/core/src/view/ports/GraphPorts.ts | 4 +- .../core/src/view/selection/GraphSelection.ts | 6 +- .../core/src/view/swimlane/GraphSwimlane.ts | 135 ++-- .../core/src/view/tooltip/GraphTooltip.ts | 20 +- .../core/src/view/tooltip/TooltipHandler.ts | 1 + .../src/view/validation/GraphValidation.ts | 4 +- 41 files changed, 1851 insertions(+), 1884 deletions(-) rename packages/core/src/util/gui/{mxPopupMenu.js => PopupMenu.ts} (76%) rename packages/core/src/util/storage/{mxClipboard.ts => Clipboard.ts} (65%) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index c75ba899d..5cb54d134 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -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; +} diff --git a/packages/core/src/util/EventUtils.ts b/packages/core/src/util/EventUtils.ts index c85b81db4..94a907e6d 100644 --- a/packages/core/src/util/EventUtils.ts +++ b/packages/core/src/util/EventUtils.ts @@ -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; }; diff --git a/packages/core/src/util/Guide.ts b/packages/core/src/util/Guide.ts index 20454fd11..cf95fdfaf 100644 --- a/packages/core/src/util/Guide.ts +++ b/packages/core/src/util/Guide.ts @@ -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 instance. */ - // graph: mxGraph; - graph: graph; + graph: MaxGraph; /** * Variable: states * * Contains the 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 for the horizontal guide. */ - // guideX: mxShape; guideX: Shape | null = null; /** @@ -75,7 +71,6 @@ class Guide { * * Holds the 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 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 = this.graph.container; + const c = this.graph.container; - if (!overrideX && this.guideX != null) { - (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); - (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) { - (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); - (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 = (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) { - (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) { - (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; } diff --git a/packages/core/src/util/Utils.ts b/packages/core/src/util/Utils.ts index 0539c1bdc..fd238c831 100644 --- a/packages/core/src/util/Utils.ts +++ b/packages/core/src/util/Utils.ts @@ -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); } }; diff --git a/packages/core/src/util/gui/mxPopupMenu.js b/packages/core/src/util/gui/PopupMenu.ts similarity index 76% rename from packages/core/src/util/gui/mxPopupMenu.js rename to packages/core/src/util/gui/PopupMenu.ts index b23bee381..269e8e4f2 100644 --- a/packages/core/src/util/gui/mxPopupMenu.js +++ b/packages/core/src/util/gui/PopupMenu.ts @@ -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 . */ -class mxPopupMenu extends EventSource { - constructor(factoryMethod) { +class PopupMenu extends EventSource implements Partial { + 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 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 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 . */ - // isEnabled(): boolean; isEnabled() { return this.enabled; } @@ -166,8 +159,7 @@ class mxPopupMenu extends EventSource { * Enables or disables event handling. This implementation * updates . */ - // setEnabled(enabled: boolean): void; - setEnabled(enabled) { + setEnabled(enabled: boolean) { this.enabled = enabled; } @@ -181,8 +173,7 @@ class mxPopupMenu extends EventSource { * * me - 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 = (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,12 +333,13 @@ 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; - td.style.backgroundImage = `url('${img}')`; - td.style.backgroundRepeat = 'no-repeat'; - td.style.backgroundPosition = '2px 50%'; + 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%'; + } } /** @@ -375,8 +353,7 @@ class mxPopupMenu extends EventSource { * * parent - An item returned by . */ - // 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,8 +373,10 @@ class mxPopupMenu extends EventSource { img.setAttribute('src', this.submenuImage); // Last column of the submenu item in the parent menu - td = parent.firstChild.nextSibling.nextSibling; - td.appendChild(img); + if (parent.firstChild?.nextSibling?.nextSibling) { + const td = parent.firstChild.nextSibling.nextSibling; + td.appendChild(img); + } } /** @@ -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 . * force - Optional boolean to ignore . 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((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((this)); this.containsItems = false; this.fireEvent(new EventObject(InternalEvent.HIDE)); } @@ -562,8 +535,7 @@ class mxPopupMenu extends EventSource { * * parent - An item returned by . */ - // 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; diff --git a/packages/core/src/util/storage/mxClipboard.ts b/packages/core/src/util/storage/Clipboard.ts similarity index 65% rename from packages/core/src/util/storage/mxClipboard.ts rename to packages/core/src/util/storage/Clipboard.ts index 65072b9c3..a93cc76c1 100644 --- a/packages/core/src/util/storage/mxClipboard.ts +++ b/packages/core/src/util/storage/Clipboard.ts @@ -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 = (graph.getExportableCells(cells)).getTopmostCells(); - mxClipboard.insertCount = 1; - mxClipboard.setCells(graph.cloneCells(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(cells); } @@ -193,4 +191,4 @@ class mxClipboard { } } -export default mxClipboard; +export default Clipboard; diff --git a/packages/core/src/view/Graph.ts b/packages/core/src/view/Graph.ts index f0328ee53..f6adce424 100644 --- a/packages/core/src/view/Graph.ts +++ b/packages/core/src/view/Graph.ts @@ -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; type PartialTooltip = Pick; type PartialValidation = Pick< GraphValidation, - 'getEdgeValidationError' | 'validationAlert' + 'getEdgeValidationError' | 'validationAlert' | 'isEdgeValid' >; type PartialLabel = Pick< GraphLabel, 'isLabelMovable' | 'isHtmlLabel' | 'isWrapping' | 'isLabelClipped' | 'getLabel' >; -type PartialTerminal = Pick; +type PartialTerminal = Pick; 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; type PartialFolding = Pick< @@ -163,8 +193,16 @@ type PartialPanning = Pick< | 'setPanDx' | 'getPanDy' | 'setPanDy' + | 'isTimerAutoScroll' + | 'isAllowAutoPanning' + | 'scrollCellToVisible' >; type PartialZoom = Pick; +type PartialDragDrop = Pick< + GraphDragDrop, + 'isDropEnabled' | 'isAutoScroll' | 'isAutoExtend' | 'isSplitEnabled' | 'isSplitTarget' +>; +type PartialSwimlane = Pick; 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() { 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() { 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() { */ 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() { * 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() { * 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() { * 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() { * 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() { * 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() { * 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() { */ 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() { * 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() { 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() { 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() { 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 = this.container; @@ -860,14 +906,8 @@ class Graph extends autoImplement() { } } } - } else if ( - this.allowAutoPanning && - !(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() { /** * 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() { if (state.cell.isEdge()) { const source = state.getVisibleTerminalState(true); const target = state.getVisibleTerminalState(false); - const geo = (state.cell).getGeometry(); + const geo = state.cell.getGeometry(); const edgeStyle = this.getView().getEdgeStyle( state, - geo != null ? geo.points : null, - source, - target + geo ? geo.points : undefined, + source, + target ); result = this.createEdgeHandler(state, edgeStyle); } else { @@ -1120,7 +1159,7 @@ class Graph extends autoImplement() { * * @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() { } else { result = new EdgeHandler(state); } - return result; + + return result as EdgeHandler; } /** @@ -1145,8 +1185,8 @@ class Graph extends autoImplement() { * * @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() { * * @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() { * 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() { * * @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() { const state = this.view.getState(current); if (state != null) { - this.selection.setSelectionCell(current); + this.setSelectionCell(current); } } } @@ -1242,7 +1281,7 @@ class Graph extends autoImplement() { * @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() { */ const orthogonal = edge.style.orthogonal; - if (orthogonal != null) { + if (orthogonal !== null) { return orthogonal; } @@ -1461,7 +1500,7 @@ class Graph extends autoImplement() { /** * Returns {@link resizeContainer}. */ - isResizeContainer(): boolean { + isResizeContainer() { return this.resizeContainer; } @@ -1477,7 +1516,7 @@ class Graph extends autoImplement() { /** * Returns true if the graph is {@link enabled}. */ - isEnabled(): boolean { + isEnabled() { return this.enabled; } @@ -1487,14 +1526,14 @@ class Graph extends autoImplement() { * * @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() { * @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() { * * @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() { * * @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() { * * @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() { 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() { } 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; diff --git a/packages/core/src/view/GraphHandler.ts b/packages/core/src/view/GraphHandler.ts index 70fe173b1..9276df827 100644 --- a/packages/core/src/view/GraphHandler.ts +++ b/packages/core/src/view/GraphHandler.ts @@ -7,13 +7,7 @@ import mxClient from '../mxClient'; import InternalEvent from './event/InternalEvent'; -import utils, { - contains, - convertPoint, - getRotatedPoint, - getValue, - toRadians, -} from '../util/Utils'; +import { contains, convertPoint, getRotatedPoint, toRadians } from '../util/Utils'; import RectangleShape from './geometry/shape/node/RectangleShape'; import mxGuide from '../util/Guide'; import Point from './geometry/Point'; @@ -24,6 +18,7 @@ import { DIALECT_SVG, DROP_TARGET_COLOR, INVALID_CONNECT_TARGET_COLOR, + NONE, VALID_COLOR, } from '../util/Constants'; import Dictionary from '../util/Dictionary'; @@ -31,7 +26,19 @@ import CellHighlight from './selection/CellHighlight'; import Rectangle from './geometry/Rectangle'; import { getClientX, getClientY, isAltDown, isMultiTouchEvent } from '../util/EventUtils'; import { MaxGraph } from './Graph'; -import { GraphPlugin, GraphPluginConstructor } from '../types'; +import Guide from '../util/Guide'; +import Shape from './geometry/shape/Shape'; +import InternalMouseEvent from './event/InternalMouseEvent'; +import SelectionCellsHandler from './selection/SelectionCellsHandler'; +import Cell from './cell/datatypes/Cell'; +import PopupMenuHandler from './popups_menus/PopupMenuHandler'; +import EventSource from './event/EventSource'; +import CellArray from './cell/datatypes/CellArray'; +import CellState from './cell/datatypes/CellState'; +import EventObject from './event/EventObject'; + +import type { ColorValue, GraphPlugin } from '../types'; +import ConnectionHandler from './connection/ConnectionHandler'; /** * Class: mxGraphHandler @@ -88,7 +95,7 @@ class GraphHandler implements GraphPlugin { this.refreshThread = window.setTimeout(() => { this.refreshThread = null; - if (this.first != null && !this.suspended) { + if (this.first && !this.suspended && this.cells) { // Updates preview with no translate to compute bounding box const dx = this.currentDx; const dy = this.currentDy; @@ -108,9 +115,13 @@ class GraphHandler implements GraphPlugin { this.updateHint(); if (this.livePreviewUsed) { + const selectionCellsHandler = this.graph.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + // Forces update to ignore last visible state this.setHandlesVisibleForCells( - this.graph.selectionCellsHandler.getHandledSelectionCells(), + selectionCellsHandler.getHandledSelectionCells(), false, true ); @@ -124,7 +135,7 @@ class GraphHandler implements GraphPlugin { this.graph.getModel().addListener(InternalEvent.CHANGE, this.refreshHandler); this.graph.addListener(InternalEvent.REFRESH, this.refreshHandler); - this.keyHandler = (e) => { + this.keyHandler = (e: KeyboardEvent) => { if ( this.graph.container != null && this.graph.container.style.visibility !== 'hidden' && @@ -132,7 +143,7 @@ class GraphHandler implements GraphPlugin { !this.suspended ) { const clone = - this.graph.isCloneEvent(e) && + this.graph.isCloneEvent((e)) && this.graph.isCellsCloneable() && this.isCloneEnabled(); @@ -157,6 +168,12 @@ class GraphHandler implements GraphPlugin { */ graph: MaxGraph; + panHandler: () => void; + escapeHandler: (sender: EventSource, evt: EventObject) => void; + refreshHandler: (sender: EventSource, evt: EventObject) => void; + keyHandler: (e: KeyboardEvent) => void; + refreshThread: number | null = null; + /** * Variable: maxCells * @@ -167,7 +184,6 @@ class GraphHandler implements GraphPlugin { * cells in the graph is limited to a small number, eg. * 500. */ - // maxCells: number; maxCells = 50; /** @@ -175,7 +191,6 @@ class GraphHandler implements GraphPlugin { * * Specifies if events are handled. Default is true. */ - // enabled: boolean; enabled = true; /** @@ -184,7 +199,6 @@ class GraphHandler implements GraphPlugin { * Specifies if drop targets under the mouse should be enabled. Default is * true. */ - // highlightEnabled: boolean; highlightEnabled = true; /** @@ -192,7 +206,6 @@ class GraphHandler implements GraphPlugin { * * Specifies if cloning by control-drag is enabled. Default is true. */ - // cloneEnabled: boolean; cloneEnabled = true; /** @@ -200,7 +213,6 @@ class GraphHandler implements GraphPlugin { * * Specifies if moving is enabled. Default is true. */ - // moveEnabled: boolean; moveEnabled = true; /** @@ -209,7 +221,6 @@ class GraphHandler implements GraphPlugin { * Specifies if other cells should be used for snapping the right, center or * left side of the current selection. Default is false. */ - // guidesEnabled: boolean; guidesEnabled = false; /** @@ -224,24 +235,21 @@ class GraphHandler implements GraphPlugin { * * Holds the instance that is used for alignment. */ - // guide: mxGuide; - guide = null; + guide: Guide | null = null; /** * Variable: currentDx * * Stores the x-coordinate of the current mouse move. */ - // currentDx: number; - currentDx = null; + currentDx = 0; /** * Variable: currentDy * * Stores the y-coordinate of the current mouse move. */ - // currentDy: number; - currentDy = null; + currentDy = 0; /** * Variable: updateCursor @@ -249,7 +257,6 @@ class GraphHandler implements GraphPlugin { * Specifies if a move cursor should be shown if the mouse is over a movable * cell. Default is true. */ - // updateCursor: boolean; updateCursor = true; /** @@ -257,7 +264,6 @@ class GraphHandler implements GraphPlugin { * * Specifies if selecting is enabled. Default is true. */ - // selectEnabled: boolean; selectEnabled = true; /** @@ -265,7 +271,6 @@ class GraphHandler implements GraphPlugin { * * Specifies if cells may be moved out of their parents. Default is true. */ - // removeCellsFromParent: boolean; removeCellsFromParent = true; /** @@ -282,7 +287,6 @@ class GraphHandler implements GraphPlugin { * Specifies if drop events are interpreted as new connections if no other * drop action is defined. Default is false. */ - // connectOnDrop: boolean; connectOnDrop = false; /** @@ -291,7 +295,6 @@ class GraphHandler implements GraphPlugin { * Specifies if the view should be scrolled so that a moved cell is * visible. Default is true. */ - // scrollOnMove: boolean; scrollOnMove = true; /** @@ -300,7 +303,6 @@ class GraphHandler implements GraphPlugin { * Specifies the minimum number of pixels for the width and height of a * selection border. Default is 6. */ - // minimumSize: number; minimumSize = 6; /** @@ -308,8 +310,7 @@ class GraphHandler implements GraphPlugin { * * Specifies the color of the preview shape. Default is black. */ - // previewColor: string; - previewColor = 'black'; + previewColor: ColorValue = 'black'; /** * Variable: htmlPreview @@ -318,7 +319,6 @@ class GraphHandler implements GraphPlugin { * then drop target detection relies entirely on because * the HTML preview does not "let events through". Default is false. */ - // htmlPreview: boolean; htmlPreview = false; /** @@ -326,15 +326,13 @@ class GraphHandler implements GraphPlugin { * * Reference to the that represents the preview. */ - // shape: mxShape; - shape = null; + shape: Shape | null = null; /** * Variable: scaleGrid * * Specifies if the grid should be scaled. Default is false. */ - // scaleGrid: boolean; scaleGrid = false; /** @@ -342,7 +340,6 @@ class GraphHandler implements GraphPlugin { * * Specifies if the bounding box should allow for rotation. Default is true. */ - // rotationEnabled: boolean; rotationEnabled = true; /** @@ -350,7 +347,6 @@ class GraphHandler implements GraphPlugin { * * Maximum number of cells for which live preview should be used. Default is 0 which means no live preview. */ - // maxLivePreview: number; maxLivePreview = 0; /** @@ -358,15 +354,35 @@ class GraphHandler implements GraphPlugin { * * If live preview is allowed on this system. Default is true for systems with SVG support. */ - // allowLivePreview: boolean; allowLivePreview = mxClient.IS_SVG; + cell: Cell | null = null; + + delayedSelection = false; + + first: Point | null = null; + cells: CellArray | null = null; + bounds: Rectangle | null = null; + pBounds: Rectangle | null = null; + allCells: Dictionary = new Dictionary(); + + cellWasClicked = false; + cloning = false; + cellCount = 0; + + target: Cell | null = null; + + suspended = false; + livePreviewActive = false; + livePreviewUsed = false; + + highlight: CellHighlight | null = null; + /** * Function: isEnabled * * Returns . */ - // isEnabled(): boolean; isEnabled() { return this.enabled; } @@ -376,8 +392,7 @@ class GraphHandler implements GraphPlugin { * * Sets . */ - // setEnabled(value: boolean): void; - setEnabled(value) { + setEnabled(value: boolean) { this.enabled = value; } @@ -386,7 +401,6 @@ class GraphHandler implements GraphPlugin { * * Returns . */ - // isCloneEnabled(): boolean; isCloneEnabled() { return this.cloneEnabled; } @@ -400,8 +414,7 @@ class GraphHandler implements GraphPlugin { * * value - Boolean that specifies the new clone enabled state. */ - // setCloneEnabled(value: boolean): void; - setCloneEnabled(value) { + setCloneEnabled(value: boolean) { this.cloneEnabled = value; } @@ -410,7 +423,6 @@ class GraphHandler implements GraphPlugin { * * Returns . */ - // isMoveEnabled(): boolean; isMoveEnabled() { return this.moveEnabled; } @@ -420,8 +432,7 @@ class GraphHandler implements GraphPlugin { * * Sets . */ - // setMoveEnabled(value: boolean): void; - setMoveEnabled(value) { + setMoveEnabled(value: boolean) { this.moveEnabled = value; } @@ -430,7 +441,6 @@ class GraphHandler implements GraphPlugin { * * Returns . */ - // isSelectEnabled(): boolean; isSelectEnabled() { return this.selectEnabled; } @@ -440,8 +450,7 @@ class GraphHandler implements GraphPlugin { * * Sets . */ - // setSelectEnabled(value: boolean): void; - setSelectEnabled(value) { + setSelectEnabled(value: boolean) { this.selectEnabled = value; } @@ -450,7 +459,6 @@ class GraphHandler implements GraphPlugin { * * Returns . */ - // isRemoveCellsFromParent(): boolean; isRemoveCellsFromParent() { return this.removeCellsFromParent; } @@ -460,8 +468,7 @@ class GraphHandler implements GraphPlugin { * * Sets . */ - // setRemoveCellsFromParent(value: boolean): void; - setRemoveCellsFromParent(value) { + setRemoveCellsFromParent(value: boolean) { this.removeCellsFromParent = value; } @@ -471,7 +478,7 @@ class GraphHandler implements GraphPlugin { * Returns true if the given cell and parent should propagate * selection state to the parent. */ - isPropagateSelectionCell(cell, immediate, me) { + isPropagateSelectionCell(cell: Cell, immediate: boolean, me: InternalMouseEvent) { const parent = cell.getParent(); if (immediate) { @@ -479,14 +486,14 @@ class GraphHandler implements GraphPlugin { return ( !this.graph.isSiblingSelected(cell) && - ((geo != null && geo.relative) || !this.graph.isSwimlane(parent)) + geo && + geo.relative /* disable swimlane for now || !this.graph.isSwimlane(parent) */ ); } return ( (!this.graph.isToggleEvent(me.getEvent()) || - (!this.graph.isSiblingSelected(cell) && - !this.graph.isCellSelected(cell) && - !this.graph.isSwimlane(parent)) || + (!this.graph.isSiblingSelected(cell) && !this.graph.isCellSelected(cell)) || + /* disable swimlane for now !this.graph.isSwimlane(parent)*/ this.graph.isCellSelected(parent)) && (this.graph.isToggleEvent(me.getEvent()) || !this.graph.isCellSelected(parent)) ); @@ -497,20 +504,18 @@ class GraphHandler implements GraphPlugin { * * Hook to return initial cell for the given event. */ - // getInitialCellForEvent(me: mxMouseEvent): mxCell; - getInitialCellForEvent(me) { + getInitialCellForEvent(me: InternalMouseEvent) { let state = me.getState(); if ( (!this.graph.isToggleEvent(me.getEvent()) || !isAltDown(me.getEvent())) && - state != null && + state && !this.graph.isCellSelected(state.cell) ) { - const { model } = this.graph; let next = this.graph.view.getState(state.cell.getParent()); while ( - next != null && + next && !this.graph.isCellSelected(next.cell) && (next.cell.isVertex() || next.cell.isEdge()) && this.isPropagateSelectionCell(state.cell, true, me) @@ -520,7 +525,7 @@ class GraphHandler implements GraphPlugin { } } - return state != null ? state.cell : null; + return state ? state.cell : null; } /** @@ -528,12 +533,15 @@ class GraphHandler implements GraphPlugin { * * Hook to return true for delayed selections. */ - // isDelayedSelection(cell: mxCell, me: mxMouseEvent): boolean; - isDelayedSelection(cell, me) { + isDelayedSelection(cell: Cell, me: InternalMouseEvent) { + const selectionCellsHandler = this.graph.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + if (!this.graph.isToggleEvent(me.getEvent()) || !isAltDown(me.getEvent())) { - while (cell != null) { - if (this.graph.selectionCellsHandler.isHandled(cell)) { - return this.graph.cellEditor.getEditingCell() != cell; + while (cell) { + if (selectionCellsHandler.isHandled(cell)) { + return this.graph.cellEditor.getEditingCell() !== cell; } cell = cell.getParent(); @@ -548,16 +556,17 @@ class GraphHandler implements GraphPlugin { * * Implements the delayed selection for the given mouse event. */ - // selectDelayed(me: mxMouseEvent): void; - selectDelayed(me) { - if (!this.graph.popupMenuHandler.isPopupTrigger(me)) { + selectDelayed(me: InternalMouseEvent) { + const popupMenuHandler = this.graph.getPlugin('PopupMenuHandler') as PopupMenuHandler; + + if (!popupMenuHandler.isPopupTrigger(me)) { let cell = me.getCell(); - if (cell == null) { + if (cell === null) { cell = this.cell; } - this.selectCellForEvent(cell, me); + if (cell) this.selectCellForEvent(cell, me); } } @@ -566,19 +575,18 @@ class GraphHandler implements GraphPlugin { * * Selects the given cell for the given . */ - selectCellForEvent(cell, me) { + selectCellForEvent(cell: Cell, me: InternalMouseEvent) { const state = this.graph.view.getState(cell); - if (state != null) { + if (state) { if (me.isSource(state.control)) { this.graph.selectCellForEvent(cell, me.getEvent()); } else { if (!this.graph.isToggleEvent(me.getEvent()) || !isAltDown(me.getEvent())) { - const model = this.graph.getModel(); let parent = cell.getParent(); while ( - this.graph.view.getState(parent) != null && + this.graph.view.getState(parent) && (parent.isVertex() || parent.isEdge()) && this.isPropagateSelectionCell(cell, false, me) ) { @@ -613,8 +621,7 @@ class GraphHandler implements GraphPlugin { * } * */ - // consumeMouseEvent(evtName: string, me: mxMouseEvent): void; - consumeMouseEvent(evtName, me) { + consumeMouseEvent(evtName: string, me: InternalMouseEvent) { me.consume(); } @@ -625,44 +632,46 @@ class GraphHandler implements GraphPlugin { * it. By consuming the event all subsequent events of the gesture are * redirected to this handler. */ - // mouseDown(sender: any, me: mxMouseEvent): void; - mouseDown(sender, me) { + mouseDown(sender: EventSource, me: InternalMouseEvent) { if ( !me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && - me.getState() != null && + me.getState() && !isMultiTouchEvent(me.getEvent()) ) { const cell = this.getInitialCellForEvent(me); - this.delayedSelection = this.isDelayedSelection(cell, me); - this.cell = null; - if (this.isSelectEnabled() && !this.delayedSelection) { - this.graph.selectCellForEvent(cell, me.getEvent()); - } + if (cell) { + this.delayedSelection = this.isDelayedSelection(cell, me); + this.cell = null; - if (this.isMoveEnabled()) { - const { model } = this.graph; - const geo = cell.getGeometry(); - - if ( - this.graph.isCellMovable(cell) && - (!cell.isEdge() || - this.graph.getSelectionCount() > 1 || - (geo.points != null && geo.points.length > 0) || - cell.getTerminal(true) == null || - cell.getTerminal(false) == null || - this.graph.allowDanglingEdges || - (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())) - ) { - this.start(cell, me.getX(), me.getY()); - } else if (this.delayedSelection) { - this.cell = cell; + if (this.isSelectEnabled() && !this.delayedSelection) { + this.graph.selectCellForEvent(cell, me.getEvent()); } - this.cellWasClicked = true; - this.consumeMouseEvent(InternalEvent.MOUSE_DOWN, me); + if (this.isMoveEnabled()) { + const geo = cell.getGeometry(); + + if ( + geo && + this.graph.isCellMovable(cell) && + (!cell.isEdge() || + this.graph.getSelectionCount() > 1 || + (geo.points != null && geo.points.length > 0) || + cell.getTerminal(true) == null || + cell.getTerminal(false) == null || + this.graph.isAllowDanglingEdges() || + (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())) + ) { + this.start(cell, me.getX(), me.getY()); + } else if (this.delayedSelection) { + this.cell = cell; + } + + this.cellWasClicked = true; + this.consumeMouseEvent(InternalEvent.MOUSE_DOWN, me); + } } } } @@ -672,21 +681,18 @@ class GraphHandler implements GraphPlugin { * * Creates an array of cell states which should be used as guides. */ - // getGuideStates(): Array; getGuideStates() { const parent = this.graph.getDefaultParent(); - const model = this.graph.getModel(); - const filter = (cell) => { + const filter = (cell: Cell) => { + const geo = cell.getGeometry(); + return ( - this.graph.view.getState(cell) != null && - cell.isVertex() && - cell.getGeometry() != null && - !cell.getGeometry().relative + !!this.graph.view.getState(cell) && cell.isVertex() && !!geo && !geo.relative ); }; - return this.graph.view.getCellStates(model.filterDescendants(filter, parent)); + return this.graph.view.getCellStates(parent.filterDescendants(filter)); } /** @@ -701,10 +707,9 @@ class GraphHandler implements GraphPlugin { * * initialCell - that triggered this handler. */ - // getCells(initialCell: mxCell): mxCell[]; - getCells(initialCell) { + getCells(initialCell: Cell) { if (!this.delayedSelection && this.graph.isCellMovable(initialCell)) { - return [initialCell]; + return new CellArray(initialCell); } return this.graph.getMovableCells(this.graph.getSelectionCells()); } @@ -715,11 +720,10 @@ class GraphHandler implements GraphPlugin { * Returns the used as the preview bounds for * moving the given cells. */ - // getPreviewBounds(cells: mxCell[]): mxRectangle; - getPreviewBounds(cells) { + getPreviewBounds(cells: CellArray) { const bounds = this.getBoundingBox(cells); - if (bounds != null) { + if (bounds) { // Corrects width and height bounds.width = Math.max(0, bounds.width - 1); bounds.height = Math.max(0, bounds.height - 1); @@ -733,9 +737,6 @@ class GraphHandler implements GraphPlugin { bounds.width = Math.ceil(bounds.width); } - const tr = this.graph.view.translate; - const s = this.graph.view.scale; - if (bounds.height < this.minimumSize) { const dy = this.minimumSize - bounds.height; bounds.y -= dy / 2; @@ -762,32 +763,27 @@ class GraphHandler implements GraphPlugin { * * cells - Array of whose bounding box should be returned. */ - // getBoundingBox(cells: mxCell[]): mxRectangle; - getBoundingBox(cells) { + getBoundingBox(cells: CellArray) { let result = null; - if (cells != null && cells.length > 0) { - const model = this.graph.getModel(); - + if (cells.length > 0) { for (let i = 0; i < cells.length; i += 1) { if (cells[i].isVertex() || cells[i].isEdge()) { const state = this.graph.view.getState(cells[i]); - if (state != null) { - let bbox = state; + if (state) { + let bbox = null; - if ( - cells[i].isVertex() && - state.shape != null && - state.shape.boundingBox != null - ) { + if (cells[i].isVertex() && state.shape && state.shape.boundingBox) { bbox = state.shape.boundingBox; } - if (result == null) { - result = Rectangle.fromRectangle(bbox); - } else { - result.add(bbox); + if (bbox) { + if (!result) { + result = Rectangle.fromRectangle(bbox); + } else { + result.add(bbox); + } } } } @@ -802,9 +798,8 @@ class GraphHandler implements GraphPlugin { * * Creates the shape used to draw the preview for the given bounds. */ - // createPreviewShape(bounds: mxRectangle): mxRectangleShape; - createPreviewShape(bounds) { - const shape = new RectangleShape(bounds, null, this.previewColor); + createPreviewShape(bounds: Rectangle) { + const shape = new RectangleShape(bounds, NONE, this.previewColor); shape.isDashed = true; if (this.htmlPreview) { @@ -838,14 +833,12 @@ class GraphHandler implements GraphPlugin { * * Starts the handling of the mouse gesture. */ - // start(cell: mxCell, x: number, y: number): void; - start(cell, x, y, cells) { + start(cell: Cell, x: number, y: number, cells?: CellArray) { this.cell = cell; this.first = convertPoint(this.graph.container, x, y); - this.cells = cells != null ? cells : this.getCells(this.cell); + this.cells = cells ? cells : this.getCells(this.cell); this.bounds = this.graph.getView().getBounds(this.cells); this.pBounds = this.getPreviewBounds(this.cells); - this.allCells = new Dictionary(); this.cloning = false; this.cellCount = 0; @@ -865,21 +858,21 @@ class GraphHandler implements GraphPlugin { for (let i = 0; i < opps.length; i += 1) { const state = this.graph.view.getState(opps[i]); - if (state != null && !connected.get(state)) { + if (state && !connected.get(state)) { connected.put(state, true); } } - this.guide.isStateIgnored = (state) => { + this.guide.isStateIgnored = (state: CellState) => { const p = state.cell.getParent(); return ( - state.cell != null && - ((!this.cloning && this.isCellMoving(state.cell)) || + !!state.cell && + ((!this.cloning && !!this.isCellMoving(state.cell)) || (state.cell !== (this.target || parent) && !ignore && !connected.get(state) && - (this.target == null || this.target.getChildCount() >= 2) && + (!this.target || this.target.getChildCount() >= 2) && p !== (this.target || parent))) ); }; @@ -891,12 +884,11 @@ class GraphHandler implements GraphPlugin { * @param cell * @param dict */ - // addStates(cell: mxCell, dict: any): number; - addStates(cell, dict) { + addStates(cell: Cell, dict: Dictionary) { const state = this.graph.view.getState(cell); let count = 0; - if (state != null && dict.get(cell) == null) { + if (state && !dict.get(cell)) { dict.put(cell, state); count++; @@ -915,8 +907,8 @@ class GraphHandler implements GraphPlugin { * * Returns true if the given cell is currently being moved. */ - isCellMoving(cell) { - return this.allCells.get(cell) != null; + isCellMoving(cell: Cell) { + return this.allCells.get(cell); } /** @@ -925,9 +917,8 @@ class GraphHandler implements GraphPlugin { * Returns true if the guides should be used for the given . * This implementation returns . */ - // useGuidesForEvent(me: mxMouseEvent): boolean; - useGuidesForEvent(me) { - return this.guide != null + useGuidesForEvent(me: InternalMouseEvent) { + return this.guide ? this.guide.isEnabledForEvent(me.getEvent()) && !this.graph.isConstrainedEvent(me.getEvent()) : true; @@ -938,8 +929,7 @@ class GraphHandler implements GraphPlugin { * * Snaps the given vector to the grid and returns the given mxPoint instance. */ - // snap(vector: mxPoint): mxPoint; - snap(vector) { + snap(vector: Point) { const scale = this.scaleGrid ? this.graph.view.scale : 1; vector.x = this.graph.snap(vector.x / scale) * scale; @@ -954,13 +944,14 @@ class GraphHandler implements GraphPlugin { * Returns an that represents the vector for moving the cells * for the given . */ - // getDelta(me: mxMouseEvent): mxPoint; - getDelta(me) { + getDelta(me: InternalMouseEvent) { const point = convertPoint(this.graph.container, me.getX(), me.getY()); - return new point( - point.x - this.first.x - this.graph.panDx, - point.y - this.first.y - this.graph.panDy + if (!this.first) return new Point(); + + return new Point( + point.x - this.first.x - this.graph.getPanDx(), + point.y - this.first.y - this.graph.getPanDy() ); } @@ -969,15 +960,13 @@ class GraphHandler implements GraphPlugin { * * Hook for subclassers do show details while the handler is active. */ - // updateHint(me: mxMouseEvent): void; - updateHint(me) {} + updateHint(me?: InternalMouseEvent) {} /** * Function: removeHint * * Hooks for subclassers to hide details when the handler gets inactive. */ - // removeHint(): void; removeHint() {} /** @@ -985,8 +974,7 @@ class GraphHandler implements GraphPlugin { * * Hook for rounding the unscaled vector. This uses Math.round. */ - // roundLength(length: number): number; - roundLength(length) { + roundLength(length: number) { return Math.round(length * 100) / 100; } @@ -995,8 +983,8 @@ class GraphHandler implements GraphPlugin { * * Returns true if the given cell is a valid drop target. */ - isValidDropTarget(target, me) { - return this.cell.getParent() !== target; + isValidDropTarget(target: Cell, me: InternalMouseEvent) { + return this.cell ? this.cell.getParent() !== target : false; } /** @@ -1017,7 +1005,7 @@ class GraphHandler implements GraphPlugin { this.livePreviewActive = true; this.livePreviewUsed = true; } - } else if (!this.livePreviewUsed && this.shape == null) { + } else if (!this.livePreviewUsed && !this.shape && this.bounds) { this.shape = this.createPreviewShape(this.bounds); } } @@ -1028,16 +1016,15 @@ class GraphHandler implements GraphPlugin { * Handles the event by highlighting possible drop targets and updating the * preview. */ - // mouseMove(sender: any, me: mxMouseEvent): void; - mouseMove(sender, me) { + mouseMove(sender: EventSource, me: InternalMouseEvent) { const { graph } = this; if ( !me.isConsumed() && graph.isMouseDown && - this.cell != null && - this.first != null && - this.bounds != null && + this.cell && + this.first && + this.bounds && !this.suspended ) { // Stops moving if a multi touch event is received @@ -1047,16 +1034,16 @@ class GraphHandler implements GraphPlugin { } let delta = this.getDelta(me); - const tol = graph.tolerance; + const tol = graph.getEventTolerance(); if ( - this.shape != null || + this.shape || this.livePreviewActive || Math.abs(delta.x) > tol || Math.abs(delta.y) > tol ) { // Highlight is used for highlighting drop targets - if (this.highlight == null) { + if (!this.highlight) { this.highlight = new CellHighlight(this.graph, DROP_TARGET_COLOR, 3); } @@ -1067,18 +1054,18 @@ class GraphHandler implements GraphPlugin { const gridEnabled = graph.isGridEnabledEvent(me.getEvent()); const cell = me.getCell(); let hideGuide = true; - let target = null; + let target: Cell | null = null; this.cloning = clone; - if (graph.isDropEnabled() && this.highlightEnabled) { + if (graph.isDropEnabled() && this.highlightEnabled && this.cells) { // Contains a call to getCellAt to find the cell under the mouse target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone); } - let state = graph.getView().getState(target); + let state = target ? graph.getView().getState(target) : null; let highlight = false; - if (state != null && (clone || this.isValidDropTarget(target, me))) { + if (state && (clone || (target && this.isValidDropTarget(target, me)))) { if (this.target !== target) { this.target = target; this.setHighlightColor(DROP_TARGET_COLOR); @@ -1090,14 +1077,15 @@ class GraphHandler implements GraphPlugin { if ( this.connectOnDrop && - cell != null && + cell && + this.cells && this.cells.length === 1 && cell.isVertex() && cell.isConnectable() ) { state = graph.getView().getState(cell); - if (state != null) { + if (state) { const error = graph.getEdgeValidationError(null, this.cell, cell); const color = error == null ? VALID_COLOR : INVALID_CONNECT_TARGET_COLOR; this.setHighlightColor(color); @@ -1156,9 +1144,10 @@ class GraphHandler implements GraphPlugin { !graph.isMouseDown ) { let cursor = graph.getCursorForMouseEvent(me); + const cell = me.getCell(); - if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell())) { - if (me.getCell().isEdge()) { + if (!cursor && cell && graph.isEnabled() && graph.isCellMovable(cell)) { + if (cell.isEdge()) { cursor = CURSOR_MOVABLE_EDGE; } else { cursor = CURSOR_MOVABLE_VERTEX; @@ -1167,7 +1156,7 @@ class GraphHandler implements GraphPlugin { // Sets the cursor on the original source state under the mouse // instead of the event source state which can be the parent - if (cursor != null && me.sourceState != null) { + if (cursor && me.sourceState) { me.sourceState.setCursor(cursor); } } @@ -1178,11 +1167,15 @@ class GraphHandler implements GraphPlugin { * * Updates the bounds of the preview shape. */ - updatePreview(remote) { + updatePreview(remote = false) { if (this.livePreviewUsed && !remote) { - if (this.cells != null) { + if (this.cells) { + const selectionCellsHandler = this.graph.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + this.setHandlesVisibleForCells( - this.graph.selectionCellsHandler.getHandledSelectionCells(), + selectionCellsHandler.getHandledSelectionCells(), false ); this.updateLivePreview(this.currentDx, this.currentDy); @@ -1197,9 +1190,8 @@ class GraphHandler implements GraphPlugin { * * Updates the bounds of the preview shape. */ - // updatePreviewShape(): void; updatePreviewShape() { - if (this.shape != null && this.pBounds != null) { + if (this.shape && this.pBounds) { this.shape.bounds = new Rectangle( Math.round(this.pBounds.x + this.currentDx), Math.round(this.pBounds.y + this.currentDy), @@ -1215,19 +1207,19 @@ class GraphHandler implements GraphPlugin { * * Updates the bounds of the preview shape. */ - updateLivePreview(dx, dy) { + updateLivePreview(dx: number, dy: number) { if (!this.suspended) { - const states = []; + const states: CellState[][] = []; if (this.allCells != null) { - this.allCells.visit((key, state) => { - const realState = this.graph.view.getState(state.cell); + this.allCells.visit((key, state: CellState | null) => { + const realState = state ? this.graph.view.getState(state.cell) : null; // Checks if cell was removed or replaced - if (realState !== state) { + if (realState !== state && state) { state.destroy(); - if (realState != null) { + if (realState) { this.allCells.put(state.cell, realState); } else { this.allCells.remove(state.cell); @@ -1236,21 +1228,21 @@ class GraphHandler implements GraphPlugin { state = realState; } - if (state != null) { + if (state) { // Saves current state const tempState = state.clone(); states.push([state, tempState]); // Makes transparent for events to detect drop targets - if (state.shape != null) { - if (state.shape.originalPointerEvents == null) { + if (state.shape) { + if (state.shape.originalPointerEvents === null) { state.shape.originalPointerEvents = state.shape.pointerEvents; } state.shape.pointerEvents = false; - if (state.text != null) { - if (state.text.originalPointerEvents == null) { + if (state.text) { + if (state.text.originalPointerEvents === null) { state.text.originalPointerEvents = state.text.pointerEvents; } @@ -1311,9 +1303,9 @@ class GraphHandler implements GraphPlugin { const geometry = state.cell.getGeometry(); const points = []; - if (geometry != null && geometry.points != null) { + if (geometry && geometry.points) { for (let j = 0; j < geometry.points.length; j++) { - if (geometry.points[j] != null) { + if (geometry.points[j]) { points.push( new Point( geometry.points[j].x + dx / s, @@ -1330,8 +1322,11 @@ class GraphHandler implements GraphPlugin { if (source == null || !this.isCellMoving(source.cell)) { const pt0 = pts[0]; - state.setAbsoluteTerminalPoint(new Point(pt0.x + dx, pt0.y + dy), true); - source = null; + + if (pt0) { + state.setAbsoluteTerminalPoint(new Point(pt0.x + dx, pt0.y + dy), true); + source = null; + } } else { state.view.updateFixedTerminalPoint( state, @@ -1343,8 +1338,11 @@ class GraphHandler implements GraphPlugin { if (target == null || !this.isCellMoving(target.cell)) { const ptn = pts[pts.length - 1]; - state.setAbsoluteTerminalPoint(new Point(ptn.x + dx, ptn.y + dy), false); - target = null; + + if (ptn) { + state.setAbsoluteTerminalPoint(new Point(ptn.x + dx, ptn.y + dy), false); + target = null; + } } else { state.view.updateFixedTerminalPoint( state, @@ -1378,9 +1376,13 @@ class GraphHandler implements GraphPlugin { * * Redraws the preview shape for the given states array. */ - redrawHandles(states) { + redrawHandles(states: CellState[][]) { + const selectionCellsHandler = this.graph.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + for (let i = 0; i < states.length; i += 1) { - const handler = this.graph.selectionCellsHandler.getHandler(states[i][0].cell); + const handler = selectionCellsHandler.getHandler(states[i][0].cell); if (handler != null) { handler.redraw(true); @@ -1393,7 +1395,7 @@ class GraphHandler implements GraphPlugin { * * Resets the given preview states array. */ - resetPreviewStates(states) { + resetPreviewStates(states: CellState[][]) { for (let i = 0; i < states.length; i += 1) { states[i][0].setState(states[i][1]); } @@ -1410,11 +1412,11 @@ class GraphHandler implements GraphPlugin { this.updateLivePreview(0, 0); } - if (this.shape != null) { + if (this.shape) { this.shape.node.style.visibility = 'hidden'; } - if (this.guide != null) { + if (this.guide) { this.guide.setVisible(false); } @@ -1429,17 +1431,17 @@ class GraphHandler implements GraphPlugin { */ resume() { if (this.suspended) { - this.suspended = null; + this.suspended = false; if (this.livePreviewUsed) { this.livePreviewActive = true; } - if (this.shape != null) { + if (this.shape) { this.shape.node.style.visibility = 'visible'; } - if (this.guide != null) { + if (this.guide) { this.guide.setVisible(true); } } @@ -1451,45 +1453,43 @@ class GraphHandler implements GraphPlugin { * Resets the livew preview. */ resetLivePreview() { - if (this.allCells != null) { - this.allCells.visit((key, state) => { - // Restores event handling - if (state.shape != null && state.shape.originalPointerEvents != null) { - state.shape.pointerEvents = state.shape.originalPointerEvents; - state.shape.originalPointerEvents = null; + this.allCells.visit((key, state) => { + // Restores event handling + if (state.shape && state.shape.originalPointerEvents !== null) { + state.shape.pointerEvents = state.shape.originalPointerEvents; + state.shape.originalPointerEvents = null; - // Forces repaint even if not moved to update pointer events - state.shape.bounds = null; + // Forces repaint even if not moved to update pointer events + state.shape.bounds = null; - if (state.text != null) { - state.text.pointerEvents = state.text.originalPointerEvents; - state.text.originalPointerEvents = null; - } + if (state.text && state.text.originalPointerEvents !== null) { + state.text.pointerEvents = state.text.originalPointerEvents; + state.text.originalPointerEvents = null; } + } - // Shows folding icon - if ( - state.control != null && - state.control.node != null && - state.control.node.style.visibility === 'hidden' - ) { - state.control.node.style.visibility = ''; + // Shows folding icon + if ( + state.control != null && + state.control.node != null && + state.control.node.style.visibility === 'hidden' + ) { + state.control.node.style.visibility = ''; + } + + // Fixes preview box for edge labels + if (!this.cloning) { + if (state.text !== null) { + state.text.updateBoundingBox(); } + } - // Fixes preview box for edge labels - if (!this.cloning) { - if (state.text != null) { - state.text.updateBoundingBox(); - } - } + // Forces repaint of connected edges + state.view.invalidate(state.cell); + }); - // Forces repaint of connected edges - state.view.invalidate(state.cell); - }); - - // Repaints all invalid states - this.graph.view.validate(); - } + // Repaints all invalid states + this.graph.view.validate(); } /** @@ -1503,12 +1503,16 @@ class GraphHandler implements GraphPlugin { * visible - Boolean that specifies if the handles should be visible. * force - Forces an update of the handler regardless of the last used value. */ - setHandlesVisibleForCells(cells, visible, force) { + setHandlesVisibleForCells(cells: CellArray, visible: boolean, force = false) { if (force || this.handlesVisible !== visible) { this.handlesVisible = visible; + const selectionCellsHandler = this.graph.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + for (let i = 0; i < cells.length; i += 1) { - const handler = this.graph.selectionCellsHandler.getHandler(cells[i]); + const handler = selectionCellsHandler.getHandler(cells[i]); if (handler != null) { handler.setHandlesVisible(visible); @@ -1530,9 +1534,8 @@ class GraphHandler implements GraphPlugin { * * color - String that represents the new highlight color. */ - // setHighlightColor(color: string): void; - setHighlightColor(color) { - if (this.highlight != null) { + setHighlightColor(color: ColorValue) { + if (this.highlight) { this.highlight.setHighlightColor(color); } } @@ -1542,33 +1545,35 @@ class GraphHandler implements GraphPlugin { * * Handles the event by applying the changes to the selection cells. */ - // mouseUp(sender: any, me: mxMouseEvent): void; - mouseUp(sender, me) { + mouseUp(sender: EventSource, me: InternalMouseEvent) { if (!me.isConsumed()) { if (this.livePreviewUsed) { this.resetLivePreview(); } if ( - this.cell != null && - this.first != null && - (this.shape != null || this.livePreviewUsed) && - this.currentDx != null && - this.currentDy != null + this.cell && + this.first && + (this.shape || this.livePreviewUsed) && + this.currentDx && + this.currentDy ) { const { graph } = this; const cell = me.getCell(); if ( this.connectOnDrop && - this.target == null && - cell != null && + !this.target && + cell && cell.isVertex() && cell.isConnectable() && graph.isEdgeValid(null, this.cell, cell) ) { - alert('CONNECT'); - graph.connectionHandler.connect(this.cell, cell, me.getEvent()); + const connectionHandler = graph.getPlugin( + 'ConnectionHandler' + ) as ConnectionHandler; + + connectionHandler.connect(this.cell, cell, me.getEvent()); } else { const clone = graph.isCloneEvent(me.getEvent()) && @@ -1582,6 +1587,7 @@ class GraphHandler implements GraphPlugin { if ( target && graph.isSplitEnabled() && + this.cells && graph.isSplitTarget(target, this.cells, me.getEvent()) ) { graph.splitEdge( @@ -1593,7 +1599,7 @@ class GraphHandler implements GraphPlugin { me.getGraphX(), me.getGraphY() ); - } else { + } else if (this.cells) { this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent()); } } @@ -1619,8 +1625,13 @@ class GraphHandler implements GraphPlugin { reset() { if (this.livePreviewUsed) { this.resetLivePreview(); + + const selectionCellsHandler = this.graph.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + this.setHandlesVisibleForCells( - this.graph.selectionCellsHandler.getHandledSelectionCells(), + selectionCellsHandler.getHandledSelectionCells(), true ); } @@ -1629,17 +1640,16 @@ class GraphHandler implements GraphPlugin { this.removeHint(); this.delayedSelection = false; - this.livePreviewActive = null; - this.livePreviewUsed = null; + this.livePreviewActive = false; + this.livePreviewUsed = false; this.cellWasClicked = false; - this.suspended = null; - this.currentDx = null; - this.currentDy = null; - this.cellCount = null; + this.suspended = false; + this.currentDx = 0; + this.currentDy = 0; + this.cellCount = 0; this.cloning = false; - this.allCells = null; + this.allCells.clear(); this.pBounds = null; - this.guides = null; this.target = null; this.first = null; this.cells = null; @@ -1652,15 +1662,14 @@ class GraphHandler implements GraphPlugin { * Returns true if the given cells should be removed from the parent for the specified * mousereleased event. */ - // shouldRemoveCellsFromParent(parent: mxCell, cells: mxCell[], evt: Event): boolean; - shouldRemoveCellsFromParent(parent, cells, evt) { + shouldRemoveCellsFromParent(parent: Cell, cells: CellArray, evt: MouseEvent) { if (parent.isVertex()) { const pState = this.graph.getView().getState(parent); - if (pState != null) { + if (pState) { let pt = convertPoint(this.graph.container, getClientX(evt), getClientY(evt)); - const alpha = toRadians(getValue(pState.style, 'rotation') || 0); + const alpha = toRadians(pState.style.rotation ?? 0); if (alpha !== 0) { const cos = Math.cos(-alpha); @@ -1681,8 +1690,16 @@ class GraphHandler implements GraphPlugin { * * Moves the given cells by the specified amount. */ - // moveCells(cells: mxCell[], dx: number, dy: number, clone: boolean, target: mxCell, evt: Event): void; - moveCells(cells, dx, dy, clone, target, evt) { + moveCells( + cells: CellArray, + dx: number, + dy: number, + clone: boolean, + target: Cell | null, + evt: MouseEvent + ) { + if (!this.cell) return; + if (clone) { cells = this.graph.getCloneableCells(cells); } @@ -1706,7 +1723,7 @@ class GraphHandler implements GraphPlugin { const parents = []; // Removes parent if all child cells are removed - if (!clone && target != null && this.removeEmptyParents) { + if (!clone && target && this.removeEmptyParents) { // Collects all non-selected parents const dict = new Dictionary(); @@ -1718,7 +1735,7 @@ class GraphHandler implements GraphPlugin { for (let i = 0; i < cells.length; i += 1) { const par = cells[i].getParent(); - if (par != null && !dict.get(par)) { + if (par && !dict.get(par)) { dict.put(par, true); parents.push(par); } @@ -1738,7 +1755,7 @@ class GraphHandler implements GraphPlugin { } } - this.graph.removeCells(temp, false); + this.graph.removeCells(new CellArray(...temp), false); } finally { this.graph.getModel().endUpdate(); } @@ -1758,7 +1775,7 @@ class GraphHandler implements GraphPlugin { * * Returns true if the given parent should be removed after removal of child cells. */ - shouldRemoveParent(parent) { + shouldRemoveParent(parent: Cell) { const state = this.graph.view.getState(parent); return ( @@ -1778,12 +1795,12 @@ class GraphHandler implements GraphPlugin { // destroyShapes(): void; destroyShapes() { // Destroys the preview dashed rectangle - if (this.shape != null) { + if (this.shape) { this.shape.destroy(); this.shape = null; } - if (this.guide != null) { + if (this.guide) { this.guide.destroy(); this.guide = null; } @@ -1804,17 +1821,10 @@ class GraphHandler implements GraphPlugin { onDestroy() { this.graph.removeMouseListener(this); this.graph.removeListener(this.panHandler); + this.graph.removeListener(this.escapeHandler); - if (this.escapeHandler != null) { - this.graph.removeListener(this.escapeHandler); - this.escapeHandler = null; - } - - if (this.refreshHandler != null) { - this.graph.getModel().removeListener(this.refreshHandler); - this.graph.removeListener(this.refreshHandler); - this.refreshHandler = null; - } + this.graph.getModel().removeListener(this.refreshHandler); + this.graph.removeListener(this.refreshHandler); InternalEvent.removeListener(document, 'keydown', this.keyHandler); InternalEvent.removeListener(document, 'keyup', this.keyHandler); diff --git a/packages/core/src/view/cell/CellMarker.ts b/packages/core/src/view/cell/CellMarker.ts index 857d13de3..036c4d121 100644 --- a/packages/core/src/view/cell/CellMarker.ts +++ b/packages/core/src/view/cell/CellMarker.ts @@ -129,7 +129,7 @@ class CellMarker extends EventSource { * * Holds the current marker color. */ - currentColor: ColorValue | null = null; + currentColor: ColorValue = NONE; /** * Variable: validState diff --git a/packages/core/src/view/cell/CellRenderer.ts b/packages/core/src/view/cell/CellRenderer.ts index 7f13a5b3f..b924b99b4 100644 --- a/packages/core/src/view/cell/CellRenderer.ts +++ b/packages/core/src/view/cell/CellRenderer.ts @@ -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 - 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 - 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,50 +863,52 @@ 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; }; - InternalEvent.addGestureListeners( - // @ts-ignore - state.shape.node, - (evt: Event) => { - if (this.isShapeEvent(state, evt as MouseEvent)) { - graph.fireMouseEvent( - InternalEvent.MOUSE_DOWN, - new InternalMouseEvent(evt as MouseEvent, state) - ); + if (state.shape) { + InternalEvent.addGestureListeners( + state.shape.node, + (evt: MouseEvent) => { + if (this.isShapeEvent(state, evt)) { + graph.fireMouseEvent( + InternalEvent.MOUSE_DOWN, + new InternalMouseEvent(evt, state) + ); + } + }, + (evt: MouseEvent) => { + if (this.isShapeEvent(state, evt)) { + graph.fireMouseEvent( + InternalEvent.MOUSE_MOVE, + new InternalMouseEvent(evt, getState(evt)) + ); + } + }, + (evt: MouseEvent) => { + if (this.isShapeEvent(state, evt)) { + graph.fireMouseEvent( + InternalEvent.MOUSE_UP, + new InternalMouseEvent(evt, getState(evt)) + ); + } } - }, - (evt: Event) => { - if (this.isShapeEvent(state, evt as MouseEvent)) { - graph.fireMouseEvent( - InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt as MouseEvent, getState(evt)) - ); - } - }, - (evt: Event) => { - if (this.isShapeEvent(state, evt as MouseEvent)) { - graph.fireMouseEvent( - InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt as MouseEvent, getState(evt)) - ); - } - } - ); + ); - // 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.consume(evt); - } - }); + // Uses double click timeout in mxGraph for quirks mode + if (graph.isNativeDblClickEnabled()) { + InternalEvent.addListener(state.shape.node, 'dblclick', (evt: MouseEvent) => { + if (this.isShapeEvent(state, evt)) { + graph.dblClick(evt, state.cell); + InternalEvent.consume(evt); + } + }); + } } } diff --git a/packages/core/src/view/cell/GraphCells.ts b/packages/core/src/view/cell/GraphCells.ts index 5808a94f4..ad58addd8 100644 --- a/packages/core/src/view/cell/GraphCells.ts +++ b/packages/core/src/view/cell/GraphCells.ts @@ -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() { @@ -1575,7 +1579,7 @@ class GraphCells extends autoImplement() { 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() { 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) { diff --git a/packages/core/src/view/cell/datatypes/CellState.ts b/packages/core/src/view/cell/datatypes/CellState.ts index 8b9ec62a8..824e0cd5e 100644 --- a/packages/core/src/view/cell/datatypes/CellState.ts +++ b/packages/core/src/view/cell/datatypes/CellState.ts @@ -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); diff --git a/packages/core/src/view/cell/edge/EdgeHandler.ts b/packages/core/src/view/cell/edge/EdgeHandler.ts index 00d3b2f57..29b7d3260 100644 --- a/packages/core/src/view/cell/edge/EdgeHandler.ts +++ b/packages/core/src/view/cell/edge/EdgeHandler.ts @@ -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 used for drawing and highlighting * constraints. */ - constraintHandler: ConstraintHandler = null; + constraintHandler: ConstraintHandler; /** * Variable: error @@ -207,7 +211,7 @@ class EdgeHandler { * * Holds the that represents the preview edge. */ - shape: Shape | null = null; + shape: Shape; /** * Variable: bends @@ -223,7 +227,7 @@ class EdgeHandler { * * Holds the 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 - 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 - that contains the current pointer position. * me - Optional 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,34 +1791,21 @@ 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(); - } + this.constraintHandler.reset(); - if (this.customHandles != null) { - for (let i = 0; i < this.customHandles.length; i += 1) { - this.customHandles[i].reset(); - } + for (let i = 0; i < this.customHandles.length; i += 1) { + this.customHandles[i].reset(); } this.setPreviewColor(EDGE_SELECTION_COLOR); @@ -1829,9 +1819,7 @@ class EdgeHandler { * Sets the color of the preview to the given value. */ setPreviewColor(color: ColorValue) { - if (this.shape != null) { - this.shape.stroke = color; - } + this.shape.stroke = color; } /** @@ -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,56 +2211,54 @@ 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; - b.bounds = new Rectangle( - Math.floor(x - b.bounds.width / 2), - Math.floor(y - b.bounds.height / 2), - b.bounds.width, - b.bounds.height - ); - b.redraw(); + + if (b.bounds) { + b.bounds = new Rectangle( + Math.floor(x - b.bounds.width / 2), + Math.floor(y - b.bounds.height / 2), + b.bounds.width, + 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(); - } + 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; + for (let i = 0; i < this.customHandles.length; i += 1) { + 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,26 +2280,18 @@ class EdgeHandler { * Shortcut to . */ 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'; - } + 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'; - } + 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'; - } + 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); - } + for (let i = 0; i < this.customHandles.length; i += 1) { + this.customHandles[i].setVisible(visible); } } @@ -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,15 +2356,13 @@ 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()) { - b2.y = b.y + b.height; - } else { - b2.y = b.y - b2.height; - } + if (intersects(b, b2)) { + if (b.getCenterY() < b2.getCenterY()) { + b2.y = b.y + b.height; + } else { + b2.y = b.y - b2.height; } } } @@ -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(); - } + this.destroyBends(this.bends); + this.bends = this.createBends(); - if (this.virtualBends != null) { - this.destroyBends(this.virtualBends); - this.virtualBends = this.createVirtualBends(); - } + this.destroyBends(this.virtualBends); + this.virtualBends = this.createVirtualBends(); - if (this.customHandles != null) { - this.destroyBends(this.customHandles); - this.customHandles = this.createCustomHandles(); - } + 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 . */ - 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; - } + this.state.view.graph.removeListener(this.escapeHandler); - if (this.marker != null) { - this.marker.destroy(); - this.marker = null; - } + this.marker.destroy(); - if (this.shape != null) { - this.shape.destroy(); - this.shape = null; - } + this.shape.destroy(); - 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; - } + this.labelShape.destroy(); - if (this.constraintHandler != null) { - this.constraintHandler.destroy(); - this.constraintHandler = null; - } + this.constraintHandler.destroy(); this.destroyBends(this.virtualBends); - this.virtualBends = null; + this.virtualBends = []; this.destroyBends(this.customHandles); - this.customHandles = null; + this.customHandles = []; this.destroyBends(this.bends); this.bends = []; diff --git a/packages/core/src/view/cell/edge/EdgeSegmentHandler.ts b/packages/core/src/view/cell/edge/EdgeSegmentHandler.ts index 7d7ee0ec1..a67c06711 100644 --- a/packages/core/src/view/cell/edge/EdgeSegmentHandler.ts +++ b/packages/core/src/view/cell/edge/EdgeSegmentHandler.ts @@ -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); + // 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 === 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))) - ) { - 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; + if ( + (pts.length === 2 && pts[0] && pts[1]) || + (pts.length === 3 && + 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; - 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, - point: Point, - terminalState: CellState, - me: MouseEvent): void { - + updatePreviewState( + edge: CellState, + point: Point, + terminalState: CellState, + 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, - terminal: Cell, - isSource: boolean, - isClone: boolean, - me: MouseEvent): Cell { - + connect( + edge: Cell, + terminal: Cell, + isSource: boolean, + isClone: boolean, + 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 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); } } } diff --git a/packages/core/src/view/cell/edge/ElbowEdgeHandler.ts b/packages/core/src/view/cell/edge/ElbowEdgeHandler.ts index e2161b88b..161d8ff3b 100644 --- a/packages/core/src/view/cell/edge/ElbowEdgeHandler.ts +++ b/packages/core/src/view/cell/edge/ElbowEdgeHandler.ts @@ -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 - 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 * . 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 * . */ - // 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 - 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 - that represents the location of the first point. * pe - 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; diff --git a/packages/core/src/view/cell/edge/GraphEdge.ts b/packages/core/src/view/cell/edge/GraphEdge.ts index 91ed278e7..45f4c773f 100644 --- a/packages/core/src/view/cell/edge/GraphEdge.ts +++ b/packages/core/src/view/cell/edge/GraphEdge.ts @@ -232,7 +232,7 @@ class GraphEdge extends autoImplement() { splitEdge( edge: Cell, cells: CellArray, - newEdge: Cell, + newEdge: Cell | null, dx = 0, dy = 0, x: number, @@ -311,7 +311,7 @@ class GraphEdge extends autoImplement() { 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 diff --git a/packages/core/src/view/cell/vertex/GraphVertex.ts b/packages/core/src/view/cell/vertex/GraphVertex.ts index c05c0779a..10d595fd1 100644 --- a/packages/core/src/view/cell/vertex/GraphVertex.ts +++ b/packages/core/src/view/cell/vertex/GraphVertex.ts @@ -13,13 +13,27 @@ class GraphVertex extends autoImplement() { * 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() { * 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() { * * @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() { /** * Returns {@link vertexLabelsMovable}. */ - isVertexLabelsMovable(): boolean { + isVertexLabelsMovable() { return this.vertexLabelsMovable; } /** * Sets {@link vertexLabelsMovable}. */ - setVertexLabelsMovable(value: boolean): void { + setVertexLabelsMovable(value: boolean) { this.vertexLabelsMovable = value; } } diff --git a/packages/core/src/view/cell/vertex/VertexHandler.ts b/packages/core/src/view/cell/vertex/VertexHandler.ts index c83b5faa5..19a2eb3df 100644 --- a/packages/core/src/view/cell/vertex/VertexHandler.ts +++ b/packages/core/src/view/cell/vertex/VertexHandler.ts @@ -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); } } } diff --git a/packages/core/src/view/connection/ConnectionHandler.ts b/packages/core/src/view/connection/ConnectionHandler.ts index 5c91bc4b1..80e60f896 100644 --- a/packages/core/src/view/connection/ConnectionHandler.ts +++ b/packages/core/src/view/connection/ConnectionHandler.ts @@ -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 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 = 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,38 +1357,40 @@ 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; } } - const dx = current.x - tmp.x; - const dy = current.y - tmp.y; + if (tmp) { + const dx = current.x - tmp.x; + const dy = current.y - tmp.y; - const len = Math.sqrt(dx * dx + dy * dy); + const len = Math.sqrt(dx * dx + dy * dy); - if (len === 0) { - return; + if (len === 0) { + return; + } + + // Stores old point to reuse when creating edge + this.originalPoint = current.clone(); + current.x -= (dx * 4) / len; + current.y -= (dy * 4) / len; } - - // Stores old point to reuse when creating edge - 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 . */ - 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,16 +1536,12 @@ class ConnectionHandler extends EventSource { } // Scales and translates the waypoints to the model - let realPoints = null; + let realPoints = []; - if (this.waypoints != null) { - realPoints = []; - - for (let i = 0; i < this.waypoints.length; i += 1) { - const pt = this.waypoints[i].clone(); - this.convertWaypoint(pt); - realPoints[i] = pt; - } + 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( @@ -1561,14 +1567,14 @@ class ConnectionHandler extends EventSource { * state - that represents the target cell state. * me - 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 - that represents the next point along the previewed edge. * me - 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 - 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 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 . */ - 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,37 +1888,47 @@ class ConnectionHandler extends EventSource { * dropTarget - 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(); - tmp.x -= pstate.origin.x; - tmp.y -= pstate.origin.y; + + 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 - 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 - 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 . */ - 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; } } } diff --git a/packages/core/src/view/connection/ConstraintHandler.ts b/packages/core/src/view/connection/ConstraintHandler.ts index b68ba6cae..d240e7deb 100644 --- a/packages/core/src/view/connection/ConstraintHandler.ts +++ b/packages/core/src/view/connection/ConstraintHandler.ts @@ -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; - } + this.graph.model.removeListener(this.resetHandler); + this.graph.view.removeListener(this.resetHandler); + this.graph.removeListener(this.resetHandler); if (this.mouseleaveHandler != null && this.graph.container != null) { InternalEvent.removeListener( @@ -492,4 +508,4 @@ class ConstraintHandler { } } -export default mxConstraintHandler; +export default ConstraintHandler; diff --git a/packages/core/src/view/connection/GraphConnections.ts b/packages/core/src/view/connection/GraphConnections.ts index eddf237f2..4f8760d39 100644 --- a/packages/core/src/view/connection/GraphConnections.ts +++ b/packages/core/src/view/connection/GraphConnections.ts @@ -202,7 +202,7 @@ class GraphConnections extends autoImplement() { */ setConnectionConstraint( edge: Cell, - terminal: Cell, + terminal: Cell | null, source: boolean = false, constraint: ConnectionConstraint | null = null ) { @@ -380,7 +380,7 @@ class GraphConnections extends autoImplement() { */ connectCell( edge: Cell, - terminal: Cell, + terminal: Cell | null = null, source: boolean = false, constraint: ConnectionConstraint | null = null ) { @@ -419,7 +419,7 @@ class GraphConnections extends autoImplement() { */ cellConnected( edge: Cell, - terminal: Cell, + terminal: Cell | null, source: boolean = false, constraint: ConnectionConstraint | null = null ) { @@ -435,9 +435,9 @@ class GraphConnections extends autoImplement() { if (this.isPortsEnabled()) { let id = null; - if (this.isPort(terminal)) { + if (terminal && this.isPort(terminal)) { id = terminal.getId(); - terminal = this.getTerminalForPort(terminal, source); + terminal = this.getTerminalForPort(terminal, source); } // Sets or resets all previous information for connecting to a child port diff --git a/packages/core/src/view/drag_drop/DragSource.ts b/packages/core/src/view/drag_drop/DragSource.ts index fd1a56fbd..51a307bde 100644 --- a/packages/core/src/view/drag_drop/DragSource.ts +++ b/packages/core/src/view/drag_drop/DragSource.ts @@ -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 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,12 +316,15 @@ class DragSource { if (mxClient.IS_TOUCH && !isMouseEvent(evt)) { this.eventSource = getSource(evt); - InternalEvent.addGestureListeners( - this.eventSource, - null, - this.mouseMoveHandler, - this.mouseUpHandler - ); + + if (this.eventSource) { + InternalEvent.addGestureListeners( + this.eventSource, + null, + this.mouseMoveHandler, + this.mouseUpHandler + ); + } } } } @@ -331,11 +332,10 @@ 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); - const state = graph.getView().getState(this.currentDropTarget); - this.currentHighlight.highlight(state); + + 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 diff --git a/packages/core/src/view/drag_drop/GraphDragDrop.ts b/packages/core/src/view/drag_drop/GraphDragDrop.ts index 89f9371fa..8d2bc1296 100644 --- a/packages/core/src/view/drag_drop/GraphDragDrop.ts +++ b/packages/core/src/view/drag_drop/GraphDragDrop.ts @@ -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; +type PartialClass = PartialValidation; + +class GraphDragDrop extends autoImplement() { /** * 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 = target.getTerminal(true); - const trg = 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; } diff --git a/packages/core/src/view/editing/CellEditor.ts b/packages/core/src/view/editing/CellEditor.ts index 879fde6ad..f5ccbec55 100644 --- a/packages/core/src/view/editing/CellEditor.ts +++ b/packages/core/src/view/editing/CellEditor.ts @@ -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 . */ -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 . */ - // 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 . */ - // textarea: Element; textarea: HTMLElement | null = null; /** @@ -335,8 +340,7 @@ class CellEditor { * Creates the