diff --git a/.changeset/rotten-bikes-hunt.md b/.changeset/rotten-bikes-hunt.md new file mode 100644 index 00000000..e4dd48ad --- /dev/null +++ b/.changeset/rotten-bikes-hunt.md @@ -0,0 +1,5 @@ +--- +'@xyflow/system': patch +--- + +Added OnReconnect types diff --git a/examples/react/src/examples/Basic/index.tsx b/examples/react/src/examples/Basic/index.tsx index 5754fd62..168bcff6 100644 --- a/examples/react/src/examples/Basic/index.tsx +++ b/examples/react/src/examples/Basic/index.tsx @@ -11,6 +11,7 @@ import { useReactFlow, Panel, OnNodeDrag, + FitViewOptions, } from '@xyflow/react'; const onNodeDrag: OnNodeDrag = (_, node: Node, nodes: Node[]) => console.log('drag', node, nodes); @@ -54,6 +55,9 @@ const initialEdges: Edge[] = [ ]; const defaultEdgeOptions = {}; +const fitViewOptions: FitViewOptions = { + padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 }, +}; const BasicFlow = () => { const { @@ -135,6 +139,7 @@ const BasicFlow = () => { { minZoom={0.2} maxZoom={4} fitView - fitViewOptions={{ - padding: { top: '100px', left: '0%', right: '10%', bottom: 0.1 }, - }} + fitViewOptions={fitViewOptions} defaultEdgeOptions={defaultEdgeOptions} selectNodesOnDrag={false} elevateEdgesOnSelect diff --git a/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx b/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx new file mode 100644 index 00000000..ca860fdf --- /dev/null +++ b/examples/react/src/examples/NodeResizer/BottomRightResizer.tsx @@ -0,0 +1,21 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps, NodeResizeControl, ResizeControlVariant } from '@xyflow/react'; + +const CustomResizerNode: FC = ({ data }) => { + return ( + <> + + +
{data.label}
+ + + ); +}; + +export default memo(CustomResizerNode); diff --git a/examples/react/src/examples/NodeResizer/index.tsx b/examples/react/src/examples/NodeResizer/index.tsx index 397c1f22..ce6faf3b 100644 --- a/examples/react/src/examples/NodeResizer/index.tsx +++ b/examples/react/src/examples/NodeResizer/index.tsx @@ -15,12 +15,14 @@ import DefaultResizer from './DefaultResizer'; import CustomResizer from './CustomResizer'; import VerticalResizer from './VerticalResizer'; import HorizontalResizer from './HorizontalResizer'; +import BottomRightResizer from './BottomRightResizer'; const nodeTypes = { defaultResizer: DefaultResizer, customResizer: CustomResizer, verticalResizer: VerticalResizer, horizontalResizer: HorizontalResizer, + bottomRightResizer: BottomRightResizer, }; const nodeStyle = { @@ -166,6 +168,13 @@ const initialNodes: Node[] = [ expandParent: true, style: { ...nodeStyle }, }, + { + id: '6', + type: 'bottomRightResizer', + data: { label: 'Bottom Right with horizontal direction' }, + position: { x: 500, y: 500 }, + style: { ...nodeStyle }, + }, ]; const CustomNodeFlow = () => { diff --git a/examples/react/src/examples/UseOnSelectionChange/index.tsx b/examples/react/src/examples/UseOnSelectionChange/index.tsx index fdbe124a..460ee9c3 100644 --- a/examples/react/src/examples/UseOnSelectionChange/index.tsx +++ b/examples/react/src/examples/UseOnSelectionChange/index.tsx @@ -10,6 +10,7 @@ import { useEdgesState, useOnSelectionChange, OnSelectionChangeParams, + Panel, } from '@xyflow/react'; const initialNodes: Node[] = [ @@ -51,6 +52,12 @@ const Flow = () => { const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); const onConnect = useCallback((params: Edge | Connection) => setEdges((els) => addEdge(params, els)), [setEdges]); + const [elementsSelectable, setElementsSelectable] = useState(true); + const [secondLoggerActive, setSecondLoggerActive] = useState(true); + + const toggleSecondLogger = () => { + setSecondLoggerActive(!secondLoggerActive); + }; return ( { onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} - /> - ); -}; + elementsSelectable={elementsSelectable} + > + + + + -const WrappedFlow = () => { - const [secondLoggerActive, setSecondLoggerActive] = useState(true); - - const toggleSecondLogger = () => { - setSecondLoggerActive(!secondLoggerActive); - }; - - return ( - - {secondLoggerActive && } -
- -
-
+
); }; -export default WrappedFlow; +export default Flow; diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 8e49ed34..495cfd7c 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,48 @@ # @xyflow/react +## 12.6.1 + +### Patch Changes + +- [#5249](https://github.com/xyflow/xyflow/pull/5249) [`895b5d81`](https://github.com/xyflow/xyflow/commit/895b5d81c8ee5236009820ecd0ed6806c6e59e29) Thanks [@moklick](https://github.com/moklick)! - Call `onNodesChange` for uncontrolled flows that use `updateNode` + +- [#5247](https://github.com/xyflow/xyflow/pull/5247) [`67e1cb68`](https://github.com/xyflow/xyflow/commit/67e1cb6891078dbcb9e1d06b9f9fdbfc79860ab6) Thanks [@moklick](https://github.com/moklick)! - Cleanup TSDoc annotations for ReactFlow + +- Updated dependencies [[`2a03213b`](https://github.com/xyflow/xyflow/commit/2a03213b0695d504f831579ec9df3f9de2d3e0bd)]: + - @xyflow/system@0.0.58 + +## 12.6.0 + +### Minor Changes + +- [#5219](https://github.com/xyflow/xyflow/pull/5219) [`4236adbc`](https://github.com/xyflow/xyflow/commit/4236adbc462b3f65ee41869ef426491ed3fa8ba0) Thanks [@moklick](https://github.com/moklick)! - Add initialMinZoom, initialMaxZoom and initialFitViewOptions to ReactFlowProvider + +- [#5227](https://github.com/xyflow/xyflow/pull/5227) [`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35) Thanks [@moklick](https://github.com/moklick)! - Add `resizeDirection` prop for the `NodeResizeControl` component + +### Patch Changes + +- [#5217](https://github.com/xyflow/xyflow/pull/5217) [`bce74e88`](https://github.com/xyflow/xyflow/commit/bce74e8811a98c967b5bd06c3e5aecde24c8b679) Thanks [@moklick](https://github.com/moklick)! - Keep node seleciton on pane click if elementsSelectable=false + +- Updated dependencies [[`a7d10ffc`](https://github.com/xyflow/xyflow/commit/a7d10ffce5a0195471681980f97b1b5f6c448f35), [`4e681f9c`](https://github.com/xyflow/xyflow/commit/4e681f9c529c3f4f8b2ac5d25b4db7878c197e14)]: + - @xyflow/system@0.0.57 + +## 12.5.6 + +### Patch Changes + +- [#5212](https://github.com/xyflow/xyflow/pull/5212) [`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13) Thanks [@moklick](https://github.com/moklick)! - Add polyfill for `Promise.withResolvers` + +- [#5192](https://github.com/xyflow/xyflow/pull/5192) [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7) Thanks [@peterkogo](https://github.com/peterkogo)! - Optimize performance of nodesInitialized + +- [#5196](https://github.com/xyflow/xyflow/pull/5196) [`7f902db4`](https://github.com/xyflow/xyflow/commit/7f902db46b6f1ea4adc94390db8d5db47f8c5903) Thanks [@moklick](https://github.com/moklick)! - Hide edge marker and attribution for screenreaders + +- [#5213](https://github.com/xyflow/xyflow/pull/5213) [`78782c16`](https://github.com/xyflow/xyflow/commit/78782c1696323ee9be0d38bbd807bd3fde5f549d) Thanks [@moklick](https://github.com/moklick)! - Do not trigger selection events if elementsSelectable=false + +- [#5191](https://github.com/xyflow/xyflow/pull/5191) [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e) Thanks [@peterkogo](https://github.com/peterkogo)! - Fix legacy padding being slightly larger than before + +- Updated dependencies [[`0f43b8ea`](https://github.com/xyflow/xyflow/commit/0f43b8ea45bd293e50e4a86d83868074bb323f13), [`fc241253`](https://github.com/xyflow/xyflow/commit/fc241253d5dba35f5febf411e77dbc5acb91d5d7), [`98fe23c7`](https://github.com/xyflow/xyflow/commit/98fe23c7c2b12972f1b7def866215ce82a86e2c0), [`e5735b51`](https://github.com/xyflow/xyflow/commit/e5735b514a54d86ba0ca7bd725e8bfead89fc08e)]: + - @xyflow/system@0.0.56 + ## 12.5.5 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index 1f42f3aa..c85ed8ed 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.5.5", + "version": "12.6.1", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.", "keywords": [ "react", diff --git a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx index 0748ffa6..beaeb527 100644 --- a/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx +++ b/packages/react/src/additional-components/NodeResizer/NodeResizeControl.tsx @@ -32,6 +32,7 @@ function ResizeControl({ maxWidth = Number.MAX_VALUE, maxHeight = Number.MAX_VALUE, keepAspectRatio = false, + resizeDirection, shouldResize, onResizeStart, onResize, @@ -117,11 +118,12 @@ function ResizeControl({ } if (change.width !== undefined && change.height !== undefined) { + const setAttributes = !resizeDirection ? true : resizeDirection === 'horizontal' ? 'width' : 'height'; const dimensionChange: NodeDimensionChange = { id, type: 'dimensions', resizing: true, - setAttributes: true, + setAttributes, dimensions: { width: change.width, height: change.height, @@ -166,6 +168,7 @@ function ResizeControl({ maxHeight, }, keepAspectRatio, + resizeDirection, onResizeStart, onResize, onResizeEnd, diff --git a/packages/react/src/additional-components/NodeResizer/types.ts b/packages/react/src/additional-components/NodeResizer/types.ts index e34329b1..e6dd9d36 100644 --- a/packages/react/src/additional-components/NodeResizer/types.ts +++ b/packages/react/src/additional-components/NodeResizer/types.ts @@ -3,6 +3,7 @@ import type { ControlPosition, ControlLinePosition, ResizeControlVariant, + ResizeControlDirection, ShouldResize, OnResizeStart, OnResize, @@ -97,6 +98,11 @@ export type ResizeControlProps = Pick< * @example ResizeControlVariant.Handle, ResizeControlVariant.Line */ variant?: ResizeControlVariant; + /** + * The direction the user can resize the node. + * If not provided, the user can resize in any direction. + */ + resizeDirection?: ResizeControlDirection; className?: string; style?: CSSProperties; children?: ReactNode; @@ -105,6 +111,6 @@ export type ResizeControlProps = Pick< /** * @expand */ -export type ResizeControlLineProps = ResizeControlProps & { +export type ResizeControlLineProps = Omit & { position?: ControlLinePosition; }; diff --git a/packages/react/src/components/BatchProvider/index.tsx b/packages/react/src/components/BatchProvider/index.tsx index f41b79a8..b2ed270f 100644 --- a/packages/react/src/components/BatchProvider/index.tsx +++ b/packages/react/src/components/BatchProvider/index.tsx @@ -40,28 +40,27 @@ export function BatchProvider[]; + if (hasDefaultNodes) { setNodes(next); - } else { - // When a controlled flow is used we need to collect the changes - const changes = getElementsDiffChanges({ - items: next, - lookup: nodeLookup, - }) as NodeChange[]; + } - // We only want to fire onNodesChange if there are changes to the nodes - if (changes.length > 0) { - onNodesChange?.(changes); - } else if (fitViewQueued) { - // If there are no changes to the nodes, we still need to call setNodes - // to trigger a re-render and fitView. - window.requestAnimationFrame(() => { - const { fitViewQueued, nodes, setNodes } = store.getState(); - if (fitViewQueued) { - setNodes(nodes); - } - }); - } + // We only want to fire onNodesChange if there are changes to the nodes + if (changes.length > 0) { + onNodesChange?.(changes); + } else if (fitViewQueued) { + // If there are no changes to the nodes, we still need to call setNodes + // to trigger a re-render and fitView. + window.requestAnimationFrame(() => { + const { fitViewQueued, nodes, setNodes } = store.getState(); + if (fitViewQueued) { + setNodes(nodes); + } + }); } }, []); diff --git a/packages/react/src/components/ReactFlowProvider/index.tsx b/packages/react/src/components/ReactFlowProvider/index.tsx index 1492ca3f..11a9c6b8 100644 --- a/packages/react/src/components/ReactFlowProvider/index.tsx +++ b/packages/react/src/components/ReactFlowProvider/index.tsx @@ -3,18 +3,49 @@ import { useState, type ReactNode } from 'react'; import { Provider } from '../../contexts/StoreContext'; import { createStore } from '../../store'; import { BatchProvider } from '../BatchProvider'; -import type { Node, Edge } from '../../types'; +import type { Node, Edge, FitViewOptions } from '../../types'; import { CoordinateExtent, NodeOrigin } from '@xyflow/system'; export type ReactFlowProviderProps = { + /** These nodes are used to initialize the flow. They are not dynamic. */ initialNodes?: Node[]; + /** These edges are used to initialize the flow. They are not dynamic. */ initialEdges?: Edge[]; + /** These nodes are used to initialize the flow. They are not dynamic. */ defaultNodes?: Node[]; + /** These edges are used to initialize the flow. They are not dynamic. */ defaultEdges?: Edge[]; + /** The initial width is necessary to be able to use fitView on the server */ initialWidth?: number; + /** The initial height is necessary to be able to use fitView on the server */ initialHeight?: number; + /** When `true`, the flow will be zoomed and panned to fit all the nodes initially provided. */ fitView?: boolean; + /** + * You can provide an object of options to customize the initial fitView behavior. + */ + initialFitViewOptions?: FitViewOptions; + /** Initial minimum zoom level */ + initialMinZoom?: number; + /** Initial maximum zoom level */ + initialMaxZoom?: number; + /** + * The origin of the node to use when placing it in the flow or looking up its `x` and `y` + * position. An origin of `[0, 0]` means that a node's top left corner will be placed at the `x` + * and `y` position. + * @default [0, 0] + * @example + * [0, 0] // default, top left + * [0.5, 0.5] // center + * [1, 1] // bottom right + */ nodeOrigin?: NodeOrigin; + /** + * By default, nodes can be placed on an infinite flow. You can use this prop to set a boundary. + * + * The first pair of coordinates is the top left boundary and the second pair is the bottom right. + * @example [[-1000, -10000], [1000, 1000]] + */ nodeExtent?: CoordinateExtent; children: ReactNode; }; @@ -60,6 +91,9 @@ export function ReactFlowProvider({ defaultEdges, initialWidth: width, initialHeight: height, + initialMinZoom: minZoom, + initialMaxZoom: maxZoom, + initialFitViewOptions: fitViewOptions, fitView, nodeOrigin, nodeExtent, @@ -74,6 +108,9 @@ export function ReactFlowProvider({ width, height, fitView, + minZoom, + maxZoom, + fitViewOptions, nodeOrigin, nodeExtent, }) diff --git a/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx b/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx index 17b5950c..1c12775d 100644 --- a/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx +++ b/packages/react/src/container/EdgeRenderer/MarkerDefinitions.tsx @@ -67,7 +67,7 @@ const MarkerDefinitions = ({ defaultColor, rfId }: MarkerDefinitionsProps) => { } return ( - +