fix(robustness): prevent errors when a plugin is not loaded (#272)

Add guards to prevent accessing properties and methods on undefined
instances when a plugin is not loaded.
Also fix and improve the JSDoc of the `EdgeHandler` and `Guide` classes.
development
Thomas Bouffard 2023-12-07 16:10:33 +01:00 committed by GitHub
parent 8d6727a411
commit ffd5036054
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 226 additions and 143 deletions

View File

@ -0,0 +1,25 @@
/*
Copyright 2023-present The maxGraph project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { expect, test } from '@jest/globals';
import { createGraphWithoutPlugins } from './utils';
test('The "ConnectionHandler" plugin is not available', () => {
const graph = createGraphWithoutPlugins();
graph.setConnectable(true);
graph.isConnectable();
expect(graph.isConnectable()).toBe(false);
});

View File

@ -15,16 +15,12 @@ limitations under the License.
*/
import { describe, expect, test } from '@jest/globals';
import { Cell, type CellStyle, Geometry, Graph } from '../../../src';
function createGraph(): Graph {
// @ts-ignore - no need for a container, we don't check the view here
return new Graph(null);
}
import { createGraphWithoutContainer } from './utils';
import { Cell, type CellStyle, Geometry } from '../../../src';
describe('insertEdge', () => {
test('with several parameters', () => {
const graph = createGraph();
const graph = createGraphWithoutContainer();
const source = new Cell();
const target = new Cell();
@ -55,7 +51,7 @@ describe('insertEdge', () => {
});
test('with single parameter', () => {
const graph = createGraph();
const graph = createGraphWithoutContainer();
const source = new Cell();
const target = new Cell();
@ -91,7 +87,7 @@ describe('insertEdge', () => {
});
test('with single parameter and non default parent', () => {
const graph = createGraph();
const graph = createGraphWithoutContainer();
const parentCell = graph.insertVertex({
value: 'non default',

View File

@ -0,0 +1,23 @@
/*
Copyright 2023-present The maxGraph project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { test } from '@jest/globals';
import { createGraphWithoutPlugins } from './utils';
test('The "PanningHandler" plugin is not available', () => {
const graph = createGraphWithoutPlugins();
graph.setPanning(true);
});

View File

@ -0,0 +1,29 @@
/*
Copyright 2023-present The maxGraph project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { test } from '@jest/globals';
import { createGraphWithoutPlugins } from './utils';
import { Cell, CellState } from '../../../src';
test('The "TooltipHandler" plugin is not available', () => {
const graph = createGraphWithoutPlugins();
graph.setTooltips(true);
});
test('The "SelectionCellsHandler" plugin is not available', () => {
const graph = createGraphWithoutPlugins();
graph.getTooltip(new CellState(null, new Cell()), null!, 0, 0);
});

View File

@ -15,16 +15,12 @@ limitations under the License.
*/
import { describe, expect, test } from '@jest/globals';
import { type CellStyle, Geometry, Graph } from '../../../src';
function createGraph(): Graph {
// @ts-ignore - no need for a container, we don't check the view here
return new Graph(null);
}
import { createGraphWithoutContainer } from './utils';
import { type CellStyle, Geometry } from '../../../src';
describe('insertVertex', () => {
test('with several parameters', () => {
const graph = createGraph();
const graph = createGraphWithoutContainer();
const style: CellStyle = { rounded: true, shape: 'cloud' };
const cell = graph.insertVertex(null, 'vertex_1', 'a value', 10, 20, 110, 120, style);
expect(cell.getId()).toBe('vertex_1');
@ -50,7 +46,7 @@ describe('insertVertex', () => {
});
test('with single parameter', () => {
const graph = createGraph();
const graph = createGraphWithoutContainer();
const style: CellStyle = { align: 'right', fillColor: 'red' };
const cell = graph.insertVertex({
value: 'a value',
@ -82,7 +78,7 @@ describe('insertVertex', () => {
});
test('with single parameter and non default parent', () => {
const graph = createGraph();
const graph = createGraphWithoutContainer();
const parentCell = graph.insertVertex({
value: 'non default',

View File

@ -0,0 +1,22 @@
/*
Copyright 2023-present The maxGraph project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Graph } from '../../../src';
// no need for a container, we don't check the view here
export const createGraphWithoutContainer = (): Graph => new Graph(null!);
export const createGraphWithoutPlugins = (): Graph => new Graph(null!, null!, []);

View File

@ -3,4 +3,5 @@ module.exports = {
// preset: 'ts-jest',
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'jsdom', // need to access to the browser objects
testMatch: ['**/__tests__/**/?(*.)+(spec|test).ts'],
};

View File

@ -1437,24 +1437,24 @@ export class Editor extends EventSource {
// event if an insert function is defined
this.installInsertHandler(graph);
// Redirects the function for creating the
// popupmenu items
// Redirects the function for creating the popupmenu items
const popupMenuHandler = <PopupMenuHandler>graph.getPlugin('PopupMenuHandler');
if (popupMenuHandler) {
popupMenuHandler.factoryMethod = (menu: any, cell: Cell | null, evt: any): void => {
return this.createPopupMenu(menu, cell, evt);
};
}
// Redirects the function for creating
// new connections in the diagram
// Redirects the function for creating new connections in the diagram
const connectionHandler = <ConnectionHandler>graph.getPlugin('ConnectionHandler');
if (connectionHandler) {
connectionHandler.factoryMethod = (
source: Cell | null,
target: Cell | null
): Cell => {
return this.createEdge(source, target);
};
}
// Maintains swimlanes and installs autolayout
this.createSwimlaneManager(graph);
@ -2458,13 +2458,13 @@ export class Editor extends EventSource {
);
if (modename === 'select') {
panningHandler.useLeftButtonForPanning = false;
panningHandler && (panningHandler.useLeftButtonForPanning = false);
this.graph.setConnectable(false);
} else if (modename === 'connect') {
panningHandler.useLeftButtonForPanning = false;
panningHandler && (panningHandler.useLeftButtonForPanning = false);
this.graph.setConnectable(true);
} else if (modename === 'pan') {
panningHandler.useLeftButtonForPanning = true;
panningHandler && (panningHandler.useLeftButtonForPanning = true);
this.graph.setConnectable(false);
}
}

View File

@ -262,14 +262,14 @@ export const VERTEX_SELECTION_DASHED = true;
export const EDGE_SELECTION_DASHED = true;
/**
* Defines the color to be used for the guidelines in mxGraphHandler.
* Default is #FF0000.
* Defines the color to be used for the guidelines in `Guide`.
* @default #FF0000.
*/
export const GUIDE_COLOR = '#FF0000';
/**
* Defines the strokewidth to be used for the guidelines in mxGraphHandler.
* Default is 1.
* Defines the strokewidth to be used for the guidelines in `Guide`.
* @default 1.
*/
export const GUIDE_STROKEWIDTH = 1;

View File

@ -68,21 +68,19 @@ export const defaultPlugins: GraphPluginConstructor[] = [
];
/**
* Extends {@link EventSource} to implement a graph component for
* the browser. This is the main class of the package. To activate
* panning and connections use {@link setPanning} and {@link setConnectable}.
* For rubberband selection you must create a new instance of
* {@link rubberband}. The following listeners are added to
* {@link mouseListeners} by default:
* Extends {@link EventSource} to implement a graph component for the browser. This is the main class of the package.
*
* To activate panning and connections use {@link setPanning} and {@link setConnectable}.
* For rubberband selection you must create a new instance of {@link rubberband}.
*
* The following listeners are added to {@link mouseListeners} by default:
*
* - tooltipHandler: {@link TooltipHandler} that displays tooltips
* - panningHandler: {@link PanningHandler} for panning and popup menus
* - connectionHandler: {@link ConnectionHandler} for creating connections
* - graphHandler: {@link SelectionHandler} for moving and cloning cells
* - selectionHandler: {@link SelectionHandler} for moving and cloning cells
*
* These listeners will be called in the above order if they are enabled.
* @class graph
* @extends {EventSource}
*/
class Graph extends EventSource {
container: HTMLElement;
@ -723,7 +721,11 @@ class Graph extends EventSource {
}
}
}
} else if (this.isAllowAutoPanning() && !panningHandler.isActive()) {
} else if (
this.isAllowAutoPanning() &&
panningHandler &&
!panningHandler.isActive()
) {
panningHandler.getPanningManager().panTo(x + this.getPanDx(), y + this.getPanDy());
}
}

View File

@ -2165,8 +2165,7 @@ export class GraphView extends EventSource {
graph.addMouseListener({
mouseDown: (sender: any, me: InternalMouseEvent) => {
const popupMenuHandler = graph.getPlugin('PopupMenuHandler') as PopupMenuHandler;
if (popupMenuHandler) popupMenuHandler.hideMenu();
popupMenuHandler?.hideMenu();
},
mouseMove: () => {
return;

View File

@ -1398,7 +1398,7 @@ class CellRenderer {
const selectionCellsHandler = graph.getPlugin(
'SelectionCellsHandler'
) as SelectionCellsHandler;
selectionCellsHandler.updateHandler(state);
selectionCellsHandler?.updateHandler(state);
}
} else if (
!force &&
@ -1412,7 +1412,7 @@ class CellRenderer {
const selectionCellsHandler = graph.getPlugin(
'SelectionCellsHandler'
) as SelectionCellsHandler;
selectionCellsHandler.updateHandler(state);
selectionCellsHandler?.updateHandler(state);
force = true;
}

View File

@ -712,10 +712,7 @@ class CellEditorHandler implements GraphPlugin {
}
const tooltipHandler = this.graph.getPlugin('TooltipHandler') as TooltipHandler;
if (tooltipHandler) {
tooltipHandler.hideTooltip();
}
tooltipHandler?.hideTooltip();
const state = this.graph.getView().getState(cell);

View File

@ -184,7 +184,7 @@ type FactoryMethod = (
* the port IDs, use <Transactions.getCell>.
*
* ```javascript
* graph.getPlugin('ConnectionHandler').addListener(mxEvent.CONNECT, (sender, evt)=>
* graph.getPlugin('ConnectionHandler')?.addListener(mxEvent.CONNECT, (sender, evt) =>
* {
* let edge = evt.getProperty('cell');
* let source = graph.getDataModel().getTerminal(edge, true);

View File

@ -75,7 +75,7 @@ import { equalPoints } from '../../util/arrayUtils';
/**
* Graph event handler that reconnects edges and modifies control points and the edge
* label location.
* Uses {@link TerminalMarker} for finding and highlighting new source and target vertices.
* Uses {@link CellMarker} for finding and highlighting new source and target vertices.
* This handler is automatically created in mxGraph.createHandler for each selected edge.
* **To enable adding/removing control points, the following code can be used**
* @example
@ -98,7 +98,7 @@ class EdgeHandler {
state: CellState;
/**
* Holds the {@link TerminalMarker} which is used for highlighting terminals.
* Holds the {@link CellMarker} which is used for highlighting terminals.
*/
marker: CellMarker;
@ -311,13 +311,14 @@ class EdgeHandler {
}
}
const graphHandler = this.graph.getPlugin('SelectionHandler') as SelectionHandler;
const selectionHandler = this.graph.getPlugin('SelectionHandler') as SelectionHandler;
// Creates bends for the non-routed absolute points
// or bends that don't correspond to points
if (
this.graph.getSelectionCount() < graphHandler.maxCells ||
graphHandler.maxCells <= 0
selectionHandler &&
(this.graph.getSelectionCount() < selectionHandler.maxCells ||
selectionHandler.maxCells <= 0)
) {
this.bends = this.createBends();
@ -496,21 +497,21 @@ class EdgeHandler {
}
/**
* Returns {@link Constants#EDGE_SELECTION_COLOR}.
* Returns {@link EDGE_SELECTION_COLOR}.
*/
getSelectionColor() {
return EDGE_SELECTION_COLOR;
}
/**
* Returns {@link Constants#EDGE_SELECTION_STROKEWIDTH}.
* Returns {@link EDGE_SELECTION_STROKEWIDTH}.
*/
getSelectionStrokeWidth() {
return EDGE_SELECTION_STROKEWIDTH;
}
/**
* Returns {@link Constants#EDGE_SELECTION_DASHED}.
* Returns {@link EDGE_SELECTION_DASHED}.
*/
isSelectionDashed() {
return EDGE_SELECTION_DASHED;
@ -525,14 +526,14 @@ class EdgeHandler {
}
/**
* Creates and returns the {@link CellMarker} used in {@link arker}.
* Creates and returns the {@link CellMarker} used in {@link marker}.
*/
getCellAt(x: number, y: number) {
return !this.outlineConnect ? this.graph.getCellAt(x, y) : null;
}
/**
* Creates and returns the {@link CellMarker} used in {@link arker}.
* Creates and returns the {@link CellMarker} used in {@link marker}.
*/
createMarker() {
return new EdgeHandlerCellMarker(this.graph, this);
@ -552,7 +553,7 @@ class EdgeHandler {
/**
* Creates and returns the bends used for modifying the edge. This is
* typically an array of {@link RectangleShapes}.
* typically an array of {@link RectangleShape}.
*/
createBends() {
const { cell } = this.state;
@ -593,7 +594,7 @@ class EdgeHandler {
/**
* Creates and returns the bends used for modifying the edge. This is
* typically an array of {@link RectangleShapes}.
* typically an array of {@link RectangleShape}.
*/
// createVirtualBends(): mxRectangleShape[];
createVirtualBends() {
@ -1439,7 +1440,7 @@ class EdgeHandler {
/**
* Handles the event to applying the previewed changes on the edge by
* using {@link oveLabel}, <connect> or <changePoints>.
* using {@link moveLabel}, <connect> or <changePoints>.
*/
mouseUp(sender: EventSource, me: InternalMouseEvent) {
// Workaround for wrong event source in Webkit

View File

@ -242,15 +242,14 @@ class KeyHandler {
isGraphEvent(evt: KeyboardEvent) {
const source = <Element>getSource(evt);
// Accepts events from the target object or
// in-place editing inside graph
const cellEditor = this.graph?.getPlugin(
// Accepts events from the target object or in-place editing inside graph
const cellEditorHandler = this.graph?.getPlugin(
'CellEditorHandler'
) as CellEditorHandler | null;
) as CellEditorHandler;
if (
source === this.target ||
source.parentNode === this.target ||
(cellEditor != null && cellEditor.isEventSource(evt))
(cellEditorHandler && cellEditorHandler.isEventSource(evt))
) {
return true;
}

View File

@ -99,11 +99,10 @@ class PopupMenuHandler extends MaxPopupMenu implements GraphPlugin {
* Initializes the shapes required for this vertex handler.
*/
init() {
// Hides the tooltip if the mouse is over
// the context menu
// Hides the tooltip if the mouse is over the context menu
InternalEvent.addGestureListeners(this.div, (evt) => {
const tooltipHandler = this.graph.getPlugin('TooltipHandler') as TooltipHandler;
tooltipHandler.hide();
tooltipHandler?.hide();
});
}
@ -176,7 +175,7 @@ class PopupMenuHandler extends MaxPopupMenu implements GraphPlugin {
// Hides the tooltip if there is one
const tooltipHandler = this.graph.getPlugin('TooltipHandler') as TooltipHandler;
tooltipHandler.hide();
tooltipHandler?.hide();
// Menu is shifted by 1 pixel so that the mouse up event
// is routed via the underlying shape instead of the DIV

View File

@ -61,13 +61,9 @@ import type { ColorValue, GraphPlugin } from '../../types';
* handlers are created using {@link Graph#createHandler} in
* {@link GraphSelectionModel#cellAdded}.
*
* To avoid the container to scroll a moved cell into view, set
* <scrollAfterMove> to false.
* To avoid the container to scroll a moved cell into view, set {@link scrollOnMove} to `false`.
*
* Constructor: mxGraphHandler
*
* Constructs an event handler that creates handles for the
* selection cells.
* Constructs an event handler that creates handles for the selection cells.
*
* @param graph Reference to the enclosing {@link Graph}.
*/
@ -132,7 +128,7 @@ class SelectionHandler implements GraphPlugin {
// Forces update to ignore last visible state
this.setHandlesVisibleForCells(
selectionCellsHandler.getHandledSelectionCells(),
selectionCellsHandler?.getHandledSelectionCells() ?? [],
false,
true
);
@ -269,8 +265,8 @@ class SelectionHandler implements GraphPlugin {
connectOnDrop = false;
/**
* Specifies if the view should be scrolled so that a moved cell is
* visible. Default is true.
* Specifies if the view should be scrolled so that a moved cell is visible.
* @default true
*/
scrollOnMove = true;
@ -479,11 +475,11 @@ class SelectionHandler implements GraphPlugin {
if (!this.graph.isToggleEvent(me.getEvent()) || !isAltDown(me.getEvent())) {
while (c) {
if (selectionCellsHandler.isHandled(c)) {
const cellEditor = this.graph.getPlugin(
if (selectionCellsHandler?.isHandled(c)) {
const cellEditorHandler = this.graph.getPlugin(
'CellEditorHandler'
) as CellEditorHandler;
return cellEditor.getEditingCell() !== c;
return cellEditorHandler?.getEditingCell() !== c;
}
c = c.getParent();
}
@ -497,7 +493,7 @@ class SelectionHandler implements GraphPlugin {
selectDelayed(me: InternalMouseEvent) {
const popupMenuHandler = this.graph.getPlugin('PopupMenuHandler') as PopupMenuHandler;
if (!popupMenuHandler.isPopupTrigger(me)) {
if (!popupMenuHandler || !popupMenuHandler.isPopupTrigger(me)) {
let cell = me.getCell();
if (cell === null) {
cell = this.cell;
@ -1064,7 +1060,7 @@ class SelectionHandler implements GraphPlugin {
) as SelectionCellsHandler;
this.setHandlesVisibleForCells(
selectionCellsHandler.getHandledSelectionCells(),
selectionCellsHandler?.getHandledSelectionCells() ?? [],
false
);
this.updateLivePreview(this.currentDx, this.currentDy);
@ -1265,11 +1261,8 @@ class SelectionHandler implements GraphPlugin {
) as SelectionCellsHandler;
for (let i = 0; i < states.length; i += 1) {
const handler = selectionCellsHandler.getHandler(states[i][0].cell);
if (handler != null) {
handler.redraw(true);
}
const handler = selectionCellsHandler?.getHandler(states[i][0].cell);
handler?.redraw(true);
}
}
@ -1383,11 +1376,9 @@ class SelectionHandler implements GraphPlugin {
) as SelectionCellsHandler;
for (let i = 0; i < cells.length; i += 1) {
const handler = selectionCellsHandler.getHandler(cells[i]);
if (handler != null) {
const handler = selectionCellsHandler?.getHandler(cells[i]);
if (handler) {
handler.setHandlesVisible(visible);
if (visible) {
handler.redraw();
}
@ -1438,7 +1429,7 @@ class SelectionHandler implements GraphPlugin {
'ConnectionHandler'
) as ConnectionHandler;
connectionHandler.connect(this.cell, cell, me.getEvent());
connectionHandler?.connect(this.cell, cell, me.getEvent());
} else {
const clone =
graph.isCloneEvent(me.getEvent()) &&
@ -1493,7 +1484,7 @@ class SelectionHandler implements GraphPlugin {
) as SelectionCellsHandler;
this.setHandlesVisibleForCells(
selectionCellsHandler.getHandledSelectionCells(),
selectionCellsHandler?.getHandledSelectionCells() ?? [],
true
);
}

View File

@ -233,12 +233,13 @@ class VertexHandler {
this.selectionBorder.setCursor(CURSOR.MOVABLE_VERTEX);
}
const graphHandler = this.graph.getPlugin('SelectionHandler') as SelectionHandler;
const selectionHandler = this.graph.getPlugin('SelectionHandler') as SelectionHandler;
// Adds the sizer handles
if (
graphHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < graphHandler.maxCells
selectionHandler &&
(selectionHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < selectionHandler.maxCells)
) {
const resizable = this.graph.isCellResizable(this.state.cell);
this.sizers = [];
@ -338,14 +339,17 @@ class VertexHandler {
* Returns true if the rotation handle should be showing.
*/
isRotationHandleVisible() {
const graphHandler = this.graph.getPlugin('SelectionHandler') as SelectionHandler;
const selectionHandler = this.graph.getPlugin('SelectionHandler') as SelectionHandler;
const selectionHandlerCheck = selectionHandler
? selectionHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < selectionHandler.maxCells
: true;
return (
this.graph.isEnabled() &&
this.rotationEnabled &&
this.graph.isCellRotatable(this.state.cell) &&
(graphHandler.maxCells <= 0 ||
this.graph.getSelectionCount() < graphHandler.maxCells)
selectionHandlerCheck
);
}
@ -724,8 +728,7 @@ class VertexHandler {
) as SelectionCellsHandler;
for (let i = 0; i < edges.length; i += 1) {
const handler = selectionCellsHandler.getHandler(edges[i]);
const handler = selectionCellsHandler?.getHandler(edges[i]);
if (handler) {
this.edgeHandlers.push(handler as EdgeHandler);
}

View File

@ -746,7 +746,7 @@ const ConnectionsMixin: PartialType = {
*/
setConnectable(connectable) {
const connectionHandler = this.getPlugin('ConnectionHandler') as ConnectionHandler;
connectionHandler.setEnabled(connectable);
connectionHandler?.setEnabled(connectable);
},
/**
@ -754,7 +754,7 @@ const ConnectionsMixin: PartialType = {
*/
isConnectable() {
const connectionHandler = this.getPlugin('ConnectionHandler') as ConnectionHandler;
return connectionHandler.isEnabled();
return connectionHandler?.isEnabled() ?? false;
},
};

View File

@ -92,8 +92,8 @@ const EditingMixin: PartialType = {
},
/**
* Fires a {@link startEditing} event and invokes {@link CellEditorHandler.startEditing}
* on {@link editor}. After editing was started, a {@link editingStarted} event is
* Fires a {@link InternalEvent.START_EDITING} event and invokes {@link CellEditorHandler.startEditing}.
* After editing was started, a {@link InternalEvent.EDITING_STARTED} event is
* fired.
*
* @param cell {@link mxCell} to start the in-place editor for.
@ -112,8 +112,10 @@ const EditingMixin: PartialType = {
new EventObject(InternalEvent.START_EDITING, { cell, event: evt })
);
const cellEditor = this.getPlugin('CellEditorHandler') as CellEditorHandler;
cellEditor.startEditing(cell, evt);
const cellEditorHandler = this.getPlugin(
'CellEditorHandler'
) as CellEditorHandler;
cellEditorHandler?.startEditing(cell, evt);
this.fireEvent(
new EventObject(InternalEvent.EDITING_STARTED, { cell, event: evt })
@ -136,14 +138,14 @@ const EditingMixin: PartialType = {
},
/**
* Stops the current editing and fires a {@link editingStopped} event.
* Stops the current editing and fires a {@link InternalEvent.EDITING_STOPPED} event.
*
* @param cancel Boolean that specifies if the current editing value
* should be stored.
*/
stopEditing(cancel = false) {
const cellEditor = this.getPlugin('CellEditorHandler') as CellEditorHandler;
cellEditor.stopEditing(cancel);
const cellEditorHandler = this.getPlugin('CellEditorHandler') as CellEditorHandler;
cellEditorHandler?.stopEditing(cancel);
this.fireEvent(new EventObject(InternalEvent.EDITING_STOPPED, { cancel }));
},
@ -218,11 +220,11 @@ const EditingMixin: PartialType = {
* If no cell is specified then this returns true if any
* cell is currently being edited.
*
* @param cell {@link mxCell} that should be checked.
* @param cell {@link Cell} that should be checked.
*/
isEditing(cell = null) {
const cellEditor = this.getPlugin('CellEditorHandler') as CellEditorHandler;
const editingCell = cellEditor.getEditingCell();
const cellEditorHandler = this.getPlugin('CellEditorHandler') as CellEditorHandler;
const editingCell = cellEditorHandler?.getEditingCell();
return !cell ? !!editingCell : cell === editingCell;
},

View File

@ -589,7 +589,7 @@ const EventsMixin: PartialType = {
if (mxe.isConsumed()) {
// Resets the state of the panning handler
panningHandler.panningTrigger = false;
panningHandler && (panningHandler.panningTrigger = false);
}
// Handles the event if it has not been consumed
@ -597,6 +597,7 @@ const EventsMixin: PartialType = {
this.isEnabled() &&
!isConsumed(evt) &&
!mxe.isConsumed() &&
connectionHandler &&
connectionHandler.isEnabled()
) {
const cell = connectionHandler.marker.getCell(me);
@ -1057,13 +1058,13 @@ const EventsMixin: PartialType = {
Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;
}
const cellEditor = this.getPlugin('CellEditorHandler') as CellEditorHandler;
const cellEditorHandler = this.getPlugin('CellEditorHandler') as CellEditorHandler;
// Stops editing for all events other than from cellEditor
// Stops editing for all events other than from cellEditorHandler
if (
evtName === InternalEvent.MOUSE_DOWN &&
this.isEditing() &&
!cellEditor.isEventSource(me.getEvent())
!cellEditorHandler?.isEventSource(me.getEvent())
) {
this.stopEditing(!this.isInvokesStopCellEditing());
}

View File

@ -395,15 +395,13 @@ const PanningMixin: PartialType = {
*****************************************************************************/
/**
* Specifies if panning should be enabled. This implementation updates
* {@link PanningHandler.panningEnabled} in {@link panningHandler}.
* Specifies if panning should be enabled. This implementation updates {@link PanningHandler.panningEnabled}.
*
* @param enabled Boolean indicating if panning should be enabled.
*/
setPanning(enabled) {
const panningHandler = this.getPlugin('PanningHandler') as PanningHandler;
if (panningHandler) panningHandler.panningEnabled = enabled;
panningHandler && (panningHandler.panningEnabled = enabled);
},
};

View File

@ -87,7 +87,7 @@ const TooltipMixin: PartialType = {
'SelectionCellsHandler'
) as SelectionCellsHandler;
const handler = selectionCellsHandler.getHandler(state.cell);
const handler = selectionCellsHandler?.getHandler(state.cell);
// @ts-ignore Guarded against undefined error already.
if (handler && typeof handler.getTooltipForNode === 'function') {
@ -147,8 +147,7 @@ const TooltipMixin: PartialType = {
*/
setTooltips(enabled: boolean) {
const tooltipHandler = this.getPlugin('TooltipHandler') as TooltipHandler;
tooltipHandler.setEnabled(enabled);
tooltipHandler?.setEnabled(enabled);
},
};

View File

@ -534,8 +534,8 @@ class DragSource {
// Guide is only needed if preview element is used
if (this.isGuidesEnabled() && this.previewElement) {
const graphHandler = graph.getPlugin('SelectionHandler') as SelectionHandler;
this.currentGuide = new Guide(graph, graphHandler.getGuideStates());
const selectionHandler = graph.getPlugin('SelectionHandler') as SelectionHandler;
this.currentGuide = new Guide(graph, selectionHandler?.getGuideStates());
}
if (this.highlightDropTargets) {

View File

@ -78,7 +78,7 @@ class Guide {
tolerance = 2;
/**
* Sets the {@link CellStates} that should be used for alignment.
* Sets the {@link CellState}s that should be used for alignment.
*/
setStates(states: CellState[]) {
this.states = states;
@ -103,8 +103,8 @@ class Guide {
/**
* Returns the mxShape to be used for painting the respective guide. This
* implementation returns a new, dashed and crisp {@link Polyline} using
* {@link Constants#GUIDE_COLOR} and {@link Constants#GUIDE_STROKEWIDTH} as the format.
* implementation returns a new, dashed and crisp {@link PolylineShape} using
* {@link GUIDE_COLOR} and {@link GUIDE_STROKEWIDTH} as the format.
*
* @param horizontal Boolean that specifies which guide should be created.
*/