diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9d325b0af..c75ba899d 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,9 +1,9 @@ import type Cell from './view/cell/datatypes/Cell'; -import CellState from './view/cell/datatypes/CellState'; -import RectangleShape from './view/geometry/shape/node/RectangleShape'; +import type CellState from './view/cell/datatypes/CellState'; +import type InternalMouseEvent from './view/event/InternalMouseEvent'; import type Shape from './view/geometry/shape/Shape'; -import type Graph from './view/Graph'; -import ImageBox from './view/image/ImageBox'; +import type { MaxGraph } from './view/Graph'; +import type ImageBox from './view/image/ImageBox'; export type CellMap = { [id: string]: Cell; @@ -19,10 +19,6 @@ export type UndoableChange = { export type StyleValue = string | number; -export type StyleProperties = { - [k: string]: StyleValue; -}; - export type Properties = { [k: string]: any; }; @@ -30,6 +26,7 @@ export type Properties = { export type CellStateStyles = { absoluteArcSize: number; align: AlignValue; + anchorPointDirection: boolean; arcSize: number; aspect: string; autosize: boolean; @@ -49,8 +46,14 @@ export type CellStateStyles = { endArrow: ArrowType; endFill: boolean; endSize: number; + entryDx: number; + entryDy: number; + entryPerimeter: boolean; entryX: number; entryY: number; + exitDx: number; + exitDy: number; + exitPerimeter: boolean; exitX: number; exitY: number; fillColor: ColorValue; @@ -75,11 +78,15 @@ export type CellStateStyles = { imageHeight: number; imageWidth: number; indicatorColor: ColorValue; + indicatorDirection: DirectionValue; indicatorHeight: number; indicatorImage: string; - indicatorShape: Shape; + indicatorShape: string; + indicatorStrokeColor: ColorValue; indicatorWidth: number; + labelBackgroundColor: ColorValue; labelBorderColor: ColorValue; + labelPadding: number; labelPosition: AlignValue; loop: Function; margin: number; @@ -206,8 +213,12 @@ export type GradientMap = { [k: string]: Gradient; }; +export interface GraphPluginConstructor { + new (graph: MaxGraph): GraphPlugin; + pluginId: string; +} + export interface GraphPlugin { - onInit: (graph: Graph) => void; onDestroy: () => void; } @@ -215,14 +226,16 @@ export interface GraphPlugin { export type Listener = { name: string; - f: EventListener; + f: MouseEventListener; }; export type ListenerTarget = { mxListenerList?: Listener[]; }; -export type Listenable = (Node | Window) & ListenerTarget; +export type Listenable = (EventSource | EventTarget) & ListenerTarget; + +export type MouseEventListener = (me: MouseEvent) => void; export type GestureEvent = Event & MouseEvent & { @@ -230,6 +243,12 @@ export type GestureEvent = Event & pointerId?: number; }; +export type MouseListenerSet = { + mouseDown: (sender: EventSource, me: InternalMouseEvent) => void; + mouseMove: (sender: EventSource, me: InternalMouseEvent) => void; + mouseUp: (sender: EventSource, me: InternalMouseEvent) => void; +}; + export type EventCache = GestureEvent[]; export interface CellHandle { @@ -237,5 +256,12 @@ export interface CellHandle { cursor: string; image: ImageBox | null; shape: Shape | null; + active: boolean; setVisible: (v: boolean) => void; + processEvent: (me: InternalMouseEvent) => void; + positionChanged: () => void; + execute: (me: InternalMouseEvent) => void; + reset: () => void; + redraw: () => void; + destroy: () => void; } diff --git a/packages/core/src/util/EventUtils.js b/packages/core/src/util/EventUtils.js deleted file mode 100644 index 4fcb5a0a1..000000000 --- a/packages/core/src/util/EventUtils.js +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Returns the touch or mouse event that contains the mouse coordinates. - */ - -import mxClient from "../mxClient"; - -// static getMainEvent(e: MouseEvent): MouseEvent; -export const getMainEvent = (e) => { - if ( - (e.type == 'touchstart' || e.type == 'touchmove') && - e.touches != null && - e.touches[0] != null - ) { - e = e.touches[0]; - } else if ( - e.type == 'touchend' && - e.changedTouches != null && - e.changedTouches[0] != null - ) { - e = e.changedTouches[0]; - } - return e; -}; - -/** - * Returns true if the meta key is pressed for the given event. - */ -// static getClientX(e: TouchEvent | MouseEvent): number; -export const getClientX = (e) => { - return getMainEvent(e).clientX; -}; - -/** - * Returns true if the meta key is pressed for the given event. - */ -// static getClientY(e: TouchEvent | MouseEvent): number; -export const getClientY = (e) => { - return getMainEvent(e).clientY; -}; - -/** - * Function: getSource - * - * Returns the event's target or srcElement depending on the browser. - */ -export const getSource = (evt) => { - return evt.srcElement != null ? evt.srcElement : evt.target; -}; - -/** - * Returns true if the event has been consumed using {@link consume}. - */ -// static isConsumed(evt: mxEventObject | mxMouseEvent | Event): boolean; -export const isConsumed = (evt) => { - return evt.isConsumed != null && evt.isConsumed; -}; - -/** - * Returns true if the event was generated using a touch device (not a pen or mouse). - */ -// static isTouchEvent(evt: Event): boolean; -export const isTouchEvent = (evt) => { - return evt.pointerType != null - ? evt.pointerType == 'touch' || - evt.pointerType === evt.MSPOINTER_TYPE_TOUCH - : evt.mozInputSource != null - ? evt.mozInputSource == 5 - : evt.type.indexOf('touch') == 0; -}; - -/** - * Returns true if the event was generated using a pen (not a touch device or mouse). - */ -// static isPenEvent(evt: Event): boolean; -export const isPenEvent = (evt) => { - return evt.pointerType != null - ? evt.pointerType == 'pen' || evt.pointerType === evt.MSPOINTER_TYPE_PEN - : evt.mozInputSource != null - ? evt.mozInputSource == 2 - : evt.type.indexOf('pen') == 0; -}; - -/** - * Returns true if the event was generated using a touch device (not a pen or mouse). - */ -// static isMultiTouchEvent(evt: Event): boolean; -export const isMultiTouchEvent = (evt) => { - return ( - evt.type != null && - evt.type.indexOf('touch') == 0 && - evt.touches != null && - evt.touches.length > 1 - ); -}; - -/** - * Returns true if the event was generated using a mouse (not a pen or touch device). - */ -// static isMouseEvent(evt: Event): boolean; -export const isMouseEvent = (evt) => { - return evt.pointerType != null - ? evt.pointerType == 'mouse' || - evt.pointerType === evt.MSPOINTER_TYPE_MOUSE - : evt.mozInputSource != null - ? evt.mozInputSource == 1 - : evt.type.indexOf('mouse') == 0; -}; - -/** - * Returns true if the left mouse button is pressed for the given event. - * To check if a button is pressed during a mouseMove you should use the - * {@link mxGraph.isMouseDown} property. Note that this returns true in Firefox - * for control+left-click on the Mac. - */ -// static isLeftMouseButton(evt: MouseEvent): boolean; -export const isLeftMouseButton = (evt) => { - // Special case for mousemove and mousedown we check the buttons - // if it exists because which is 0 even if no button is pressed - if ( - 'buttons' in evt && - (evt.type == 'mousedown' || evt.type == 'mousemove') - ) { - return evt.buttons == 1; - } - if ('which' in evt) { - return evt.which === 1; - } - return evt.button === 1; -}; - -/** - * Returns true if the middle mouse button is pressed for the given event. - * To check if a button is pressed during a mouseMove you should use the - * {@link mxGraph.isMouseDown} property. - */ -// static isMiddleMouseButton(evt: MouseEvent): boolean; -export const isMiddleMouseButton = (evt) => { - if ('which' in evt) { - return evt.which === 2; - } - return evt.button === 4; -}; - -/** - * Returns true if the right mouse button was pressed. Note that this - * button might not be available on some systems. For handling a popup - * trigger {@link isPopupTrigger} should be used. - */ -// static isRightMouseButton(evt: MouseEvent): boolean; -export const isRightMouseButton = (evt) => { - if ('which' in evt) { - return evt.which === 3; - } - return evt.button === 2; -}; - -/** - * Returns true if the event is a popup trigger. This implementation - * returns true if the right button or the left button and control was - * pressed on a Mac. - */ -// static isPopupTrigger(evt: Event): boolean; -export const isPopupTrigger = (evt) => { - return ( - isRightMouseButton(evt) || - (mxClient.IS_MAC && - isControlDown(evt) && - !isShiftDown(evt) && - !isMetaDown(evt) && - !isAltDown(evt)) - ); -}; - -/** - * Returns true if the shift key is pressed for the given event. - */ -// static isShiftDown(evt: MouseEvent): boolean; -export const isShiftDown = (evt) => { - return evt != null ? evt.shiftKey : false; -}; - -/** - * Returns true if the alt key is pressed for the given event. - */ -// static isAltDown(evt: MouseEvent): boolean; -export const isAltDown = (evt) => { - return evt != null ? evt.altKey : false; -}; - -/** - * Returns true if the control key is pressed for the given event. - */ -// static isControlDown(evt: MouseEvent): boolean; -export const isControlDown = (evt) => { - return evt != null ? evt.ctrlKey : false; -}; - -/** - * Returns true if the meta key is pressed for the given event. - */ -// static isMetaDown(evt: MouseEvent): boolean; -export const isMetaDown = (evt) => { - return evt != null ? evt.metaKey : false; -}; \ No newline at end of file diff --git a/packages/core/src/util/EventUtils.ts b/packages/core/src/util/EventUtils.ts new file mode 100644 index 000000000..c85b81db4 --- /dev/null +++ b/packages/core/src/util/EventUtils.ts @@ -0,0 +1,189 @@ +/** + * Returns the touch or mouse event that contains the mouse coordinates. + */ + +import mxClient from '../mxClient'; + +export const getMainEvent = (evt: MouseEvent) => { + let t = evt as any; + + if ((t.type === 'touchstart' || t.type === 'touchmove') && t.touches && t.touches[0]) { + t = t.touches[0]; + } else if (t.type === 'touchend' && t.changedTouches && t.changedTouches[0]) { + t = t.changedTouches[0]; + } + + return t as MouseEvent; +}; + +/** + * Returns true if the meta key is pressed for the given event. + */ +export const getClientX = (evt: MouseEvent) => { + return getMainEvent(evt).clientX; +}; + +/** + * Returns true if the meta key is pressed for the given event. + */ +// static getClientY(e: TouchEvent | MouseEvent): number; +export const getClientY = (evt: MouseEvent) => { + return getMainEvent(evt).clientY; +}; + +/** + * Function: getSource + * + * Returns the event's target or srcElement depending on the browser. + */ +export const getSource = (evt: MouseEvent) => { + return evt.srcElement !== undefined ? evt.srcElement : evt.target; +}; + +/** + * Returns true if the event has been consumed using {@link consume}. + */ +export const isConsumed = (evt: MouseEvent) => { + const t = evt as any; + return t.isConsumed !== undefined && t.isConsumed; +}; + +/** + * Returns true if the event was generated using a touch device (not a pen or mouse). + */ +export const isTouchEvent = (evt: MouseEvent) => { + const t = evt as any; + + return t.pointerType + ? t.pointerType === 'touch' || t.pointerType === t.MSPOINTER_TYPE_TOUCH + : t.mozInputSource !== undefined + ? t.mozInputSource === 5 + : t.type.indexOf('touch') === 0; +}; + +/** + * Returns true if the event was generated using a pen (not a touch device or mouse). + */ +export const isPenEvent = (evt: MouseEvent) => { + const t = evt as any; + + return t.pointerType + ? t.pointerType == 'pen' || t.pointerType === t.MSPOINTER_TYPE_PEN + : t.mozInputSource !== undefined + ? t.mozInputSource === 2 + : t.type.indexOf('pen') === 0; +}; + +/** + * Returns true if the event was generated using a touch device (not a pen or mouse). + */ +export const isMultiTouchEvent = (evt: MouseEvent) => { + const t = evt as any; + + return ( + t.type && + t.type.indexOf('touch') == 0 && + t.touches !== undefined && + t.touches.length > 1 + ); +}; + +/** + * Returns true if the event was generated using a mouse (not a pen or touch device). + */ +export const isMouseEvent = (evt: Event) => { + const t = evt as any; + + return t.pointerType + ? t.pointerType == 'mouse' || t.pointerType === t.MSPOINTER_TYPE_MOUSE + : t.mozInputSource !== undefined + ? t.mozInputSource === 1 + : t.type.indexOf('mouse') === 0; +}; + +/** + * Returns true if the left mouse button is pressed for the given event. + * To check if a button is pressed during a mouseMove you should use the + * {@link mxGraph.isMouseDown} property. Note that this returns true in Firefox + * for control+left-click on the Mac. + */ +// static isLeftMouseButton(evt: MouseEvent): boolean; +export const isLeftMouseButton = (evt: MouseEvent) => { + // Special case for mousemove and mousedown we check the buttons + // if it exists because which is 0 even if no button is pressed + if ('buttons' in evt && (evt.type === 'mousedown' || evt.type === 'mousemove')) { + return evt.buttons === 1; + } + if ('which' in evt) { + return evt.which === 1; + } + return evt.button === 1; +}; + +/** + * Returns true if the middle mouse button is pressed for the given event. + * To check if a button is pressed during a mouseMove you should use the + * {@link mxGraph.isMouseDown} property. + */ +export const isMiddleMouseButton = (evt: MouseEvent) => { + if ('which' in evt) { + return evt.which === 2; + } + return evt.button === 4; +}; + +/** + * Returns true if the right mouse button was pressed. Note that this + * button might not be available on some systems. For handling a popup + * trigger {@link isPopupTrigger} should be used. + */ +export const isRightMouseButton = (evt: MouseEvent) => { + if ('which' in evt) { + return evt.which === 3; + } + return evt.button === 2; +}; + +/** + * Returns true if the event is a popup trigger. This implementation + * returns true if the right button or the left button and control was + * pressed on a Mac. + */ +export const isPopupTrigger = (evt: MouseEvent) => { + return ( + isRightMouseButton(evt) || + (mxClient.IS_MAC && + isControlDown(evt) && + !isShiftDown(evt) && + !isMetaDown(evt) && + !isAltDown(evt)) + ); +}; + +/** + * Returns true if the shift key is pressed for the given event. + */ +export const isShiftDown = (evt: MouseEvent) => { + return evt.shiftKey; +}; + +/** + * Returns true if the alt key is pressed for the given event. + */ +export const isAltDown = (evt: MouseEvent) => { + return evt.altKey; +}; + +/** + * Returns true if the control key is pressed for the given event. + */ +export const isControlDown = (evt: MouseEvent) => { + return evt.ctrlKey; +}; + +/** + * Returns true if the meta key is pressed for the given event. + */ +export const isMetaDown = (evt: MouseEvent) => { + return evt.metaKey; +}; diff --git a/packages/core/src/util/Utils.ts b/packages/core/src/util/Utils.ts index 4fa324dd3..0539c1bdc 100644 --- a/packages/core/src/util/Utils.ts +++ b/packages/core/src/util/Utils.ts @@ -41,7 +41,7 @@ import Cell from '../view/cell/datatypes/Cell'; import Model from '../view/model/Model'; import graph from '../view/Graph'; -import type { CellStateStyles, Properties, StyleProperties, StyleValue } from '../types'; +import type { CellStateStyles, Properties, StyleValue } from '../types'; import CellArray from '../view/cell/datatypes/CellArray'; /** @@ -159,7 +159,11 @@ export const parseCssNumber = (value: string) => { * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%'); * (end) */ -export const setPrefixedStyle = (style: StyleProperties, name: string, value: string) => { +export const setPrefixedStyle = ( + style: CSSStyleDeclaration, + name: string, + value: string +) => { let prefix = null; if (mxClient.IS_SF || mxClient.IS_GC) { @@ -189,7 +193,7 @@ export const setPrefixedStyle = (style: StyleProperties, name: string, value: st export const hasScrollbars = (node: HTMLElement) => { const style = getCurrentStyle(node); - return style && (style.overflow === 'scroll' || style.overflow === 'auto'); + return !!style && (style.overflow === 'scroll' || style.overflow === 'auto'); }; /** @@ -402,14 +406,16 @@ export const getColor = (array: any, key: string, defaultValue: any) => { * a - Array of to be compared. * b - Array of to be compared. */ -export const equalPoints = (a: Point[] | null, b: Point[] | null) => { +export const equalPoints = (a: (Point | null)[] | null, b: (Point | null)[] | null) => { if ((!a && b) || (a && !b) || (a && b && a.length != b.length)) { return false; } if (a && b) { for (let i = 0; i < a.length; i += 1) { - if (!a[i] || !a[i].equals(b[i])) return false; + const p = a[i]; + + if (!p || (p && !p.equals(b[i]))) return false; } } diff --git a/packages/core/src/view/Graph.ts b/packages/core/src/view/Graph.ts index 771dbc041..f0328ee53 100644 --- a/packages/core/src/view/Graph.ts +++ b/packages/core/src/view/Graph.ts @@ -24,16 +24,14 @@ import Point from './geometry/Point'; import { applyMixins, autoImplement, - getBoundingBox, getCurrentStyle, - getValue, hasScrollbars, parseCssNumber, } from '../util/Utils'; import Cell from './cell/datatypes/Cell'; import Model from './model/Model'; import Stylesheet from './style/Stylesheet'; -import { DIALECT_SVG, PAGE_FORMAT_A4_PORTRAIT } from '../util/Constants'; +import { PAGE_FORMAT_A4_PORTRAIT } from '../util/Constants'; import ChildChange from './model/ChildChange'; import GeometryChange from './geometry/GeometryChange'; @@ -49,23 +47,27 @@ 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 GraphEvents from './event/GraphEvents'; -import GraphImage from './image/GraphImage'; -import GraphCells from './cell/GraphCells'; -import GraphSelection from './selection/GraphSelection'; -import GraphConnections from './connection/GraphConnections'; -import GraphEdge from './cell/edge/GraphEdge'; -import GraphVertex from './cell/vertex/GraphVertex'; -import GraphOverlays from './layout/GraphOverlays'; -import GraphEditing from './editing/GraphEditing'; -import GraphFolding from './folding/GraphFolding'; -import GraphLabel from './label/GraphLabel'; -import GraphValidation from './validation/GraphValidation'; -import GraphSnap from './snap/GraphSnap'; -import type { GraphPlugin } from '../types'; -import GraphTooltip from './tooltip/GraphTooltip'; -import GraphTerminal from './terminal/GraphTerminal'; +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'; type PartialEvents = Pick< GraphEvents, @@ -81,7 +83,9 @@ type PartialEvents = Pick< | 'isIgnoreTerminalEvent' | 'isCloneEvent' | 'isToggleEvent' - | 'getClickTolerance' + | 'getEventTolerance' + | 'isInvokesStopCellEditing' + | 'getPointForEvent' >; type PartialSelection = Pick< GraphSelection, @@ -90,6 +94,13 @@ type PartialSelection = Pick< | 'getSelectionCount' | 'selectCellForEvent' | 'setSelectionCell' + | 'getSelectionCells' + | 'updateSelection' + | 'selectRegion' + | 'cellAdded' + | 'cellRemoved' + | 'getUpdatingSelectionResource' + | 'getDoneResource' >; type PartialCells = Pick< GraphCells, @@ -100,6 +111,14 @@ type PartialCells = Pick< | 'isCellsCloneable' | 'cloneCell' | 'setCellStyles' + | 'isCellMovable' + | 'isCellResizable' + | 'getChildCells' + | 'isCellRotatable' + | 'getCellContainmentArea' + | 'getCurrentCellStyle' + | 'resizeCell' + | 'removeStateForCell' >; type PartialConnections = Pick< GraphConnections, @@ -108,17 +127,44 @@ type PartialConnections = Pick< | 'isCellDisconnectable' | 'getOutlineConstraint' | 'connectCell' + | 'getConnections' + | 'isConstrainChild' + | 'isValidSource' >; -type PartialEditing = Pick; +type PartialEditing = Pick; type PartialTooltip = Pick; type PartialValidation = Pick< GraphValidation, 'getEdgeValidationError' | 'validationAlert' >; -type PartialLabel = Pick; +type PartialLabel = Pick< + GraphLabel, + 'isLabelMovable' | 'isHtmlLabel' | 'isWrapping' | 'isLabelClipped' | 'getLabel' +>; type PartialTerminal = Pick; -type PartialSnap = Pick; -type PartialEdge = Pick; +type PartialSnap = Pick< + GraphSnap, + 'snap' | 'getGridSize' | 'isGridEnabled' | 'getSnapTolerance' +>; +type PartialEdge = Pick< + GraphEdge, + 'isAllowDanglingEdges' | 'isResetEdgesOnConnect' | 'getEdges' | 'insertEdge' | 'addEdge' +>; +type PartialOverlays = Pick; +type PartialFolding = Pick< + GraphFolding, + 'getFoldingImage' | 'isFoldingEnabled' | 'foldCells' +>; +type PartialPanning = Pick< + GraphPanning, + | 'panGraph' + | 'isUseScrollbarsForPanning' + | 'getPanDx' + | 'setPanDx' + | 'getPanDy' + | 'setPanDy' +>; +type PartialZoom = Pick; type PartialClass = PartialEvents & PartialSelection & PartialCells & @@ -130,10 +176,22 @@ type PartialClass = PartialEvents & PartialTerminal & PartialSnap & PartialEdge & + PartialOverlays & + PartialFolding & + PartialPanning & + PartialZoom & EventSource; export type MaxGraph = Graph & PartialClass; +const defaultPlugins: GraphPluginConstructor[] = [ + TooltipHandler, + SelectionCellsHandler, + PopupMenuHandler, + ConnectionHandler, + GraphHandler, +]; + /** * Extends {@link EventSource} to implement a graph component for * the browser. This is the main class of the package. To activate @@ -151,12 +209,12 @@ export type MaxGraph = Graph & PartialClass; * @class graph * @extends {EventSource} */ -// @ts-ignore +// @ts-ignore recursive reference error class Graph extends autoImplement() { constructor( container: HTMLElement, model: Model, - plugins: GraphPlugin[] = [], + plugins: GraphPluginConstructor[] = defaultPlugins, stylesheet: Stylesheet | null = null ) { super(); @@ -165,7 +223,6 @@ class Graph extends autoImplement() { this.model = model; this.plugins = plugins; this.cellRenderer = this.createCellRenderer(); - this.setSelectionModel(this.createSelectionModel()); this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet()); this.view = this.createGraphView(); @@ -176,21 +233,6 @@ class Graph extends autoImplement() { this.getModel().addListener(InternalEvent.CHANGE, this.graphModelChangeListener); - // Installs basic event handlers with disabled default settings. - this.createHandlers(); - - // Initializes the display if a container was specified - this.init(); - - this.view.revalidate(); - } - - /** - * Initializes the {@link container} and creates the respective datastructures. - * - * @param container DOM node that will contain the graph display. - */ - init() { // Initializes the in-place editor this.cellEditor = this.createCellEditor(); @@ -200,22 +242,14 @@ class Graph extends autoImplement() { // Updates the size of the container for the current graph this.sizeDidChange(); - // Hides tooltips and resets tooltip timer if mouse leaves container - InternalEvent.addListener(this.container, 'mouseleave', (evt: Event) => { - if ( - this.tooltipHandler.div && - this.tooltipHandler.div !== (evt).relatedTarget - ) { - this.tooltipHandler.hide(); - } + // Initiailzes plugins + this.plugins.forEach((p: GraphPluginConstructor) => { + this.pluginsMap.put(p.pluginId, new p(this)); }); - // Initiailzes plugins - this.plugins.forEach((p) => p.onInit(this)); + this.view.revalidate(); } - // TODO: Document me! - container: HTMLElement; getContainer = () => this.container; @@ -224,21 +258,23 @@ class Graph extends autoImplement() { // Handlers // @ts-ignore Cannot be null. - tooltipHandler: TooltipHandler; + // tooltipHandler: TooltipHandler; // @ts-ignore Cannot be null. - selectionCellsHandler: SelectionCellsHandler; + // selectionCellsHandler: SelectionCellsHandler; // @ts-ignore Cannot be null. - popupMenuHandler: PopupMenuHandler; + // popupMenuHandler: PopupMenuHandler; // @ts-ignore Cannot be null. - connectionHandler: ConnectionHandler; + // connectionHandler: ConnectionHandler; // @ts-ignore Cannot be null. - graphHandler: GraphHandler; + // graphHandler: GraphHandler; - getTooltipHandler = () => this.tooltipHandler; - getSelectionCellsHandler = () => this.selectionCellsHandler; - getPopupMenuHandler = () => this.popupMenuHandler; - getConnectionHandler = () => this.connectionHandler; - getGraphHandler = () => this.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; @@ -252,7 +288,8 @@ class Graph extends autoImplement() { */ model: Model; - plugins: GraphPlugin[]; + plugins: GraphPluginConstructor[]; + pluginsMap: Dictionary = new Dictionary(); /** * Holds the {@link GraphView} that caches the {@link CellState}s for the cells. @@ -275,11 +312,6 @@ class Graph extends autoImplement() { // @ts-ignore stylesheet: Stylesheet; - /** - * Holds the {@link mxGraphSelectionModel} that models the current selection. - */ - selectionModel: mxGraphSelectionModel | null = null; - /** * Holds the {@link CellEditor} that is used as the in-place editing. */ @@ -591,62 +623,6 @@ class Graph extends autoImplement() { } } - /** - * Creates the tooltip-, panning-, connection- and graph-handler (in this - * order). This is called in the constructor before {@link init} is called. - */ - createHandlers(): void { - this.tooltipHandler = this.createTooltipHandler(); - this.tooltipHandler.setEnabled(false); - this.selectionCellsHandler = this.createSelectionCellsHandler(); - this.connectionHandler = this.createConnectionHandler(); - this.connectionHandler.setEnabled(false); - this.graphHandler = this.createGraphHandler(); - this.popupMenuHandler = this.createPopupMenuHandler(); - } - - /** - * Creates and returns a new {@link TooltipHandler} to be used in this graph. - */ - createTooltipHandler() { - return new TooltipHandler(this); - } - - /** - * Creates and returns a new {@link TooltipHandler} to be used in this graph. - */ - createSelectionCellsHandler(): SelectionCellsHandler { - return new SelectionCellsHandler(this); - } - - /** - * Creates and returns a new {@link ConnectionHandler} to be used in this graph. - */ - createConnectionHandler(): ConnectionHandler { - return new ConnectionHandler(this); - } - - /** - * Creates and returns a new {@link GraphHandler} to be used in this graph. - */ - createGraphHandler(): GraphHandler { - return new GraphHandler(this); - } - - /** - * Creates and returns a new {@link PopupMenuHandler} to be used in this graph. - */ - createPopupMenuHandler(): PopupMenuHandler { - return new PopupMenuHandler(this); - } - - /** - * Creates a new {@link mxGraphSelectionModel} to be used in this graph. - */ - createSelectionModel(): mxGraphSelectionModel { - return new mxGraphSelectionModel(this); - } - /** * Creates a new {@link mxGraphSelectionModel} to be used in this graph. */ @@ -732,7 +708,7 @@ class Graph extends autoImplement() { if (change instanceof RootChange) { this.clearSelection(); this.setDefaultParent(null); - this.cells.removeStateForCell(change.previous); + this.removeStateForCell(change.previous); if (this.resetViewOnRootChange) { this.view.scale = 1; @@ -752,7 +728,7 @@ class Graph extends autoImplement() { if (!this.getModel().contains(newParent) || newParent.isCollapsed()) { this.view.invalidate(change.child, true, true); - this.cells.removeStateForCell(change.child); + this.removeStateForCell(change.child); // Handles special case of current root of view being removed if (this.view.currentRoot == change.child) { @@ -803,7 +779,7 @@ class Graph extends autoImplement() { // Removes the state from the cache by default else if (change.cell != null && change.cell instanceof Cell) { - this.cells.removeStateForCell(change.cell); + this.removeStateForCell(change.cell); } } @@ -1109,7 +1085,7 @@ class Graph extends autoImplement() { * * @param state {@link mxCellState} whose handler should be created. */ - createHandler(state: CellState): EdgeHandler | VertexHandler | null { + createHandler(state: CellState) { let result: EdgeHandler | VertexHandler | null = null; if (state.cell.isEdge()) { diff --git a/packages/core/src/view/GraphHandler.ts b/packages/core/src/view/GraphHandler.ts index d1df991e9..70fe173b1 100644 --- a/packages/core/src/view/GraphHandler.ts +++ b/packages/core/src/view/GraphHandler.ts @@ -31,6 +31,7 @@ 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'; /** * Class: mxGraphHandler @@ -52,7 +53,9 @@ import { MaxGraph } from './Graph'; * * graph - Reference to the enclosing . */ -class GraphHandler { +class GraphHandler implements GraphPlugin { + static pluginId = 'GraphHandler'; + constructor(graph: MaxGraph) { this.graph = graph; this.graph.addMouseListener(this); @@ -1798,7 +1801,7 @@ class GraphHandler { * Destroys the handler and all its resources and DOM nodes. */ // destroy(): void; - destroy() { + onDestroy() { this.graph.removeMouseListener(this); this.graph.removeListener(this.panHandler); diff --git a/packages/core/src/view/cell/CellMarker.ts b/packages/core/src/view/cell/CellMarker.ts index 1e8cb6029..857d13de3 100644 --- a/packages/core/src/view/cell/CellMarker.ts +++ b/packages/core/src/view/cell/CellMarker.ts @@ -11,6 +11,7 @@ import { DEFAULT_VALID_COLOR, MAX_HOTSPOT_SIZE, MIN_HOTSPOT_SIZE, + NONE, } from '../../util/Constants'; import CellHighlight from '../selection/CellHighlight'; import EventObject from '../event/EventObject'; @@ -273,11 +274,7 @@ class CellMarker extends EventSource { * * Sets and marks the current valid state. */ - setCurrentState( - state: CellState | null, - me: InternalMouseEvent, - color: ColorValue | null = null - ) { + setCurrentState(state: CellState | null, me: InternalMouseEvent, color?: ColorValue) { const isValid = state ? this.isValidState(state) : false; color = color ?? this.getMarkerColor(me.getEvent(), state, isValid); diff --git a/packages/core/src/view/cell/CellRenderer.ts b/packages/core/src/view/cell/CellRenderer.ts index d0df01707..7f13a5b3f 100644 --- a/packages/core/src/view/cell/CellRenderer.ts +++ b/packages/core/src/view/cell/CellRenderer.ts @@ -71,6 +71,8 @@ import Model from '../model/Model'; import CellOverlay from './CellOverlay'; import { getClientX, getClientY, getSource } from '../../util/EventUtils'; import { isNode } from '../../util/DomUtils'; +import { CellStateStyles } from '../../types'; +import CellArray from './datatypes/CellArray'; /** * Class: mxCellRenderer @@ -119,6 +121,7 @@ class CellRenderer { * * Defines the default shape for edges. Default is . */ + // @ts-expect-error The constructors for Shape and Connector are different. defaultEdgeShape: typeof Shape = Connector; /** @@ -264,12 +267,11 @@ class CellRenderer { let ctor = this.getShape(state.style.shape); if (!ctor) { - ctor = ( - (state.cell.isEdge() ? this.defaultEdgeShape : this.defaultVertexShape) - ); + // @ts-expect-error The various Shape constructors are not compatible. + ctor = state.cell.isEdge() ? this.defaultEdgeShape : this.defaultVertexShape; } - return ctor; + return ctor as typeof Shape; } /** @@ -286,12 +288,12 @@ class CellRenderer { if (shape) { shape.apply(state); - shape.imageSrc = state.getImage(); + shape.imageSrc = state.getImageSrc(); shape.indicatorColor = state.getIndicatorColor(); shape.indicatorStrokeColor = state.style.indicatorStrokeColor; shape.indicatorGradientColor = state.getIndicatorGradientColor(); shape.indicatorDirection = state.style.indicatorDirection; - shape.indicatorImage = state.getIndicatorImage(); + shape.indicatorImageSrc = state.getIndicatorImageSrc(); this.postConfigureShape(state); } } @@ -304,8 +306,8 @@ class CellRenderer { * This implementation resolves these keywords on the fill, stroke * and gradient color keys. */ - postConfigureShape(state: CellState): void { - if (state.shape != null) { + postConfigureShape(state: CellState) { + if (state.shape) { this.resolveColor(state, 'indicatorGradientColor', 'gradientColor'); this.resolveColor(state, 'indicatorColor', 'fillColor'); this.resolveColor(state, 'gradient', 'gradientColor'); @@ -320,14 +322,19 @@ class CellRenderer { * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets * the respective color on the shape. */ - checkPlaceholderStyles(state: CellState): boolean { + checkPlaceholderStyles(state: CellState) { // LATER: Check if the color has actually changed - if (state.style != null) { + if (state.style) { const values = ['inherit', 'swimlane', 'indicated']; - const styles = ['fillColor', 'strokeColor', 'gradientColor', 'fontColor']; + const styles: (keyof CellStateStyles)[] = [ + 'fillColor', + 'strokeColor', + 'gradientColor', + 'fontColor', + ]; for (let i = 0; i < styles.length; i += 1) { - if (values.indexOf(state.style[styles[i]]) >= 0) { + if (values.indexOf(state.style[styles[i]] as string) >= 0) { return true; } } @@ -341,26 +348,25 @@ class CellRenderer { * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets * the respective color on the shape. */ - resolveColor(state: CellState, field: string, key: string): void { - const shape = key === 'fontColor' ? state.text : state.shape; + resolveColor(state: CellState, field: string, key: string) { + const shape: Shape | null = key === 'fontColor' ? state.text : state.shape; - if (shape != null) { + if (shape) { const { graph } = state.view; + // @ts-ignore const value = shape[field]; let referenced = null; if (value === 'inherit') { - // @ts-ignore referenced = state.cell.getParent(); + /* disable swimlane for now } else if (value === 'swimlane') { // @ts-ignore shape[field] = key === 'strokeColor' || key === 'fontColor' ? '#000000' : '#ffffff'; - // @ts-ignore - if (state.cell.getTerminal(false) != null) { - // @ts-ignore + if (state.cell.getTerminal(false)) { referenced = state.cell.getTerminal(false); } else { referenced = state.cell; @@ -368,30 +374,27 @@ class CellRenderer { referenced = graph.swimlane.getSwimlane(referenced); key = graph.swimlane.swimlaneIndicatorColorAttribute; - } else if (value === 'indicated' && state.shape != null) { + */ + } else if (value === 'indicated' && state.shape) { // @ts-ignore shape[field] = state.shape.indicatorColor; - } else if (key !== 'fillColor' && value === 'fillColor' && state.shape != null) { + } else if (key !== 'fillColor' && value === 'fillColor' && state.shape) { // @ts-ignore shape[field] = state.style.fillColor; - } else if ( - key !== 'strokeColor' && - value === 'strokeColor' && - state.shape != null - ) { + } else if (key !== 'strokeColor' && value === 'strokeColor' && state.shape) { // @ts-ignore shape[field] = state.style.strokeColor; } - if (referenced != null) { + if (referenced) { const rstate = graph.getView().getState(referenced); // @ts-ignore shape[field] = null; - if (rstate != null) { + if (rstate) { const rshape = key === 'fontColor' ? rstate.text : rstate.shape; - if (rshape != null && field !== 'indicatorColor') { + if (rshape && field !== 'indicatorColor') { // @ts-ignore shape[field] = rshape[field]; } else { @@ -412,7 +415,6 @@ class CellRenderer { * * state - for which the label should be created. */ - // getLabelValue(state: mxCellState): string; getLabelValue(state: CellState) { return state.view.graph.getLabel(state.cell); } @@ -426,15 +428,12 @@ class CellRenderer { * * state - for which the label should be created. */ - // createLabel(state: mxCellState, value: string): void; - createLabel(state: CellState, value: any) { + createLabel(state: CellState, value: string) { const { graph } = state.view; - const isEdge = state.cell.isEdge(); if (state.style.fontSize > 0 || state.style.fontSize == null) { // Avoids using DOM node for empty labels - const isForceHtml = - graph.isHtmlLabel(state.cell) || (value != null && isNode(value)); + const isForceHtml = graph.isHtmlLabel(state.cell) || isNode(value); state.text = new this.defaultTextShape( value, @@ -459,8 +458,7 @@ class CellRenderer { state.style.labelPadding, state.style.textDirection || DEFAULT_TEXT_DIRECTION ); - state.text.opacity = - state.style.textOpacity == null ? 100 : state.style.textOpacity; + state.text.opacity = state.style.textOpacity; state.text.dialect = isForceHtml ? DIALECT_STRICTHTML : state.view.graph.dialect; state.text.style = state.style; state.text.state = state; @@ -474,7 +472,7 @@ class CellRenderer { let forceGetCell = false; const getState = (evt: Event | InternalMouseEvent) => { - let result = state; + let result: CellState | null = state; if (mxClient.IS_TOUCH || forceGetCell) { const x = getClientX(evt); @@ -483,7 +481,7 @@ 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)); + result = graph.view.getState(graph.getCellAt(pt.x, pt.y) as Cell); } return result; }; @@ -491,29 +489,29 @@ class CellRenderer { // TODO: Add handling for special touch device gestures InternalEvent.addGestureListeners( state.text.node, - (evt: MouseEvent) => { - if (this.isLabelEvent(state, evt)) { - graph.event.fireMouseEvent( + (evt: Event) => { + if (this.isLabelEvent(state, evt as MouseEvent)) { + graph.fireMouseEvent( InternalEvent.MOUSE_DOWN, - new InternalMouseEvent(evt, state) + new InternalMouseEvent(evt as MouseEvent, state) ); forceGetCell = graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG'; } }, - (evt: MouseEvent) => { - if (this.isLabelEvent(state, evt)) { - graph.event.fireMouseEvent( + (evt: Event) => { + if (this.isLabelEvent(state, evt as MouseEvent)) { + graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt, getState(evt)) + new InternalMouseEvent(evt as MouseEvent, getState(evt)) ); } }, - (evt: MouseEvent) => { - if (this.isLabelEvent(state, evt)) { - graph.event.fireMouseEvent( + (evt: Event) => { + if (this.isLabelEvent(state, evt as MouseEvent)) { + graph.fireMouseEvent( InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt, getState(evt)) + new InternalMouseEvent(evt as MouseEvent, getState(evt)) ); forceGetCell = false; } @@ -521,10 +519,10 @@ class CellRenderer { ); // Uses double click timeout in mxGraph for quirks mode - if (graph.event.nativeDblClickEnabled) { - InternalEvent.addListener(state.text.node, 'dblclick', (evt: MouseEvent) => { - if (this.isLabelEvent(state, evt)) { - graph.event.dblClick(evt, state.cell); + 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.consume(evt); } }); @@ -558,47 +556,37 @@ class CellRenderer { * * state - for which the overlay should be created. */ - createCellOverlays(state: CellState): void { + createCellOverlays(state: CellState) { const { graph } = state.view; const overlays = graph.getCellOverlays(state.cell); - let dict = null; + const dict = new Dictionary(); - if (overlays != null) { - dict = new Dictionary(); + for (let i = 0; i < overlays.length; i += 1) { + const shape = state.overlays.remove(overlays[i]); - for (let i = 0; i < overlays.length; i += 1) { - const shape = state.overlays != null ? state.overlays.remove(overlays[i]) : null; + if (!shape) { + const tmp = new ImageShape(new Rectangle(), overlays[i].image.src); + tmp.dialect = state.view.graph.dialect; + tmp.preserveImageAspect = false; + tmp.overlay = overlays[i]; + this.initializeOverlay(state, tmp); + this.installCellOverlayListeners(state, overlays[i], tmp); - if (shape == null) { - const tmp = new ImageShape( - new Rectangle(), - // @ts-ignore - overlays[i].image.src - ); - tmp.dialect = state.view.graph.dialect; - tmp.preserveImageAspect = false; - tmp.overlay = overlays[i]; - this.initializeOverlay(state, tmp); - this.installCellOverlayListeners(state, overlays[i], tmp); - - if (overlays[i].cursor != null) { - // @ts-ignore - tmp.node.style.cursor = overlays[i].cursor; - } - - dict.put(overlays[i], tmp); - } else { - dict.put(overlays[i], shape); + if (overlays[i].cursor) { + tmp.node.style.cursor = overlays[i].cursor; } + + dict.put(overlays[i], tmp); + } else { + dict.put(overlays[i], shape); } } // Removes unused - if (state.overlays != null) { - state.overlays.visit((id: any, shape: { destroy: () => void }) => { - shape.destroy(); - }); - } + state.overlays.visit((id: any, shape: { destroy: () => void }) => { + shape.destroy(); + }); + state.overlays = dict; } @@ -612,7 +600,7 @@ class CellRenderer { * state - for which the overlay should be created. * overlay - that represents the overlay. */ - initializeOverlay(state: CellState, overlay: ImageShape): void { + initializeOverlay(state: CellState, overlay: ImageShape) { overlay.init(state.view.getOverlayPane()); } @@ -622,16 +610,12 @@ class CellRenderer { * Installs the listeners for the given , and * that represents the overlay. */ - installCellOverlayListeners( - state: CellState, - overlay: CellOverlay, - shape: Shape - ): void { + installCellOverlayListeners(state: CellState, overlay: CellOverlay, shape: Shape) { const { graph } = state.view; InternalEvent.addListener(shape.node, 'click', (evt: Event) => { - if (graph.editing.isEditing()) { - graph.editing.stopEditing(!graph.editing.isInvokesStopCellEditing()); + if (graph.isEditing()) { + graph.stopEditing(!graph.isInvokesStopCellEditing()); } overlay.fireEvent( @@ -645,9 +629,9 @@ class CellRenderer { InternalEvent.consume(evt); }, (evt: Event) => { - graph.event.fireMouseEvent( + graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt, state) + new InternalMouseEvent(evt as MouseEvent, state) ); } ); @@ -670,12 +654,12 @@ class CellRenderer { * * state - for which the control should be created. */ - createControl(state: CellState): void { + createControl(state: CellState) { const { graph } = state.view; const image = graph.getFoldingImage(state); - if (graph.foldingEnabled && image != null) { - if (state.control == null) { + if (graph.isFoldingEnabled() && image) { + if (!state.control) { const b = new Rectangle(0, 0, image.width, image.height); state.control = new ImageShape(b, image.src); state.control.preserveImageAspect = false; @@ -688,7 +672,7 @@ class CellRenderer { this.createControlClickHandler(state) ); } - } else if (state.control != null) { + } else if (state.control) { state.control.destroy(); state.control = null; } @@ -703,13 +687,13 @@ class CellRenderer { * * state - whose control click handler should be returned. */ - createControlClickHandler(state: CellState): Function { + createControlClickHandler(state: CellState) { const { graph } = state.view; - return (evt: EventObject) => { + return (evt: Event) => { if (this.forceControlClickHandler || graph.isEnabled()) { const collapse = !state.cell.isCollapsed(); - graph.foldCells(collapse, false, [state.cell], false, evt); + graph.foldCells(collapse, false, new CellArray(state.cell), false, evt); InternalEvent.consume(evt); } }; @@ -731,7 +715,7 @@ class CellRenderer { state: CellState, control: Shape, handleEvents: boolean, - clickHandler: Function + clickHandler: EventListener ): Element { const { graph } = state.view; @@ -744,8 +728,7 @@ class CellRenderer { if (isForceHtml) { control.dialect = DIALECT_PREFERHTML; control.init(graph.container); - // @ts-ignore - control.node.style.zIndex = 1; + control.node.style.zIndex = String(1); } else { control.init(state.view.getOverlayPane()); } @@ -753,9 +736,8 @@ class CellRenderer { const node = control.node; // Workaround for missing click event on iOS is to check tolerance below - if (clickHandler != null && !mxClient.IS_IOS) { + if (clickHandler && !mxClient.IS_IOS) { if (graph.isEnabled()) { - // @ts-ignore node.style.cursor = 'pointer'; } @@ -769,35 +751,34 @@ class CellRenderer { node, (evt: Event) => { first = new Point(getClientX(evt), getClientY(evt)); - graph.event.fireMouseEvent( + graph.fireMouseEvent( InternalEvent.MOUSE_DOWN, - new InternalMouseEvent(evt, state) + new InternalMouseEvent(evt as MouseEvent, state) ); InternalEvent.consume(evt); }, (evt: Event) => { - graph.event.fireMouseEvent( + graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt, state) + new InternalMouseEvent(evt as MouseEvent, state) ); }, (evt: Event) => { - graph.event.fireMouseEvent( + graph.fireMouseEvent( InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt, state) + new InternalMouseEvent(evt as MouseEvent, state) ); InternalEvent.consume(evt); } ); // Uses capture phase for event interception to stop bubble phase - if (clickHandler != null && mxClient.IS_IOS) { - // @ts-ignore + if (clickHandler && mxClient.IS_IOS) { node.addEventListener( 'touchend', (evt) => { - if (first != null) { - const tol = graph.tolerance; + if (first) { + const tol = graph.getEventTolerance(); if ( Math.abs(first.x - getClientX(evt)) < tol && @@ -827,7 +808,7 @@ class CellRenderer { * state - whose shape fired the event. * evt - Mouse event which was fired. */ - isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent): boolean { + isShapeEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) { return true; } @@ -842,7 +823,7 @@ class CellRenderer { * state - whose label fired the event. * evt - Mouse event which was fired. */ - isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent): boolean { + isLabelEvent(state: CellState, evt: InternalMouseEvent | MouseEvent) { return true; } @@ -855,14 +836,14 @@ class CellRenderer { * * state - for which the event listeners should be isntalled. */ - installListeners(state: CellState): void { + installListeners(state: CellState) { const { graph } = state.view; // 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) => { - let result = state; + let result: CellState | null = state; if ( (graph.dialect !== DIALECT_SVG && getSource(evt).nodeName === 'IMG') || @@ -874,7 +855,7 @@ 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)); + result = graph.view.getState(graph.getCellAt(pt.x, pt.y) as Cell); } return result; @@ -883,38 +864,38 @@ class CellRenderer { InternalEvent.addGestureListeners( // @ts-ignore state.shape.node, - (evt: MouseEvent) => { - if (this.isShapeEvent(state, evt)) { + (evt: Event) => { + if (this.isShapeEvent(state, evt as MouseEvent)) { graph.fireMouseEvent( InternalEvent.MOUSE_DOWN, - new InternalMouseEvent(evt, state) + new InternalMouseEvent(evt as MouseEvent, state) ); } }, - (evt: MouseEvent) => { - if (this.isShapeEvent(state, evt)) { + (evt: Event) => { + if (this.isShapeEvent(state, evt as MouseEvent)) { graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt, getState(evt)) + new InternalMouseEvent(evt as MouseEvent, getState(evt)) ); } }, - (evt: MouseEvent) => { - if (this.isShapeEvent(state, evt)) { + (evt: Event) => { + if (this.isShapeEvent(state, evt as MouseEvent)) { graph.fireMouseEvent( InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt, getState(evt)) + new InternalMouseEvent(evt as MouseEvent, getState(evt)) ); } } ); // Uses double click timeout in mxGraph for quirks mode - if (graph.nativeDblClickEnabled) { + if (graph.isNativeDblClickEnabled()) { // @ts-ignore InternalEvent.addListener(state.shape.node, 'dblclick', (evt) => { - if (this.isShapeEvent(state, evt)) { - graph.dblClick(evt, state.cell); + if (this.isShapeEvent(state, evt as MouseEvent)) { + graph.dblClick(evt as MouseEvent, state.cell); InternalEvent.consume(evt); } }); @@ -930,22 +911,22 @@ class CellRenderer { * * state - whose label should be redrawn. */ - redrawLabel(state: CellState, forced: boolean): void { + redrawLabel(state: CellState, forced: boolean) { const { graph } = state.view; const value = this.getLabelValue(state); const wrapping = graph.isWrapping(state.cell); const clipping = graph.isLabelClipped(state.cell); const isForceHtml = - state.view.graph.isHtmlLabel(state.cell) || (value != null && isNode(value)); + state.view.graph.isHtmlLabel(state.cell) || (value && isNode(value)); const dialect = isForceHtml ? DIALECT_STRICTHTML : state.view.graph.dialect; const overflow = state.style.overflow || 'visible'; if ( - state.text != null && - (state.text.wrap != wrapping || - state.text.clipped != clipping || - state.text.overflow != overflow || - state.text.dialect != dialect) + state.text && + (state.text.wrap !== wrapping || + state.text.clipped !== clipping || + state.text.overflow !== overflow || + state.text.dialect !== dialect) ) { state.text.destroy(); state.text = null; @@ -972,7 +953,7 @@ class CellRenderer { state.text.apply(state); // Special case where value is obtained via hook in graph - state.text.valign = state.getVerticalAlign(); + state.text.valign = state.getVerticalAlign(); } const bounds = this.getLabelBounds(state); @@ -1597,23 +1578,23 @@ class CellRenderer { if (state.shape.indicatorShape != null) { state.shape.indicator = new state.shape.indicatorShape(); state.shape.indicator.dialect = state.shape.dialect; - state.shape.indicator.init(state.node); + state.shape.indicator.init(state.node as HTMLElement); force = true; } } - if (state.shape != null) { + if (state.shape) { // Handles changes of the collapse icon this.createControl(state); // Redraws the cell if required, ignores changes to bounds if points are // defined as the bounds are updated for the given points inside the shape if (force || this.isShapeInvalid(state, state.shape)) { - if (state.absolutePoints != null) { + if (state.absolutePoints.length > 0) { state.shape.points = state.absolutePoints.slice(); state.shape.bounds = null; } else { - state.shape.points = null; + state.shape.points = []; state.shape.bounds = new Rectangle(state.x, state.y, state.width, state.height); } @@ -1664,22 +1645,20 @@ class CellRenderer { * * state - for which the shapes should be destroyed. */ - destroy(state: CellState): void { - if (state.shape != null) { - if (state.text != null) { + destroy(state: CellState) { + if (state.shape) { + if (state.text) { state.text.destroy(); state.text = null; } - if (state.overlays != null) { - state.overlays.visit((id: string, shape: Shape) => { - shape.destroy(); - }); + state.overlays.visit((id: string, shape: Shape) => { + shape.destroy(); + }); - state.overlays = null; - } + state.overlays = new Dictionary(); - if (state.control != null) { + if (state.control) { state.control.destroy(); state.control = null; } @@ -1699,6 +1678,7 @@ CellRenderer.registerShape(SHAPE_ELLIPSE, EllipseShape); CellRenderer.registerShape(SHAPE_RHOMBUS, RhombusShape); // @ts-ignore CellRenderer.registerShape(SHAPE_CYLINDER, CylinderShape); +// @ts-ignore CellRenderer.registerShape(SHAPE_CONNECTOR, Connector); // @ts-ignore CellRenderer.registerShape(SHAPE_ACTOR, Actor); @@ -1706,14 +1686,19 @@ CellRenderer.registerShape(SHAPE_TRIANGLE, TriangleShape); CellRenderer.registerShape(SHAPE_HEXAGON, HexagonShape); // @ts-ignore CellRenderer.registerShape(SHAPE_CLOUD, CloudShape); +// @ts-ignore CellRenderer.registerShape(SHAPE_LINE, Line); +// @ts-ignore CellRenderer.registerShape(SHAPE_ARROW, Arrow); +// @ts-ignore CellRenderer.registerShape(SHAPE_ARROW_CONNECTOR, ArrowConnector); // @ts-ignore CellRenderer.registerShape(SHAPE_DOUBLE_ELLIPSE, DoubleEllipseShape); +// @ts-ignore CellRenderer.registerShape(SHAPE_SWIMLANE, SwimlaneShape); // @ts-ignore CellRenderer.registerShape(SHAPE_IMAGE, ImageShape); +// @ts-ignore CellRenderer.registerShape(SHAPE_LABEL, Label); export default CellRenderer; diff --git a/packages/core/src/view/cell/TemporaryCellStates.ts b/packages/core/src/view/cell/TemporaryCellStates.ts index 6e6fdf3ac..e2cda598d 100644 --- a/packages/core/src/view/cell/TemporaryCellStates.ts +++ b/packages/core/src/view/cell/TemporaryCellStates.ts @@ -97,7 +97,7 @@ class TemporaryCellStates { /** * Holds the states of the rectangle. */ - oldStates: Dictionary; + oldStates: Dictionary; /** * Holds the bounds of the rectangle. diff --git a/packages/core/src/view/cell/TreeTraversal.ts b/packages/core/src/view/cell/TreeTraversal.ts index 9daedfb75..7ae0e3ab8 100644 --- a/packages/core/src/view/cell/TreeTraversal.ts +++ b/packages/core/src/view/cell/TreeTraversal.ts @@ -1,7 +1,7 @@ -import Cell from "./datatypes/Cell"; -import CellArray from "./datatypes/CellArray"; -import Dictionary from "../../util/Dictionary"; -import Graph from "../Graph"; +import Cell from './datatypes/Cell'; +import CellArray from './datatypes/CellArray'; +import Dictionary from '../../util/Dictionary'; +import Graph from '../Graph'; class TreeTraversal { dependencies = ['connections']; @@ -42,7 +42,7 @@ class TreeTraversal { for (const cell of parent.getChildren()) { if (cell.isVertex() && cell.isVisible()) { - const conns = this.graph.connection.getConnections(cell, isolate ? parent : null); + const conns = this.graph.getConnections(cell, isolate ? parent : null); let fanOut = 0; let fanIn = 0; @@ -117,13 +117,13 @@ class TreeTraversal { directed: boolean = true, func: Function | null = null, edge: Cell | null = null, - visited: Dictionary | null = null, + visited: Dictionary | null = null, inverse: boolean = false ): void { if (func != null && vertex != null) { directed = directed != null ? directed : true; inverse = inverse != null ? inverse : false; - visited = visited || new Dictionary(); + visited = visited || new Dictionary(); if (!visited.get(vertex)) { visited.put(vertex, true); diff --git a/packages/core/src/view/cell/datatypes/CellState.ts b/packages/core/src/view/cell/datatypes/CellState.ts index dbd5e5b65..8b9ec62a8 100644 --- a/packages/core/src/view/cell/datatypes/CellState.ts +++ b/packages/core/src/view/cell/datatypes/CellState.ts @@ -15,6 +15,7 @@ import Dictionary from '../../../util/Dictionary'; import { NONE } from '../../../util/Constants'; import { CellStateStyles } from 'packages/core/src/types'; import RectangleShape from '../../geometry/shape/node/RectangleShape'; +import CellOverlay from '../CellOverlay'; /** * Class: mxCellState @@ -67,7 +68,7 @@ class CellState extends Rectangle { control: Shape | null = null; // Used by mxCellRenderer's createCellOverlays() - overlays: Dictionary | null = null; + overlays: Dictionary = new Dictionary(); /** * Variable: view @@ -197,6 +198,8 @@ class CellState extends Rectangle { parentHighlight: RectangleShape | null = null; + point: Point | null = null; + /** * Function: getPerimeterBounds * diff --git a/packages/core/src/view/cell/edge/EdgeHandler.ts b/packages/core/src/view/cell/edge/EdgeHandler.ts index 661757544..00d3b2f57 100644 --- a/packages/core/src/view/cell/edge/EdgeHandler.ts +++ b/packages/core/src/view/cell/edge/EdgeHandler.ts @@ -63,6 +63,7 @@ import InternalMouseEvent from '../../event/InternalMouseEvent'; import Cell from '../datatypes/Cell'; import ImageBox from '../../image/ImageBox'; import Marker from '../../geometry/shape/edge/Marker'; +import EventSource from '../../event/EventSource'; /** * Graph event handler that reconnects edges and modifies control points and the edge @@ -381,8 +382,12 @@ class EdgeHandler { customHandles: CellHandle[] = []; - startX: number = 0; - startY: number = 0; + startX = 0; + startY = 0; + + outline = true; + + active = true; /** * Function: isParentHighlightVisible @@ -461,7 +466,7 @@ class EdgeHandler { * is true and the current style allows and * renders custom waypoints. */ - isVirtualBendsEnabled(evt: Event) { + isVirtualBendsEnabled(evt?: Event) { return ( this.virtualBendsEnabled && (this.state.style.edge == null || @@ -970,7 +975,7 @@ class EdgeHandler { * control point. The source and target points are used for reconnecting * the edge. */ - mouseDown(sender: Listenable, me: InternalMouseEvent) { + mouseDown(sender: EventSource, me: InternalMouseEvent) { const handle = this.getHandleForEvent(me); if (handle !== null && this.bends[handle]) { @@ -1284,7 +1289,7 @@ class EdgeHandler { // Removes point if user tries to straighten a segment if (!result && this.straightRemoveEnabled && (!me || !isAltDown(me.getEvent()))) { - const tol = this.graph.getClickTolerance() * this.graph.getClickTolerance(); + const tol = this.graph.getEventTolerance() * this.graph.getEventTolerance(); const abs = this.state.absolutePoints.slice(); abs[this.index] = pt; @@ -1517,8 +1522,7 @@ class EdgeHandler { * * Handles the event by updating the preview. */ - // mouseMove(sender: any, me: mxMouseEvent): void; - mouseMove(sender: Listenable, me: InternalMouseEvent) { + mouseMove(sender: EventSource, me: InternalMouseEvent) { if (this.index != null && this.marker != null) { this.currentPoint = this.getPointForEvent(me); this.error = null; @@ -1628,7 +1632,7 @@ class EdgeHandler { * Handles the event to applying the previewed changes on the edge by * using , or . */ - mouseUp(sender: Listenable, me: InternalMouseEvent) { + mouseUp(sender: EventSource, me: InternalMouseEvent) { // Workaround for wrong event source in Webkit if (this.index != null && this.marker != null) { if (this.shape != null && this.shape.node != null) { @@ -1675,7 +1679,7 @@ class EdgeHandler { } else if (this.isLabel && this.label) { this.moveLabel(this.state, this.label.x, this.label.y); } else if (this.isSource || this.isTarget) { - let terminal = null; + let terminal: Cell | null = null; if ( this.constraintHandler.currentConstraint != null && @@ -1685,17 +1689,17 @@ class EdgeHandler { } if ( - terminal == null && + !terminal && this.marker.hasValidState() && this.marker.highlight != null && this.marker.highlight.shape != null && this.marker.highlight.shape.stroke !== 'transparent' && this.marker.highlight.shape.stroke !== 'white' ) { - terminal = this.marker.validState.cell; + terminal = this.marker.validState!.cell; } - if (terminal != null) { + if (terminal) { const model = this.graph.getModel(); const parent = edge.getParent(); @@ -1704,18 +1708,18 @@ class EdgeHandler { // Clones and adds the cell if (clone) { let geo = edge.getGeometry(); - clone = this.graph.cloneCell(edge); - model.add(parent, clone, parent.getChildCount()); + const cloned = this.graph.cloneCell(edge); + model.add(parent, cloned, parent.getChildCount()); if (geo != null) { geo = geo.clone(); - model.setGeometry(clone, geo); + model.setGeometry(cloned, geo); } const other = edge.getTerminal(!this.isSource); - this.graph.connectCell(clone, other, !this.isSource); + this.graph.connectCell(cloned, other, !this.isSource); - edge = clone; + edge = cloned; } edge = this.connect(edge, terminal, this.isSource, clone, me); @@ -2127,7 +2131,7 @@ class EdgeHandler { * * Redraws the preview, and the bends- and label control points. */ - redraw(ignoreHandles: boolean) { + redraw(ignoreHandles?: boolean) { this.abspoints = this.state.absolutePoints.slice(); const g = this.state.cell.getGeometry(); diff --git a/packages/core/src/view/cell/vertex/VertexHandle.ts b/packages/core/src/view/cell/vertex/VertexHandle.ts index 2df019fd8..798ef7179 100644 --- a/packages/core/src/view/cell/vertex/VertexHandle.ts +++ b/packages/core/src/view/cell/vertex/VertexHandle.ts @@ -69,6 +69,8 @@ class VertexHandle implements CellHandle { */ ignoreGrid = false; + active = true; + /** * Hook for subclassers to return the current position of the handle. */ diff --git a/packages/core/src/view/cell/vertex/VertexHandler.ts b/packages/core/src/view/cell/vertex/VertexHandler.ts index 27d29971f..c83b5faa5 100644 --- a/packages/core/src/view/cell/vertex/VertexHandler.ts +++ b/packages/core/src/view/cell/vertex/VertexHandler.ts @@ -16,6 +16,7 @@ import { HANDLE_STROKECOLOR, LABEL_HANDLE_FILLCOLOR, LABEL_HANDLE_SIZE, + NONE, VERTEX_SELECTION_COLOR, VERTEX_SELECTION_DASHED, VERTEX_SELECTION_STROKEWIDTH, @@ -28,10 +29,18 @@ import Point from '../../geometry/Point'; import utils, { getRotatedPoint, intersects, mod, toRadians } from '../../../util/Utils'; import mxClient from '../../../mxClient'; import { isMouseEvent, isShiftDown } from '../../../util/EventUtils'; -import Graph from '../../Graph'; +import Graph, { MaxGraph } from '../../Graph'; import CellState from '../datatypes/CellState'; import Image from '../../image/ImageBox'; import Cell from '../datatypes/Cell'; +import { CellHandle, Listenable } from 'packages/core/src/types'; +import Shape from '../../geometry/shape/Shape'; +import InternalMouseEvent from '../../event/InternalMouseEvent'; +import VertexHandle from './VertexHandle'; +import CellArray from '../datatypes/CellArray'; +import EdgeHandler from '../edge/EdgeHandler'; +import CellHighlight from '../../selection/CellHighlight'; +import EventSource from '../../event/EventSource'; /** * Class: mxVertexHandler @@ -53,10 +62,109 @@ class VertexHandler { constructor(state: CellState) { this.state = state; - this.init(); + this.graph = this.state.view.graph; + this.selectionBounds = this.getSelectionBounds(this.state); + this.bounds = new Rectangle( + this.selectionBounds.x, + this.selectionBounds.y, + this.selectionBounds.width, + this.selectionBounds.height + ); + this.selectionBorder = this.createSelectionShape(this.bounds); + // VML dialect required here for event transparency in IE + this.selectionBorder.dialect = DIALECT_SVG; + this.selectionBorder.pointerEvents = false; + this.selectionBorder.rotation = Number(this.state.style.rotation || '0'); + this.selectionBorder.init(this.graph.getView().getOverlayPane()); + InternalEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state); + + if (this.graph.isCellMovable(this.state.cell)) { + this.selectionBorder.setCursor(CURSOR_MOVABLE_VERTEX); + } + + // Adds the sizer handles + if ( + this.graph.graphHandler.maxCells <= 0 || + this.graph.getSelectionCount() < this.graph.graphHandler.maxCells + ) { + const resizable = this.graph.isCellResizable(this.state.cell); + this.sizers = []; + + if ( + resizable || + (this.graph.isLabelMovable(this.state.cell) && + this.state.width >= 2 && + this.state.height >= 2) + ) { + let i = 0; + + if (resizable) { + if (!this.singleSizer) { + this.sizers.push(this.createSizer('nw-resize', i++)); + this.sizers.push(this.createSizer('n-resize', i++)); + this.sizers.push(this.createSizer('ne-resize', i++)); + this.sizers.push(this.createSizer('w-resize', i++)); + this.sizers.push(this.createSizer('e-resize', i++)); + this.sizers.push(this.createSizer('sw-resize', i++)); + this.sizers.push(this.createSizer('s-resize', i++)); + } + + this.sizers.push(this.createSizer('se-resize', i++)); + } + + const geo = this.state.cell.getGeometry(); + + if ( + geo != null && + !geo.relative && + //!this.graph.isSwimlane(this.state.cell) && disable for now + this.graph.isLabelMovable(this.state.cell) + ) { + // Marks this as the label handle for getHandleForEvent + this.labelShape = this.createSizer( + CURSOR_LABEL_HANDLE, + InternalEvent.LABEL_HANDLE, + LABEL_HANDLE_SIZE, + LABEL_HANDLE_FILLCOLOR + ); + this.sizers.push(this.labelShape); + } + } else if ( + this.graph.isCellMovable(this.state.cell) && + !this.graph.isCellResizable(this.state.cell) && + this.state.width < 2 && + this.state.height < 2 + ) { + this.labelShape = this.createSizer( + CURSOR_MOVABLE_VERTEX, + InternalEvent.LABEL_HANDLE, + undefined, + LABEL_HANDLE_FILLCOLOR + ); + this.sizers.push(this.labelShape); + } + } + + // Adds the rotation handler + if (this.isRotationHandleVisible()) { + this.rotationShape = this.createSizer( + this.rotationCursor, + InternalEvent.ROTATION_HANDLE, + HANDLE_SIZE + 3, + HANDLE_FILLCOLOR + ); + this.sizers.push(this.rotationShape); + } + + this.customHandles = this.createCustomHandles(); + this.redraw(); + + if (this.constrainGroupByChildren) { + this.updateMinBounds(); + } // Handles escape keystrokes - this.escapeHandler = (sender, evt) => { + this.escapeHandler = (sender: Listenable, evt: Event) => { if (this.livePreview && this.index != null) { // Redraws the live preview this.state.view.graph.cellRenderer.redraw(this.state, true); @@ -73,17 +181,17 @@ class VertexHandler { this.state.view.graph.addListener(InternalEvent.ESCAPE, this.escapeHandler); } - escapeHandler: Function; - selectionBounds?: Rectangle; - bounds?: Rectangle; - selectionBorder?: RectangleShape; + escapeHandler: (sender: Listenable, evt: Event) => void; + selectionBounds: Rectangle; + bounds: Rectangle; + selectionBorder: RectangleShape; /** * Variable: graph * * Reference to the enclosing . */ - graph?: Graph; + graph: MaxGraph; /** * Variable: state @@ -92,6 +200,8 @@ class VertexHandler { */ state: CellState; + sizers: Shape[] = []; + /** * Variable: singleSizer * @@ -220,120 +330,49 @@ class VertexHandler { */ verticalOffset: number = 0; - /** - * Function: init - * - * Initializes the shapes required for this vertex handler. - */ - init(): void { - this.graph = this.state.view.graph; - this.selectionBounds = this.getSelectionBounds(this.state); - this.bounds = new Rectangle( - this.selectionBounds.x, - this.selectionBounds.y, - this.selectionBounds.width, - this.selectionBounds.height - ); - this.selectionBorder = this.createSelectionShape(this.bounds); - // VML dialect required here for event transparency in IE - this.selectionBorder.dialect = DIALECT_SVG; - this.selectionBorder.pointerEvents = false; - this.selectionBorder.rotation = Number(this.state.style.rotation || '0'); - this.selectionBorder.init(this.graph.getView().getOverlayPane()); - InternalEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state); + minBounds: Rectangle | null = null; - if (this.graph.isCellMovable(this.state.cell)) { - this.selectionBorder.setCursor(CURSOR_MOVABLE_VERTEX); - } + x0 = 0; + y0 = 0; - // Adds the sizer handles - if ( - this.graph.graphHandler.maxCells <= 0 || - this.graph.selection.getSelectionCount() < this.graph.graphHandler.maxCells - ) { - const resizable = this.graph.isCellResizable(this.state.cell); - this.sizers = []; + customHandles: CellHandle[] = []; - if ( - resizable || - (this.graph.label.isLabelMovable(this.state.cell) && - this.state.width >= 2 && - this.state.height >= 2) - ) { - let i = 0; + inTolerance = false; - if (resizable) { - if (!this.singleSizer) { - this.sizers.push(this.createSizer('nw-resize', i++)); - this.sizers.push(this.createSizer('n-resize', i++)); - this.sizers.push(this.createSizer('ne-resize', i++)); - this.sizers.push(this.createSizer('w-resize', i++)); - this.sizers.push(this.createSizer('e-resize', i++)); - this.sizers.push(this.createSizer('sw-resize', i++)); - this.sizers.push(this.createSizer('s-resize', i++)); - } + startX = 0; + startY = 0; - this.sizers.push(this.createSizer('se-resize', i++)); - } + rotationShape: Shape | null = null; - const geo = this.state.cell.getGeometry(); + currentAlpha = 100; + startAngle = 0; + startDist = 0; - if ( - geo != null && - !geo.relative && - !this.graph.isSwimlane(this.state.cell) && - this.graph.isLabelMovable(this.state.cell) - ) { - // Marks this as the label handle for getHandleForEvent - this.labelShape = this.createSizer( - CURSOR_LABEL_HANDLE, - InternalEvent.LABEL_HANDLE, - LABEL_HANDLE_SIZE, - LABEL_HANDLE_FILLCOLOR - ); - this.sizers.push(this.labelShape); - } - } else if ( - this.graph.isCellMovable(this.state.cell) && - !this.graph.isCellResizable(this.state.cell) && - this.state.width < 2 && - this.state.height < 2 - ) { - this.labelShape = this.createSizer( - CURSOR_MOVABLE_VERTEX, - InternalEvent.LABEL_HANDLE, - null, - LABEL_HANDLE_FILLCOLOR - ); - this.sizers.push(this.labelShape); - } - } + ghostPreview: Shape | null = null; - // Adds the rotation handler - if (this.isRotationHandleVisible()) { - this.rotationShape = this.createSizer( - this.rotationCursor, - InternalEvent.ROTATION_HANDLE, - HANDLE_SIZE + 3, - HANDLE_FILLCOLOR - ); - this.sizers.push(this.rotationShape); - } + livePreviewActive = false; - this.customHandles = this.createCustomHandles(); - this.redraw(); + childOffsetX = 0; + childOffsetY = 0; - if (this.constrainGroupByChildren) { - this.updateMinBounds(); - } - } + parentState: CellState | null = null; + parentHighlight: RectangleShape | null = null; + + unscaledBounds: Rectangle | null = null; + + preview: Shape | null = null; + + labelShape: Shape | null = null; + + edgeHandlers: EdgeHandler[] = []; + + EMPTY_POINT = new Point(); /** * Function: isRotationHandleVisible * * Returns true if the rotation handle should be showing. */ - // isRotationHandleVisible(): boolean; isRotationHandleVisible() { return ( this.graph.isEnabled() && @@ -349,8 +388,7 @@ class VertexHandler { * * Returns true if the aspect ratio if the cell should be maintained. */ - // isConstrainedEvent(me: mxMouseEvent): boolean; - isConstrainedEvent(me) { + isConstrainedEvent(me: InternalMouseEvent) { return isShiftDown(me.getEvent()) || this.state.style.aspect === 'fixed'; } @@ -359,8 +397,7 @@ class VertexHandler { * * Returns true if the center of the vertex should be maintained during the resize. */ - // isCenteredEvent(state: mxCellState, me: mxMouseEvent): boolean; - isCenteredEvent(state, me) { + isCenteredEvent(state: CellState, me: InternalMouseEvent) { return false; } @@ -369,9 +406,8 @@ class VertexHandler { * * Returns an array of custom handles. This implementation returns null. */ - // createCustomHandles(): any[]; createCustomHandles() { - return null; + return []; } /** @@ -379,14 +415,13 @@ class VertexHandler { * * Initializes the shapes required for this vertex handler. */ - // updateMinBounds(): void; updateMinBounds() { const children = this.graph.getChildCells(this.state.cell); if (children.length > 0) { this.minBounds = this.graph.view.getBounds(children); - if (this.minBounds != null) { + if (this.minBounds) { const s = this.state.view.scale; const t = this.state.view.translate; @@ -408,8 +443,7 @@ class VertexHandler { * Returns the mxRectangle that defines the bounds of the selection * border. */ - // getSelectionBounds(state: mxCellState): mxRectangle; - getSelectionBounds(state) { + getSelectionBounds(state: CellState) { return new Rectangle( Math.round(state.x), Math.round(state.y), @@ -423,8 +457,7 @@ class VertexHandler { * * Creates the shape used to draw the selection border. */ - // createParentHighlightShape(bounds: mxRectangle): mxRectangleShape; - createParentHighlightShape(bounds) { + createParentHighlightShape(bounds: Rectangle) { return this.createSelectionShape(bounds); } @@ -433,11 +466,10 @@ class VertexHandler { * * Creates the shape used to draw the selection border. */ - // createSelectionShape(bounds: mxRectangle): mxRectangleShape; - createSelectionShape(bounds) { + createSelectionShape(bounds: Rectangle) { const shape = new RectangleShape( Rectangle.fromRectangle(bounds), - null, + NONE, this.getSelectionColor() ); shape.strokeWidth = this.getSelectionStrokeWidth(); @@ -451,7 +483,6 @@ class VertexHandler { * * Returns . */ - // getSelectionColor(): string; getSelectionColor() { return VERTEX_SELECTION_COLOR; } @@ -461,7 +492,6 @@ class VertexHandler { * * Returns . */ - // getSelectionStrokeWidth(): number; getSelectionStrokeWidth() { return VERTEX_SELECTION_STROKEWIDTH; } @@ -471,7 +501,6 @@ class VertexHandler { * * Returns . */ - // isSelectionDashed(): boolean; isSelectionDashed() { return VERTEX_SELECTION_DASHED; } @@ -482,16 +511,19 @@ class VertexHandler { * Creates a sizer handle for the specified cursor and index and returns * the new that represents the handle. */ - // createSizer(cursor: string, index: number, size: number, fillColor: string): mxRectangleShape; - createSizer(cursor, index, size, fillColor) { - size = size || HANDLE_SIZE; - + createSizer( + cursor: string, + index: number, + size = HANDLE_SIZE, + fillColor = HANDLE_FILLCOLOR + ) { const bounds = new Rectangle(0, 0, size, size); const sizer = this.createSizerShape(bounds, index, fillColor); if ( + sizer.bounds && sizer.isHtmlAllowed() && - this.state.text != null && + this.state.text && this.state.text.node.parentNode === this.graph.container ) { sizer.bounds.height -= 1; @@ -523,8 +555,7 @@ class VertexHandler { * Returns true if the sizer for the given index is visible. * This returns true for all given indices. */ - // isSizerVisible(index: number): boolean; - isSizerVisible(index) { + isSizerVisible(index: number) { return true; } @@ -535,9 +566,8 @@ class VertexHandler { * index. Only images and rectangles should be returned if support for HTML * labels with not foreign objects is required. */ - // createSizerShape(bounds: mxRectangle, index: number, fillColor: string): mxShape; - createSizerShape(bounds, index, fillColor) { - if (this.handleImage != null) { + createSizerShape(bounds: Rectangle, index: number, fillColor = HANDLE_FILLCOLOR) { + if (this.handleImage) { bounds = new Rectangle( bounds.x, bounds.y, @@ -552,9 +582,9 @@ class VertexHandler { return shape; } if (index === InternalEvent.ROTATION_HANDLE) { - return new EllipseShape(bounds, fillColor || HANDLE_FILLCOLOR, HANDLE_STROKECOLOR); + return new EllipseShape(bounds, fillColor, HANDLE_STROKECOLOR); } - return new RectangleShape(bounds, fillColor || HANDLE_FILLCOLOR, HANDLE_STROKECOLOR); + return new RectangleShape(bounds, fillColor, HANDLE_STROKECOLOR); } /** @@ -563,14 +593,13 @@ class VertexHandler { * Helper method to create an around the given centerpoint * with a width and height of 2*s or 6, if no s is given. */ - // moveSizerTo(shape: mxRectangleShape, x: number, y: number): void; - moveSizerTo(shape, x, y) { - if (shape != null) { + moveSizerTo(shape: Shape, x: number, y: number) { + if (shape && shape.bounds) { shape.bounds.x = Math.floor(x - shape.bounds.width / 2); shape.bounds.y = Math.floor(y - shape.bounds.height / 2); // Fixes visible inactive handles in VML - if (shape.node != null && shape.node.style.display !== 'none') { + if (shape.node && shape.node.style.display !== 'none') { shape.redraw(); } } @@ -582,8 +611,7 @@ class VertexHandler { * Returns the index of the handle for the given event. This returns the index * of the sizer from where the event originated or . */ - // getHandleForEvent(me: mxMouseEvent): number; - getHandleForEvent(me) { + getHandleForEvent(me: InternalMouseEvent) { // Connection highlight may consume events before they reach sizer handle const tol = !isMouseEvent(me.getEvent()) ? this.tolerance : 1; const hit = @@ -591,25 +619,25 @@ class VertexHandler { ? new Rectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null; - const checkShape = (shape) => { + const checkShape = (shape: Shape | null) => { const st = - shape != null && shape.constructor !== ImageShape && this.allowHandleBoundsCheck - ? shape.strokewidth + shape.svgStrokeTolerance + shape && shape.constructor !== ImageShape && this.allowHandleBoundsCheck + ? shape.strokeWidth + shape.svgStrokeTolerance : null; - const real = - st != null - ? new Rectangle( - me.getGraphX() - Math.floor(st / 2), - me.getGraphY() - Math.floor(st / 2), - st, - st - ) - : hit; + const real = st + ? new Rectangle( + me.getGraphX() - Math.floor(st / 2), + me.getGraphY() - Math.floor(st / 2), + st, + st + ) + : hit; return ( - shape != null && + shape && + shape.bounds && (me.isSource(shape) || - (real != null && + (real && intersects(shape.bounds, real) && shape.node.style.display !== 'none' && shape.node.style.visibility !== 'hidden')) @@ -623,11 +651,9 @@ class VertexHandler { return InternalEvent.LABEL_HANDLE; } - if (this.sizers != null) { - for (let i = 0; i < this.sizers.length; i += 1) { - if (checkShape(this.sizers[i])) { - return i; - } + for (let i = 0; i < this.sizers.length; i += 1) { + if (checkShape(this.sizers[i])) { + return i; } } @@ -650,8 +676,7 @@ class VertexHandler { * Returns true if the given event allows custom handles to be changed. This * implementation returns true. */ - // isCustomHandleEvent(me: mxMouseEvent): boolean; - isCustomHandleEvent(me) { + isCustomHandleEvent(me: InternalMouseEvent) { return true; } @@ -662,12 +687,11 @@ class VertexHandler { * 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.graph.isEnabled()) { const handle = this.getHandleForEvent(me); - if (handle != null) { + if (handle) { this.start(me.getGraphX(), me.getGraphY(), handle); me.consume(); } @@ -680,12 +704,11 @@ class VertexHandler { * Called if is enabled to check if a border should be painted. * This implementation returns true if the shape is transparent. */ - // isLivePreviewBorder(): boolean; isLivePreviewBorder() { return ( - this.state.shape != null && - this.state.shape.fill == null && - this.state.shape.stroke == null + this.state.shape && + this.state.shape.fill === NONE && + this.state.shape.stroke === NONE ); } @@ -694,87 +717,84 @@ class VertexHandler { * * Starts the handling of the mouse gesture. */ - // start(x: number, y: number, index: number): void; - start(x, y, index) { - if (this.selectionBorder != null) { - this.livePreviewActive = this.livePreview && this.state.cell.getChildCount() === 0; - this.inTolerance = true; - this.childOffsetX = 0; - this.childOffsetY = 0; - this.index = index; - this.startX = x; - this.startY = y; + start(x: number, y: number, index: number) { + this.livePreviewActive = this.livePreview && this.state.cell.getChildCount() === 0; + this.inTolerance = true; + this.childOffsetX = 0; + this.childOffsetY = 0; + this.index = index; + this.startX = x; + this.startY = y; - if (this.index <= InternalEvent.CUSTOM_HANDLE && this.isGhostPreview()) { - this.ghostPreview = this.createGhostPreview(); - } else { - // Saves reference to parent state - const { model } = this.state.view.graph; - const parent = this.state.cell.getParent(); + if (this.index <= InternalEvent.CUSTOM_HANDLE && this.isGhostPreview()) { + this.ghostPreview = this.createGhostPreview(); + } else { + // Saves reference to parent state + const { model } = this.state.view.graph; + const parent = this.state.cell.getParent(); + + if ( + this.state.view.currentRoot !== parent && + (parent.isVertex() || parent.isEdge()) + ) { + this.parentState = this.state.view.graph.view.getState(parent); + } + + // Creates a preview that can be on top of any HTML label + this.selectionBorder.node.style.display = + index === InternalEvent.ROTATION_HANDLE ? 'inline' : 'none'; + + // Creates the border that represents the new bounds + if (!this.livePreviewActive || this.isLivePreviewBorder()) { + this.preview = this.createSelectionShape(this.bounds); if ( - this.state.view.currentRoot !== parent && - (parent.isVertex() || parent.isEdge()) + !(mxClient.IS_SVG && Number(this.state.style.rotation || '0') !== 0) && + this.state.text != null && + this.state.text.node.parentNode === this.graph.container ) { - this.parentState = this.state.view.graph.view.getState(parent); + this.preview.dialect = DIALECT_STRICTHTML; + this.preview.init(this.graph.container); + } else { + this.preview.dialect = DIALECT_SVG; + this.preview.init(this.graph.view.getOverlayPane()); + } + } + + if (index === InternalEvent.ROTATION_HANDLE) { + // With the rotation handle in a corner, need the angle and distance + const pos = this.getRotationHandlePosition(); + + const dx = pos.x - this.state.getCenterX(); + const dy = pos.y - this.state.getCenterY(); + + this.startAngle = dx !== 0 ? (Math.atan(dy / dx) * 180) / Math.PI + 90 : 0; + this.startDist = Math.sqrt(dx * dx + dy * dy); + } + + // Prepares the handles for live preview + if (this.livePreviewActive) { + this.hideSizers(); + + if (index === InternalEvent.ROTATION_HANDLE && this.rotationShape) { + this.rotationShape.node.style.display = ''; + } else if (index === InternalEvent.LABEL_HANDLE && this.labelShape) { + this.labelShape.node.style.display = ''; + } else if (this.sizers[index]) { + this.sizers[index].node.style.display = ''; + } else if (index <= InternalEvent.CUSTOM_HANDLE) { + this.customHandles[InternalEvent.CUSTOM_HANDLE - index].setVisible(true); } - // Creates a preview that can be on top of any HTML label - this.selectionBorder.node.style.display = - index === InternalEvent.ROTATION_HANDLE ? 'inline' : 'none'; + // Gets the array of connected edge handlers for redrawing + const edges = this.graph.getEdges(this.state.cell); + this.edgeHandlers = []; - // Creates the border that represents the new bounds - if (!this.livePreviewActive || this.isLivePreviewBorder()) { - this.preview = this.createSelectionShape(this.bounds); + for (let i = 0; i < edges.length; i += 1) { + const handler = this.graph.selectionCellsHandler.getHandler(edges[i]); - if ( - !(mxClient.IS_SVG && Number(this.state.style.rotation || '0') !== 0) && - this.state.text != null && - this.state.text.node.parentNode === this.graph.container - ) { - this.preview.dialect = DIALECT_STRICTHTML; - this.preview.init(this.graph.container); - } else { - this.preview.dialect = DIALECT_SVG; - this.preview.init(this.graph.view.getOverlayPane()); - } - } - - if (index === InternalEvent.ROTATION_HANDLE) { - // With the rotation handle in a corner, need the angle and distance - const pos = this.getRotationHandlePosition(); - - const dx = pos.x - this.state.getCenterX(); - const dy = pos.y - this.state.getCenterY(); - - this.startAngle = dx !== 0 ? (Math.atan(dy / dx) * 180) / Math.PI + 90 : 0; - this.startDist = Math.sqrt(dx * dx + dy * dy); - } - - // Prepares the handles for live preview - if (this.livePreviewActive) { - this.hideSizers(); - - if (index === InternalEvent.ROTATION_HANDLE) { - this.rotationShape.node.style.display = ''; - } else if (index === InternalEvent.LABEL_HANDLE) { - this.labelShape.node.style.display = ''; - } else if (this.sizers != null && this.sizers[index] != null) { - this.sizers[index].node.style.display = ''; - } else if (index <= InternalEvent.CUSTOM_HANDLE && this.customHandles != null) { - this.customHandles[InternalEvent.CUSTOM_HANDLE - index].setVisible(true); - } - - // Gets the array of connected edge handlers for redrawing - 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]); - - if (handler != null) { - this.edgeHandlers.push(handler); - } + if (handler != null) { + this.edgeHandlers.push(handler); } } } @@ -801,20 +821,15 @@ class VertexHandler { * * Shortcut to . */ - // setHandlesVisible(visible: boolean): void; - setHandlesVisible(visible) { + setHandlesVisible(visible: boolean) { this.handlesVisible = visible; - if (this.sizers != null) { - for (let i = 0; i < this.sizers.length; i += 1) { - this.sizers[i].node.style.display = visible ? '' : 'none'; - } + for (let i = 0; i < this.sizers.length; i += 1) { + this.sizers[i].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); } } @@ -825,7 +840,6 @@ class VertexHandler { * * Starts the handling of the mouse gesture. */ - // hideSizers(): void; hideSizers() { this.setHandlesVisible(false); } @@ -837,13 +851,12 @@ class VertexHandler { * . If the event is a mouse event then the tolerance is * ignored. */ - // checkTolerance(me: mxMouseEvent): void; - checkTolerance(me) { - if (this.inTolerance && this.startX != null && this.startY != null) { + checkTolerance(me: InternalMouseEvent) { + if (this.inTolerance && this.startX !== null && this.startY !== null) { if ( isMouseEvent(me.getEvent()) || - Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance || - Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance + Math.abs(me.getGraphX() - this.startX) > this.graph.getEventTolerance() || + Math.abs(me.getGraphY() - this.startY) > this.graph.getEventTolerance() ) { this.inTolerance = false; } @@ -855,15 +868,13 @@ class VertexHandler { * * 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() {} /** @@ -872,7 +883,7 @@ class VertexHandler { * Hook for rounding the angle. This uses Math.round. */ // roundAngle(angle: number): number; - roundAngle(angle) { + roundAngle(angle: number) { return Math.round(angle * 10) / 10; } @@ -881,8 +892,7 @@ class VertexHandler { * * Hook for rounding the unscaled width or height. This uses Math.round. */ - // roundLength(length: number): number; - roundLength(length) { + roundLength(length: number) { return Math.round(length * 100) / 100; } @@ -891,8 +901,7 @@ class VertexHandler { * * Handles the event by updating the preview. */ - // mouseMove(sender: any, me: mxMouseEvent): void; - mouseMove(sender, me) { + mouseMove(sender: EventSource, me: InternalMouseEvent) { if (!me.isConsumed() && this.index != null) { // Checks tolerance for ignoring single clicks this.checkTolerance(me); @@ -905,7 +914,7 @@ class VertexHandler { if (this.ghostPreview != null) { this.ghostPreview.apply(this.state); - this.ghostPreview.strokewidth = + this.ghostPreview.strokeWidth = this.getSelectionStrokeWidth() / this.ghostPreview.scale / this.ghostPreview.scale; @@ -942,7 +951,7 @@ class VertexHandler { me.consume(); } // Workaround for disabling the connect highlight when over handle - else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null) { + else if (!this.graph.isMouseDown && this.getHandleForEvent(me)) { me.consume(false); } } @@ -961,8 +970,7 @@ class VertexHandler { * * Rotates the vertex. */ - // moveLabel(me: mxMouseEvent): void; - moveLabel(me) { + moveLabel(me: InternalMouseEvent) { const point = new Point(me.getGraphX(), me.getGraphY()); const tr = this.graph.view.translate; const { scale } = this.graph.view; @@ -972,8 +980,7 @@ class VertexHandler { point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale; } - const index = - this.rotationShape != null ? this.sizers.length - 2 : this.sizers.length - 1; + const index = this.rotationShape ? this.sizers.length - 2 : this.sizers.length - 1; this.moveSizerTo(this.sizers[index], point.x, point.y); } @@ -982,8 +989,7 @@ class VertexHandler { * * Rotates the vertex. */ - // rotateVertex(me: mxMouseEvent): void; - rotateVertex(me) { + rotateVertex(me: InternalMouseEvent) { const point = new Point(me.getGraphX(), me.getGraphY()); let dx = this.state.x + this.state.width / 2 - point.x; let dy = this.state.y + this.state.height / 2 - point.y; @@ -1029,10 +1035,9 @@ class VertexHandler { * * Rotates the vertex. */ - // resizeVertex(me: mxMouseEvent): void; - resizeVertex(me) { - const ct = new point(this.state.getCenterX(), this.state.getCenterY()); - const alpha = toRadians(this.state.style.rotation || '0'); + resizeVertex(me: InternalMouseEvent) { + const ct = new Point(this.state.getCenterX(), this.state.getCenterY()); + const alpha = toRadians(this.state.style.rotation); const point = new Point(me.getGraphX(), me.getGraphY()); const tr = this.graph.view.translate; const { scale } = this.graph.view; @@ -1050,20 +1055,22 @@ class VertexHandler { dy = ty; const geo = this.state.cell.getGeometry(); - this.unscaledBounds = this.union( - geo, - dx / scale, - dy / scale, - this.index, - this.graph.isGridEnabledEvent(me.getEvent()), - 1, - new point(0, 0), - this.isConstrainedEvent(me), - this.isCenteredEvent(this.state, me) - ); + if (geo && this.index !== null) { + this.unscaledBounds = this.union( + geo, + dx / scale, + dy / scale, + this.index, + this.graph.isGridEnabledEvent(me.getEvent()), + 1, + new Point(0, 0), + this.isConstrainedEvent(me), + this.isCenteredEvent(this.state, me) + ); + } // Keeps vertex within maximum graph or parent bounds - if (!geo.relative) { + if (geo && !geo.relative) { let max = this.graph.getMaximumGraphBounds(); // Handles child cells @@ -1089,7 +1096,7 @@ class VertexHandler { tmp.height += 2 * tmp.height * overlap; } - if (max == null) { + if (!max) { max = tmp; } else { max = Rectangle.fromRectangle(max); @@ -1098,7 +1105,7 @@ class VertexHandler { } } - if (max != null) { + if (max && this.unscaledBounds) { if (this.unscaledBounds.x < max.x) { this.unscaledBounds.width -= max.x - this.unscaledBounds.x; this.unscaledBounds.x = max.x; @@ -1121,68 +1128,71 @@ class VertexHandler { } } - const old = this.bounds; - this.bounds = new Rectangle( - (this.parentState != null ? this.parentState.x : tr.x * scale) + - this.unscaledBounds.x * scale, - (this.parentState != null ? this.parentState.y : tr.y * scale) + - this.unscaledBounds.y * scale, - this.unscaledBounds.width * scale, - this.unscaledBounds.height * scale - ); + if (this.unscaledBounds) { + const old = this.bounds; - if (geo.relative && this.parentState != null) { - this.bounds.x += this.state.x - this.parentState.x; - this.bounds.y += this.state.y - this.parentState.y; - } + this.bounds = new Rectangle( + (this.parentState ? this.parentState.x : tr.x * scale) + + this.unscaledBounds.x * scale, + (this.parentState ? this.parentState.y : tr.y * scale) + + this.unscaledBounds.y * scale, + this.unscaledBounds.width * scale, + this.unscaledBounds.height * scale + ); - cos = Math.cos(alpha); - sin = Math.sin(alpha); - - const c2 = new point(this.bounds.getCenterX(), this.bounds.getCenterY()); - - dx = c2.x - ct.x; - dy = c2.y - ct.y; - - const dx2 = cos * dx - sin * dy; - const dy2 = sin * dx + cos * dy; - - const dx3 = dx2 - dx; - const dy3 = dy2 - dy; - - const dx4 = this.bounds.x - this.state.x; - const dy4 = this.bounds.y - this.state.y; - - const dx5 = cos * dx4 - sin * dy4; - const dy5 = sin * dx4 + cos * dy4; - - this.bounds.x += dx3; - this.bounds.y += dy3; - - // Rounds unscaled bounds to int - this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale); - this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale); - this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width); - this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height); - - // Shifts the children according to parent offset - if (!this.state.cell.isCollapsed() && (dx3 !== 0 || dy3 !== 0)) { - this.childOffsetX = this.state.x - this.bounds.x + dx5; - this.childOffsetY = this.state.y - this.bounds.y + dy5; - } else { - this.childOffsetX = 0; - this.childOffsetY = 0; - } - - if (!old.equals(this.bounds)) { - if (this.livePreviewActive) { - this.updateLivePreview(me); + if (geo && geo.relative && this.parentState) { + this.bounds.x += this.state.x - this.parentState.x; + this.bounds.y += this.state.y - this.parentState.y; } - if (this.preview != null) { - this.drawPreview(); + cos = Math.cos(alpha); + sin = Math.sin(alpha); + + const c2 = new Point(this.bounds.getCenterX(), this.bounds.getCenterY()); + + dx = c2.x - ct.x; + dy = c2.y - ct.y; + + const dx2 = cos * dx - sin * dy; + const dy2 = sin * dx + cos * dy; + + const dx3 = dx2 - dx; + const dy3 = dy2 - dy; + + const dx4 = this.bounds.x - this.state.x; + const dy4 = this.bounds.y - this.state.y; + + const dx5 = cos * dx4 - sin * dy4; + const dy5 = sin * dx4 + cos * dy4; + + this.bounds.x += dx3; + this.bounds.y += dy3; + + // Rounds unscaled bounds to int + this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale); + this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale); + this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width); + this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height); + + // Shifts the children according to parent offset + if (!this.state.cell.isCollapsed() && (dx3 !== 0 || dy3 !== 0)) { + this.childOffsetX = this.state.x - this.bounds.x + dx5; + this.childOffsetY = this.state.y - this.bounds.y + dy5; } else { - this.updateParentHighlight(); + this.childOffsetX = 0; + this.childOffsetY = 0; + } + + if (!old.equals(this.bounds)) { + if (this.livePreviewActive) { + this.updateLivePreview(me); + } + + if (this.preview != null) { + this.drawPreview(); + } else { + this.updateParentHighlight(); + } } } } @@ -1192,8 +1202,7 @@ class VertexHandler { * * Repaints the live preview. */ - // updateLivePreview(me: mxMouseEvent): void; - updateLivePreview(me) { + updateLivePreview(me: InternalMouseEvent) { // TODO: Apply child offset to children in live preview const { scale } = this.graph.view; const tr = this.graph.view.translate; @@ -1261,20 +1270,17 @@ class VertexHandler { */ moveToFront() { if ( - (this.state.text != null && - this.state.text.node != null && - this.state.text.node.nextSibling != null) || - (this.state.shape != null && - this.state.shape.node != null && - this.state.shape.node.nextSibling != null && - (this.state.text == null || - this.state.shape.node.nextSibling !== this.state.text.node)) + (this.state.text && this.state.text.node && this.state.text.node.nextSibling) || + (this.state.shape && + this.state.shape.node && + this.state.shape.node.nextSibling && + (!this.state.text || this.state.shape.node.nextSibling !== this.state.text.node)) ) { - if (this.state.shape != null && this.state.shape.node != null) { + if (this.state.shape && this.state.shape.node && this.state.shape.node.parentNode) { this.state.shape.node.parentNode.appendChild(this.state.shape.node); } - if (this.state.text != null && this.state.text.node != null) { + if (this.state.text && this.state.text.node && this.state.text.node.parentNode) { this.state.text.node.parentNode.appendChild(this.state.text.node); } } @@ -1285,8 +1291,7 @@ class VertexHandler { * * Handles the event by applying the changes to the geometry. */ - // mouseUp(sender: any, me: mxMouseEvent): void; - mouseUp(sender, me) { + mouseUp(sender: EventSource, me: InternalMouseEvent) { if (this.index != null && this.state != null) { const point = new Point(me.getGraphX(), me.getGraphY()); const { index } = this; @@ -1330,7 +1335,7 @@ class VertexHandler { } } else { const gridEnabled = this.graph.isGridEnabledEvent(me.getEvent()); - const alpha = toRadians(this.state.style.rotation || '0'); + const alpha = toRadians(this.state.style.rotation); const cos = Math.cos(-alpha); const sin = Math.sin(-alpha); @@ -1371,8 +1376,7 @@ class VertexHandler { * * Rotates the given cell to the given rotation. */ - // isRecursiveResize(state: mxCellState, me: mxMouseEvent): boolean; - isRecursiveResize(state, me) { + isRecursiveResize(state: CellState, me: InternalMouseEvent) { return this.graph.isRecursiveResize(this.state); } @@ -1383,7 +1387,6 @@ class VertexHandler { * This code is executed as part of the model transaction. This implementation * is empty. */ - // rotateClick(): void; rotateClick() {} /** @@ -1396,8 +1399,7 @@ class VertexHandler { * cell - to be rotated. * angle - Angle in degrees. */ - // rotateCell(cell: mxCell, angle: number, parent: mxCell): void; - rotateCell(cell, angle, parent) { + rotateCell(cell: Cell, angle: number, parent?: Cell) { if (angle !== 0) { const model = this.graph.getModel(); @@ -1405,12 +1407,12 @@ class VertexHandler { if (!cell.isEdge()) { const style = this.graph.getCurrentCellStyle(cell); const total = (style.rotation || 0) + angle; - this.graph.setCellStyles('rotation', total, [cell]); + this.graph.setCellStyles('rotation', total, new CellArray(cell)); } let geo = cell.getGeometry(); - if (geo != null) { + if (geo && parent) { const pgeo = parent.getGeometry(); if (pgeo != null && !parent.isEdge()) { @@ -1437,75 +1439,60 @@ class VertexHandler { * * Resets the state of this handler. */ - // reset(): void; reset() { - if ( - this.sizers != null && - this.index != null && - this.sizers[this.index] != null && - this.sizers[this.index].node.style.display === 'none' - ) { + if (this.index !== null && this.sizers[this.index].node.style.display === 'none') { this.sizers[this.index].node.style.display = ''; } - this.currentAlpha = null; - this.inTolerance = null; this.index = null; // TODO: Reset and redraw cell states for live preview - if (this.preview != null) { + if (this.preview) { this.preview.destroy(); this.preview = null; } - if (this.ghostPreview != null) { + if (this.ghostPreview) { this.ghostPreview.destroy(); this.ghostPreview = null; } - if (this.livePreviewActive && this.sizers != null) { + if (this.livePreviewActive) { for (let i = 0; i < this.sizers.length; i += 1) { - if (this.sizers[i] != null) { - this.sizers[i].node.style.display = ''; - } + this.sizers[i].node.style.display = ''; } // Shows folding icon - if (this.state.control != null && this.state.control.node != null) { + if (this.state.control && this.state.control.node) { this.state.control.node.style.visibility = ''; } } - if (this.customHandles != null) { - for (let i = 0; i < this.customHandles.length; i += 1) { - if (this.customHandles[i].active) { - this.customHandles[i].active = false; - this.customHandles[i].reset(); - } else { - this.customHandles[i].setVisible(true); - } + for (let i = 0; i < this.customHandles.length; i += 1) { + if (this.customHandles[i].active) { + this.customHandles[i].active = false; + this.customHandles[i].reset(); + } else { + this.customHandles[i].setVisible(true); } } // Checks if handler has been destroyed - if (this.selectionBorder != null) { - this.selectionBorder.node.style.display = 'inline'; - this.selectionBounds = this.getSelectionBounds(this.state); - this.bounds = new Rectangle( - this.selectionBounds.x, - this.selectionBounds.y, - this.selectionBounds.width, - this.selectionBounds.height - ); - this.drawPreview(); - } + this.selectionBorder.node.style.display = 'inline'; + this.selectionBounds = this.getSelectionBounds(this.state); + this.bounds = new Rectangle( + this.selectionBounds.x, + this.selectionBounds.y, + this.selectionBounds.width, + this.selectionBounds.height + ); + this.drawPreview(); this.removeHint(); this.redrawHandles(); - this.edgeHandlers = null; + this.edgeHandlers = []; this.handlesVisible = true; this.unscaledBounds = null; - this.livePreviewActive = null; } /** @@ -1514,12 +1501,20 @@ class VertexHandler { * Uses the given vector to change the bounds of the given cell * in the graph using . */ - resizeCell(cell, dx, dy, index, gridEnabled, constrained, recurse) { + resizeCell( + cell: Cell, + dx: number, + dy: number, + index: number, + gridEnabled: boolean, + constrained: boolean, + recurse: boolean + ) { let geo = cell.getGeometry(); - if (geo != null) { + if (geo && this.labelShape && this.labelShape.bounds) { if (index === InternalEvent.LABEL_HANDLE) { - const alpha = -toRadians(this.state.style.rotation || '0'); + const alpha = -toRadians(this.state.style.rotation); const cos = Math.cos(alpha); const sin = Math.sin(alpha); const { scale } = this.graph.view; @@ -1542,7 +1537,7 @@ class VertexHandler { } this.graph.model.setGeometry(cell, geo); - } else if (this.unscaledBounds != null) { + } else if (this.unscaledBounds) { const { scale } = this.graph.view; if (this.childOffsetX !== 0 || this.childOffsetY !== 0) { @@ -1563,8 +1558,7 @@ class VertexHandler { * * Moves the children of the given cell by the given vector. */ - // moveChildren(cell: mxCell, dx: number, dy: number): void; - moveChildren(cell, dx, dy) { + moveChildren(cell: Cell, dx: number, dy: number) { const model = this.graph.getModel(); const childCount = cell.getChildCount(); @@ -1632,11 +1626,18 @@ class VertexHandler { * }; * (end) */ - union(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered) { - gridEnabled = - gridEnabled != null - ? gridEnabled && this.graph.gridEnabled - : this.graph.gridEnabled; + union( + bounds: Rectangle, + dx: number, + dy: number, + index: number, + gridEnabled: boolean, + scale: number, + tr: Point, + constrained: boolean, + centered: boolean + ) { + gridEnabled = gridEnabled && this.graph.isGridEnabled(); if (this.singleSizer) { let x = bounds.x + bounds.width + dx; @@ -1770,8 +1771,7 @@ class VertexHandler { * * Redraws the handles and the preview. */ - // redraw(): void; - redraw(ignoreHandles) { + redraw(ignoreHandles?: boolean) { this.selectionBounds = this.getSelectionBounds(this.state); this.bounds = new Rectangle( this.selectionBounds.x, @@ -1789,16 +1789,14 @@ class VertexHandler { /** * Returns the padding to be used for drawing handles for the current . */ - // getHandlePadding(): mxPoint; getHandlePadding() { // KNOWN: Tolerance depends on event type (eg. 0 for mouse events) const result = new Point(0, 0); let tol = this.tolerance; if ( - this.sizers != null && this.sizers.length > 0 && - this.sizers[0] != null && + this.sizers[0].bounds && (this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol || this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol) ) { @@ -1838,29 +1836,30 @@ class VertexHandler { * }; * (end) */ - // redrawHandles(): void; redrawHandles() { let s = this.getSizerBounds(); const tol = this.tolerance; this.horizontalOffset = 0; this.verticalOffset = 0; - 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 = + shape.node.style.visibility = this.handlesVisible && this.isCustomHandleVisible(this.customHandles[i]) ? '' : 'hidden'; } } - if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null) { - if (this.index == null && this.manageSizers && this.sizers.length >= 8) { + if (this.sizers.length > 0 && this.sizers[0]) { + if (this.index === null && this.manageSizers && this.sizers.length >= 8) { // KNOWN: Tolerance depends on event type (eg. 0 for mouse events) const padding = this.getHandlePadding(); this.horizontalOffset = padding.x; @@ -1877,8 +1876,9 @@ class VertexHandler { if (this.sizers.length >= 8) { if ( - s.width < 2 * this.sizers[0].bounds.width + 2 * tol || - s.height < 2 * this.sizers[0].bounds.height + 2 * tol + this.sizers[0].bounds && + (s.width < 2 * this.sizers[0].bounds.width + 2 * tol || + s.height < 2 * this.sizers[0].bounds.height + 2 * tol) ) { this.sizers[0].node.style.display = 'none'; this.sizers[2].node.style.display = 'none'; @@ -1914,7 +1914,7 @@ class VertexHandler { 'w-resize', ]; - const alpha = toRadians(this.state.style.rotation || '0'); + const alpha = toRadians(this.state.style.rotation); const cos = Math.cos(alpha); const sin = Math.sin(alpha); const da = Math.round((alpha * 4) / Math.PI); @@ -1982,10 +1982,8 @@ class VertexHandler { } } - if (this.rotationShape != null) { - const alpha = toRadians( - this.currentAlpha != null ? this.currentAlpha : this.state.style.rotation || '0' - ); + if (this.rotationShape) { + const alpha = toRadians(this.currentAlpha); const cos = Math.cos(alpha); const sin = Math.sin(alpha); @@ -2017,7 +2015,7 @@ class VertexHandler { * * Returns true if the given custom handle is visible. */ - isCustomHandleVisible(handle) { + isCustomHandleVisible(handle: CellHandle) { return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() === 1; } @@ -2026,7 +2024,6 @@ class VertexHandler { * * Returns an that defines the rotation handle position. */ - // getRotationHandlePosition(): mxPoint; getRotationHandlePosition() { return new Point( this.bounds.x + this.bounds.width / 2, @@ -2049,19 +2046,19 @@ class VertexHandler { * * Updates the highlight of the parent if is true. */ - // updateParentHighlight(): void; updateParentHighlight() { if (!this.isDestroyed()) { const visible = this.isParentHighlightVisible(); const parent = this.state.cell.getParent(); const pstate = this.graph.view.getState(parent); - if (this.parentHighlight != null) { + if (this.parentHighlight) { if (parent.isVertex() && visible) { const b = this.parentHighlight.bounds; if ( - pstate != null && + pstate && + b && (b.x !== pstate.x || b.y !== pstate.y || b.width !== pstate.width || @@ -2144,21 +2141,19 @@ class VertexHandler { */ // destroy(): void; destroy() { - if (this.escapeHandler != null) { - this.state.view.graph.removeListener(this.escapeHandler); - this.escapeHandler = null; - } + this.state.view.graph.removeListener(this.escapeHandler); + this.escapeHandler = () => {}; - if (this.preview != null) { + if (this.preview) { this.preview.destroy(); this.preview = null; } - if (this.parentHighlight != null) { + if (this.parentHighlight) { const parent = this.state.cell.getParent(); const pstate = this.graph.view.getState(parent); - if (pstate != null && pstate.parentHighlight === this.parentHighlight) { + if (pstate && pstate.parentHighlight === this.parentHighlight) { pstate.parentHighlight = null; } @@ -2166,33 +2161,24 @@ class VertexHandler { this.parentHighlight = null; } - if (this.ghostPreview != null) { + if (this.ghostPreview) { this.ghostPreview.destroy(); this.ghostPreview = null; } - if (this.selectionBorder != null) { + if (this.selectionBorder) { this.selectionBorder.destroy(); - this.selectionBorder = null; } this.labelShape = null; this.removeHint(); - if (this.sizers != null) { - for (let i = 0; i < this.sizers.length; i += 1) { - this.sizers[i].destroy(); - } - - this.sizers = null; + for (let i = 0; i < this.sizers.length; i += 1) { + this.sizers[i].destroy(); } - if (this.customHandles != null) { - for (let i = 0; i < this.customHandles.length; i += 1) { - this.customHandles[i].destroy(); - } - - this.customHandles = null; + for (let i = 0; i < this.customHandles.length; i += 1) { + this.customHandles[i].destroy(); } } } diff --git a/packages/core/src/view/connection/ConnectionConstraint.ts b/packages/core/src/view/connection/ConnectionConstraint.ts index e7e627f63..77b9c4950 100644 --- a/packages/core/src/view/connection/ConnectionConstraint.ts +++ b/packages/core/src/view/connection/ConnectionConstraint.ts @@ -13,17 +13,17 @@ import Point from '../geometry/Point'; */ class ConnectionConstraint { constructor( - point: Point | null = null, - perimeter: boolean = true, + point: Point | null, + perimeter = true, name: string | null = null, - dx: number | null = null, - dy: number | null = null + dx = 0, + dy = 0 ) { this.point = point; - this.perimeter = perimeter != null ? perimeter : true; + this.perimeter = perimeter; this.name = name; - this.dx = dx || 0; - this.dy = dy || 0; + this.dx = dx; + this.dy = dy; } /** @@ -31,7 +31,7 @@ class ConnectionConstraint { * * that specifies the fixed location of the connection point. */ - point: Point | null = null; + point: Point | null; /** * Variable: perimeter @@ -39,7 +39,7 @@ class ConnectionConstraint { * Boolean that specifies if the point should be projected onto the perimeter * of the terminal. */ - perimeter: boolean = true; + perimeter = true; /** * Variable: name @@ -53,14 +53,14 @@ class ConnectionConstraint { * * Optional float that specifies the horizontal offset of the constraint. */ - dx: number | null = null; + dx = 0; /** * Variable: dy * * Optional float that specifies the vertical offset of the constraint. */ - dy: number | null = null; + dy = 0; } export default ConnectionConstraint; diff --git a/packages/core/src/view/connection/ConnectionHandler.ts b/packages/core/src/view/connection/ConnectionHandler.ts index 7154e7d1c..5c91bc4b1 100644 --- a/packages/core/src/view/connection/ConnectionHandler.ts +++ b/packages/core/src/view/connection/ConnectionHandler.ts @@ -14,6 +14,7 @@ import { DIALECT_SVG, HIGHLIGHT_STROKEWIDTH, INVALID_COLOR, + NONE, OUTLINE_HIGHLIGHT_COLOR, OUTLINE_HIGHLIGHT_STROKEWIDTH, TOOLTIP_VERTICAL_OFFSET, @@ -41,12 +42,13 @@ import { isConsumed, isShiftDown, } from '../../util/EventUtils'; -import graph from '../Graph'; +import graph, { MaxGraph } from '../Graph'; import Image from '../image/ImageBox'; import CellState from '../cell/datatypes/CellState'; import Graph from '../Graph'; import ConnectionConstraint from './ConnectionConstraint'; import Shape from '../geometry/shape/Shape'; +import { Listenable } from '../../types'; type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell; @@ -207,12 +209,45 @@ type FactoryMethod = (source: Cell, target: Cell, style?: string) => Cell; * the that represents the new edge. */ class ConnectionHandler extends EventSource { - constructor(graph: Graph, factoryMethod: FactoryMethod | null = null) { + constructor(graph: MaxGraph, factoryMethod: FactoryMethod | null = null) { super(); this.graph = graph; this.factoryMethod = factoryMethod; - this.init(); + + this.graph.addMouseListener(this); + this.marker = this.createMarker(); + this.constraintHandler = new ConstraintHandler(this.graph); + + // Redraws the icons if the graph changes + this.changeHandler = (sender: Listenable) => { + if (this.iconState) { + this.iconState = this.graph.getView().getState(this.iconState.cell); + } + + if (this.iconState) { + this.redrawIcons(this.icons, this.iconState); + this.constraintHandler.reset(); + } else if (this.previous && !this.graph.view.getState(this.previous.cell)) { + this.reset(); + } + }; + + this.graph.getModel().addListener(InternalEvent.CHANGE, this.changeHandler); + this.graph.getView().addListener(InternalEvent.SCALE, this.changeHandler); + this.graph.getView().addListener(InternalEvent.TRANSLATE, this.changeHandler); + this.graph + .getView() + .addListener(InternalEvent.SCALE_AND_TRANSLATE, this.changeHandler); + + // Removes the icon if we step into/up or start editing + this.drillHandler = (sender: Listenable) => { + this.reset(); + }; + + this.graph.addListener(InternalEvent.START_EDITING, this.drillHandler); + this.graph.getView().addListener(InternalEvent.DOWN, this.drillHandler); + this.graph.getView().addListener(InternalEvent.UP, this.drillHandler); // Handles escape keystrokes this.escapeHandler = () => { @@ -225,7 +260,7 @@ class ConnectionHandler extends EventSource { // TODO: Document me! previous: CellState | null = null; iconState: CellState | null = null; - icons: ImageShape[] | null = null; + icons: ImageShape[] = []; cell: Cell | null = null; currentPoint: Point | null = null; sourceConstraint: ConnectionConstraint | null = null; @@ -241,7 +276,7 @@ class ConnectionHandler extends EventSource { * * Reference to the enclosing . */ - graph: Graph; + graph: MaxGraph; /** * Variable: factoryMethod @@ -319,7 +354,6 @@ class ConnectionHandler extends EventSource { * * Holds the used for finding source and target cells. */ - // @ts-ignore marker: CellMarker; /** @@ -328,14 +362,14 @@ class ConnectionHandler extends EventSource { * Holds the used for drawing and highlighting * constraints. */ - constraintHandler: ConstraintHandler | null = null; + constraintHandler: ConstraintHandler; /** * Variable: error * * Holds the current validation error while connections are being created. */ - error: any = null; + error: string | null = null; /** * Variable: waypointsEnabled @@ -385,14 +419,14 @@ class ConnectionHandler extends EventSource { * * Holds the change event listener for later removal. */ - changeHandler: any = null; + changeHandler: (sender: Listenable) => void; /** * Variable: drillHandler * * Holds the drill event listener for later removal. */ - drillHandler: any = null; + drillHandler: (sender: Listenable) => void; /** * Variable: mouseDownCounter @@ -539,52 +573,6 @@ class ConnectionHandler extends EventSource { return shape; } - /** - * Function: init - * - * Initializes the shapes required for this connection handler. This should - * be invoked if is assigned after the connection - * handler has been created. - */ - init(): void { - this.graph.event.addMouseListener(this); - this.marker = this.createMarker(); - this.constraintHandler = new ConstraintHandler(this.graph); - - // Redraws the icons if the graph changes - this.changeHandler = (sender) => { - if (this.iconState != null) { - this.iconState = this.graph.getView().getState(this.iconState.cell); - } - - if (this.iconState != null) { - this.redrawIcons(this.icons, this.iconState); - this.constraintHandler.reset(); - } else if ( - this.previous != null && - this.graph.view.getState(this.previous.cell) == null - ) { - this.reset(); - } - }; - - this.graph.getModel().addListener(InternalEvent.CHANGE, this.changeHandler); - this.graph.getView().addListener(InternalEvent.SCALE, this.changeHandler); - this.graph.getView().addListener(InternalEvent.TRANSLATE, this.changeHandler); - this.graph - .getView() - .addListener(InternalEvent.SCALE_AND_TRANSLATE, this.changeHandler); - - // Removes the icon if we step into/up or start editing - this.drillHandler = (sender) => { - this.reset(); - }; - - this.graph.addListener(InternalEvent.START_EDITING, this.drillHandler); - this.graph.getView().addListener(InternalEvent.DOWN, this.drillHandler); - this.graph.getView().addListener(InternalEvent.UP, this.drillHandler); - } - /** * Function: isConnectableCell * @@ -600,7 +588,7 @@ class ConnectionHandler extends EventSource { * * Creates and returns the used in . */ - createMarker(): CellMarker { + createMarker() { const self = this; class MyCellMarker extends CellMarker { @@ -613,12 +601,12 @@ class ConnectionHandler extends EventSource { self.error = null; // Checks for cell at preview point (with grid) - if (cell == null && self.currentPoint != null) { + if (!cell && self.currentPoint) { cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y); } // Uses connectable parent vertex if one exists - if (cell != null && !cell.isConnectable()) { + if (cell && !cell.isConnectable() && self.cell) { const parent = self.cell.getParent(); if (parent.isVertex() && parent.isConnectable()) { @@ -626,6 +614,7 @@ class ConnectionHandler extends EventSource { } } + /* disable swimlane for now if ( (self.graph.swimlane.isSwimlane(cell) && self.currentPoint != null && @@ -638,13 +627,14 @@ class ConnectionHandler extends EventSource { ) { cell = null; } + */ - if (cell != null) { + if (cell) { if (self.isConnecting()) { - if (self.previous != null) { + if (self.previous) { self.error = self.validateConnection(self.previous.cell, cell); - if (self.error != null && self.error.length === 0) { + if (self.error && self.error.length === 0) { cell = null; // Enables create target inside groups @@ -659,7 +649,7 @@ class ConnectionHandler extends EventSource { } else if ( self.isConnecting() && !self.isCreateTarget(me.getEvent()) && - !self.graph.allowDanglingEdges + !self.graph.isAllowDanglingEdges() ) { self.error = ''; } @@ -670,30 +660,30 @@ class ConnectionHandler extends EventSource { // Sets the highlight color according to validateConnection isValidState(state: CellState) { if (self.isConnecting()) { - return self.error == null; + return !self.error; } return super.isValidState(state); } // Overrides to use marker color only in highlight mode or for // target selection - getMarkerColor(evt: Event, state: CellState, isValid: boolean): string | null { - return self.connectImage == null || self.isConnecting() + getMarkerColor(evt: Event, state: CellState, isValid: boolean) { + return !self.connectImage || self.isConnecting() ? super.getMarkerColor(evt, state, isValid) - : null; + : NONE; } // Overrides to use hotspot only for source selection otherwise // intersects always returns true when over a cell intersects(state: CellState, evt: InternalMouseEvent) { - if (self.connectImage != null || self.isConnecting()) { + if (self.connectImage || self.isConnecting()) { return true; } return super.intersects(state, evt); } } - return new MyCellMarker(this.graph); + return new MyCellMarker(this.graph); } /** @@ -701,10 +691,10 @@ class ConnectionHandler extends EventSource { * * Starts a new connection for the given state and coordinates. */ - start(state: CellState, x: number, y: number, edgeState: CellState): void { + start(state: CellState, x: number, y: number, edgeState: CellState) { this.previous = state; this.first = new Point(x, y); - this.edgeState = edgeState != null ? edgeState : this.createEdgeState(null); + this.edgeState = edgeState ?? this.createEdgeState(); // Marks the source state this.marker.currentColor = this.marker.validColor; @@ -720,8 +710,8 @@ class ConnectionHandler extends EventSource { * Returns true if the source terminal has been clicked and a new * connection is currently being previewed. */ - isConnecting(): boolean { - return this.first != null && this.shape != null; + isConnecting() { + return !!this.first && !!this.shape; } /** @@ -734,7 +724,7 @@ class ConnectionHandler extends EventSource { * cell - that represents the source terminal. * me - that is associated with this call. */ - isValidSource(cell: Cell, me: InternalMouseEvent): boolean { + isValidSource(cell: Cell, me: InternalMouseEvent) { return this.graph.isValidSource(cell); } @@ -749,7 +739,7 @@ class ConnectionHandler extends EventSource { * * cell - that represents the target terminal. */ - isValidTarget(cell: Cell): boolean { + isValidTarget(cell: Cell) { return true; } @@ -765,7 +755,7 @@ class ConnectionHandler extends EventSource { * source - that represents the source terminal. * target - that represents the target terminal. */ - validateConnection(source: Cell, target: Cell): string { + validateConnection(source: Cell, target: Cell) { if (!this.isValidTarget(target)) { return ''; } @@ -782,7 +772,7 @@ class ConnectionHandler extends EventSource { * * state - whose connect image should be returned. */ - getConnectImage(state: CellState): Image | null { + getConnectImage(state: CellState) { return this.connectImage; } @@ -796,8 +786,8 @@ class ConnectionHandler extends EventSource { * * state - whose connect icons should be returned. */ - isMoveIconToFrontForState(state: CellState): boolean { - if (state.text != null && state.text.node.parentNode === this.graph.container) { + isMoveIconToFrontForState(state: CellState) { + if (state.text && state.text.node.parentNode === this.graph.container) { return true; } return this.moveIconFront; @@ -813,10 +803,10 @@ class ConnectionHandler extends EventSource { * * state - whose connect icons should be returned. */ - createIcons(state: CellState): ImageShape[] | null { + createIcons(state: CellState) { const image = this.getConnectImage(state); - if (image != null && state != null) { + if (image) { this.iconState = state; const icons = []; @@ -825,7 +815,7 @@ class ConnectionHandler extends EventSource { // connect-icon appears behind the selection border and the selection // border consumes the events before the icon gets a chance const bounds = new Rectangle(0, 0, image.width, image.height); - const icon = new ImageShape(bounds, image.src, null, null, 0); + const icon = new ImageShape(bounds, image.src, undefined, undefined, 0); icon.preserveImageAspect = false; if (this.isMoveIconToFrontForState(state)) { @@ -836,7 +826,7 @@ class ConnectionHandler extends EventSource { icon.init(this.graph.getView().getOverlayPane()); // Move the icon back in the overlay pane - if (this.moveIconBack && icon.node.previousSibling != null) { + if (this.moveIconBack && icon.node.parentNode && icon.node.previousSibling) { icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild); } } @@ -845,11 +835,11 @@ class ConnectionHandler extends EventSource { // Events transparency const getState = () => { - return this.currentState != null ? this.currentState : state; + return this.currentState ?? state; }; // Updates the local icon before firing the mouse down event. - const mouseDown = (evt) => { + const mouseDown = (evt: MouseEvent) => { if (!isConsumed(evt)) { this.icon = icon; this.graph.fireMouseEvent( @@ -877,10 +867,10 @@ class ConnectionHandler extends EventSource { * * Parameters: * - * icons - Optional array of to be redrawn. + * icons - Array of to be redrawn. */ - redrawIcons(icons?: ImageShape[] | null, state?: CellState): void { - if (icons != null && icons[0] != null && state != null) { + redrawIcons(icons: ImageShape[], state: CellState) { + if (icons[0] && icons[0].bounds) { const pos = this.getIconPosition(icons[0], state); icons[0].bounds.x = pos.x; icons[0].bounds.y = pos.y; @@ -889,11 +879,12 @@ class ConnectionHandler extends EventSource { } // TODO: Document me! =========================================================================================================== - getIconPosition(icon: ImageShape, state: CellState): Point { - const { scale } = this.graph.getView(); + getIconPosition(icon: ImageShape, state: CellState) { + // const { scale } = this.graph.getView(); let cx = state.getCenterX(); let cy = state.getCenterY(); + /* disable swimlane for now if (this.graph.isSwimlane(state.cell)) { const size = this.graph.getStartSize(state.cell); @@ -910,8 +901,9 @@ class ConnectionHandler extends EventSource { cx = pt.x; cy = pt.y; } - } - return new Point(cx - icon.bounds.width / 2, cy - icon.bounds.height / 2); + }*/ + + return new Point(cx - icon.bounds!.width / 2, cy - icon.bounds!.height / 2); } /** @@ -919,17 +911,14 @@ class ConnectionHandler extends EventSource { * * Destroys the connect icons and resets the respective state. */ - destroyIcons(): void { - if (this.icons != null) { - for (let i = 0; i < this.icons.length; i += 1) { - this.icons[i].destroy(); - } - - this.icons = null; - this.icon = null; - this.selectedIcon = null; - this.iconState = null; + destroyIcons() { + for (let i = 0; i < this.icons.length; i += 1) { + this.icons[i].destroy(); } + + this.icon = null; + this.selectedIcon = null; + this.iconState = null; } /** @@ -941,7 +930,7 @@ class ConnectionHandler extends EventSource { * are not null, or and are not null and * is null or and are not null. */ - isStartEvent(me: InternalMouseEvent): boolean { + isStartEvent(me: InternalMouseEvent) { return ( (this.constraintHandler.currentFocus !== null && this.constraintHandler.currentConstraint !== null) || @@ -956,7 +945,7 @@ class ConnectionHandler extends EventSource { * * Handles the event by initiating a new connection. */ - mouseDown(sender: any, me: InternalMouseEvent): void { + mouseDown(sender: Listenable, me: InternalMouseEvent) { this.mouseDownCounter += 1; if ( @@ -967,9 +956,9 @@ class ConnectionHandler extends EventSource { this.isStartEvent(me) ) { if ( - this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null && - this.constraintHandler.currentPoint != null + this.constraintHandler.currentConstraint && + this.constraintHandler.currentFocus && + this.constraintHandler.currentPoint ) { this.sourceConstraint = this.constraintHandler.currentConstraint; this.previous = this.constraintHandler.currentFocus; @@ -982,17 +971,17 @@ class ConnectionHandler extends EventSource { this.edgeState = this.createEdgeState(me); this.mouseDownCounter = 1; - if (this.waypointsEnabled && this.shape == null) { + if (this.waypointsEnabled && !this.shape) { this.waypoints = null; this.shape = this.createShape(); - if (this.edgeState != null) { + if (this.edgeState) { this.shape.apply(this.edgeState); } } // Stores the starting point in the geometry of the preview - if (this.previous == null && this.edgeState != null) { + if (!this.previous && this.edgeState && this.edgeState.cell.geometry) { const pt = this.graph.getPointForEvent(me.getEvent()); this.edgeState.cell.geometry.setTerminalPoint(pt, true); } @@ -1013,7 +1002,7 @@ class ConnectionHandler extends EventSource { * connecting. This implementation returns true if the state is not movable * in the graph. */ - isImmediateConnectSource(state: CellState): boolean { + isImmediateConnectSource(state: CellState) { return !this.graph.isCellMovable(state.cell); } @@ -1034,7 +1023,7 @@ class ConnectionHandler extends EventSource { * }; * (end) */ - createEdgeState(me: InternalMouseEvent): CellState | null { + createEdgeState(me?: InternalMouseEvent): CellState | null { return null; } @@ -1044,7 +1033,7 @@ class ConnectionHandler extends EventSource { * Returns true if is true and the source of the event is the outline shape * or shift is pressed. */ - isOutlineConnectEvent(me: InternalMouseEvent): boolean { + isOutlineConnectEvent(me: InternalMouseEvent) { const offset = getOffset(this.graph.container); const evt = me.getEvent(); @@ -1143,7 +1132,7 @@ class ConnectionHandler extends EventSource { // Handles special case where mouse is on outline away from actual end point // in which case the grid is ignored and mouse point is used instead if (me.isSource(this.marker.highlight.shape)) { - point = new point(me.getGraphX(), me.getGraphY()); + point = new Point(me.getGraphX(), me.getGraphY()); } const constraint = this.graph.getOutlineConstraint(point, this.currentState, me); @@ -1191,7 +1180,7 @@ class ConnectionHandler extends EventSource { * * Returns true if the given cell does not allow new connections to be created. */ - isCellEnabled(cell: Cell): boolean { + isCellEnabled(cell: Cell) { return true; } @@ -1200,7 +1189,7 @@ class ConnectionHandler extends EventSource { * * Converts the given point from screen coordinates to model coordinates. */ - convertWaypoint(point: Point): void { + convertWaypoint(point: Point) { const scale = this.graph.getView().getScale(); const tr = this.graph.getView().getTranslate(); @@ -1214,13 +1203,13 @@ class ConnectionHandler extends EventSource { * Called to snap the given point to the current preview. This snaps to the * first point of the preview if alt is not pressed. */ - snapToPreview(me: MouseEvent, point: Point): void { - if (!isAltDown(me.getEvent()) && this.previous != null) { - const tol = (this.graph.gridSize * this.graph.view.scale) / 2; + snapToPreview(me: InternalMouseEvent, point: Point) { + if (!isAltDown(me.getEvent()) && this.previous) { + const tol = (this.graph.getGridSize() * this.graph.view.scale) / 2; const tmp = - this.sourceConstraint != null + this.sourceConstraint && this.first ? this.first - : new point(this.previous.getCenterX(), this.previous.getCenterY()); + : new Point(this.previous.getCenterX(), this.previous.getCenterY()); if (Math.abs(tmp.x - me.getGraphX()) < tol) { point.x = tmp.x; @@ -1238,13 +1227,13 @@ 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): void { + mouseMove(sender: MouseEvent, me: InternalMouseEvent) { if ( !me.isConsumed() && - (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown) + (this.ignoreMouseDown || this.first || !this.graph.isMouseDown) ) { // Handles special case when handler is disabled during highlight - if (!this.isEnabled() && this.currentState != null) { + if (!this.isEnabled() && this.currentState) { this.destroyIcons(); this.currentState = null; } @@ -1255,10 +1244,10 @@ class ConnectionHandler extends EventSource { let point = new Point(me.getGraphX(), me.getGraphY()); this.error = null; - if (this.graph.grid.isGridEnabledEvent(me.getEvent())) { - point = new point( - (this.graph.grid.snap(point.x / scale - tr.x) + tr.x) * scale, - (this.graph.grid.snap(point.y / scale - tr.y) + tr.y) * scale + if (this.graph.isGridEnabledEvent(me.getEvent())) { + point = new Point( + (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale, + (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale ); } @@ -1266,29 +1255,29 @@ class ConnectionHandler extends EventSource { this.currentPoint = point; if ( - (this.first != null || (this.isEnabled() && this.graph.isEnabled())) && - (this.shape != null || - this.first == null || - Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance || - Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance) + (this.first || (this.isEnabled() && this.graph.isEnabled())) && + (this.shape || + !this.first || + Math.abs(me.getGraphX() - this.first.x) > this.graph.getEventTolerance() || + Math.abs(me.getGraphY() - this.first.y) > this.graph.getEventTolerance()) ) { this.updateCurrentState(me, point); } - if (this.first != null) { + if (this.first) { let constraint = null; let current = point; // Uses the current point from the constraint handler if available if ( - this.constraintHandler.currentConstraint != null && - this.constraintHandler.currentFocus != null && - this.constraintHandler.currentPoint != null + this.constraintHandler.currentConstraint && + this.constraintHandler.currentFocus && + this.constraintHandler.currentPoint ) { constraint = this.constraintHandler.currentConstraint; current = this.constraintHandler.currentPoint.clone(); } else if ( - this.previous != null && + this.previous && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && isShiftDown(me.getEvent()) ) { @@ -1305,11 +1294,11 @@ class ConnectionHandler extends EventSource { let pt2 = this.first; // Moves the connect icon with the mouse - if (this.selectedIcon != null) { + if (this.selectedIcon && this.selectedIcon.bounds) { const w = this.selectedIcon.bounds.width; const h = this.selectedIcon.bounds.height; - if (this.currentState != null && this.targetConnectImage) { + if (this.currentState && this.targetConnectImage) { const pos = this.getIconPosition(this.selectedIcon, this.currentState); this.selectedIcon.bounds.x = pos.x; this.selectedIcon.bounds.y = pos.y; @@ -1327,7 +1316,7 @@ class ConnectionHandler extends EventSource { } // Uses edge state to compute the terminal points - if (this.edgeState != null) { + if (this.edgeState) { this.updateEdgeState(current, constraint); current = this.edgeState.absolutePoints[ this.edgeState.absolutePoints.length - 1 @@ -1397,7 +1386,10 @@ class ConnectionHandler extends EventSource { const dx = Math.abs(me.getGraphX() - this.first.x); const dy = Math.abs(me.getGraphY() - this.first.y); - if (dx > this.graph.tolerance || dy > this.graph.tolerance) { + if ( + dx > this.graph.getEventTolerance() || + dy > this.graph.getEventTolerance() + ) { this.shape = this.createShape(); if (this.edgeState != null) { @@ -1487,28 +1479,33 @@ class ConnectionHandler extends EventSource { * * Updates . */ - updateEdgeState(current: CellState, constraint: CellState): void { + updateEdgeState(current: Point, constraint: ConnectionConstraint) { + if (!this.edgeState) return; + // TODO: Use generic method for writing constraint to style - if (this.sourceConstraint != null && this.sourceConstraint.point != null) { + if (this.sourceConstraint && this.sourceConstraint.point) { this.edgeState.style.exitX = this.sourceConstraint.point.x; this.edgeState.style.exitY = this.sourceConstraint.point.y; } - if (constraint != null && constraint.point != null) { + if (constraint && constraint.point) { this.edgeState.style.entryX = constraint.point.x; this.edgeState.style.entryY = constraint.point.y; } else { - delete this.edgeState.style.entryX; - delete this.edgeState.style.entryY; + this.edgeState.style.entryX = 0; + this.edgeState.style.entryY = 0; } this.edgeState.absolutePoints = [null, this.currentState != null ? null : current]; - this.graph.view.updateFixedTerminalPoint( - this.edgeState, - this.previous, - true, - this.sourceConstraint - ); + + if (this.sourceConstraint) { + this.graph.view.updateFixedTerminalPoint( + this.edgeState, + this.previous, + true, + this.sourceConstraint + ); + } if (this.currentState != null) { if (constraint == null) { @@ -1683,7 +1680,7 @@ class ConnectionHandler extends EventSource { const addPoint = this.waypoints != null || (this.mouseDownCounter > 1 && - (dx > this.graph.tolerance || dy > this.graph.tolerance)); + (dx > this.graph.getEventTolerance() || dy > this.graph.getEventTolerance())); if (addPoint) { if (this.waypoints == null) { @@ -1691,7 +1688,7 @@ class ConnectionHandler extends EventSource { } const { scale } = this.graph.view; - point = new point( + point = new Point( this.graph.snap(me.getGraphX() / scale) * scale, this.graph.snap(me.getGraphY() / scale) * scale ); @@ -1823,9 +1820,9 @@ class ConnectionHandler extends EventSource { * Redraws the preview edge using the color and width returned by * and . */ - drawPreview(): void { - this.updatePreview(this.error == null); - this.shape.redraw(); + drawPreview() { + this.updatePreview(this.error === null); + if (this.shape) this.shape.redraw(); } /** @@ -1839,9 +1836,11 @@ class ConnectionHandler extends EventSource { * valid - Boolean indicating if the color for a valid edge should be * returned. */ - updatePreview(valid: boolean): void { - this.shape.strokeWidth = this.getEdgeWidth(valid); - this.shape.stroke = this.getEdgeColor(valid); + updatePreview(valid: boolean) { + if (this.shape) { + this.shape.strokeWidth = this.getEdgeWidth(valid); + this.shape.stroke = this.getEdgeColor(valid); + } } /** @@ -1855,7 +1854,7 @@ class ConnectionHandler extends EventSource { * valid - Boolean indicating if the color for a valid edge should be * returned. */ - getEdgeColor(valid: boolean): string { + getEdgeColor(valid: boolean) { return valid ? VALID_COLOR : INVALID_COLOR; } @@ -1889,7 +1888,7 @@ class ConnectionHandler extends EventSource { * released. */ connect(source: Cell, target: Cell, evt: MouseEvent, dropTarget: Cell): void { - if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges) { + if (target != null || this.isCreateTarget(evt) || this.graph.isAllowDanglingEdges()) { // Uses the common parent of source and target or // the default parent to insert the edge const model = this.graph.getModel(); @@ -2068,7 +2067,7 @@ class ConnectionHandler extends EventSource { * Selects the given edge after adding a new connection. The target argument * contains the target vertex if one has been inserted. */ - selectCells(edge: Cell, target: Cell): void { + selectCells(edge: Cell, target: Cell) { this.graph.setSelectionCell(edge); } @@ -2087,7 +2086,7 @@ class ConnectionHandler extends EventSource { target: Cell, style: string ): Cell { - if (this.factoryMethod == null) { + if (!this.factoryMethod) { return this.graph.insertEdge(parent, id, value, source, target, style); } let edge = this.createEdge(value, source, target, style); @@ -2160,9 +2159,9 @@ class ConnectionHandler extends EventSource { * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2. */ getAlignmentTolerance(evt: MouseEvent): number { - return this.graph.grid.isGridEnabled() - ? this.graph.grid.gridSize / 2 - : this.graph.grid.tolerance; + return this.graph.isGridEnabled() + ? this.graph.getGridSize() / 2 + : this.graph.getSnapTolerance(); } /** diff --git a/packages/core/src/view/connection/GraphConnections.ts b/packages/core/src/view/connection/GraphConnections.ts index 9d62496f1..eddf237f2 100644 --- a/packages/core/src/view/connection/GraphConnections.ts +++ b/packages/core/src/view/connection/GraphConnections.ts @@ -4,7 +4,12 @@ import InternalMouseEvent from '../event/InternalMouseEvent'; import ConnectionConstraint from './ConnectionConstraint'; import Rectangle from '../geometry/Rectangle'; import { DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_WEST } from '../../util/Constants'; -import utils, { getRotatedPoint, getValue, toRadians } from '../../util/Utils'; +import utils, { + autoImplement, + getRotatedPoint, + getValue, + toRadians, +} from '../../util/Utils'; import Cell from '../cell/datatypes/Cell'; import CellArray from '../cell/datatypes/CellArray'; import EventObject from '../event/EventObject'; @@ -13,28 +18,53 @@ import Dictionary from '../../util/Dictionary'; import Geometry from '../geometry/Geometry'; import Graph from '../Graph'; import ConnectionHandler from './ConnectionHandler'; +import GraphCells from '../cell/GraphCells'; +import GraphPorts from '../ports/GraphPorts'; +import GraphEdge from '../cell/edge/GraphEdge'; -class GraphConnections { - constructor(graph: Graph) { - this.graph = graph; - } - - graph: Graph; +type PartialGraph = Pick; +type PartialCells = Pick; +type PartialPorts = Pick; +type PartialEdge = Pick< + GraphEdge, + | 'isResetEdgesOnConnect' + | 'resetEdge' + | 'getEdges' + | 'isAllowDanglingEdges' + | 'isConnectableEdges' +>; +type PartialClass = PartialGraph & PartialCells & PartialPorts & PartialEdge; +// @ts-ignore recursive reference error +class GraphConnections extends autoImplement() { /***************************************************************************** * Group: Cell connecting and connection constraints *****************************************************************************/ + connectionHandler: ConnectionHandler | null = null; + + getConnectionHandler() { + return this.connectionHandler; + } + + setConnectionHandler(connectionHandler: ConnectionHandler) { + this.connectionHandler = connectionHandler; + } + + constrainChildren = false; + + constrainRelativeChildren = false; + + disconnectOnMove = false; + + cellsDisconnectable = true; + /** * Returns the constraint used to connect to the outline of the given state. */ - getOutlineConstraint( - point: Point, - terminalState: CellState, - me: InternalMouseEvent - ): ConnectionConstraint | null { - if (terminalState.shape != null) { - const bounds = this.graph.view.getPerimeterBounds(terminalState); + getOutlineConstraint(point: Point, terminalState: CellState, me: InternalMouseEvent) { + if (terminalState.shape) { + const bounds = this.getView().getPerimeterBounds(terminalState); const direction = terminalState.style.direction; if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) { @@ -51,7 +81,7 @@ class GraphConnections { const cos = Math.cos(-alpha); const sin = Math.sin(-alpha); - const ct = new point(bounds.getCenterX(), bounds.getCenterY()); + const ct = new Point(bounds.getCenterX(), bounds.getCenterY()); point = getRotatedPoint(point, cos, sin, ct); } @@ -61,16 +91,10 @@ class GraphConnections { let dy = 0; // LATER: Add flipping support for image shapes - if ((terminalState.cell).isVertex()) { + if (terminalState.cell.isVertex()) { let flipH = terminalState.style.flipH; let flipV = terminalState.style.flipV; - // Legacy support for stencilFlipH/V - if (terminalState.shape != null && terminalState.shape.stencil != null) { - flipH = getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH; - flipV = getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV; - } - if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) { const tmp = flipH; flipH = flipV; @@ -88,7 +112,7 @@ class GraphConnections { } } - point = new point( + point = new Point( (point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y ); @@ -102,7 +126,7 @@ class GraphConnections { ? 0 : Math.round(((point.y - bounds.y) * 1000) / bounds.height) / 1000; - return new ConnectionConstraint(new point(x, y), false); + return new ConnectionConstraint(new Point(x, y), false); } return null; } @@ -115,11 +139,8 @@ class GraphConnections { * @param terminal {@link mxCellState} that represents the terminal. * @param source Boolean that specifies if the terminal is the source or target. */ - getAllConnectionConstraints( - terminal: CellState, - source: boolean - ): ConnectionConstraint[] | null { - if (terminal != null && terminal.shape != null && terminal.shape.stencil != null) { + getAllConnectionConstraints(terminal: CellState | null, source: boolean) { + if (terminal && terminal.shape && terminal.shape.stencil) { return terminal.shape.stencil.constraints; } return null; @@ -135,19 +156,18 @@ class GraphConnections { */ getConnectionConstraint( edge: CellState, - terminal: CellState | null = null, + terminal: CellState | null, source: boolean = false - ): ConnectionConstraint { - let point = null; - // @ts-ignore - const x = edge.style[source ? 'exitX' : 'entryX']; + ) { + let point: Point | null = null; - if (x != null) { - // @ts-ignore - const y = edge.style[source ? 'exitY' : 'entryY']; + const x = edge.style[source ? 'exitX' : 'entryX']; - if (y != null) { - point = new point(parseFloat(x), parseFloat(y)); + if (x !== undefined) { + const y = edge.style[source ? 'exitY' : 'entryY']; + + if (y !== undefined) { + point = new Point(x, y); } } @@ -155,14 +175,12 @@ class GraphConnections { let dx = 0; let dy = 0; - if (point != null) { - perimeter = getValue(edge.style, source ? 'exitPerimeter' : 'entryPerimeter', true); + if (point) { + perimeter = edge.style[source ? 'exitPerimeter' : 'entryPerimeter']; // Add entry/exit offset - // @ts-ignore - dx = parseFloat(edge.style[source ? 'exitDx' : 'entryDx']); - // @ts-ignore - dy = parseFloat(edge.style[source ? 'exitDy' : 'entryDy']); + dx = edge.style[source ? 'exitDx' : 'entryDx']; + dy = edge.style[source ? 'exitDy' : 'entryDy']; dx = Number.isFinite(dx) ? dx : 0; dy = Number.isFinite(dy) ? dy : 0; @@ -187,12 +205,12 @@ class GraphConnections { terminal: Cell, source: boolean = false, constraint: ConnectionConstraint | null = null - ): void { - if (constraint != null) { + ) { + if (constraint) { this.getModel().beginUpdate(); try { - if (constraint == null || constraint.point == null) { + if (!constraint || !constraint.point) { this.setCellStyles(source ? 'exitX' : 'entryX', null, new CellArray(edge)); this.setCellStyles(source ? 'exitY' : 'entryY', null, new CellArray(edge)); this.setCellStyles(source ? 'exitDx' : 'entryDx', null, new CellArray(edge)); @@ -202,7 +220,7 @@ class GraphConnections { null, new CellArray(edge) ); - } else if (constraint.point != null) { + } else if (constraint.point) { this.setCellStyles( source ? 'exitX' : 'entryX', constraint.point.x, @@ -257,17 +275,17 @@ class GraphConnections { vertex: CellState, constraint: ConnectionConstraint, round: boolean = true - ): Point { - let point = null; + ) { + let point: Point | null = null; - if (vertex != null && constraint.point != null) { - const bounds = this.graph.view.getPerimeterBounds(vertex); - const cx = new point(bounds.getCenterX(), bounds.getCenterY()); + if (constraint.point) { + const bounds = this.getView().getPerimeterBounds(vertex); + const cx = new Point(bounds.getCenterX(), bounds.getCenterY()); const direction = vertex.style.direction; let r1 = 0; // Bounds need to be rotated by 90 degrees for further computation - if (direction != null && getValue(vertex.style, 'anchorPointDirection', 1) == 1) { + if (vertex.style.anchorPointDirection) { if (direction === DIRECTION_NORTH) { r1 += 270; } else if (direction === DIRECTION_WEST) { @@ -282,8 +300,8 @@ class GraphConnections { } } - const { scale } = this.view; - point = new point( + const { scale } = this.getView(); + point = new Point( bounds.x + constraint.point.x * bounds.width + constraint.dx * scale, bounds.y + constraint.point.y * bounds.height + constraint.dy * scale ); @@ -308,19 +326,13 @@ class GraphConnections { point = getRotatedPoint(point, cos, sin, cx); } - point = this.graph.view.getPerimeterPoint(vertex, point, false); + point = this.getView().getPerimeterPoint(vertex, point, false); } else { r2 += r1; - if ((vertex.cell).isVertex()) { - let flipH = vertex.style.flipH == 1; - let flipV = vertex.style.flipV == 1; - - // Legacy support for stencilFlipH/V - if (vertex.shape != null && vertex.shape.stencil != null) { - flipH = getValue(vertex.style, 'stencilFlipH', 0) == 1 || flipH; - flipV = getValue(vertex.style, 'stencilFlipV', 0) == 1 || flipV; - } + if (vertex.cell.isVertex()) { + let flipH = vertex.style.flipH; + let flipV = vertex.style.flipV; if (direction === DIRECTION_NORTH || direction === DIRECTION_SOUTH) { const temp = flipH; @@ -339,7 +351,7 @@ class GraphConnections { } // Generic rotation after projection on perimeter - if (r2 !== 0 && point != null) { + if (r2 !== 0 && point) { const rad = toRadians(r2); const cos = Math.cos(rad); const sin = Math.sin(rad); @@ -348,7 +360,7 @@ class GraphConnections { } } - if (round && point != null) { + if (round && point) { point.x = Math.round(point.x); point.y = Math.round(point.y); } @@ -371,7 +383,7 @@ class GraphConnections { terminal: Cell, source: boolean = false, constraint: ConnectionConstraint | null = null - ): Cell { + ) { this.getModel().beginUpdate(); try { const previous = edge.getTerminal(source); @@ -410,52 +422,50 @@ class GraphConnections { terminal: Cell, source: boolean = false, constraint: ConnectionConstraint | null = null - ): void { - if (edge != null) { - this.getModel().beginUpdate(); - try { - const previous = edge.getTerminal(source); + ) { + this.getModel().beginUpdate(); + try { + const previous = edge.getTerminal(source); - // Updates the constraint - this.setConnectionConstraint(edge, terminal, source, constraint); + // Updates the constraint + this.setConnectionConstraint(edge, terminal, source, constraint); - // Checks if the new terminal is a port, uses the ID of the port in the - // style and the parent of the port as the actual terminal of the edge. - if (this.isPortsEnabled()) { - let id = null; + // Checks if the new terminal is a port, uses the ID of the port in the + // style and the parent of the port as the actual terminal of the edge. + if (this.isPortsEnabled()) { + let id = null; - if (this.isPort(terminal)) { - id = terminal.getId(); - terminal = this.getTerminalForPort(terminal, source); - } - - // Sets or resets all previous information for connecting to a child port - const key = source ? 'sourcePort' : 'targetPort'; - this.setCellStyles(key, id, new CellArray(edge)); + if (this.isPort(terminal)) { + id = terminal.getId(); + terminal = this.getTerminalForPort(terminal, source); } - this.getModel().setTerminal(edge, terminal, source); - - if (this.resetEdgesOnConnect) { - this.resetEdge(edge); - } - - this.fireEvent( - new EventObject( - InternalEvent.CELL_CONNECTED, - 'edge', - edge, - 'terminal', - terminal, - 'source', - source, - 'previous', - previous - ) - ); - } finally { - this.getModel().endUpdate(); + // Sets or resets all previous information for connecting to a child port + const key = source ? 'sourcePort' : 'targetPort'; + this.setCellStyles(key, id, new CellArray(edge)); } + + this.getModel().setTerminal(edge, terminal, source); + + if (this.isResetEdgesOnConnect()) { + this.resetEdge(edge); + } + + this.fireEvent( + new EventObject( + InternalEvent.CELL_CONNECTED, + 'edge', + edge, + 'terminal', + terminal, + 'source', + source, + 'previous', + previous + ) + ); + } finally { + this.getModel().endUpdate(); } } @@ -465,84 +475,77 @@ class GraphConnections { * * @param cells Array of {@link Cell} to be disconnected. */ - disconnectGraph(cells: CellArray | null) { - if (cells != null) { - this.getModel().beginUpdate(); - try { - const { scale } = this.view; - const tr = this.graph.view.translate; + disconnectGraph(cells: CellArray) { + this.getModel().beginUpdate(); + try { + const { scale, translate: tr } = this.getView(); - // Fast lookup for finding cells in array - const dict = new Dictionary(); + // Fast lookup for finding cells in array + const dict = new Dictionary(); - for (let i = 0; i < cells.length; i += 1) { - dict.put(cells[i], true); - } + for (let i = 0; i < cells.length; i += 1) { + dict.put(cells[i], true); + } - for (const cell of cells) { - if (cell.isEdge()) { - let geo = cell.getGeometry(); + for (const cell of cells) { + if (cell.isEdge()) { + let geo = cell.getGeometry(); - if (geo != null) { - const state = this.graph.view.getState(cell); - const pstate = this.graph.view.getState(cell.getParent()); + if (geo) { + const state = this.getView().getState(cell); + const pstate = this.getView().getState(cell.getParent()); - if (state != null && pstate != null) { - geo = geo.clone(); + if (state && pstate) { + geo = geo.clone(); - // @ts-ignore - const dx = -pstate.origin.x; - // @ts-ignore - const dy = -pstate.origin.y; - const pts = state.absolutePoints; + const dx = -pstate.origin.x; + const dy = -pstate.origin.y; + const pts = state.absolutePoints; - let src = cell.getTerminal(true); + let src = cell.getTerminal(true); - if (src != null && this.isCellDisconnectable(cell, src, true)) { - while (src != null && !dict.get(src)) { - src = src.getParent(); - } - - if (src == null) { - geo.setTerminalPoint( - new Point( - pts[0].x / scale - tr.x + dx, - pts[0].y / scale - tr.y + dy - ), - true - ); - this.getModel().setTerminal(cell, null, true); - } + if (src && this.isCellDisconnectable(cell, src, true)) { + while (src && !dict.get(src)) { + src = src.getParent(); } - let trg = cell.getTerminal(false); + if (!src && pts[0]) { + geo.setTerminalPoint( + new Point(pts[0].x / scale - tr.x + dx, pts[0].y / scale - tr.y + dy), + true + ); + this.getModel().setTerminal(cell, null, true); + } + } - if (trg != null && this.isCellDisconnectable(cell, trg, false)) { - while (trg != null && !dict.get(trg)) { - trg = trg.getParent(); - } + let trg = cell.getTerminal(false); - if (trg == null) { - const n = pts.length - 1; + if (trg && this.isCellDisconnectable(cell, trg, false)) { + while (trg && !dict.get(trg)) { + trg = trg.getParent(); + } + + if (!trg) { + const n = pts.length - 1; + const p = pts[n]; + + if (p) { geo.setTerminalPoint( - new Point( - (pts[n]).x / scale - tr.x + dx, - (pts[n]).y / scale - tr.y + dy - ), + new Point(p.x / scale - tr.x + dx, p.y / scale - tr.y + dy), false ); this.getModel().setTerminal(cell, null, false); } } - - this.getModel().setGeometry(cell, geo); } + + this.getModel().setGeometry(cell, geo); } } } - } finally { - this.getModel().endUpdate(); } + } finally { + this.getModel().endUpdate(); } } @@ -553,7 +556,7 @@ class GraphConnections { * @param parent Optional parent of the opposite end for a connection to be * returned. */ - getConnections(cell: Cell, parent: Cell | null = null): CellArray { + getConnections(cell: Cell, parent: Cell | null = null) { return this.getEdges(cell, parent, true, true, false); } @@ -565,7 +568,7 @@ class GraphConnections { * * @param cell {@link mxCell} that should be constrained. */ - isConstrainChild(cell: Cell): boolean { + isConstrainChild(cell: Cell) { return ( this.isConstrainChildren() && !!cell.getParent() && @@ -576,28 +579,28 @@ class GraphConnections { /** * Returns {@link constrainChildren}. */ - isConstrainChildren(): boolean { + isConstrainChildren() { return this.constrainChildren; } /** * Sets {@link constrainChildren}. */ - setConstrainChildren(value: boolean): void { + setConstrainChildren(value: boolean) { this.constrainChildren = value; } /** * Returns {@link constrainRelativeChildren}. */ - isConstrainRelativeChildren(): boolean { + isConstrainRelativeChildren() { return this.constrainRelativeChildren; } /** * Sets {@link constrainRelativeChildren}. */ - setConstrainRelativeChildren(value: boolean): void { + setConstrainRelativeChildren(value: boolean) { this.constrainRelativeChildren = value; } @@ -608,7 +611,7 @@ class GraphConnections { /** * Returns {@link disconnectOnMove} as a boolean. */ - isDisconnectOnMove(): boolean { + isDisconnectOnMove() { return this.disconnectOnMove; } @@ -619,7 +622,7 @@ class GraphConnections { * @param value Boolean indicating if edges should be disconnected * when moved. */ - setDisconnectOnMove(value: boolean): void { + setDisconnectOnMove(value: boolean) { this.disconnectOnMove = value; } @@ -637,21 +640,21 @@ class GraphConnections { cell: Cell, terminal: Cell | null = null, source: boolean = false - ): boolean { + ) { return this.isCellsDisconnectable() && !this.isCellLocked(cell); } /** * Returns {@link cellsDisconnectable}. */ - isCellsDisconnectable(): boolean { + isCellsDisconnectable() { return this.cellsDisconnectable; } /** * Sets {@link cellsDisconnectable}. */ - setCellsDisconnectable(value: boolean): void { + setCellsDisconnectable(value: boolean) { this.cellsDisconnectable = value; } @@ -662,10 +665,12 @@ class GraphConnections { * * @param cell {@link mxCell} that represents a possible source or null. */ - isValidSource(cell: Cell | null): boolean { + isValidSource(cell: Cell | null) { return ( - (cell == null && this.allowDanglingEdges) || - (cell != null && (!cell.isEdge() || this.connectableEdges) && cell.isConnectable()) + (cell == null && this.isAllowDanglingEdges()) || + (cell != null && + (!cell.isEdge() || this.isConnectableEdges()) && + cell.isConnectable()) ); } @@ -699,14 +704,14 @@ class GraphConnections { * * @param connectable Boolean indicating if new connections should be allowed. */ - setConnectable(connectable: boolean): void { + setConnectable(connectable: boolean) { (this.connectionHandler).setEnabled(connectable); } /** * Returns true if the {@link connectionHandler} is enabled. */ - isConnectable(): boolean { + isConnectable() { return (this.connectionHandler).isEnabled(); } } diff --git a/packages/core/src/view/editing/GraphEditing.ts b/packages/core/src/view/editing/GraphEditing.ts index fdfc56a9a..671aa9c4b 100644 --- a/packages/core/src/view/editing/GraphEditing.ts +++ b/packages/core/src/view/editing/GraphEditing.ts @@ -22,6 +22,7 @@ type PartialCells = Pick< >; type PartialClass = PartialGraph & PartialSelection & PartialEvents & PartialCells; +// @ts-ignore recursive reference error class GraphEditing extends autoImplement() { /** * Specifies the return value for {@link isCellEditable}. diff --git a/packages/core/src/view/event/EventSource.ts b/packages/core/src/view/event/EventSource.ts index 18ca5228a..970f6e989 100644 --- a/packages/core/src/view/event/EventSource.ts +++ b/packages/core/src/view/event/EventSource.ts @@ -7,7 +7,7 @@ import EventObject from './EventObject'; -type EventListener = { +type EventListenerObject = { funct: Function; name: string; }; @@ -46,7 +46,7 @@ class EventSource { * contains the event name followed by the respective listener for each * registered listener. */ - eventListeners: EventListener[] = []; + eventListeners: EventListenerObject[] = []; /** * Variable: eventsEnabled @@ -60,7 +60,7 @@ class EventSource { * * Optional source for events. Default is null. */ - eventSource: EventSource | null; + eventSource: EventSource | EventTarget | null; /** * Function: isEventsEnabled @@ -94,7 +94,7 @@ class EventSource { * * Sets . */ - setEventSource(value: EventSource | null) { + setEventSource(value: EventSource | EventTarget | null) { this.eventSource = value; } @@ -106,7 +106,7 @@ class EventSource { * * The parameters of the listener are the sender and an . */ - addListener(name: string, funct: (...args: any[]) => any) { + addListener(name: string, funct: Function) { this.eventListeners.push({ name, funct }); } @@ -115,7 +115,7 @@ class EventSource { * * Removes all occurrences of the given listener from . */ - removeListener(funct: (...args: any[]) => any) { + removeListener(funct: Function) { let i = 0; while (i < this.eventListeners.length) { @@ -146,7 +146,7 @@ class EventSource { * sender - Optional sender to be passed to the listener. Default value is * the return value of . */ - fireEvent(evt: EventObject, sender: any = null) { + fireEvent(evt: EventObject, sender: EventSource | EventTarget | null = null) { if (this.isEventsEnabled()) { if (!evt) { evt = new EventObject(''); diff --git a/packages/core/src/view/event/GraphEvents.ts b/packages/core/src/view/event/GraphEvents.ts index 9a219d5c5..a5aff0e27 100644 --- a/packages/core/src/view/event/GraphEvents.ts +++ b/packages/core/src/view/event/GraphEvents.ts @@ -32,6 +32,7 @@ import type GraphCells from '../cell/GraphCells'; import type GraphSelection from '../selection/GraphSelection'; import GraphEditing from '../editing/GraphEditing'; import GraphSnap from '../snap/GraphSnap'; +import { MouseEventListener } from '../../types'; type PartialGraph = Pick< Graph, @@ -59,21 +60,15 @@ type PartialClass = PartialGraph & PartialSnap & EventSource; -type MouseListener = { - mouseDown: Function; - mouseMove: Function; - mouseUp: Function; -}; - // @ts-ignore recursive reference error class GraphEvents extends autoImplement() { /** * Holds the mouse event listeners. See {@link fireMouseEvent}. */ - mouseListeners: MouseListener[] = []; + mouseListeners: MouseListenerSet[] = []; // TODO: Document me! - lastTouchEvent: InternalMouseEvent | null = null; + lastTouchEvent: MouseEvent | null = null; doubleClickCounter: number = 0; lastTouchCell: Cell | null = null; fireDoubleClick: boolean | null = null; @@ -82,8 +77,8 @@ class GraphEvents extends autoImplement() { lastMouseY: number | null = null; isMouseTrigger: boolean | null = null; ignoreMouseEvents: boolean | null = null; - mouseMoveRedirect: EventListener | null = null; - mouseUpRedirect: EventListener | null = null; + mouseMoveRedirect: MouseEventListener | null = null; + mouseUpRedirect: MouseEventListener | null = null; lastEvent: any; // FIXME: Check if this can be more specific - DOM events or mxEventObjects! /** @@ -199,7 +194,7 @@ class GraphEvents extends autoImplement() { */ tolerance: number = 4; - getClickTolerance = () => this.tolerance; + getEventTolerance = () => this.tolerance; /***************************************************************************** * Group: Event processing @@ -210,7 +205,7 @@ class GraphEvents extends autoImplement() { * * @param evt Mouseevent that represents the keystroke. */ - escape(evt: InternalMouseEvent): void { + escape(evt: Event) { this.fireEvent(new EventObject(InternalEvent.ESCAPE, 'event', evt)); } @@ -355,7 +350,7 @@ class GraphEvents extends autoImplement() { * @param evt Mouseevent that represents the doubleclick. * @param cell Optional {@link Cell} under the mousepointer. */ - dblClick(evt: MouseEvent, cell?: Cell): void { + dblClick(evt: MouseEvent, cell?: Cell) { const mxe = new EventObject(InternalEvent.DOUBLE_CLICK, { event: evt, cell: cell }); this.fireEvent(mxe); @@ -364,7 +359,7 @@ class GraphEvents extends autoImplement() { this.isEnabled() && !isConsumed(evt) && !mxe.isConsumed() && - cell != null && + cell && this.isCellEditable(cell) && !this.isEditing(cell) ) { @@ -379,7 +374,7 @@ class GraphEvents extends autoImplement() { * @param me {@link mxMouseEvent} that represents the touch event. * @param state Optional {@link CellState} that is associated with the event. */ - tapAndHold(me: InternalMouseEvent): void { + tapAndHold(me: InternalMouseEvent) { const evt = me.getEvent(); const mxe = new EventObject( InternalEvent.TAP_AND_HOLD, @@ -434,8 +429,7 @@ class GraphEvents extends autoImplement() { * * @param listener Listener to be added to the graph event listeners. */ - // addMouseListener(listener: { [key: string]: (sender: mxEventSource, me: mxMouseEvent) => void }): void; - addMouseListener(listener: MouseListener): void { + addMouseListener(listener: MouseListenerSet) { this.mouseListeners.push(listener); } @@ -444,8 +438,7 @@ class GraphEvents extends autoImplement() { * * @param listener Listener to be removed from the graph event listeners. */ - // removeMouseListener(listener: { [key: string]: (sender: mxEventSource, me: mxMouseEvent) => void }): void; - removeMouseListener(listener: MouseListener) { + removeMouseListener(listener: MouseListenerSet) { for (let i = 0; i < this.mouseListeners.length; i += 1) { if (this.mouseListeners[i] === listener) { this.mouseListeners.splice(i, 1); @@ -461,30 +454,28 @@ class GraphEvents extends autoImplement() { * @param me {@link mxMouseEvent} to be updated. * @param evtName Name of the mouse event. */ - updateMouseEvent(me: InternalMouseEvent, evtName: string): InternalMouseEvent { - if (me.graphX == null || me.graphY == null) { - const pt = convertPoint(this.getContainer(), me.getX(), me.getY()); + updateMouseEvent(me: InternalMouseEvent, evtName: string) { + const pt = convertPoint(this.getContainer(), me.getX(), me.getY()); - me.graphX = pt.x - this.panning.panDx; - me.graphY = pt.y - this.panning.panDy; + me.graphX = pt.x - this.panning.panDx; + me.graphY = pt.y - this.panning.panDy; - // Searches for rectangles using method if native hit detection is disabled on shape - if ( - me.getCell() == null && - this.isMouseDown && - evtName === InternalEvent.MOUSE_MOVE - ) { - me.state = this.getView().getState( - this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => { - return ( - state.shape == null || - state.shape.paintBackground !== this.paintBackground || - getValue(state.style, 'pointerEvents', '1') == '1' || - (state.shape.fill != null && state.shape.fill !== NONE) - ); - }) - ); - } + // Searches for rectangles using method if native hit detection is disabled on shape + if ( + me.getCell() == null && + this.isMouseDown && + evtName === InternalEvent.MOUSE_MOVE + ) { + me.state = this.getView().getState( + this.getCellAt(pt.x, pt.y, null, true, true, (state: CellState) => { + return ( + state.shape == null || + state.shape.paintBackground !== this.paintBackground || + getValue(state.style, 'pointerEvents', '1') == '1' || + (state.shape.fill != null && state.shape.fill !== NONE) + ); + }) + ); } return me; @@ -493,8 +484,7 @@ class GraphEvents extends autoImplement() { /** * Returns the state for the given touch event. */ - // getStateForTouchEvent(evt: MouseEvent | TouchEvent): mxCellState; - getStateForTouchEvent(evt: InternalMouseEvent) { + getStateForTouchEvent(evt: MouseEvent) { const x = getClientX(evt); const y = getClientY(evt); @@ -508,8 +498,7 @@ class GraphEvents extends autoImplement() { /** * Returns true if the event should be ignored in {@link fireMouseEvent}. */ - // isEventIgnored(evtName: string, me: mxMouseEvent, sender: mxEventSource): boolean; - isEventIgnored(evtName: string, me: InternalMouseEvent, sender: any): boolean { + isEventIgnored(evtName: string, me: InternalMouseEvent, sender: EventSource) { const mouseEvent = isMouseEvent(me.getEvent()); let result = false; @@ -545,13 +534,13 @@ class GraphEvents extends autoImplement() { ) { this.setEventSource(me.getSource()); - this.mouseMoveRedirect = (evt: InternalMouseEvent) => { + this.mouseMoveRedirect = (evt: MouseEvent) => { this.fireMouseEvent( InternalEvent.MOUSE_MOVE, new InternalMouseEvent(evt, this.getStateForTouchEvent(evt)) ); }; - this.mouseUpRedirect = (evt: InternalMouseEvent) => { + this.mouseUpRedirect = (evt: MouseEvent) => { this.fireMouseEvent( InternalEvent.MOUSE_UP, new InternalMouseEvent(evt, this.getStateForTouchEvent(evt)) @@ -675,11 +664,7 @@ class GraphEvents extends autoImplement() { * @param me {@link mxMouseEvent} to be fired. * @param sender Optional sender argument. Default is `this`. */ - fireMouseEvent( - evtName: string, - me: InternalMouseEvent, - sender: EventSource = this - ): void { + fireMouseEvent(evtName: string, me: InternalMouseEvent, sender: EventSource = this) { if (this.isEventSourceIgnored(evtName, me)) { if (this.tooltipHandler != null) { this.tooltipHandler.hide(); @@ -687,10 +672,6 @@ class GraphEvents extends autoImplement() { return; } - if (sender == null) { - sender = this; - } - // Updates the graph coordinates in the event me = this.updateMouseEvent(me, evtName); @@ -992,7 +973,7 @@ class GraphEvents extends autoImplement() { * Returns true if the given event is a clone event. This implementation * returns true if control is pressed. */ - isCloneEvent(evt: MouseEvent): boolean { + isCloneEvent(evt: MouseEvent) { return isControlDown(evt); } @@ -1001,7 +982,7 @@ class GraphEvents extends autoImplement() { * returns true the cell behind the selected cell will be selected. This * implementation returns false; */ - isTransparentClickEvent(evt: MouseEvent): boolean { + isTransparentClickEvent(evt: MouseEvent) { return false; } @@ -1010,21 +991,21 @@ class GraphEvents extends autoImplement() { * returns true if the meta key (Cmd) is pressed on Macs or if control is * pressed on any other platform. */ - isToggleEvent(evt: MouseEvent): boolean { + isToggleEvent(evt: MouseEvent) { return mxClient.IS_MAC ? isMetaDown(evt) : isControlDown(evt); } /** * Returns true if the given mouse event should be aligned to the grid. */ - isGridEnabledEvent(evt: MouseEvent): boolean { - return evt != null && !isAltDown(evt); + isGridEnabledEvent(evt: MouseEvent) { + return !isAltDown(evt); } /** * Returns true if the given mouse event should be aligned to the grid. */ - isConstrainedEvent(evt: MouseEvent): boolean { + isConstrainedEvent(evt: MouseEvent) { return isShiftDown(evt); } @@ -1032,7 +1013,7 @@ class GraphEvents extends autoImplement() { * Returns true if the given mouse event should not allow any connections to be * made. This implementation returns false. */ - isIgnoreTerminalEvent(evt: MouseEvent): boolean { + isIgnoreTerminalEvent(evt: MouseEvent) { return false; } @@ -1044,7 +1025,7 @@ class GraphEvents extends autoImplement() { * @param addOffset Optional boolean that specifies if the position should be * offset by half of the {@link gridSize}. Default is `true`. */ - getPointForEvent(evt: InternalMouseEvent, addOffset: boolean = true): Point { + getPointForEvent(evt: MouseEvent, addOffset: boolean = true) { const p = convertPoint(this.getContainer(), getClientX(evt), getClientY(evt)); const s = this.getView().scale; const tr = this.getView().translate; @@ -1052,6 +1033,7 @@ class GraphEvents extends autoImplement() { p.x = this.snap(p.x / s - tr.x - off); p.y = this.snap(p.y / s - tr.y - off); + return p; } diff --git a/packages/core/src/view/event/InternalEvent.ts b/packages/core/src/view/event/InternalEvent.ts index ffbe4ca54..240df8b66 100644 --- a/packages/core/src/view/event/InternalEvent.ts +++ b/packages/core/src/view/event/InternalEvent.ts @@ -9,7 +9,7 @@ import mxClient from '../../mxClient'; import { isConsumed, isMouseEvent } from '../../util/EventUtils'; import graph from '../Graph'; import CellState from '../cell/datatypes/CellState'; -import { EventCache, GestureEvent, Listenable } from '../../types'; +import { EventCache, GestureEvent, Listenable, MouseEventListener } from '../../types'; // Checks if passive event listeners are supported // see https://github.com/Modernizr/Modernizr/issues/1894 @@ -50,11 +50,10 @@ class InternalEvent { * {@link mxUtils.bind} in order to bind the "this" keyword inside the function * to a given execution scope. */ - // static addListener(element: Node | Window, eventName: string, funct: Function): void; - static addListener(element: Listenable, eventName: string, funct: EventListener) { + static addListener(element: Listenable, eventName: string, funct: MouseEventListener) { element.addEventListener( eventName, - funct, + funct as EventListener, supportsPassive ? { passive: false } : false ); @@ -69,9 +68,12 @@ class InternalEvent { /** * Removes the specified listener from the given element. */ - // static removeListener(element: Node | Window, eventName: string, funct: Function): void; - static removeListener(element: Listenable, eventName: string, funct: EventListener) { - element.removeEventListener(eventName, funct, false); + static removeListener( + element: Listenable, + eventName: string, + funct: MouseEventListener + ) { + element.removeEventListener(eventName, funct as EventListener, false); if (element.mxListenerList) { const listenerCount = element.mxListenerList.length; @@ -90,7 +92,6 @@ class InternalEvent { /** * Removes all listeners from the given element. */ - // static removeAllListeners(element: Node | Window): void; static removeAllListeners(element: Listenable) { const list = element.mxListenerList; @@ -112,10 +113,10 @@ class InternalEvent { * will be registered as well as the mouse events. */ static addGestureListeners( - node: Listenable, - startListener: EventListener | null = null, - moveListener: EventListener | null = null, - endListener: EventListener | null = null + node: EventSource | EventTarget, + startListener: MouseEventListener | null = null, + moveListener: MouseEventListener | null = null, + endListener: MouseEventListener | null = null ) { if (startListener) { InternalEvent.addListener( @@ -164,9 +165,9 @@ class InternalEvent { */ static removeGestureListeners( node: Listenable, - startListener: EventListener | null, - moveListener: EventListener | null, - endListener: EventListener | null + startListener: MouseEventListener | null, + moveListener: MouseEventListener | null, + endListener: MouseEventListener | null ) { if (startListener) { InternalEvent.removeListener( @@ -221,10 +222,10 @@ class InternalEvent { node: Listenable, graph: graph, state: CellState | ((evt: Event) => CellState) | null = null, - down: EventListener | null = null, - move: EventListener | null = null, - up: EventListener | null = null, - dblClick: EventListener | null = null + down: MouseEventListener | null = null, + move: MouseEventListener | null = null, + up: MouseEventListener | null = null, + dblClick: MouseEventListener | null = null ) { const getState = (evt: Event) => { return typeof state === 'function' ? state(evt) : state; @@ -238,7 +239,7 @@ class InternalEvent { } else if (!isConsumed(evt)) { graph.fireMouseEvent( InternalEvent.MOUSE_DOWN, - new InternalMouseEvent(evt as MouseEvent, getState(evt)) + new InternalMouseEvent(evt, getState(evt)) ); } }, @@ -248,7 +249,7 @@ class InternalEvent { } else if (!isConsumed(evt)) { graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt as MouseEvent, getState(evt)) + new InternalMouseEvent(evt, getState(evt)) ); } }, @@ -258,7 +259,7 @@ class InternalEvent { } else if (!isConsumed(evt)) { graph.fireMouseEvent( InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt as MouseEvent, getState(evt)) + new InternalMouseEvent(evt, getState(evt)) ); } } @@ -269,7 +270,7 @@ class InternalEvent { dblClick(evt); } else if (!isConsumed(evt)) { const tmp = getState(evt); - graph.dblClick(evt as MouseEvent, tmp?.cell); + graph.dblClick(evt, tmp?.cell); } }); } @@ -279,17 +280,17 @@ class InternalEvent { * * @param element DOM node to remove the listeners from. */ - static release(element: Listenable | null) { + static release(element: Listenable) { try { - if (element) { - InternalEvent.removeAllListeners(element); + InternalEvent.removeAllListeners(element); - if ('childNodes' in element) { - const children = element.childNodes; - const childCount = children.length; - for (let i = 0; i < childCount; i += 1) { - InternalEvent.release(children[i]); - } + // @ts-ignore + const children = element.childNodes; + + if (children !== undefined) { + const childCount = children.length; + for (let i = 0; i < childCount; i += 1) { + InternalEvent.release(children[i]); } } } catch (e) { diff --git a/packages/core/src/view/folding/GraphFolding.ts b/packages/core/src/view/folding/GraphFolding.ts index 3233f7813..1e6e26e92 100644 --- a/packages/core/src/view/folding/GraphFolding.ts +++ b/packages/core/src/view/folding/GraphFolding.ts @@ -66,6 +66,8 @@ class GraphFolding extends autoImplement() { getCollapseExpandResource = () => this.collapseExpandResource; + isFoldingEnabled = () => this.options.foldingEnabled; + /** * * @default true @@ -133,7 +135,7 @@ class GraphFolding extends autoImplement() { recurse: boolean = false, cells: CellArray | null = null, checkFoldable: boolean = false, - evt: EventObject | null = null + evt: Event | null = null ): CellArray | null { if (cells == null) { cells = this.getFoldableCells(this.getSelectionCells(), collapse); diff --git a/packages/core/src/view/geometry/shape/Shape.ts b/packages/core/src/view/geometry/shape/Shape.ts index c5d85a2bc..6c29d5a39 100644 --- a/packages/core/src/view/geometry/shape/Shape.ts +++ b/packages/core/src/view/geometry/shape/Shape.ts @@ -326,6 +326,8 @@ class Shape { image: ImageBox | null = null; + imageSrc: string | null = null; + indicatorColor: ColorValue = NONE; indicatorStrokeColor: ColorValue = NONE; @@ -334,6 +336,8 @@ class Shape { indicatorDirection: DirectionValue = DIRECTION_EAST; + indicatorImageSrc: string | null = null; + /** * Function: isHtmlAllowed * diff --git a/packages/core/src/view/geometry/shape/node/TextShape.ts b/packages/core/src/view/geometry/shape/node/TextShape.ts index 775bd67bc..bd88c9c46 100644 --- a/packages/core/src/view/geometry/shape/node/TextShape.ts +++ b/packages/core/src/view/geometry/shape/node/TextShape.ts @@ -63,7 +63,7 @@ import SvgCanvas2D from 'packages/core/src/util/canvas/SvgCanvas2D'; */ class TextShape extends Shape { constructor( - value: string, + value: string | HTMLElement | SVGGElement, bounds: Rectangle, align: AlignValue = ALIGN_CENTER, valign: VAlignValue = ALIGN_MIDDLE, @@ -112,8 +112,7 @@ class TextShape extends Shape { this.updateMargin(); } - // TODO: Document me! - value: string | HTMLElement | SVGGElement | null; + value: string | HTMLElement | SVGGElement; bounds: Rectangle; align: AlignValue; valign: VAlignValue; diff --git a/packages/core/src/view/label/GraphLabel.ts b/packages/core/src/view/label/GraphLabel.ts index efabe4272..fc5d1183c 100644 --- a/packages/core/src/view/label/GraphLabel.ts +++ b/packages/core/src/view/label/GraphLabel.ts @@ -61,7 +61,7 @@ class GraphLabel extends autoImplement() { * * @param cell {@link mxCell} whose label should be returned. */ - getLabel(cell: Cell): string | Node | null { + getLabel(cell: Cell) { let result: string | null = ''; if (this.isLabelsVisible() && cell != null) { @@ -71,6 +71,7 @@ class GraphLabel extends autoImplement() { result = this.convertValueToString(cell); } } + return result; } diff --git a/packages/core/src/view/panning/GraphPanning.ts b/packages/core/src/view/panning/GraphPanning.ts index 62dde0248..5158f6300 100644 --- a/packages/core/src/view/panning/GraphPanning.ts +++ b/packages/core/src/view/panning/GraphPanning.ts @@ -1,23 +1,19 @@ -import {hasScrollbars} from "../../util/Utils"; -import EventObject from "../event/EventObject"; -import InternalEvent from "../event/InternalEvent"; -import PanningManager from './PanningManager'; -import PanningHandler from "./PanningHandler"; -import Graph from "../Graph"; -import Cell from "../cell/datatypes/Cell"; -import Rectangle from "../geometry/Rectangle"; -import Point from "../geometry/Point"; +import { autoImplement, hasScrollbars } from '../../util/Utils'; +import EventObject from '../event/EventObject'; +import InternalEvent from '../event/InternalEvent'; +import PanningHandler from './PanningHandler'; +import Graph from '../Graph'; +import Cell from '../cell/datatypes/Cell'; +import Rectangle from '../geometry/Rectangle'; +import Point from '../geometry/Point'; +import GraphEvents from '../event/GraphEvents'; +import SelectionCellsHandler from '../selection/SelectionCellsHandler'; -class GraphPanning { - constructor(graph: Graph) { - this.graph = graph; - this.createHandlers() - } +type PartialGraph = Pick; +type PartialEvents = Pick; +type PartialClass = PartialGraph & PartialEvents; - graph: Graph; - - panningHandler: PanningHandler | null = null; - panningManager: PanningManager | null = null; +class GraphPanning extends autoImplement() { shiftPreview1: HTMLElement | null = null; shiftPreview2: HTMLElement | null = null; @@ -28,7 +24,9 @@ class GraphPanning { * then no panning occurs if this is `true`. * @default true */ - useScrollbarsForPanning: boolean = true; + useScrollbarsForPanning = true; + + isUseScrollbarsForPanning = () => this.useScrollbarsForPanning; /** * Specifies if autoscrolling should be carried out via mxPanningManager even @@ -38,7 +36,7 @@ class GraphPanning { * are visible and scrollable in all directions. * @default false */ - timerAutoScroll: boolean = false; + timerAutoScroll = false; /** * Specifies if panning via {@link panGraph} should be allowed to implement autoscroll @@ -47,38 +45,25 @@ class GraphPanning { * positive value. * @default false */ - allowAutoPanning: boolean = false; + allowAutoPanning = false; /** * Current horizontal panning value. * @default 0 */ - panDx: number = 0; + panDx = 0; + + getPanDx = () => this.panDx; + setPanDx = (dx: number) => (this.panDx = dx); /** * Current vertical panning value. * @default 0 */ - panDy: number = 0; + panDy = 0; - createHandlers() { - this.panningHandler = this.createPanningHandler(); - this.panningHandler.panningEnabled = false; - } - - /** - * Creates and returns a new {@link PanningHandler} to be used in this graph. - */ - createPanningHandler(): PanningHandler { - return new PanningHandler(this); - } - - /** - * Creates and returns an {@link PanningManager}. - */ - createPanningManager(): PanningManager { - return new PanningManager(this); - } + getPanDy = () => this.panDy; + setPanDy = (dy: number) => (this.panDy = dy); /** * Shifts the graph display by the given amount. This is used to preview @@ -88,30 +73,30 @@ class GraphPanning { * @param dx Amount to shift the graph along the x-axis. * @param dy Amount to shift the graph along the y-axis. */ - panGraph(dx: number, dy: number): void { - const container = this.graph.container; + panGraph(dx: number, dy: number) { + const container = this.getContainer(); if (this.useScrollbarsForPanning && hasScrollbars(container)) { container.scrollLeft = -dx; container.scrollTop = -dy; } else { - const canvas = this.graph.view.getCanvas(); + const canvas = this.getView().getCanvas(); // Puts everything inside the container in a DIV so that it // can be moved without changing the state of the container if (dx === 0 && dy === 0) { canvas.removeAttribute('transform'); - if (this.shiftPreview1 != null) { + if (this.shiftPreview1) { let child = this.shiftPreview1.firstChild; - while (child != null) { + while (child) { const next = child.nextSibling; container.appendChild(child); child = next; } - if (this.shiftPreview1.parentNode != null) { + if (this.shiftPreview1.parentNode) { this.shiftPreview1.parentNode.removeChild(this.shiftPreview1); } @@ -121,13 +106,13 @@ class GraphPanning { const shiftPreview2 = this.shiftPreview2; child = shiftPreview2.firstChild; - while (child != null) { + while (child) { const next = child.nextSibling; container.appendChild(child); child = next; } - if (shiftPreview2.parentNode != null) { + if (shiftPreview2.parentNode) { shiftPreview2.parentNode.removeChild(shiftPreview2); } this.shiftPreview2 = null; @@ -135,7 +120,7 @@ class GraphPanning { } else { canvas.setAttribute('transform', `translate(${dx},${dy})`); - if (this.shiftPreview1 == null) { + if (!this.shiftPreview1) { // Needs two divs for stuff before and after the SVG element this.shiftPreview1 = document.createElement('div'); this.shiftPreview1.style.position = 'absolute'; @@ -148,7 +133,7 @@ class GraphPanning { let current = this.shiftPreview1; let child = container.firstChild; - while (child != null) { + while (child) { const next = child.nextSibling; // SVG element is moved via transform attribute @@ -163,11 +148,11 @@ class GraphPanning { } // Inserts elements only if not empty - if (this.shiftPreview1.firstChild != null) { + if (this.shiftPreview1.firstChild) { container.insertBefore(this.shiftPreview1, canvas.parentNode); } - if (this.shiftPreview2.firstChild != null) { + if (this.shiftPreview2.firstChild) { container.appendChild(this.shiftPreview2); } } @@ -184,7 +169,7 @@ class GraphPanning { this.panDx = dx; this.panDy = dy; - this.graph.fireEvent(new EventObject(InternalEvent.PAN)); + this.fireEvent(new EventObject(InternalEvent.PAN)); } } @@ -203,23 +188,18 @@ class GraphPanning { * @param cell {@link mxCell} to be made visible. * @param center Optional boolean flag. Default is `false`. */ - scrollCellToVisible(cell: Cell, center: boolean = false): void { - const x = -this.graph.view.translate.x; - const y = -this.graph.view.translate.y; + scrollCellToVisible(cell: Cell, center = false) { + const x = -this.getView().translate.x; + const y = -this.getView().translate.y; - const state = this.graph.view.getState(cell); + const state = this.getView().getState(cell); - if (state != null) { - const bounds = new Rectangle( - x + state.x, - y + state.y, - state.width, - state.height - ); + if (state) { + const bounds = new Rectangle(x + state.x, y + state.y, state.width, state.height); - if (center && this.graph.container != null) { - const w = this.graph.container.clientWidth; - const h = this.graph.container.clientHeight; + if (center && this.getContainer()) { + const w = this.getContainer().clientWidth; + const h = this.getContainer().clientHeight; bounds.x = bounds.getCenterX() - w / 2; bounds.width = w; @@ -227,20 +207,14 @@ class GraphPanning { bounds.height = h; } - const tr = new Point( - this.graph.view.translate.x, - this.graph.view.translate.y - ); + const tr = new Point(this.getView().translate.x, this.getView().translate.y); if (this.scrollRectToVisible(bounds)) { // Triggers an update via the view's event source - const tr2 = new Point( - this.graph.view.translate.x, - this.graph.view.translate.y - ); - this.graph.view.translate.x = tr.x; - this.graph.view.translate.y = tr.y; - this.graph.view.setTranslate(tr2.x, tr2.y); + const tr2 = new Point(this.getView().translate.x, this.getView().translate.y); + this.getView().translate.x = tr.x; + this.getView().translate.y = tr.y; + this.getView().setTranslate(tr2.x, tr2.y); } } } @@ -250,84 +224,84 @@ class GraphPanning { * * @param rect {@link mxRectangle} to be made visible. */ - scrollRectToVisible(rect: Rectangle): boolean { + scrollRectToVisible(rect: Rectangle) { let isChanged = false; - if (rect != null) { - const container = this.graph.container; - const w = container.offsetWidth; - const h = container.offsetHeight; + const container = this.getContainer(); + const w = container.offsetWidth; + const h = container.offsetHeight; - const widthLimit = Math.min(w, rect.width); - const heightLimit = Math.min(h, rect.height); + const widthLimit = Math.min(w, rect.width); + const heightLimit = Math.min(h, rect.height); - if (hasScrollbars(container)) { - rect.x += this.graph.view.translate.x; - rect.y += this.graph.view.translate.y; - let dx = container.scrollLeft - rect.x; - const ddx = Math.max(dx - container.scrollLeft, 0); + if (hasScrollbars(container)) { + rect.x += this.getView().translate.x; + rect.y += this.getView().translate.y; + let dx = container.scrollLeft - rect.x; + const ddx = Math.max(dx - container.scrollLeft, 0); + + if (dx > 0) { + container.scrollLeft -= dx + 2; + } else { + dx = rect.x + widthLimit - container.scrollLeft - container.clientWidth; if (dx > 0) { - container.scrollLeft -= dx + 2; - } else { - dx = - rect.x + widthLimit - container.scrollLeft - container.clientWidth; - - if (dx > 0) { - container.scrollLeft += dx + 2; - } + container.scrollLeft += dx + 2; } + } - let dy = container.scrollTop - rect.y; - const ddy = Math.max(0, dy - container.scrollTop); + let dy = container.scrollTop - rect.y; + const ddy = Math.max(0, dy - container.scrollTop); + + if (dy > 0) { + container.scrollTop -= dy + 2; + } else { + dy = rect.y + heightLimit - container.scrollTop - container.clientHeight; if (dy > 0) { - container.scrollTop -= dy + 2; - } else { - dy = - rect.y + heightLimit - container.scrollTop - container.clientHeight; - - if (dy > 0) { - container.scrollTop += dy + 2; - } + container.scrollTop += dy + 2; } + } - if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0)) { - this.graph.view.setTranslate(ddx, ddy); - } - } else { - const x = -this.graph.view.translate.x; - const y = -this.graph.view.translate.y; + if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0)) { + this.getView().setTranslate(ddx, ddy); + } + } else { + const x = -this.getView().translate.x; + const y = -this.getView().translate.y; - const s = this.graph.view.scale; + const s = this.getView().scale; - if (rect.x + widthLimit > x + w) { - this.graph.view.translate.x -= (rect.x + widthLimit - w - x) / s; - isChanged = true; - } + if (rect.x + widthLimit > x + w) { + this.getView().translate.x -= (rect.x + widthLimit - w - x) / s; + isChanged = true; + } - if (rect.y + heightLimit > y + h) { - this.graph.view.translate.y -= (rect.y + heightLimit - h - y) / s; - isChanged = true; - } + if (rect.y + heightLimit > y + h) { + this.getView().translate.y -= (rect.y + heightLimit - h - y) / s; + isChanged = true; + } - if (rect.x < x) { - this.graph.view.translate.x += (x - rect.x) / s; - isChanged = true; - } + if (rect.x < x) { + this.getView().translate.x += (x - rect.x) / s; + isChanged = true; + } - if (rect.y < y) { - this.graph.view.translate.y += (y - rect.y) / s; - isChanged = true; - } + if (rect.y < y) { + this.getView().translate.y += (y - rect.y) / s; + isChanged = true; + } - if (isChanged) { - this.graph.view.refresh(); + if (isChanged) { + this.getView().refresh(); - // Repaints selection marker (ticket 18) - if (this.selectionCellsHandler != null) { - this.selectionCellsHandler.refresh(); - } + const selectionCellsHandler = this.getPlugin( + 'SelectionCellsHandler' + ) as SelectionCellsHandler; + + // Repaints selection marker (ticket 18) + if (selectionCellsHandler) { + selectionCellsHandler.refresh(); } } } @@ -345,10 +319,11 @@ class GraphPanning { * * @param enabled Boolean indicating if panning should be enabled. */ - setPanning(enabled: boolean): void { - (this.panningHandler).panningEnabled = enabled; - } + setPanning(enabled: boolean) { + const panningHandler = this.getPlugin('PanningHandler') as PanningHandler; + if (panningHandler) panningHandler.panningEnabled = enabled; + } } export default GraphPanning; diff --git a/packages/core/src/view/panning/PanningHandler.ts b/packages/core/src/view/panning/PanningHandler.ts index 3052d9911..7a207f9fa 100644 --- a/packages/core/src/view/panning/PanningHandler.ts +++ b/packages/core/src/view/panning/PanningHandler.ts @@ -5,10 +5,22 @@ * Type definitions from the typed-mxgraph project */ import EventSource from '../event/EventSource'; -import utils, { hasScrollbars } from '../../util/Utils'; +import { hasScrollbars } from '../../util/Utils'; import EventObject from '../event/EventObject'; import InternalEvent from '../event/InternalEvent'; -import { isConsumed, isControlDown, isLeftMouseButton, isMultiTouchEvent, isPopupTrigger, isShiftDown } from '../../util/EventUtils'; +import { + isConsumed, + isControlDown, + isLeftMouseButton, + isMultiTouchEvent, + isPopupTrigger, + isShiftDown, +} from '../../util/EventUtils'; +import PanningManager from './PanningManager'; +import InternalMouseEvent from '../event/InternalMouseEvent'; + +import type { GraphPlugin, MouseEventListener } from '../../types'; +import type { MaxGraph } from '../Graph'; /** * Class: mxPanningHandler @@ -39,66 +51,65 @@ import { isConsumed, isControlDown, isLeftMouseButton, isMultiTouchEvent, isPopu * Fires when the panning handler changes its state to false. The * event property contains the corresponding . */ -class PanningHandler extends EventSource { - constructor(graph) { +class PanningHandler extends EventSource implements GraphPlugin { + static pluginId = 'PanningHandler'; + + constructor(graph: MaxGraph) { super(); - if (graph != null) { - this.graph = graph; - this.graph.addMouseListener(this); + this.graph = graph; + this.graph.addMouseListener(this); - // Handles force panning event - this.forcePanningHandler = (sender, evt) => { - const evtName = evt.getProperty('eventName'); - const me = evt.getProperty('event'); + // Handles force panning event + this.forcePanningHandler = (sender: EventSource, eo: EventObject) => { + const evtName = eo.getProperty('eventName'); + const me = eo.getProperty('event'); - if (evtName === InternalEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) { - this.start(me); - this.active = true; - this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me)); - me.consume(); - } - }; + if (evtName === InternalEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) { + this.start(me); + this.active = true; + this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me)); + me.consume(); + } + }; - this.graph.addListener( - InternalEvent.FIRE_MOUSE_EVENT, - this.forcePanningHandler - ); + this.graph.addListener(InternalEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler); - // Handles pinch gestures - this.gestureHandler = (sender, eo) => { - if (this.isPinchEnabled()) { - const evt = eo.getProperty('event'); + // Handles pinch gestures + this.gestureHandler = (sender: EventSource, eo: EventObject) => { + if (this.isPinchEnabled()) { + const evt = eo.getProperty('event'); - if (!isConsumed(evt) && evt.type === 'gesturestart') { - this.initialScale = this.graph.view.scale; + if (!isConsumed(evt) && evt.type === 'gesturestart') { + this.initialScale = this.graph.view.scale; - // Forces start of panning when pinch gesture starts - if (!this.active && this.mouseDownEvent != null) { - this.start(this.mouseDownEvent); - this.mouseDownEvent = null; - } - } else if (evt.type === 'gestureend' && this.initialScale != null) { - this.initialScale = null; - } - - if (this.initialScale != null) { - this.zoomGraph(evt); + // Forces start of panning when pinch gesture starts + if (!this.active && this.mouseDownEvent) { + this.start(this.mouseDownEvent); + this.mouseDownEvent = null; } + } else if (evt.type === 'gestureend' && this.initialScale !== 0) { + this.initialScale = 0; } - }; - this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler); - - this.mouseUpListener = () => { - if (this.active) { - this.reset(); + if (this.initialScale !== 0) { + this.zoomGraph(evt); } - }; + } + }; - // Stops scrolling on every mouseup anywhere in the document - InternalEvent.addListener(document, 'mouseup', this.mouseUpListener); - } + this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler); + + this.mouseUpListener = () => { + if (this.active) { + this.reset(); + } + }; + + // Stops scrolling on every mouseup anywhere in the document + InternalEvent.addListener(document, 'mouseup', this.mouseUpListener); + + this.panningManager = new PanningManager(graph); } /** @@ -106,8 +117,9 @@ class PanningHandler extends EventSource { * * Reference to the enclosing . */ - // graph: mxGraph; - graph = null; + graph: MaxGraph; + + panningManager: PanningManager; /** * Variable: useLeftButtonForPanning @@ -115,7 +127,6 @@ class PanningHandler extends EventSource { * Specifies if panning should be active for the left mouse button. * Setting this to true may conflict with . Default is false. */ - // useLeftButtonForPanning: boolean; useLeftButtonForPanning = false; /** @@ -123,7 +134,6 @@ class PanningHandler extends EventSource { * * Specifies if should also be used for panning. */ - // usePopupTrigger: boolean; usePopupTrigger = true; /** @@ -132,7 +142,6 @@ class PanningHandler extends EventSource { * Specifies if panning should be active even if there is a cell under the * mousepointer. Default is false. */ - // ignoreCell: boolean; ignoreCell = false; /** @@ -140,7 +149,6 @@ class PanningHandler extends EventSource { * * Specifies if the panning should be previewed. Default is true. */ - // previewEnabled: boolean; previewEnabled = true; /** @@ -149,7 +157,6 @@ class PanningHandler extends EventSource { * Specifies if the panning steps should be aligned to the grid size. * Default is false. */ - // useGrid: boolean; useGrid = false; /** @@ -157,7 +164,6 @@ class PanningHandler extends EventSource { * * Specifies if panning should be enabled. Default is true. */ - // panningEnabled: boolean; panningEnabled = true; /** @@ -165,15 +171,15 @@ class PanningHandler extends EventSource { * * Specifies if pinch gestures should be handled as zoom. Default is true. */ - // pinchEnabled: boolean; pinchEnabled = true; + initialScale = 0; + /** * Variable: maxScale * * Specifies the maximum scale. Default is 8. */ - // maxScale: number; maxScale = 8; /** @@ -181,7 +187,6 @@ class PanningHandler extends EventSource { * * Specifies the minimum scale. Default is 0.01. */ - // minScale: number; minScale = 0.01; /** @@ -189,23 +194,20 @@ class PanningHandler extends EventSource { * * Holds the current horizontal offset. */ - // dx: number; - dx = null; + dx = 0; /** * Variable: dy * * Holds the current vertical offset. */ - // dy: number; - dy = null; + dy = 0; /** * Variable: startX * * Holds the x-coordinate of the start point. */ - // startX: number; startX = 0; /** @@ -213,17 +215,29 @@ class PanningHandler extends EventSource { * * Holds the y-coordinate of the start point. */ - // startY: number; startY = 0; + dx0 = 0; + dy0 = 0; + + panningTrigger = false; + + active = false; + + forcePanningHandler: (sender: EventSource, evt: EventObject) => void; + gestureHandler: (sender: EventSource, evt: EventObject) => void; + + mouseUpListener: MouseEventListener; + + mouseDownEvent: InternalMouseEvent | null = null; + /** * Function: isActive * * Returns true if the handler is currently active. */ - // isActive(): boolean; isActive() { - return this.active || this.initialScale != null; + return this.active || this.initialScale !== null; } /** @@ -231,7 +245,6 @@ class PanningHandler extends EventSource { * * Returns . */ - // isPanningEnabled(): boolean; isPanningEnabled() { return this.panningEnabled; } @@ -241,8 +254,7 @@ class PanningHandler extends EventSource { * * Sets . */ - // setPanningEnabled(value: boolean): void; - setPanningEnabled(value) { + setPanningEnabled(value: boolean) { this.panningEnabled = value; } @@ -251,7 +263,6 @@ class PanningHandler extends EventSource { * * Returns . */ - // isPinchEnabled(): boolean; isPinchEnabled() { return this.pinchEnabled; } @@ -261,8 +272,7 @@ class PanningHandler extends EventSource { * * Sets . */ - // setPinchEnabled(value: boolean): void; - setPinchEnabled(value) { + setPinchEnabled(value: boolean) { this.pinchEnabled = value; } @@ -273,14 +283,11 @@ class PanningHandler extends EventSource { * given cell. This returns true if control-shift is pressed or if * is true and the event is a popup trigger. */ - // isPanningTrigger(me: mxMouseEvent): boolean; - isPanningTrigger(me) { + isPanningTrigger(me: InternalMouseEvent) { const evt = me.getEvent(); return ( - (this.useLeftButtonForPanning && - me.getState() == null && - isLeftMouseButton(evt)) || + (this.useLeftButtonForPanning && !me.getState() && isLeftMouseButton(evt)) || (isControlDown(evt) && isShiftDown(evt)) || (this.usePopupTrigger && isPopupTrigger(evt)) ); @@ -293,8 +300,7 @@ class PanningHandler extends EventSource { * implementation always returns true if is true or for * multi touch events. */ - // isForcePanningEvent(me: mxMouseEvent): boolean; - isForcePanningEvent(me) { + isForcePanningEvent(me: InternalMouseEvent) { return this.ignoreCell || isMultiTouchEvent(me.getEvent()); } @@ -304,8 +310,7 @@ class PanningHandler extends EventSource { * Handles the event by initiating the panning. By consuming the event all * subsequent events of the gesture are redirected to this handler. */ - // mouseDown(sender: any, me: mxMouseEvent): void; - mouseDown(sender, me) { + mouseDown(sender: EventSource, me: InternalMouseEvent) { this.mouseDownEvent = me; if ( @@ -324,16 +329,15 @@ class PanningHandler extends EventSource { * * Starts panning at the given event. */ - // start(me: mxMouseEvent): void; - start(me) { + start(me: InternalMouseEvent) { this.dx0 = -this.graph.container.scrollLeft; this.dy0 = -this.graph.container.scrollTop; // Stores the location of the trigger event this.startX = me.getX(); this.startY = me.getY(); - this.dx = null; - this.dy = null; + this.dx = 0; + this.dy = 0; this.panningTrigger = true; } @@ -366,8 +370,7 @@ class PanningHandler extends EventSource { * }; * (end) */ - // consumePanningTrigger(me: mxMouseEvent): void; - consumePanningTrigger(me) { + consumePanningTrigger(me: InternalMouseEvent) { me.consume(); } @@ -376,8 +379,7 @@ class PanningHandler extends EventSource { * * Handles the event by updating the panning on the graph. */ - // mouseMove(sender: any, me: mxMouseEvent): void; - mouseMove(sender, me) { + mouseMove(sender: EventSource, me: InternalMouseEvent) { this.dx = me.getX() - this.startX; this.dy = me.getY() - this.startY; @@ -399,8 +401,8 @@ class PanningHandler extends EventSource { // Panning is activated only if the mouse is moved // beyond the graph tolerance this.active = - Math.abs(this.dx) > this.graph.tolerance || - Math.abs(this.dy) > this.graph.tolerance; + Math.abs(this.dx) > this.graph.getSnapTolerance() || + Math.abs(this.dy) > this.graph.getSnapTolerance(); if (!tmp && this.active) { this.fireEvent(new EventObject(InternalEvent.PAN_START, 'event', me)); @@ -418,13 +420,12 @@ class PanningHandler extends EventSource { * Handles the event by setting the translation on the view or showing the * popupmenu. */ - // mouseUp(sender: any, me: mxMouseEvent): void; - mouseUp(sender, me) { + mouseUp(sender: EventSource, me: InternalMouseEvent) { if (this.active) { - if (this.dx != null && this.dy != null) { + if (this.dx !== 0 && this.dy !== 0) { // Ignores if scrollbars have been used for panning if ( - !this.graph.useScrollbarsForPanning || + !this.graph.isUseScrollbarsForPanning() || !hasScrollbars(this.graph.container) ) { const { scale } = this.graph.getView(); @@ -447,16 +448,11 @@ class PanningHandler extends EventSource { * * Zooms the graph to the given value and consumed the event if needed. */ - zoomGraph(evt) { + zoomGraph(evt: Event) { + // @ts-ignore evt may have scale property let value = Math.round(this.initialScale * evt.scale * 100) / 100; - - if (this.minScale != null) { - value = Math.max(this.minScale, value); - } - - if (this.maxScale != null) { - value = Math.min(this.maxScale, value); - } + value = Math.max(this.minScale, value); + value = Math.min(this.maxScale, value); if (this.graph.view.scale !== value) { this.graph.zoomTo(value); @@ -470,13 +466,12 @@ class PanningHandler extends EventSource { * Handles the event by setting the translation on the view or showing the * popupmenu. */ - // reset(): void; reset() { this.panningTrigger = false; this.mouseDownEvent = null; this.active = false; - this.dx = null; - this.dy = null; + this.dx = 0; + this.dy = 0; } /** @@ -484,8 +479,7 @@ class PanningHandler extends EventSource { * * Pans by the given amount. */ - // panGraph(dx: number, dy: number): void; - panGraph(dx, dy) { + panGraph(dx: number, dy: number) { this.graph.getView().setTranslate(dx, dy); } @@ -494,8 +488,7 @@ class PanningHandler extends EventSource { * * Destroys the handler and all its resources and DOM nodes. */ - // destroy(): void; - destroy() { + onDestroy() { this.graph.removeMouseListener(this); this.graph.removeListener(this.forcePanningHandler); this.graph.removeListener(this.gestureHandler); diff --git a/packages/core/src/view/panning/PanningManager.ts b/packages/core/src/view/panning/PanningManager.ts index 0aff317de..90d479884 100644 --- a/packages/core/src/view/panning/PanningManager.ts +++ b/packages/core/src/view/panning/PanningManager.ts @@ -5,8 +5,11 @@ * Type definitions from the typed-mxgraph project */ +import { MouseEventListener, MouseListenerSet } from '../../types'; import { hasScrollbars } from '../../util/Utils'; import EventObject from '../event/EventObject'; +import InternalEvent from '../event/InternalEvent'; +import { MaxGraph } from '../Graph'; /** * Class: mxPanningManager @@ -14,7 +17,7 @@ import EventObject from '../event/EventObject'; * Implements a handler for panning. */ class PanningManager { - constructor(graph) { + constructor(graph: MaxGraph) { this.thread = null; this.active = false; this.tdx = 0; @@ -46,7 +49,7 @@ class PanningManager { }; // Stops scrolling on every mouseup anywhere in the document - mxEvent.addListener(document, 'mouseup', this.mouseUpListener); + InternalEvent.addListener(document, 'mouseup', this.mouseUpListener); const createThread = () => { this.scrollbars = hasScrollbars(graph.container); @@ -61,9 +64,9 @@ class PanningManager { const left = -graph.container.scrollLeft - Math.ceil(this.dx); const top = -graph.container.scrollTop - Math.ceil(this.dy); graph.panGraph(left, top); - graph.panDx = this.scrollLeft - graph.container.scrollLeft; - graph.panDy = this.scrollTop - graph.container.scrollTop; - graph.fireEvent(new EventObject(mxEvent.PAN)); + graph.setPanDx(this.scrollLeft - graph.container.scrollLeft); + graph.setPanDy(this.scrollTop - graph.container.scrollTop); + graph.fireEvent(new EventObject(InternalEvent.PAN)); // TODO: Implement graph.autoExtend } else { graph.panGraph(this.getDx(), this.getDy()); @@ -171,8 +174,8 @@ class PanningManager { this.tdy = 0; if (!this.scrollbars) { - const px = graph.panDx; - const py = graph.panDy; + const px = graph.getPanDx(); + const py = graph.getPanDy(); if (px != 0 || py != 0) { graph.panGraph(0, 0); @@ -182,16 +185,16 @@ class PanningManager { ); } } else { - graph.panDx = 0; - graph.panDy = 0; - graph.fireEvent(new EventObject(mxEvent.PAN)); + graph.setPanDx(0); + graph.setPanDy(0); + graph.fireEvent(new EventObject(InternalEvent.PAN)); } } }; this.destroy = () => { graph.removeMouseListener(this.mouseListener); - mxEvent.removeListener(document, 'mouseup', this.mouseUpListener); + InternalEvent.removeListener(document, 'mouseup', this.mouseUpListener); }; } @@ -200,7 +203,6 @@ class PanningManager { * * Damper value for the panning. Default is 1/6. */ - // damper: number; damper = 1 / 6; /** @@ -208,7 +210,6 @@ class PanningManager { * * Delay in milliseconds for the panning. Default is 10. */ - // delay: number; delay = 10; /** @@ -216,7 +217,6 @@ class PanningManager { * * Specifies if mouse events outside of the component should be handled. Default is true. */ - // handleMouseOut: boolean; handleMouseOut = true; /** @@ -224,8 +224,33 @@ class PanningManager { * * Border to handle automatic panning inside the component. Default is 0 (disabled). */ - // border: number; border = 0; + + thread: number | null = null; + + active = false; + + tdx = 0; + tdy = 0; + t0x = 0; + t0y = 0; + dx = 0; + dy = 0; + scrollbars = false; + scrollLeft = 0; + scrollTop = 0; + + mouseListener: MouseListenerSet; + + mouseUpListener: MouseEventListener; + + stop: () => void; + isActive: () => boolean; + getDx: () => number; + getDy: () => number; + start: () => void; + panTo: (x: number, y: number, w: number, h: number) => void; + destroy: () => void; } export default PanningManager; diff --git a/packages/core/src/view/popups_menus/PopupMenuHandler.ts b/packages/core/src/view/popups_menus/PopupMenuHandler.ts index 9528b29a7..1bd8036fb 100644 --- a/packages/core/src/view/popups_menus/PopupMenuHandler.ts +++ b/packages/core/src/view/popups_menus/PopupMenuHandler.ts @@ -11,6 +11,7 @@ import { getMainEvent, isMultiTouchEvent } from '../../util/EventUtils'; import Graph from '../Graph'; import InternalMouseEvent from '../event/InternalMouseEvent'; import Cell from '../cell/datatypes/Cell'; +import { GraphPlugin } from '../../types'; /** * Class: mxPopupMenuHandler @@ -21,7 +22,7 @@ import Cell from '../cell/datatypes/Cell'; * * Constructs an event handler that creates a . */ -class PopupMenuHandler extends mxPopupMenu { +class PopupMenuHandler extends mxPopupMenu implements GraphPlugin { constructor(graph: Graph, factoryMethod: any) { super(); diff --git a/packages/core/src/view/selection/CellHighlight.ts b/packages/core/src/view/selection/CellHighlight.ts index bd37935e3..d6508b965 100644 --- a/packages/core/src/view/selection/CellHighlight.ts +++ b/packages/core/src/view/selection/CellHighlight.ts @@ -72,7 +72,7 @@ class CellHighlight { } // TODO: Document me!! - highlightColor: ColorValue | null = null; + highlightColor: ColorValue; strokeWidth: number = 0; @@ -80,7 +80,7 @@ class CellHighlight { opacity = 100; - repaintHandler: Function | null = null; + repaintHandler: Function; shape: Shape | null = null; @@ -112,14 +112,14 @@ class CellHighlight { * Holds the handler that automatically invokes reset if the highlight should be hidden. * @default null */ - resetHandler: Function | null = null; + resetHandler: Function; /** * Sets the color of the rectangle used to highlight drop targets. * * @param {string} color - String that represents the new highlight color. */ - setHighlightColor(color: ColorValue | null) { + setHighlightColor(color: ColorValue) { this.highlightColor = color; if (this.shape) { @@ -134,10 +134,12 @@ class CellHighlight { this.shape = this.createShape(); this.repaint(); - const node = this.shape.node; + if (this.shape) { + const node = this.shape.node; - if (!this.keepOnTop && node?.parentNode?.firstChild !== node) { - node.parentNode.insertBefore(node, node.parentNode.firstChild); + if (!this.keepOnTop && node?.parentNode?.firstChild !== node && node.parentNode) { + node.parentNode.insertBefore(node, node.parentNode.firstChild); + } } } @@ -145,11 +147,11 @@ class CellHighlight { * Creates and returns the highlight shape for the given state. */ createShape() { - if (!this.state) return; + if (!this.state) return null; const shape = this.graph.cellRenderer.createShape(this.state); - shape.svgStrokeTolerance = this.graph.tolerance; + shape.svgStrokeTolerance = this.graph.getEventTolerance(); shape.points = this.state.absolutePoints; shape.apply(this.state); shape.stroke = this.highlightColor; @@ -173,7 +175,7 @@ class CellHighlight { /** * Updates the highlight after a change of the model or view. */ - getStrokeWidth(state: CellState | null = null): number | null { + getStrokeWidth(state: CellState | null = null) { return this.strokeWidth; } @@ -256,13 +258,13 @@ class CellHighlight { /** * Destroys the handler and all its resources and DOM nodes. */ - destroy(): void { - const graph = this.graph; + destroy() { + const graph = this.graph; graph.getView().removeListener(this.resetHandler); graph.getView().removeListener(this.repaintHandler); graph.getModel().removeListener(this.repaintHandler); - if (this.shape != null) { + if (this.shape) { this.shape.destroy(); this.shape = null; } diff --git a/packages/core/src/view/selection/GraphSelection.ts b/packages/core/src/view/selection/GraphSelection.ts index b8d3eed48..91dc5452b 100644 --- a/packages/core/src/view/selection/GraphSelection.ts +++ b/packages/core/src/view/selection/GraphSelection.ts @@ -15,6 +15,7 @@ import type GraphCells from '../cell/GraphCells'; import type Graph from '../Graph'; import type GraphEvents from '../event/GraphEvents'; import type EventSource from '../event/EventSource'; +import { MaxGraph } from '../Graph'; type PartialGraph = Pick< Graph, @@ -36,6 +37,8 @@ class GraphSelection extends autoImplement() { */ doneResource: string = mxClient.language !== 'none' ? 'done' : ''; + getDoneResource = () => this.doneResource; + /** * Specifies the resource key for the status message while the selection is * being updated. If the resource for this key does not exist then the @@ -44,6 +47,8 @@ class GraphSelection extends autoImplement() { updatingSelectionResource: string = mxClient.language !== 'none' ? 'updatingSelection' : ''; + getUpdatingSelectionResource = () => this.updatingSelectionResource; + /** * Specifies if only one selected item at a time is allowed. * Default is false. @@ -222,7 +227,7 @@ class GraphSelection extends autoImplement() { (removed && removed.length > 0 && removed[0]) ) { const change = new SelectionChange( - this, + this as MaxGraph, added || new CellArray(), removed || new CellArray() ); @@ -512,7 +517,7 @@ class GraphSelection extends autoImplement() { ) { const filter = (cell: Cell) => { return ( - this.getView().getState(cell) && + !!this.getView().getState(cell) && (((selectGroups || cell.getChildCount() === 0) && cell.isVertex() && vertices && diff --git a/packages/core/src/view/selection/RubberBand.ts b/packages/core/src/view/selection/RubberBand.ts index 37e3eec9f..220d6cb4a 100644 --- a/packages/core/src/view/selection/RubberBand.ts +++ b/packages/core/src/view/selection/RubberBand.ts @@ -17,9 +17,10 @@ import mxClient from '../../mxClient'; import Rectangle from '../geometry/Rectangle'; import { isAltDown, isMultiTouchEvent } from '../../util/EventUtils'; import { clearSelection } from '../../util/DomUtils'; -import Graph from '../Graph'; +import { MaxGraph } from '../Graph'; import { GraphPlugin } from '../../types'; import EventObject from '../event/EventObject'; +import EventSource from '../event/EventSource'; /** * Event handler that selects rectangular regions. @@ -27,23 +28,14 @@ import EventObject from '../event/EventObject'; * To enable rubberband selection in a graph, use the following code. */ class RubberBand implements GraphPlugin { - forceRubberbandHandler?: Function; - panHandler?: Function; - gestureHandler?: Function; - graph?: Graph; - first: Point | null = null; - destroyed: boolean = false; - dragHandler?: Function; - dropHandler?: Function; + static pluginId = 'RubberBand'; - constructor() {} - - onInit(graph: Graph) { + constructor(graph: MaxGraph) { this.graph = graph; this.graph.addMouseListener(this); // Handles force rubberband event - this.forceRubberbandHandler = (sender: any, evt: EventObject) => { + this.forceRubberbandHandler = (sender: EventSource, evt: EventObject) => { const evtName = evt.getProperty('eventName'); const me = evt.getProperty('event'); @@ -67,8 +59,8 @@ class RubberBand implements GraphPlugin { this.graph.addListener(InternalEvent.PAN, this.panHandler); // Does not show menu if any touch gestures take place after the trigger - this.gestureHandler = (sender, eo) => { - if (this.first != null) { + this.gestureHandler = (sender: EventSource, eo: EventObject) => { + if (this.first) { this.reset(); } }; @@ -76,6 +68,20 @@ class RubberBand implements GraphPlugin { this.graph.addListener(InternalEvent.GESTURE, this.gestureHandler); } + forceRubberbandHandler: Function; + panHandler: Function; + gestureHandler: Function; + graph: MaxGraph; + first: Point | null = null; + destroyed: boolean = false; + dragHandler: ((evt: MouseEvent) => void) | null = null; + dropHandler: ((evt: MouseEvent) => void) | null = null; + + x = 0; + y = 0; + width = 0; + height = 0; + /** * Specifies the default opacity to be used for the rubberband div. Default is 20. */ @@ -93,14 +99,14 @@ class RubberBand implements GraphPlugin { * * Holds the DIV element which is currently visible. */ - div = null; + div: HTMLElement | null = null; /** * Variable: sharedDiv * * Holds the DIV element which is used to display the rubberband. */ - sharedDiv = null; + sharedDiv: HTMLElement | null = null; /** * Variable: currentX @@ -119,12 +125,12 @@ class RubberBand implements GraphPlugin { /** * Optional fade out effect. Default is false. */ - fadeOut: boolean = false; + fadeOut = false; /** * Creates the rubberband selection shape. */ - isEnabled(): boolean { + isEnabled() { return this.enabled; } @@ -155,12 +161,12 @@ class RubberBand implements GraphPlugin { * event all subsequent events of the gesture are redirected to this * handler. */ - mouseDown(sender: any, me: InternalMouseEvent): void { + mouseDown(sender: EventSource, me: InternalMouseEvent) { if ( !me.isConsumed() && this.isEnabled() && this.graph.isEnabled() && - me.getState() == null && + !me.getState() && !isMultiTouchEvent(me.getEvent()) ) { const offset = getOffset(this.graph.container); @@ -181,12 +187,12 @@ class RubberBand implements GraphPlugin { /** * Creates the rubberband selection shape. */ - start(x: number, y: number): void { + start(x: number, y: number) { this.first = new Point(x, y); const { container } = this.graph; - function createMouseEvent(evt) { + function createMouseEvent(evt: MouseEvent) { const me = new InternalMouseEvent(evt); const pt = convertPoint(container, me.getX(), me.getY()); @@ -196,11 +202,11 @@ class RubberBand implements GraphPlugin { return me; } - this.dragHandler = (evt) => { + this.dragHandler = (evt: MouseEvent) => { this.mouseMove(this.graph, createMouseEvent(evt)); }; - this.dropHandler = (evt) => { + this.dropHandler = (evt: MouseEvent) => { this.mouseUp(this.graph, createMouseEvent(evt)); }; @@ -220,8 +226,8 @@ class RubberBand implements GraphPlugin { * * Handles the event by updating therubberband selection. */ - mouseMove(sender: any, me: InternalMouseEvent): void { - if (!me.isConsumed() && this.first != null) { + mouseMove(sender: EventSource, me: InternalMouseEvent) { + if (!me.isConsumed() && this.first) { const origin = getScrollOrigin(this.graph.container); const offset = getOffset(this.graph.container); origin.x -= offset.x; @@ -230,10 +236,10 @@ class RubberBand implements GraphPlugin { const y = me.getY() + origin.y; const dx = this.first.x - x; const dy = this.first.y - y; - const tol = this.graph.tolerance; + const tol = this.graph.getEventTolerance(); - if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) { - if (this.div == null) { + if (this.div || Math.abs(dx) > tol || Math.abs(dy) > tol) { + if (!this.div) { this.div = this.createShape(); } @@ -250,8 +256,8 @@ class RubberBand implements GraphPlugin { /** * Creates the rubberband selection shape. */ - createShape(): HTMLElement | null { - if (this.sharedDiv == null) { + createShape() { + if (!this.sharedDiv) { this.sharedDiv = document.createElement('div'); this.sharedDiv.className = 'mxRubberband'; setOpacity(this.sharedDiv, this.defaultOpacity); @@ -272,8 +278,8 @@ class RubberBand implements GraphPlugin { * * Returns true if this handler is active. */ - isActive(sender: any, me: InternalMouseEvent): boolean { - return this.div != null && this.div.style.display !== 'none'; + isActive(sender?: EventSource, me?: InternalMouseEvent) { + return this.div && this.div.style.display !== 'none'; } /** @@ -282,7 +288,7 @@ class RubberBand implements GraphPlugin { * Handles the event by selecting the region of the rubberband using * . */ - mouseUp(sender: any, me: InternalMouseEvent): void { + mouseUp(sender: EventSource, me: InternalMouseEvent) { const active = this.isActive(); this.reset(); @@ -298,7 +304,7 @@ class RubberBand implements GraphPlugin { * Resets the state of this handler and selects the current region * for the given event. */ - execute(evt) { + execute(evt: MouseEvent) { const rect = new Rectangle(this.x, this.y, this.width, this.height); this.graph.selectRegion(rect, evt); } @@ -309,18 +315,18 @@ class RubberBand implements GraphPlugin { * Resets the state of the rubberband selection. */ reset() { - if (this.div != null) { + if (this.div) { if (mxClient.IS_SVG && this.fadeOut) { const temp = this.div; setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear'); temp.style.pointerEvents = 'none'; - temp.style.opacity = 0; + temp.style.opacity = String(0); window.setTimeout(() => { - temp.parentNode.removeChild(temp); + if (temp.parentNode) temp.parentNode.removeChild(temp); }, 200); } else { - this.div.parentNode.removeChild(this.div); + if (this.div.parentNode) this.div.parentNode.removeChild(this.div); } } @@ -344,7 +350,7 @@ class RubberBand implements GraphPlugin { * * Sets and and calls . */ - update(x, y) { + update(x: number, y: number) { this.currentX = x; this.currentY = y; @@ -357,9 +363,9 @@ class RubberBand implements GraphPlugin { * Computes the bounding box and updates the style of the
. */ repaint() { - if (this.div != null) { - const x = this.currentX - this.graph.panDx; - const y = this.currentY - this.graph.panDy; + if (this.div && this.first) { + const x = this.currentX - this.graph.getPanDx(); + const y = this.currentY - this.graph.getPanDy(); this.x = Math.min(this.first.x, x); this.y = Math.min(this.first.y, y); @@ -391,7 +397,7 @@ class RubberBand implements GraphPlugin { this.graph.removeListener(this.panHandler); this.reset(); - if (this.sharedDiv != null) { + if (this.sharedDiv) { this.sharedDiv = null; } } diff --git a/packages/core/src/view/selection/SelectionCellsHandler.ts b/packages/core/src/view/selection/SelectionCellsHandler.ts index da8429c4e..c30255334 100644 --- a/packages/core/src/view/selection/SelectionCellsHandler.ts +++ b/packages/core/src/view/selection/SelectionCellsHandler.ts @@ -8,11 +8,14 @@ import EventSource from '../event/EventSource'; import Dictionary from '../../util/Dictionary'; import EventObject from '../event/EventObject'; import InternalEvent from '../event/InternalEvent'; -import utils, { sortCells } from '../../util/Utils'; -import Graph from '../Graph'; +import { sortCells } from '../../util/Utils'; +import { MaxGraph } from '../Graph'; import Cell from '../cell/datatypes/Cell'; -import CellArray from '../cell/datatypes/CellArray'; import CellState from '../cell/datatypes/CellState'; +import { GraphPlugin } from '../../types'; +import EdgeHandler from '../cell/edge/EdgeHandler'; +import VertexHandler from '../cell/vertex/VertexHandler'; +import InternalMouseEvent from '../event/InternalMouseEvent'; /** * Class: mxSelectionCellsHandler @@ -36,21 +39,23 @@ import CellState from '../cell/datatypes/CellState'; * * graph - Reference to the enclosing . */ -class SelectionCellsHandler extends EventSource { - constructor(graph: Graph) { +class SelectionCellsHandler extends EventSource implements GraphPlugin { + static pluginId = 'SelectionCellsHandler'; + + constructor(graph: MaxGraph) { super(); this.graph = graph; this.handlers = new Dictionary(); this.graph.addMouseListener(this); - this.refreshHandler = (sender, evt) => { + this.refreshHandler = (sender: EventSource, evt: EventObject) => { if (this.isEnabled()) { this.refresh(); } }; - this.graph.getSelectionModel().addListener(InternalEvent.CHANGE, this.refreshHandler); + this.graph.addListener(InternalEvent.CHANGE, this.refreshHandler); this.graph.getModel().addListener(InternalEvent.CHANGE, this.refreshHandler); this.graph.getView().addListener(InternalEvent.SCALE, this.refreshHandler); this.graph.getView().addListener(InternalEvent.TRANSLATE, this.refreshHandler); @@ -66,42 +71,42 @@ class SelectionCellsHandler extends EventSource { * * Reference to the enclosing . */ - graph: Graph; + graph: MaxGraph; /** * Variable: enabled * * Specifies if events are handled. Default is true. */ - enabled: boolean = true; + enabled = true; /** * Variable: refreshHandler * * Keeps a reference to an event listener for later removal. */ - refreshHandler: any = null; + refreshHandler: (sender: EventSource, evt: EventObject) => void; /** * Variable: maxHandlers * * Defines the maximum number of handlers to paint individually. Default is 100. */ - maxHandlers: number = 100; + maxHandlers = 100; /** * Variable: handlers * * that maps from cells to handlers. */ - handlers: Dictionary; + handlers: Dictionary; /** * Function: isEnabled * * Returns . */ - isEnabled(): boolean { + isEnabled() { return this.enabled; } @@ -110,7 +115,7 @@ class SelectionCellsHandler extends EventSource { * * Sets . */ - setEnabled(value: boolean): void { + setEnabled(value: boolean) { this.enabled = value; } @@ -119,7 +124,7 @@ class SelectionCellsHandler extends EventSource { * * Returns the handler for the given cell. */ - getHandler(cell: Cell): any { + getHandler(cell: Cell) { return this.handlers.get(cell); } @@ -128,8 +133,8 @@ class SelectionCellsHandler extends EventSource { * * Returns true if the given cell has a handler. */ - isHandled(cell: Cell): boolean { - return this.getHandler(cell) != null; + isHandled(cell: Cell) { + return !!this.getHandler(cell); } /** @@ -137,7 +142,7 @@ class SelectionCellsHandler extends EventSource { * * Resets all handlers. */ - reset(): void { + reset() { this.handlers.visit((key, handler) => { handler.reset.apply(handler); }); @@ -148,8 +153,8 @@ class SelectionCellsHandler extends EventSource { * * Reloads or updates all handlers. */ - getHandledSelectionCells(): CellArray { - return this.graph.selection.getSelectionCells(); + getHandledSelectionCells() { + return this.graph.getSelectionCells(); } /** @@ -157,7 +162,7 @@ class SelectionCellsHandler extends EventSource { * * Reloads or updates all handlers. */ - refresh(): void { + refresh() { // Removes all existing handlers const oldHandlers = this.handlers; this.handlers = new Dictionary(); @@ -169,23 +174,22 @@ class SelectionCellsHandler extends EventSource { for (let i = 0; i < tmp.length; i += 1) { const state = this.graph.view.getState(tmp[i]); - if (state != null) { + if (state) { let handler = oldHandlers.remove(tmp[i]); - if (handler != null) { + if (handler) { if (handler.state !== state) { handler.destroy(); handler = null; } else if (!this.isHandlerActive(handler)) { - if (handler.refresh != null) { - handler.refresh(); - } + // @ts-ignore refresh may exist + if (handler.refresh) handler.refresh(); handler.redraw(); } } - if (handler != null) { + if (handler) { this.handlers.put(tmp[i], handler); } } @@ -201,10 +205,10 @@ class SelectionCellsHandler extends EventSource { for (let i = 0; i < tmp.length; i += 1) { const state = this.graph.view.getState(tmp[i]); - if (state != null) { + if (state) { let handler = this.handlers.get(tmp[i]); - if (handler == null) { + if (!handler) { handler = this.graph.createHandler(state); this.fireEvent(new EventObject(InternalEvent.ADD, 'state', state)); this.handlers.put(tmp[i], handler); @@ -220,8 +224,8 @@ class SelectionCellsHandler extends EventSource { * * Returns true if the given handler is active and should not be redrawn. */ - isHandlerActive(handler: any): boolean { - return handler.index != null; + isHandlerActive(handler: EdgeHandler | VertexHandler) { + return handler.index !== null; } /** @@ -229,10 +233,10 @@ class SelectionCellsHandler extends EventSource { * * Updates the handler for the given shape if one exists. */ - updateHandler(state: CellState): void { + updateHandler(state: CellState) { let handler = this.handlers.remove(state.cell); - if (handler != null) { + if (handler) { // Transfers the current state to the new handler const { index } = handler; const x = handler.startX; @@ -241,10 +245,10 @@ class SelectionCellsHandler extends EventSource { handler.destroy(); handler = this.graph.createHandler(state); - if (handler != null) { + if (handler) { this.handlers.put(state.cell, handler); - if (index != null && x != null && y != null) { + if (index !== null) { handler.start(x, y, index); } } @@ -256,12 +260,10 @@ class SelectionCellsHandler extends EventSource { * * Redirects the given event to the handlers. */ - mouseDown(sender: Event, me: Event): void { + mouseDown(sender: EventSource, me: InternalMouseEvent) { if (this.graph.isEnabled() && this.isEnabled()) { - const args = [sender, me]; - this.handlers.visit((key, handler) => { - handler.mouseDown.apply(handler, args); + handler.mouseDown(sender, me); }); } } @@ -271,12 +273,10 @@ class SelectionCellsHandler extends EventSource { * * Redirects the given event to the handlers. */ - mouseMove(sender: Event, me: Event): void { + mouseMove(sender: EventSource, me: InternalMouseEvent) { if (this.graph.isEnabled() && this.isEnabled()) { - const args = [sender, me]; - this.handlers.visit((key, handler) => { - handler.mouseMove.apply(handler, args); + handler.mouseMove(sender, me); }); } } @@ -286,12 +286,10 @@ class SelectionCellsHandler extends EventSource { * * Redirects the given event to the handlers. */ - mouseUp(sender: Event, me: Event): void { + mouseUp(sender: EventSource, me: InternalMouseEvent) { if (this.graph.isEnabled() && this.isEnabled()) { - const args = [sender, me]; - this.handlers.visit((key, handler) => { - handler.mouseUp.apply(handler, args); + handler.mouseUp(sender, me); }); } } @@ -301,15 +299,11 @@ class SelectionCellsHandler extends EventSource { * * Destroys the handler and all its resources and DOM nodes. */ - destroy(): void { + onDestroy() { this.graph.removeMouseListener(this); - - if (this.refreshHandler != null) { - this.graph.selection.getSelectionModel().removeListener(this.refreshHandler); - this.graph.getModel().removeListener(this.refreshHandler); - this.graph.getView().removeListener(this.refreshHandler); - this.refreshHandler = null; - } + this.graph.removeListener(this.refreshHandler); + this.graph.getModel().removeListener(this.refreshHandler); + this.graph.getView().removeListener(this.refreshHandler); } } diff --git a/packages/core/src/view/selection/SelectionChange.ts b/packages/core/src/view/selection/SelectionChange.ts index 08cba195b..65a591521 100644 --- a/packages/core/src/view/selection/SelectionChange.ts +++ b/packages/core/src/view/selection/SelectionChange.ts @@ -1,12 +1,10 @@ import EventObject from '../event/EventObject'; import Resources from '../../util/Resources'; -import mxLog from '../../util/gui/mxLog'; import InternalEvent from '../event/InternalEvent'; -import mxGraphSelectionModel from '../view/selection/mxGraphSelectionModel'; -import Cell from '../cell/datatypes/Cell'; -import CellArray from "../cell/datatypes/CellArray"; +import CellArray from '../cell/datatypes/CellArray'; import type { UndoableChange } from '../../types'; +import type { MaxGraph } from '../Graph'; /** * @class SelectionChange @@ -14,16 +12,16 @@ import type { UndoableChange } from '../../types'; */ class SelectionChange implements UndoableChange { constructor( - selectionModel: mxGraphSelectionModel, + graph: MaxGraph, added: CellArray = new CellArray(), removed: CellArray = new CellArray() ) { - this.selectionModel = selectionModel; + this.graph = graph; this.added = added.slice(); this.removed = removed.slice(); } - selectionModel: mxGraphSelectionModel; + graph: MaxGraph; added: CellArray; @@ -33,35 +31,25 @@ class SelectionChange implements UndoableChange { * Changes the current root of the view. */ execute() { - const t0: any = mxLog.enter('mxSelectionChange.execute'); - window.status = - Resources.get(this.selectionModel.updatingSelectionResource) || - this.selectionModel.updatingSelectionResource; + Resources.get(this.graph.getUpdatingSelectionResource()) || + this.graph.getUpdatingSelectionResource(); for (const removed of this.removed) { - this.selectionModel.cellRemoved(removed); + this.graph.cellRemoved(removed); } for (const added of this.added) { - this.selectionModel.cellAdded(added); + this.graph.cellAdded(added); } [this.added, this.removed] = [this.removed, this.added]; window.status = - Resources.get(this.selectionModel.doneResource) || - this.selectionModel.doneResource; - mxLog.leave('mxSelectionChange.execute', t0); + Resources.get(this.graph.getDoneResource()) || this.graph.getDoneResource(); - this.selectionModel.fireEvent( - new EventObject( - InternalEvent.CHANGE, - 'added', - this.added, - 'removed', - this.removed - ) + this.graph.fireEvent( + new EventObject(InternalEvent.CHANGE, 'added', this.added, 'removed', this.removed) ); } } diff --git a/packages/core/src/view/snap/GraphSnap.ts b/packages/core/src/view/snap/GraphSnap.ts index a68c4f95c..9b327886f 100644 --- a/packages/core/src/view/snap/GraphSnap.ts +++ b/packages/core/src/view/snap/GraphSnap.ts @@ -11,6 +11,8 @@ class GraphSnap extends autoImplement() { // TODO: Document me! tolerance: number = 0; + getSnapTolerance = () => this.tolerance; + /** * Specifies the grid size. * @default 10 diff --git a/packages/core/src/view/tooltip/TooltipHandler.ts b/packages/core/src/view/tooltip/TooltipHandler.ts index 438c882ef..56128459c 100644 --- a/packages/core/src/view/tooltip/TooltipHandler.ts +++ b/packages/core/src/view/tooltip/TooltipHandler.ts @@ -9,10 +9,12 @@ import { fit, getScrollOrigin } from '../../util/Utils'; import { TOOLTIP_VERTICAL_OFFSET } from '../../util/Constants'; import { getSource, isMouseEvent } from '../../util/EventUtils'; import { isNode } from '../../util/DomUtils'; -import Graph, { MaxGraph } from '../Graph'; +import { MaxGraph } from '../Graph'; import CellState from '../cell/datatypes/CellState'; import InternalMouseEvent from '../event/InternalMouseEvent'; -import { Listenable } from '../../types'; +import PopupMenuHandler from '../popups_menus/PopupMenuHandler'; + +import type { GraphPlugin } from '../../types'; /** * Class: mxTooltipHandler @@ -38,14 +40,41 @@ import { Listenable } from '../../types'; * graph - Reference to the enclosing . * delay - Optional delay in milliseconds. */ -class TooltipHandler { - constructor(graph: MaxGraph, delay: number = 500) { +class TooltipHandler implements GraphPlugin { + static pluginId = 'TooltipHandler'; + + constructor(graph: MaxGraph) { this.graph = graph; - this.delay = delay; + this.delay = 500; this.graph.addMouseListener(this); + + this.div = document.createElement('div'); + this.div.className = 'mxTooltip'; + this.div.style.visibility = 'hidden'; + + document.body.appendChild(this.div); + + InternalEvent.addGestureListeners(this.div, (evt) => { + const source = getSource(evt); + + // @ts-ignore nodeName may exist + if (source && source.nodeName !== 'A') { + this.hideTooltip(); + } + }); + + // Hides tooltips and resets tooltip timer if mouse leaves container + InternalEvent.addListener( + this.graph.getContainer(), + 'mouseleave', + (evt: MouseEvent) => { + if (this.div !== evt.relatedTarget) { + this.hide(); + } + } + ); } - // @ts-ignore Cannot be null. div: HTMLElement; /** @@ -60,7 +89,7 @@ class TooltipHandler { * * Reference to the enclosing . */ - graph: Graph; + graph: MaxGraph; /** * Variable: delay @@ -91,10 +120,10 @@ class TooltipHandler { */ destroyed = false; - lastX: number = 0; - lastY: number = 0; + lastX = 0; + lastY = 0; state: CellState | null = null; - stateSource: boolean = false; + stateSource = false; node: any; thread: number | null = null; @@ -143,27 +172,6 @@ class TooltipHandler { this.hideOnHover = value; } - /** - * Function: init - * - * Initializes the DOM nodes required for this tooltip handler. - */ - init() { - this.div = document.createElement('div'); - this.div.className = 'mxTooltip'; - this.div.style.visibility = 'hidden'; - - document.body.appendChild(this.div); - - InternalEvent.addGestureListeners(this.div, (evt) => { - const source = getSource(evt); - - if (source.nodeName !== 'A') { - this.hideTooltip(); - } - }); - } - /** * Function: getStateForEvent * @@ -180,7 +188,7 @@ class TooltipHandler { * event all subsequent events of the gesture are redirected to this * handler. */ - mouseDown(sender: any, me: InternalMouseEvent) { + mouseDown(sender: EventSource, me: InternalMouseEvent) { this.reset(me, false); this.hideTooltip(); } @@ -190,7 +198,7 @@ class TooltipHandler { * * Handles the event by updating the rubberband selection. */ - mouseMove(sender: Listenable, me: InternalMouseEvent) { + mouseMove(sender: EventSource, me: InternalMouseEvent) { if (me.getX() !== this.lastX || me.getY() !== this.lastY) { this.reset(me, true); const state = this.getStateForEvent(me); @@ -218,7 +226,7 @@ class TooltipHandler { * Handles the event by resetting the tooltip timer or hiding the existing * tooltip. */ - mouseUp(sender: any, me: InternalMouseEvent): void { + mouseUp(sender: EventSource, me: InternalMouseEvent) { this.reset(me, true); this.hideTooltip(); } @@ -228,8 +236,8 @@ class TooltipHandler { * * Resets the timer. */ - resetTimer(): void { - if (this.thread !== null) { + resetTimer() { + if (this.thread) { window.clearTimeout(this.thread); this.thread = null; } @@ -255,18 +263,28 @@ class TooltipHandler { const x = me.getX(); const y = me.getY(); const stateSource = me.isSource(state.shape) || me.isSource(state.text); + const popupMenuHandler = this.graph.getPlugin( + 'PopupMenuHandler' + ) as PopupMenuHandler; this.thread = window.setTimeout(() => { if ( state && + node && !this.graph.isEditing() && - !this.graph.popupMenuHandler.isMenuShowing() && + popupMenuHandler && + !popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown ) { // Uses information from inside event cause using the event at // this (delayed) point in time is not possible in IE as it no // longer contains the required information (member not found) - const tip = this.graph.getTooltip(state, node, x, y); + const tip = this.graph.getTooltip( + state, + node as HTMLElement | SVGElement, + x, + y + ); this.show(tip, x, y); this.state = state; this.node = node; @@ -328,12 +346,12 @@ class TooltipHandler { * * Destroys the handler and all its resources and DOM nodes. */ - destroy() { + onDestroy() { if (!this.destroyed) { this.graph.removeMouseListener(this); InternalEvent.release(this.div); - if (this.div.parentNode != null) { + if (this.div.parentNode) { this.div.parentNode.removeChild(this.div); } diff --git a/packages/core/src/view/view/GraphView.ts b/packages/core/src/view/view/GraphView.ts index c4a5a5276..7d8809382 100644 --- a/packages/core/src/view/view/GraphView.ts +++ b/packages/core/src/view/view/GraphView.ts @@ -51,6 +51,8 @@ import CellArray from '../cell/datatypes/CellArray'; import type { MaxGraph } from '../Graph'; import StyleRegistry from '../style/StyleRegistry'; +import TooltipHandler from '../tooltip/TooltipHandler'; +import { MouseEventListener } from '../../types'; /** * @class GraphView @@ -687,30 +689,26 @@ class GraphView extends EventSource { // container and finishing the handling of a single gesture InternalEvent.addGestureListeners( this.backgroundPageShape.node, - (evt: Event) => { - graph.fireMouseEvent( - InternalEvent.MOUSE_DOWN, - new InternalMouseEvent(evt as MouseEvent) - ); + (evt: MouseEvent) => { + graph.fireMouseEvent(InternalEvent.MOUSE_DOWN, new InternalMouseEvent(evt)); }, - (evt: Event) => { + (evt: MouseEvent) => { + const tooltipHandler = graph.getPlugin('TooltipHandler') as TooltipHandler; + // Hides the tooltip if mouse is outside container - if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) { - graph.tooltipHandler.hide(); + if (tooltipHandler && tooltipHandler.isHideOnHover()) { + tooltipHandler.hide(); } if (graph.isMouseDown && !isConsumed(evt)) { graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt as MouseEvent) + new InternalMouseEvent(evt) ); } }, - (evt: Event) => { - graph.fireMouseEvent( - InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt as MouseEvent) - ); + (evt: MouseEvent) => { + graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt)); } ); } @@ -2034,19 +2032,22 @@ class GraphView extends EventSource { * Returns true if the event origin is one of the drawing panes or * containers of the view. */ - isContainerEvent(evt: Event | MouseEvent) { + isContainerEvent(evt: MouseEvent) { const source = getSource(evt); return ( - source === this.graph.container || - source.parentNode === this.backgroundPane || - (source.parentNode && source.parentNode.parentNode === this.backgroundPane) || - source === this.canvas.parentNode || - source === this.canvas || - source === this.backgroundPane || - source === this.drawPane || - source === this.overlayPane || - source === this.decoratorPane + source && + (source === this.graph.container || + // @ts-ignore parentNode may exist + source.parentNode === this.backgroundPane || + // @ts-ignore parentNode may exist + (source.parentNode && source.parentNode.parentNode === this.backgroundPane) || + source === this.canvas.parentNode || + source === this.canvas || + source === this.backgroundPane || + source === this.drawPane || + source === this.overlayPane || + source === this.decoratorPane) ); } @@ -2125,24 +2126,18 @@ class GraphView extends EventSource { pointerId = evt.pointerId; } }) as EventListener, - (evt: Event) => { + (evt: MouseEvent) => { if ( this.isContainerEvent(evt) && // @ts-ignore (pointerId === null || evt.pointerId === pointerId) ) { - graph.fireMouseEvent( - InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt as MouseEvent) - ); + graph.fireMouseEvent(InternalEvent.MOUSE_MOVE, new InternalMouseEvent(evt)); } }, - (evt: Event) => { + (evt: MouseEvent) => { if (this.isContainerEvent(evt)) { - graph.fireMouseEvent( - InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt as MouseEvent) - ); + graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt)); } pointerId = null; @@ -2161,7 +2156,7 @@ class GraphView extends EventSource { // Workaround for touch events which started on some DOM node // on top of the container, in which case the cells under the // mouse for the move and up events are not detected. - const getState = (evt: Event) => { + const getState = (evt: MouseEvent) => { let state = null; // Workaround for touch events which started on some DOM node @@ -2188,16 +2183,20 @@ class GraphView extends EventSource { // in Firefox and Chrome graph.addMouseListener({ mouseDown: (sender: any, me: InternalMouseEvent) => { - (graph.popupMenuHandler).hideMenu(); + const popupMenuHandler = graph.getPlugin('PopupMenuHandler') as PopupMenuHandler; + + if (popupMenuHandler) popupMenuHandler.hideMenu(); }, mouseMove: () => {}, mouseUp: () => {}, }); - this.moveHandler = (evt: Event) => { + this.moveHandler = (evt: MouseEvent) => { + const tooltipHandler = graph.getPlugin('TooltipHandler') as TooltipHandler; + // Hides the tooltip if mouse is outside container - if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) { - graph.tooltipHandler.hide(); + if (tooltipHandler && tooltipHandler.isHideOnHover()) { + tooltipHandler.hide(); } if ( @@ -2211,12 +2210,12 @@ class GraphView extends EventSource { ) { graph.fireMouseEvent( InternalEvent.MOUSE_MOVE, - new InternalMouseEvent(evt as MouseEvent, getState(evt)) + new InternalMouseEvent(evt, getState(evt)) ); } }; - this.endHandler = (evt: Event) => { + this.endHandler = (evt: MouseEvent) => { if ( this.captureDocumentGesture && graph.isMouseDown && @@ -2225,10 +2224,7 @@ class GraphView extends EventSource { graph.container.style.display !== 'none' && graph.container.style.visibility !== 'hidden' ) { - graph.fireMouseEvent( - InternalEvent.MOUSE_UP, - new InternalMouseEvent(evt as MouseEvent) - ); + graph.fireMouseEvent(InternalEvent.MOUSE_UP, new InternalMouseEvent(evt)); } }; @@ -2320,8 +2316,8 @@ class GraphView extends EventSource { } } - endHandler: EventListener | null = null; - moveHandler: EventListener | null = null; + endHandler: MouseEventListener | null = null; + moveHandler: MouseEventListener | null = null; } export default GraphView;