diff --git a/examples/vite-app/package.json b/examples/vite-app/package.json
index 677f7289..2a05d338 100644
--- a/examples/vite-app/package.json
+++ b/examples/vite-app/package.json
@@ -13,6 +13,7 @@
"test-e2e": "start-server-and-test 'pnpm serve' http-get://localhost:3000 'pnpm test-e2e-cypress'"
},
"dependencies": {
+ "@reactflow/node-resizer": "workspace:^1.0.0",
"classcat": "^5.0.3",
"dagre": "^0.8.5",
"localforage": "^1.10.0",
diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx
index 5b596f48..bb68c028 100644
--- a/examples/vite-app/src/App/index.tsx
+++ b/examples/vite-app/src/App/index.tsx
@@ -20,6 +20,7 @@ import Intersection from '../examples/Intersection';
import Layouting from '../examples/Layouting';
import MultiFlows from '../examples/MultiFlows';
import NestedNodes from '../examples/NestedNodes';
+import NodeResizer from '../examples/NodeResizer';
import NodeTypeChange from '../examples/NodeTypeChange';
import NodeTypesObjectChange from '../examples/NodeTypesObjectChange';
import Overview from '../examples/Overview';
@@ -174,6 +175,11 @@ const routes: IRoute[] = [
path: '/node-toolbar',
component: NodeToolbar,
},
+ {
+ name: 'NodeResizer',
+ path: '/node-resizer',
+ component: NodeResizer,
+ },
{
name: 'Overview',
path: '/overview',
diff --git a/examples/vite-app/src/examples/Basic/index.tsx b/examples/vite-app/src/examples/Basic/index.tsx
index 92077b35..dfba56ef 100644
--- a/examples/vite-app/src/examples/Basic/index.tsx
+++ b/examples/vite-app/src/examples/Basic/index.tsx
@@ -94,7 +94,7 @@ const BasicFlow = () => {
fitView
defaultEdgeOptions={defaultEdgeOptions}
selectNodesOnDrag={false}
- nodeOrigin={nodeOrigin}
+ // nodeOrigin={nodeOrigin}
>
diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx
new file mode 100644
index 00000000..996e24c5
--- /dev/null
+++ b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx
@@ -0,0 +1,27 @@
+import { memo, FC, CSSProperties } from 'react';
+import { Handle, Position, NodeProps } from 'reactflow';
+
+import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer';
+import '@reactflow/node-resizer/dist/style.css';
+import ResizeIcon from './ResizeIcon';
+
+const controlStyle = {
+ background: 'transparent',
+ border: 'none',
+};
+
+const CustomNode: FC = ({ id, data }) => {
+ return (
+ <>
+
+
+
+
+
+ {data.label}
+
+ >
+ );
+};
+
+export default memo(CustomNode);
diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx
new file mode 100644
index 00000000..27833556
--- /dev/null
+++ b/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx
@@ -0,0 +1,19 @@
+import { memo, FC } from 'react';
+import { Handle, Position, NodeProps } from 'reactflow';
+
+import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer';
+import '@reactflow/node-resizer/dist/style.css';
+
+const CustomNode: FC = ({ id, data }) => {
+ return (
+ <>
+
+
+
+ {data.label}
+
+ >
+ );
+};
+
+export default memo(CustomNode);
diff --git a/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx b/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx
new file mode 100644
index 00000000..3a68cd9f
--- /dev/null
+++ b/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx
@@ -0,0 +1,18 @@
+import { memo, FC } from 'react';
+import { Handle, Position, NodeProps } from 'reactflow';
+
+import { NodeResizer } from '@reactflow/node-resizer';
+import '@reactflow/node-resizer/dist/style.css';
+
+const CustomNode: FC = ({ id, data, selected }) => {
+ return (
+ <>
+
+
+ {data.label}
+
+ >
+ );
+};
+
+export default memo(CustomNode);
diff --git a/examples/vite-app/src/examples/NodeResizer/ResizeIcon.tsx b/examples/vite-app/src/examples/NodeResizer/ResizeIcon.tsx
new file mode 100644
index 00000000..8db1cb92
--- /dev/null
+++ b/examples/vite-app/src/examples/NodeResizer/ResizeIcon.tsx
@@ -0,0 +1,24 @@
+function ResizeIcon() {
+ return (
+
+ );
+}
+
+export default ResizeIcon;
diff --git a/examples/vite-app/src/examples/NodeResizer/index.tsx b/examples/vite-app/src/examples/NodeResizer/index.tsx
new file mode 100644
index 00000000..683f9ab5
--- /dev/null
+++ b/examples/vite-app/src/examples/NodeResizer/index.tsx
@@ -0,0 +1,96 @@
+import { CSSProperties, useCallback, useState } from 'react';
+import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useEdgesState, Panel } from 'reactflow';
+
+import NodeResizerNode from './NodeResizerNode';
+import CustomResizer from './CustomResizer';
+import CustomResizer2 from './CustomResizer2';
+
+const nodeTypes = {
+ resizer: NodeResizerNode,
+ customResizer: CustomResizer,
+ customResizer2: CustomResizer2,
+};
+
+const initialEdges = [
+ {
+ id: 'e1-2',
+ source: '1',
+ target: '2',
+ },
+];
+
+const initialNodes = [
+ {
+ id: '1',
+ type: 'input',
+ data: { label: 'An input node' },
+ position: { x: 0, y: 0 },
+ sourcePosition: Position.Right,
+ },
+ {
+ id: '2',
+ type: 'resizer',
+ data: { label: 'default resizer' },
+ position: { x: 250, y: 0 },
+ style: {
+ width: 200,
+ height: 150,
+ border: '1px solid #222',
+ fontSize: 10,
+ },
+ },
+ {
+ id: '3',
+ type: 'customResizer',
+ data: { label: 'resize control with child component' },
+ position: { x: 250, y: 200 },
+ style: { border: '1px solid #222', fontSize: 10, width: 100 },
+ },
+ {
+ id: '4',
+ type: 'customResizer2',
+ data: { label: 'resize controls' },
+ position: { x: 100, y: 150 },
+ style: { border: '1px solid #222', fontSize: 10 },
+ },
+ {
+ id: '5',
+ type: 'customResizer2',
+ data: { label: 'min width and height' },
+ position: { x: 250, y: 250 },
+ style: { border: '1px solid #222', fontSize: 10 },
+ },
+];
+
+const CustomNodeFlow = () => {
+ const [snapToGrid, setSnapToGrid] = useState(false);
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+
+ const onConnect = useCallback(
+ (connection: Connection) => setEdges((eds) => addEdge({ ...connection }, eds)),
+ [setEdges]
+ );
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default CustomNodeFlow;
diff --git a/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx b/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx
index 4f8ab270..ca2ac0b7 100644
--- a/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx
+++ b/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx
@@ -4,7 +4,7 @@ import { Handle, Position, NodeProps, NodeToolbar } from 'reactflow';
const CustomNode: FC = ({ id, data }) => {
return (
<>
-
+
diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md
index 1713e819..bdbef9c7 100644
--- a/packages/background/CHANGELOG.md
+++ b/packages/background/CHANGELOG.md
@@ -1,5 +1,12 @@
# @reactflow/background
+## 11.0.7
+
+### Patch Changes
+
+- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]:
+ - @reactflow/core@11.3.2
+
## 11.0.6
### Patch Changes
diff --git a/packages/background/package.json b/packages/background/package.json
index 7df6af43..5011906c 100644
--- a/packages/background/package.json
+++ b/packages/background/package.json
@@ -1,6 +1,6 @@
{
"name": "@reactflow/background",
- "version": "11.0.6",
+ "version": "11.0.7",
"description": "Background component with different variants for React Flow",
"keywords": [
"react",
diff --git a/packages/controls/CHANGELOG.md b/packages/controls/CHANGELOG.md
index 40de22b6..9df35ad9 100644
--- a/packages/controls/CHANGELOG.md
+++ b/packages/controls/CHANGELOG.md
@@ -1,5 +1,12 @@
# @reactflow/controls
+## 11.0.7
+
+### Patch Changes
+
+- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]:
+ - @reactflow/core@11.3.2
+
## 11.0.6
### Patch Changes
diff --git a/packages/controls/package.json b/packages/controls/package.json
index 91d19ade..7458ee83 100644
--- a/packages/controls/package.json
+++ b/packages/controls/package.json
@@ -1,6 +1,6 @@
{
"name": "@reactflow/controls",
- "version": "11.0.6",
+ "version": "11.0.7",
"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 59a8cfca..b0b48f29 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,17 @@
# @reactflow/core
+## 11.3.2
+
+In this update we did some changes so that we could implement the new [``](https://reactflow.dev/docs/api/nodes/node-resizer/) component more smoothly.
+
+### Patch Changes
+
+- [#2646](https://github.com/wbkd/react-flow/pull/2646) [`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a) - Fix getRectOfNodes
+- [#2648](https://github.com/wbkd/react-flow/pull/2648) [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae) - Allow middle mouse pan over edges
+- [#2647](https://github.com/wbkd/react-flow/pull/2647) [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d) Thanks [@neo](https://github.com/neo)! - Invalidate node trying to connect itself with the same handle
+- [#2626](https://github.com/wbkd/react-flow/pull/2626) [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18) - Export the useNodeId hook, refactor how changes are applied and create a helper function
+- [#2642](https://github.com/wbkd/react-flow/pull/2642) [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6) - Ignore key events for nodes when input is focused
+
## 11.3.1
### Patch Changes
diff --git a/packages/core/package.json b/packages/core/package.json
index 58f31d51..9bd0f67b 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
{
"name": "@reactflow/core",
- "version": "11.3.1",
+ "version": "11.3.2",
"description": "Core components and util functions of React Flow.",
"keywords": [
"react",
diff --git a/packages/core/src/components/Handle/handler.ts b/packages/core/src/components/Handle/handler.ts
index ff726d30..0052120f 100644
--- a/packages/core/src/components/Handle/handler.ts
+++ b/packages/core/src/components/Handle/handler.ts
@@ -60,7 +60,7 @@ export function checkElementBelowIsValid(
const isValid =
connectionMode === ConnectionMode.Strict
? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget)
- : true;
+ : elementBelowNodeId !== nodeId || elementBelowHandleId !== handleId;
if (isValid) {
result.isValid = isValidConnection(connection);
diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx
index c47035a9..a2d0502f 100644
--- a/packages/core/src/components/Handle/index.tsx
+++ b/packages/core/src/components/Handle/index.tsx
@@ -1,9 +1,9 @@
-import { memo, useContext, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent } from 'react';
+import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent } from 'react';
import cc from 'classcat';
import shallow from 'zustand/shallow';
import { useStore, useStoreApi } from '../../hooks/useStore';
-import NodeIdContext from '../../contexts/NodeIdContext';
+import { useNodeId } from '../../contexts/NodeIdContext';
import { checkElementBelowIsValid, handleMouseDown } from './handler';
import { getHostForElement } from '../../utils';
import { addEdge } from '../../utils/graph';
@@ -37,7 +37,9 @@ const Handle = forwardRef(
ref
) => {
const store = useStoreApi();
- const nodeId = useContext(NodeIdContext) as string;
+
+ // @fixme: remove type assertion and handle nodeId === null
+ const nodeId = useNodeId() as string;
const { connectionStartHandle, connectOnClick, noPanClassName } = useStore(selector, shallow);
const handleId = id || null;
diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx
index afb1f761..70b36ddb 100644
--- a/packages/core/src/components/Nodes/wrapNode.tsx
+++ b/packages/core/src/components/Nodes/wrapNode.tsx
@@ -8,7 +8,7 @@ import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions';
import useDrag from '../../hooks/useDrag';
import useUpdateNodePositions from '../../hooks/useUpdateNodePositions';
import { getMouseHandler, handleNodeClick } from './utils';
-import { elementSelectionKeys } from '../../utils';
+import { elementSelectionKeys, isInputDOMNode } from '../../utils';
import type { NodeProps, WrapNodeProps, XYPosition } from '../../types';
export const arrowKeyDiffs: Record = {
@@ -84,7 +84,12 @@ export default (NodeComponent: ComponentType) => {
};
const onKeyDown = (event: KeyboardEvent) => {
+ if (isInputDOMNode(event)) {
+ return;
+ }
+
const { snapGrid, snapToGrid } = store.getState();
+
if (elementSelectionKeys.includes(event.key) && isSelectable) {
const unselect = event.key === 'Escape';
if (unselect) {
diff --git a/packages/core/src/container/NodeRenderer/index.tsx b/packages/core/src/container/NodeRenderer/index.tsx
index 70a62dfe..389cd2d0 100644
--- a/packages/core/src/container/NodeRenderer/index.tsx
+++ b/packages/core/src/container/NodeRenderer/index.tsx
@@ -57,6 +57,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
nodeElement: entry.target as HTMLDivElement,
forceUpdate: true,
}));
+
updateNodeDimensions(updates);
});
diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx
index a5783a7a..f94bbe58 100644
--- a/packages/core/src/container/ZoomPane/index.tsx
+++ b/packages/core/src/container/ZoomPane/index.tsx
@@ -228,7 +228,11 @@ const ZoomPane = ({
const zoomScroll = zoomActivationKeyPressed || zoomOnScroll;
const pinchZoom = zoomOnPinch && event.ctrlKey;
- if (event.button === 1 && event.type === 'mousedown' && event.target.closest(`.react-flow__node`)) {
+ if (
+ event.button === 1 &&
+ event.type === 'mousedown' &&
+ (isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge'))
+ ) {
return true;
}
diff --git a/packages/core/src/contexts/NodeIdContext.ts b/packages/core/src/contexts/NodeIdContext.ts
index c43a1c3b..28a14b78 100644
--- a/packages/core/src/contexts/NodeIdContext.ts
+++ b/packages/core/src/contexts/NodeIdContext.ts
@@ -1,7 +1,12 @@
-import { createContext } from 'react';
+import { createContext, useContext } from 'react';
export const NodeIdContext = createContext(null);
export const Provider = NodeIdContext.Provider;
export const Consumer = NodeIdContext.Consumer;
+export const useNodeId = (): string | null => {
+ const nodeId = useContext(NodeIdContext);
+ return nodeId;
+};
+
export default NodeIdContext;
diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts
index 47c2113a..3ab1d0bb 100644
--- a/packages/core/src/hooks/useDrag/index.ts
+++ b/packages/core/src/hooks/useDrag/index.ts
@@ -1,15 +1,14 @@
-import { useEffect, useRef, useState, useCallback } from 'react';
+import { useEffect, useRef, useState } from 'react';
import type { RefObject, MouseEvent } from 'react';
import { drag } from 'd3-drag';
import { select } from 'd3-selection';
-import type { D3DragEvent, SubjectPosition } from 'd3';
import { useStoreApi } from '../../hooks/useStore';
import { getDragItems, getEventHandlerParams, hasSelector, calcNextPosition } from './utils';
import { handleNodeClick } from '../../components/Nodes/utils';
-import type { NodeDragItem, Node, SelectionDragHandler } from '../../types';
+import useGetPointerPosition from '../useGetPointerPosition';
+import type { NodeDragItem, Node, SelectionDragHandler, UseDragEvent } from '../../types';
-export type UseDragEvent = D3DragEvent;
export type UseDragData = { dx: number; dy: number };
type UseDragParams = {
@@ -40,24 +39,7 @@ function useDrag({
const dragItems = useRef();
const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null });
- // returns the pointer position projected to the RF coordinate system
- const getPointerPosition = useCallback(({ sourceEvent }: UseDragEvent) => {
- const { transform, snapGrid, snapToGrid } = store.getState();
- const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
- const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
-
- const pointerPos = {
- x: (x - transform[0]) / transform[2],
- y: (y - transform[1]) / transform[2],
- };
-
- // we need the snapped position in order to be able to skip unnecessary drag events
- return {
- xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x,
- ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y,
- ...pointerPos,
- };
- }, []);
+ const getPointerPosition = useGetPointerPosition();
useEffect(() => {
if (nodeRef?.current) {
diff --git a/packages/core/src/hooks/useDrag/utils.ts b/packages/core/src/hooks/useDrag/utils.ts
index 61b0d3f9..74484baf 100644
--- a/packages/core/src/hooks/useDrag/utils.ts
+++ b/packages/core/src/hooks/useDrag/utils.ts
@@ -1,6 +1,6 @@
import type { RefObject } from 'react';
-import { clampPosition, devWarn } from '../../utils';
+import { clampPosition, devWarn, isNumeric } from '../../utils';
import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types';
import { getNodePositionWithOrigin } from '../../utils/graph';
@@ -69,17 +69,14 @@ export function calcNextPosition(
if (node.extent === 'parent') {
if (node.parentNode && node.width && node.height) {
const parent = nodeInternals.get(node.parentNode);
- const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin);
+ const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, nodeOrigin).positionAbsolute;
currentExtent =
- parentPosition.positionAbsolute && parent?.width && parent?.height
+ parent && isNumeric(parentX) && isNumeric(parentY) && isNumeric(parent.width) && isNumeric(parent.height)
? [
+ [parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]],
[
- parentPosition.positionAbsolute.x + node.width * nodeOrigin[0],
- parentPosition.positionAbsolute.y + node.height * nodeOrigin[1],
- ],
- [
- parentPosition.positionAbsolute.x + parent.width - node.width + node.width * nodeOrigin[0],
- parentPosition.positionAbsolute.y + parent.height - node.height + node.height * nodeOrigin[1],
+ parentX + parent.width - node.width + node.width * nodeOrigin[0],
+ parentY + parent.height - node.height + node.height * nodeOrigin[1],
],
]
: currentExtent;
@@ -90,8 +87,7 @@ export function calcNextPosition(
}
} else if (node.extent && node.parentNode) {
const parent = nodeInternals.get(node.parentNode);
- const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin);
- const { x: parentX, y: parentY } = parentPosition.positionAbsolute;
+ const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, nodeOrigin).positionAbsolute;
currentExtent = [
[node.extent[0][0] + parentX, node.extent[0][1] + parentY],
[node.extent[1][0] + parentX, node.extent[1][1] + parentY],
diff --git a/packages/core/src/hooks/useGetPointerPosition.ts b/packages/core/src/hooks/useGetPointerPosition.ts
new file mode 100644
index 00000000..38c12470
--- /dev/null
+++ b/packages/core/src/hooks/useGetPointerPosition.ts
@@ -0,0 +1,31 @@
+import { useCallback } from 'react';
+
+import { useStoreApi } from './useStore';
+import type { UseDragEvent } from '../types';
+
+function useGetPointerPosition() {
+ const store = useStoreApi();
+
+ // returns the pointer position projected to the RF coordinate system
+ const getPointerPosition = useCallback(({ sourceEvent }: UseDragEvent) => {
+ const { transform, snapGrid, snapToGrid } = store.getState();
+ const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX;
+ const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY;
+
+ const pointerPos = {
+ x: (x - transform[0]) / transform[2],
+ y: (y - transform[1]) / transform[2],
+ };
+
+ // we need the snapped position in order to be able to skip unnecessary drag events
+ return {
+ xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x,
+ ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y,
+ ...pointerPos,
+ };
+ }, []);
+
+ return getPointerPosition;
+}
+
+export default useGetPointerPosition;
diff --git a/packages/core/src/hooks/useKeyPress.ts b/packages/core/src/hooks/useKeyPress.ts
index 4273feaa..20dcf37e 100644
--- a/packages/core/src/hooks/useKeyPress.ts
+++ b/packages/core/src/hooks/useKeyPress.ts
@@ -1,5 +1,6 @@
import { useState, useEffect, useRef, useMemo } from 'react';
+import { isInputDOMNode } from '../utils';
import type { KeyCode } from '../types';
type Keys = Array;
@@ -106,14 +107,3 @@ function isMatchingKey(keyCodes: Array, pressedKeys: PressedKeys, isUp: bo
function useKeyOrCode(eventCode: string, keysToWatch: KeyCode): KeyOrCode {
return keysToWatch.includes(eventCode) ? 'code' : 'key';
}
-
-function isInputDOMNode(event: KeyboardEvent): boolean {
- // using composed path for handling shadow dom
- const target = (event.composedPath?.()[0] || event.target) as HTMLElement;
-
- return (
- ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) ||
- target?.hasAttribute('contenteditable') ||
- !!target?.closest('.nokey')
- );
-}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index d51540e9..c4a5b19e 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -38,5 +38,7 @@ export { useStore, useStoreApi } from './hooks/useStore';
export { default as useOnViewportChange } from './hooks/useOnViewportChange';
export { default as useOnSelectionChange } from './hooks/useOnSelectionChange';
export { default as useNodesInitialized } from './hooks/useNodesInitialized';
+export { default as useGetPointerPosition } from './hooks/useGetPointerPosition';
+export { useNodeId } from './contexts/NodeIdContext';
export * from './types';
diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts
index 5ffd64c3..bdb02137 100644
--- a/packages/core/src/store/index.ts
+++ b/packages/core/src/store/index.ts
@@ -17,6 +17,7 @@ import type {
NodePositionChange,
NodeDragItem,
UnselectNodesAndEdgesParams,
+ NodeChange,
} from '../types';
const createRFStore = () =>
@@ -103,35 +104,40 @@ const createRFStore = () =>
}
},
updateNodePositions: (nodeDragItems: NodeDragItem[] | Node[], positionChanged = true, dragging = false) => {
+ const { triggerNodeChanges } = get();
+
+ const changes = nodeDragItems.map((node) => {
+ const change: NodePositionChange = {
+ id: node.id,
+ type: 'position',
+ dragging,
+ };
+
+ if (positionChanged) {
+ change.positionAbsolute = node.positionAbsolute;
+ change.position = node.position;
+ }
+
+ return change;
+ });
+
+ triggerNodeChanges(changes);
+ },
+
+ triggerNodeChanges: (changes: NodeChange[]) => {
const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin } = get();
- if (hasDefaultNodes || onNodesChange) {
- const changes = nodeDragItems.map((node) => {
- const change: NodePositionChange = {
- id: node.id,
- type: 'position',
- dragging,
- };
-
- if (positionChanged) {
- change.positionAbsolute = node.positionAbsolute;
- change.position = node.position;
- }
-
- return change;
- });
-
- if (changes?.length) {
- if (hasDefaultNodes) {
- const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values()));
- const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin);
- set({ nodeInternals: nextNodeInternals });
- }
-
- onNodesChange?.(changes);
+ if (changes?.length) {
+ if (hasDefaultNodes) {
+ const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values()));
+ const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin);
+ set({ nodeInternals: nextNodeInternals });
}
+
+ onNodesChange?.(changes);
}
},
+
addSelectedNodes: (selectedNodeIds: string[]) => {
const { multiSelectionActive, nodeInternals, edges } = get();
let changedNodes: NodeSelectionChange[];
diff --git a/packages/core/src/styles/style.css b/packages/core/src/styles/style.css
index 54b37f49..9850564d 100644
--- a/packages/core/src/styles/style.css
+++ b/packages/core/src/styles/style.css
@@ -13,6 +13,13 @@
}
}
+.react-flow__node.selectable {
+ &:focus,
+ &:focus-visible {
+ outline: none;
+ }
+}
+
.react-flow__node-default,
.react-flow__node-input,
.react-flow__node-output,
@@ -37,7 +44,6 @@
&:focus,
&:focus-visible {
box-shadow: 0 0 0 0.5px #1a192b;
- outline: none;
}
}
}
diff --git a/packages/core/src/types/changes.ts b/packages/core/src/types/changes.ts
index b4395803..375c8bf2 100644
--- a/packages/core/src/types/changes.ts
+++ b/packages/core/src/types/changes.ts
@@ -7,7 +7,9 @@ import type { Edge } from './edges';
export type NodeDimensionChange = {
id: string;
type: 'dimensions';
- dimensions: Dimensions;
+ dimensions?: Dimensions;
+ updateStyle?: boolean;
+ resizing?: boolean;
};
export type NodePositionChange = {
diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts
index b18f4d32..472df101 100644
--- a/packages/core/src/types/general.ts
+++ b/packages/core/src/types/general.ts
@@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { MouseEvent as ReactMouseEvent, ComponentType, MemoExoticComponent } from 'react';
-import type { Selection as D3Selection, ZoomBehavior } from 'd3';
+import type { D3DragEvent, Selection as D3Selection, SubjectPosition, ZoomBehavior } from 'd3';
import type { XYPosition, Rect, Transform, CoordinateExtent } from './utils';
-import type { NodeChange, EdgeChange } from './changes';
+import type { NodeChange, EdgeChange, NodePositionChange } from './changes';
import type {
Node,
NodeInternals,
@@ -226,6 +226,7 @@ export type ReactFlowActions = {
setNodeExtent: (nodeExtent: CoordinateExtent) => void;
cancelConnection: () => void;
reset: () => void;
+ triggerNodeChanges: (changes: NodeChange[]) => void;
};
export type ReactFlowState = ReactFlowStore & ReactFlowActions;
@@ -245,3 +246,5 @@ export type ProOptions = {
account?: string;
hideAttribution: boolean;
};
+
+export type UseDragEvent = D3DragEvent;
diff --git a/packages/core/src/types/nodes.ts b/packages/core/src/types/nodes.ts
index 39d8755c..c27e79cf 100644
--- a/packages/core/src/types/nodes.ts
+++ b/packages/core/src/types/nodes.ts
@@ -31,6 +31,7 @@ export type Node = {
positionAbsolute?: XYPosition;
ariaLabel?: string;
focusable?: boolean;
+ resizing?: boolean;
// only used internally
[internalsSymbol]?: {
diff --git a/packages/core/src/utils/changes.ts b/packages/core/src/utils/changes.ts
index 897c1ffb..5deb3fdc 100644
--- a/packages/core/src/utils/changes.ts
+++ b/packages/core/src/utils/changes.ts
@@ -50,58 +50,67 @@ function applyChanges(changes: any[], elements: any[]): any[] {
const initElements: any[] = changes.filter((c) => c.type === 'add').map((c) => c.item);
return elements.reduce((res: any[], item: any) => {
- const currentChange = changes.find((c) => c.id === item.id);
+ const currentChanges = changes.filter((c) => c.id === item.id);
- if (currentChange) {
- switch (currentChange.type) {
- case 'select': {
- res.push({ ...item, selected: currentChange.selected });
- return res;
- }
- case 'position': {
- const updateItem = { ...item };
+ if (currentChanges.length === 0) {
+ res.push(item);
+ return res;
+ }
- if (typeof currentChange.position !== 'undefined') {
- updateItem.position = currentChange.position;
+ const updateItem = { ...item };
+
+ for (const currentChange of currentChanges) {
+ if (currentChange) {
+ switch (currentChange.type) {
+ case 'select': {
+ updateItem.selected = currentChange.selected;
+ break;
}
+ case 'position': {
+ if (typeof currentChange.position !== 'undefined') {
+ updateItem.position = currentChange.position;
+ }
- if (typeof currentChange.positionAbsolute !== 'undefined') {
- updateItem.positionAbsolute = currentChange.positionAbsolute;
+ if (typeof currentChange.positionAbsolute !== 'undefined') {
+ updateItem.positionAbsolute = currentChange.positionAbsolute;
+ }
+
+ if (typeof currentChange.dragging !== 'undefined') {
+ updateItem.dragging = currentChange.dragging;
+ }
+
+ if (updateItem.expandParent) {
+ handleParentExpand(res, updateItem);
+ }
+ break;
}
+ case 'dimensions': {
+ if (typeof currentChange.dimensions !== 'undefined') {
+ updateItem.width = currentChange.dimensions.width;
+ updateItem.height = currentChange.dimensions.height;
+ }
- if (typeof currentChange.dragging !== 'undefined') {
- updateItem.dragging = currentChange.dragging;
+ if (typeof currentChange.updateStyle !== 'undefined') {
+ updateItem.style = { ...(updateItem.style || {}), ...currentChange.dimensions };
+ }
+
+ if (typeof currentChange.resizing === 'boolean') {
+ updateItem.resizing = currentChange.resizing;
+ }
+
+ if (updateItem.expandParent) {
+ handleParentExpand(res, updateItem);
+ }
+ break;
}
-
- if (updateItem.expandParent) {
- handleParentExpand(res, updateItem);
+ case 'remove': {
+ return res;
}
-
- res.push(updateItem);
- return res;
- }
- case 'dimensions': {
- const updateItem = { ...item };
-
- if (typeof currentChange.dimensions !== 'undefined') {
- updateItem.width = currentChange.dimensions.width;
- updateItem.height = currentChange.dimensions.height;
- }
-
- if (updateItem.expandParent) {
- handleParentExpand(res, updateItem);
- }
-
- res.push(updateItem);
- return res;
- }
- case 'remove': {
- return res;
}
}
}
- res.push(item);
+ res.push(updateItem);
return res;
}, initElements);
}
diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts
index ea390967..9a3d4c42 100644
--- a/packages/core/src/utils/graph.ts
+++ b/packages/core/src/utils/graph.ts
@@ -2,7 +2,7 @@
import type { Selection as D3Selection } from 'd3';
import { boxToRect, clamp, devWarn, getBoundsOfBoxes, getOverlappingArea, rectToBox } from '../utils';
-import type {
+import {
Node,
Edge,
Connection,
@@ -156,18 +156,22 @@ export const getNodePositionWithOrigin = (
};
}
- const offset: XYPosition = {
- x: (node.width ?? 0) * nodeOrigin[0],
- y: (node.height ?? 0) * nodeOrigin[1],
+ const offsetX = (node.width ?? 0) * nodeOrigin[0];
+ const offsetY = (node.height ?? 0) * nodeOrigin[1];
+
+ const position: XYPosition = {
+ x: node.position.x - offsetX,
+ y: node.position.y - offsetY,
};
return {
- x: node.position.x - offset.x,
- y: node.position.y - offset.y,
- positionAbsolute: {
- x: (node.positionAbsolute?.x ?? 0) - offset.x,
- y: (node.positionAbsolute?.y ?? 0) - offset.y,
- },
+ ...position,
+ positionAbsolute: node.positionAbsolute
+ ? {
+ x: node.positionAbsolute.x - offsetX,
+ y: node.positionAbsolute.y - offsetY,
+ }
+ : position,
};
};
@@ -178,15 +182,12 @@ export const getRectOfNodes = (nodes: Node[], nodeOrigin: NodeOrigin = [0, 0]):
const box = nodes.reduce(
(currBox, node) => {
- const { positionAbsolute, ...position } = getNodePositionWithOrigin(node, nodeOrigin);
- const nodeX = positionAbsolute ? positionAbsolute.x : position.x;
- const nodeY = positionAbsolute ? positionAbsolute.y : position.y;
-
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
return getBoundsOfBoxes(
currBox,
rectToBox({
- x: nodeX,
- y: nodeY,
+ x,
+ y,
width: node.width || 0,
height: node.height || 0,
})
diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts
index d869a81a..f6e9d994 100644
--- a/packages/core/src/utils/index.ts
+++ b/packages/core/src/utils/index.ts
@@ -1,3 +1,4 @@
+import type { KeyboardEvent as ReactKeyboardEvent } from 'react';
import type { Dimensions, Node, XYPosition, CoordinateExtent, Box, Rect } from '../types';
export const getDimensions = (node: HTMLDivElement): Dimensions => ({
@@ -69,3 +70,18 @@ export const devWarn = (message: string) => {
console.warn(`[React Flow]: ${message}`);
}
};
+
+const isReactKeyboardEvent = (event: KeyboardEvent | ReactKeyboardEvent): event is ReactKeyboardEvent =>
+ 'nativeEvent' in event;
+
+export function isInputDOMNode(event: KeyboardEvent | ReactKeyboardEvent): boolean {
+ const kbEvent = isReactKeyboardEvent(event) ? event.nativeEvent : event;
+ // using composed path for handling shadow dom
+ const target = (kbEvent.composedPath?.()?.[0] || event.target) as HTMLElement;
+
+ return (
+ ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) ||
+ target?.hasAttribute('contenteditable') ||
+ !!target?.closest('.nokey')
+ );
+}
diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md
index b9cbb0be..a98b4072 100644
--- a/packages/minimap/CHANGELOG.md
+++ b/packages/minimap/CHANGELOG.md
@@ -1,5 +1,14 @@
# @reactflow/minimap
+## 11.2.3
+
+### Patch Changes
+
+- [#2646](https://github.com/wbkd/react-flow/pull/2646) [`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a) - Cleanup get node position with origin usage
+
+- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]:
+ - @reactflow/core@11.3.2
+
## 11.2.2
### Patch Changes
diff --git a/packages/minimap/package.json b/packages/minimap/package.json
index d6dc1331..ce4db650 100644
--- a/packages/minimap/package.json
+++ b/packages/minimap/package.json
@@ -1,6 +1,6 @@
{
"name": "@reactflow/minimap",
- "version": "11.2.2",
+ "version": "11.2.3",
"description": "Minimap component for React Flow.",
"keywords": [
"react",
diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx
index 722f1423..b1d4c555 100644
--- a/packages/minimap/src/MiniMap.tsx
+++ b/packages/minimap/src/MiniMap.tsx
@@ -167,13 +167,13 @@ function MiniMap({
>
{ariaLabel && {ariaLabel}}
{nodes.map((node) => {
- const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin);
+ const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
return (
`](https://reactflow.dev/docs/api/nodes/node-resizer/) component and a [``](https://reactflow.dev/docs/api/nodes/node-resizer/#noderesizecontrol--component) component.
+
+### Major Changes
+
+- [#2626](https://github.com/wbkd/react-flow/pull/2626) [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18) - Add a node resizer component that can be used to resize a custom node
+
+### Patch Changes
+
+- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]:
+ - @reactflow/core@11.3.2
diff --git a/packages/node-resizer/README.md b/packages/node-resizer/README.md
new file mode 100644
index 00000000..d808473e
--- /dev/null
+++ b/packages/node-resizer/README.md
@@ -0,0 +1,10 @@
+# @reactflow/node-resizer
+
+A resizer component for React Flow that can be attached to a node.
+
+## Installation
+
+```sh
+npm install @reactflow/node-resizer
+```
+
diff --git a/packages/node-resizer/package.json b/packages/node-resizer/package.json
new file mode 100644
index 00000000..43b00744
--- /dev/null
+++ b/packages/node-resizer/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "@reactflow/node-resizer",
+ "version": "1.0.0",
+ "description": "A helper component for resizing nodes.",
+ "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": [
+ "*.css"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/wbkd/react-flow.git",
+ "directory": "packages/node-resizer"
+ },
+ "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": {
+ "@reactflow/core": "workspace:*",
+ "classcat": "^5.0.4",
+ "d3-drag": "^3.0.0",
+ "d3-selection": "^3.0.0",
+ "zustand": "^4.1.1"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ },
+ "devDependencies": {
+ "@reactflow/eslint-config": "workspace:*",
+ "@reactflow/rollup-config": "workspace:*",
+ "@reactflow/tsconfig": "workspace:*",
+ "@types/d3-drag": "^3.0.1",
+ "@types/d3-selection": "^3.0.3",
+ "@types/node": "^18.7.16",
+ "@types/react": "^18.0.19",
+ "@types/react-dom": "^18.0.6",
+ "react": "^18.2.0",
+ "typescript": "^4.8.3"
+ },
+ "rollup": {
+ "globals": {
+ "zustand": "Zustand",
+ "zustand/shallow": "zustandShallow",
+ "classcat": "cc"
+ },
+ "name": "ReactFlowNodeResizer"
+ }
+}
diff --git a/packages/node-resizer/src/NodeResizer.tsx b/packages/node-resizer/src/NodeResizer.tsx
new file mode 100644
index 00000000..1b2aee1f
--- /dev/null
+++ b/packages/node-resizer/src/NodeResizer.tsx
@@ -0,0 +1,45 @@
+import ResizeControl from './ResizeControl';
+import { ControlPosition, NodeResizerProps, ResizeControlVariant, ControlLinePosition } from './types';
+
+const handleControls: ControlPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right'];
+const lineControls: ControlLinePosition[] = ['top', 'right', 'bottom', 'left'];
+
+export default function NodeResizer({
+ nodeId,
+ isVisible = true,
+ handleClassName,
+ handleStyle,
+ lineClassName,
+ lineStyle,
+ color,
+}: NodeResizerProps) {
+ if (!isVisible) {
+ return null;
+ }
+
+ return (
+ <>
+ {lineControls.map((c) => (
+
+ ))}
+ {handleControls.map((c) => (
+
+ ))}
+ >
+ );
+}
diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx
new file mode 100644
index 00000000..47050fec
--- /dev/null
+++ b/packages/node-resizer/src/ResizeControl.tsx
@@ -0,0 +1,178 @@
+import { useRef, useEffect, memo } from 'react';
+import cc from 'classcat';
+import { drag } from 'd3-drag';
+import { select } from 'd3-selection';
+import {
+ useStoreApi,
+ useGetPointerPosition,
+ NodeChange,
+ NodeDimensionChange,
+ useNodeId,
+ NodePositionChange,
+} from '@reactflow/core';
+
+import { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps, ResizeControlVariant } from './types';
+
+const initPrevValues = { width: 0, height: 0, x: 0, y: 0 };
+
+const initStartValues = {
+ ...initPrevValues,
+ pointerX: 0,
+ pointerY: 0,
+};
+
+function ResizeControl({
+ nodeId,
+ position,
+ variant = ResizeControlVariant.Handle,
+ className,
+ style = {},
+ children,
+ color,
+ minWidth = 10,
+ minHeight = 10,
+}: ResizeControlProps) {
+ const contextNodeId = useNodeId();
+ const id = typeof nodeId === 'string' ? nodeId : contextNodeId;
+ const store = useStoreApi();
+ const resizeControlRef = useRef(null);
+ const startValues = useRef(initStartValues);
+ const prevValues = useRef(initPrevValues);
+ const getPointerPosition = useGetPointerPosition();
+ const defaultPosition = variant === ResizeControlVariant.Line ? 'right' : 'bottom-right';
+ const controlPosition = position ?? defaultPosition;
+
+ useEffect(() => {
+ if (!resizeControlRef.current || !id) {
+ return;
+ }
+
+ const selection = select(resizeControlRef.current);
+ const dragHandler = drag()
+ .on('start', (event: ResizeDragEvent) => {
+ const node = store.getState().nodeInternals.get(id);
+ const { xSnapped, ySnapped } = getPointerPosition(event);
+
+ prevValues.current = {
+ width: node?.width ?? 0,
+ height: node?.height ?? 0,
+ x: node?.position.x ?? 0,
+ y: node?.position.y ?? 0,
+ };
+
+ startValues.current = {
+ ...prevValues.current,
+ pointerX: xSnapped,
+ pointerY: ySnapped,
+ };
+ })
+ .on('drag', (event: ResizeDragEvent) => {
+ const { nodeInternals, triggerNodeChanges } = store.getState();
+ const { xSnapped, ySnapped } = getPointerPosition(event);
+ const node = nodeInternals.get(id);
+ const enableX = controlPosition.includes('right') || controlPosition.includes('left');
+ const enableY = controlPosition.includes('bottom') || controlPosition.includes('top');
+ const invertX = controlPosition.includes('left');
+ const invertY = controlPosition.includes('top');
+
+ if (node) {
+ const changes: NodeChange[] = [];
+ const {
+ pointerX: startX,
+ pointerY: startY,
+ width: startWidth,
+ height: startHeight,
+ x: startNodeX,
+ y: startNodeY,
+ } = startValues.current;
+
+ const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current;
+
+ const distX = Math.floor(enableX ? xSnapped - startX : 0);
+ const distY = Math.floor(enableY ? ySnapped - startY : 0);
+ const width = Math.max(startWidth + (invertX ? -distX : distX), minWidth);
+ const height = Math.max(startHeight + (invertY ? -distY : distY), minHeight);
+
+ const isWidthChange = width !== prevWidth;
+ const isHeightChange = height !== prevHeight;
+
+ if (invertX || invertY) {
+ const x = invertX ? startNodeX - (width - startWidth) : startNodeX;
+ const y = invertY ? startNodeY - (height - startHeight) : startNodeY;
+
+ // only transform the node if the width or height changes
+ const isXPosChange = x !== prevX && isWidthChange;
+ const isYPosChange = y !== prevY && isHeightChange;
+
+ if (isXPosChange || isYPosChange) {
+ const positionChange: NodePositionChange = {
+ id: node.id,
+ type: 'position',
+ position: {
+ x: isXPosChange ? x : prevX,
+ y: isYPosChange ? y : prevY,
+ },
+ };
+
+ changes.push(positionChange);
+ prevValues.current.x = positionChange.position!.x;
+ prevValues.current.y = positionChange.position!.y;
+ }
+ }
+
+ if (isWidthChange || isHeightChange) {
+ const dimensionChange: NodeDimensionChange = {
+ id: id,
+ type: 'dimensions',
+ updateStyle: true,
+ resizing: true,
+ dimensions: {
+ width: width,
+ height: height,
+ },
+ };
+ changes.push(dimensionChange);
+ prevValues.current.width = width;
+ prevValues.current.height = height;
+ }
+
+ triggerNodeChanges(changes);
+ }
+ })
+ .on('end', () => {
+ const dimensionChange: NodeDimensionChange = {
+ id: id,
+ type: 'dimensions',
+ resizing: true,
+ };
+
+ store.getState().triggerNodeChanges([dimensionChange]);
+ });
+
+ selection.call(dragHandler);
+
+ return () => {
+ selection.on('.drag', null);
+ };
+ }, [id, controlPosition, getPointerPosition]);
+
+ const positionClassNames = controlPosition.split('-');
+ const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor';
+ const controlStyle = color ? { ...style, [colorStyleProp]: color } : style;
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function ResizeControlLine(props: ResizeControlLineProps) {
+ return ;
+}
+
+export default memo(ResizeControl);
diff --git a/packages/node-resizer/src/index.tsx b/packages/node-resizer/src/index.tsx
new file mode 100644
index 00000000..c3cd1d5d
--- /dev/null
+++ b/packages/node-resizer/src/index.tsx
@@ -0,0 +1,4 @@
+export { default as NodeResizer } from './NodeResizer';
+export { default as NodeResizeControl } from './ResizeControl';
+
+export * from './types';
diff --git a/packages/node-resizer/src/style.css b/packages/node-resizer/src/style.css
new file mode 100644
index 00000000..0df9c1fb
--- /dev/null
+++ b/packages/node-resizer/src/style.css
@@ -0,0 +1,103 @@
+.react-flow__resize-control {
+ position: absolute;
+}
+
+.react-flow__resize-control.left,
+.react-flow__resize-control.right {
+ cursor: ew-resize;
+}
+
+.react-flow__resize-control.top,
+.react-flow__resize-control.bottom {
+ cursor: ns-resize;
+}
+
+.react-flow__resize-control.top.left,
+.react-flow__resize-control.bottom.right {
+ cursor: nwse-resize;
+}
+
+.react-flow__resize-control.bottom.left,
+.react-flow__resize-control.top.right {
+ cursor: nesw-resize;
+}
+
+/* handle styles */
+.react-flow__resize-control.handle {
+ width: 4px;
+ height: 4px;
+ border: 1px solid #fff;
+ border-radius: 1px;
+ background-color: #3367d9;
+ transform: translate(-50%, -50%);
+}
+
+.react-flow__resize-control.handle.left {
+ left: 0;
+ top: 50%;
+}
+.react-flow__resize-control.handle.right {
+ left: 100%;
+ top: 50%;
+}
+.react-flow__resize-control.handle.top {
+ left: 50%;
+ top: 0;
+}
+.react-flow__resize-control.handle.bottom {
+ left: 50%;
+ top: 100%;
+}
+.react-flow__resize-control.handle.top.left {
+ left: 0;
+}
+.react-flow__resize-control.handle.bottom.left {
+ left: 0;
+}
+.react-flow__resize-control.handle.top.right {
+ left: 100%;
+}
+.react-flow__resize-control.handle.bottom.right {
+ left: 100%;
+}
+
+/* line styles */
+.react-flow__resize-control.line {
+ border-color: #3367d9;
+ border-width: 0;
+ border-style: solid;
+}
+
+.react-flow__resize-control.line.left,
+.react-flow__resize-control.line.right {
+ width: 1px;
+ transform: translate(-50%, 0);
+ top: 0;
+ height: 100%;
+}
+
+.react-flow__resize-control.line.left {
+ left: 0;
+ border-left-width: 1px;
+}
+.react-flow__resize-control.line.right {
+ left: 100%;
+ border-right-width: 1px;
+}
+
+.react-flow__resize-control.line.top,
+.react-flow__resize-control.line.bottom {
+ height: 1px;
+ transform: translate(0, -50%);
+ left: 0;
+ width: 100%;
+}
+
+.react-flow__resize-control.line.top {
+ top: 0;
+ border-top-width: 1px;
+}
+.react-flow__resize-control.line.bottom {
+ border-bottom-width: 1px;
+ top: 100%;
+}
diff --git a/packages/node-resizer/src/types.ts b/packages/node-resizer/src/types.ts
new file mode 100644
index 00000000..000ad536
--- /dev/null
+++ b/packages/node-resizer/src/types.ts
@@ -0,0 +1,39 @@
+import type { CSSProperties, ReactNode } from 'react';
+import type { D3DragEvent, SubjectPosition } from 'd3-drag';
+
+export type NodeResizerProps = {
+ nodeId?: string;
+ color?: string;
+ handleClassName?: string;
+ handleStyle?: CSSProperties;
+ lineClassName?: string;
+ lineStyle?: CSSProperties;
+ isVisible?: boolean;
+};
+
+export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right';
+
+export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
+
+export enum ResizeControlVariant {
+ Line = 'line',
+ Handle = 'handle',
+}
+
+export type ResizeControlProps = {
+ nodeId?: string;
+ position?: ControlPosition;
+ variant?: ResizeControlVariant;
+ color?: string;
+ className?: string;
+ style?: CSSProperties;
+ children?: ReactNode;
+ minWidth?: number;
+ minHeight?: number;
+};
+
+export type ResizeControlLineProps = ResizeControlProps & {
+ position: ControlLinePosition;
+};
+
+export type ResizeDragEvent = D3DragEvent;
diff --git a/packages/node-resizer/tsconfig.json b/packages/node-resizer/tsconfig.json
new file mode 100644
index 00000000..d0e5a1ce
--- /dev/null
+++ b/packages/node-resizer/tsconfig.json
@@ -0,0 +1,6 @@
+{
+ "extends": "@reactflow/tsconfig/react.json",
+ "display": "@reactflow/node-resizer",
+ "include": ["**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/packages/node-toolbar/CHANGELOG.md b/packages/node-toolbar/CHANGELOG.md
index 7bad5ebf..624d1510 100644
--- a/packages/node-toolbar/CHANGELOG.md
+++ b/packages/node-toolbar/CHANGELOG.md
@@ -1,5 +1,14 @@
# @reactflow/node-toolbar
+## 1.0.2
+
+### Patch Changes
+
+- [#2626](https://github.com/wbkd/react-flow/pull/2626) [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18) - Get nodeId from React Flow context if it is not passed explicitly as prop
+
+- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]:
+ - @reactflow/core@11.3.2
+
## 1.0.1
### Patch Changes
diff --git a/packages/node-toolbar/package.json b/packages/node-toolbar/package.json
index 18f05350..2163d4ce 100644
--- a/packages/node-toolbar/package.json
+++ b/packages/node-toolbar/package.json
@@ -1,6 +1,6 @@
{
"name": "@reactflow/node-toolbar",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "A toolbar component for React Flow that can be attached to a node.",
"keywords": [
"react",
diff --git a/packages/node-toolbar/src/NodeToolbar.tsx b/packages/node-toolbar/src/NodeToolbar.tsx
index 1dfd7669..5c6cbc86 100644
--- a/packages/node-toolbar/src/NodeToolbar.tsx
+++ b/packages/node-toolbar/src/NodeToolbar.tsx
@@ -8,6 +8,7 @@ import {
Rect,
Position,
internalsSymbol,
+ useNodeId,
} from '@reactflow/core';
import cc from 'classcat';
import shallow from 'zustand/shallow';
@@ -72,9 +73,11 @@ function NodeToolbar({
offset = 10,
...rest
}: NodeToolbarProps) {
+ const contextNodeId = useNodeId();
+
const nodesSelector = useCallback(
(state: ReactFlowState): Node[] => {
- const nodeIds: string[] = typeof nodeId === 'string' ? [nodeId] : nodeId;
+ const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || ''];
return nodeIds.reduce((acc, id) => {
const node = state.nodeInternals.get(id);
@@ -84,7 +87,7 @@ function NodeToolbar({
return acc;
}, [] as Node[]);
},
- [nodeId]
+ [nodeId, contextNodeId]
);
const nodes = useStore(nodesSelector, nodesEqualityFn);
const { transform, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow);
diff --git a/packages/node-toolbar/src/types.ts b/packages/node-toolbar/src/types.ts
index a9f547ad..e2b0d73c 100644
--- a/packages/node-toolbar/src/types.ts
+++ b/packages/node-toolbar/src/types.ts
@@ -2,7 +2,7 @@ import { Position } from '@reactflow/core';
import type { HTMLAttributes } from 'react';
export type NodeToolbarProps = HTMLAttributes & {
- nodeId: string | string[];
+ nodeId?: string | string[];
isVisible?: boolean;
position?: Position;
offset?: number;
diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md
index f3141ce1..dfeb78db 100644
--- a/packages/reactflow/CHANGELOG.md
+++ b/packages/reactflow/CHANGELOG.md
@@ -1,5 +1,18 @@
# reactflow
+## 11.3.3
+
+In this update we did some changes so that we could implement the new [``](https://reactflow.dev/docs/api/nodes/node-resizer/) component (not part of the `reactflow` package!) more smoothly.
+
+### Patch Changes
+
+- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]:
+ - @reactflow/core@11.3.2
+ - @reactflow/minimap@11.2.3
+ - @reactflow/node-toolbar@1.0.2
+ - @reactflow/background@11.0.7
+ - @reactflow/controls@11.0.7
+
## 11.3.2
### Patch Changes
diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json
index f84f948a..f1f3ecab 100644
--- a/packages/reactflow/package.json
+++ b/packages/reactflow/package.json
@@ -1,6 +1,6 @@
{
"name": "reactflow",
- "version": "11.3.2",
+ "version": "11.3.3",
"description": "A highly customizable React library for building node-based editors and interactive flow charts",
"keywords": [
"react",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6d9414c1..4eafb92f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -57,6 +57,7 @@ importers:
examples/vite-app:
specifiers:
'@cypress/skip-test': ^2.6.1
+ '@reactflow/node-resizer': workspace:^0.0.0
'@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/node-resizer': link:../../packages/node-resizer
classcat: registry.npmjs.org/classcat/5.0.4
dagre: registry.npmjs.org/dagre/0.8.5
localforage: registry.npmjs.org/localforage/1.10.0
@@ -215,6 +217,45 @@ importers:
react: registry.npmjs.org/react/18.2.0
typescript: registry.npmjs.org/typescript/4.8.3
+ packages/node-resizer:
+ specifiers:
+ '@babel/runtime': ^7.18.9
+ '@reactflow/core': workspace:*
+ '@reactflow/eslint-config': workspace:*
+ '@reactflow/rollup-config': workspace:*
+ '@reactflow/tsconfig': workspace:*
+ '@types/d3': ^7.4.0
+ '@types/d3-drag': ^3.0.1
+ '@types/d3-selection': ^3.0.3
+ '@types/node': ^18.7.16
+ '@types/react': ^18.0.19
+ '@types/react-dom': ^18.0.6
+ classcat: ^5.0.4
+ d3-drag: ^3.0.0
+ d3-selection: ^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
+ classcat: registry.npmjs.org/classcat/5.0.4
+ d3-drag: registry.npmjs.org/d3-drag/3.0.0
+ d3-selection: registry.npmjs.org/d3-selection/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/d3': registry.npmjs.org/@types/d3/7.4.0
+ '@types/d3-drag': registry.npmjs.org/@types/d3-drag/3.0.1
+ '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3
+ '@types/node': registry.npmjs.org/@types/node/18.7.16
+ '@types/react': registry.npmjs.org/@types/react/18.0.19
+ '@types/react-dom': registry.npmjs.org/@types/react-dom/18.0.6
+ react: registry.npmjs.org/react/18.2.0
+ typescript: registry.npmjs.org/typescript/4.8.3
+
packages/node-toolbar:
specifiers:
'@babel/runtime': ^7.18.9
@@ -1380,7 +1421,6 @@ packages:
resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz}
name: '@types/d3-array'
version: 3.0.3
- dev: false
registry.npmjs.org/@types/d3-axis/3.0.1:
resolution: {integrity: sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz}
@@ -1388,7 +1428,6 @@ packages:
version: 3.0.1
dependencies:
'@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3
- dev: false
registry.npmjs.org/@types/d3-brush/3.0.1:
resolution: {integrity: sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz}
@@ -1396,19 +1435,16 @@ packages:
version: 3.0.1
dependencies:
'@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3
- dev: false
registry.npmjs.org/@types/d3-chord/3.0.1:
resolution: {integrity: sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz}
name: '@types/d3-chord'
version: 3.0.1
- dev: false
registry.npmjs.org/@types/d3-color/3.1.0:
resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz}
name: '@types/d3-color'
version: 3.1.0
- dev: false
registry.npmjs.org/@types/d3-contour/3.0.1:
resolution: {integrity: sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz}
@@ -1417,19 +1453,16 @@ packages:
dependencies:
'@types/d3-array': registry.npmjs.org/@types/d3-array/3.0.3
'@types/geojson': registry.npmjs.org/@types/geojson/7946.0.10
- dev: false
registry.npmjs.org/@types/d3-delaunay/6.0.1:
resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz}
name: '@types/d3-delaunay'
version: 6.0.1
- dev: false
registry.npmjs.org/@types/d3-dispatch/3.0.1:
resolution: {integrity: sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz}
name: '@types/d3-dispatch'
version: 3.0.1
- dev: false
registry.npmjs.org/@types/d3-drag/3.0.1:
resolution: {integrity: sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz}
@@ -1437,19 +1470,16 @@ packages:
version: 3.0.1
dependencies:
'@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3
- dev: false
registry.npmjs.org/@types/d3-dsv/3.0.0:
resolution: {integrity: sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz}
name: '@types/d3-dsv'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-ease/3.0.0:
resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz}
name: '@types/d3-ease'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-fetch/3.0.1:
resolution: {integrity: sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz}
@@ -1457,19 +1487,16 @@ packages:
version: 3.0.1
dependencies:
'@types/d3-dsv': registry.npmjs.org/@types/d3-dsv/3.0.0
- dev: false
registry.npmjs.org/@types/d3-force/3.0.3:
resolution: {integrity: sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz}
name: '@types/d3-force'
version: 3.0.3
- dev: false
registry.npmjs.org/@types/d3-format/3.0.1:
resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz}
name: '@types/d3-format'
version: 3.0.1
- dev: false
registry.npmjs.org/@types/d3-geo/3.0.2:
resolution: {integrity: sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz}
@@ -1477,13 +1504,11 @@ packages:
version: 3.0.2
dependencies:
'@types/geojson': registry.npmjs.org/@types/geojson/7946.0.10
- dev: false
registry.npmjs.org/@types/d3-hierarchy/3.1.0:
resolution: {integrity: sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz}
name: '@types/d3-hierarchy'
version: 3.1.0
- dev: false
registry.npmjs.org/@types/d3-interpolate/3.0.1:
resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz}
@@ -1491,37 +1516,31 @@ packages:
version: 3.0.1
dependencies:
'@types/d3-color': registry.npmjs.org/@types/d3-color/3.1.0
- dev: false
registry.npmjs.org/@types/d3-path/3.0.0:
resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz}
name: '@types/d3-path'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-polygon/3.0.0:
resolution: {integrity: sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz}
name: '@types/d3-polygon'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-quadtree/3.0.2:
resolution: {integrity: sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz}
name: '@types/d3-quadtree'
version: 3.0.2
- dev: false
registry.npmjs.org/@types/d3-random/3.0.1:
resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz}
name: '@types/d3-random'
version: 3.0.1
- dev: false
registry.npmjs.org/@types/d3-scale-chromatic/3.0.0:
resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz}
name: '@types/d3-scale-chromatic'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-scale/4.0.2:
resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz}
@@ -1529,13 +1548,11 @@ packages:
version: 4.0.2
dependencies:
'@types/d3-time': registry.npmjs.org/@types/d3-time/3.0.0
- dev: false
registry.npmjs.org/@types/d3-selection/3.0.3:
resolution: {integrity: sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz}
name: '@types/d3-selection'
version: 3.0.3
- dev: false
registry.npmjs.org/@types/d3-shape/3.1.0:
resolution: {integrity: sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz}
@@ -1543,25 +1560,21 @@ packages:
version: 3.1.0
dependencies:
'@types/d3-path': registry.npmjs.org/@types/d3-path/3.0.0
- dev: false
registry.npmjs.org/@types/d3-time-format/4.0.0:
resolution: {integrity: sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz}
name: '@types/d3-time-format'
version: 4.0.0
- dev: false
registry.npmjs.org/@types/d3-time/3.0.0:
resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz}
name: '@types/d3-time'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-timer/3.0.0:
resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz}
name: '@types/d3-timer'
version: 3.0.0
- dev: false
registry.npmjs.org/@types/d3-transition/3.0.2:
resolution: {integrity: sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.2.tgz}
@@ -1569,7 +1582,6 @@ packages:
version: 3.0.2
dependencies:
'@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3
- dev: false
registry.npmjs.org/@types/d3-zoom/3.0.1:
resolution: {integrity: sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz}
@@ -1578,7 +1590,6 @@ packages:
dependencies:
'@types/d3-interpolate': registry.npmjs.org/@types/d3-interpolate/3.0.1
'@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3
- dev: false
registry.npmjs.org/@types/d3/7.4.0:
resolution: {integrity: sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz}
@@ -1615,7 +1626,6 @@ packages:
'@types/d3-timer': registry.npmjs.org/@types/d3-timer/3.0.0
'@types/d3-transition': registry.npmjs.org/@types/d3-transition/3.0.2
'@types/d3-zoom': registry.npmjs.org/@types/d3-zoom/3.0.1
- dev: false
registry.npmjs.org/@types/estree/0.0.39:
resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz}
@@ -1633,7 +1643,6 @@ packages:
resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz}
name: '@types/geojson'
version: 7946.0.10
- dev: false
registry.npmjs.org/@types/is-ci/3.0.0:
resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/is-ci/-/is-ci-3.0.0.tgz}