diff --git a/.changeset/blue-teachers-sell.md b/.changeset/blue-teachers-sell.md new file mode 100644 index 00000000..f0d944aa --- /dev/null +++ b/.changeset/blue-teachers-sell.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Make it possible to use expandParent with immer and other immutable helpers diff --git a/.changeset/cuddly-mails-kneel.md b/.changeset/cuddly-mails-kneel.md new file mode 100644 index 00000000..2235a163 --- /dev/null +++ b/.changeset/cuddly-mails-kneel.md @@ -0,0 +1,6 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Add group node to BuiltInNode type diff --git a/.changeset/old-lobsters-cover.md b/.changeset/old-lobsters-cover.md new file mode 100644 index 00000000..82795889 --- /dev/null +++ b/.changeset/old-lobsters-cover.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Allow custom data-testid for ReactFlow component diff --git a/.changeset/quick-carrots-allow.md b/.changeset/quick-carrots-allow.md new file mode 100644 index 00000000..960c3815 --- /dev/null +++ b/.changeset/quick-carrots-allow.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Type isValidConnection prop correctly by passing EdgeType diff --git a/.changeset/selfish-tables-tie.md b/.changeset/selfish-tables-tie.md new file mode 100644 index 00000000..8ac759cf --- /dev/null +++ b/.changeset/selfish-tables-tie.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Support passing `path` element attributes to `BaseEdge` component. diff --git a/.changeset/silly-beans-remain.md b/.changeset/silly-beans-remain.md new file mode 100644 index 00000000..42c8b49e --- /dev/null +++ b/.changeset/silly-beans-remain.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Prevent default scrolling behavior when nodes or a selection is moved with an arrow key press. diff --git a/.changeset/thick-fans-cheer.md b/.changeset/thick-fans-cheer.md new file mode 100644 index 00000000..af987d35 --- /dev/null +++ b/.changeset/thick-fans-cheer.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Forward ref of the div inside Panel components. diff --git a/README.md b/README.md index 8f6ce20b..585dac0e 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ React Flow and Svelte Flow are maintained by the team behind [xyflow](https://xy - Christopher • [Twitter](https://twitter.com/chrtze) • [Github](https://github.com/chrtze) - Hayleigh • [Twitter](https://twitter.com/hayleighdotdev) • [Github](https://github.com/hayleigh-dot-dev) -- John • [Website](https://johnrobbdesign.com/) • [Mastodon](https://mastodon.social/@johnrobbjr) +- Abbey • [Github](https://github.com/printerscanner) - Moritz • [Twitter](https://twitter.com/moklick) • [Github](https://github.com/moklick) - Peter • [Github](https://github.com/peterkogo) diff --git a/examples/react/src/examples/Edges/CustomEdge3.css b/examples/react/src/examples/Edges/CustomEdge3.css new file mode 100644 index 00000000..570b7c75 --- /dev/null +++ b/examples/react/src/examples/Edges/CustomEdge3.css @@ -0,0 +1,14 @@ +@keyframes react-flow-edge-dash { + from { + stroke-dashoffset: 100; + } + to { + stroke-dashoffset: 0; + } +} + +.react-flow__edge-custom3 { + stroke-dasharray: 100; + stroke-dashoffset: 100; + animation: react-flow-edge-dash 1s linear forwards; +} diff --git a/examples/react/src/examples/Edges/CustomEdge3.tsx b/examples/react/src/examples/Edges/CustomEdge3.tsx new file mode 100644 index 00000000..37a3d40f --- /dev/null +++ b/examples/react/src/examples/Edges/CustomEdge3.tsx @@ -0,0 +1,38 @@ +import { FC } from 'react'; +import { BaseEdge, EdgeProps, EdgeText, getSmoothStepPath } from '@xyflow/react'; +import './CustomEdge3.css'; + +const CustomEdge: FC = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + data, +}) => { + const [edgePath, labelX, labelY] = getSmoothStepPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + return ( + <> + + console.log(data)} + /> + + ); +}; + +export default CustomEdge; diff --git a/examples/react/src/examples/Edges/index.tsx b/examples/react/src/examples/Edges/index.tsx index bbba30fb..6e18d24a 100644 --- a/examples/react/src/examples/Edges/index.tsx +++ b/examples/react/src/examples/Edges/index.tsx @@ -16,6 +16,7 @@ import { import CustomEdge from './CustomEdge'; import CustomEdge2 from './CustomEdge2'; +import CustomEdge3 from './CustomEdge3'; const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); @@ -63,6 +64,12 @@ const initialNodes: Node[] = [ data: { label: 'Output 9' }, position: { x: 675, y: 500 }, }, + { + id: '10', + type: 'output', + data: { label: 'Output 10' }, + position: { x: 50, y: 400 }, + }, ]; const initialEdges: Edge[] = [ @@ -137,6 +144,13 @@ const initialEdges: Edge[] = [ type: 'custom2', data: { text: 'custom edge 2' }, }, + { + id: 'e3a-10', + source: '3a', + target: '10', + type: 'custom3', + data: { text: 'custom edge 3' }, + }, { id: 'e5-6', source: '5', @@ -173,6 +187,7 @@ const initialEdges: Edge[] = [ const edgeTypes: EdgeTypes = { custom: CustomEdge, custom2: CustomEdge2, + custom3: CustomEdge3, }; const defaultEdgeOptions = { diff --git a/packages/react/src/components/Edges/BaseEdge.tsx b/packages/react/src/components/Edges/BaseEdge.tsx index fab06220..3d951796 100644 --- a/packages/react/src/components/Edges/BaseEdge.tsx +++ b/packages/react/src/components/Edges/BaseEdge.tsx @@ -5,7 +5,6 @@ import { EdgeText } from './EdgeText'; import type { BaseEdgeProps } from '../../types'; export function BaseEdge({ - id, path, labelX, labelY, @@ -15,23 +14,12 @@ export function BaseEdge({ labelBgStyle, labelBgPadding, labelBgBorderRadius, - style, - markerEnd, - markerStart, - className, interactionWidth = 20, + ...props }: BaseEdgeProps) { return ( <> - + {interactionWidth && ( ({ nodeRef, }); } else if (isDraggable && node.selected && Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) { + // prevent default scrolling behavior on arrow key press when node is moved + event.preventDefault(); + store.setState({ ariaLiveMessage: `Moved selected node ${event.key .replace('Arrow', '') diff --git a/packages/react/src/components/NodesSelection/index.tsx b/packages/react/src/components/NodesSelection/index.tsx index e2bcd238..ecaef17b 100644 --- a/packages/react/src/components/NodesSelection/index.tsx +++ b/packages/react/src/components/NodesSelection/index.tsx @@ -68,6 +68,8 @@ export function NodesSelection({ const onKeyDown = (event: KeyboardEvent) => { if (Object.prototype.hasOwnProperty.call(arrowKeyDiffs, event.key)) { + event.preventDefault(); + moveSelectedNodes({ direction: arrowKeyDiffs[event.key], factor: event.shiftKey ? 4 : 1, diff --git a/packages/react/src/components/Panel/index.tsx b/packages/react/src/components/Panel/index.tsx index eeb01a15..83b4a87b 100644 --- a/packages/react/src/components/Panel/index.tsx +++ b/packages/react/src/components/Panel/index.tsx @@ -1,4 +1,4 @@ -import type { HTMLAttributes, ReactNode } from 'react'; +import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'; import cc from 'classcat'; import type { PanelPosition } from '@xyflow/system'; @@ -15,17 +15,20 @@ export type PanelProps = HTMLAttributes & { const selector = (s: ReactFlowState) => (s.userSelectionActive ? 'none' : 'all'); -export function Panel({ position = 'top-left', children, className, style, ...rest }: PanelProps) { - const pointerEvents = useStore(selector); - const positionClasses = `${position}`.split('-'); +export const Panel = forwardRef( + ({ position = 'top-left', children, className, style, ...rest }, ref) => { + const pointerEvents = useStore(selector); + const positionClasses = `${position}`.split('-'); - return ( -
- {children} -
- ); -} + return ( +
+ {children} +
+ ); + } +); diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index 962b6846..82206fc8 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -153,11 +153,11 @@ function ReactFlow( return (
true */ - isValidConnection?: IsValidConnection; + isValidConnection?: IsValidConnection; /** With a threshold greater than zero you can control the distinction between node drag and click events. * * If threshold equals 1, you need to drag the node 1 pixel before a drag event is fired. diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 8b0cebab..ce21e84f 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { CSSProperties, HTMLAttributes, ReactNode, MouseEvent as ReactMouseEvent, ComponentType } from 'react'; +import type { CSSProperties, SVGAttributes, ReactNode, MouseEvent as ReactMouseEvent, ComponentType } from 'react'; import type { EdgeBase, BezierPathOptions, @@ -14,7 +14,6 @@ import type { EdgePosition, StepPathOptions, OnError, - ConnectionState, FinalConnectionState, } from '@xyflow/system'; @@ -94,7 +93,7 @@ export type EdgeWrapperProps = { export type DefaultEdgeOptions = DefaultEdgeOptionsBase; -export type EdgeTextProps = HTMLAttributes & +export type EdgeTextProps = SVGAttributes & EdgeLabelOptions & { x: number; y: number; @@ -123,28 +122,17 @@ export type EdgeProps = Pick< * BaseEdge component props * @public */ -export type BaseEdgeProps = EdgeLabelOptions & { - /** Unique id of edge */ - id?: string; - /** Additional padding where interacting with an edge is still possible */ - interactionWidth?: number; - className?: string; - /** The x position of edge label */ - labelX?: number; - /** The y position of edge label */ - labelY?: number; - /** Marker at start of edge - * @example 'url(#arrow)' - */ - markerStart?: string; - /** Marker at end of edge - * @example 'url(#arrow)' - */ - markerEnd?: string; - /** SVG path of the edge */ - path: string; - style?: CSSProperties; -}; +export type BaseEdgeProps = Omit, 'd'> & + EdgeLabelOptions & { + /** Additional padding where interacting with an edge is still possible */ + interactionWidth?: number; + /** The x position of edge label */ + labelX?: number; + /** The y position of edge label */ + labelY?: number; + /** SVG path of the edge */ + path: string; + }; /** * Helper type for edge components that get exported by the library diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index f4ad2820..0d3924e4 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -56,6 +56,8 @@ export type NodeWrapperProps = { nodeClickDistance?: number; }; -export type BuiltInNode = Node<{ label: string }, 'input' | 'output' | 'default'>; +export type BuiltInNode = + | Node<{ label: string }, 'input' | 'output' | 'default'> + | Node, 'group'>; export type NodeProps = NodePropsBase; diff --git a/packages/svelte/src/lib/types/nodes.ts b/packages/svelte/src/lib/types/nodes.ts index 373b9b34..9cbfc169 100644 --- a/packages/svelte/src/lib/types/nodes.ts +++ b/packages/svelte/src/lib/types/nodes.ts @@ -42,7 +42,9 @@ export type NodeTypes = Record< export type DefaultNodeOptions = Partial>; -export type BuiltInNode = Node<{ label: string }, 'input' | 'output' | 'default'>; +export type BuiltInNode = + | Node<{ label: string }, 'input' | 'output' | 'default'> + | Node, 'group'>; // TODO SVELTE5 remove this export type NodeEventMap = {