diff --git a/.changeset/empty-swans-exercise.md b/.changeset/empty-swans-exercise.md new file mode 100644 index 00000000..8769d594 --- /dev/null +++ b/.changeset/empty-swans-exercise.md @@ -0,0 +1,5 @@ +--- +"@xyflow/react": patch +--- + +fix: improve TSDoc comments for `BackgroundProps` diff --git a/.changeset/famous-impalas-relate.md b/.changeset/famous-impalas-relate.md new file mode 100644 index 00000000..318bcc30 --- /dev/null +++ b/.changeset/famous-impalas-relate.md @@ -0,0 +1,6 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Prevent onPaneClick when connection is in progress. Closes [#5057](https://github.com/xyflow/xyflow/issues/5057) diff --git a/.changeset/forty-phones-lick.md b/.changeset/forty-phones-lick.md new file mode 100644 index 00000000..86132fa3 --- /dev/null +++ b/.changeset/forty-phones-lick.md @@ -0,0 +1,5 @@ +--- +"@xyflow/react": patch +--- + +fix: improve TSDoc comments for `EdgeLabelOptions` and `BaseEdgeProps` diff --git a/.changeset/polite-drinks-listen.md b/.changeset/polite-drinks-listen.md new file mode 100644 index 00000000..34be961a --- /dev/null +++ b/.changeset/polite-drinks-listen.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Add TSDoc annotations for exported edges diff --git a/.changeset/twenty-sloths-reflect.md b/.changeset/twenty-sloths-reflect.md new file mode 100644 index 00000000..fda503ae --- /dev/null +++ b/.changeset/twenty-sloths-reflect.md @@ -0,0 +1,6 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Hidden nodes are not displayed in the mini map anymore diff --git a/.changeset/unlucky-lobsters-refuse.md b/.changeset/unlucky-lobsters-refuse.md new file mode 100644 index 00000000..e98d4c08 --- /dev/null +++ b/.changeset/unlucky-lobsters-refuse.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Release key even when an inout field is focused diff --git a/.changeset/witty-toys-wash.md b/.changeset/witty-toys-wash.md new file mode 100644 index 00000000..4f2972f6 --- /dev/null +++ b/.changeset/witty-toys-wash.md @@ -0,0 +1,5 @@ +--- +'@xyflow/system': patch +--- + +Add center-left and center-right as a panel position diff --git a/packages/react/src/additional-components/Background/types.ts b/packages/react/src/additional-components/Background/types.ts index 50219e2d..d805a91c 100644 --- a/packages/react/src/additional-components/Background/types.ts +++ b/packages/react/src/additional-components/Background/types.ts @@ -16,6 +16,7 @@ export enum BackgroundVariant { * @expand */ export type BackgroundProps = { + /** When multiple backgrounds are present on the page, each one should have a unique id. */ id?: string; /** Color of the pattern */ color?: string; @@ -25,16 +26,31 @@ export type BackgroundProps = { className?: string; /** Class applied to the pattern */ patternClassName?: string; - /** Gap between repetitions of the pattern */ + /** + * The gap between patterns. Passing in a tuple allows you to control the x and y gap + * independently. + * @default '28' + */ gap?: number | [number, number]; - /** Size of a single pattern element */ + /** + * The radius of each dot or the size of each rectangle if `BackgroundVariant.Dots` or + * `BackgroundVariant.Cross` is used. This defaults to 1 or 6 respectively, or ignored if + * `BackgroundVariant.Lines` is used. + */ size?: number; - /** Offset of the pattern */ + /** + * Offset of the pattern + * @default 2 + */ offset?: number | [number, number]; - /** Line width of the Line pattern */ + /** + * The stroke thickness used when drawing the pattern. + * @default 1 + */ lineWidth?: number; /** * Variant of the pattern + * @default BackgroundVariant.Dots * @example BackgroundVariant.Lines, BackgroundVariant.Dots, BackgroundVariant.Cross * 'lines', 'dots', 'cross' */ diff --git a/packages/react/src/additional-components/MiniMap/MiniMap.tsx b/packages/react/src/additional-components/MiniMap/MiniMap.tsx index c7f1fdce..ad899043 100644 --- a/packages/react/src/additional-components/MiniMap/MiniMap.tsx +++ b/packages/react/src/additional-components/MiniMap/MiniMap.tsx @@ -15,6 +15,8 @@ import type { MiniMapProps } from './types'; const defaultWidth = 200; const defaultHeight = 150; +const filterHidden = (node: Node) => !node.hidden; + const selector = (s: ReactFlowState) => { const viewBB: Rect = { x: -s.transform[0] / s.transform[2], @@ -25,7 +27,10 @@ const selector = (s: ReactFlowState) => { return { viewBB, - boundingRect: s.nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup), viewBB) : viewBB, + boundingRect: + s.nodeLookup.size > 0 + ? getBoundsOfRects(getInternalNodesBounds(s.nodeLookup, { filter: filterHidden }), viewBB) + : viewBB, rfId: s.rfId, panZoom: s.panZoom, translateExtent: s.translateExtent, @@ -113,16 +118,16 @@ function MiniMapComponent({ const onSvgClick = onClick ? (event: MouseEvent) => { - const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; - onClick(event, { x, y }); - } + const [x, y] = minimapInstance.current?.pointer(event) || [0, 0]; + onClick(event, { x, y }); + } : undefined; const onSvgNodeClick = onNodeClick ? useCallback((event: MouseEvent, nodeId: string) => { - const node = store.getState().nodeLookup.get(nodeId)!; - onNodeClick(event, node); - }, []) + const node = store.getState().nodeLookup.get(nodeId)!; + onNodeClick(event, node); + }, []) : undefined; return ( diff --git a/packages/react/src/components/EdgeLabelRenderer/index.tsx b/packages/react/src/components/EdgeLabelRenderer/index.tsx index 647811cb..2b603dc3 100644 --- a/packages/react/src/components/EdgeLabelRenderer/index.tsx +++ b/packages/react/src/components/EdgeLabelRenderer/index.tsx @@ -10,37 +10,38 @@ const selector = (s: ReactFlowState) => s.domNode?.querySelector('.react-flow__e * Edges are SVG-based. If you want to render more complex labels you can use the * `` component to access a div based renderer. This component * is a portal that renders the label in a `
` that is positioned on top of - * the edges. You can see an example usage of the component in the [edge label renderer](/examples/edges/edge-label-renderer) example. + * the edges. You can see an example usage of the component in the + * [edge label renderer example](/examples/edges/edge-label-renderer). * @public * * @example - *```jsx - *import React from 'react'; - *import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react'; + * ```jsx + * import React from 'react'; + * import { getBezierPath, EdgeLabelRenderer, BaseEdge } from '@xyflow/react'; * - *export function CustomEdge({ id, data, ...props }) { - * const [edgePath, labelX, labelY] = getBezierPath(props); + * export function CustomEdge({ id, data, ...props }) { + * const [edgePath, labelX, labelY] = getBezierPath(props); * - * return ( - * <> - * - * - *
- * {data.label} - *
- *
- * - * ); - *}; - *``` + * return ( + * <> + * + * + *
+ * {data.label} + *
+ *
+ * + * ); + * }; + * ``` * * @remarks The `` has no pointer events by default. If you want to * add mouse interactions you need to set the style `pointerEvents: all` and add diff --git a/packages/react/src/components/Edges/BezierEdge.tsx b/packages/react/src/components/Edges/BezierEdge.tsx index 482f1fd8..3e84a39b 100644 --- a/packages/react/src/components/Edges/BezierEdge.tsx +++ b/packages/react/src/components/Edges/BezierEdge.tsx @@ -61,7 +61,34 @@ function createBezierEdge(params: { isInternal: boolean }) { ); } +/** + * Component that can be used inside a custom edge to render a bezier curve. + * + * @public + * @example + * + * ```tsx + * import { BezierEdge } from '@xyflow/react'; + * + * function CustomEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }) { + * return ( + * + * ); + * } + * ``` + */ const BezierEdge = createBezierEdge({ isInternal: false }); + +/** + * @internal + */ const BezierEdgeInternal = createBezierEdge({ isInternal: true }); BezierEdge.displayName = 'BezierEdge'; diff --git a/packages/react/src/components/Edges/EdgeAnchor.tsx b/packages/react/src/components/Edges/EdgeAnchor.tsx index a975eaf5..9ee5f600 100644 --- a/packages/react/src/components/Edges/EdgeAnchor.tsx +++ b/packages/react/src/components/Edges/EdgeAnchor.tsx @@ -27,6 +27,9 @@ export interface EdgeAnchorProps extends SVGAttributes { const EdgeUpdaterClassName = 'react-flow__edgeupdater'; +/** + * @internal + */ export function EdgeAnchor({ position, centerX, diff --git a/packages/react/src/components/Edges/EdgeText.tsx b/packages/react/src/components/Edges/EdgeText.tsx index 2c79f718..9807302a 100644 --- a/packages/react/src/components/Edges/EdgeText.tsx +++ b/packages/react/src/components/Edges/EdgeText.tsx @@ -77,26 +77,26 @@ EdgeTextComponent.displayName = 'EdgeText'; * You can use the `` component as a helper component to display text * within your custom edges. * - *@public + * @public * - *@example - *```jsx - *import { EdgeText } from '@xyflow/react'; + * @example + * ```jsx + * import { EdgeText } from '@xyflow/react'; * - *export function CustomEdgeLabel({ label }) { - * return ( - * - * ); - *} + * export function CustomEdgeLabel({ label }) { + * return ( + * + * ); + * } *``` */ export const EdgeText = memo(EdgeTextComponent); diff --git a/packages/react/src/components/Edges/SmoothStepEdge.tsx b/packages/react/src/components/Edges/SmoothStepEdge.tsx index 1f6bcfd1..8b1475cf 100644 --- a/packages/react/src/components/Edges/SmoothStepEdge.tsx +++ b/packages/react/src/components/Edges/SmoothStepEdge.tsx @@ -62,7 +62,34 @@ function createSmoothStepEdge(params: { isInternal: boolean }) { ); } +/** + * Component that can be used inside a custom edge to render a smooth step edge. + * + * @public + * @example + * + * ```tsx + * import { SmoothStepEdge } from '@xyflow/react'; + * + * function CustomEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }) { + * return ( + * + * ); + * } + * ``` + */ const SmoothStepEdge = createSmoothStepEdge({ isInternal: false }); + +/** + * @internal + */ const SmoothStepEdgeInternal = createSmoothStepEdge({ isInternal: true }); SmoothStepEdge.displayName = 'SmoothStepEdge'; diff --git a/packages/react/src/components/Edges/StepEdge.tsx b/packages/react/src/components/Edges/StepEdge.tsx index 6385e8f0..cd08758c 100644 --- a/packages/react/src/components/Edges/StepEdge.tsx +++ b/packages/react/src/components/Edges/StepEdge.tsx @@ -21,7 +21,34 @@ function createStepEdge(params: { isInternal: boolean }) { }); } +/** + * Component that can be used inside a custom edge to render a step edge. + * + * @public + * @example + * + * ```tsx + * import { StepEdge } from '@xyflow/react'; + * + * function CustomEdge({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }) { + * return ( + * + * ); + * } + * ``` + */ const StepEdge = createStepEdge({ isInternal: false }); + +/** + * @internal + */ const StepEdgeInternal = createStepEdge({ isInternal: true }); StepEdge.displayName = 'StepEdge'; diff --git a/packages/react/src/components/Edges/StraightEdge.tsx b/packages/react/src/components/Edges/StraightEdge.tsx index 53d1ac29..4f4cd76b 100644 --- a/packages/react/src/components/Edges/StraightEdge.tsx +++ b/packages/react/src/components/Edges/StraightEdge.tsx @@ -50,7 +50,32 @@ function createStraightEdge(params: { isInternal: boolean }) { ); } +/** + * Component that can be used inside a custom edge to render a straight line. + * + * @public + * @example + * + * ```tsx + * import { StraightEdge } from '@xyflow/react'; + * + * function CustomEdge({ sourceX, sourceY, targetX, targetY }) { + * return ( + * + * ); + * } + * ``` + */ const StraightEdge = createStraightEdge({ isInternal: false }); + +/** + * @internal + */ const StraightEdgeInternal = createStraightEdge({ isInternal: true }); StraightEdge.displayName = 'StraightEdge'; diff --git a/packages/react/src/container/Pane/index.tsx b/packages/react/src/container/Pane/index.tsx index 91459539..4d2e392a 100644 --- a/packages/react/src/container/Pane/index.tsx +++ b/packages/react/src/container/Pane/index.tsx @@ -61,6 +61,7 @@ const wrapHandler = ( const selector = (s: ReactFlowState) => ({ userSelectionActive: s.userSelectionActive, elementsSelectable: s.elementsSelectable, + connectionInProgress: s.connection.inProgress, dragging: s.paneDragging, }); @@ -81,7 +82,7 @@ export function Pane({ children, }: PaneProps) { const store = useStoreApi(); - const { userSelectionActive, elementsSelectable, dragging } = useStore(selector, shallow); + const { userSelectionActive, elementsSelectable, dragging, connectionInProgress } = useStore(selector, shallow); const hasActiveSelection = elementsSelectable && (isSelecting || userSelectionActive); const container = useRef(null); @@ -96,7 +97,8 @@ export function Pane({ const onClick = (event: ReactMouseEvent) => { // We prevent click events when the user let go of the selectionKey during a selection - if (selectionInProgress.current) { + // We also prevent click events when a connection is in progress + if (selectionInProgress.current || connectionInProgress) { selectionInProgress.current = false; return; } diff --git a/packages/react/src/hooks/useKeyPress.ts b/packages/react/src/hooks/useKeyPress.ts index 00cd012b..b2928baa 100644 --- a/packages/react/src/hooks/useKeyPress.ts +++ b/packages/react/src/hooks/useKeyPress.ts @@ -106,13 +106,6 @@ export function useKeyPress( }; const upHandler = (event: KeyboardEvent) => { - const preventAction = - (!modifierPressed.current || (modifierPressed.current && !options.actInsideInputWithModifier)) && - isInputDOMNode(event); - - if (preventAction) { - return false; - } const keyOrCode = useKeyOrCode(event.code, keysToWatch); if (isMatchingKey(keyCodes, pressedKeys.current, true)) { diff --git a/packages/react/src/types/edges.ts b/packages/react/src/types/edges.ts index 4d1e3f04..3b922796 100644 --- a/packages/react/src/types/edges.ts +++ b/packages/react/src/types/edges.ts @@ -23,7 +23,11 @@ import { EdgeTypes, InternalNode, Node } from '.'; * @inline */ export type EdgeLabelOptions = { - label?: string | ReactNode; + /** + * The label or custom element to render along the edge. This is commonly a text label or some + * custom controls. + */ + label?: ReactNode; labelStyle?: CSSProperties; labelShowBg?: boolean; labelBgStyle?: CSSProperties; @@ -136,13 +140,20 @@ export type EdgeProps = Pick< */ export type BaseEdgeProps = Omit, 'd'> & EdgeLabelOptions & { - /** Additional padding where interacting with an edge is still possible */ + /** + * The width of the invisible area around the edge that the user can interact with. This is + * useful for making the edge easier to click or hover over. + */ interactionWidth?: number; /** The x position of edge label */ labelX?: number; /** The y position of edge label */ labelY?: number; - /** SVG path of the edge */ + /** + * The SVG path string that defines the edge. This should look something like + * `'M 0 0 L 100 100'` for a simple line. The utility functions like `getSimpleBezierEdge` can + * be used to generate this string for you. + */ path: string; }; diff --git a/packages/svelte/src/lib/container/Pane/Pane.svelte b/packages/svelte/src/lib/container/Pane/Pane.svelte index aaa36560..c2200935 100644 --- a/packages/svelte/src/lib/container/Pane/Pane.svelte +++ b/packages/svelte/src/lib/container/Pane/Pane.svelte @@ -63,7 +63,8 @@ selectionKeyPressed, selectionMode, panActivationKeyPressed, - unselectNodesAndEdges + unselectNodesAndEdges, + connection, } = useStore(); let container: HTMLDivElement; @@ -80,7 +81,8 @@ function onClick(event: MouseEvent | TouchEvent) { // We prevent click events when the user let go of the selectionKey during a selection - if (selectionInProgress) { + // We also prevent click events when a connection is in progress + if (selectionInProgress || $connection.inProgress) { selectionInProgress = false; return; } diff --git a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte index 789dc476..fa8e7703 100644 --- a/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte +++ b/packages/svelte/src/lib/plugins/Minimap/Minimap.svelte @@ -76,7 +76,12 @@ $: { boundingRect = - $nodeLookup.size > 0 ? getBoundsOfRects(getInternalNodesBounds($nodeLookup), viewBB) : viewBB; + $nodeLookup.size > 0 + ? getBoundsOfRects( + getInternalNodesBounds($nodeLookup, { filter: (n) => !n.hidden }), + viewBB + ) + : viewBB; $nodes; } diff --git a/packages/system/src/styles/init.css b/packages/system/src/styles/init.css index e167c53f..b0651116 100644 --- a/packages/system/src/styles/init.css +++ b/packages/system/src/styles/init.css @@ -278,6 +278,14 @@ svg.xy-flow__connectionline { bottom: 0; } + &.top, + &.bottom { + &.center { + left: 50%; + transform: translateX(-50%); + } + } + &.left { left: 0; } @@ -286,9 +294,12 @@ svg.xy-flow__connectionline { right: 0; } - &.center { - left: 50%; - transform: translateX(-50%); + &.left, + &.right { + &.center { + top: 50%; + transform: translateY(-50%); + } } } diff --git a/packages/system/src/types/general.ts b/packages/system/src/types/general.ts index 8ac3f9e9..fc92bc7e 100644 --- a/packages/system/src/types/general.ts +++ b/packages/system/src/types/general.ts @@ -169,7 +169,15 @@ export type UpdateNodeInternals = (nodeId: string | string[]) => void; * * @public */ -export type PanelPosition = 'top-left' | 'top-center' | 'top-right' | 'bottom-left' | 'bottom-center' | 'bottom-right'; +export type PanelPosition = + | 'top-left' + | 'top-center' + | 'top-right' + | 'bottom-left' + | 'bottom-center' + | 'bottom-right' + | 'center-left' + | 'center-right'; export type ProOptions = { account?: string;