From 423f45e0c92ae44bc73516edeebaf7cc1055b3c0 Mon Sep 17 00:00:00 2001 From: pengfu Date: Sat, 17 Sep 2022 21:35:22 +0800 Subject: [PATCH 01/25] feature(deleteSelectedElements): add `deleteSelectedElements` method --- example/src/Basic/index.tsx | 7 ++- src/hooks/useGlobalKeyHandler.ts | 81 +++----------------------- src/hooks/useReactFlow.ts | 97 +++++++++++++++++++++++++++++++- src/types/instance.ts | 2 + 4 files changed, 110 insertions(+), 77 deletions(-) diff --git a/example/src/Basic/index.tsx b/example/src/Basic/index.tsx index 74d0f69b..7db68b3c 100644 --- a/example/src/Basic/index.tsx +++ b/example/src/Basic/index.tsx @@ -56,6 +56,10 @@ const BasicFlow = () => { ); }; + const deleteElements = () => { + instance.deleteSelectedElements() + } + return ( { - + + ); diff --git a/src/hooks/useGlobalKeyHandler.ts b/src/hooks/useGlobalKeyHandler.ts index 8c9cb89d..e9574624 100644 --- a/src/hooks/useGlobalKeyHandler.ts +++ b/src/hooks/useGlobalKeyHandler.ts @@ -1,94 +1,27 @@ import { useEffect } from 'react'; -import shallow from 'zustand/shallow'; -import { useStore, useStoreApi } from '../store'; +import { useStoreApi } from '../store'; import useKeyPress from './useKeyPress'; -import { getConnectedEdges } from '../utils/graph'; -import { EdgeChange, KeyCode, NodeChange, Node, ReactFlowState } from '../types'; +import { KeyCode } from '../types'; +import useReactFlow from './useReactFlow'; interface HookParams { deleteKeyCode: KeyCode | null; multiSelectionKeyCode: KeyCode | null; } -const selector = (s: ReactFlowState) => ({ - onNodesChange: s.onNodesChange, - onEdgesChange: s.onEdgesChange, -}); - export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { const store = useStoreApi(); - const { onNodesChange, onEdgesChange } = useStore(selector, shallow); + const { deleteSelectedElements } = useReactFlow(); const deleteKeyPressed = useKeyPress(deleteKeyCode); const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); useEffect(() => { - const { nodeInternals, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete } = store.getState(); - const nodes = Array.from(nodeInternals.values()); - const nodesToRemove = nodes.reduce((res, node) => { - if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) { - res.push(node); - } else if (node.selected) { - res.push(node); - } - - return res; - }, []); - const selectedEdges = edges.filter((e) => e.selected); - - if (deleteKeyPressed && (nodesToRemove || selectedEdges)) { - const connectedEdges = getConnectedEdges(nodesToRemove, edges); - const edgesToRemove = [...selectedEdges, ...connectedEdges]; - const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => { - if (!res.includes(edge.id)) { - res.push(edge.id); - } - return res; - }, []); - - if (hasDefaultEdges || hasDefaultNodes) { - if (hasDefaultEdges) { - store.setState({ - edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)), - }); - } - - if (hasDefaultNodes) { - nodesToRemove.forEach((node) => { - nodeInternals.delete(node.id); - }); - - store.setState({ - nodeInternals: new Map(nodeInternals), - }); - } - } - - if (edgeIdsToRemove.length > 0) { - onEdgesDelete?.(edgesToRemove); - - if (onEdgesChange) { - const edgeChanges: EdgeChange[] = edgeIdsToRemove.map((id) => ({ - id, - type: 'remove', - })); - onEdgesChange(edgeChanges); - } - } - - if (nodesToRemove.length > 0) { - onNodesDelete?.(nodesToRemove); - - if (onNodesChange) { - const nodeChanges: NodeChange[] = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' })); - onNodesChange(nodeChanges); - } - } - - store.setState({ nodesSelectionActive: false }); + if (deleteKeyPressed) { + deleteSelectedElements(); } - }, [deleteKeyPressed, onNodesChange, onEdgesChange]); + }, [deleteKeyPressed]); useEffect(() => { store.setState({ multiSelectionActive: multiSelectionKeyPressed }); diff --git a/src/hooks/useReactFlow.ts b/src/hooks/useReactFlow.ts index 3bffe8fa..ab8bc54e 100644 --- a/src/hooks/useReactFlow.ts +++ b/src/hooks/useReactFlow.ts @@ -1,7 +1,7 @@ import { useCallback, useMemo } from 'react'; import useViewportHelper from './useViewportHelper'; -import { useStoreApi } from '../store'; +import { useStore, useStoreApi } from '../store'; import { ReactFlowInstance, Instance, @@ -11,12 +11,25 @@ import { EdgeResetChange, NodeRemoveChange, EdgeRemoveChange, + ReactFlowState, + EdgeChange, + NodeChange, + Node, } from '../types'; +import { getConnectedEdges } from '../utils/graph'; +import shallow from 'zustand/shallow'; + +const selector = (s: ReactFlowState) => ({ + onNodesChange: s.onNodesChange, + onEdgesChange: s.onEdgesChange, +}); export default function useReactFlow(): ReactFlowInstance { const viewportHelper = useViewportHelper(); const store = useStoreApi(); + const { onNodesChange, onEdgesChange } = useStore(selector, shallow); + const getNodes = useCallback>(() => { const { nodeInternals } = store.getState(); const nodes = Array.from(nodeInternals.values()); @@ -110,6 +123,73 @@ export default function useReactFlow(): ReactFlo }; }, []); + const deleteSelectedElements = useCallback(() => { + const { nodeInternals, edges, hasDefaultNodes, hasDefaultEdges, onNodesDelete, onEdgesDelete } = store.getState(); + const nodes = Array.from(nodeInternals.values()); + const nodesToRemove = nodes.reduce((res, node) => { + if (!node.selected && node.parentNode && res.find((n) => n.id === node.parentNode)) { + res.push(node); + } else if (node.selected) { + res.push(node); + } + + return res; + }, []); + const selectedEdges = edges.filter((e) => e.selected); + + if (nodesToRemove || selectedEdges) { + const connectedEdges = getConnectedEdges(nodesToRemove, edges); + const edgesToRemove = [...selectedEdges, ...connectedEdges]; + const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => { + if (!res.includes(edge.id)) { + res.push(edge.id); + } + return res; + }, []); + + if (hasDefaultEdges || hasDefaultNodes) { + if (hasDefaultEdges) { + store.setState({ + edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)), + }); + } + + if (hasDefaultNodes) { + nodesToRemove.forEach((node) => { + nodeInternals.delete(node.id); + }); + + store.setState({ + nodeInternals: new Map(nodeInternals), + }); + } + } + + if (edgeIdsToRemove.length > 0) { + onEdgesDelete?.(edgesToRemove); + + if (onEdgesChange) { + const edgeChanges: EdgeChange[] = edgeIdsToRemove.map((id) => ({ + id, + type: 'remove', + })); + onEdgesChange(edgeChanges); + } + } + + if (nodesToRemove.length > 0) { + onNodesDelete?.(nodesToRemove); + + if (onNodesChange) { + const nodeChanges: NodeChange[] = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' })); + onNodesChange(nodeChanges); + } + } + + store.setState({ nodesSelectionActive: false }); + } + }, []); + return useMemo(() => { return { ...viewportHelper, @@ -122,6 +202,19 @@ export default function useReactFlow(): ReactFlo addNodes, addEdges, toObject, + deleteSelectedElements, }; - }, [viewportHelper, getNodes, getNode, getEdges, getEdge, setNodes, setEdges, addNodes, addEdges, toObject]); + }, [ + viewportHelper, + getNodes, + getNode, + getEdges, + getEdge, + setNodes, + setEdges, + addNodes, + addEdges, + toObject, + deleteSelectedElements, + ]); } diff --git a/src/types/instance.ts b/src/types/instance.ts index 14867f00..4d21af37 100644 --- a/src/types/instance.ts +++ b/src/types/instance.ts @@ -22,6 +22,7 @@ export namespace Instance { export type GetEdge = (id: string) => Edge | undefined; export type AddEdges = (payload: Edge[] | Edge) => void; export type ToObject = () => ReactFlowJsonObject; + export type DeleteSelectedElements = () => void; } export type ReactFlowInstance = { @@ -34,5 +35,6 @@ export type ReactFlowInstance = { addEdges: Instance.AddEdges; getEdge: Instance.GetEdge; toObject: Instance.ToObject; + deleteSelectedElements: Instance.DeleteSelectedElements; viewportInitialized: boolean; } & Omit; From d057a1cd346d76f8168ea8bf5e9ce7e57bb08ce5 Mon Sep 17 00:00:00 2001 From: moklick Date: Sun, 9 Oct 2022 19:55:25 +0200 Subject: [PATCH 02/25] feat(packages): add interactive minimap --- examples/vite-app/package.json | 3 +- examples/vite-app/src/App/index.tsx | 6 + .../src/examples/InteractiveMinimap/index.tsx | 121 +++++++++++++++++ packages/interactive-minimap/.eslintrc.js | 4 + packages/interactive-minimap/CHANGELOG.md | 42 ++++++ packages/interactive-minimap/README.md | 10 ++ packages/interactive-minimap/package.json | 65 +++++++++ packages/interactive-minimap/src/MiniMap.tsx | 121 +++++++++++++++++ .../interactive-minimap/src/MiniMapNode.tsx | 53 ++++++++ packages/interactive-minimap/src/index.tsx | 2 + packages/interactive-minimap/src/style.css | 3 + packages/interactive-minimap/src/types.ts | 15 +++ packages/interactive-minimap/tsconfig.json | 6 + pnpm-lock.yaml | 125 +++++++++++------- 14 files changed, 527 insertions(+), 49 deletions(-) create mode 100644 examples/vite-app/src/examples/InteractiveMinimap/index.tsx create mode 100644 packages/interactive-minimap/.eslintrc.js create mode 100644 packages/interactive-minimap/CHANGELOG.md create mode 100644 packages/interactive-minimap/README.md create mode 100644 packages/interactive-minimap/package.json create mode 100644 packages/interactive-minimap/src/MiniMap.tsx create mode 100644 packages/interactive-minimap/src/MiniMapNode.tsx create mode 100644 packages/interactive-minimap/src/index.tsx create mode 100644 packages/interactive-minimap/src/style.css create mode 100644 packages/interactive-minimap/src/types.ts create mode 100644 packages/interactive-minimap/tsconfig.json diff --git a/examples/vite-app/package.json b/examples/vite-app/package.json index 23bbc7fc..27ace109 100644 --- a/examples/vite-app/package.json +++ b/examples/vite-app/package.json @@ -19,7 +19,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", - "reactflow": "workspace:*" + "reactflow": "workspace:*", + "@reactflow/interactive-minimap": "workspace:*" }, "devDependencies": { "@cypress/skip-test": "^2.6.1", diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 0ec2a021..71c4989d 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -36,6 +36,7 @@ import Validation from '../examples/Validation'; import UseKeyPress from '../examples/UseKeyPress'; import EdgeRouting from '../examples/EdgeRouting'; import CancelConnection from '../examples/CancelConnection'; +import InteractiveMinimap from '../examples/InteractiveMinimap'; interface IRoute { name: string; @@ -124,6 +125,11 @@ const routes: IRoute[] = [ path: '/interaction', component: Interaction, }, + { + name: 'Interactive Minimap', + path: '/interactive-minimap', + component: InteractiveMinimap, + }, { name: 'Layouting', path: '/layouting', diff --git a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx new file mode 100644 index 00000000..f80b2971 --- /dev/null +++ b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx @@ -0,0 +1,121 @@ +import { MouseEvent } from 'react'; +import ReactFlow, { + Background, + BackgroundVariant, + Controls, + ReactFlowProvider, + Node, + Edge, + useReactFlow, +} from 'reactflow'; +import { MiniMap } from '@reactflow/interactive-minimap'; + +const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node); +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); + +const initialNodes: Node[] = [ + { + id: '1', + type: 'input', + data: { label: 'Node 1' }, + position: { x: 250, y: 5 }, + className: 'light', + }, + { + id: '2', + data: { label: 'Node 2' }, + position: { x: 100, y: 100 }, + className: 'light', + }, + { + id: '3', + data: { label: 'Node 3' }, + position: { x: 400, y: 100 }, + className: 'light', + }, + { + id: '4', + data: { label: 'Node 4' }, + position: { x: 400, y: 200 }, + className: 'light', + }, +]; + +const initialEdges: Edge[] = [ + { id: 'e1-2', source: '1', target: '2', animated: true }, + { id: 'e1-3', source: '1', target: '3' }, +]; + +const defaultEdgeOptions = { zIndex: 0 }; + +const BasicFlow = () => { + const instance = useReactFlow(); + + const updatePos = () => { + instance.setNodes((nodes) => + nodes.map((node) => { + node.position = { + x: Math.random() * 400, + y: Math.random() * 400, + }; + + return node; + }) + ); + }; + + const logToObject = () => console.log(instance.toObject()); + const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 }); + + const toggleClassnames = () => { + instance.setNodes((nodes) => + nodes.map((node) => { + node.className = node.className === 'light' ? 'dark' : 'light'; + + return node; + }) + ); + }; + + return ( + + + + + +
+ + + + +
+
+ ); +}; + +export default function App() { + return ( + + + + ); +} diff --git a/packages/interactive-minimap/.eslintrc.js b/packages/interactive-minimap/.eslintrc.js new file mode 100644 index 00000000..31cfbd11 --- /dev/null +++ b/packages/interactive-minimap/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['@reactflow/eslint-config'], +}; diff --git a/packages/interactive-minimap/CHANGELOG.md b/packages/interactive-minimap/CHANGELOG.md new file mode 100644 index 00000000..eb44ff49 --- /dev/null +++ b/packages/interactive-minimap/CHANGELOG.md @@ -0,0 +1,42 @@ +# @reactflow/minimap + +## 11.0.1 + +### Patch Changes + +- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]: + - @reactflow/core@11.1.0 + +## 11.0.0 + +### Major Changes + +- **Better [Accessibility](/docs/guides/accessibility)** + - Nodes and edges are focusable, selectable, moveable and deleteable with the keyboard. + - `aria-` default attributes for all elements and controllable via `ariaLabel` options + - Keyboard controls can be disabled with the new `disableKeyboardA11y` prop +- **Better selectable edges** via new edge option: `interactionWidth` - renders invisible edge that makes it easier to interact +- **Better routing for smoothstep and step edges**: https://twitter.com/reactflowdev/status/1567535405284614145 +- **Nicer edge updating behaviour**: https://twitter.com/reactflowdev/status/1564966917517021184 +- **Node origin**: The new `nodeOrigin` prop lets you control the origin of a node. Useful for layouting. +- **New background pattern**: `BackgroundVariant.Cross` variant +- **[`useOnViewportChange`](/docs/api/hooks/use-on-viewport-change) hook** - handle viewport changes within a component +- **[`useOnSelectionChange`](/docs/api/hooks/use-on-selection-change) hook** - handle selection changes within a component +- **[`useNodesInitialized`](/docs/api/hooks/use-nodes-initialized) hook** - returns true if all nodes are initialized and if there is more than one node +- **Deletable option** for Nodes and edges +- **New Event handlers**: `onPaneMouseEnter`, `onPaneMouseMove` and `onPaneMouseLeave` +- **Edge `pathOptions`** for `smoothstep` and `default` edges +- **Nicer cursor defaults**: Cursor is grabbing, while dragging a node or panning +- **Pane moveable** with middle mouse button +- **Pan over nodes** when they are not draggable (`draggable=false` or `nodesDraggable` false) +- **[``](/docs/api/edges/base-edge) component** that makes it easier to build custom edges +- **[Separately installable packages](/docs/overview/packages/)** + - @reactflow/core + - @reactflow/background + - @reactflow/controls + - @reactflow/minimap + +### Patch Changes + +- Updated dependencies: + - @reactflow/core@11.0.0 diff --git a/packages/interactive-minimap/README.md b/packages/interactive-minimap/README.md new file mode 100644 index 00000000..2dba17d9 --- /dev/null +++ b/packages/interactive-minimap/README.md @@ -0,0 +1,10 @@ +# @reactflow/minimap + +Mini map component for React Flow. + +## Installation + +```sh +npm install @reactflow/minimap +``` + diff --git a/packages/interactive-minimap/package.json b/packages/interactive-minimap/package.json new file mode 100644 index 00000000..e5409e1a --- /dev/null +++ b/packages/interactive-minimap/package.json @@ -0,0 +1,65 @@ +{ + "name": "@reactflow/interactive-minimap", + "version": "0.0.1-alpha.0", + "description": "Interactive Minimap component for React Flow.", + "keywords": [ + "react", + "node-based UI", + "graph", + "diagram", + "workflow", + "react-flow" + ], + "files": [ + "dist" + ], + "source": "src/index.tsx", + "main": "dist/umd/index.js", + "module": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "sideEffects": false, + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wbkd/react-flow.git", + "directory": "packages/interactive-minimap" + }, + "scripts": { + "dev": "concurrently \"rollup --config node:@reactflow/rollup-config --watch\" pnpm:css-watch", + "build": "rollup --config node:@reactflow/rollup-config --environment NODE_ENV:production && npm run css", + "css": "postcss src/*.css --config ../../tooling/postcss-config/postcss.config.js --dir dist", + "css-watch": "pnpm css --watch", + "lint": "eslint --ext .js,.jsx,.ts,.tsx src", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@babel/runtime": "^7.18.9", + "@reactflow/core": "workspace:*", + "classcat": "^5.0.3", + "zustand": "^4.1.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + }, + "devDependencies": { + "@reactflow/eslint-config": "workspace:^0.0.0", + "@reactflow/rollup-config": "workspace:*", + "@reactflow/tsconfig": "workspace:*", + "@types/node": "^18.7.16", + "@types/react": "^18.0.19", + "react": "^18.2.0", + "typescript": "^4.8.3" + }, + "rollup": { + "globals": { + "zustand": "Zustand", + "zustand/shallow": "zustandShallow", + "classcat": "cc" + }, + "name": "ReactFlowMinimap" + } +} diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx new file mode 100644 index 00000000..b7ef604c --- /dev/null +++ b/packages/interactive-minimap/src/MiniMap.tsx @@ -0,0 +1,121 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { memo, useEffect, useRef } from 'react'; +import cc from 'classcat'; +import shallow from 'zustand/shallow'; +import { zoom, D3ZoomEvent } from 'd3-zoom'; +import { select } from 'd3-selection'; +import { useStore, getRectOfNodes, ReactFlowState, Rect, Panel, getBoundsOfRects } from '@reactflow/core'; + +import MiniMapNode from './MiniMapNode'; +import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; + +declare const window: any; + +const defaultWidth = 200; +const defaultHeight = 150; + +const selector = (s: ReactFlowState) => { + const nodes = Array.from(s.nodeInternals.values()); + const viewBB: Rect = { + x: -s.transform[0] / s.transform[2], + y: -s.transform[1] / s.transform[2], + width: s.width / s.transform[2], + height: s.height / s.transform[2], + }; + + return { + nodes: nodes.filter((node) => !node.hidden && node.width && node.height), + viewBB, + boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes), viewBB) : viewBB, + rfId: s.rfId, + }; +}; + +const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); + +const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; + +function MiniMap({ + style, + className, + nodeStrokeColor = '#555', + nodeColor = '#fff', + nodeClassName = '', + nodeBorderRadius = 5, + nodeStrokeWidth = 2, + maskColor = 'rgb(240, 242, 243, 0.7)', + position = 'bottom-right', +}: MiniMapProps) { + const svg = useRef(null); + const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); + const elementWidth = (style?.width as number) ?? defaultWidth; + const elementHeight = (style?.height as number) ?? defaultHeight; + const nodeColorFunc = getAttrFunction(nodeColor); + const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); + const nodeClassNameFunc = getAttrFunction(nodeClassName); + const scaledWidth = boundingRect.width / elementWidth; + const scaledHeight = boundingRect.height / elementHeight; + const viewScale = Math.max(scaledWidth, scaledHeight); + const viewWidth = viewScale * elementWidth; + const viewHeight = viewScale * elementHeight; + const offset = 5 * viewScale; + const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; + const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; + const width = viewWidth + offset * 2; + const height = viewHeight + offset * 2; + const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; + const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; + + useEffect(() => { + const d3ZoomInstance = zoom(); + const selection = select(svg.current as Element).call(d3ZoomInstance); + + d3ZoomInstance.on('zoom', (event: D3ZoomEvent) => { + console.log(event); + }); + }, []); + + return ( + + + React Flow mini map + {nodes.map((node) => { + return ( + + ); + })} + + + + ); +} + +MiniMap.displayName = 'MiniMap'; + +export default memo(MiniMap); diff --git a/packages/interactive-minimap/src/MiniMapNode.tsx b/packages/interactive-minimap/src/MiniMapNode.tsx new file mode 100644 index 00000000..3ea3bd33 --- /dev/null +++ b/packages/interactive-minimap/src/MiniMapNode.tsx @@ -0,0 +1,53 @@ +import { memo, CSSProperties } from 'react'; +import cc from 'classcat'; + +interface MiniMapNodeProps { + x: number; + y: number; + width: number; + height: number; + borderRadius: number; + className: string; + color: string; + shapeRendering: string; + strokeColor: string; + strokeWidth: number; + style?: CSSProperties; +} + +const MiniMapNode = ({ + x, + y, + width, + height, + style, + color, + strokeColor, + strokeWidth, + className, + borderRadius, + shapeRendering, +}: MiniMapNodeProps) => { + const { background, backgroundColor } = style || {}; + const fill = (color || background || backgroundColor) as string; + + return ( + + ); +}; + +MiniMapNode.displayName = 'MiniMapNode'; + +export default memo(MiniMapNode); diff --git a/packages/interactive-minimap/src/index.tsx b/packages/interactive-minimap/src/index.tsx new file mode 100644 index 00000000..7b91e95d --- /dev/null +++ b/packages/interactive-minimap/src/index.tsx @@ -0,0 +1,2 @@ +export { default as MiniMap } from './MiniMap'; +export * from './types'; diff --git a/packages/interactive-minimap/src/style.css b/packages/interactive-minimap/src/style.css new file mode 100644 index 00000000..dbb9a931 --- /dev/null +++ b/packages/interactive-minimap/src/style.css @@ -0,0 +1,3 @@ +.react-flow__minimap { + background-color: #fff; +} diff --git a/packages/interactive-minimap/src/types.ts b/packages/interactive-minimap/src/types.ts new file mode 100644 index 00000000..5f82824d --- /dev/null +++ b/packages/interactive-minimap/src/types.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { HTMLAttributes } from 'react'; +import { Node, PanelPosition } from '@reactflow/core'; + +export type GetMiniMapNodeAttribute = (node: Node) => string; + +export interface MiniMapProps extends HTMLAttributes { + nodeColor?: string | GetMiniMapNodeAttribute; + nodeStrokeColor?: string | GetMiniMapNodeAttribute; + nodeClassName?: string | GetMiniMapNodeAttribute; + nodeBorderRadius?: number; + nodeStrokeWidth?: number; + maskColor?: string; + position?: PanelPosition; +} diff --git a/packages/interactive-minimap/tsconfig.json b/packages/interactive-minimap/tsconfig.json new file mode 100644 index 00000000..8a8e74ce --- /dev/null +++ b/packages/interactive-minimap/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@reactflow/tsconfig/react.json", + "display": "@reactflow/minimap", + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26177a37..924aeef0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,8 +32,8 @@ importers: '@changesets/changelog-github': registry.npmjs.org/@changesets/changelog-github/0.4.6 '@changesets/cli': registry.npmjs.org/@changesets/cli/2.24.4 '@preconstruct/cli': registry.npmjs.org/@preconstruct/cli/2.2.1 - '@typescript-eslint/eslint-plugin': registry.npmjs.org/@typescript-eslint/eslint-plugin/5.38.0_wsb62dxj2oqwgas4kadjymcmry - '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.38.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/eslint-plugin': registry.npmjs.org/@typescript-eslint/eslint-plugin/5.39.0_jdrczqv3ll7udvbunca43w7rz4 + '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.39.0_irgkl5vooow2ydyo6aokmferha autoprefixer: registry.npmjs.org/autoprefixer/10.4.9_postcss@8.4.16 concurrently: registry.npmjs.org/concurrently/7.4.0 cypress: registry.npmjs.org/cypress/10.7.0 @@ -57,6 +57,7 @@ importers: examples/vite-app: specifiers: '@cypress/skip-test': ^2.6.1 + '@reactflow/interactive-minimap': workspace:* '@types/react': ^18.0.17 '@types/react-dom': ^18.0.6 '@vitejs/plugin-react': ^2.1.0 @@ -73,6 +74,7 @@ importers: typescript: ^4.8.3 vite: ^3.1.0 dependencies: + '@reactflow/interactive-minimap': link:../../packages/interactive-minimap classcat: registry.npmjs.org/classcat/5.0.4 dagre: registry.npmjs.org/dagre/0.8.5 localforage: registry.npmjs.org/localforage/1.10.0 @@ -178,6 +180,33 @@ importers: react: registry.npmjs.org/react/18.2.0 typescript: registry.npmjs.org/typescript/4.8.3 + packages/interactive-minimap: + specifiers: + '@babel/runtime': ^7.18.9 + '@reactflow/core': workspace:* + '@reactflow/eslint-config': workspace:^0.0.0 + '@reactflow/rollup-config': workspace:* + '@reactflow/tsconfig': workspace:* + '@types/node': ^18.7.16 + '@types/react': ^18.0.19 + classcat: ^5.0.3 + react: ^18.2.0 + typescript: ^4.8.3 + zustand: ^4.1.1 + dependencies: + '@babel/runtime': registry.npmjs.org/@babel/runtime/7.19.0 + '@reactflow/core': link:../core + classcat: registry.npmjs.org/classcat/5.0.4 + zustand: registry.npmjs.org/zustand/4.1.1_react@18.2.0 + devDependencies: + '@reactflow/eslint-config': link:../../tooling/eslint-config + '@reactflow/rollup-config': link:../../tooling/rollup-config + '@reactflow/tsconfig': link:../../tooling/tsconfig + '@types/node': registry.npmjs.org/@types/node/18.7.16 + '@types/react': registry.npmjs.org/@types/react/18.0.19 + react: registry.npmjs.org/react/18.2.0 + typescript: registry.npmjs.org/typescript/4.8.3 + packages/minimap: specifiers: '@babel/runtime': ^7.18.9 @@ -1704,11 +1733,11 @@ packages: dev: true optional: true - registry.npmjs.org/@typescript-eslint/eslint-plugin/5.38.0_wsb62dxj2oqwgas4kadjymcmry: - resolution: {integrity: sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.0.tgz} - id: registry.npmjs.org/@typescript-eslint/eslint-plugin/5.38.0 + registry.npmjs.org/@typescript-eslint/eslint-plugin/5.39.0_jdrczqv3ll7udvbunca43w7rz4: + resolution: {integrity: sha512-xVfKOkBm5iWMNGKQ2fwX5GVgBuHmZBO1tCRwXmY5oAIsPscfwm2UADDuNB8ZVYCtpQvJK4xpjrK7jEhcJ0zY9A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz} + id: registry.npmjs.org/@typescript-eslint/eslint-plugin/5.39.0 name: '@typescript-eslint/eslint-plugin' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -1718,10 +1747,10 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.38.0_irgkl5vooow2ydyo6aokmferha - '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.38.0 - '@typescript-eslint/type-utils': registry.npmjs.org/@typescript-eslint/type-utils/5.38.0_irgkl5vooow2ydyo6aokmferha - '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.38.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.39.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0 + '@typescript-eslint/type-utils': registry.npmjs.org/@typescript-eslint/type-utils/5.39.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.39.0_irgkl5vooow2ydyo6aokmferha debug: registry.npmjs.org/debug/4.3.4 eslint: registry.npmjs.org/eslint/8.23.1 ignore: registry.npmjs.org/ignore/5.2.0 @@ -1733,11 +1762,11 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/parser/5.38.0_irgkl5vooow2ydyo6aokmferha: - resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.0.tgz} - id: registry.npmjs.org/@typescript-eslint/parser/5.38.0 + registry.npmjs.org/@typescript-eslint/parser/5.39.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-PhxLjrZnHShe431sBAGHaNe6BDdxAASDySgsBCGxcBecVCi8NQWxQZMcizNA4g0pN51bBAn/FUfkWG3SDVcGlA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.39.0.tgz} + id: registry.npmjs.org/@typescript-eslint/parser/5.39.0 name: '@typescript-eslint/parser' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1746,9 +1775,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.38.0 - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.38.0 - '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3 + '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 + '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3 debug: registry.npmjs.org/debug/4.3.4 eslint: registry.npmjs.org/eslint/8.23.1 typescript: registry.npmjs.org/typescript/4.8.3 @@ -1756,21 +1785,21 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/scope-manager/5.38.0: - resolution: {integrity: sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.0.tgz} + registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0: + resolution: {integrity: sha512-/I13vAqmG3dyqMVSZPjsbuNQlYS082Y7OMkwhCfLXYsmlI0ca4nkL7wJ/4gjX70LD4P8Hnw1JywUVVAwepURBw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.39.0.tgz} name: '@typescript-eslint/scope-manager' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.38.0 - '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.38.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 + '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.39.0 dev: true - registry.npmjs.org/@typescript-eslint/type-utils/5.38.0_irgkl5vooow2ydyo6aokmferha: - resolution: {integrity: sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.0.tgz} - id: registry.npmjs.org/@typescript-eslint/type-utils/5.38.0 + registry.npmjs.org/@typescript-eslint/type-utils/5.39.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-KJHJkOothljQWzR3t/GunL0TPKY+fGJtnpl+pX+sJ0YiKTz3q2Zr87SGTmFqsCMFrLt5E0+o+S6eQY0FAXj9uA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.39.0.tgz} + id: registry.npmjs.org/@typescript-eslint/type-utils/5.39.0 name: '@typescript-eslint/type-utils' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -1779,8 +1808,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3 - '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.38.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3 + '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.39.0_irgkl5vooow2ydyo6aokmferha debug: registry.npmjs.org/debug/4.3.4 eslint: registry.npmjs.org/eslint/8.23.1 tsutils: registry.npmjs.org/tsutils/3.21.0_typescript@4.8.3 @@ -1789,18 +1818,18 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/types/5.38.0: - resolution: {integrity: sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.0.tgz} + registry.npmjs.org/@typescript-eslint/types/5.39.0: + resolution: {integrity: sha512-gQMZrnfEBFXK38hYqt8Lkwt8f4U6yq+2H5VDSgP/qiTzC8Nw8JO3OuSUOQ2qW37S/dlwdkHDntkZM6SQhKyPhw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/types/-/types-5.39.0.tgz} name: '@typescript-eslint/types' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - registry.npmjs.org/@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3: - resolution: {integrity: sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.0.tgz} - id: registry.npmjs.org/@typescript-eslint/typescript-estree/5.38.0 + registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3: + resolution: {integrity: sha512-qLFQP0f398sdnogJoLtd43pUgB18Q50QSA+BTE5h3sUxySzbWDpTSdgt4UyxNSozY/oDK2ta6HVAzvGgq8JYnA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.39.0.tgz} + id: registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0 name: '@typescript-eslint/typescript-estree' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1808,8 +1837,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.38.0 - '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.38.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 + '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.39.0 debug: registry.npmjs.org/debug/4.3.4 globby: registry.npmjs.org/globby/11.1.0 is-glob: registry.npmjs.org/is-glob/4.0.3 @@ -1820,19 +1849,19 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/utils/5.38.0_irgkl5vooow2ydyo6aokmferha: - resolution: {integrity: sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.0.tgz} - id: registry.npmjs.org/@typescript-eslint/utils/5.38.0 + registry.npmjs.org/@typescript-eslint/utils/5.39.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-+DnY5jkpOpgj+EBtYPyHRjXampJfC0yUZZzfzLuUWVZvCuKqSdJVC8UhdWipIw7VKNTfwfAPiOWzYkAwuIhiAg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.39.0.tgz} + id: registry.npmjs.org/@typescript-eslint/utils/5.39.0 name: '@typescript-eslint/utils' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': registry.npmjs.org/@types/json-schema/7.0.11 - '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.38.0 - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.38.0 - '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3 + '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 + '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3 eslint: registry.npmjs.org/eslint/8.23.1 eslint-scope: registry.npmjs.org/eslint-scope/5.1.1 eslint-utils: registry.npmjs.org/eslint-utils/3.0.0_eslint@8.23.1 @@ -1841,13 +1870,13 @@ packages: - typescript dev: true - registry.npmjs.org/@typescript-eslint/visitor-keys/5.38.0: - resolution: {integrity: sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.0.tgz} + registry.npmjs.org/@typescript-eslint/visitor-keys/5.39.0: + resolution: {integrity: sha512-yyE3RPwOG+XJBLrhvsxAidUgybJVQ/hG8BhiJo0k8JSAYfk/CshVcxf0HwP4Jt7WZZ6vLmxdo1p6EyN3tzFTkg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.39.0.tgz} name: '@typescript-eslint/visitor-keys' - version: 5.38.0 + version: 5.39.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.38.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 eslint-visitor-keys: registry.npmjs.org/eslint-visitor-keys/3.3.0 dev: true From eccd5673476cfd7b7698ec630522c3670cfe4311 Mon Sep 17 00:00:00 2001 From: Wenchen Li <9028430+neo@users.noreply.github.com> Date: Fri, 14 Oct 2022 11:33:50 -0400 Subject: [PATCH 03/25] Put wrapper container in its own stacking context --- packages/core/src/container/ReactFlow/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/container/ReactFlow/index.tsx b/packages/core/src/container/ReactFlow/index.tsx index f540cdf5..cc8c980a 100644 --- a/packages/core/src/container/ReactFlow/index.tsx +++ b/packages/core/src/container/ReactFlow/index.tsx @@ -53,8 +53,9 @@ const initDefaultViewport: Viewport = { x: 0, y: 0, zoom: 1 }; const wrapperStyle: CSSProperties = { width: '100%', height: '100%', - position: 'relative', overflow: 'hidden', + position: 'relative', + zIndex: 0, }; const ReactFlow = forwardRef( From f39936e5084a8b87933cd8e7aff91016d3fe0b55 Mon Sep 17 00:00:00 2001 From: chang_su Date: Mon, 24 Oct 2022 23:37:46 +0800 Subject: [PATCH 04/25] feature(deleteElements): add deleteElements method --- .../vite-app/src/examples/Basic/index.tsx | 5 -- .../src/examples/UseReactFlow/index.tsx | 21 +++++ .../core/src/hooks/useGlobalKeyHandler.ts | 13 +++- packages/core/src/hooks/useReactFlow.ts | 77 +++++++++++++++++++ packages/core/src/types/instance.ts | 2 + 5 files changed, 111 insertions(+), 7 deletions(-) diff --git a/examples/vite-app/src/examples/Basic/index.tsx b/examples/vite-app/src/examples/Basic/index.tsx index f3f74dda..c157ce23 100644 --- a/examples/vite-app/src/examples/Basic/index.tsx +++ b/examples/vite-app/src/examples/Basic/index.tsx @@ -78,10 +78,6 @@ const BasicFlow = () => { ); }; - const deleteElements = () => { - instance.deleteSelectedElements() - } - return ( { toggle classnames - ); diff --git a/examples/vite-app/src/examples/UseReactFlow/index.tsx b/examples/vite-app/src/examples/UseReactFlow/index.tsx index 7133cec9..87a610b5 100644 --- a/examples/vite-app/src/examples/UseReactFlow/index.tsx +++ b/examples/vite-app/src/examples/UseReactFlow/index.tsx @@ -43,6 +43,7 @@ const initialNodes: Node[] = [ const initialEdges: Edge[] = [ { id: 'e1-2', source: '1', target: '2', animated: true }, { id: 'e1-3', source: '1', target: '3' }, + // { id: 'e3-4', source: '3', target: '4' } ]; let id = 5; @@ -64,6 +65,8 @@ const UseZoomPanHelperFlow = () => { addEdges, getNodes, getEdges, + deleteSelectedElements, + deleteElements } = useReactFlow(); const onPaneClick = useCallback( @@ -111,7 +114,22 @@ const UseZoomPanHelperFlow = () => { console.log('edges', getEdges()); }, [getNodes, getEdges]); + const deleteSelectedElements1 = useCallback(() => { + const selectedNodes = nodes.filter(node => node.selected).map(node => node.id); + const selectedEdges = edges.filter(edge => edge.selected).map(edge => edge.id); + deleteElements(selectedNodes, selectedEdges); + }, [deleteElements, nodes, edges]) + + const deleteSelectedElements2 = useCallback(() => { + deleteSelectedElements(); + }, [deleteSelectedElements]) + + const deleteSomeElements = useCallback(() => { + deleteElements(['2'],['e1-3']) + }, []) + useEffect(() => { + // called twice in strict mode of development mode addEdges({ id: 'e3-4', source: '3', target: '4' }); }, [addEdges]); @@ -137,6 +155,9 @@ const UseZoomPanHelperFlow = () => { + + + diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts index 3d563941..52d918dc 100644 --- a/packages/core/src/hooks/useGlobalKeyHandler.ts +++ b/packages/core/src/hooks/useGlobalKeyHandler.ts @@ -12,14 +12,23 @@ interface HookParams { export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { const store = useStoreApi(); - const { deleteSelectedElements } = useReactFlow(); + const { deleteElements } = useReactFlow(); + // const { deleteSelectedElements } = useReactFlow(); const deleteKeyPressed = useKeyPress(deleteKeyCode); const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); useEffect(() => { if (deleteKeyPressed) { - deleteSelectedElements(); + const { + nodeInternals, + edges, + } = store.getState(); + const nodes = Array.from(nodeInternals.values()); + const nodeIds = nodes.filter(node => node.selected).map(node => node.id); + const edgeIds = edges.filter(edge => edge.selected).map(edge => edge.id); + deleteElements(nodeIds, edgeIds); + store.setState({ nodesSelectionActive: false }); } }, [deleteKeyPressed]); diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index 76043a2e..0e1ed6bb 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -192,6 +192,81 @@ export default function useReactFlow(): ReactFlo } }, []); + const deleteElements = useCallback((nodeIds, edgeIds) => { + const { + nodeInternals, + edges, + hasDefaultNodes, + hasDefaultEdges, + onNodesDelete, + onEdgesDelete, + onNodesChange, + onEdgesChange, + } = store.getState(); + const nodes = Array.from(nodeInternals.values()); + const nodesToRemove = nodes.reduce((res, node) => { + const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode); + const deletable = typeof node.deletable === 'boolean' ? node.deletable : true; + if (deletable && (nodeIds.includes(node.id) || parentHit)) { + res.push(node); + } + + return res; + }, []); + const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true)); + const initialHitEdges = deletableEdges.filter((e) => edgeIds.includes(e.id)); + if (nodesToRemove || initialHitEdges) { + const connectedEdges = getConnectedEdges(nodesToRemove, deletableEdges); + const edgesToRemove = [...initialHitEdges, ...connectedEdges]; + const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => { + if (!res.includes(edge.id)) { + res.push(edge.id); + } + return res; + }, []); + + if (hasDefaultEdges || hasDefaultNodes) { + if (hasDefaultEdges) { + store.setState({ + edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)), + }); + } + + if (hasDefaultNodes) { + nodesToRemove.forEach((node) => { + nodeInternals.delete(node.id); + }); + + store.setState({ + nodeInternals: new Map(nodeInternals), + }); + } + } + + if (edgeIdsToRemove.length > 0) { + onEdgesDelete?.(edgesToRemove); + + if (onEdgesChange) { + onEdgesChange( + edgeIdsToRemove.map((id) => ({ + id, + type: 'remove', + })) + ); + } + } + + if (nodesToRemove.length > 0) { + onNodesDelete?.(nodesToRemove); + + if (onNodesChange) { + const nodeChanges: NodeChange[] = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' })); + onNodesChange(nodeChanges); + } + } + } + }, []); + return useMemo(() => { return { ...viewportHelper, @@ -205,6 +280,7 @@ export default function useReactFlow(): ReactFlo addEdges, toObject, deleteSelectedElements, + deleteElements, }; }, [ viewportHelper, @@ -218,5 +294,6 @@ export default function useReactFlow(): ReactFlo addEdges, toObject, deleteSelectedElements, + deleteElements, ]); } diff --git a/packages/core/src/types/instance.ts b/packages/core/src/types/instance.ts index 058da326..deffc86c 100644 --- a/packages/core/src/types/instance.ts +++ b/packages/core/src/types/instance.ts @@ -25,6 +25,7 @@ export namespace Instance { export type AddEdges = (payload: Edge[] | Edge) => void; export type ToObject = () => ReactFlowJsonObject; export type DeleteSelectedElements = () => void; + export type DeleteElements = (nodeIds: string[], edgeIds: string[]) => void } export type ReactFlowInstance = { @@ -37,6 +38,7 @@ export type ReactFlowInstance = { addEdges: Instance.AddEdges; getEdge: Instance.GetEdge; toObject: Instance.ToObject; + deleteElements: Instance.DeleteElements; deleteSelectedElements: Instance.DeleteSelectedElements; viewportInitialized: boolean; } & Omit; From b6ec8b70b20154a79c161462d80bd3d856a238da Mon Sep 17 00:00:00 2001 From: chang_su Date: Tue, 25 Oct 2022 14:43:44 +0800 Subject: [PATCH 05/25] chore: remove deleteSelectedElements method --- .../src/examples/UseReactFlow/index.tsx | 5 -- .../core/src/hooks/useGlobalKeyHandler.ts | 1 - packages/core/src/hooks/useReactFlow.ts | 80 ------------------- packages/core/src/types/instance.ts | 2 - 4 files changed, 88 deletions(-) diff --git a/examples/vite-app/src/examples/UseReactFlow/index.tsx b/examples/vite-app/src/examples/UseReactFlow/index.tsx index 87a610b5..eb83a88e 100644 --- a/examples/vite-app/src/examples/UseReactFlow/index.tsx +++ b/examples/vite-app/src/examples/UseReactFlow/index.tsx @@ -65,7 +65,6 @@ const UseZoomPanHelperFlow = () => { addEdges, getNodes, getEdges, - deleteSelectedElements, deleteElements } = useReactFlow(); @@ -120,10 +119,6 @@ const UseZoomPanHelperFlow = () => { deleteElements(selectedNodes, selectedEdges); }, [deleteElements, nodes, edges]) - const deleteSelectedElements2 = useCallback(() => { - deleteSelectedElements(); - }, [deleteSelectedElements]) - const deleteSomeElements = useCallback(() => { deleteElements(['2'],['e1-3']) }, []) diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts index 52d918dc..345864a0 100644 --- a/packages/core/src/hooks/useGlobalKeyHandler.ts +++ b/packages/core/src/hooks/useGlobalKeyHandler.ts @@ -13,7 +13,6 @@ interface HookParams { export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { const store = useStoreApi(); const { deleteElements } = useReactFlow(); - // const { deleteSelectedElements } = useReactFlow(); const deleteKeyPressed = useKeyPress(deleteKeyCode); const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode); diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index 0e1ed6bb..92480da2 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -114,84 +114,6 @@ export default function useReactFlow(): ReactFlo }; }, []); - const deleteSelectedElements = useCallback(() => { - const { - nodeInternals, - edges, - hasDefaultNodes, - hasDefaultEdges, - onNodesDelete, - onEdgesDelete, - onNodesChange, - onEdgesChange, - } = store.getState(); - const nodes = Array.from(nodeInternals.values()); - const nodesToRemove = nodes.reduce((res, node) => { - const parentSelected = !node.selected && node.parentNode && res.find((n) => n.id === node.parentNode); - const deletable = typeof node.deletable === 'boolean' ? node.deletable : true; - if (deletable && (node.selected || parentSelected)) { - res.push(node); - } - - return res; - }, []); - const deletableEdges = edges.filter((e) => (typeof e.deletable === 'boolean' ? e.deletable : true)); - const selectedEdges = deletableEdges.filter((e) => e.selected); - - if (nodesToRemove || selectedEdges) { - const connectedEdges = getConnectedEdges(nodesToRemove, deletableEdges); - const edgesToRemove = [...selectedEdges, ...connectedEdges]; - const edgeIdsToRemove = edgesToRemove.reduce((res, edge) => { - if (!res.includes(edge.id)) { - res.push(edge.id); - } - return res; - }, []); - - if (hasDefaultEdges || hasDefaultNodes) { - if (hasDefaultEdges) { - store.setState({ - edges: edges.filter((e) => !edgeIdsToRemove.includes(e.id)), - }); - } - - if (hasDefaultNodes) { - nodesToRemove.forEach((node) => { - nodeInternals.delete(node.id); - }); - - store.setState({ - nodeInternals: new Map(nodeInternals), - }); - } - } - - if (edgeIdsToRemove.length > 0) { - onEdgesDelete?.(edgesToRemove); - - if (onEdgesChange) { - onEdgesChange( - edgeIdsToRemove.map((id) => ({ - id, - type: 'remove', - })) - ); - } - } - - if (nodesToRemove.length > 0) { - onNodesDelete?.(nodesToRemove); - - if (onNodesChange) { - const nodeChanges: NodeChange[] = nodesToRemove.map((n) => ({ id: n.id, type: 'remove' })); - onNodesChange(nodeChanges); - } - } - - store.setState({ nodesSelectionActive: false }); - } - }, []); - const deleteElements = useCallback((nodeIds, edgeIds) => { const { nodeInternals, @@ -279,7 +201,6 @@ export default function useReactFlow(): ReactFlo addNodes, addEdges, toObject, - deleteSelectedElements, deleteElements, }; }, [ @@ -293,7 +214,6 @@ export default function useReactFlow(): ReactFlo addNodes, addEdges, toObject, - deleteSelectedElements, deleteElements, ]); } diff --git a/packages/core/src/types/instance.ts b/packages/core/src/types/instance.ts index deffc86c..733b5392 100644 --- a/packages/core/src/types/instance.ts +++ b/packages/core/src/types/instance.ts @@ -24,7 +24,6 @@ export namespace Instance { export type GetEdge = (id: string) => Edge | undefined; export type AddEdges = (payload: Edge[] | Edge) => void; export type ToObject = () => ReactFlowJsonObject; - export type DeleteSelectedElements = () => void; export type DeleteElements = (nodeIds: string[], edgeIds: string[]) => void } @@ -39,6 +38,5 @@ export type ReactFlowInstance = { getEdge: Instance.GetEdge; toObject: Instance.ToObject; deleteElements: Instance.DeleteElements; - deleteSelectedElements: Instance.DeleteSelectedElements; viewportInitialized: boolean; } & Omit; From a22229eb14a01e909c2c5f7fa9d8653d738f0ad9 Mon Sep 17 00:00:00 2001 From: chang_su Date: Tue, 25 Oct 2022 16:17:16 +0800 Subject: [PATCH 06/25] chore: update deleteElements param --- .../vite-app/src/examples/UseReactFlow/index.tsx | 14 ++++++-------- packages/core/src/hooks/useGlobalKeyHandler.ts | 6 +++--- packages/core/src/hooks/useReactFlow.ts | 4 +++- packages/core/src/types/instance.ts | 6 +++++- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/examples/vite-app/src/examples/UseReactFlow/index.tsx b/examples/vite-app/src/examples/UseReactFlow/index.tsx index eb83a88e..6fca9627 100644 --- a/examples/vite-app/src/examples/UseReactFlow/index.tsx +++ b/examples/vite-app/src/examples/UseReactFlow/index.tsx @@ -113,18 +113,17 @@ const UseZoomPanHelperFlow = () => { console.log('edges', getEdges()); }, [getNodes, getEdges]); - const deleteSelectedElements1 = useCallback(() => { - const selectedNodes = nodes.filter(node => node.selected).map(node => node.id); - const selectedEdges = edges.filter(edge => edge.selected).map(edge => edge.id); - deleteElements(selectedNodes, selectedEdges); + const deleteSelectedElements = useCallback(() => { + const selectedNodes = nodes.filter(node => node.selected); + const selectedEdges = edges.filter(edge => edge.selected); + deleteElements({ nodes: selectedNodes, edges: selectedEdges }); }, [deleteElements, nodes, edges]) const deleteSomeElements = useCallback(() => { - deleteElements(['2'],['e1-3']) + deleteElements({ nodes: [{ id: '2' }], edges: [{ id: 'e1-3' }] }) }, []) useEffect(() => { - // called twice in strict mode of development mode addEdges({ id: 'e3-4', source: '3', target: '4' }); }, [addEdges]); @@ -150,8 +149,7 @@ const UseZoomPanHelperFlow = () => { - - + diff --git a/packages/core/src/hooks/useGlobalKeyHandler.ts b/packages/core/src/hooks/useGlobalKeyHandler.ts index 2827d9cb..f2b206e7 100644 --- a/packages/core/src/hooks/useGlobalKeyHandler.ts +++ b/packages/core/src/hooks/useGlobalKeyHandler.ts @@ -20,9 +20,9 @@ export default ({ deleteKeyCode, multiSelectionKeyCode }: HookParams): void => { if (deleteKeyPressed) { const { nodeInternals, edges } = store.getState(); const nodes = Array.from(nodeInternals.values()); - const nodeIds = nodes.filter((node) => node.selected).map((node) => node.id); - const edgeIds = edges.filter((edge) => edge.selected).map((edge) => edge.id); - deleteElements(nodeIds, edgeIds); + const selectedNodes = nodes.filter((node) => node.selected); + const selectedEdges = edges.filter((edge) => edge.selected); + deleteElements({nodes: selectedNodes, edges: selectedEdges}); store.setState({ nodesSelectionActive: false }); } }, [deleteKeyPressed]); diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index 32f4ee86..77415b9b 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -114,7 +114,7 @@ export default function useReactFlow(): ReactFlo }; }, []); - const deleteElements = useCallback((nodeIds, edgeIds) => { + const deleteElements = useCallback(({ nodes: nodesDeleted, edges: edgesDeleted }) => { const { nodeInternals, edges, @@ -126,6 +126,8 @@ export default function useReactFlow(): ReactFlo onEdgesChange, } = store.getState(); const nodes = Array.from(nodeInternals.values()); + const nodeIds = (nodesDeleted || []).map((node) => node.id); + const edgeIds = (edgesDeleted || []).map((edge) => edge.id); const nodesToRemove = nodes.reduce((res, node) => { const parentHit = !nodeIds.includes(node.id) && node.parentNode && res.find((n) => n.id === node.parentNode); const deletable = typeof node.deletable === 'boolean' ? node.deletable : true; diff --git a/packages/core/src/types/instance.ts b/packages/core/src/types/instance.ts index 8548c9f3..0c7200d2 100644 --- a/packages/core/src/types/instance.ts +++ b/packages/core/src/types/instance.ts @@ -8,6 +8,10 @@ export type ReactFlowJsonObject = { viewport: Viewport; }; +export type DeleteElementsOptions = { + nodes?: (Partial & { id: Node['id'] })[], + edges?: (Partial & { id: Edge['id'] })[] +}; export namespace Instance { export type GetNodes = () => Node[]; export type SetNodes = ( @@ -22,7 +26,7 @@ export namespace Instance { export type GetEdge = (id: string) => Edge | undefined; export type AddEdges = (payload: Edge[] | Edge) => void; export type ToObject = () => ReactFlowJsonObject; - export type DeleteElements = (nodeIds: string[], edgeIds: string[]) => void + export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => void; } export type ReactFlowInstance = { From 322ea0ebedd527d2ffc15d209ce0c5a1ef06fa4c Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 31 Oct 2022 17:55:32 +0100 Subject: [PATCH 07/25] feat(interactive-minimap): add zoom and pan --- .../src/examples/InteractiveMinimap/index.tsx | 10 +-- .../core/src/container/ZoomPane/index.tsx | 8 +- packages/interactive-minimap/src/MiniMap.tsx | 82 +++++++++++++++++-- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx index f80b2971..66cab6a0 100644 --- a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx +++ b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx @@ -19,25 +19,25 @@ const initialNodes: Node[] = [ id: '1', type: 'input', data: { label: 'Node 1' }, - position: { x: 250, y: 5 }, + position: { x: 0, y: 0 }, className: 'light', }, { id: '2', data: { label: 'Node 2' }, - position: { x: 100, y: 100 }, + position: { x: 0, y: 200 }, className: 'light', }, { id: '3', data: { label: 'Node 3' }, - position: { x: 400, y: 100 }, + position: { x: 200, y: 200 }, className: 'light', }, { id: '4', data: { label: 'Node 4' }, - position: { x: 400, y: 200 }, + position: { x: 200, y: 400 }, className: 'light', }, ]; @@ -88,9 +88,9 @@ const BasicFlow = () => { className="react-flow-basic-example" minZoom={0.2} maxZoom={4} - fitView defaultEdgeOptions={defaultEdgeOptions} selectNodesOnDrag={false} + fitView > diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index cde4ec47..a5783a7a 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -173,9 +173,12 @@ const ZoomPane = ({ useEffect(() => { if (d3Zoom) { d3Zoom.on('start', (event: D3ZoomEvent) => { + if (!event.sourceEvent) { + return null; + } + const { onViewportChangeStart } = store.getState(); isZoomingOrPanning.current = true; - if (event.sourceEvent?.type === 'mousedown') { store.setState({ paneDragging: true }); } @@ -194,6 +197,9 @@ const ZoomPane = ({ useEffect(() => { if (d3Zoom) { d3Zoom.on('end', (event: D3ZoomEvent) => { + if (!event.sourceEvent) { + return null; + } const { onViewportChangeEnd } = store.getState(); isZoomingOrPanning.current = false; diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx index b7ef604c..e5a7927e 100644 --- a/packages/interactive-minimap/src/MiniMap.tsx +++ b/packages/interactive-minimap/src/MiniMap.tsx @@ -2,9 +2,19 @@ import { memo, useEffect, useRef } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; -import { zoom, D3ZoomEvent } from 'd3-zoom'; +import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; import { select } from 'd3-selection'; -import { useStore, getRectOfNodes, ReactFlowState, Rect, Panel, getBoundsOfRects } from '@reactflow/core'; +import { + useStore, + getRectOfNodes, + ReactFlowState, + Rect, + Panel, + getBoundsOfRects, + useStoreApi, + useReactFlow, + XYPosition, +} from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; @@ -46,7 +56,9 @@ function MiniMap({ maskColor = 'rgb(240, 242, 243, 0.7)', position = 'bottom-right', }: MiniMapProps) { + const store = useStoreApi(); const svg = useRef(null); + const initialized = useRef(false); const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); const elementWidth = (style?.width as number) ?? defaultWidth; const elementHeight = (style?.height as number) ?? defaultHeight; @@ -65,15 +77,69 @@ function MiniMap({ const height = viewHeight + offset * 2; const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; + const { setCenter } = useReactFlow(); + const startTransform = useRef({ x: 0, y: 0 }); + const viewScaleRef = useRef(0); + const viewScaleWRef = useRef(0); + const viewScaleHRef = useRef(0); + + viewScaleRef.current = viewScale; + viewScaleWRef.current = scaledWidth; + viewScaleHRef.current = scaledHeight; useEffect(() => { - const d3ZoomInstance = zoom(); - const selection = select(svg.current as Element).call(d3ZoomInstance); + if (!initialized.current && svg.current) { + const bounds = svg.current.getBoundingClientRect() || { left: 0, top: 0 }; - d3ZoomInstance.on('zoom', (event: D3ZoomEvent) => { - console.log(event); - }); - }, []); + const selection = select(svg.current as Element); + + const zoomHandler = zoom() + .on('start', (event: D3ZoomEvent) => { + const { transform } = store.getState(); + + const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; + const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; + + startTransform.current = { + x: px - transform[0] / transform[2], + y: py - transform[1] / transform[2], + }; + }) + .on('zoom.wheel', (event: D3ZoomEvent) => { + const { transform, d3Selection, d3Zoom } = store.getState(); + + if (event.sourceEvent.type !== 'wheel' || !d3Selection || !d3Zoom) { + return; + } + + const pinchDelta = + -event.sourceEvent.deltaY * + (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * + 10; + const zoom = transform[2] * Math.pow(2, pinchDelta); + d3Zoom.scaleTo(d3Selection, zoom, [startTransform.current.x, startTransform.current.y]); + }) + .on('zoom', (event: D3ZoomEvent) => { + if (event.sourceEvent.type !== 'mousemove') { + return; + } + + const { transform, d3Selection, d3Zoom } = store.getState(); + const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; + const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; + + const position = { + x: (px - startTransform.current.x) * transform[2], + y: (py - startTransform.current.y) * transform[2], + }; + + const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + d3Zoom!.transform(d3Selection!, nextTransform); + }); + selection.call(zoomHandler); + initialized.current = true; + } + }, [setCenter]); return ( From f71ee4223f78c34633ced1cef7028d58c11d210e Mon Sep 17 00:00:00 2001 From: moklick Date: Mon, 31 Oct 2022 20:11:03 +0100 Subject: [PATCH 08/25] fix(interactive-mini): add deps --- packages/interactive-minimap/package.json | 2 ++ pnpm-lock.yaml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/packages/interactive-minimap/package.json b/packages/interactive-minimap/package.json index e5409e1a..d3b50f6f 100644 --- a/packages/interactive-minimap/package.json +++ b/packages/interactive-minimap/package.json @@ -39,6 +39,8 @@ "@babel/runtime": "^7.18.9", "@reactflow/core": "workspace:*", "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", "zustand": "^4.1.1" }, "peerDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aaf55e75..ca0b28de 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -190,6 +190,8 @@ importers: '@types/node': ^18.7.16 '@types/react': ^18.0.19 classcat: ^5.0.3 + d3-selection: ^3.0.0 + d3-zoom: ^3.0.0 react: ^18.2.0 typescript: ^4.8.3 zustand: ^4.1.1 @@ -197,6 +199,8 @@ importers: '@babel/runtime': registry.npmjs.org/@babel/runtime/7.19.0 '@reactflow/core': link:../core classcat: registry.npmjs.org/classcat/5.0.4 + d3-selection: registry.npmjs.org/d3-selection/3.0.0 + d3-zoom: registry.npmjs.org/d3-zoom/3.0.0 zustand: registry.npmjs.org/zustand/4.1.1_react@18.2.0 devDependencies: '@reactflow/eslint-config': link:../../tooling/eslint-config From 6eec913c89159b441e0c5044c258328426ffc978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=B6ller?= Date: Tue, 1 Nov 2022 13:37:53 +0100 Subject: [PATCH 09/25] feat(interactive-minimap): add type defs for d3 packages --- packages/interactive-minimap/package.json | 2 ++ pnpm-lock.yaml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/packages/interactive-minimap/package.json b/packages/interactive-minimap/package.json index d3b50f6f..8dc4a82a 100644 --- a/packages/interactive-minimap/package.json +++ b/packages/interactive-minimap/package.json @@ -38,6 +38,8 @@ "dependencies": { "@babel/runtime": "^7.18.9", "@reactflow/core": "workspace:*", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", "classcat": "^5.0.3", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca0b28de..91f51a8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -187,6 +187,8 @@ importers: '@reactflow/eslint-config': workspace:^0.0.0 '@reactflow/rollup-config': workspace:* '@reactflow/tsconfig': workspace:* + '@types/d3-selection': ^3.0.3 + '@types/d3-zoom': ^3.0.1 '@types/node': ^18.7.16 '@types/react': ^18.0.19 classcat: ^5.0.3 @@ -198,6 +200,8 @@ importers: dependencies: '@babel/runtime': registry.npmjs.org/@babel/runtime/7.19.0 '@reactflow/core': link:../core + '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 + '@types/d3-zoom': registry.npmjs.org/@types/d3-zoom/3.0.1 classcat: registry.npmjs.org/classcat/5.0.4 d3-selection: registry.npmjs.org/d3-selection/3.0.0 d3-zoom: registry.npmjs.org/d3-zoom/3.0.0 From 4098ced7d80ce7e2507754de169a1bdbc89d680a Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 1 Nov 2022 14:12:31 +0100 Subject: [PATCH 10/25] refactor(interactive-minimap): drag mask instead of pane --- .../src/examples/InteractiveMinimap/index.tsx | 57 ++++-- packages/interactive-minimap/src/MiniMap.tsx | 64 +++--- .../interactive-minimap/src/MiniMapDrag.tsx | 187 ++++++++++++++++++ 3 files changed, 261 insertions(+), 47 deletions(-) create mode 100644 packages/interactive-minimap/src/MiniMapDrag.tsx diff --git a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx index 66cab6a0..cde9b722 100644 --- a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx +++ b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx @@ -17,35 +17,70 @@ const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const initialNodes: Node[] = [ { id: '1', - type: 'input', data: { label: 'Node 1' }, position: { x: 0, y: 0 }, - className: 'light', }, { id: '2', data: { label: 'Node 2' }, position: { x: 0, y: 200 }, - className: 'light', }, { id: '3', data: { label: 'Node 3' }, - position: { x: 200, y: 200 }, - className: 'light', + position: { x: 200, y: 0 }, }, + { id: '4', data: { label: 'Node 4' }, - position: { x: 200, y: 400 }, - className: 'light', + position: { x: 1000, y: 0 }, + }, + { + id: '5', + data: { label: 'Node 5' }, + position: { x: 1000, y: 200 }, + }, + { + id: '6', + data: { label: 'Node 6' }, + position: { x: 800, y: 0 }, + }, + + { + id: '7', + data: { label: 'Node 4' }, + position: { x: 0, y: 1000 }, + }, + { + id: '8', + data: { label: 'Node 5' }, + position: { x: 0, y: 800 }, + }, + { + id: '9', + data: { label: 'Node 6' }, + position: { x: 200, y: 1000 }, + }, + + { + id: '10', + data: { label: 'Node 4' }, + position: { x: 1000, y: 1000 }, + }, + { + id: '11', + data: { label: 'Node 5' }, + position: { x: 800, y: 1000 }, + }, + { + id: '12', + data: { label: 'Node 6' }, + position: { x: 1000, y: 800 }, }, ]; -const initialEdges: Edge[] = [ - { id: 'e1-2', source: '1', target: '2', animated: true }, - { id: 'e1-3', source: '1', target: '3' }, -]; +const initialEdges: Edge[] = []; const defaultEdgeOptions = { zIndex: 0 }; diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx index e5a7927e..a8cbd404 100644 --- a/packages/interactive-minimap/src/MiniMap.tsx +++ b/packages/interactive-minimap/src/MiniMap.tsx @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, useEffect, useRef } from 'react'; +import { memo, MouseEvent, useEffect, useRef } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; -import { select } from 'd3-selection'; +import { select, pointer } from 'd3-selection'; import { useStore, getRectOfNodes, @@ -13,7 +13,6 @@ import { getBoundsOfRects, useStoreApi, useReactFlow, - XYPosition, } from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; @@ -38,6 +37,8 @@ const selector = (s: ReactFlowState) => { viewBB, boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes), viewBB) : viewBB, rfId: s.rfId, + width: s.width, + height: s.height, }; }; @@ -49,17 +50,16 @@ function MiniMap({ style, className, nodeStrokeColor = '#555', - nodeColor = '#fff', + nodeColor = '#999', nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth = 2, - maskColor = 'rgb(240, 242, 243, 0.7)', + maskColor = 'rgb(200, 200, 200, 0.9)', position = 'bottom-right', }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); - const initialized = useRef(false); - const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); + const { boundingRect, viewBB, nodes, rfId, width: w, height: h } = useStore(selector, shallow); const elementWidth = (style?.width as number) ?? defaultWidth; const elementHeight = (style?.height as number) ?? defaultHeight; const nodeColorFunc = getAttrFunction(nodeColor); @@ -77,33 +77,27 @@ function MiniMap({ const height = viewHeight + offset * 2; const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; - const { setCenter } = useReactFlow(); - const startTransform = useRef({ x: 0, y: 0 }); + const { setCenter, setViewport, project } = useReactFlow(); + const startTransform = useRef<[number, number]>([0, 0]); const viewScaleRef = useRef(0); - const viewScaleWRef = useRef(0); - const viewScaleHRef = useRef(0); - viewScaleRef.current = viewScale; - viewScaleWRef.current = scaledWidth; - viewScaleHRef.current = scaledHeight; + viewScaleRef.current = Math.max(w / elementWidth, h / elementHeight); + + const onClick = (event: MouseEvent) => { + const rfCoord = pointer(event); + + console.log(rfCoord); + }; useEffect(() => { - if (!initialized.current && svg.current) { - const bounds = svg.current.getBoundingClientRect() || { left: 0, top: 0 }; - + if (svg.current) { const selection = select(svg.current as Element); const zoomHandler = zoom() .on('start', (event: D3ZoomEvent) => { - const { transform } = store.getState(); + const rfCoord = pointer(event); - const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; - const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; - - startTransform.current = { - x: px - transform[0] / transform[2], - y: py - transform[1] / transform[2], - }; + startTransform.current = rfCoord; }) .on('zoom.wheel', (event: D3ZoomEvent) => { const { transform, d3Selection, d3Zoom } = store.getState(); @@ -117,29 +111,26 @@ function MiniMap({ (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * 10; const zoom = transform[2] * Math.pow(2, pinchDelta); - d3Zoom.scaleTo(d3Selection, zoom, [startTransform.current.x, startTransform.current.y]); + + d3Zoom.scaleTo(d3Selection, zoom, startTransform.current); }) .on('zoom', (event: D3ZoomEvent) => { - if (event.sourceEvent.type !== 'mousemove') { + const { transform, d3Selection, d3Zoom } = store.getState(); + if (event.sourceEvent.type !== 'mousemove' || !d3Selection || !d3Zoom) { return; } - const { transform, d3Selection, d3Zoom } = store.getState(); - const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; - const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; - const position = { - x: (px - startTransform.current.x) * transform[2], - y: (py - startTransform.current.y) * transform[2], + x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * transform[2], + y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * transform[2], }; const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); - d3Zoom!.transform(d3Selection!, nextTransform); + d3Zoom.transform(d3Selection, nextTransform); }); selection.call(zoomHandler); - initialized.current = true; } - }, [setCenter]); + }, [setCenter, setViewport, project]); return ( @@ -150,6 +141,7 @@ function MiniMap({ role="img" aria-labelledby={labelledBy} ref={svg} + onClick={onClick} > React Flow mini map {nodes.map((node) => { diff --git a/packages/interactive-minimap/src/MiniMapDrag.tsx b/packages/interactive-minimap/src/MiniMapDrag.tsx new file mode 100644 index 00000000..e5a7927e --- /dev/null +++ b/packages/interactive-minimap/src/MiniMapDrag.tsx @@ -0,0 +1,187 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { memo, useEffect, useRef } from 'react'; +import cc from 'classcat'; +import shallow from 'zustand/shallow'; +import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; +import { select } from 'd3-selection'; +import { + useStore, + getRectOfNodes, + ReactFlowState, + Rect, + Panel, + getBoundsOfRects, + useStoreApi, + useReactFlow, + XYPosition, +} from '@reactflow/core'; + +import MiniMapNode from './MiniMapNode'; +import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; + +declare const window: any; + +const defaultWidth = 200; +const defaultHeight = 150; + +const selector = (s: ReactFlowState) => { + const nodes = Array.from(s.nodeInternals.values()); + const viewBB: Rect = { + x: -s.transform[0] / s.transform[2], + y: -s.transform[1] / s.transform[2], + width: s.width / s.transform[2], + height: s.height / s.transform[2], + }; + + return { + nodes: nodes.filter((node) => !node.hidden && node.width && node.height), + viewBB, + boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes), viewBB) : viewBB, + rfId: s.rfId, + }; +}; + +const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); + +const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; + +function MiniMap({ + style, + className, + nodeStrokeColor = '#555', + nodeColor = '#fff', + nodeClassName = '', + nodeBorderRadius = 5, + nodeStrokeWidth = 2, + maskColor = 'rgb(240, 242, 243, 0.7)', + position = 'bottom-right', +}: MiniMapProps) { + const store = useStoreApi(); + const svg = useRef(null); + const initialized = useRef(false); + const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); + const elementWidth = (style?.width as number) ?? defaultWidth; + const elementHeight = (style?.height as number) ?? defaultHeight; + const nodeColorFunc = getAttrFunction(nodeColor); + const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); + const nodeClassNameFunc = getAttrFunction(nodeClassName); + const scaledWidth = boundingRect.width / elementWidth; + const scaledHeight = boundingRect.height / elementHeight; + const viewScale = Math.max(scaledWidth, scaledHeight); + const viewWidth = viewScale * elementWidth; + const viewHeight = viewScale * elementHeight; + const offset = 5 * viewScale; + const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; + const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; + const width = viewWidth + offset * 2; + const height = viewHeight + offset * 2; + const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; + const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; + const { setCenter } = useReactFlow(); + const startTransform = useRef({ x: 0, y: 0 }); + const viewScaleRef = useRef(0); + const viewScaleWRef = useRef(0); + const viewScaleHRef = useRef(0); + + viewScaleRef.current = viewScale; + viewScaleWRef.current = scaledWidth; + viewScaleHRef.current = scaledHeight; + + useEffect(() => { + if (!initialized.current && svg.current) { + const bounds = svg.current.getBoundingClientRect() || { left: 0, top: 0 }; + + const selection = select(svg.current as Element); + + const zoomHandler = zoom() + .on('start', (event: D3ZoomEvent) => { + const { transform } = store.getState(); + + const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; + const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; + + startTransform.current = { + x: px - transform[0] / transform[2], + y: py - transform[1] / transform[2], + }; + }) + .on('zoom.wheel', (event: D3ZoomEvent) => { + const { transform, d3Selection, d3Zoom } = store.getState(); + + if (event.sourceEvent.type !== 'wheel' || !d3Selection || !d3Zoom) { + return; + } + + const pinchDelta = + -event.sourceEvent.deltaY * + (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * + 10; + const zoom = transform[2] * Math.pow(2, pinchDelta); + d3Zoom.scaleTo(d3Selection, zoom, [startTransform.current.x, startTransform.current.y]); + }) + .on('zoom', (event: D3ZoomEvent) => { + if (event.sourceEvent.type !== 'mousemove') { + return; + } + + const { transform, d3Selection, d3Zoom } = store.getState(); + const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; + const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; + + const position = { + x: (px - startTransform.current.x) * transform[2], + y: (py - startTransform.current.y) * transform[2], + }; + + const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + d3Zoom!.transform(d3Selection!, nextTransform); + }); + selection.call(zoomHandler); + initialized.current = true; + } + }, [setCenter]); + + return ( + + + React Flow mini map + {nodes.map((node) => { + return ( + + ); + })} + + + + ); +} + +MiniMap.displayName = 'MiniMap'; + +export default memo(MiniMap); From b4658d467686305d5179f2e26943f5c90d0b5c1f Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 1 Nov 2022 16:10:23 +0100 Subject: [PATCH 11/25] chore(interactive-minimap): cleanup --- .../src/examples/InteractiveMinimap/index.tsx | 9 ++++- packages/interactive-minimap/src/MiniMap.tsx | 39 +++++++------------ packages/interactive-minimap/src/types.ts | 9 +++-- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx index cde9b722..fa66d209 100644 --- a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx +++ b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx @@ -1,4 +1,4 @@ -import { MouseEvent } from 'react'; +import { MouseEvent, useCallback } from 'react'; import ReactFlow, { Background, BackgroundVariant, @@ -7,6 +7,7 @@ import ReactFlow, { Node, Edge, useReactFlow, + XYPosition, } from 'reactflow'; import { MiniMap } from '@reactflow/interactive-minimap'; @@ -113,6 +114,10 @@ const BasicFlow = () => { ); }; + const onMiniMapClick = useCallback((event: MouseEvent, pos: XYPosition) => { + console.log(pos); + }, []); + return ( { fitView > - +
diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx index a8cbd404..d8c8e77e 100644 --- a/packages/interactive-minimap/src/MiniMap.tsx +++ b/packages/interactive-minimap/src/MiniMap.tsx @@ -1,19 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, MouseEvent, useEffect, useRef } from 'react'; +import { memo, useEffect, useRef, MouseEvent } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; import { select, pointer } from 'd3-selection'; -import { - useStore, - getRectOfNodes, - ReactFlowState, - Rect, - Panel, - getBoundsOfRects, - useStoreApi, - useReactFlow, -} from '@reactflow/core'; + +import { useStore, getRectOfNodes, ReactFlowState, Rect, Panel, getBoundsOfRects, useStoreApi } from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; @@ -56,6 +48,7 @@ function MiniMap({ nodeStrokeWidth = 2, maskColor = 'rgb(200, 200, 200, 0.9)', position = 'bottom-right', + onClick, }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); @@ -77,16 +70,13 @@ function MiniMap({ const height = viewHeight + offset * 2; const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; - const { setCenter, setViewport, project } = useReactFlow(); - const startTransform = useRef<[number, number]>([0, 0]); const viewScaleRef = useRef(0); viewScaleRef.current = Math.max(w / elementWidth, h / elementHeight); - const onClick = (event: MouseEvent) => { + const onSvgClick = (event: MouseEvent) => { const rfCoord = pointer(event); - - console.log(rfCoord); + onClick?.(event, { x: rfCoord[0], y: rfCoord[1] }); }; useEffect(() => { @@ -94,11 +84,6 @@ function MiniMap({ const selection = select(svg.current as Element); const zoomHandler = zoom() - .on('start', (event: D3ZoomEvent) => { - const rfCoord = pointer(event); - - startTransform.current = rfCoord; - }) .on('zoom.wheel', (event: D3ZoomEvent) => { const { transform, d3Selection, d3Zoom } = store.getState(); @@ -112,25 +97,29 @@ function MiniMap({ 10; const zoom = transform[2] * Math.pow(2, pinchDelta); - d3Zoom.scaleTo(d3Selection, zoom, startTransform.current); + d3Zoom.scaleTo(d3Selection, zoom); }) .on('zoom', (event: D3ZoomEvent) => { const { transform, d3Selection, d3Zoom } = store.getState(); + if (event.sourceEvent.type !== 'mousemove' || !d3Selection || !d3Zoom) { return; } + let nextTransform = null; + const position = { x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * transform[2], y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * transform[2], }; - const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + d3Zoom.transform(d3Selection, nextTransform); }); selection.call(zoomHandler); } - }, [setCenter, setViewport, project]); + }, []); return ( @@ -141,7 +130,7 @@ function MiniMap({ role="img" aria-labelledby={labelledBy} ref={svg} - onClick={onClick} + onClick={onSvgClick} > React Flow mini map {nodes.map((node) => { diff --git a/packages/interactive-minimap/src/types.ts b/packages/interactive-minimap/src/types.ts index 5f82824d..aff2aeab 100644 --- a/packages/interactive-minimap/src/types.ts +++ b/packages/interactive-minimap/src/types.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { HTMLAttributes } from 'react'; -import { Node, PanelPosition } from '@reactflow/core'; +import { HTMLAttributes, MouseEvent } from 'react'; +import { Node, PanelPosition, XYPosition } from '@reactflow/core'; export type GetMiniMapNodeAttribute = (node: Node) => string; -export interface MiniMapProps extends HTMLAttributes { +export type MiniMapProps = Omit, 'onClick'> & { nodeColor?: string | GetMiniMapNodeAttribute; nodeStrokeColor?: string | GetMiniMapNodeAttribute; nodeClassName?: string | GetMiniMapNodeAttribute; @@ -12,4 +12,5 @@ export interface MiniMapProps extends HTMLAttributes void; +}; From d650609cd9042802af2f7eeb51966758996f5fd1 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 1 Nov 2022 17:27:18 +0100 Subject: [PATCH 12/25] chore(interactive-minimap): remove unused file --- packages/interactive-minimap/src/MiniMap.tsx | 12 +- .../interactive-minimap/src/MiniMapDrag.tsx | 187 ------------------ 2 files changed, 4 insertions(+), 195 deletions(-) delete mode 100644 packages/interactive-minimap/src/MiniMapDrag.tsx diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx index d8c8e77e..57444d62 100644 --- a/packages/interactive-minimap/src/MiniMap.tsx +++ b/packages/interactive-minimap/src/MiniMap.tsx @@ -5,7 +5,7 @@ import shallow from 'zustand/shallow'; import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; import { select, pointer } from 'd3-selection'; -import { useStore, getRectOfNodes, ReactFlowState, Rect, Panel, getBoundsOfRects, useStoreApi } from '@reactflow/core'; +import { useStore, getRectOfNodes, Panel, getBoundsOfRects, useStoreApi, ReactFlowState, Rect } from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; @@ -29,8 +29,6 @@ const selector = (s: ReactFlowState) => { viewBB, boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes), viewBB) : viewBB, rfId: s.rfId, - width: s.width, - height: s.height, }; }; @@ -52,7 +50,7 @@ function MiniMap({ }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); - const { boundingRect, viewBB, nodes, rfId, width: w, height: h } = useStore(selector, shallow); + const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); const elementWidth = (style?.width as number) ?? defaultWidth; const elementHeight = (style?.height as number) ?? defaultHeight; const nodeColorFunc = getAttrFunction(nodeColor); @@ -72,7 +70,7 @@ function MiniMap({ const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; const viewScaleRef = useRef(0); - viewScaleRef.current = Math.max(w / elementWidth, h / elementHeight); + viewScaleRef.current = viewScale; const onSvgClick = (event: MouseEvent) => { const rfCoord = pointer(event); @@ -106,14 +104,12 @@ function MiniMap({ return; } - let nextTransform = null; - const position = { x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * transform[2], y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * transform[2], }; - nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); d3Zoom.transform(d3Selection, nextTransform); }); diff --git a/packages/interactive-minimap/src/MiniMapDrag.tsx b/packages/interactive-minimap/src/MiniMapDrag.tsx deleted file mode 100644 index e5a7927e..00000000 --- a/packages/interactive-minimap/src/MiniMapDrag.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, useEffect, useRef } from 'react'; -import cc from 'classcat'; -import shallow from 'zustand/shallow'; -import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; -import { select } from 'd3-selection'; -import { - useStore, - getRectOfNodes, - ReactFlowState, - Rect, - Panel, - getBoundsOfRects, - useStoreApi, - useReactFlow, - XYPosition, -} from '@reactflow/core'; - -import MiniMapNode from './MiniMapNode'; -import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; - -declare const window: any; - -const defaultWidth = 200; -const defaultHeight = 150; - -const selector = (s: ReactFlowState) => { - const nodes = Array.from(s.nodeInternals.values()); - const viewBB: Rect = { - x: -s.transform[0] / s.transform[2], - y: -s.transform[1] / s.transform[2], - width: s.width / s.transform[2], - height: s.height / s.transform[2], - }; - - return { - nodes: nodes.filter((node) => !node.hidden && node.width && node.height), - viewBB, - boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes), viewBB) : viewBB, - rfId: s.rfId, - }; -}; - -const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); - -const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; - -function MiniMap({ - style, - className, - nodeStrokeColor = '#555', - nodeColor = '#fff', - nodeClassName = '', - nodeBorderRadius = 5, - nodeStrokeWidth = 2, - maskColor = 'rgb(240, 242, 243, 0.7)', - position = 'bottom-right', -}: MiniMapProps) { - const store = useStoreApi(); - const svg = useRef(null); - const initialized = useRef(false); - const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); - const elementWidth = (style?.width as number) ?? defaultWidth; - const elementHeight = (style?.height as number) ?? defaultHeight; - const nodeColorFunc = getAttrFunction(nodeColor); - const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); - const nodeClassNameFunc = getAttrFunction(nodeClassName); - const scaledWidth = boundingRect.width / elementWidth; - const scaledHeight = boundingRect.height / elementHeight; - const viewScale = Math.max(scaledWidth, scaledHeight); - const viewWidth = viewScale * elementWidth; - const viewHeight = viewScale * elementHeight; - const offset = 5 * viewScale; - const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; - const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; - const width = viewWidth + offset * 2; - const height = viewHeight + offset * 2; - const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; - const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; - const { setCenter } = useReactFlow(); - const startTransform = useRef({ x: 0, y: 0 }); - const viewScaleRef = useRef(0); - const viewScaleWRef = useRef(0); - const viewScaleHRef = useRef(0); - - viewScaleRef.current = viewScale; - viewScaleWRef.current = scaledWidth; - viewScaleHRef.current = scaledHeight; - - useEffect(() => { - if (!initialized.current && svg.current) { - const bounds = svg.current.getBoundingClientRect() || { left: 0, top: 0 }; - - const selection = select(svg.current as Element); - - const zoomHandler = zoom() - .on('start', (event: D3ZoomEvent) => { - const { transform } = store.getState(); - - const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; - const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; - - startTransform.current = { - x: px - transform[0] / transform[2], - y: py - transform[1] / transform[2], - }; - }) - .on('zoom.wheel', (event: D3ZoomEvent) => { - const { transform, d3Selection, d3Zoom } = store.getState(); - - if (event.sourceEvent.type !== 'wheel' || !d3Selection || !d3Zoom) { - return; - } - - const pinchDelta = - -event.sourceEvent.deltaY * - (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * - 10; - const zoom = transform[2] * Math.pow(2, pinchDelta); - d3Zoom.scaleTo(d3Selection, zoom, [startTransform.current.x, startTransform.current.y]); - }) - .on('zoom', (event: D3ZoomEvent) => { - if (event.sourceEvent.type !== 'mousemove') { - return; - } - - const { transform, d3Selection, d3Zoom } = store.getState(); - const px = (event.sourceEvent.clientX - bounds.left) * viewScaleRef.current; - const py = (event.sourceEvent.clientY - bounds.top) * viewScaleRef.current; - - const position = { - x: (px - startTransform.current.x) * transform[2], - y: (py - startTransform.current.y) * transform[2], - }; - - const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); - d3Zoom!.transform(d3Selection!, nextTransform); - }); - selection.call(zoomHandler); - initialized.current = true; - } - }, [setCenter]); - - return ( - - - React Flow mini map - {nodes.map((node) => { - return ( - - ); - })} - - - - ); -} - -MiniMap.displayName = 'MiniMap'; - -export default memo(MiniMap); From 0f855308ccb6700b64b8855091cd3a8867f2677b Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 1 Nov 2022 18:04:29 +0100 Subject: [PATCH 13/25] feat(interactive-minimap): add node click handler --- .../src/examples/InteractiveMinimap/index.tsx | 6 +++++- packages/interactive-minimap/src/MiniMap.tsx | 14 ++++++++++++++ packages/interactive-minimap/src/MiniMapNode.tsx | 7 ++++++- packages/interactive-minimap/src/types.ts | 1 + 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx index fa66d209..db8d754f 100644 --- a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx +++ b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx @@ -118,6 +118,10 @@ const BasicFlow = () => { console.log(pos); }, []); + const onMiniMapNodeClick = useCallback((event: MouseEvent, node: Node) => { + console.log(node); + }, []); + return ( { fitView > - +
diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx index 57444d62..46b0ec9f 100644 --- a/packages/interactive-minimap/src/MiniMap.tsx +++ b/packages/interactive-minimap/src/MiniMap.tsx @@ -47,6 +47,7 @@ function MiniMap({ maskColor = 'rgb(200, 200, 200, 0.9)', position = 'bottom-right', onClick, + onNodeClick, }: MiniMapProps) { const store = useStoreApi(); const svg = useRef(null); @@ -114,9 +115,20 @@ function MiniMap({ d3Zoom.transform(d3Selection, nextTransform); }); selection.call(zoomHandler); + + return () => { + selection.on('.zoom', null); + }; } }, []); + const onSvgNodeClick = onNodeClick + ? (event: MouseEvent, nodeId: string) => { + const node = store.getState().nodeInternals.get(nodeId)!; + onNodeClick(event, node); + } + : undefined; + return ( ); })} diff --git a/packages/interactive-minimap/src/MiniMapNode.tsx b/packages/interactive-minimap/src/MiniMapNode.tsx index 3ea3bd33..15a5ba3f 100644 --- a/packages/interactive-minimap/src/MiniMapNode.tsx +++ b/packages/interactive-minimap/src/MiniMapNode.tsx @@ -1,7 +1,8 @@ -import { memo, CSSProperties } from 'react'; +import { memo, CSSProperties, MouseEvent } from 'react'; import cc from 'classcat'; interface MiniMapNodeProps { + id: string; x: number; y: number; width: number; @@ -13,9 +14,11 @@ interface MiniMapNodeProps { strokeColor: string; strokeWidth: number; style?: CSSProperties; + onClick?: (event: MouseEvent, id: string) => void; } const MiniMapNode = ({ + id, x, y, width, @@ -27,6 +30,7 @@ const MiniMapNode = ({ className, borderRadius, shapeRendering, + onClick, }: MiniMapNodeProps) => { const { background, backgroundColor } = style || {}; const fill = (color || background || backgroundColor) as string; @@ -44,6 +48,7 @@ const MiniMapNode = ({ stroke={strokeColor} strokeWidth={strokeWidth} shapeRendering={shapeRendering} + onClick={onClick ? (event) => onClick(event, id) : undefined} /> ); }; diff --git a/packages/interactive-minimap/src/types.ts b/packages/interactive-minimap/src/types.ts index aff2aeab..80534fc3 100644 --- a/packages/interactive-minimap/src/types.ts +++ b/packages/interactive-minimap/src/types.ts @@ -13,4 +13,5 @@ export type MiniMapProps = Omit, ' maskColor?: string; position?: PanelPosition; onClick?: (event: MouseEvent, position: XYPosition) => void; + onNodeClick?: (event: MouseEvent, node: Node) => void; }; From a96044b2a2a2005c0deda3f71e79633b5cd3eca8 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 1 Nov 2022 22:45:08 +0100 Subject: [PATCH 14/25] feat(minimap): add pannable and zoomable props, remove interactive-minimap package --- .../src/examples/InteractiveMinimap/index.tsx | 4 +- .../src/examples/NestedNodes/index.tsx | 2 +- packages/interactive-minimap/.eslintrc.js | 4 - packages/interactive-minimap/CHANGELOG.md | 42 ----- packages/interactive-minimap/README.md | 10 - packages/interactive-minimap/package.json | 69 ------- packages/interactive-minimap/src/MiniMap.tsx | 178 ------------------ .../interactive-minimap/src/MiniMapNode.tsx | 58 ------ packages/interactive-minimap/src/index.tsx | 2 - packages/interactive-minimap/src/style.css | 3 - packages/interactive-minimap/src/types.ts | 17 -- packages/interactive-minimap/tsconfig.json | 6 - packages/minimap/package.json | 4 + packages/minimap/src/MiniMap.tsx | 80 +++++++- packages/minimap/src/MiniMapNode.tsx | 7 +- packages/minimap/src/types.ts | 10 +- pnpm-lock.yaml | 8 + 17 files changed, 106 insertions(+), 398 deletions(-) delete mode 100644 packages/interactive-minimap/.eslintrc.js delete mode 100644 packages/interactive-minimap/CHANGELOG.md delete mode 100644 packages/interactive-minimap/README.md delete mode 100644 packages/interactive-minimap/package.json delete mode 100644 packages/interactive-minimap/src/MiniMap.tsx delete mode 100644 packages/interactive-minimap/src/MiniMapNode.tsx delete mode 100644 packages/interactive-minimap/src/index.tsx delete mode 100644 packages/interactive-minimap/src/style.css delete mode 100644 packages/interactive-minimap/src/types.ts delete mode 100644 packages/interactive-minimap/tsconfig.json diff --git a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx index db8d754f..18fa854a 100644 --- a/examples/vite-app/src/examples/InteractiveMinimap/index.tsx +++ b/examples/vite-app/src/examples/InteractiveMinimap/index.tsx @@ -1,5 +1,6 @@ import { MouseEvent, useCallback } from 'react'; import ReactFlow, { + MiniMap, Background, BackgroundVariant, Controls, @@ -9,7 +10,6 @@ import ReactFlow, { useReactFlow, XYPosition, } from 'reactflow'; -import { MiniMap } from '@reactflow/interactive-minimap'; const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node); const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); @@ -137,7 +137,7 @@ const BasicFlow = () => { fitView > - +
diff --git a/examples/vite-app/src/examples/NestedNodes/index.tsx b/examples/vite-app/src/examples/NestedNodes/index.tsx index 5e9c80f1..a6c083ce 100644 --- a/examples/vite-app/src/examples/NestedNodes/index.tsx +++ b/examples/vite-app/src/examples/NestedNodes/index.tsx @@ -160,7 +160,7 @@ const NestedFlow = () => { maxZoom={4} onlyRenderVisibleElements={false} > - + diff --git a/packages/interactive-minimap/.eslintrc.js b/packages/interactive-minimap/.eslintrc.js deleted file mode 100644 index 31cfbd11..00000000 --- a/packages/interactive-minimap/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ['@reactflow/eslint-config'], -}; diff --git a/packages/interactive-minimap/CHANGELOG.md b/packages/interactive-minimap/CHANGELOG.md deleted file mode 100644 index eb44ff49..00000000 --- a/packages/interactive-minimap/CHANGELOG.md +++ /dev/null @@ -1,42 +0,0 @@ -# @reactflow/minimap - -## 11.0.1 - -### Patch Changes - -- Updated dependencies [[`def11008`](https://github.com/wbkd/react-flow/commit/def11008d88749fec40e6fcba8bc41eea2511bab), [`d00faa6b`](https://github.com/wbkd/react-flow/commit/d00faa6b3e77388bfd655d4c02e9a5375bc515e4)]: - - @reactflow/core@11.1.0 - -## 11.0.0 - -### Major Changes - -- **Better [Accessibility](/docs/guides/accessibility)** - - Nodes and edges are focusable, selectable, moveable and deleteable with the keyboard. - - `aria-` default attributes for all elements and controllable via `ariaLabel` options - - Keyboard controls can be disabled with the new `disableKeyboardA11y` prop -- **Better selectable edges** via new edge option: `interactionWidth` - renders invisible edge that makes it easier to interact -- **Better routing for smoothstep and step edges**: https://twitter.com/reactflowdev/status/1567535405284614145 -- **Nicer edge updating behaviour**: https://twitter.com/reactflowdev/status/1564966917517021184 -- **Node origin**: The new `nodeOrigin` prop lets you control the origin of a node. Useful for layouting. -- **New background pattern**: `BackgroundVariant.Cross` variant -- **[`useOnViewportChange`](/docs/api/hooks/use-on-viewport-change) hook** - handle viewport changes within a component -- **[`useOnSelectionChange`](/docs/api/hooks/use-on-selection-change) hook** - handle selection changes within a component -- **[`useNodesInitialized`](/docs/api/hooks/use-nodes-initialized) hook** - returns true if all nodes are initialized and if there is more than one node -- **Deletable option** for Nodes and edges -- **New Event handlers**: `onPaneMouseEnter`, `onPaneMouseMove` and `onPaneMouseLeave` -- **Edge `pathOptions`** for `smoothstep` and `default` edges -- **Nicer cursor defaults**: Cursor is grabbing, while dragging a node or panning -- **Pane moveable** with middle mouse button -- **Pan over nodes** when they are not draggable (`draggable=false` or `nodesDraggable` false) -- **[``](/docs/api/edges/base-edge) component** that makes it easier to build custom edges -- **[Separately installable packages](/docs/overview/packages/)** - - @reactflow/core - - @reactflow/background - - @reactflow/controls - - @reactflow/minimap - -### Patch Changes - -- Updated dependencies: - - @reactflow/core@11.0.0 diff --git a/packages/interactive-minimap/README.md b/packages/interactive-minimap/README.md deleted file mode 100644 index 2dba17d9..00000000 --- a/packages/interactive-minimap/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# @reactflow/minimap - -Mini map component for React Flow. - -## Installation - -```sh -npm install @reactflow/minimap -``` - diff --git a/packages/interactive-minimap/package.json b/packages/interactive-minimap/package.json deleted file mode 100644 index 8dc4a82a..00000000 --- a/packages/interactive-minimap/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@reactflow/interactive-minimap", - "version": "0.0.1-alpha.0", - "description": "Interactive Minimap component for React Flow.", - "keywords": [ - "react", - "node-based UI", - "graph", - "diagram", - "workflow", - "react-flow" - ], - "files": [ - "dist" - ], - "source": "src/index.tsx", - "main": "dist/umd/index.js", - "module": "dist/esm/index.js", - "types": "dist/esm/index.d.ts", - "sideEffects": false, - "publishConfig": { - "access": "public" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/wbkd/react-flow.git", - "directory": "packages/interactive-minimap" - }, - "scripts": { - "dev": "concurrently \"rollup --config node:@reactflow/rollup-config --watch\" pnpm:css-watch", - "build": "rollup --config node:@reactflow/rollup-config --environment NODE_ENV:production && npm run css", - "css": "postcss src/*.css --config ../../tooling/postcss-config/postcss.config.js --dir dist", - "css-watch": "pnpm css --watch", - "lint": "eslint --ext .js,.jsx,.ts,.tsx src", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@babel/runtime": "^7.18.9", - "@reactflow/core": "workspace:*", - "@types/d3-selection": "^3.0.3", - "@types/d3-zoom": "^3.0.1", - "classcat": "^5.0.3", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0", - "zustand": "^4.1.1" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - }, - "devDependencies": { - "@reactflow/eslint-config": "workspace:^0.0.0", - "@reactflow/rollup-config": "workspace:*", - "@reactflow/tsconfig": "workspace:*", - "@types/node": "^18.7.16", - "@types/react": "^18.0.19", - "react": "^18.2.0", - "typescript": "^4.8.3" - }, - "rollup": { - "globals": { - "zustand": "Zustand", - "zustand/shallow": "zustandShallow", - "classcat": "cc" - }, - "name": "ReactFlowMinimap" - } -} diff --git a/packages/interactive-minimap/src/MiniMap.tsx b/packages/interactive-minimap/src/MiniMap.tsx deleted file mode 100644 index 46b0ec9f..00000000 --- a/packages/interactive-minimap/src/MiniMap.tsx +++ /dev/null @@ -1,178 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo, useEffect, useRef, MouseEvent } from 'react'; -import cc from 'classcat'; -import shallow from 'zustand/shallow'; -import { zoom, D3ZoomEvent, zoomIdentity } from 'd3-zoom'; -import { select, pointer } from 'd3-selection'; - -import { useStore, getRectOfNodes, Panel, getBoundsOfRects, useStoreApi, ReactFlowState, Rect } from '@reactflow/core'; - -import MiniMapNode from './MiniMapNode'; -import { MiniMapProps, GetMiniMapNodeAttribute } from './types'; - -declare const window: any; - -const defaultWidth = 200; -const defaultHeight = 150; - -const selector = (s: ReactFlowState) => { - const nodes = Array.from(s.nodeInternals.values()); - const viewBB: Rect = { - x: -s.transform[0] / s.transform[2], - y: -s.transform[1] / s.transform[2], - width: s.width / s.transform[2], - height: s.height / s.transform[2], - }; - - return { - nodes: nodes.filter((node) => !node.hidden && node.width && node.height), - viewBB, - boundingRect: nodes.length > 0 ? getBoundsOfRects(getRectOfNodes(nodes), viewBB) : viewBB, - rfId: s.rfId, - }; -}; - -const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); - -const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; - -function MiniMap({ - style, - className, - nodeStrokeColor = '#555', - nodeColor = '#999', - nodeClassName = '', - nodeBorderRadius = 5, - nodeStrokeWidth = 2, - maskColor = 'rgb(200, 200, 200, 0.9)', - position = 'bottom-right', - onClick, - onNodeClick, -}: MiniMapProps) { - const store = useStoreApi(); - const svg = useRef(null); - const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); - const elementWidth = (style?.width as number) ?? defaultWidth; - const elementHeight = (style?.height as number) ?? defaultHeight; - const nodeColorFunc = getAttrFunction(nodeColor); - const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); - const nodeClassNameFunc = getAttrFunction(nodeClassName); - const scaledWidth = boundingRect.width / elementWidth; - const scaledHeight = boundingRect.height / elementHeight; - const viewScale = Math.max(scaledWidth, scaledHeight); - const viewWidth = viewScale * elementWidth; - const viewHeight = viewScale * elementHeight; - const offset = 5 * viewScale; - const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; - const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; - const width = viewWidth + offset * 2; - const height = viewHeight + offset * 2; - const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; - const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; - const viewScaleRef = useRef(0); - - viewScaleRef.current = viewScale; - - const onSvgClick = (event: MouseEvent) => { - const rfCoord = pointer(event); - onClick?.(event, { x: rfCoord[0], y: rfCoord[1] }); - }; - - useEffect(() => { - if (svg.current) { - const selection = select(svg.current as Element); - - const zoomHandler = zoom() - .on('zoom.wheel', (event: D3ZoomEvent) => { - const { transform, d3Selection, d3Zoom } = store.getState(); - - if (event.sourceEvent.type !== 'wheel' || !d3Selection || !d3Zoom) { - return; - } - - const pinchDelta = - -event.sourceEvent.deltaY * - (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * - 10; - const zoom = transform[2] * Math.pow(2, pinchDelta); - - d3Zoom.scaleTo(d3Selection, zoom); - }) - .on('zoom', (event: D3ZoomEvent) => { - const { transform, d3Selection, d3Zoom } = store.getState(); - - if (event.sourceEvent.type !== 'mousemove' || !d3Selection || !d3Zoom) { - return; - } - - const position = { - x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * transform[2], - y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * transform[2], - }; - - const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); - - d3Zoom.transform(d3Selection, nextTransform); - }); - selection.call(zoomHandler); - - return () => { - selection.on('.zoom', null); - }; - } - }, []); - - const onSvgNodeClick = onNodeClick - ? (event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeInternals.get(nodeId)!; - onNodeClick(event, node); - } - : undefined; - - return ( - - - React Flow mini map - {nodes.map((node) => { - return ( - - ); - })} - - - - ); -} - -MiniMap.displayName = 'MiniMap'; - -export default memo(MiniMap); diff --git a/packages/interactive-minimap/src/MiniMapNode.tsx b/packages/interactive-minimap/src/MiniMapNode.tsx deleted file mode 100644 index 15a5ba3f..00000000 --- a/packages/interactive-minimap/src/MiniMapNode.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { memo, CSSProperties, MouseEvent } from 'react'; -import cc from 'classcat'; - -interface MiniMapNodeProps { - id: string; - x: number; - y: number; - width: number; - height: number; - borderRadius: number; - className: string; - color: string; - shapeRendering: string; - strokeColor: string; - strokeWidth: number; - style?: CSSProperties; - onClick?: (event: MouseEvent, id: string) => void; -} - -const MiniMapNode = ({ - id, - x, - y, - width, - height, - style, - color, - strokeColor, - strokeWidth, - className, - borderRadius, - shapeRendering, - onClick, -}: MiniMapNodeProps) => { - const { background, backgroundColor } = style || {}; - const fill = (color || background || backgroundColor) as string; - - return ( - onClick(event, id) : undefined} - /> - ); -}; - -MiniMapNode.displayName = 'MiniMapNode'; - -export default memo(MiniMapNode); diff --git a/packages/interactive-minimap/src/index.tsx b/packages/interactive-minimap/src/index.tsx deleted file mode 100644 index 7b91e95d..00000000 --- a/packages/interactive-minimap/src/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export { default as MiniMap } from './MiniMap'; -export * from './types'; diff --git a/packages/interactive-minimap/src/style.css b/packages/interactive-minimap/src/style.css deleted file mode 100644 index dbb9a931..00000000 --- a/packages/interactive-minimap/src/style.css +++ /dev/null @@ -1,3 +0,0 @@ -.react-flow__minimap { - background-color: #fff; -} diff --git a/packages/interactive-minimap/src/types.ts b/packages/interactive-minimap/src/types.ts deleted file mode 100644 index 80534fc3..00000000 --- a/packages/interactive-minimap/src/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { HTMLAttributes, MouseEvent } from 'react'; -import { Node, PanelPosition, XYPosition } from '@reactflow/core'; - -export type GetMiniMapNodeAttribute = (node: Node) => string; - -export type MiniMapProps = Omit, 'onClick'> & { - nodeColor?: string | GetMiniMapNodeAttribute; - nodeStrokeColor?: string | GetMiniMapNodeAttribute; - nodeClassName?: string | GetMiniMapNodeAttribute; - nodeBorderRadius?: number; - nodeStrokeWidth?: number; - maskColor?: string; - position?: PanelPosition; - onClick?: (event: MouseEvent, position: XYPosition) => void; - onNodeClick?: (event: MouseEvent, node: Node) => void; -}; diff --git a/packages/interactive-minimap/tsconfig.json b/packages/interactive-minimap/tsconfig.json deleted file mode 100644 index 8a8e74ce..00000000 --- a/packages/interactive-minimap/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "@reactflow/tsconfig/react.json", - "display": "@reactflow/minimap", - "include": ["**/*.ts", "**/*.tsx"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/minimap/package.json b/packages/minimap/package.json index 527aab38..100a12b0 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -40,7 +40,11 @@ "dependencies": { "@babel/runtime": "^7.18.9", "@reactflow/core": "workspace:*", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", "zustand": "^4.1.1" }, "peerDependencies": { diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 05cd8771..4fd639da 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -1,8 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { memo } from 'react'; +import { memo, useEffect, useRef } from 'react'; +import type { MouseEvent } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; -import { useStore, getRectOfNodes, getBoundsOfRects, Panel } from '@reactflow/core'; +import { zoom, zoomIdentity } from 'd3-zoom'; +import type { D3ZoomEvent } from 'd3-zoom'; +import { select, pointer } from 'd3-selection'; +import { useStore, getRectOfNodes, Panel, getBoundsOfRects, useStoreApi } from '@reactflow/core'; import type { ReactFlowState, Rect } from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; @@ -44,7 +48,13 @@ function MiniMap({ nodeStrokeWidth = 2, maskColor = 'rgb(240, 242, 243, 0.7)', position = 'bottom-right', + onClick, + onNodeClick, + pannable = false, + zoomable = false, }: MiniMapProps) { + const store = useStoreApi(); + const svg = useRef(null); const { boundingRect, viewBB, nodes, rfId } = useStore(selector, shallow); const elementWidth = (style?.width as number) ?? defaultWidth; const elementHeight = (style?.height as number) ?? defaultHeight; @@ -63,6 +73,68 @@ function MiniMap({ const height = viewHeight + offset * 2; const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const labelledBy = `${ARIA_LABEL_KEY}-${rfId}`; + const viewScaleRef = useRef(0); + + viewScaleRef.current = viewScale; + + useEffect(() => { + if (svg.current) { + const selection = select(svg.current as Element); + + const zoomHandler = zoom() + .on('zoom.wheel', (event: D3ZoomEvent) => { + const { transform, d3Selection, d3Zoom } = store.getState(); + + if (event.sourceEvent.type !== 'wheel' || !zoomable || !d3Selection || !d3Zoom) { + return; + } + + const pinchDelta = + -event.sourceEvent.deltaY * + (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * + 10; + const zoom = transform[2] * Math.pow(2, pinchDelta); + + d3Zoom.scaleTo(d3Selection, zoom); + }) + .on('zoom', (event: D3ZoomEvent) => { + const { transform, d3Selection, d3Zoom } = store.getState(); + + if (event.sourceEvent.type !== 'mousemove' || !pannable || !d3Selection || !d3Zoom) { + return; + } + + // @TODO: how to calculate the correct next position? Math.max(1, transform[2]) is a workaround. + const position = { + x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * Math.max(1, transform[2]), + y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * Math.max(1, transform[2]), + }; + + const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + + d3Zoom.transform(d3Selection, nextTransform); + }); + selection.call(zoomHandler); + + return () => { + selection.on('.zoom', null); + }; + } + }, [pannable, zoomable]); + + const onSvgClick = onClick + ? (event: MouseEvent) => { + const rfCoord = pointer(event); + onClick(event, { x: rfCoord[0], y: rfCoord[1] }); + } + : undefined; + + const onSvgNodeClick = onNodeClick + ? (event: MouseEvent, nodeId: string) => { + const node = store.getState().nodeInternals.get(nodeId)!; + onNodeClick(event, node); + } + : undefined; return ( @@ -72,6 +144,8 @@ function MiniMap({ viewBox={`${x} ${y} ${width} ${height}`} role="img" aria-labelledby={labelledBy} + ref={svg} + onClick={onSvgClick} > React Flow mini map {nodes.map((node) => { @@ -89,6 +163,8 @@ function MiniMap({ strokeColor={nodeStrokeColorFunc(node)} strokeWidth={nodeStrokeWidth} shapeRendering={shapeRendering} + onClick={onSvgNodeClick} + id={node.id} /> ); })} diff --git a/packages/minimap/src/MiniMapNode.tsx b/packages/minimap/src/MiniMapNode.tsx index 111c7ee0..ae000d02 100644 --- a/packages/minimap/src/MiniMapNode.tsx +++ b/packages/minimap/src/MiniMapNode.tsx @@ -1,8 +1,9 @@ import { memo } from 'react'; -import type { CSSProperties } from 'react'; +import type { CSSProperties, MouseEvent } from 'react'; import cc from 'classcat'; interface MiniMapNodeProps { + id: string; x: number; y: number; width: number; @@ -14,9 +15,11 @@ interface MiniMapNodeProps { strokeColor: string; strokeWidth: number; style?: CSSProperties; + onClick?: (event: MouseEvent, id: string) => void; } const MiniMapNode = ({ + id, x, y, width, @@ -28,6 +31,7 @@ const MiniMapNode = ({ className, borderRadius, shapeRendering, + onClick, }: MiniMapNodeProps) => { const { background, backgroundColor } = style || {}; const fill = (color || background || backgroundColor) as string; @@ -45,6 +49,7 @@ const MiniMapNode = ({ stroke={strokeColor} strokeWidth={strokeWidth} shapeRendering={shapeRendering} + onClick={onClick ? (event) => onClick(event, id) : undefined} /> ); }; diff --git a/packages/minimap/src/types.ts b/packages/minimap/src/types.ts index 4fed29ba..63d8c6d9 100644 --- a/packages/minimap/src/types.ts +++ b/packages/minimap/src/types.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { HTMLAttributes } from 'react'; -import type { Node, PanelPosition } from '@reactflow/core'; +import type { HTMLAttributes, MouseEvent } from 'react'; +import type { Node, PanelPosition, XYPosition } from '@reactflow/core'; export type GetMiniMapNodeAttribute = (node: Node) => string; -export type MiniMapProps = HTMLAttributes & { +export type MiniMapProps = Omit, 'onClick'> & { nodeColor?: string | GetMiniMapNodeAttribute; nodeStrokeColor?: string | GetMiniMapNodeAttribute; nodeClassName?: string | GetMiniMapNodeAttribute; @@ -12,4 +12,8 @@ export type MiniMapProps = HTMLAttributes & { nodeStrokeWidth?: number; maskColor?: string; position?: PanelPosition; + onClick?: (event: MouseEvent, position: XYPosition) => void; + onNodeClick?: (event: MouseEvent, node: Node) => void; + pannable?: boolean; + zoomable?: boolean; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91f51a8d..56aff557 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -222,16 +222,24 @@ importers: '@reactflow/eslint-config': workspace:^0.0.0 '@reactflow/rollup-config': workspace:* '@reactflow/tsconfig': workspace:* + '@types/d3-selection': ^3.0.3 + '@types/d3-zoom': ^3.0.1 '@types/node': ^18.7.16 '@types/react': ^18.0.19 classcat: ^5.0.3 + d3-selection: ^3.0.0 + d3-zoom: ^3.0.0 react: ^18.2.0 typescript: ^4.8.3 zustand: ^4.1.1 dependencies: '@babel/runtime': registry.npmjs.org/@babel/runtime/7.19.0 '@reactflow/core': link:../core + '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 + '@types/d3-zoom': registry.npmjs.org/@types/d3-zoom/3.0.1 classcat: registry.npmjs.org/classcat/5.0.4 + d3-selection: registry.npmjs.org/d3-selection/3.0.0 + d3-zoom: registry.npmjs.org/d3-zoom/3.0.0 zustand: registry.npmjs.org/zustand/4.1.1_react@18.2.0 devDependencies: '@reactflow/eslint-config': link:../../tooling/eslint-config From e85e29343c1ddd4ce3dc2bf6b2e190bdb5847eed Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 1 Nov 2022 23:28:21 +0100 Subject: [PATCH 15/25] chore(examples): remove interactive minimap dep --- examples/vite-app/package.json | 3 +- pnpm-lock.yaml | 186 ++++++++++++--------------------- 2 files changed, 70 insertions(+), 119 deletions(-) diff --git a/examples/vite-app/package.json b/examples/vite-app/package.json index 27ace109..23bbc7fc 100644 --- a/examples/vite-app/package.json +++ b/examples/vite-app/package.json @@ -19,8 +19,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.3.0", - "reactflow": "workspace:*", - "@reactflow/interactive-minimap": "workspace:*" + "reactflow": "workspace:*" }, "devDependencies": { "@cypress/skip-test": "^2.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56aff557..7220fd5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,15 +32,15 @@ importers: '@changesets/changelog-github': registry.npmjs.org/@changesets/changelog-github/0.4.7 '@changesets/cli': registry.npmjs.org/@changesets/cli/2.25.0 '@preconstruct/cli': registry.npmjs.org/@preconstruct/cli/2.2.1 - '@typescript-eslint/eslint-plugin': registry.npmjs.org/@typescript-eslint/eslint-plugin/5.39.0_jdrczqv3ll7udvbunca43w7rz4 - '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.39.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/eslint-plugin': registry.npmjs.org/@typescript-eslint/eslint-plugin/5.42.0_dcn2ddfkdgyby36a3kmqplwxkq + '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.42.0_irgkl5vooow2ydyo6aokmferha autoprefixer: registry.npmjs.org/autoprefixer/10.4.9_postcss@8.4.16 concurrently: registry.npmjs.org/concurrently/7.4.0 cypress: registry.npmjs.org/cypress/10.7.0 eslint: registry.npmjs.org/eslint/8.23.1 eslint-config-prettier: registry.npmjs.org/eslint-config-prettier/8.5.0_eslint@8.23.1 eslint-plugin-prettier: registry.npmjs.org/eslint-plugin-prettier/4.2.1_cabrci5exjdaojcvd6xoxgeowu - eslint-plugin-react: registry.npmjs.org/eslint-plugin-react/7.31.9_eslint@8.23.1 + eslint-plugin-react: registry.npmjs.org/eslint-plugin-react/7.31.10_eslint@8.23.1 postcss: registry.npmjs.org/postcss/8.4.16 postcss-cli: registry.npmjs.org/postcss-cli/10.0.0_postcss@8.4.16 postcss-combine-duplicated-selectors: registry.npmjs.org/postcss-combine-duplicated-selectors/10.0.3_postcss@8.4.16 @@ -57,7 +57,6 @@ importers: examples/vite-app: specifiers: '@cypress/skip-test': ^2.6.1 - '@reactflow/interactive-minimap': workspace:* '@types/react': ^18.0.17 '@types/react-dom': ^18.0.6 '@vitejs/plugin-react': ^2.1.0 @@ -74,7 +73,6 @@ importers: typescript: ^4.8.3 vite: ^3.1.0 dependencies: - '@reactflow/interactive-minimap': link:../../packages/interactive-minimap classcat: registry.npmjs.org/classcat/5.0.4 dagre: registry.npmjs.org/dagre/0.8.5 localforage: registry.npmjs.org/localforage/1.10.0 @@ -180,41 +178,6 @@ importers: react: registry.npmjs.org/react/18.2.0 typescript: registry.npmjs.org/typescript/4.8.3 - packages/interactive-minimap: - specifiers: - '@babel/runtime': ^7.18.9 - '@reactflow/core': workspace:* - '@reactflow/eslint-config': workspace:^0.0.0 - '@reactflow/rollup-config': workspace:* - '@reactflow/tsconfig': workspace:* - '@types/d3-selection': ^3.0.3 - '@types/d3-zoom': ^3.0.1 - '@types/node': ^18.7.16 - '@types/react': ^18.0.19 - classcat: ^5.0.3 - d3-selection: ^3.0.0 - d3-zoom: ^3.0.0 - react: ^18.2.0 - typescript: ^4.8.3 - zustand: ^4.1.1 - dependencies: - '@babel/runtime': registry.npmjs.org/@babel/runtime/7.19.0 - '@reactflow/core': link:../core - '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 - '@types/d3-zoom': registry.npmjs.org/@types/d3-zoom/3.0.1 - classcat: registry.npmjs.org/classcat/5.0.4 - d3-selection: registry.npmjs.org/d3-selection/3.0.0 - d3-zoom: registry.npmjs.org/d3-zoom/3.0.0 - zustand: registry.npmjs.org/zustand/4.1.1_react@18.2.0 - devDependencies: - '@reactflow/eslint-config': link:../../tooling/eslint-config - '@reactflow/rollup-config': link:../../tooling/rollup-config - '@reactflow/tsconfig': link:../../tooling/tsconfig - '@types/node': registry.npmjs.org/@types/node/18.7.16 - '@types/react': registry.npmjs.org/@types/react/18.0.19 - react: registry.npmjs.org/react/18.2.0 - typescript: registry.npmjs.org/typescript/4.8.3 - packages/minimap: specifiers: '@babel/runtime': ^7.18.9 @@ -287,7 +250,7 @@ importers: eslint: registry.npmjs.org/eslint/8.23.1 eslint-config-prettier: registry.npmjs.org/eslint-config-prettier/8.5.0_eslint@8.23.1 eslint-config-turbo: registry.npmjs.org/eslint-config-turbo/0.0.4_eslint@8.23.1 - eslint-plugin-react: registry.npmjs.org/eslint-plugin-react/7.31.8_eslint@8.23.1 + eslint-plugin-react: registry.npmjs.org/eslint-plugin-react/7.31.10_eslint@8.23.1 tooling/rollup-config: specifiers: @@ -1727,6 +1690,12 @@ packages: version: 6.2.3 dev: true + registry.npmjs.org/@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz} + name: '@types/semver' + version: 7.3.13 + dev: true + registry.npmjs.org/@types/sinonjs__fake-timers/8.1.1: resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz} name: '@types/sinonjs__fake-timers' @@ -1749,11 +1718,11 @@ packages: dev: true optional: true - registry.npmjs.org/@typescript-eslint/eslint-plugin/5.39.0_jdrczqv3ll7udvbunca43w7rz4: - resolution: {integrity: sha512-xVfKOkBm5iWMNGKQ2fwX5GVgBuHmZBO1tCRwXmY5oAIsPscfwm2UADDuNB8ZVYCtpQvJK4xpjrK7jEhcJ0zY9A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz} - id: registry.npmjs.org/@typescript-eslint/eslint-plugin/5.39.0 + registry.npmjs.org/@typescript-eslint/eslint-plugin/5.42.0_dcn2ddfkdgyby36a3kmqplwxkq: + resolution: {integrity: sha512-5TJh2AgL6+wpL8H/GTSjNb4WrjKoR2rqvFxR/DDTqYNk6uXn8BJMEcncLSpMbf/XV1aS0jAjYwn98uvVCiAywQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.42.0.tgz} + id: registry.npmjs.org/@typescript-eslint/eslint-plugin/5.42.0 name: '@typescript-eslint/eslint-plugin' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -1763,13 +1732,14 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.39.0_irgkl5vooow2ydyo6aokmferha - '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0 - '@typescript-eslint/type-utils': registry.npmjs.org/@typescript-eslint/type-utils/5.39.0_irgkl5vooow2ydyo6aokmferha - '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.39.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/parser': registry.npmjs.org/@typescript-eslint/parser/5.42.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.42.0 + '@typescript-eslint/type-utils': registry.npmjs.org/@typescript-eslint/type-utils/5.42.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.42.0_irgkl5vooow2ydyo6aokmferha debug: registry.npmjs.org/debug/4.3.4 eslint: registry.npmjs.org/eslint/8.23.1 ignore: registry.npmjs.org/ignore/5.2.0 + natural-compare-lite: registry.npmjs.org/natural-compare-lite/1.4.0 regexpp: registry.npmjs.org/regexpp/3.2.0 semver: registry.npmjs.org/semver/7.3.8 tsutils: registry.npmjs.org/tsutils/3.21.0_typescript@4.8.3 @@ -1778,11 +1748,11 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/parser/5.39.0_irgkl5vooow2ydyo6aokmferha: - resolution: {integrity: sha512-PhxLjrZnHShe431sBAGHaNe6BDdxAASDySgsBCGxcBecVCi8NQWxQZMcizNA4g0pN51bBAn/FUfkWG3SDVcGlA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.39.0.tgz} - id: registry.npmjs.org/@typescript-eslint/parser/5.39.0 + registry.npmjs.org/@typescript-eslint/parser/5.42.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-Ixh9qrOTDRctFg3yIwrLkgf33AHyEIn6lhyf5cCfwwiGtkWhNpVKlEZApi3inGQR/barWnY7qY8FbGKBO7p3JA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.42.0.tgz} + id: registry.npmjs.org/@typescript-eslint/parser/5.42.0 name: '@typescript-eslint/parser' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1791,9 +1761,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0 - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 - '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3 + '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.42.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.42.0 + '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.42.0_typescript@4.8.3 debug: registry.npmjs.org/debug/4.3.4 eslint: registry.npmjs.org/eslint/8.23.1 typescript: registry.npmjs.org/typescript/4.8.3 @@ -1801,21 +1771,21 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0: - resolution: {integrity: sha512-/I13vAqmG3dyqMVSZPjsbuNQlYS082Y7OMkwhCfLXYsmlI0ca4nkL7wJ/4gjX70LD4P8Hnw1JywUVVAwepURBw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.39.0.tgz} + registry.npmjs.org/@typescript-eslint/scope-manager/5.42.0: + resolution: {integrity: sha512-l5/3IBHLH0Bv04y+H+zlcLiEMEMjWGaCX6WyHE5Uk2YkSGAMlgdUPsT/ywTSKgu9D1dmmKMYgYZijObfA39Wow==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.42.0.tgz} name: '@typescript-eslint/scope-manager' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 - '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.39.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.42.0 + '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.42.0 dev: true - registry.npmjs.org/@typescript-eslint/type-utils/5.39.0_irgkl5vooow2ydyo6aokmferha: - resolution: {integrity: sha512-KJHJkOothljQWzR3t/GunL0TPKY+fGJtnpl+pX+sJ0YiKTz3q2Zr87SGTmFqsCMFrLt5E0+o+S6eQY0FAXj9uA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.39.0.tgz} - id: registry.npmjs.org/@typescript-eslint/type-utils/5.39.0 + registry.npmjs.org/@typescript-eslint/type-utils/5.42.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-HW14TXC45dFVZxnVW8rnUGnvYyRC0E/vxXShFCthcC9VhVTmjqOmtqj6H5rm9Zxv+ORxKA/1aLGD7vmlLsdlOg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.42.0.tgz} + id: registry.npmjs.org/@typescript-eslint/type-utils/5.42.0 name: '@typescript-eslint/type-utils' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -1824,8 +1794,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3 - '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.39.0_irgkl5vooow2ydyo6aokmferha + '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.42.0_typescript@4.8.3 + '@typescript-eslint/utils': registry.npmjs.org/@typescript-eslint/utils/5.42.0_irgkl5vooow2ydyo6aokmferha debug: registry.npmjs.org/debug/4.3.4 eslint: registry.npmjs.org/eslint/8.23.1 tsutils: registry.npmjs.org/tsutils/3.21.0_typescript@4.8.3 @@ -1834,18 +1804,18 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/types/5.39.0: - resolution: {integrity: sha512-gQMZrnfEBFXK38hYqt8Lkwt8f4U6yq+2H5VDSgP/qiTzC8Nw8JO3OuSUOQ2qW37S/dlwdkHDntkZM6SQhKyPhw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/types/-/types-5.39.0.tgz} + registry.npmjs.org/@typescript-eslint/types/5.42.0: + resolution: {integrity: sha512-t4lzO9ZOAUcHY6bXQYRuu+3SSYdD9TS8ooApZft4WARt4/f2Cj/YpvbTe8A4GuhT4bNW72goDMOy7SW71mZwGw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/types/-/types-5.42.0.tgz} name: '@typescript-eslint/types' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3: - resolution: {integrity: sha512-qLFQP0f398sdnogJoLtd43pUgB18Q50QSA+BTE5h3sUxySzbWDpTSdgt4UyxNSozY/oDK2ta6HVAzvGgq8JYnA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.39.0.tgz} - id: registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0 + registry.npmjs.org/@typescript-eslint/typescript-estree/5.42.0_typescript@4.8.3: + resolution: {integrity: sha512-2O3vSq794x3kZGtV7i4SCWZWCwjEtkWfVqX4m5fbUBomOsEOyd6OAD1qU2lbvV5S8tgy/luJnOYluNyYVeOTTg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.0.tgz} + id: registry.npmjs.org/@typescript-eslint/typescript-estree/5.42.0 name: '@typescript-eslint/typescript-estree' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1853,8 +1823,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 - '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.39.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.42.0 + '@typescript-eslint/visitor-keys': registry.npmjs.org/@typescript-eslint/visitor-keys/5.42.0 debug: registry.npmjs.org/debug/4.3.4 globby: registry.npmjs.org/globby/11.1.0 is-glob: registry.npmjs.org/is-glob/4.0.3 @@ -1865,34 +1835,36 @@ packages: - supports-color dev: true - registry.npmjs.org/@typescript-eslint/utils/5.39.0_irgkl5vooow2ydyo6aokmferha: - resolution: {integrity: sha512-+DnY5jkpOpgj+EBtYPyHRjXampJfC0yUZZzfzLuUWVZvCuKqSdJVC8UhdWipIw7VKNTfwfAPiOWzYkAwuIhiAg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.39.0.tgz} - id: registry.npmjs.org/@typescript-eslint/utils/5.39.0 + registry.npmjs.org/@typescript-eslint/utils/5.42.0_irgkl5vooow2ydyo6aokmferha: + resolution: {integrity: sha512-JZ++3+h1vbeG1NUECXQZE3hg0kias9kOtcQr3+JVQ3whnjvKuMyktJAAIj6743OeNPnGBmjj7KEmiDL7qsdnCQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.42.0.tgz} + id: registry.npmjs.org/@typescript-eslint/utils/5.42.0 name: '@typescript-eslint/utils' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': registry.npmjs.org/@types/json-schema/7.0.11 - '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.39.0 - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 - '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.39.0_typescript@4.8.3 + '@types/semver': registry.npmjs.org/@types/semver/7.3.13 + '@typescript-eslint/scope-manager': registry.npmjs.org/@typescript-eslint/scope-manager/5.42.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.42.0 + '@typescript-eslint/typescript-estree': registry.npmjs.org/@typescript-eslint/typescript-estree/5.42.0_typescript@4.8.3 eslint: registry.npmjs.org/eslint/8.23.1 eslint-scope: registry.npmjs.org/eslint-scope/5.1.1 eslint-utils: registry.npmjs.org/eslint-utils/3.0.0_eslint@8.23.1 + semver: registry.npmjs.org/semver/7.3.8 transitivePeerDependencies: - supports-color - typescript dev: true - registry.npmjs.org/@typescript-eslint/visitor-keys/5.39.0: - resolution: {integrity: sha512-yyE3RPwOG+XJBLrhvsxAidUgybJVQ/hG8BhiJo0k8JSAYfk/CshVcxf0HwP4Jt7WZZ6vLmxdo1p6EyN3tzFTkg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.39.0.tgz} + registry.npmjs.org/@typescript-eslint/visitor-keys/5.42.0: + resolution: {integrity: sha512-QHbu5Hf/2lOEOwy+IUw0GoSCuAzByTAWWrOTKzTzsotiUnWFpuKnXcAhC9YztAf2EElQ0VvIK+pHJUPkM0q7jg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.0.tgz} name: '@typescript-eslint/visitor-keys' - version: 5.39.0 + version: 5.42.0 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.39.0 + '@typescript-eslint/types': registry.npmjs.org/@typescript-eslint/types/5.42.0 eslint-visitor-keys: registry.npmjs.org/eslint-visitor-keys/3.3.0 dev: true @@ -3415,37 +3387,11 @@ packages: prettier-linter-helpers: registry.npmjs.org/prettier-linter-helpers/1.0.0 dev: true - registry.npmjs.org/eslint-plugin-react/7.31.8_eslint@8.23.1: - resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.8.tgz} - id: registry.npmjs.org/eslint-plugin-react/7.31.8 + registry.npmjs.org/eslint-plugin-react/7.31.10_eslint@8.23.1: + resolution: {integrity: sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz} + id: registry.npmjs.org/eslint-plugin-react/7.31.10 name: eslint-plugin-react - version: 7.31.8 - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - array-includes: registry.npmjs.org/array-includes/3.1.5 - array.prototype.flatmap: registry.npmjs.org/array.prototype.flatmap/1.3.0 - doctrine: registry.npmjs.org/doctrine/2.1.0 - eslint: registry.npmjs.org/eslint/8.23.1 - estraverse: registry.npmjs.org/estraverse/5.3.0 - jsx-ast-utils: registry.npmjs.org/jsx-ast-utils/3.3.3 - minimatch: registry.npmjs.org/minimatch/3.1.2 - object.entries: registry.npmjs.org/object.entries/1.1.5 - object.fromentries: registry.npmjs.org/object.fromentries/2.0.5 - object.hasown: registry.npmjs.org/object.hasown/1.1.1 - object.values: registry.npmjs.org/object.values/1.1.5 - prop-types: registry.npmjs.org/prop-types/15.8.1 - resolve: registry.npmjs.org/resolve/2.0.0-next.4 - semver: registry.npmjs.org/semver/6.3.0 - string.prototype.matchall: registry.npmjs.org/string.prototype.matchall/4.0.7 - dev: true - - registry.npmjs.org/eslint-plugin-react/7.31.9_eslint@8.23.1: - resolution: {integrity: sha512-vrVJwusIw4L99lyfXjtCw8HWdloajsiYslMavogrBe2Gl8gr95TJsJnOMRasN4b4N24I3XuJf6aAV6MhyGmjqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.9.tgz} - id: registry.npmjs.org/eslint-plugin-react/7.31.9 - name: eslint-plugin-react - version: 7.31.9 + version: 7.31.10 engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 @@ -5219,6 +5165,12 @@ packages: hasBin: true dev: true + registry.npmjs.org/natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz} + name: natural-compare-lite + version: 1.4.0 + dev: true + registry.npmjs.org/natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz} name: natural-compare From a246f720e099180dcce1770926607dde6153b140 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 11:30:48 +0100 Subject: [PATCH 16/25] refactor(minimap): cleanup events when not pannable or zoomable --- packages/minimap/src/MiniMap.tsx | 70 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 4fd639da..f2487f18 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { memo, useEffect, useRef } from 'react'; import type { MouseEvent } from 'react'; @@ -41,12 +42,12 @@ const ARIA_LABEL_KEY = 'react-flow__minimap-desc'; function MiniMap({ style, className, - nodeStrokeColor = '#555', - nodeColor = '#fff', + nodeStrokeColor = 'transparent', + nodeColor = '#e2e2e2', nodeClassName = '', nodeBorderRadius = 5, nodeStrokeWidth = 2, - maskColor = 'rgb(240, 242, 243, 0.7)', + maskColor = 'rgb(240, 240, 240, 0.6)', position = 'bottom-right', onClick, onNodeClick, @@ -81,43 +82,50 @@ function MiniMap({ if (svg.current) { const selection = select(svg.current as Element); - const zoomHandler = zoom() - .on('zoom.wheel', (event: D3ZoomEvent) => { - const { transform, d3Selection, d3Zoom } = store.getState(); + const zoomHandler = (event: D3ZoomEvent) => { + const { transform, d3Selection, d3Zoom } = store.getState(); - if (event.sourceEvent.type !== 'wheel' || !zoomable || !d3Selection || !d3Zoom) { - return; - } + if (event.sourceEvent.type !== 'wheel' || !d3Selection || !d3Zoom) { + return; + } - const pinchDelta = - -event.sourceEvent.deltaY * - (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * - 10; - const zoom = transform[2] * Math.pow(2, pinchDelta); + const pinchDelta = + -event.sourceEvent.deltaY * + (event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) * + 10; + const zoom = transform[2] * Math.pow(2, pinchDelta); - d3Zoom.scaleTo(d3Selection, zoom); - }) - .on('zoom', (event: D3ZoomEvent) => { - const { transform, d3Selection, d3Zoom } = store.getState(); + d3Zoom.scaleTo(d3Selection, zoom); + }; - if (event.sourceEvent.type !== 'mousemove' || !pannable || !d3Selection || !d3Zoom) { - return; - } + const panHandler = (event: D3ZoomEvent) => { + const { transform, d3Selection, d3Zoom } = store.getState(); - // @TODO: how to calculate the correct next position? Math.max(1, transform[2]) is a workaround. - const position = { - x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * Math.max(1, transform[2]), - y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * Math.max(1, transform[2]), - }; + if (event.sourceEvent.type !== 'mousemove' || !d3Selection || !d3Zoom) { + return; + } - const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + // @TODO: how to calculate the correct next position? Math.max(1, transform[2]) is a workaround. + const position = { + x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * Math.max(1, transform[2]), + y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * Math.max(1, transform[2]), + }; - d3Zoom.transform(d3Selection, nextTransform); - }); - selection.call(zoomHandler); + const nextTransform = zoomIdentity.translate(position.x, position.y).scale(transform[2]); + + d3Zoom.transform(d3Selection, nextTransform); + }; + + const zoomAndPanHandler = zoom() + // @ts-ignore + .on('zoom', pannable ? panHandler : null) + // @ts-ignore + .on('zoom.wheel', zoomable ? zoomHandler : null); + + selection.call(zoomAndPanHandler); return () => { - selection.on('.zoom', null); + selection.on('zoom', null); }; } }, [pannable, zoomable]); From 8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 11:34:41 +0100 Subject: [PATCH 17/25] chore(changeset): add --- .changeset/twenty-items-invite.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/twenty-items-invite.md diff --git a/.changeset/twenty-items-invite.md b/.changeset/twenty-items-invite.md new file mode 100644 index 00000000..7252cb31 --- /dev/null +++ b/.changeset/twenty-items-invite.md @@ -0,0 +1,8 @@ +--- +'@reactflow/examples': minor +'@reactflow/core': minor +'@reactflow/minimap': minor +'reactflow': minor +--- + +Feat: Add pan and zoom to mini map From 095e1fbe81347fa5c1fa8320d81a33a57b24ba1d Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 14:38:37 +0100 Subject: [PATCH 18/25] feat(edge-labels): add div based renderer --- examples/vite-app/src/App/index.tsx | 6 + .../src/examples/EdgeRenderer/CustomEdge.tsx | 61 ++++++ .../src/examples/EdgeRenderer/CustomEdge2.tsx | 51 +++++ .../src/examples/EdgeRenderer/index.tsx | 206 ++++++++++++++++++ packages/core/package.json | 1 + .../components/EdgeLabelRenderer/index.tsx | 15 ++ .../core/src/container/GraphView/index.tsx | 2 + packages/core/src/index.ts | 1 + packages/core/src/styles/init.css | 7 + pnpm-lock.yaml | 2 + 10 files changed, 352 insertions(+) create mode 100644 examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx create mode 100644 examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx create mode 100644 examples/vite-app/src/examples/EdgeRenderer/index.tsx create mode 100644 packages/core/src/components/EdgeLabelRenderer/index.tsx diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 90339935..0e2823f6 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -10,6 +10,7 @@ import DefaultNodes from '../examples/DefaultNodes'; import DragHandle from '../examples/DragHandle'; import DragNDrop from '../examples/DragNDrop'; import Edges from '../examples/Edges'; +import EdgeRenderer from '../examples/EdgeRenderer'; import EdgeTypes from '../examples/EdgeTypes'; import Empty from '../examples/Empty'; import FloatingEdges from '../examples/FloatingEdges'; @@ -95,6 +96,11 @@ const routes: IRoute[] = [ path: '/edges', component: Edges, }, + { + name: 'Edge Renderer', + path: '/edge-renderer', + component: EdgeRenderer, + }, { name: 'Edge Types', path: '/edge-types', diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx new file mode 100644 index 00000000..f7a42270 --- /dev/null +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge.tsx @@ -0,0 +1,61 @@ +import { FC, MouseEvent } from 'react'; +import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore, ReactFlowStore } from 'reactflow'; + +const CustomEdge: FC = ({ + id, + source, + target, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, +}) => { + const isConnectedNodeDragging = useStore((s) => + Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) + ); + + const [edgePath, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + const onClick = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + console.log('click', data.text); + }; + + return ( + <> + + + +
+ {data.text} + + +
+
+ + ); +}; + +export default CustomEdge; diff --git a/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx new file mode 100644 index 00000000..e0e8e794 --- /dev/null +++ b/examples/vite-app/src/examples/EdgeRenderer/CustomEdge2.tsx @@ -0,0 +1,51 @@ +import { FC } from 'react'; +import { EdgeProps, getBezierPath, EdgeLabelRenderer, useStore } from 'reactflow'; + +const CustomEdge: FC = ({ + id, + source, + target, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, +}) => { + const isConnectedNodeDragging = useStore((s) => + Array.from(s.nodeInternals.values()).find((n) => n.dragging && (target === n.id || source === n.id)) + ); + + const [edgePath, labelX, labelY] = getBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + return ( + <> + + + +
+ {data.text} +
+
+ + ); +}; + +export default CustomEdge; diff --git a/examples/vite-app/src/examples/EdgeRenderer/index.tsx b/examples/vite-app/src/examples/EdgeRenderer/index.tsx new file mode 100644 index 00000000..f4315360 --- /dev/null +++ b/examples/vite-app/src/examples/EdgeRenderer/index.tsx @@ -0,0 +1,206 @@ +import React, { MouseEvent, useCallback } from 'react'; +import ReactFlow, { + Controls, + Background, + MiniMap, + addEdge, + Connection, + Edge, + EdgeTypes, + MarkerType, + Node, + useEdgesState, + useNodesState, +} from 'reactflow'; + +import CustomEdge from './CustomEdge'; +import CustomEdge2 from './CustomEdge2'; + +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); +const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); +const onEdgeDoubleClick = (_: MouseEvent, edge: Edge) => console.log('dblclick', edge); +const onEdgeMouseEnter = (_: MouseEvent, edge: Edge) => console.log('enter', edge); +const onEdgeMouseMove = (_: MouseEvent, edge: Edge) => console.log('move', edge); +const onEdgeMouseLeave = (_: MouseEvent, edge: Edge) => console.log('leave', edge); + +const initialNodes: Node[] = [ + { + id: '1', + type: 'input', + data: { label: 'Input 1' }, + position: { x: 250, y: 0 }, + }, + { id: '2', data: { label: 'Node 2' }, position: { x: 150, y: 100 } }, + { id: '2a', data: { label: 'Node 2a' }, position: { x: 0, y: 180 } }, + { id: '2b', data: { label: 'Node 2b' }, position: { x: -40, y: 300 } }, + { id: '3', data: { label: 'Node 3' }, position: { x: 250, y: 200 } }, + { id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 300 } }, + { id: '3a', data: { label: 'Node 3a' }, position: { x: 150, y: 300 } }, + { id: '5', data: { label: 'Node 5' }, position: { x: 250, y: 400 } }, + { + id: '6', + type: 'output', + data: { label: 'Output 6' }, + position: { x: 50, y: 550 }, + }, + { + id: '7', + type: 'output', + data: { label: 'Output 7' }, + position: { x: 250, y: 550 }, + }, + { + id: '8', + type: 'output', + data: { label: 'Output 8' }, + position: { x: 525, y: 600 }, + }, + { + id: '9', + type: 'output', + data: { label: 'Output 9' }, + position: { x: 675, y: 500 }, + }, +]; + +const initialEdges: Edge[] = [ + { + id: 'e1-2', + source: '1', + target: '2', + label: 'bezier edge (default)', + className: 'normal-edge', + }, + { + id: 'e2-2a', + source: '2', + target: '2a', + type: 'smoothstep', + label: 'smoothstep edge', + }, + { + id: 'e2a-2b', + source: '2a', + target: '2b', + type: 'simplebezier', + label: 'simple bezier edge', + }, + { id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' }, + { + id: 'e3-4', + source: '3', + target: '4', + type: 'straight', + label: 'straight edge', + }, + { + id: 'e3-3a', + source: '3', + target: '3a', + type: 'straight', + label: 'label only edge', + style: { stroke: 'none' }, + }, + { + id: 'e3-5', + source: '4', + target: '5', + animated: true, + label: 'animated styled edge', + style: { stroke: 'red' }, + }, + { + id: 'e5-7', + source: '5', + target: '7', + label: 'label with styled bg', + labelBgPadding: [8, 4], + labelBgBorderRadius: 4, + labelBgStyle: { fill: '#FFCC00', color: '#fff', fillOpacity: 0.7 }, + markerEnd: { + type: MarkerType.ArrowClosed, + }, + }, + { + id: 'e5-8', + source: '5', + target: '8', + type: 'custom', + data: { text: 'custom edge' }, + }, + { + id: 'e5-9', + source: '5', + target: '9', + type: 'custom2', + data: { text: 'custom edge 2' }, + }, + { + id: 'e5-6', + source: '5', + target: '6', + label: ( + <> + i am using + + {''} + + + ), + labelStyle: { fill: 'red', fontWeight: 700 }, + style: { stroke: '#ffcc00' }, + markerEnd: { + type: MarkerType.Arrow, + color: '#FFCC00', + markerUnits: 'userSpaceOnUse', + width: 20, + height: 20, + strokeWidth: 2, + }, + markerStart: { + type: MarkerType.ArrowClosed, + color: '#FFCC00', + orient: 'auto-start-reverse', + markerUnits: 'userSpaceOnUse', + width: 20, + height: 20, + }, + }, +]; + +const edgeTypes: EdgeTypes = { + custom: CustomEdge, + custom2: CustomEdge2, +}; + +const EdgesFlow = () => { + const [nodes, , onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onConnect = useCallback((params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), [setEdges]); + + return ( + + + + + + ); +}; + +export default EdgesFlow; diff --git a/packages/core/package.json b/packages/core/package.json index ea025613..231578f0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,7 @@ "@reactflow/tsconfig": "workspace:*", "@types/node": "^18.7.16", "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", "react": "^18.2.0", "typescript": "^4.8.3" }, diff --git a/packages/core/src/components/EdgeLabelRenderer/index.tsx b/packages/core/src/components/EdgeLabelRenderer/index.tsx new file mode 100644 index 00000000..0647364a --- /dev/null +++ b/packages/core/src/components/EdgeLabelRenderer/index.tsx @@ -0,0 +1,15 @@ +import { useRef } from 'react'; +import type { ReactNode } from 'react'; +import { createPortal } from 'react-dom'; + +function EdgeLabelRenderer({ children }: { children: ReactNode }) { + const wrapperRef = useRef(document.getElementById('edgelabel-portal')); + + if (!wrapperRef.current) { + return null; + } + + return createPortal(children, wrapperRef.current); +} + +export default EdgeLabelRenderer; diff --git a/packages/core/src/container/GraphView/index.tsx b/packages/core/src/container/GraphView/index.tsx index 690b66f6..4190bed0 100644 --- a/packages/core/src/container/GraphView/index.tsx +++ b/packages/core/src/container/GraphView/index.tsx @@ -158,6 +158,8 @@ const GraphView = ({ disableKeyboardA11y={disableKeyboardA11y} rfId={rfId} /> +
+ Date: Wed, 2 Nov 2022 15:49:13 +0100 Subject: [PATCH 19/25] chore(changeset): add --- .changeset/few-donkeys-eat.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/few-donkeys-eat.md diff --git a/.changeset/few-donkeys-eat.md b/.changeset/few-donkeys-eat.md new file mode 100644 index 00000000..11d2b19e --- /dev/null +++ b/.changeset/few-donkeys-eat.md @@ -0,0 +1,6 @@ +--- +'@reactflow/core': minor +'reactflow': minor +--- + +Feat: Add edge label renderer From b25d499ec05b5c6f21ac552d03650eb37433552e Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 15:57:24 +0100 Subject: [PATCH 20/25] chore(changeset): add --- .changeset/four-islands-prove.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/four-islands-prove.md diff --git a/.changeset/four-islands-prove.md b/.changeset/four-islands-prove.md new file mode 100644 index 00000000..76260327 --- /dev/null +++ b/.changeset/four-islands-prove.md @@ -0,0 +1,6 @@ +--- +'@reactflow/core': minor +'reactflow': minor +--- + +Feat: add deleteElements helper function From 740659c0e788c7572d4a1e64e1d33d60712233fc Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 16:18:20 +0100 Subject: [PATCH 21/25] chore(changeset): add --- .changeset/famous-llamas-learn.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-llamas-learn.md diff --git a/.changeset/famous-llamas-learn.md b/.changeset/famous-llamas-learn.md new file mode 100644 index 00000000..3ea9d9f1 --- /dev/null +++ b/.changeset/famous-llamas-learn.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': patch +--- + +Refactor: put React Flow in isolated stacking context From 8f55c21cd7ae423b73aec5ac325d70de689a62fd Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 19:38:02 +0100 Subject: [PATCH 22/25] feat(useReactFlow): add intersection helpers --- examples/vite-app/src/App/index.tsx | 6 + .../src/examples/Intersection/index.tsx | 104 ++++++++++++++++++ .../src/examples/Intersection/style.css | 4 + packages/core/src/hooks/useReactFlow.ts | 63 +++++++++++ packages/core/src/types/instance.ts | 18 ++- packages/core/src/utils/graph.ts | 12 +- packages/core/src/utils/index.ts | 18 ++- 7 files changed, 214 insertions(+), 11 deletions(-) create mode 100644 examples/vite-app/src/examples/Intersection/index.tsx create mode 100644 examples/vite-app/src/examples/Intersection/style.css diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 31d41fd6..f0e32fb8 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -16,6 +16,7 @@ import Empty from '../examples/Empty'; import FloatingEdges from '../examples/FloatingEdges'; import Hidden from '../examples/Hidden'; import Interaction from '../examples/Interaction'; +import Intersection from '../examples/Intersection'; import Layouting from '../examples/Layouting'; import MultiFlows from '../examples/MultiFlows'; import NestedNodes from '../examples/NestedNodes'; @@ -132,6 +133,11 @@ const routes: IRoute[] = [ path: '/interaction', component: Interaction, }, + { + name: 'Intersection', + path: '/intersection', + component: Intersection, + }, { name: 'Interactive Minimap', path: '/interactive-minimap', diff --git a/examples/vite-app/src/examples/Intersection/index.tsx b/examples/vite-app/src/examples/Intersection/index.tsx new file mode 100644 index 00000000..1f82b308 --- /dev/null +++ b/examples/vite-app/src/examples/Intersection/index.tsx @@ -0,0 +1,104 @@ +import { useCallback, MouseEvent } from 'react'; +import ReactFlow, { + MiniMap, + Background, + Controls, + ReactFlowProvider, + Node, + Edge, + useReactFlow, + useNodesState, +} from 'reactflow'; + +import './style.css'; + +const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); +const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); + +const initialNodes: Node[] = [ + { + id: '1', + type: 'input', + data: { label: 'Node 1' }, + position: { x: 0, y: 0 }, + className: 'light', + style: { + width: 200, + height: 100, + }, + }, + { + id: '2', + data: { label: 'Node 2' }, + position: { x: 0, y: 150 }, + className: 'light', + }, + { + id: '3', + data: { label: 'Node 3' }, + position: { x: 250, y: 0 }, + className: 'light', + }, + { + id: '4', + data: { label: 'Node' }, + position: { x: 350, y: 150 }, + style: { + width: 50, + height: 50, + }, + className: 'light', + }, +]; + +const initialEdges: Edge[] = []; + +const defaultEdgeOptions = { zIndex: 0 }; + +const BasicFlow = () => { + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const { getIntersectingNodes, isNodeIntersecting } = useReactFlow(); + + const onNodeDrag = useCallback((_: MouseEvent, node: Node) => { + const intersections = getIntersectingNodes(node).map((n) => n.id); + const isIntersecting = isNodeIntersecting(node, { x: 0, y: 0, width: 100, height: 100 }); + + console.log(isIntersecting); + + setNodes((ns) => + ns.map((n) => ({ + ...n, + className: intersections.includes(n.id) ? 'highlight' : '', + })) + ); + }, []); + + return ( + + + + + + ); +}; + +export default function App() { + return ( + + + + ); +} diff --git a/examples/vite-app/src/examples/Intersection/style.css b/examples/vite-app/src/examples/Intersection/style.css new file mode 100644 index 00000000..16b4cc4c --- /dev/null +++ b/examples/vite-app/src/examples/Intersection/style.css @@ -0,0 +1,4 @@ +.react-flow__node.highlight { + background-color: #ff5050; + color: white; +} diff --git a/packages/core/src/hooks/useReactFlow.ts b/packages/core/src/hooks/useReactFlow.ts index 77415b9b..0801a772 100644 --- a/packages/core/src/hooks/useReactFlow.ts +++ b/packages/core/src/hooks/useReactFlow.ts @@ -13,8 +13,10 @@ import type { EdgeRemoveChange, NodeChange, Node, + Rect, } from '../types'; import { getConnectedEdges } from '../utils/graph'; +import { getOverlappingArea, isRectObject, nodeToRect } from '../utils'; /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export default function useReactFlow(): ReactFlowInstance { @@ -191,6 +193,63 @@ export default function useReactFlow(): ReactFlo } }, []); + const getNodeRect = useCallback( + ( + nodeOrRect: (Partial> & { id: Node['id'] }) | Rect + ): [Rect | null, Node | null | undefined, boolean] => { + const isRect = isRectObject(nodeOrRect); + const node = isRect ? null : store.getState().nodeInternals.get(nodeOrRect.id); + + if (!isRect && !node) { + [null, null, isRect]; + } + + const nodeRect = isRect ? nodeOrRect : nodeToRect(node!); + + return [nodeRect, node, isRect]; + }, + [] + ); + + const getIntersectingNodes = useCallback>( + (nodeOrRect, partially = true, nodes) => { + const [nodeRect, node, isRect] = getNodeRect(nodeOrRect); + + if (!nodeRect) { + return []; + } + + return (nodes || Array.from(store.getState().nodeInternals.values())).filter((n) => { + if (!isRect && (n.id === node!.id || !n.positionAbsolute)) { + return false; + } + + const currNodeRect = nodeToRect(n); + const overlappingArea = getOverlappingArea(currNodeRect, nodeRect); + const partiallyVisible = partially && overlappingArea > 0; + + return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!; + }); + }, + [] + ); + + const isNodeIntersecting = useCallback>( + (nodeOrRect, area, partially = true) => { + const [nodeRect] = getNodeRect(nodeOrRect); + + if (!nodeRect) { + return false; + } + + const overlappingArea = getOverlappingArea(nodeRect, area); + const partiallyVisible = partially && overlappingArea > 0; + + return partiallyVisible || overlappingArea >= nodeOrRect.width! * nodeOrRect.height!; + }, + [] + ); + return useMemo(() => { return { ...viewportHelper, @@ -204,6 +263,8 @@ export default function useReactFlow(): ReactFlo addEdges, toObject, deleteElements, + getIntersectingNodes, + isNodeIntersecting, }; }, [ viewportHelper, @@ -217,5 +278,7 @@ export default function useReactFlow(): ReactFlo addEdges, toObject, deleteElements, + getIntersectingNodes, + isNodeIntersecting, ]); } diff --git a/packages/core/src/types/instance.ts b/packages/core/src/types/instance.ts index 0c7200d2..7ff85606 100644 --- a/packages/core/src/types/instance.ts +++ b/packages/core/src/types/instance.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-namespace */ -import { ViewportHelperFunctions, Viewport, Node, Edge } from '.'; +import { ViewportHelperFunctions, Viewport, Node, Edge, Rect } from '.'; export type ReactFlowJsonObject = { nodes: Node[]; @@ -9,8 +9,8 @@ export type ReactFlowJsonObject = { }; export type DeleteElementsOptions = { - nodes?: (Partial & { id: Node['id'] })[], - edges?: (Partial & { id: Edge['id'] })[] + nodes?: (Partial & { id: Node['id'] })[]; + edges?: (Partial & { id: Edge['id'] })[]; }; export namespace Instance { export type GetNodes = () => Node[]; @@ -27,6 +27,16 @@ export namespace Instance { export type AddEdges = (payload: Edge[] | Edge) => void; export type ToObject = () => ReactFlowJsonObject; export type DeleteElements = ({ nodes, edges }: DeleteElementsOptions) => void; + export type GetIntersectingNodes = ( + node: (Partial> & { id: Node['id'] }) | Rect, + partially?: boolean, + nodes?: Node[] + ) => Node[]; + export type IsNodeIntersecting = ( + node: (Partial> & { id: Node['id'] }) | Rect, + area: Rect, + partially?: boolean + ) => boolean; } export type ReactFlowInstance = { @@ -40,5 +50,7 @@ export type ReactFlowInstance = { getEdge: Instance.GetEdge; toObject: Instance.ToObject; deleteElements: Instance.DeleteElements; + getIntersectingNodes: Instance.GetIntersectingNodes; + isNodeIntersecting: Instance.IsNodeIntersecting; viewportInitialized: boolean; } & Omit; diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 123fd13e..78569276 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Selection as D3Selection } from 'd3'; -import { boxToRect, clamp, devWarn, getBoundsOfBoxes, rectToBox } from '../utils'; +import { boxToRect, clamp, devWarn, getBoundsOfBoxes, getOverlappingArea, rectToBox } from '../utils'; import type { Node, Edge, Connection, EdgeMarkerType, Transform, XYPosition, Rect, NodeInternals } from '../types'; export const isEdge = (element: Node | Connection | Edge): element is Edge => @@ -161,12 +161,12 @@ export const getNodesInside = ( // set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute excludeNonSelectableNodes = false ): Node[] => { - const rBox = rectToBox({ + const paneRect = { x: (rect.x - tx) / tScale, y: (rect.y - ty) / tScale, width: rect.width / tScale, height: rect.height / tScale, - }); + }; const visibleNodes: Node[] = []; @@ -177,10 +177,8 @@ export const getNodesInside = ( return false; } - const nBox = rectToBox({ ...positionAbsolute, width: width || 0, height: height || 0 }); - const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x)); - const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y)); - const overlappingArea = Math.ceil(xOverlap * yOverlap); + const nodeRect = { ...positionAbsolute, width: width || 0, height: height || 0 }; + const overlappingArea = getOverlappingArea(paneRect, nodeRect); const notInitialized = typeof width === 'undefined' || typeof height === 'undefined' || width === null || height === null; diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index c3afe773..02cff5a0 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,4 +1,4 @@ -import type { Dimensions, XYPosition, CoordinateExtent, Box, Rect } from '../types'; +import type { Dimensions, Node, XYPosition, CoordinateExtent, Box, Rect } from '../types'; export const getDimensions = (node: HTMLDivElement): Dimensions => ({ width: node.offsetWidth, @@ -36,9 +36,25 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({ height: y2 - y, }); +export const nodeToRect = (node: Node): Rect => ({ + ...(node.positionAbsolute || { x: 0, y: 0 }), + width: node.width || 0, + height: node.height || 0, +}); + export const getBoundsOfRects = (rect1: Rect, rect2: Rect): Rect => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2))); +export const getOverlappingArea = (rectA: Rect, rectB: Rect): number => { + const xOverlap = Math.max(0, Math.min(rectA.x + rectA.width, rectB.x + rectB.width) - Math.max(rectA.x, rectB.x)); + const yOverlap = Math.max(0, Math.min(rectA.y + rectA.height, rectB.y + rectB.height) - Math.max(rectA.y, rectB.y)); + + return Math.ceil(xOverlap * yOverlap); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isRectObject = (obj: any): obj is Rect => !!obj.width && !!obj.height && !!obj.x && !!obj.y; + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ export const isNumeric = (n: any): n is number => !isNaN(n) && isFinite(n); From 4fc1253eadf9b7dd392d8dc2348f44fa8d08f931 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 20:33:00 +0100 Subject: [PATCH 23/25] chore(changeset): add --- .changeset/stale-games-fry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/stale-games-fry.md diff --git a/.changeset/stale-games-fry.md b/.changeset/stale-games-fry.md new file mode 100644 index 00000000..c6774e25 --- /dev/null +++ b/.changeset/stale-games-fry.md @@ -0,0 +1,5 @@ +--- +'@reactflow/core': minor +--- + +Feat: add intersection helpers From 59479713c5c7fe94c73b07001eea9db1b4b10242 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 20:33:34 +0100 Subject: [PATCH 24/25] chore(changeset): add --- .changeset/stale-games-fry.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/stale-games-fry.md b/.changeset/stale-games-fry.md index c6774e25..66ebe083 100644 --- a/.changeset/stale-games-fry.md +++ b/.changeset/stale-games-fry.md @@ -1,5 +1,6 @@ --- '@reactflow/core': minor +'reactflow': minor --- Feat: add intersection helpers From d54b6b861783727af8bf5f45ebe49c9086d82f44 Mon Sep 17 00:00:00 2001 From: moklick Date: Wed, 2 Nov 2022 20:44:10 +0100 Subject: [PATCH 25/25] chore(versions): bump --- .changeset/famous-llamas-learn.md | 5 ----- .changeset/few-donkeys-eat.md | 6 ------ .changeset/four-islands-prove.md | 6 ------ .changeset/stale-games-fry.md | 6 ------ .changeset/twenty-items-invite.md | 8 -------- examples/vite-app/package.json | 2 +- packages/background/CHANGELOG.md | 7 +++++++ packages/background/package.json | 2 +- packages/controls/CHANGELOG.md | 7 +++++++ packages/controls/package.json | 2 +- packages/core/CHANGELOG.md | 16 ++++++++++++++++ packages/core/package.json | 2 +- packages/minimap/CHANGELOG.md | 11 +++++++++++ packages/minimap/package.json | 2 +- packages/reactflow/CHANGELOG.md | 22 ++++++++++++++++++++++ packages/reactflow/package.json | 2 +- 16 files changed, 69 insertions(+), 37 deletions(-) delete mode 100644 .changeset/famous-llamas-learn.md delete mode 100644 .changeset/few-donkeys-eat.md delete mode 100644 .changeset/four-islands-prove.md delete mode 100644 .changeset/stale-games-fry.md delete mode 100644 .changeset/twenty-items-invite.md diff --git a/.changeset/famous-llamas-learn.md b/.changeset/famous-llamas-learn.md deleted file mode 100644 index 3ea9d9f1..00000000 --- a/.changeset/famous-llamas-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@reactflow/core': patch ---- - -Refactor: put React Flow in isolated stacking context diff --git a/.changeset/few-donkeys-eat.md b/.changeset/few-donkeys-eat.md deleted file mode 100644 index 11d2b19e..00000000 --- a/.changeset/few-donkeys-eat.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@reactflow/core': minor -'reactflow': minor ---- - -Feat: Add edge label renderer diff --git a/.changeset/four-islands-prove.md b/.changeset/four-islands-prove.md deleted file mode 100644 index 76260327..00000000 --- a/.changeset/four-islands-prove.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@reactflow/core': minor -'reactflow': minor ---- - -Feat: add deleteElements helper function diff --git a/.changeset/stale-games-fry.md b/.changeset/stale-games-fry.md deleted file mode 100644 index 66ebe083..00000000 --- a/.changeset/stale-games-fry.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@reactflow/core': minor -'reactflow': minor ---- - -Feat: add intersection helpers diff --git a/.changeset/twenty-items-invite.md b/.changeset/twenty-items-invite.md deleted file mode 100644 index 7252cb31..00000000 --- a/.changeset/twenty-items-invite.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@reactflow/examples': minor -'@reactflow/core': minor -'@reactflow/minimap': minor -'reactflow': minor ---- - -Feat: Add pan and zoom to mini map diff --git a/examples/vite-app/package.json b/examples/vite-app/package.json index 23bbc7fc..677f7289 100644 --- a/examples/vite-app/package.json +++ b/examples/vite-app/package.json @@ -1,7 +1,7 @@ { "name": "@reactflow/examples", "private": true, - "version": "0.0.2", + "version": "0.0.0", "type": "module", "scripts": { "dev": "vite --port 3000 --open", diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index 55bb8b95..7d2cfbff 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/background +## 11.0.4 + +### Patch Changes + +- Updated dependencies [[`740659c0`](https://github.com/wbkd/react-flow/commit/740659c0e788c7572d4a1e64e1d33d60712233fc), [`7902a3ce`](https://github.com/wbkd/react-flow/commit/7902a3ce3188426d5cd07cf0943a68f679e67948), [`b25d499e`](https://github.com/wbkd/react-flow/commit/b25d499ec05b5c6f21ac552d03650eb37433552e), [`4fc1253e`](https://github.com/wbkd/react-flow/commit/4fc1253eadf9b7dd392d8dc2348f44fa8d08f931), [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21)]: + - @reactflow/core@11.2.0 + ## 11.0.3 ### Patch Changes diff --git a/packages/background/package.json b/packages/background/package.json index 12031de8..b3575939 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/background", - "version": "11.0.3", + "version": "11.0.4", "description": "Background component with different variants for React Flow", "keywords": [ "react", diff --git a/packages/controls/CHANGELOG.md b/packages/controls/CHANGELOG.md index 1c2d37b1..92d15c50 100644 --- a/packages/controls/CHANGELOG.md +++ b/packages/controls/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/controls +## 11.0.4 + +### Patch Changes + +- Updated dependencies [[`740659c0`](https://github.com/wbkd/react-flow/commit/740659c0e788c7572d4a1e64e1d33d60712233fc), [`7902a3ce`](https://github.com/wbkd/react-flow/commit/7902a3ce3188426d5cd07cf0943a68f679e67948), [`b25d499e`](https://github.com/wbkd/react-flow/commit/b25d499ec05b5c6f21ac552d03650eb37433552e), [`4fc1253e`](https://github.com/wbkd/react-flow/commit/4fc1253eadf9b7dd392d8dc2348f44fa8d08f931), [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21)]: + - @reactflow/core@11.2.0 + ## 11.0.3 ### Patch Changes diff --git a/packages/controls/package.json b/packages/controls/package.json index e998486e..882da3b0 100644 --- a/packages/controls/package.json +++ b/packages/controls/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/controls", - "version": "11.0.3", + "version": "11.0.4", "description": "Component to control the viewport of a React Flow instance", "keywords": [ "react", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 7fb7463f..3c8382a4 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,21 @@ # @reactflow/core +## 11.2.0 + +### Minor Changes + +- [#2535](https://github.com/wbkd/react-flow/pull/2535) [`7902a3ce`](https://github.com/wbkd/react-flow/commit/7902a3ce3188426d5cd07cf0943a68f679e67948) Thanks [@moklick](https://github.com/moklick)! - Feat: Add edge label renderer + +- [#2536](https://github.com/wbkd/react-flow/pull/2536) [`b25d499e`](https://github.com/wbkd/react-flow/commit/b25d499ec05b5c6f21ac552d03650eb37433552e) Thanks [@pengfu](https://github.com/pengfu)! - Feat: add deleteElements helper function + +- [#2539](https://github.com/wbkd/react-flow/pull/2539) [`4fc1253e`](https://github.com/wbkd/react-flow/commit/4fc1253eadf9b7dd392d8dc2348f44fa8d08f931) Thanks [@moklick](https://github.com/moklick)! - Feat: add intersection helpers + +- [#2530](https://github.com/wbkd/react-flow/pull/2530) [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21) Thanks [@moklick](https://github.com/moklick)! - Feat: Add pan and zoom to mini map + +### Patch Changes + +- [#2538](https://github.com/wbkd/react-flow/pull/2538) [`740659c0`](https://github.com/wbkd/react-flow/commit/740659c0e788c7572d4a1e64e1d33d60712233fc) Thanks [@neo](https://github.com/neo)! - Refactor: put React Flow in isolated stacking context + ## 11.1.2 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 231578f0..aef400ec 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/core", - "version": "11.1.2", + "version": "11.2.0", "description": "Core components and util functions of React Flow.", "keywords": [ "react", diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md index 4fd440ab..c9660630 100644 --- a/packages/minimap/CHANGELOG.md +++ b/packages/minimap/CHANGELOG.md @@ -1,5 +1,16 @@ # @reactflow/minimap +## 11.1.0 + +### Minor Changes + +- [#2530](https://github.com/wbkd/react-flow/pull/2530) [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21) Thanks [@moklick](https://github.com/moklick)! - Feat: Add pan and zoom to mini map + +### Patch Changes + +- Updated dependencies [[`740659c0`](https://github.com/wbkd/react-flow/commit/740659c0e788c7572d4a1e64e1d33d60712233fc), [`7902a3ce`](https://github.com/wbkd/react-flow/commit/7902a3ce3188426d5cd07cf0943a68f679e67948), [`b25d499e`](https://github.com/wbkd/react-flow/commit/b25d499ec05b5c6f21ac552d03650eb37433552e), [`4fc1253e`](https://github.com/wbkd/react-flow/commit/4fc1253eadf9b7dd392d8dc2348f44fa8d08f931), [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21)]: + - @reactflow/core@11.2.0 + ## 11.0.3 ### Patch Changes diff --git a/packages/minimap/package.json b/packages/minimap/package.json index 100a12b0..49dcd7cc 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/minimap", - "version": "11.0.3", + "version": "11.1.0", "description": "Minimap component for React Flow.", "keywords": [ "react", diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md index a0d75bf3..10b93714 100644 --- a/packages/reactflow/CHANGELOG.md +++ b/packages/reactflow/CHANGELOG.md @@ -1,5 +1,27 @@ # reactflow +## 11.2.0 + +### Minor Changes + +- [#2535](https://github.com/wbkd/react-flow/pull/2535) [`7902a3ce`](https://github.com/wbkd/react-flow/commit/7902a3ce3188426d5cd07cf0943a68f679e67948) Thanks [@moklick](https://github.com/moklick)! - Feat: Add edge label renderer + +- [#2536](https://github.com/wbkd/react-flow/pull/2536) [`b25d499e`](https://github.com/wbkd/react-flow/commit/b25d499ec05b5c6f21ac552d03650eb37433552e) Thanks [@pengfu](https://github.com/pengfu)! - Feat: add deleteElements helper function + +- [#2539](https://github.com/wbkd/react-flow/pull/2539) [`4fc1253e`](https://github.com/wbkd/react-flow/commit/4fc1253eadf9b7dd392d8dc2348f44fa8d08f931) Thanks [@moklick](https://github.com/moklick)! - Feat: add intersection helpers + +- [#2530](https://github.com/wbkd/react-flow/pull/2530) [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21) Thanks [@moklick](https://github.com/moklick)! - Feat: Add pan and zoom to mini map + +### Patch Changes + +- [#2538](https://github.com/wbkd/react-flow/pull/2538) [`740659c0`](https://github.com/wbkd/react-flow/commit/740659c0e788c7572d4a1e64e1d33d60712233fc) Thanks [@neo](https://github.com/neo)! - Refactor: put React Flow in isolated stacking context + +- Updated dependencies [[`740659c0`](https://github.com/wbkd/react-flow/commit/740659c0e788c7572d4a1e64e1d33d60712233fc), [`7902a3ce`](https://github.com/wbkd/react-flow/commit/7902a3ce3188426d5cd07cf0943a68f679e67948), [`b25d499e`](https://github.com/wbkd/react-flow/commit/b25d499ec05b5c6f21ac552d03650eb37433552e), [`4fc1253e`](https://github.com/wbkd/react-flow/commit/4fc1253eadf9b7dd392d8dc2348f44fa8d08f931), [`8ba4dd5d`](https://github.com/wbkd/react-flow/commit/8ba4dd5d1d4b2e6f107c148de62aec0b688d8b21)]: + - @reactflow/core@11.2.0 + - @reactflow/minimap@11.1.0 + - @reactflow/background@11.0.4 + - @reactflow/controls@11.0.4 + ## 11.1.2 Housekeeping release with some fixes and some cleanups for the types. diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json index d26d4e28..3cd02d16 100644 --- a/packages/reactflow/package.json +++ b/packages/reactflow/package.json @@ -1,6 +1,6 @@ { "name": "reactflow", - "version": "11.1.2", + "version": "11.2.0", "description": "A highly customizable React library for building node-based editors and interactive flow charts", "keywords": [ "react",