-
- {editMode ? (
-
- ) : (
-
+
setSelectedId(String(id)) : undefined}
+ onBlankPointerClick={editMode ? () => setSelectedId(null) : undefined}
+ onElementMouseEnter={
+ editMode
+ ? undefined
+ : ({ id, model }) => {
+ const data = model.attributes.data as NodeData | undefined;
+ setHovered(data?.label ?? String(id));
+ }
+ }
+ onElementMouseLeave={editMode ? undefined : () => setHovered(null)}
+ />
+ {!editMode && hovered && (
+
+ Hovering: {hovered}
+
)}
{editMode && (
diff --git a/packages/joint-react/src/stories/examples/with-element-controls/code.tsx b/packages/joint-react/src/stories/examples/with-element-controls/code.tsx
index 4caa2125d0..efc0a18719 100644
--- a/packages/joint-react/src/stories/examples/with-element-controls/code.tsx
+++ b/packages/joint-react/src/stories/examples/with-element-controls/code.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import { type CellRecord, GraphProvider, useCell, Paper, useNodesMeasuredEffect, type ElementRecord, selectElementSize } from '@joint/react';
import '../index.css';
import { PAPER_CLASSNAME, PAPER_STYLE, PRIMARY, LIGHT, TEXT } from 'storybook-config/theme';
@@ -743,7 +744,7 @@ function Main() {
useNodesMeasuredEffect(handleElementsMeasured);
return (
-
{
- paper.removeTools();
-
- if (timeoutIdRef.current) {
- clearTimeout(timeoutIdRef.current);
- timeoutIdRef.current = null;
- }
-
- const tools = model.isLink()
- ? getLinkTools(view as dia.LinkView)
- : getElementTools(view as dia.ElementView);
-
- const toolsView = new dia.ToolsView({ tools });
- view.addTools(toolsView);
- currentToolsViewRef.current = toolsView;
- },
- onCellMouseLeave: () => {
- timeoutIdRef.current = setTimeout(() => {
- currentToolsViewRef.current?.remove();
- currentToolsViewRef.current = null;
- timeoutIdRef.current = null;
- }, 1000);
-
- currentToolsViewRef.current?.el.classList.add(
- 'opacity-0',
- 'transition-opacity',
- 'duration-300',
- 'delay-300'
- );
- },
- onElementPointerMove: ({ view }) => view.removeTools(),
- });
-
return (
- {
+ paper.removeTools();
+
+ if (timeoutIdRef.current) {
+ clearTimeout(timeoutIdRef.current);
+ timeoutIdRef.current = null;
+ }
+
+ const tools = model.isLink()
+ ? getLinkTools(view as dia.LinkView)
+ : getElementTools(view as dia.ElementView);
+
+ const toolsView = new dia.ToolsView({ tools });
+ view.addTools(toolsView);
+ currentToolsViewRef.current = toolsView;
+ }}
+ onCellMouseLeave={() => {
+ timeoutIdRef.current = setTimeout(() => {
+ currentToolsViewRef.current?.remove();
+ currentToolsViewRef.current = null;
+ timeoutIdRef.current = null;
+ }, 1000);
+
+ currentToolsViewRef.current?.el.classList.add(
+ 'opacity-0',
+ 'transition-opacity',
+ 'duration-300',
+ 'delay-300'
+ );
+ }}
+ onElementPointerMove={({ view }) => view.removeTools()}
className={PAPER_CLASSNAME}
renderElement={RenderElement}
gridSize={20}
diff --git a/packages/joint-react/src/stories/examples/with-graph-neighbors/code.tsx b/packages/joint-react/src/stories/examples/with-graph-neighbors/code.tsx
index 6dbf2ecbf3..2caddc1b6b 100644
--- a/packages/joint-react/src/stories/examples/with-graph-neighbors/code.tsx
+++ b/packages/joint-react/src/stories/examples/with-graph-neighbors/code.tsx
@@ -1,7 +1,8 @@
-
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
+/* eslint-disable react-perf/jsx-no-new-function-as-prop */
import { useCallback, useEffect, useRef, useState } from 'react';
-import { type CellRecord, GraphProvider, useCell, Paper, SVGText, useGraph, useMarkup, usePaperEvents, selectElementSize } from '@joint/react';
+import { type CellRecord, GraphProvider, useCell, Paper, SVGText, useGraph, useMarkup, selectElementSize } from '@joint/react';
import { highlighters, type dia } from '@joint/core';
import { LIGHT, PAPER_CLASSNAME, PAPER_STYLE, PRIMARY, SECONDARY } from 'storybook-config/theme';
@@ -179,9 +180,18 @@ function Main() {
}
}, [highlightState]);
- usePaperEvents(
- {
- onElementPointerClick: ({ id }) => {
+ const renderElement = useCallback(
+ (data: NodeData) => ,
+ []
+ );
+
+ return (
+ {
const clickedId = String(id);
setHighlightState((previous) => {
if (previous.selectedId === clickedId) {
@@ -202,23 +212,8 @@ function Main() {
connectedLinkIds: nextConnectedLinkIds,
};
});
- },
- onBlankPointerClick: () => setHighlightState(INITIAL_STATE),
- },
- [graph]
- );
-
- const renderElement = useCallback(
- (data: NodeData) => ,
- []
- );
-
- return (
- setHighlightState(INITIAL_STATE)}
/>
);
}
diff --git a/packages/joint-react/src/stories/examples/with-highlighter/code.tsx b/packages/joint-react/src/stories/examples/with-highlighter/code.tsx
index 78024f127d..a837c3efb4 100644
--- a/packages/joint-react/src/stories/examples/with-highlighter/code.tsx
+++ b/packages/joint-react/src/stories/examples/with-highlighter/code.tsx
@@ -1,5 +1,6 @@
/* eslint-disable react-perf/jsx-no-new-object-as-prop */
-import { type CellRecord, GraphProvider, useCell, Paper, useMarkup, usePaperEvents, selectElementSize } from '@joint/react';
+/* eslint-disable react-perf/jsx-no-new-function-as-prop */
+import { type CellRecord, GraphProvider, useCell, Paper, useMarkup, selectElementSize } from '@joint/react';
import { type dia, highlighters } from '@joint/core';
import '../index.css';
import { useRef } from 'react';
@@ -99,20 +100,14 @@ function removeHighlighter(variant: HighlighterVariant, elementView: dia.Element
function Main({ variant }: Readonly) {
const paperRef = useRef(null);
- usePaperEvents(
- {
- onElementMouseEnter: ({ view }) => addHighlighter(variant, view),
- onElementMouseLeave: ({ view }) => removeHighlighter(variant, view),
- },
- [variant]
- );
-
return (
addHighlighter(variant, view)}
+ onElementMouseLeave={({ view }) => removeHighlighter(variant, view)}
/>
);
diff --git a/packages/joint-react/src/stories/examples/with-link-labels/code.tsx b/packages/joint-react/src/stories/examples/with-link-labels/code.tsx
index 56e456e80b..6142c44d03 100644
--- a/packages/joint-react/src/stories/examples/with-link-labels/code.tsx
+++ b/packages/joint-react/src/stories/examples/with-link-labels/code.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import { type CellRecord, GraphProvider, Paper } from '@joint/react';
import { LIGHT, PAPER_CLASSNAME, PRIMARY, SECONDARY } from 'storybook-config/theme';
import '../index.css';
@@ -79,7 +80,7 @@ const initialCells: ReadonlyArray> = [
function Main() {
return (
-
diff --git a/packages/joint-react/src/stories/examples/with-link-markers-named/code.tsx b/packages/joint-react/src/stories/examples/with-link-markers-named/code.tsx
index c2cc3c9ccc..e426ba6731 100644
--- a/packages/joint-react/src/stories/examples/with-link-markers-named/code.tsx
+++ b/packages/joint-react/src/stories/examples/with-link-markers-named/code.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import type { CellRecord, LinkRecord } from '@joint/react';
import { GraphProvider, Paper } from '@joint/react';
import { PAPER_CLASSNAME } from 'storybook-config/theme';
@@ -51,7 +52,7 @@ const initialCells = buildCells();
export default function App() {
return (
- view.addTools(toolsView),
- onLinkMouseLeave: ({ view }) => view.removeTools(),
- });
-
return (
view.addTools(toolsView)}
+ onLinkMouseLeave={({ view }) => view.removeTools()}
/>
-
) {
className="absolute bg-black bottom-6 right-6 border border-[#dde6ed] rounded-lg overflow-hidden"
style={{ width: MINIMAP_WIDTH, height: MINIMAP_HEIGHT }}
>
- setSelectedElement(id),
- onElementPointerDblClick: ({ model, graph }) => {
- model.clone().translate(10, 10).addTo(graph);
- },
- onBlankPointerClick: () => setSelectedElement(null),
- },
- [setSelectedElement]
- );
-
return (
-
setSelectedElement(id)}
+ onElementPointerDblClick={({ model, graph }) => {
+ model.clone().translate(10, 10).addTo(graph);
+ }}
+ onBlankPointerClick={() => setSelectedElement(null)}
>
diff --git a/packages/joint-react/src/stories/examples/with-shape-animations/code.tsx b/packages/joint-react/src/stories/examples/with-shape-animations/code.tsx
index bad413698e..22ff94098b 100644
--- a/packages/joint-react/src/stories/examples/with-shape-animations/code.tsx
+++ b/packages/joint-react/src/stories/examples/with-shape-animations/code.tsx
@@ -420,7 +420,7 @@ function PowerControl() {
function Main() {
return (
-
) {
const { setCell } = useGraph>();
- usePaperEvents({
- 'blank:pointerdblclick': (_event, x, y) => {
- setCell({
- id: `node-${Date.now()}`,
- type: 'element',
- data: { label: 'New Node' },
- position: { x, y },
- portMap: { in: PORT_IN, out: PORT_OUT },
- z: 2,
- });
- },
- 'link:contextmenu': (linkView, event_, _x, _y) => {
- const labelNode = (event_.target as Element | null)?.closest('[label-idx]');
- if (!labelNode) return;
- const labelIndex = Number.parseInt(labelNode.getAttribute('label-idx') ?? '', 10);
- if (Number.isNaN(labelIndex)) return;
- event_.preventDefault();
- setCell(String(linkView.model.id), (previous) => {
- const rawLabelMap = (previous as { labelMap?: Record }).labelMap;
- if (!rawLabelMap) return previous;
- const keys = Object.keys(rawLabelMap);
- if (labelIndex >= keys.length) return previous;
- const labelKey = keys[labelIndex];
- const labelMap = Object.fromEntries(
- Object.entries(rawLabelMap).filter(([key]) => key !== labelKey)
- );
- return { ...previous, labelMap } as CellRecord;
- });
- },
- 'link:pointerdblclick': (linkView, _event, x, y) => {
- const totalLength = linkView.getConnectionLength();
- const closestLength = linkView.getClosestPointLength({ x, y });
- const position = totalLength > 0 ? closestLength / totalLength : 0.5;
- const labelKey = `label-${Date.now()}`;
- setCell(String(linkView.model.id), (previous) => {
- const linkRecord = previous as CellRecord & { labelMap?: Record };
- return {
- ...linkRecord,
- labelMap: { ...linkRecord.labelMap, [labelKey]: { position, text: 'New Label' } },
- };
- });
- },
- });
-
return (
) {
embeddingMode
validateEmbedding={validateEmbedding}
transform={`scale(${zoom})`}
+ onBlankPointerDblClick={({ x, y }) => {
+ setCell({
+ id: `node-${Date.now()}`,
+ type: 'element',
+ data: { label: 'New Node' },
+ position: { x, y },
+ portMap: { in: PORT_IN, out: PORT_OUT },
+ z: 2,
+ });
+ }}
+ onLinkContextMenu={({ view, event }) => {
+ const labelNode = (event.target as Element | null)?.closest('[label-idx]');
+ if (!labelNode) return;
+ const labelIndex = Number.parseInt(labelNode.getAttribute('label-idx') ?? '', 10);
+ if (Number.isNaN(labelIndex)) return;
+ event.preventDefault();
+ setCell(String(view.model.id), (previous) => {
+ const rawLabelMap = (previous as { labelMap?: Record }).labelMap;
+ if (!rawLabelMap) return previous;
+ const keys = Object.keys(rawLabelMap);
+ if (labelIndex >= keys.length) return previous;
+ const labelKey = keys[labelIndex];
+ const labelMap = Object.fromEntries(
+ Object.entries(rawLabelMap).filter(([key]) => key !== labelKey)
+ );
+ return { ...previous, labelMap } as CellRecord;
+ });
+ }}
+ onLinkPointerDblClick={({ view, x, y }) => {
+ const totalLength = view.getConnectionLength();
+ const closestLength = view.getClosestPointLength({ x, y });
+ const position = totalLength > 0 ? closestLength / totalLength : 0.5;
+ const labelKey = `label-${Date.now()}`;
+ setCell(String(view.model.id), (previous) => {
+ const linkRecord = previous as CellRecord & { labelMap?: Record };
+ return {
+ ...linkRecord,
+ labelMap: { ...linkRecord.labelMap, [labelKey]: { position, text: 'New Label' } },
+ };
+ });
+ }}
/>
);
}
diff --git a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-jotai.tsx b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-jotai.tsx
index 7f7f6328b2..a0480047ea 100644
--- a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-jotai.tsx
+++ b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-jotai.tsx
@@ -1,5 +1,6 @@
/* eslint-disable sonarjs/pseudo-random */
/* eslint-disable react-perf/jsx-no-new-function-as-prop */
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
/**
* ============================================================================
diff --git a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-peerjs.tsx b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-peerjs.tsx
index 4fc3d62979..752a4087fe 100644
--- a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-peerjs.tsx
+++ b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-peerjs.tsx
@@ -1,5 +1,6 @@
/* eslint-disable sonarjs/pseudo-random */
/* eslint-disable react-perf/jsx-no-new-function-as-prop */
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
/**
* ============================================================================
diff --git a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-redux.tsx b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-redux.tsx
index 344bc107ae..865c5d61aa 100644
--- a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-redux.tsx
+++ b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-redux.tsx
@@ -1,6 +1,7 @@
/* eslint-disable @eslint-react/hooks-extra/no-direct-set-state-in-use-effect */
/* eslint-disable sonarjs/pseudo-random */
/* eslint-disable react-perf/jsx-no-new-function-as-prop */
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import {
GraphProvider,
diff --git a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-zustand.tsx b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-zustand.tsx
index 43d3a2848a..6020ad9dc0 100644
--- a/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-zustand.tsx
+++ b/packages/joint-react/src/stories/tutorials/step-by-step/code-controlled-mode-zustand.tsx
@@ -1,5 +1,6 @@
/* eslint-disable sonarjs/pseudo-random */
/* eslint-disable react-perf/jsx-no-new-function-as-prop */
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
/**
* ============================================================================
diff --git a/packages/joint-react/src/stories/tutorials/step-by-step/code-html-renderer.tsx b/packages/joint-react/src/stories/tutorials/step-by-step/code-html-renderer.tsx
index 5fc780afa4..7c868a7753 100644
--- a/packages/joint-react/src/stories/tutorials/step-by-step/code-html-renderer.tsx
+++ b/packages/joint-react/src/stories/tutorials/step-by-step/code-html-renderer.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import { useCallback, useRef, useState } from 'react';
import {
type CellRecord,
diff --git a/packages/joint-react/src/utils/joint-jsx/jsx-to-markup.stories.tsx b/packages/joint-react/src/utils/joint-jsx/jsx-to-markup.stories.tsx
index 129ad5c5b8..6c08432a7b 100644
--- a/packages/joint-react/src/utils/joint-jsx/jsx-to-markup.stories.tsx
+++ b/packages/joint-react/src/utils/joint-jsx/jsx-to-markup.stories.tsx
@@ -1,4 +1,4 @@
-
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import { useMemo } from 'react';
import { dia } from '@joint/core';
import '../../stories/examples/index.css';
diff --git a/packages/joint-react/src/utils/test-wrappers.tsx b/packages/joint-react/src/utils/test-wrappers.tsx
index 2ff83b10aa..d8d4fd446b 100644
--- a/packages/joint-react/src/utils/test-wrappers.tsx
+++ b/packages/joint-react/src/utils/test-wrappers.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable react-perf/jsx-no-new-object-as-prop */
import { useCallback } from 'react';
import { GraphProvider, Paper, type GraphProviderProps, type PaperProps } from '../components';
import { dia } from '@joint/core';