Merge branch 'main' into feat/packages

This commit is contained in:
moklick
2023-03-28 16:27:18 +02:00
61 changed files with 2513 additions and 1150 deletions

38
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,38 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective and/or restorative action in response to any instances of unacceptable behaviour.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
Where it is appropriate and parties consent, project maintainers will endeavour to facilitate restorative justice over punitive action.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
Additionally, we ask that all community members uphold the standards laid out in this Code of Conduct to create a safe and welcome space for all.
## Attribution
This Code of Conduct is a direct decendant of the [Gleam Code of Conduct](https://github.com/gleam-lang/gleam/blob/f793b5d28a3102276a8b861c7e16a19c5231426e/CODE_OF_CONDUCT.md), which is itself a decendant of the [Contributor Covenant (v1.4)](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html).

View File

@@ -26,7 +26,7 @@ describe('useNodesInitialized.cy.tsx', () => {
</ReactFlow>
);
cy.get('@initSpy').should('to.be.calledOnce');
// cy.get('@initSpy').should('to.be.calledOnce');
cy.get('@initSpy').should('have.be.calledWith', false);
});

View File

@@ -21,7 +21,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.3.0",
"reactflow": "workspace:*"
"reactflow": "workspace:*",
"zustand": "^4.3.1"
},
"devDependencies": {
"@cypress/skip-test": "^2.6.1",

View File

@@ -45,6 +45,7 @@ import CancelConnection from '../examples/CancelConnection';
import InteractiveMinimap from '../examples/InteractiveMinimap';
import UseOnSelectionChange from '../examples/UseOnSelectionChange';
import NodeToolbar from '../examples/NodeToolbar';
import useNodesInitialized from '../examples/UseNodesInit';
interface IRoute {
name: string;
@@ -248,6 +249,11 @@ const routes: IRoute[] = [
path: '/update-node',
component: UpdateNode,
},
{
name: 'useNodesInitialized',
path: '/use-nodes-initialized',
component: useNodesInitialized,
},
{
name: 'useOnSelectionChange',
path: '/use-on-selection-change',

View File

@@ -19,13 +19,15 @@ const initialNodes: Node[] = [
},
];
const Flow: FC<{ id: string; bgProps: BackgroundProps }> = ({ id, bgProps }) => {
const Flow: FC<{ id: string; bgProps: BackgroundProps[] }> = ({ id, bgProps }) => {
const [nodes, , onNodesChange] = useNodesState(initialNodes);
return (
<ReactFlowProvider>
<ReactFlow nodes={nodes} onNodesChange={onNodesChange} id={id}>
<Background {...bgProps} />
{bgProps.map((props, idx) => (
<Background key={idx} id={idx.toString()} {...props} />
))}
</ReactFlow>
</ReactFlowProvider>
);
@@ -33,9 +35,16 @@ const Flow: FC<{ id: string; bgProps: BackgroundProps }> = ({ id, bgProps }) =>
const Backgrounds: FC = () => (
<div className={styles.wrapper}>
<Flow id="flow-a" bgProps={{ variant: BackgroundVariant.Dots }} />
<Flow id="flow-b" bgProps={{ variant: BackgroundVariant.Lines, gap: [50, 50] }} />
<Flow id="flow-c" bgProps={{ variant: BackgroundVariant.Cross, gap: [100, 50] }} />
<Flow id="flow-a" bgProps={[{ variant: BackgroundVariant.Dots }]} />
<Flow id="flow-b" bgProps={[{ variant: BackgroundVariant.Lines, gap: [50, 50] }]} />
<Flow id="flow-c" bgProps={[{ variant: BackgroundVariant.Cross, gap: [100, 50] }]} />
<Flow
id="flow-d"
bgProps={[
{ variant: BackgroundVariant.Lines, gap: 10 },
{ variant: BackgroundVariant.Lines, gap: 100, offset: 2, color: '#ccc' },
]}
/>
</div>
);

View File

@@ -1,4 +1,4 @@
import { MouseEvent, useCallback } from 'react';
import { MouseEvent, useCallback, useState } from 'react';
import ReactFlow, {
MiniMap,
Background,
@@ -87,6 +87,7 @@ const defaultEdgeOptions = { zIndex: 0 };
const BasicFlow = () => {
const instance = useReactFlow();
const [inverse, setInverse] = useState(false);
const updatePos = () => {
instance.setNodes((nodes) =>
@@ -103,6 +104,7 @@ const BasicFlow = () => {
const logToObject = () => console.log(instance.toObject());
const resetTransform = () => instance.setViewport({ x: 0, y: 0, zoom: 1 });
const toggleInverse = () => setInverse(!inverse);
const toggleClassnames = () => {
instance.setNodes((nodes) =>
@@ -137,7 +139,8 @@ const BasicFlow = () => {
fitView
>
<Background variant={BackgroundVariant.Dots} />
<MiniMap onClick={onMiniMapClick} onNodeClick={onMiniMapNodeClick} pannable zoomable />
<MiniMap onClick={onMiniMapClick} onNodeClick={onMiniMapNodeClick} pannable zoomable
inversePan={inverse}/>
<Controls />
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
@@ -150,7 +153,12 @@ const BasicFlow = () => {
<button onClick={toggleClassnames} style={{ marginRight: 5 }}>
toggle classnames
</button>
<button onClick={logToObject}>toObject</button>
<button onClick={logToObject} style={{ marginRight: 5 }}>
toObject
</button>
<button onClick={toggleInverse} style={{ marginRight: 5 }}>
{inverse ? 'un-inverse pan' : 'inverse pan'}
</button>
</div>
</ReactFlow>
);

View File

@@ -1,8 +1,6 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow';
import { Handle, Position, NodeProps, NodeResizeControl } from 'reactflow';
import { NodeResizeControl } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css';
import ResizeIcon from './ResizeIcon';
const controlStyle = {

View File

@@ -1,8 +1,5 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow';
import { NodeResizer } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css';
import { Handle, Position, NodeProps, NodeResizer } from 'reactflow';
const DefaultResizerNode: FC<NodeProps> = ({ data, selected }) => {
return (

View File

@@ -1,8 +1,5 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow';
import { NodeResizeControl } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css';
import { Handle, Position, NodeProps, NodeResizeControl } from 'reactflow';
const HorizontalResizerNode: FC<NodeProps> = ({ data }) => {
return (

View File

@@ -1,8 +1,5 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps } from 'reactflow';
import { NodeResizeControl } from '@reactflow/node-resizer';
import '@reactflow/node-resizer/dist/style.css';
import { Handle, Position, NodeProps, NodeResizeControl } from 'reactflow';
const CustomNode: FC<NodeProps> = ({ id, data }) => {
return (

View File

@@ -6,6 +6,8 @@ import CustomResizer from './CustomResizer';
import VerticalResizer from './VerticalResizer';
import HorizontalResizer from './HorizontalResizer';
import 'reactflow/dist/style.css';
const nodeTypes = {
defaultResizer: DefaultResizer,
customResizer: CustomResizer,

View File

@@ -36,7 +36,7 @@ const initialNodes: Node[] = [
</>
),
},
position: { x: 100, y: 100 },
position: { x: 75, y: 0 },
},
{
id: '3',
@@ -55,9 +55,46 @@ const initialNodes: Node[] = [
width: 180,
},
},
{
id: '4',
data: {
label: (
<>
Node <strong>D</strong>
</>
),
},
position: { x: -75, y: 100 },
},
{
id: '5',
data: {
label: (
<>
Node <strong>E</strong>
</>
),
},
position: { x: 150, y: 100 },
},
{
id: '6',
data: {
label: (
<>
Node <strong>F</strong>
</>
),
},
position: { x: 150, y: 250 },
},
];
const initialEdges = [{ id: 'e1-2', source: '1', target: '2', label: 'This is a draggable edge' }];
const initialEdges: Edge[] = [
{ id: 'e1-3', source: '1', target: '3', label: 'This edge can only be updated from source', updatable: 'source' },
{ id: 'e2-4', source: '2', target: '4', label: 'This edge can only be updated from target', updatable: 'target' },
{ id: 'e5-6', source: '5', target: '6', label: 'This edge can be updated from both sides' },
];
const onInit = (reactFlowInstance: ReactFlowInstance) => reactFlowInstance.fitView();
const onEdgeUpdateStart = (_: ReactMouseEvent, edge: Edge, handleType: HandleType) =>

View File

@@ -0,0 +1,83 @@
import { useCallback, useEffect } from 'react';
import ReactFlow, {
Background,
MiniMap,
Node,
addEdge,
ReactFlowProvider,
Edge,
useNodesState,
useEdgesState,
OnConnect,
useNodesInitialized,
} from 'reactflow';
const initialNodes: Node[] = [
{
id: '1',
type: 'input',
data: { label: 'Node 1' },
position: { x: 250, y: 5 },
className: 'light',
},
{
id: '2',
data: { label: 'Node 2' },
position: { x: 100, y: 100 },
className: 'light',
},
{
id: '3',
data: { label: 'Node 3' },
position: { x: 400, y: 100 },
className: 'light',
},
{
id: '4',
data: { label: 'Node 4' },
position: { x: 400, y: 200 },
className: 'light',
hidden: true,
},
];
const initialEdges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
// { id: 'e3-4', source: '3', target: '4' }
];
const UseZoomPanHelperFlow = () => {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect: OnConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), []);
const initialized = useNodesInitialized();
useEffect(() => {
console.log('initialized', initialized);
}, [initialized]);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
maxZoom={Infinity}
>
<Background />
<MiniMap />
</ReactFlow>
);
};
const WrappedFlow = () => (
<ReactFlowProvider>
<UseZoomPanHelperFlow />
</ReactFlowProvider>
);
export default WrappedFlow;

View File

@@ -0,0 +1,50 @@
import { ReactFlowState, useStore } from 'reactflow';
import { shallow } from 'zustand/shallow';
import styles from './validation.module.css';
const selector = (state: ReactFlowState) => ({
connectionPosition: state.connectionPosition,
connectionStatus: state.connectionStatus,
connectionStartNodeId: state.connectionStartHandle?.nodeId,
connectionStartHandleType: state.connectionStartHandle?.type,
connectionEndNodeId: state.connectionEndHandle?.nodeId,
connectionEndHandleType: state.connectionEndHandle?.type,
});
function ConnectionStatus() {
const {
connectionPosition,
connectionStatus,
connectionStartNodeId,
connectionStartHandleType,
connectionEndNodeId,
connectionEndHandleType,
} = useStore(selector, shallow);
if (!connectionPosition) {
return null;
}
return (
<div className={styles.connectionstatus}>
{connectionStartNodeId ? (
<>
<div>
<strong>connection info</strong>
</div>
<div>position: {JSON.stringify(connectionPosition)}</div>
<div>status: {connectionStatus}</div>
<div>from node id: {connectionStartNodeId}</div>
<div>from handle type: {connectionStartHandleType}</div>
<div>to node id: {connectionEndNodeId}</div>
<div>to handle type: {connectionEndHandleType}</div>
</>
) : (
'no connection data'
)}
</div>
);
}
export default ConnectionStatus;

View File

@@ -16,6 +16,8 @@ import ReactFlow, {
Edge,
} from 'reactflow';
import ConnectionStatus from './ConnectionStatus';
import styles from './validation.module.css';
const initialNodes: Node[] = [
@@ -36,7 +38,7 @@ const CustomInput: FC<NodeProps> = () => (
const CustomNode: FC<NodeProps> = ({ id }) => (
<>
<Handle type="target" position={Position.Left} />
<Handle type="target" position={Position.Left} isConnectableStart={false} />
<div>{id}</div>
<Handle type="source" position={Position.Right} />
</>
@@ -96,7 +98,9 @@ const ValidationFlow = () => {
onEdgeUpdate={onEdgeUpdate}
isValidConnection={isValidConnection}
fitView
/>
>
<ConnectionStatus />
</ReactFlow>
);
};

View File

@@ -39,3 +39,10 @@
.validationflow :global .invalid .react-flow__connection-path {
stroke: #ff6060;
}
.connectionstatus {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}

View File

@@ -1,5 +1,22 @@
# @reactflow/background
## 11.2.0
### Minor Changes
- [#2941](https://github.com/wbkd/react-flow/pull/2941) Thanks [@Elringus](https://github.com/Elringus)! - add `id` and `offset` props
### Patch Changes
- Updated dependencies [[`098eee3d`](https://github.com/wbkd/react-flow/commit/098eee3d41dabc870777b081796401ff13b5a776), [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb), [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402), [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38), [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065), [`c22e1c28`](https://github.com/wbkd/react-flow/commit/c22e1c28c5555a638c2a8e82c3bfc986b3965d36)]:
- @reactflow/core@11.7.0
## 11.1.10
### Patch Changes
- Updated dependencies
## 11.1.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@reactflow/background",
"version": "11.1.9",
"version": "11.2.0",
"description": "Background component with different variants for React Flow",
"keywords": [
"react",

View File

@@ -21,12 +21,14 @@ const defaultSize = {
const selector = (s: ReactFlowState) => ({ transform: s.transform, patternId: `pattern-${s.rfId}` });
function Background({
id,
variant = BackgroundVariant.Dots,
gap = 20,
// only used for dots and cross
size,
gap = 20,
// only used for lines and cross
size,
lineWidth = 1,
offset = 2,
color,
style,
className,
@@ -44,8 +46,8 @@ function Background({
const patternDimensions: [number, number] = isCross ? [scaledSize, scaledSize] : scaledGap;
const patternOffset = isDots
? [scaledSize / 2, scaledSize / 2]
: [patternDimensions[0] / 2, patternDimensions[1] / 2];
? [scaledSize / offset, scaledSize / offset]
: [patternDimensions[0] / offset, patternDimensions[1] / offset];
return (
<svg
@@ -62,7 +64,7 @@ function Background({
data-testid="rf__background"
>
<pattern
id={patternId}
id={patternId+id}
x={transform[0] % scaledGap[0]}
y={transform[1] % scaledGap[1]}
width={scaledGap[0]}
@@ -71,12 +73,12 @@ function Background({
patternTransform={`translate(-${patternOffset[0]},-${patternOffset[1]})`}
>
{isDots ? (
<DotPattern color={patternColor} radius={scaledSize / 2} />
<DotPattern color={patternColor} radius={scaledSize / offset} />
) : (
<LinePattern dimensions={patternDimensions} color={patternColor} lineWidth={lineWidth} />
)}
</pattern>
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId})`} />
<rect x="0" y="0" width="100%" height="100%" fill={`url(#${patternId+id})`} />
</svg>
);
}

View File

@@ -7,10 +7,12 @@ export enum BackgroundVariant {
}
export type BackgroundProps = {
id?: string
color?: string;
className?: string;
gap?: number | [number, number];
size?: number;
offset?: number;
lineWidth?: number;
variant?: BackgroundVariant;
style?: CSSProperties;

View File

@@ -1,5 +1,18 @@
# @reactflow/controls
## 11.1.11
### Patch Changes
- Updated dependencies [[`098eee3d`](https://github.com/wbkd/react-flow/commit/098eee3d41dabc870777b081796401ff13b5a776), [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb), [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402), [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38), [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065), [`c22e1c28`](https://github.com/wbkd/react-flow/commit/c22e1c28c5555a638c2a8e82c3bfc986b3965d36)]:
- @reactflow/core@11.7.0
## 11.1.10
### Patch Changes
- Updated dependencies
## 11.1.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@reactflow/controls",
"version": "11.1.9",
"version": "11.1.11",
"description": "Component to control the viewport of a React Flow instance",
"keywords": [
"react",

View File

@@ -1,5 +1,32 @@
# @reactflow/core
## 11.7.0
Most notable updates:
- Handles: `isConnectableStart` and `isConnectableEnd` props to configure if you can start or end a connection at a certain handle
- Edges: `updatable` option to enable updates for specific edges
- useNodesInitialized: options to configure if hidden nodes should be included (false by default)
### Minor Changes
- [#2960](https://github.com/wbkd/react-flow/pull/2960) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - edges: add `updatable` option
- [#2958](https://github.com/wbkd/react-flow/pull/2958) [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065) - handles: add `isConnectableStart` and `isConnectableEnd` props
- [#2956](https://github.com/wbkd/react-flow/pull/2956) [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38) - add options for `useNodesInitialized`, ignore hidden nodes by default
### Patch Changes
- [#2926](https://github.com/wbkd/react-flow/pull/2926) Thanks [@Elringus](https://github.com/Elringus)! - fix non-passive wheel event listener violation
- [#2933](https://github.com/wbkd/react-flow/pull/2933) [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb) - prefix error keys with "error"
- [#2939](https://github.com/wbkd/react-flow/pull/2939) [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402) - add connection result to store
## 11.6.1
### Patch Changes
- Always create new edge object (fixes an issue with Redux toolkit and other immutable helper libs)
## 11.6.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@reactflow/core",
"version": "11.6.0",
"version": "11.7.0",
"description": "Core components and util functions of React Flow.",
"keywords": [
"react",

View File

@@ -54,6 +54,7 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
rfId,
ariaLabel,
isFocusable,
isUpdatable,
pathOptions,
interactionWidth,
}: WrapEdgeProps): JSX.Element | null => {
@@ -137,7 +138,6 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
const onEdgeUpdaterMouseOut = () => setUpdateHover(false);
const inactive = !elementsSelectable && !onClick;
const handleEdgeUpdate = typeof onEdgeUpdate !== 'undefined';
const onKeyDown = (event: KeyboardEvent) => {
if (elementSelectionKeys.includes(event.key) && elementsSelectable) {
@@ -204,28 +204,32 @@ export default (EdgeComponent: ComponentType<EdgeProps>) => {
interactionWidth={interactionWidth}
/>
)}
{handleEdgeUpdate && (
{isUpdatable && (
<>
<EdgeAnchor
position={sourcePosition}
centerX={sourceX}
centerY={sourceY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterSourceMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="source"
/>
<EdgeAnchor
position={targetPosition}
centerX={targetX}
centerY={targetY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterTargetMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="target"
/>
{(isUpdatable === 'source' || isUpdatable === true) && (
<EdgeAnchor
position={sourcePosition}
centerX={sourceX}
centerY={sourceY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterSourceMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="source"
/>
)}
{(isUpdatable === 'target' || isUpdatable === true) && (
<EdgeAnchor
position={targetPosition}
centerX={targetX}
centerY={targetY}
radius={edgeUpdaterRadius}
onMouseDown={onEdgeUpdaterTargetMouseDown}
onMouseEnter={onEdgeUpdaterMouseEnter}
onMouseOut={onEdgeUpdaterMouseOut}
type="target"
/>
)}
</>
)}
</g>

View File

@@ -95,10 +95,17 @@ export function handlePointerDown({
setState({
connectionPosition,
connectionStatus: null,
// connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
connectionNodeId: nodeId,
connectionHandleId: handleId,
connectionHandleType: handleType,
connectionStatus: null,
connectionStartHandle: {
nodeId,
handleId,
type: handleType,
},
connectionEndHandle: null,
});
onConnectStart?.(event, { nodeId, handleId, handleType });
@@ -145,6 +152,7 @@ export function handlePointerDown({
)
: connectionPosition,
connectionStatus: getConnectionStatus(!!prevClosestHandle, isValid),
connectionEndHandle: result.endHandle,
});
if (!prevClosestHandle && !isValid && !handleDomNode) {

View File

@@ -1,15 +1,15 @@
import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import { errorMessages, Position, type HandleProps, type Connection } from '@reactflow/system';
import { errorMessages, Position, type HandleProps, type Connection, type HandleType } from '@reactflow/system';
import { getHostForElement, isMouseEvent } from '@reactflow/utils';
import { useStore, useStoreApi } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext';
import { handlePointerDown } from './handler';
import { addEdge } from '../../utils/';
import { type ReactFlowState } from '../../types';
import { isValidHandle } from './utils';
import { addEdge } from '../../utils';
import type { ReactFlowState } from '../../types';
const alwaysValid = () => true;
@@ -21,6 +21,23 @@ const selector = (s: ReactFlowState) => ({
noPanClassName: s.noPanClassName,
});
const connectingSelector =
(nodeId: string | null, handleId: string | null, type: HandleType) => (state: ReactFlowState) => {
const {
connectionStartHandle: startHandle,
connectionEndHandle: endHandle,
connectionClickStartHandle: clickHandle,
} = state;
return {
connecting:
(startHandle?.nodeId === nodeId && startHandle?.handleId === handleId && startHandle?.type === type) ||
(endHandle?.nodeId === nodeId && endHandle?.handleId === handleId && endHandle?.type === type),
clickConnecting:
clickHandle?.nodeId === nodeId && clickHandle?.handleId === handleId && clickHandle?.type === type,
};
};
const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
(
{
@@ -28,6 +45,8 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
position = Position.Top,
isValidConnection,
isConnectable = true,
isConnectableStart = true,
isConnectableEnd = true,
id,
onConnect,
children,
@@ -38,19 +57,16 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
},
ref
) => {
const store = useStoreApi();
const nodeId = useNodeId();
if (!nodeId) {
store.getState().onError?.('010', errorMessages['010']());
return null;
}
const { connectionStartHandle, connectOnClick, noPanClassName } = useStore(selector, shallow);
const handleId = id || null;
const isTarget = type === 'target';
const store = useStoreApi();
const nodeId = useNodeId();
const { connectOnClick, noPanClassName } = useStore(selector, shallow);
const { connecting, clickConnecting } = useStore(connectingSelector(nodeId, handleId, type));
if (!nodeId) {
store.getState().onError?.('010', errorMessages['error010']());
}
const onConnectExtended = (params: Connection) => {
const { defaultEdgeOptions, onConnect: onConnectAction, hasDefaultEdges } = store.getState();
@@ -69,9 +85,16 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
};
const onPointerDown = (event: ReactMouseEvent<HTMLDivElement> | ReactTouchEvent<HTMLDivElement>) => {
if (!nodeId) {
return;
}
const isMouseTriggered = isMouseEvent(event.nativeEvent);
if ((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered) {
if (
isConnectableStart &&
((isMouseTriggered && (event as ReactMouseEvent<HTMLDivElement>).button === 0) || !isMouseTriggered)
) {
handlePointerDown({
event,
handleId,
@@ -95,12 +118,18 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
const {
onClickConnectStart,
onClickConnectEnd,
connectionClickStartHandle,
connectionMode,
isValidConnection: isValidConnectionStore,
} = store.getState();
if (!connectionStartHandle) {
if (!nodeId || (!connectionClickStartHandle && !isConnectableStart)) {
return;
}
if (!connectionClickStartHandle) {
onClickConnectStart?.(event, { nodeId, handleId, handleType: type });
store.setState({ connectionStartHandle: { nodeId, type, handleId } });
store.setState({ connectionClickStartHandle: { nodeId, type, handleId } });
return;
}
@@ -114,9 +143,9 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
type,
},
connectionMode,
connectionStartHandle.nodeId,
connectionStartHandle.handleId || null,
connectionStartHandle.type,
connectionClickStartHandle.nodeId,
connectionClickStartHandle.handleId || null,
connectionClickStartHandle.type,
isValidConnectionHandler,
doc
);
@@ -127,7 +156,7 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
onClickConnectEnd?.(event as unknown as MouseEvent);
store.setState({ connectionStartHandle: null });
store.setState({ connectionClickStartHandle: null });
};
return (
@@ -146,10 +175,12 @@ const Handle = forwardRef<HTMLDivElement, HandleComponentProps>(
source: !isTarget,
target: isTarget,
connectable: isConnectable,
connecting:
connectionStartHandle?.nodeId === nodeId &&
connectionStartHandle?.handleId === handleId &&
connectionStartHandle?.type === type,
connectablestart: isConnectableStart,
connectableend: isConnectableEnd,
connecting: clickConnecting,
// this class is used to style the handle when the user is connecting
connectionindicator:
isConnectable && ((isConnectableStart && !connecting) || (isConnectableEnd && connecting)),
},
])}
onMouseDown={onPointerDown}

View File

@@ -1,12 +1,12 @@
import { MouseEvent as ReactMouseEvent, TouchEvent as ReactTouchEvent } from 'react';
import {
internalsSymbol,
ConnectionMode,
ConnectionStatus,
type ConnectingHandle,
type Connection,
type HandleType,
type XYPosition,
type NodeHandleBounds,
type XYPosition,
} from '@reactflow/system';
import { getEventPosition } from '@reactflow/utils';
@@ -67,6 +67,7 @@ type Result = {
handleDomNode: Element | null;
isValid: boolean;
connection: Connection;
endHandle: ConnectingHandle | null;
};
const nullConnection: Connection = { source: null, target: null, sourceHandle: null, targetHandle: null };
@@ -78,7 +79,7 @@ export function isValidHandle(
connectionMode: ConnectionMode,
fromNodeId: string,
fromHandleId: string | null,
fromType: string,
fromType: HandleType,
isValidConnection: ValidConnectionFunc,
doc: Document | ShadowRoot
) {
@@ -94,12 +95,15 @@ export function isValidHandle(
handleDomNode: handleToCheck,
isValid: false,
connection: nullConnection,
endHandle: null,
};
if (handleToCheck) {
const handleType = getHandleType(undefined, handleToCheck);
const handleNodeId = handleToCheck.getAttribute('data-nodeid');
const handleId = handleToCheck.getAttribute('data-handleid');
const connectable = handleToCheck.classList.contains('connectable');
const connectableEnd = handleToCheck.classList.contains('connectableend');
const connection: Connection = {
source: isTarget ? handleNodeId : fromNodeId,
@@ -110,14 +114,21 @@ export function isValidHandle(
result.connection = connection;
const isConnectable = connectable && connectableEnd;
// in strict mode we don't allow target to target or source to source connections
const isValid =
handleToCheck.classList.contains('connectable') &&
isConnectable &&
(connectionMode === ConnectionMode.Strict
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId);
if (isValid) {
result.endHandle = {
nodeId: handleNodeId as string,
handleId,
type: handleType as HandleType,
};
result.isValid = isValidConnection(connection);
}
}

View File

@@ -21,6 +21,7 @@ type StoreUpdaterProps = Pick<
| 'nodesConnectable'
| 'nodesFocusable'
| 'edgesFocusable'
| 'edgesUpdatable'
| 'minZoom'
| 'maxZoom'
| 'nodeExtent'
@@ -99,6 +100,7 @@ const StoreUpdater = ({
nodesConnectable,
nodesFocusable,
edgesFocusable,
edgesUpdatable,
elevateNodesOnSelect,
minZoom,
maxZoom,
@@ -163,6 +165,7 @@ const StoreUpdater = ({
useDirectStoreUpdater('nodesConnectable', nodesConnectable, store.setState);
useDirectStoreUpdater('nodesFocusable', nodesFocusable, store.setState);
useDirectStoreUpdater('edgesFocusable', edgesFocusable, store.setState);
useDirectStoreUpdater('edgesUpdatable', edgesUpdatable, store.setState);
useDirectStoreUpdater('elementsSelectable', elementsSelectable, store.setState);
useDirectStoreUpdater('elevateNodesOnSelect', elevateNodesOnSelect, store.setState);
useDirectStoreUpdater('snapToGrid', snapToGrid, store.setState);

View File

@@ -43,7 +43,7 @@ export function useMarkerSymbol(type: MarkerType) {
const symbolExists = Object.prototype.hasOwnProperty.call(MarkerSymbols, type);
if (!symbolExists) {
store.getState().onError?.('009', errorMessages['009'](type));
store.getState().onError?.('009', errorMessages['error009'](type));
return null;
}

View File

@@ -38,6 +38,7 @@ type EdgeRendererProps = Pick<
const selector = (s: ReactFlowState) => ({
nodesConnectable: s.nodesConnectable,
edgesFocusable: s.edgesFocusable,
edgesUpdatable: s.edgesUpdatable,
elementsSelectable: s.elementsSelectable,
width: s.width,
height: s.height,
@@ -65,10 +66,8 @@ const EdgeRenderer = ({
onEdgeUpdateEnd,
children,
}: EdgeRendererProps) => {
const { edgesFocusable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } = useStore(
selector,
shallow
);
const { edgesFocusable, edgesUpdatable, elementsSelectable, width, height, connectionMode, nodeInternals, onError } =
useStore(selector, shallow);
const edgeTree = useVisibleEdges(onlyRenderVisibleElements, nodeInternals, elevateEdgesOnSelect);
if (!width) {
@@ -98,7 +97,7 @@ const EdgeRenderer = ({
let edgeType = edge.type || 'default';
if (!edgeTypes[edgeType]) {
onError?.('011', errorMessages['011'](edgeType));
onError?.('011', errorMessages['error011'](edgeType));
edgeType = 'default';
}
@@ -113,9 +112,12 @@ const EdgeRenderer = ({
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const isFocusable = !!(edge.focusable || (edgesFocusable && typeof edge.focusable === 'undefined'));
const isUpdatable =
typeof onEdgeUpdate !== 'undefined' &&
(edge.updatable || (edgesUpdatable && typeof edge.updatable === 'undefined'));
if (!sourceHandle || !targetHandle) {
onError?.('008', errorMessages['008'](sourceHandle, edge));
onError?.('008', errorMessages['error008'](sourceHandle, edge));
return null;
}
@@ -172,6 +174,7 @@ const EdgeRenderer = ({
rfId={rfId}
ariaLabel={edge.ariaLabel}
isFocusable={isFocusable}
isUpdatable={isUpdatable}
pathOptions={'pathOptions' in edge ? edge.pathOptions : undefined}
interactionWidth={edge.interactionWidth}
/>

View File

@@ -75,7 +75,7 @@ const NodeRenderer = (props: NodeRendererProps) => {
let nodeType = node.type || 'default';
if (!props.nodeTypes[nodeType]) {
onError?.('003', errorMessages['003'](nodeType));
onError?.('003', errorMessages['error003'](nodeType));
nodeType = 'default';
}

View File

@@ -119,6 +119,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
nodesFocusable,
nodeOrigin = initNodeOrigin,
edgesFocusable,
edgesUpdatable,
elementsSelectable,
defaultViewport = initDefaultViewport,
minZoom = 0.5,
@@ -270,6 +271,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
nodesConnectable={nodesConnectable}
nodesFocusable={nodesFocusable}
edgesFocusable={edgesFocusable}
edgesUpdatable={edgesUpdatable}
elementsSelectable={elementsSelectable}
elevateNodesOnSelect={elevateNodesOnSelect}
minZoom={minZoom}

View File

@@ -17,7 +17,7 @@ export function useNodeOrEdgeTypes(nodeOrEdgeTypes: any, createTypes: any): any
if (process.env.NODE_ENV === 'development') {
const typeKeys = Object.keys(nodeOrEdgeTypes);
if (shallow(typesKeysRef.current, typeKeys)) {
devWarn('002', errorMessages['002']());
devWarn('002', errorMessages['error002']());
}
typesKeysRef.current = typeKeys;

View File

@@ -139,7 +139,7 @@ const ZoomPane = ({
-(deltaX / currentZoom) * panOnScrollSpeed,
-(deltaY / currentZoom) * panOnScrollSpeed
);
});
}, { passive: false });
} else if (typeof d3ZoomHandler !== 'undefined') {
d3Selection.on('wheel.zoom', function (event: any, d: any) {
if (!preventScrolling || isWrappedWithClass(event, noWheelClassName)) {
@@ -148,7 +148,7 @@ const ZoomPane = ({
event.preventDefault();
d3ZoomHandler.call(this, event, d);
});
}, { passive: false });
}
}
}, [

View File

@@ -103,7 +103,7 @@ export function calcNextPosition(
]
: currentExtent;
} else {
onError?.('005', errorMessages['005']());
onError?.('005', errorMessages['error005']());
currentExtent = nodeExtent;
}

View File

@@ -3,16 +3,27 @@ import { internalsSymbol } from '@reactflow/system';
import { useStore } from './useStore';
import type { ReactFlowState } from '../types';
const selector = (s: ReactFlowState) => {
export type UseNodesInitializedOptions = {
includeHiddenNodes?: boolean;
};
const selector = (options: UseNodesInitializedOptions) => (s: ReactFlowState) => {
if (s.nodeInternals.size === 0) {
return false;
}
return s.getNodes().every((n) => n[internalsSymbol]?.handleBounds !== undefined);
return s
.getNodes()
.filter((n) => (options.includeHiddenNodes ? true : !n.hidden))
.every((n) => n[internalsSymbol]?.handleBounds !== undefined);
};
function useNodesInitialized(): boolean {
const initialized = useStore(selector);
const defaultOptions = {
includeHiddenNodes: false,
};
function useNodesInitialized(options: UseNodesInitializedOptions = defaultOptions): boolean {
const initialized = useStore(selector(options));
return initialized;
}

View File

@@ -18,7 +18,7 @@ function useResizeHandler(rendererNode: MutableRefObject<HTMLDivElement | null>)
const size = getDimensions(rendererNode.current);
if (size.height === 0 || size.width === 0) {
store.getState().onError?.('004', errorMessages['004']());
store.getState().onError?.('004', errorMessages['error004']());
}
store.setState({ width: size.width || 500, height: size.height || 500 });

View File

@@ -5,7 +5,7 @@ import { errorMessages } from '@reactflow/system';
import StoreContext from '../contexts/RFStoreContext';
import type { ReactFlowState } from '../types';
const zustandErrorMessage = errorMessages['001']();
const zustandErrorMessage = errorMessages['error001']();
type ExtractState = StoreApi<ReactFlowState> extends { getState: () => infer T } ? T : never;

View File

@@ -19,9 +19,9 @@ export { default as useViewport } from './hooks/useViewport';
export { default as useKeyPress } from './hooks/useKeyPress';
export * from './hooks/useNodesEdgesState';
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 useOnViewportChange, type UseOnViewportChangeOptions } from './hooks/useOnViewportChange';
export { default as useOnSelectionChange, type UseOnSelectionChangeOptions } from './hooks/useOnSelectionChange';
export { default as useNodesInitialized, type UseNodesInitializedOptions } from './hooks/useNodesInitialized';
export { default as useGetPointerPosition } from './hooks/useGetPointerPosition';
export { useNodeId } from './contexts/NodeIdContext';
export * from '@reactflow/edge-utils';

View File

@@ -35,8 +35,8 @@ const createRFStore = () =>
return Array.from(get().nodeInternals.values());
},
setEdges: (edges: Edge[]) => {
const { defaultEdgeOptions = null } = get();
set({ edges: defaultEdgeOptions ? edges.map((e) => ({ ...defaultEdgeOptions, ...e })) : edges });
const { defaultEdgeOptions = {} } = get();
set({ edges: edges.map((e) => ({ ...defaultEdgeOptions, ...e })) });
},
setDefaultNodesAndEdges: (nodes?: Node[], edges?: Edge[]) => {
const hasDefaultNodes = typeof nodes !== 'undefined';
@@ -299,6 +299,8 @@ const createRFStore = () =>
connectionHandleId: initialState.connectionHandleId,
connectionHandleType: initialState.connectionHandleType,
connectionStatus: initialState.connectionStatus,
connectionStartHandle: initialState.connectionStartHandle,
connectionEndHandle: initialState.connectionEndHandle,
}),
reset: () => set({ ...initialState }),
}));

View File

@@ -42,6 +42,7 @@ const initialState: ReactFlowStore = {
nodesConnectable: true,
nodesFocusable: true,
edgesFocusable: true,
edgesUpdatable: true,
elementsSelectable: true,
elevateNodesOnSelect: true,
fitViewOnInit: false,
@@ -51,6 +52,8 @@ const initialState: ReactFlowStore = {
multiSelectionActive: false,
connectionStartHandle: null,
connectionEndHandle: null,
connectionClickStartHandle: null,
connectOnClick: true,
ariaLiveMessage: '',

View File

@@ -144,7 +144,7 @@
min-width: 5px;
min-height: 5px;
&.connectable {
&.connectionindicator {
pointer-events: all;
cursor: crosshair;
}

View File

@@ -114,6 +114,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
nodesFocusable?: boolean;
nodeOrigin?: NodeOrigin;
edgesFocusable?: boolean;
edgesUpdatable?: boolean;
initNodeOrigin?: NodeOrigin;
elementsSelectable?: boolean;
selectNodesOnDrag?: boolean;

View File

@@ -25,11 +25,14 @@ export type EdgeLabelOptions = {
labelBgBorderRadius?: number;
};
export type EdgeUpdatable = boolean | HandleType;
export type DefaultEdge<EdgeData = any> = BaseEdge<EdgeData> & {
style?: CSSProperties;
className?: string;
sourceNode?: Node;
targetNode?: Node;
updatable?: EdgeUpdatable;
} & EdgeLabelOptions;
type SmoothStepEdgeType<T> = DefaultEdge<T> & {
@@ -68,6 +71,7 @@ export type WrapEdgeProps<T = any> = Omit<Edge<T>, 'sourceHandle' | 'targetHandl
onEdgeUpdateEnd?: (event: MouseEvent | TouchEvent, edge: Edge, handleType: HandleType) => void;
rfId?: string;
isFocusable: boolean;
isUpdatable: EdgeUpdatable;
pathOptions?: BezierPathOptions | SmoothStepPathOptions;
};

View File

@@ -13,7 +13,7 @@ import {
OnViewportChange,
SelectionRect,
SnapGrid,
StartHandle,
ConnectingHandle,
Transform,
XYPosition,
} from '@reactflow/system';
@@ -80,12 +80,15 @@ export type ReactFlowStore = {
nodesConnectable: boolean;
nodesFocusable: boolean;
edgesFocusable: boolean;
edgesUpdatable: boolean;
elementsSelectable: boolean;
elevateNodesOnSelect: boolean;
multiSelectionActive: boolean;
connectionStartHandle: StartHandle | null;
connectionStartHandle: ConnectingHandle | null;
connectionEndHandle: ConnectingHandle | null;
connectionClickStartHandle: ConnectingHandle | null;
onNodeDragStart?: NodeDragHandler;
onNodeDrag?: NodeDragHandler;

View File

@@ -1,5 +1,22 @@
# @reactflow/minimap
## 11.5.0
### Minor Changes
- [#2944](https://github.com/wbkd/react-flow/pull/2944) Thanks [@Elringus](https://github.com/Elringus)! - add `inversePan` and `zoomStep` props
### Patch Changes
- Updated dependencies [[`098eee3d`](https://github.com/wbkd/react-flow/commit/098eee3d41dabc870777b081796401ff13b5a776), [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb), [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402), [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38), [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065), [`c22e1c28`](https://github.com/wbkd/react-flow/commit/c22e1c28c5555a638c2a8e82c3bfc986b3965d36)]:
- @reactflow/core@11.7.0
## 11.4.1
### Patch Changes
- Updated dependencies
## 11.4.0
### Minor Changes

View File

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

View File

@@ -67,6 +67,8 @@ function MiniMap({
pannable = false,
zoomable = false,
ariaLabel = 'React Flow mini map',
inversePan = false,
zoomStep = 10
}: MiniMapProps) {
const store = useStoreApi();
const svg = useRef<SVGSVGElement>(null);
@@ -106,7 +108,7 @@ function MiniMap({
const pinchDelta =
-event.sourceEvent.deltaY *
(event.sourceEvent.deltaMode === 1 ? 0.05 : event.sourceEvent.deltaMode ? 1 : 0.002) *
10;
zoomStep;
const zoom = transform[2] * Math.pow(2, pinchDelta);
d3Zoom.scaleTo(d3Selection, zoom);
@@ -120,9 +122,10 @@ function MiniMap({
}
// @TODO: how to calculate the correct next position? Math.max(1, transform[2]) is a workaround.
const moveScale = viewScaleRef.current * Math.max(1, transform[2]) * (inversePan ? -1 : 1);
const position = {
x: transform[0] - event.sourceEvent.movementX * viewScaleRef.current * Math.max(1, transform[2]),
y: transform[1] - event.sourceEvent.movementY * viewScaleRef.current * Math.max(1, transform[2]),
x: transform[0] - event.sourceEvent.movementX * moveScale,
y: transform[1] - event.sourceEvent.movementY * moveScale,
};
const extent: CoordinateExtent = [
[0, 0],
@@ -147,7 +150,7 @@ function MiniMap({
selection.on('zoom', null);
};
}
}, [pannable, zoomable]);
}, [pannable, zoomable, inversePan, zoomStep]);
const onSvgClick = onClick
? (event: MouseEvent) => {

View File

@@ -20,6 +20,8 @@ export type MiniMapProps<NodeData = any> = Omit<HTMLAttributes<SVGSVGElement>, '
pannable?: boolean;
zoomable?: boolean;
ariaLabel?: string | null;
inversePan?: boolean;
zoomStep?: number;
};
export interface MiniMapNodeProps {

View File

@@ -1,5 +1,18 @@
# @reactflow/node-toolbar
## 1.1.11
### Patch Changes
- Updated dependencies [[`098eee3d`](https://github.com/wbkd/react-flow/commit/098eee3d41dabc870777b081796401ff13b5a776), [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb), [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402), [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38), [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065), [`c22e1c28`](https://github.com/wbkd/react-flow/commit/c22e1c28c5555a638c2a8e82c3bfc986b3965d36)]:
- @reactflow/core@11.7.0
## 1.1.10
### Patch Changes
- Updated dependencies
## 1.1.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@reactflow/node-toolbar",
"version": "1.1.9",
"version": "1.1.11",
"description": "A toolbar component for React Flow that can be attached to a node.",
"keywords": [
"react",

View File

@@ -1,5 +1,50 @@
# reactflow
## 11.7.0
Most notable updates:
- `@reactflow/node-resizer` is now part of this package. No need to install it separately anymore.
- Handles: `isConnectableStart` and `isConnectableEnd` props to configure if you can start or end a connection at a certain handle
- Edges: `updatable` option to enable updates for specific edges
- MiniMap: `inversePan` and `zoomStep` props
- Background: `id` and `offset` props - this enables you to combine different patterns (useful if you want a graph paper like background for example)
- useNodesInitialized: options to configure if hidden nodes should be included (false by default)
Big thanks to [@Elringus](https://github.com/Elringus) and [@bcakmakoglu](https://github.com/bcakmakoglu)!
### Minor Changes
- [#2964](https://github.com/wbkd/react-flow/pull/2964) [`2fb4c2c8`](https://github.com/wbkd/react-flow/commit/2fb4c2c82343751ff536da262de74bd9080321b4) - add @reactflow/node-resizer package
- [#2960](https://github.com/wbkd/react-flow/pull/2960) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - edges: add `updatable` option
- [#2958](https://github.com/wbkd/react-flow/pull/2958) [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065) - handles: add `isConnectableStart` and `isConnectableEnd` props
- [#2956](https://github.com/wbkd/react-flow/pull/2956) [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38) - add options for `useNodesInitialized`, ignore hidden nodes by default
- [#2944](https://github.com/wbkd/react-flow/pull/2944) Thanks [@Elringus](https://github.com/Elringus)! - add `inversePan` and `zoomStep` props
- [#2941](https://github.com/wbkd/react-flow/pull/2941) Thanks [@Elringus](https://github.com/Elringus)! - background: add `id` and `offset` props
### Patch Changes
- [#2926](https://github.com/wbkd/react-flow/pull/2926) Thanks [@Elringus](https://github.com/Elringus)! - fix non-passive wheel event listener violation
- [#2933](https://github.com/wbkd/react-flow/pull/2933) [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb) - prefix error keys with "error"
- [#2939](https://github.com/wbkd/react-flow/pull/2939) [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402) - add connection result to store
### Patch Changes
- Updated dependencies [[`098eee3d`](https://github.com/wbkd/react-flow/commit/098eee3d41dabc870777b081796401ff13b5a776), [`fe8cac0a`](https://github.com/wbkd/react-flow/commit/fe8cac0adb359109e0e9eafe8b9261ba354076bb), [`4a4ca171`](https://github.com/wbkd/react-flow/commit/4a4ca171955f5c8d58b23e3ad48406f1a21dc402), [`c1448c2f`](https://github.com/wbkd/react-flow/commit/c1448c2f7415dd3b4b2c54e05404c5ab24e8978d), [`923a54c4`](https://github.com/wbkd/react-flow/commit/923a54c481b90954806202817ba844cfa7203a38), [`4d97a0ed`](https://github.com/wbkd/react-flow/commit/4d97a0ed168ce643fc0c99fa6b47cf1296d66065), [`771c7a5d`](https://github.com/wbkd/react-flow/commit/771c7a5d133ce96e9f7471394c15189e0657ce01), [`c22e1c28`](https://github.com/wbkd/react-flow/commit/c22e1c28c5555a638c2a8e82c3bfc986b3965d36)]:
- @reactflow/core@11.7.0
- @reactflow/minimap@11.5.0
- @reactflow/background@11.2.0
- @reactflow/controls@11.1.11
- @reactflow/node-toolbar@1.1.11
## 11.6.1
### Patch Changes
- Always create new edge object (fixes an issue with Redux toolkit and other immutable helper libs)
## 11.6.0
This release introduces a new `isValidConnection` prop for the ReactFlow component. You no longer need to pass it to all your Handle components but can pass it once. We also added a new option for the `updateEdge` function that allows you to specify if you want to replace an id when updating it. More over the `MiniMap` got a new `nodeComponent` prop to pass a custom component for the mini map nodes.

View File

@@ -1,6 +1,6 @@
{
"name": "reactflow",
"version": "11.6.0",
"version": "11.7.0",
"description": "A highly customizable React library for building node-based editors and interactive flow charts",
"keywords": [
"react",
@@ -39,6 +39,7 @@
"@reactflow/controls": "workspace:*",
"@reactflow/core": "workspace:*",
"@reactflow/minimap": "workspace:*",
"@reactflow/node-resizer": "workspace:*",
"@reactflow/node-toolbar": "workspace:*"
},
"peerDependencies": {

View File

@@ -1,3 +1,4 @@
@import '@reactflow/core/dist/base.css';
@import '@reactflow/controls/dist/style.css';
@import '@reactflow/minimap/dist/style.css';
@import '@reactflow/node-resizer/dist/style.css';

View File

@@ -3,5 +3,6 @@ export * from '@reactflow/minimap';
export * from '@reactflow/controls';
export * from '@reactflow/background';
export * from '@reactflow/node-toolbar';
export * from '@reactflow/node-resizer';
export { ReactFlow as default } from '@reactflow/core';

View File

@@ -1,3 +1,4 @@
@import '@reactflow/core/dist/style.css';
@import '@reactflow/controls/dist/style.css';
@import '@reactflow/minimap/dist/style.css';
@import '@reactflow/node-resizer/dist/style.css';

View File

@@ -1,20 +1,20 @@
import { BaseEdge, HandleElement } from './types';
export const errorMessages = {
'001': () =>
error001: () =>
'[React Flow]: Seems like you have not used zustand provider as an ancestor. Help: https://reactflow.dev/error#001',
'002': () =>
error002: () =>
"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",
'003': (nodeType: string) => `Node type "${nodeType}" not found. Using fallback type "default".`,
'004': () => 'The React Flow parent container needs a width and a height to render the graph.',
'005': () => 'Only child nodes can use a parent extent.',
'006': () => "Can't create edge. An edge needs a source and a target.",
'007': (id: string) => `The old edge with id=${id} does not exist.`,
'009': (type: string) => `Marker type "${type}" doesn't exist.`,
'008': (sourceHandle: HandleElement | null, edge: BaseEdge) =>
error003: (nodeType: string) => `Node type "${nodeType}" not found. Using fallback type "default".`,
error004: () => 'The React Flow parent container needs a width and a height to render the graph.',
error005: () => 'Only child nodes can use a parent extent.',
error006: () => "Can't create edge. An edge needs a source and a target.",
error007: (id: string) => `The old edge with id=${id} does not exist.`,
error009: (type: string) => `Marker type "${type}" doesn't exist.`,
error008: (sourceHandle: HandleElement | null, edge: BaseEdge) =>
`Couldn't create edge for ${!sourceHandle ? 'source' : 'target'} handle id: "${
!sourceHandle ? edge.sourceHandle : edge.targetHandle
}", edge id: ${edge.id}.`,
'010': () => 'Handle: No node id found. Make sure to only use a Handle inside a custom Node.',
'011': (edgeType: string) => `Edge type "${edgeType}" not found. Using fallback type "default".`,
error010: () => 'Handle: No node id found. Make sure to only use a Handle inside a custom Node.',
error011: (edgeType: string) => `Edge type "${edgeType}" not found. Using fallback type "default".`,
};

View File

@@ -8,7 +8,7 @@ export type HandleElement = XYPosition &
position: Position;
};
export type StartHandle = {
export type ConnectingHandle = {
nodeId: string;
type: HandleType;
handleId?: string | null;
@@ -18,6 +18,8 @@ export type HandleProps = {
type: HandleType;
position: Position;
isConnectable?: boolean;
isConnectableStart?: boolean;
isConnectableEnd?: boolean;
onConnect?: OnConnect;
isValidConnection?: (connection: Connection) => boolean;
id?: string;

View File

@@ -86,7 +86,7 @@ export const addEdgeBase = <EdgeType extends BaseEdge>(
edges: EdgeType[]
): EdgeType[] => {
if (!edgeParams.source || !edgeParams.target) {
devWarn('006', errorMessages['006']());
devWarn('006', errorMessages['error006']());
return edges;
}
@@ -121,7 +121,7 @@ export const updateEdgeBase = <EdgeType extends BaseEdge>(
const { id: oldEdgeId, ...rest } = oldEdge;
if (!newConnection.source || !newConnection.target) {
devWarn('006', errorMessages['006']());
devWarn('006', errorMessages['error006']());
return edges;
}
@@ -129,7 +129,7 @@ export const updateEdgeBase = <EdgeType extends BaseEdge>(
const foundEdge = edges.find((e) => e.id === oldEdge.id) as EdgeType;
if (!foundEdge) {
devWarn('007', errorMessages['007'](oldEdgeId));
devWarn('007', errorMessages['error007'](oldEdgeId));
return edges;
}

2918
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff