UNPKG

@maxgraph/core

Version:

maxGraph is a fully client side JavaScript diagramming library that uses SVG and HTML for rendering.

639 lines (638 loc) 26.6 kB
import Cell from '../cell/Cell.js'; import Point from '../geometry/Point.js'; import InternalMouseEvent from '../event/InternalMouseEvent.js'; import ImageShape from '../shape/node/ImageShape.js'; import CellMarker from '../cell/CellMarker.js'; import ConstraintHandler from '../handler/ConstraintHandler.js'; import EventSource from '../event/EventSource.js'; import Image from '../image/ImageBox.js'; import CellState from '../cell/CellState.js'; import type { AbstractGraph } from '../AbstractGraph.js'; import ConnectionConstraint from '../other/ConnectionConstraint.js'; import Shape from '../shape/Shape.js'; import type { CellStyle, ColorValue, GraphPlugin, Listenable, MouseListenerSet } from '../../types.js'; type FactoryMethod = (source: Cell | null, target: Cell | null, style?: CellStyle) => Cell; /** * Graph event handler that creates new connections. * Uses {@link CellMarker} for finding and highlighting the source and target vertices and {@link factoryMethod} to create the edge instance. * * This handler is enabled using {@link AbstractGraph.setConnectable}. * * Example: * * ```javascript * new ConnectionHandler(graph, (source, target, style)=> * { * edge = new Cell('', new Geometry()); * edge.setEdge(true); * edge.setStyle(style); * edge.geometry.relative = true; * return edge; * }); * ``` * * Here is an alternative solution that just sets a specific user object for new edges by overriding {@link insertEdge}. * * ```javascript * originalConnectionHandlerInsertEdge = connectionHandler.insertEdge; * connectionHandler.insertEdge = (parent, id, value, source, target, style) => { * value = 'Test'; * return originalConnectionHandlerInsertEdge.apply(this, arguments); * }; * ``` * * ### Using images to trigger connections * * This handler uses {@link CellMarker} to find the source and target cell for * the new connection and creates a new edge using {@link connect}. The new edge is * created using {@link createEdge} which in turn uses {@link factoryMethod} or creates a * new default edge. * * The handler uses a "highlight-paradigm" for indicating if a cell is being * used as a source or target terminal, as seen in other diagramming products. * In order to allow both, moving and connecting cells at the same time, * {@link DEFAULT_HOTSPOT} is used in the handler to determine the hotspot * of a cell, that is, the region of the cell which is used to trigger a new * connection. The constant is a value between 0 and 1 that specifies the * amount of the width and height around the center to be used for the hotspot * of a cell and its default value is 0.5. In addition, * {@link MIN_HOTSPOT_SIZE} defines the minimum number of pixels for the * width and height of the hotspot. * * This solution, while standards compliant, may be somewhat confusing because * there is no visual indicator for the hotspot and the highlight is seen to * switch on and off while the mouse is being moved in and out. Furthermore, * this paradigm does not allow to create different connections depending on * the highlighted hotspot as there is only one hotspot per cell, and it * normally does not allow cells to be moved and connected at the same time as * there is no clear indication of the connectable area of the cell. * * To come across these issues, the handle has an additional {@link createIcons} hook * with a default implementation that allows to create one icon to be used to * trigger new connections. If this icon is specified, then new connections can * only be created if the image is clicked while the cell is being highlighted. * The {@link createIcons} hook may be overridden to create more than one * {@link ImageShape} for creating new connections, but the default implementation * supports one image and is used as follows: * * In order to display the "connect image" whenever the mouse is over the cell, an DEFAULT_HOTSPOT of 1 should be used: * * ```javascript * mxConstants.DEFAULT_HOTSPOT = 1; * ``` * * In order to avoid confusion with the highlighting, the highlight color should not be used with a connect image: * * ```javascript * mxConstants.HIGHLIGHT_COLOR = null; * ``` * * To install the image, the connectImage field of the ConnectionHandler must be assigned a new {@link Image} instance: * * ```javascript * connectImage = new ImageBox('images/green-dot.gif', 14, 14); * ``` * * This will use the green-dot.gif with a width and height of 14 pixels as the * image to trigger new connections. In createIcons the icon field of the * handler will be set in order to remember the icon that has been clicked for * creating the new connection. This field will be available under selectedIcon * in the connect method, which may be overridden to take the icon that * triggered the new connection into account. This is useful if more than one * icon may be used to create a connection. * * ### Events * * #### InternalEvent.START * * Fires when a new connection is being created by the user. The `state` * property contains the state of the source cell. * * #### InternalEvent.CONNECT * * Fires between begin- and endUpdate in {@link connect}. The `cell` * property contains the inserted edge, the `event` and `target` * properties contain the respective arguments that were passed to {@link connect} (where * target corresponds to the dropTarget argument). Finally, the `terminal` * property corresponds to the target argument in {@link connect} or the clone of the source * terminal if {@link createTarget} is enabled. * * Note that the target is the cell under the mouse where the mouse button was released. * Depending on the logic in the handler, this doesn't necessarily have to be the target * of the inserted edge. To print the source, target or any optional ports IDs that the * edge is connected to, the following code can be used. To get more details about the * actual connection point, {@link AbstractGraph.getConnectionConstraint} can be used. To resolve * the port IDs, use {@link GraphDataModel.getCell}. * * ```javascript * graph.getPlugin('ConnectionHandler')?.addListener(mxEvent.CONNECT, (sender, evt) => { * const edge = evt.getProperty('cell'); * const source = graph.getDataModel().getTerminal(edge, true); * const target = graph.getDataModel().getTerminal(edge, false); * * const style = graph.getCellStyle(edge); * const sourcePortId = style.sourcePort; * const targetPortId = style.targetPort; * * GlobalConfig.logger.show(); * GlobalConfig.logger.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId); * }); * ``` * * #### InternalEvent.RESET * * Fires when the {@link reset} method is invoked. * * @category Plugin */ declare class ConnectionHandler extends EventSource implements GraphPlugin, MouseListenerSet { static readonly pluginId = "ConnectionHandler"; previous: CellState | null; iconState: CellState | null; icons: ImageShape[]; cell: Cell | null; currentPoint: Point | null; sourceConstraint: ConnectionConstraint | null; shape: Shape | null; icon: ImageShape | null; originalPoint: Point | null; currentState: CellState | null; selectedIcon: ImageShape | null; waypoints: Point[]; /** * Reference to the enclosing {@link AbstractGraph}. */ graph: AbstractGraph; /** * Function that is used for creating new edges. The function takes the * source and target {@link Cell} as the first and second argument and returns * a new {@link Cell} that represents the edge. This is used in {@link createEdge}. */ factoryMethod: FactoryMethod | null; /** * Specifies if icons should be displayed inside the graph container instead * of the overlay pane. This is used for HTML labels on vertices which hide * the connect icon. This has precedence over {@link moveIconBack} when set * to true. * @default `false` */ moveIconFront: boolean; /** * Specifies if icons should be moved to the back of the overlay pane. This can * be set to true if the icons of the connection handler conflict with other * handles, such as the vertex label move handle. Default is false. */ moveIconBack: boolean; /** * {@link Image} that is used to trigger the creation of a new connection. * This is used in {@link createIcons}. * @default null */ connectImage: Image | null; /** * Specifies if the connect icon should be centered on the target state * while connections are being previewed. Default is false. */ targetConnectImage: boolean; /** * Specifies if events are handled. Default is false. */ enabled: boolean; /** * Specifies if new edges should be selected. Default is true. */ select: boolean; /** * Specifies if <createTargetVertex> should be called if no target was under the * mouse for the new connection. Setting this to true means the connection * will be drawn as valid if no target is under the mouse, and * <createTargetVertex> will be called before the connection is created between * the source cell and the newly created vertex in <createTargetVertex>, which * can be overridden to create a new target. Default is false. */ createTarget: boolean; /** * Holds the {@link CellMarker} used for finding source and target cells. */ marker: CellMarker; /** * Holds the {@link ConstraintHandler} used for drawing and highlighting constraints. */ constraintHandler: ConstraintHandler; /** * Holds the current validation error while connections are being created. */ error: string | null; /** * Specifies if single clicks should add waypoints on the new edge. Default is * false. */ waypointsEnabled: boolean; /** * Specifies if the connection handler should ignore the state of the mouse * button when highlighting the source. Default is false, that is, the * handler only highlights the source if no button is being pressed. */ ignoreMouseDown: boolean; /** * Holds the {@link Point} where the mouseDown took place while the handler is * active. */ first: Point | null; /** * Holds the offset for connect icons during connection preview. * Default is mxPoint(0, {@link Constants#TOOLTIP_VERTICAL_OFFSET}). * Note that placing the icon under the mouse pointer with an * offset of (0,0) will affect hit detection. */ connectIconOffset: Point; /** * Optional <CellState> that represents the preview edge while the * handler is active. This is created in <createEdgeState>. */ edgeState: CellState | null; /** * Holds the change event listener for later removal. */ changeHandler: (sender: Listenable) => void; /** * Holds the drill event listener for later removal. */ drillHandler: (sender: Listenable) => void; /** * Counts the number of mouseDown events since the start. The initial mouse * down event counts as 1. */ mouseDownCounter: number; /** * Switch to enable moving the preview away from the mousepointer. This is required in browsers * where the preview cannot be made transparent to events and if the built-in hit detection on * the HTML elements in the page should be used. * @default false */ movePreviewAway: boolean; /** * Specifies if connections to the outline of a highlighted target should be * enabled. This will allow to place the connection point along the outline of * the highlighted target. * @default false */ outlineConnect: boolean; /** * Specifies if the actual shape of the edge state should be used for the preview. * Default is false. (Ignored if no edge state is created in <createEdgeState>.) */ livePreview: boolean; /** * Specifies the cursor to be used while the handler is active. * @default null */ cursor: string | null; /** * Defines the cursor for a connectable state. * @default 'pointer' * @since 0.20.0 */ cursorConnect: string; /** * Specifies if new edges should be inserted before the source vertex in the * cell hierarchy. Default is false for backwards compatibility. */ insertBeforeSource: boolean; escapeHandler: () => void; /** * Constructs an event handler that connects vertices using the specified * factory method to create the new edges. * * @param graph Reference to the enclosing {@link AbstractGraph}. * @param factoryMethod Optional function to create the edge. The function takes * the source and target {@link Cell} as the first and second argument and an * optional cell style from the preview as the third argument. It returns * the {@link Cell} that represents the new edge. */ constructor(graph: AbstractGraph, factoryMethod?: FactoryMethod | null); /** * Hook for subclasses to change the implementation of {@link ConstraintHandler} used here. * @since 0.21.0 */ protected createConstraintHandler(): ConstraintHandler; /** * Returns true if events are handled. This implementation * returns <enabled>. */ isEnabled(): boolean; /** * Enables or disables event handling. This implementation * updates <enabled>. * * @param enabled Boolean that specifies the new enabled state. */ setEnabled(enabled: boolean): void; /** * Returns <insertBeforeSource> for non-loops and false for loops. * * @param edge <Cell> that represents the edge to be inserted. * @param source <Cell> that represents the source terminal. * @param target <Cell> that represents the target terminal. * @param evt Mousedown event of the connect gesture. * @param dropTarget <Cell> that represents the cell under the mouse when it was * released. */ isInsertBefore(edge: Cell, source: Cell | null, target: Cell | null, evt: MouseEvent, dropTarget: Cell | null): boolean; /** * Returns <createTarget>. * * @param evt Current active native pointer event. */ isCreateTarget(evt: Event): boolean; /** * Sets <createTarget>. */ setCreateTarget(value: boolean): void; /** * Creates the preview shape for new connections. */ createShape(): Shape; /** * Returns true if the given cell is connectable. This is a hook to * disable floating connections. This implementation returns true. */ isConnectableCell(cell: Cell): boolean; /** * Creates and returns the {@link CellMarker} used in {@link arker}. */ createMarker(): ConnectionHandlerCellMarker; /** * Starts a new connection for the given state and coordinates. */ start(state: CellState, x: number, y: number, edgeState?: CellState): void; /** * Returns true if the source terminal has been clicked and a new * connection is currently being previewed. */ isConnecting(): boolean; /** * Returns {@link AbstractGraph.isValidSource} for the given source terminal. * * @param cell <Cell> that represents the source terminal. * @param me {@link MouseEvent} that is associated with this call. */ isValidSource(cell: Cell, me: InternalMouseEvent): boolean; /** * Returns true. The call to {@link AbstractGraph.isValidTarget} is implicit by calling * {@link AbstractGraph.getEdgeValidationError} in <validateConnection>. This is an * additional hook for disabling certain targets in this specific handler. * * @param cell <Cell> that represents the target terminal. */ isValidTarget(cell: Cell): boolean; /** * Returns the error message or an empty string if the connection for the * given source target pair is not valid. Otherwise it returns null. This * implementation uses {@link AbstractGraph.getEdgeValidationError}. * * @param source <Cell> that represents the source terminal. * @param target <Cell> that represents the target terminal. */ validateConnection(source: Cell, target: Cell): string | null; /** * Hook to return the {@link Image} used for the connection icon of the given * {@link CellState}. This implementation returns {@link connectImage}. * * @param state {@link CellState} whose connect image should be returned. */ getConnectImage(state: CellState): Image | null; /** * Returns true if the state has a HTML label in the graph's container, otherwise * it returns {@link oveIconFront}. * * @param state <CellState> whose connect icons should be returned. */ isMoveIconToFrontForState(state: CellState): boolean; /** * Creates the array {@link ImageShape}s that represent the connect icons for * the given {@link CellState}. * * @param state {@link CellState} whose connect icons should be returned. */ createIcons(state: CellState): ImageShape[]; /** * Redraws the given array of {@link ImageShapes}. * * @param icons Array of {@link ImageShapes} to be redrawn. */ redrawIcons(icons: ImageShape[], state: CellState): void; getIconPosition(icon: ImageShape, state: CellState): Point; /** * Destroys the connect icons and resets the respective state. */ destroyIcons(): void; /** * Returns true if the given mouse down event should start this handler. The * This implementation returns true if the event does not force marquee * selection, and the currentConstraint and currentFocus of the * <constraintHandler> are not null, or <previous> and <error> are not null and * <icons> is null or <icons> and <icon> are not null. */ isStartEvent(me: InternalMouseEvent): boolean; /** * Handles the event by initiating a new connection. */ mouseDown(_sender: EventSource, me: InternalMouseEvent): void; /** * Returns true if a tap on the given source state should immediately start * connecting. This implementation returns true if the state is not movable * in the graph. */ isImmediateConnectSource(state: CellState): boolean; /** * Hook to return an <CellState> which may be used during the preview. * This implementation returns null. * * Use the following code to create a preview for an existing edge style: * * ```javascript * graph.getPlugin('ConnectionHandler').createEdgeState(me) * { * var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle'); * * return new CellState(this.graph.view, edge, this.graph.getCellStyle(edge)); * }; * ``` */ createEdgeState(me?: InternalMouseEvent): CellState | null; /** * Returns true if <outlineConnect> is true and the source of the event is the outline shape * or shift is pressed. */ isOutlineConnectEvent(me: InternalMouseEvent): boolean; /** * Updates the current state for a given mouse move event by using * the {@link arker}. */ updateCurrentState(me: InternalMouseEvent, point: Point): void; /** * Returns true if the given cell does not allow new connections to be created. */ isCellEnabled(cell: Cell): boolean; /** * Converts the given point from screen coordinates to model coordinates. */ convertWaypoint(point: Point): void; /** * 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: InternalMouseEvent, point: Point): void; /** * Handles the event by updating the preview edge or by highlighting * a possible source or target terminal. */ mouseMove(_sender: EventSource, me: InternalMouseEvent): void; /** * Updates {@link edgeState}. */ updateEdgeState(current: Point | null, constraint: ConnectionConstraint | null): void; /** * Returns the perimeter point for the given target state. * * @param state <CellState> that represents the target cell state. * @param _me {@link MouseEvent} that represents the mouse move. */ getTargetPerimeterPoint(state: CellState, _me: InternalMouseEvent): Point | null; /** * Hook to update the icon position(s) based on a mouseOver event. This is * an empty implementation. * * @param state <CellState> that represents the target cell state. * @param next {@link Point} that represents the next point along the previewed edge. * @param me {@link MouseEvent} that represents the mouse move. */ getSourcePerimeterPoint(state: CellState, next: Point, me: InternalMouseEvent): Point | null; /** * Hook to update the icon position(s) based on a mouseOver event. This is * an empty implementation. * * @param state <CellState> under the mouse. * @param icons Array of currently displayed icons. * @param me {@link MouseEvent} that contains the mouse event. */ updateIcons(state: CellState, icons: ImageShape[], me: InternalMouseEvent): void; /** * Returns true if the given mouse up event should stop this handler. The * connection will be created if <error> is null. Note that this is only * called if <waypointsEnabled> is true. This implemtation returns true * if there is a cell state in the given event. */ isStopEvent(me: InternalMouseEvent): boolean; /** * Adds the waypoint for the given event to <waypoints>. */ addWaypointForEvent(me: InternalMouseEvent): void; /** * Returns true if the connection for the given constraints is valid. This * implementation returns true if the constraints are not pointing to the * same fixed connection point. */ checkConstraints(c1: ConnectionConstraint | null, c2: ConnectionConstraint | null): boolean; /** * Handles the event by inserting the new connection. */ mouseUp(_sender: EventSource, me: InternalMouseEvent): void; /** * Resets the state of this handler. */ reset(): void; /** * Redraws the preview edge using the color and width returned by * <getEdgeColor> and <getEdgeWidth>. */ drawPreview(): void; /** * Returns the color used to draw the preview edge. This returns green if * there is no edge validation error and red otherwise. * * @param valid Boolean indicating if the color for a valid edge should be * returned. */ updatePreview(valid: boolean): void; /** * Returns the color used to draw the preview edge. This returns green if * there is no edge validation error and red otherwise. * * @param valid Boolean indicating if the color for a valid edge should be * returned. */ getEdgeColor(valid: boolean): "#00FF00" | "#FF0000"; /** * Returns the width used to draw the preview edge. This returns 3 if * there is no edge validation error and 1 otherwise. * * @param valid Boolean indicating if the width for a valid edge should be * returned. */ getEdgeWidth(valid: boolean): number; /** * Connects the given source and target using a new edge. This * implementation uses <createEdge> to create the edge. * * @param source <Cell> that represents the source terminal. * @param target <Cell> that represents the target terminal. * @param evt Mousedown event of the connect gesture. * @param dropTarget <Cell> that represents the cell under the mouse when it was * released. */ connect(source: Cell | null, target: Cell | null, evt: MouseEvent, dropTarget?: Cell | null): void; /** * Selects the given edge after adding a new connection. The target argument * contains the target vertex if one has been inserted. */ selectCells(edge: Cell | null, target: Cell | null): void; /** * Creates, inserts and returns the new edge for the given parameters. This * implementation does only use {@link createEdge} if {@link factoryMethod} is defined, * otherwise {@link AbstractGraph.insertEdge} will be used. */ insertEdge(parent: Cell, id: string, value: any, source: Cell | null, target: Cell | null, style: CellStyle): Cell; /** * Hook method for creating new vertices on the fly if no target was * under the mouse. This is only called if <createTarget> is true and * returns null. * * @param evt Mousedown event of the connect gesture. * @param source <Cell> that represents the source terminal. */ createTargetVertex(evt: MouseEvent, source: Cell): Cell; /** * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2. */ getAlignmentTolerance(evt?: MouseEvent): number; /** * Creates and returns a new edge using {@link factoryMethod} if one exists. If * no factory method is defined, then a new default edge is returned. The * source and target arguments are informal, the actual connection is * set up later by the caller of this function. * * @param value Value to be used for creating the edge. * @param source {@link Cell} that represents the source terminal. * @param target {@link Cell} that represents the target terminal. * @param style Optional style from the preview edge. */ createEdge(value: any, source: Cell | null, target: Cell | null, style?: CellStyle): Cell; /** * Destroys the handler and all its resources and DOM nodes. This should be * called on all instances. It is called automatically for the built-in * instance created for each {@link AbstractGraph}. */ onDestroy(): void; } declare class ConnectionHandlerCellMarker extends CellMarker { connectionHandler: ConnectionHandler; hotspotEnabled: boolean; constructor(graph: AbstractGraph, connectionHandler: ConnectionHandler, validColor?: ColorValue, invalidColor?: ColorValue, hotspot?: number); getCell(me: InternalMouseEvent): Cell | null; isValidState(state: CellState): boolean; getMarkerColor(evt: Event, state: CellState, isValid: boolean): string; intersects(state: CellState, evt: InternalMouseEvent): boolean; } export default ConnectionHandler;