diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx
index baba592e5886e..b4408e20c04d2 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx
@@ -14,7 +14,7 @@ import { useUrlParams } from '../../../hooks/useUrlParams';
import { getAPMHref } from '../../shared/Links/apm/APMLink';
import { APMQueryParams } from '../../shared/Links/url_helpers';
import { CytoscapeContext } from './Cytoscape';
-import { getAnimationOptions, getNodeHeight } from './cytoscapeOptions';
+import { getAnimationOptions, getNodeHeight } from './cytoscape_options';
const ControlsContainer = styled('div')`
left: ${({ theme }) => theme.eui.gutterTypes.gutterMedium};
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
index 7b944ed1b6ceb..8a76c5f7bd8f1 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx
@@ -17,7 +17,7 @@ import React, {
useState,
} from 'react';
import { useTheme } from '../../../hooks/useTheme';
-import { getCytoscapeOptions } from './cytoscapeOptions';
+import { getCytoscapeOptions } from './cytoscape_options';
import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers';
cytoscape.use(dagre);
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx
index 3938349050e5e..788e5f25b6310 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx
@@ -22,7 +22,7 @@ import { useTheme } from '../../../../hooks/useTheme';
import { fontSize, px } from '../../../../style/variables';
import { asInteger, asDuration } from '../../../../../common/utils/formatters';
import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink';
-import { popoverWidth } from '../cytoscapeOptions';
+import { popoverWidth } from '../cytoscape_options';
import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types';
import {
getSeverity,
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
index 197bc94c62603..6dd0c68165732 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
@@ -15,7 +15,7 @@ import React, { MouseEvent } from 'react';
import { Buttons } from './Buttons';
import { Info } from './Info';
import { ServiceStatsFetcher } from './ServiceStatsFetcher';
-import { popoverWidth } from '../cytoscapeOptions';
+import { popoverWidth } from '../cytoscape_options';
interface ContentsProps {
isService: boolean;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx
index c4272d2869016..7b7e3b46bb317 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx
@@ -18,7 +18,7 @@ import cytoscape from 'cytoscape';
import { useTheme } from '../../../../hooks/useTheme';
import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames';
import { CytoscapeContext } from '../Cytoscape';
-import { getAnimationOptions } from '../cytoscapeOptions';
+import { getAnimationOptions } from '../cytoscape_options';
import { Contents } from './Contents';
interface PopoverProps {
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts
similarity index 95%
rename from x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
rename to x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts
index 136be1c7d947c..e51f53567b5ff 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts
@@ -5,17 +5,18 @@
*/
import cytoscape from 'cytoscape';
import { CSSProperties } from 'react';
-import {
- getServiceHealthStatusColor,
- ServiceHealthStatus,
-} from '../../../../common/service_health_status';
+import { EuiTheme } from '../../../../../observability/public';
+import { ServiceAnomalyStats } from '../../../../common/anomaly_detection';
import {
SERVICE_NAME,
SPAN_DESTINATION_SERVICE_RESOURCE,
} from '../../../../common/elasticsearch_fieldnames';
-import { EuiTheme } from '../../../../../observability/public';
+import {
+ getServiceHealthStatusColor,
+ ServiceHealthStatus,
+} from '../../../../common/service_health_status';
+import { FETCH_STATUS } from '../../../hooks/useFetcher';
import { defaultIcon, iconForNode } from './icons';
-import { ServiceAnomalyStats } from '../../../../common/anomaly_detection';
export const popoverWidth = 280;
@@ -104,6 +105,11 @@ function isService(el: cytoscape.NodeSingular) {
const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => {
const lineColor = theme.eui.euiColorMediumShade;
return [
+ {
+ selector: 'core',
+ // @ts-expect-error DefinitelyTyped does not recognize 'active-bg-opacity'
+ style: { 'active-bg-opacity': 0 },
+ },
{
selector: 'node',
style: {
@@ -226,7 +232,10 @@ const getStyle = (theme: EuiTheme): cytoscape.Stylesheet[] => {
// The CSS styles for the div containing the cytoscape element. Makes a
// background grid of dots.
-export const getCytoscapeDivStyle = (theme: EuiTheme): CSSProperties => ({
+export const getCytoscapeDivStyle = (
+ theme: EuiTheme,
+ status: FETCH_STATUS
+): CSSProperties => ({
background: `linear-gradient(
90deg,
${theme.eui.euiPageBackgroundColor}
@@ -242,6 +251,7 @@ linear-gradient(
center,
${theme.eui.euiColorLightShade}`,
backgroundSize: `${theme.eui.euiSizeL} ${theme.eui.euiSizeL}`,
+ cursor: `${status === FETCH_STATUS.LOADING ? 'wait' : 'grab'}`,
margin: `-${theme.eui.gutterTypes.gutterLarge}`,
marginTop: 0,
});
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
index 1d2e4ada43add..d167b6a9a0565 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx
@@ -19,7 +19,7 @@ import { callApmApi } from '../../../services/rest/createCallApmApi';
import { LicensePrompt } from '../../shared/LicensePrompt';
import { Controls } from './Controls';
import { Cytoscape } from './Cytoscape';
-import { getCytoscapeDivStyle } from './cytoscapeOptions';
+import { getCytoscapeDivStyle } from './cytoscape_options';
import { EmptyBanner } from './EmptyBanner';
import { EmptyPrompt } from './empty_prompt';
import { Popover } from './Popover';
@@ -121,7 +121,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) {
elements={data.elements}
height={height}
serviceName={serviceName}
- style={getCytoscapeDivStyle(theme)}
+ style={getCytoscapeDivStyle(theme, status)}
>
{serviceName && }
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.test.tsx
index 4212d866c0853..ab16da1410662 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.test.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.test.tsx
@@ -6,9 +6,12 @@
import { renderHook } from '@testing-library/react-hooks';
import cytoscape from 'cytoscape';
-import { EuiTheme } from '../../../../../observability/public';
-import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers';
import dagre from 'cytoscape-dagre';
+import { EuiTheme, useUiTracker } from '../../../../../observability/public';
+import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers';
+import lodash from 'lodash';
+
+jest.mock('../../../../../observability/public');
cytoscape.use(dagre);
@@ -25,14 +28,109 @@ describe('useCytoscapeEventHandlers', () => {
});
});
+ describe('when data is received', () => {
+ describe('with a service name', () => {
+ it('sets the primary class', () => {
+ const cy = cytoscape({
+ elements: [{ data: { id: 'test' } }],
+ });
+
+ // Mock the chain that leads to layout run
+ jest.spyOn(cy, 'elements').mockReturnValueOnce(({
+ difference: () =>
+ (({
+ layout: () =>
+ (({ run: () => {} } as unknown) as cytoscape.Layouts),
+ } as unknown) as cytoscape.CollectionReturnValue),
+ } as unknown) as cytoscape.CollectionReturnValue);
+
+ renderHook(() =>
+ useCytoscapeEventHandlers({ serviceName: 'test', cy, theme })
+ );
+ cy.trigger('custom:data');
+
+ expect(cy.getElementById('test').hasClass('primary')).toEqual(true);
+ });
+ });
+
+ it('runs the layout', () => {
+ const cy = cytoscape({
+ elements: [{ data: { id: 'test' } }],
+ });
+ const run = jest.fn();
+
+ // Mock the chain that leads to layout run
+ jest.spyOn(cy, 'elements').mockReturnValueOnce(({
+ difference: () =>
+ (({
+ layout: () => (({ run } as unknown) as cytoscape.Layouts),
+ } as unknown) as cytoscape.CollectionReturnValue),
+ } as unknown) as cytoscape.CollectionReturnValue);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.trigger('custom:data');
+
+ expect(run).toHaveBeenCalled();
+ });
+ });
+
+ describe('when layoutstop is triggered', () => {
+ it('applies cubic bézier styles', () => {
+ const cy = cytoscape({
+ elements: [
+ { data: { id: 'test', source: 'a', target: 'b' } },
+ { data: { id: 'a' } },
+ { data: { id: 'b' } },
+ ],
+ });
+ const edge = cy.getElementById('test');
+ const style = jest.spyOn(edge, 'style');
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.trigger('layoutstop');
+
+ expect(style).toHaveBeenCalledWith('control-point-distances', [-0, 0]);
+ });
+ });
+
describe('when an element is dragged', () => {
it('sets the hasBeenDragged data', () => {
const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const node = cy.getElementById('test');
renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
- cy.getElementById('test').trigger('drag');
+ node.trigger('drag');
- expect(cy.getElementById('test').data('hasBeenDragged')).toEqual(true);
+ expect(node.data('hasBeenDragged')).toEqual(true);
+ });
+
+ describe('when it has already been dragged', () => {
+ it('keeps hasBeenDragged as true', () => {
+ const cy = cytoscape({
+ elements: [{ data: { hasBeenDragged: true, id: 'test' } }],
+ });
+ const node = cy.getElementById('test');
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ node.trigger('drag');
+
+ expect(node.data('hasBeenDragged')).toEqual(true);
+ });
+ });
+ });
+
+ describe('when a drag ends', () => {
+ it('changes the cursor to pointer', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const container = ({
+ style: { cursor: 'grabbing' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('dragfree');
+
+ expect(container.style.cursor).toEqual('pointer');
});
});
@@ -48,6 +146,36 @@ describe('useCytoscapeEventHandlers', () => {
expect(node.hasClass('hover')).toEqual(true);
});
+
+ it('sets the cursor to pointer', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const container = ({
+ style: { cursor: 'default' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('mouseover');
+
+ expect(container.style.cursor).toEqual('pointer');
+ });
+
+ it('tracks an event', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const trackApmEvent = jest.fn();
+ (useUiTracker as jest.Mock).mockReturnValueOnce(trackApmEvent);
+ jest.spyOn(lodash, 'debounce').mockImplementationOnce((fn: any) => {
+ fn();
+ return fn;
+ });
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('mouseover');
+
+ expect(trackApmEvent).toHaveBeenCalledWith({
+ metric: 'service_map_node_or_edge_hover',
+ });
+ });
});
describe('when a node is un-hovered', () => {
@@ -62,5 +190,157 @@ describe('useCytoscapeEventHandlers', () => {
expect(node.hasClass('hover')).toEqual(false);
});
+
+ it('sets the cursor to the default', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const container = ({
+ style: { cursor: 'pointer' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('mouseout');
+
+ expect(container.style.cursor).toEqual('grab');
+ });
+ });
+
+ describe('when an edge is hovered', () => {
+ it('does not set the cursor to pointer', () => {
+ const cy = cytoscape({
+ elements: [
+ { data: { id: 'test', source: 'a', target: 'b' } },
+ { data: { id: 'a' } },
+ { data: { id: 'b' } },
+ ],
+ });
+ const container = ({
+ style: { cursor: 'default' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('mouseover');
+
+ expect(container.style.cursor).toEqual('default');
+ });
+ });
+
+ describe('when a node is selected', () => {
+ it('tracks an event', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const trackApmEvent = jest.fn();
+ (useUiTracker as jest.Mock).mockReturnValueOnce(trackApmEvent);
+ jest.spyOn(lodash, 'debounce').mockImplementationOnce((fn: any) => {
+ fn();
+ return fn;
+ });
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('select');
+
+ expect(trackApmEvent).toHaveBeenCalledWith({
+ metric: 'service_map_node_select',
+ });
+ });
+ });
+
+ describe('when a node is unselected', () => {
+ it('resets connected edge styles', () => {
+ const cy = cytoscape({
+ elements: [
+ { data: { id: 'test' } },
+ { data: { id: 'edge', source: 'test', target: 'test2' } },
+ { data: { id: 'test2' } },
+ ],
+ });
+
+ renderHook(() =>
+ useCytoscapeEventHandlers({
+ serviceName: 'test',
+ cy,
+ theme,
+ })
+ );
+ cy.getElementById('test').trigger('unselect');
+
+ expect(cy.getElementById('edge').hasClass('highlight')).toEqual(true);
+ });
+ });
+
+ describe('when a tap starts', () => {
+ it('sets the cursor to grabbing', () => {
+ const cy = cytoscape({});
+ const container = ({
+ style: { cursor: 'grab' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.trigger('tapstart');
+
+ expect(container.style.cursor).toEqual('grabbing');
+ });
+
+ describe('when the target is a node', () => {
+ it('does not change the cursor', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const container = ({
+ style: { cursor: 'grab' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('tapstart');
+
+ expect(container.style.cursor).toEqual('grab');
+ });
+ });
+ });
+
+ describe('when a tap ends', () => {
+ it('sets the cursor to the default', () => {
+ const cy = cytoscape({});
+ const container = ({
+ style: { cursor: 'grabbing' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.trigger('tapend');
+
+ expect(container.style.cursor).toEqual('grab');
+ });
+
+ describe('when the target is a node', () => {
+ it('does not change the cursor', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ const container = ({
+ style: { cursor: 'pointer' },
+ } as unknown) as HTMLElement;
+ jest.spyOn(cy, 'container').mockReturnValueOnce(container);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('tapend');
+
+ expect(container.style.cursor).toEqual('pointer');
+ });
+ });
+ });
+
+ describe('when debug is enabled', () => {
+ it('logs a debug message', () => {
+ const cy = cytoscape({ elements: [{ data: { id: 'test' } }] });
+ (useUiTracker as jest.Mock).mockReturnValueOnce(() => {});
+ jest.spyOn(Storage.prototype, 'getItem').mockReturnValueOnce('true');
+ const debug = jest
+ .spyOn(window.console, 'debug')
+ .mockReturnValueOnce(undefined);
+
+ renderHook(() => useCytoscapeEventHandlers({ cy, theme }));
+ cy.getElementById('test').trigger('select');
+
+ expect(debug).toHaveBeenCalled();
+ });
});
});
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts
index e8c6a3165ce93..a9125a13fc6fd 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/use_cytoscape_event_handlers.ts
@@ -8,7 +8,7 @@ import cytoscape from 'cytoscape';
import { debounce } from 'lodash';
import { useEffect } from 'react';
import { EuiTheme, useUiTracker } from '../../../../../observability/public';
-import { getAnimationOptions, getNodeHeight } from './cytoscapeOptions';
+import { getAnimationOptions, getNodeHeight } from './cytoscape_options';
/*
* @notice
@@ -66,6 +66,24 @@ function getLayoutOptions({
};
}
+function setCursor(cursor: string, event: cytoscape.EventObjectCore) {
+ const container = event.cy.container();
+
+ if (container) {
+ container.style.cursor = cursor;
+ }
+}
+
+function resetConnectedEdgeStyle(
+ cytoscapeInstance: cytoscape.Core,
+ node?: cytoscape.NodeSingular
+) {
+ cytoscapeInstance.edges().removeClass('highlight');
+ if (node) {
+ node.connectedEdges().addClass('highlight');
+ }
+}
+
export function useCytoscapeEventHandlers({
cy,
serviceName,
@@ -80,16 +98,6 @@ export function useCytoscapeEventHandlers({
useEffect(() => {
const nodeHeight = getNodeHeight(theme);
- const resetConnectedEdgeStyle = (
- cytoscapeInstance: cytoscape.Core,
- node?: cytoscape.NodeSingular
- ) => {
- cytoscapeInstance.edges().removeClass('highlight');
- if (node) {
- node.connectedEdges().addClass('highlight');
- }
- };
-
const dataHandler: cytoscape.EventHandler = (event, fit) => {
if (serviceName) {
const node = event.cy.getElementById(serviceName);
@@ -123,11 +131,17 @@ export function useCytoscapeEventHandlers({
);
const mouseoverHandler: cytoscape.EventHandler = (event) => {
+ if (event.target.isNode()) {
+ setCursor('pointer', event);
+ }
+
trackNodeEdgeHover();
event.target.addClass('hover');
event.target.connectedEdges().addClass('nodeHover');
};
const mouseoutHandler: cytoscape.EventHandler = (event) => {
+ setCursor('grab', event);
+
event.target.removeClass('hover');
event.target.connectedEdges().removeClass('nodeHover');
};
@@ -148,17 +162,37 @@ export function useCytoscapeEventHandlers({
console.debug('cytoscape:', event);
}
};
-
const dragHandler: cytoscape.EventHandler = (event) => {
+ setCursor('grabbing', event);
+
applyCubicBezierStyles(event.target.connectedEdges());
if (!event.target.data('hasBeenDragged')) {
event.target.data('hasBeenDragged', true);
}
};
+ const dragfreeHandler: cytoscape.EventHandler = (event) => {
+ setCursor('pointer', event);
+ };
+ const tapstartHandler: cytoscape.EventHandler = (event) => {
+ // Onle set cursot to "grabbing" if the target doesn't have an "isNode"
+ // property (meaning it's the canvas) or if "isNode" is false (meaning
+ // it's an edge.)
+ if (!event.target.isNode || !event.target.isNode()) {
+ setCursor('grabbing', event);
+ }
+ };
+ const tapendHandler: cytoscape.EventHandler = (event) => {
+ if (!event.target.isNode || !event.target.isNode()) {
+ setCursor('grab', event);
+ }
+ };
if (cy) {
- cy.on('custom:data drag layoutstop select unselect', debugHandler);
+ cy.on(
+ 'custom:data drag dragfree layoutstop select tapstart tapend unselect',
+ debugHandler
+ );
cy.on('custom:data', dataHandler);
cy.on('layoutstop', layoutstopHandler);
cy.on('mouseover', 'edge, node', mouseoverHandler);
@@ -166,12 +200,15 @@ export function useCytoscapeEventHandlers({
cy.on('select', 'node', selectHandler);
cy.on('unselect', 'node', unselectHandler);
cy.on('drag', 'node', dragHandler);
+ cy.on('dragfree', 'node', dragfreeHandler);
+ cy.on('tapstart', tapstartHandler);
+ cy.on('tapend', tapendHandler);
}
return () => {
if (cy) {
cy.removeListener(
- 'custom:data drag layoutstop select unselect',
+ 'custom:data drag dragfree layoutstop select tapstart tapend unselect',
undefined,
debugHandler
);
@@ -182,6 +219,9 @@ export function useCytoscapeEventHandlers({
cy.removeListener('select', 'node', selectHandler);
cy.removeListener('unselect', 'node', unselectHandler);
cy.removeListener('drag', 'node', dragHandler);
+ cy.removeListener('dragfree', 'node', dragfreeHandler);
+ cy.removeListener('tapstart', undefined, tapstartHandler);
+ cy.removeListener('tapend', undefined, tapendHandler);
}
};
}, [cy, serviceName, trackApmEvent, theme]);