Merge pull request #2643 from wbkd/next-release

Next Release
This commit is contained in:
Moritz Klack
2022-12-06 18:48:07 +01:00
committed by GitHub
54 changed files with 977 additions and 181 deletions

View File

@@ -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",

View File

@@ -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',

View File

@@ -94,7 +94,7 @@ const BasicFlow = () => {
fitView
defaultEdgeOptions={defaultEdgeOptions}
selectNodesOnDrag={false}
nodeOrigin={nodeOrigin}
// nodeOrigin={nodeOrigin}
>
<Background variant={BackgroundVariant.Dots} />
<MiniMap />

View File

@@ -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<NodeProps> = ({ id, data }) => {
return (
<>
<NodeResizeControl style={controlStyle}>
<ResizeIcon />
</NodeResizeControl>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomNode);

View File

@@ -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<NodeProps> = ({ id, data }) => {
return (
<>
<NodeResizeControl color="red" position="top" />
<NodeResizeControl color="red" position="bottom" />
<Handle type="target" position={Position.Left} />
<div style={{ padding: 10 }}>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomNode);

View File

@@ -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<NodeProps> = ({ id, data, selected }) => {
return (
<>
<NodeResizer isVisible={selected} />
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(CustomNode);

View File

@@ -0,0 +1,24 @@
function ResizeIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="8"
height="8"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
style={{ position: 'absolute', right: 2, bottom: 2 }}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="16 20 20 20 20 16" />
<line x1="14" y1="14" x2="20" y2="20" />
<polyline points="8 4 4 4 4 8" />
<line x1="4" y1="4" x2="10" y2="10" />
</svg>
);
}
export default ResizeIcon;

View File

@@ -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 (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
nodeTypes={nodeTypes}
minZoom={0.2}
maxZoom={5}
snapToGrid={snapToGrid}
fitView
>
<Controls />
<Panel position="bottom-right">
<button onClick={() => setSnapToGrid(!snapToGrid)}>snapToGrid: {snapToGrid ? 'on' : 'off'}</button>
</Panel>
</ReactFlow>
);
};
export default CustomNodeFlow;

View File

@@ -4,7 +4,7 @@ import { Handle, Position, NodeProps, NodeToolbar } from 'reactflow';
const CustomNode: FC<NodeProps> = ({ id, data }) => {
return (
<>
<NodeToolbar nodeId={id} isVisible={data.toolbarVisible} position={data.toolbarPosition}>
<NodeToolbar isVisible={data.toolbarVisible} position={data.toolbarPosition}>
<button>delete</button>
<button>copy</button>
<button>expand</button>

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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",

View File

@@ -1,5 +1,17 @@
# @reactflow/core
## 11.3.2
In this update we did some changes so that we could implement the new [`<NodeResizer />`](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

View File

@@ -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",

View File

@@ -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);

View File

@@ -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<HTMLDivElement, HandleComponentProps>(
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;

View File

@@ -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<string, XYPosition> = {
@@ -84,7 +84,12 @@ export default (NodeComponent: ComponentType<NodeProps>) => {
};
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) {

View File

@@ -57,6 +57,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
nodeElement: entry.target as HTMLDivElement,
forceUpdate: true,
}));
updateNodeDimensions(updates);
});

View File

@@ -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;
}

View File

@@ -1,7 +1,12 @@
import { createContext } from 'react';
import { createContext, useContext } from 'react';
export const NodeIdContext = createContext<string | null>(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;

View File

@@ -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<HTMLDivElement, null, SubjectPosition>;
export type UseDragData = { dx: number; dy: number };
type UseDragParams = {
@@ -40,24 +39,7 @@ function useDrag({
const dragItems = useRef<NodeDragItem[]>();
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) {

View File

@@ -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],

View File

@@ -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;

View File

@@ -1,5 +1,6 @@
import { useState, useEffect, useRef, useMemo } from 'react';
import { isInputDOMNode } from '../utils';
import type { KeyCode } from '../types';
type Keys = Array<string>;
@@ -106,14 +107,3 @@ function isMatchingKey(keyCodes: Array<Keys>, 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')
);
}

View File

@@ -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';

View File

@@ -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[];

View File

@@ -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;
}
}
}

View File

@@ -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 = {

View File

@@ -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<HTMLDivElement, null, SubjectPosition>;

View File

@@ -31,6 +31,7 @@ export type Node<T = any> = {
positionAbsolute?: XYPosition;
ariaLabel?: string;
focusable?: boolean;
resizing?: boolean;
// only used internally
[internalsSymbol]?: {

View File

@@ -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);
}

View File

@@ -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,
})

View File

@@ -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')
);
}

View File

@@ -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

View File

@@ -1,6 +1,6 @@
{
"name": "@reactflow/minimap",
"version": "11.2.2",
"version": "11.2.3",
"description": "Minimap component for React Flow.",
"keywords": [
"react",

View File

@@ -167,13 +167,13 @@ function MiniMap({
>
{ariaLabel && <title id={labelledBy}>{ariaLabel}</title>}
{nodes.map((node) => {
const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin);
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
return (
<MiniMapNode
key={node.id}
x={positionAbsolute.x}
y={positionAbsolute.y}
x={x}
y={y}
width={node.width!}
height={node.height!}
style={node.style}

View File

@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['@reactflow/eslint-config'],
};

View File

@@ -0,0 +1,14 @@
# @reactflow/node-resizer
## 1.0.0
This is a new package that exports components to build a UI for resizing a node 🎉 It exports a [`<NodeResizer />`](https://reactflow.dev/docs/api/nodes/node-resizer/) component and a [`<NodeResizeControl />`](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

View File

@@ -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
```

View File

@@ -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"
}
}

View File

@@ -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) => (
<ResizeControl
key={c}
className={lineClassName}
style={lineStyle}
nodeId={nodeId}
position={c}
variant={ResizeControlVariant.Line}
color={color}
/>
))}
{handleControls.map((c) => (
<ResizeControl
key={c}
className={handleClassName}
style={handleStyle}
nodeId={nodeId}
position={c}
color={color}
/>
))}
</>
);
}

View File

@@ -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<HTMLDivElement>(null);
const startValues = useRef<typeof initStartValues>(initStartValues);
const prevValues = useRef<typeof initPrevValues>(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<HTMLDivElement, unknown>()
.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 (
<div
className={cc(['react-flow__resize-control', 'nodrag', ...positionClassNames, variant, className])}
ref={resizeControlRef}
style={controlStyle}
>
{children}
</div>
);
}
export function ResizeControlLine(props: ResizeControlLineProps) {
return <ResizeControl {...props} variant={ResizeControlVariant.Line} />;
}
export default memo(ResizeControl);

View File

@@ -0,0 +1,4 @@
export { default as NodeResizer } from './NodeResizer';
export { default as NodeResizeControl } from './ResizeControl';
export * from './types';

View File

@@ -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%;
}

View File

@@ -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<HTMLDivElement, null, SubjectPosition>;

View File

@@ -0,0 +1,6 @@
{
"extends": "@reactflow/tsconfig/react.json",
"display": "@reactflow/node-resizer",
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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<Node[]>((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);

View File

@@ -2,7 +2,7 @@ import { Position } from '@reactflow/core';
import type { HTMLAttributes } from 'react';
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
nodeId: string | string[];
nodeId?: string | string[];
isVisible?: boolean;
position?: Position;
offset?: number;

View File

@@ -1,5 +1,18 @@
# reactflow
## 11.3.3
In this update we did some changes so that we could implement the new [`<NodeResizer />`](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

View File

@@ -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",

73
pnpm-lock.yaml generated
View File

@@ -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}