diff --git a/README.md b/README.md index 722fa001..df474f4f 100644 --- a/README.md +++ b/README.md @@ -17,20 +17,20 @@ Powerful open source libraries for building node-based UIs with React or Svelte. ## The xyflow mono repo The xyflow repository is the home of four packages: -* React Flow v11 `reactflow` [v11 branch](https://github.com/xyflow/xyflow/tree/v11) -* React Flow v12 (not published yet) [packages/react](./packages/react) +* React Flow 11 `reactflow` [v11 branch](https://github.com/xyflow/xyflow/tree/v11) +* React Flow 12 (beta) `@xyflow/svelte` [packages/react](./packages/react) * Svelte Flow `@xyflow/svelte` [packages/svelte](./packages/svelte) * Shared helper library `@xyflow/system` [packages/system](./packages/system) **We just moved repositories** from the @wbkd org to this one. React Flow v11 will remain on the [v11 branch](https://github.com/xyflow/xyflow/tree/v11). When we have a stable v12, the package name of React Flow will change from `reactflow` to `@xyflow/react`. -## Commercial Usage +## Commercial usage **Are you using React Flow or Svelte Flow for a personal project?** Great! No sponsorship needed, you can support us by reporting any bugs you find, sending us screenshots of your projects, and starring us on Github 🌟 **Are you using React Flow or Svelte Flow at your organization and making money from it?** Awesome! We rely on your support to keep our libraries developed and maintained under an MIT License, just how we like it. For React Flow you can do that on the [React Flow Pro website](https://reactflow.dev/pro) and for both of our libraries you can do it through [Github Sponsors](https://github.com/sponsors/xyflow). -## Getting Started +## Getting started The best way to get started is to check out the [React Flow](https://reactflow.dev/learn) or [Svelte Flow](https://svelteflow.dev/learn) learn section. However if you want to get a sneak peek of how to install the and use the libraries you can see it here: diff --git a/examples/react/src/examples/Intersection/index.tsx b/examples/react/src/examples/Intersection/index.tsx index 006ddc77..2deb471e 100644 --- a/examples/react/src/examples/Intersection/index.tsx +++ b/examples/react/src/examples/Intersection/index.tsx @@ -17,28 +17,34 @@ const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', n const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const initialNodes: Node[] = [ + { + id: '0', + data: { label: 'rectangle' }, + position: { x: 0, y: 0 }, + width: 100, + height: 100, + draggable: false, + style: { + opacity: 0.5, + }, + }, { id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 0, y: 0 }, - className: 'light', - style: { - width: 200, - height: 100, - }, + width: 200, + height: 100, }, { id: '2', data: { label: 'Node 2' }, position: { x: 0, y: 150 }, - className: 'light', }, { id: '3', data: { label: 'Node 3' }, position: { x: 250, y: 0 }, - className: 'light', }, { id: '4', @@ -48,7 +54,6 @@ const initialNodes: Node[] = [ width: 50, height: 50, }, - className: 'light', }, ]; diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 0bc7f603..1124d817 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,13 @@ # @xyflow/react +## 12.0.0-next.12 + +## Patch changes + +- fix useNodesData: handle invalid node id thanks @saswatax +- fix forwardRef: use custom fixForwardRef function +- refactor intersection helpers to use passed node value + ## 12.0.0-next.11 ## Patch changes diff --git a/packages/react/package.json b/packages/react/package.json index 542a7071..3a47cbb2 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/react", - "version": "12.0.0-next.11", + "version": "12.0.0-next.12", "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/components/EdgeWrapper/EdgeUpdateAnchors.tsx b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx index c84c2d3b..6061d8a7 100644 --- a/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx +++ b/packages/react/src/components/EdgeWrapper/EdgeUpdateAnchors.tsx @@ -96,6 +96,7 @@ export function EdgeUpdateAnchors({ onEdgeUpdateEnd: _onEdgeUpdateEnd, updateConnection, getTransform: () => store.getState().transform, + getConnectionStartHandle: () => store.getState().connectionStartHandle, }); }; diff --git a/packages/react/src/components/Handle/index.tsx b/packages/react/src/components/Handle/index.tsx index f1f84cce..ee36d066 100644 --- a/packages/react/src/components/Handle/index.tsx +++ b/packages/react/src/components/Handle/index.tsx @@ -4,7 +4,6 @@ import { type TouchEvent as ReactTouchEvent, type ForwardedRef, memo, - forwardRef, } from 'react'; import cc from 'classcat'; import { shallow } from 'zustand/shallow'; @@ -24,6 +23,7 @@ import { import { useStore, useStoreApi } from '../../hooks/useStore'; import { useNodeId } from '../../contexts/NodeIdContext'; import { type ReactFlowState } from '../../types'; +import { fixedForwardRef } from '../../utils'; export interface HandleComponentProps extends HandleProps, Omit, 'id'> {} @@ -140,6 +140,7 @@ function HandleComponent( onConnect: onConnectExtended, isValidConnection: isValidConnection || currentStore.isValidConnection, getTransform: () => store.getState().transform, + getConnectionStartHandle: () => store.getState().connectionStartHandle, }); } @@ -242,4 +243,4 @@ function HandleComponent( /** * The Handle component is a UI element that is used to connect nodes. */ -export const Handle = memo(forwardRef(HandleComponent)); +export const Handle = memo(fixedForwardRef(HandleComponent)); diff --git a/packages/react/src/container/ReactFlow/index.tsx b/packages/react/src/container/ReactFlow/index.tsx index eb741684..eb6e3a1d 100644 --- a/packages/react/src/container/ReactFlow/index.tsx +++ b/packages/react/src/container/ReactFlow/index.tsx @@ -1,4 +1,4 @@ -import { ForwardedRef, forwardRef, type CSSProperties } from 'react'; +import { ForwardedRef, type CSSProperties } from 'react'; import cc from 'classcat'; import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system'; @@ -11,6 +11,7 @@ import { GraphView } from '../GraphView'; import { Wrapper } from './Wrapper'; import type { Edge, Node, ReactFlowProps } from '../../types'; import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values'; +import { fixedForwardRef } from '../../utils/general'; const wrapperStyle: CSSProperties = { width: '100%', @@ -286,4 +287,4 @@ function ReactFlow( ); } -export default forwardRef(ReactFlow); +export default fixedForwardRef(ReactFlow); diff --git a/packages/react/src/hooks/useReactFlow.ts b/packages/react/src/hooks/useReactFlow.ts index 5fd605fd..5c10ad6a 100644 --- a/packages/react/src/hooks/useReactFlow.ts +++ b/packages/react/src/hooks/useReactFlow.ts @@ -1,5 +1,12 @@ import { useCallback, useMemo, useRef, useState } from 'react'; -import { getElementsToRemove, getOverlappingArea, isRectObject, nodeToRect, type Rect } from '@xyflow/system'; +import { + getElementsToRemove, + getOverlappingArea, + isRectObject, + nodeHasDimensions, + nodeToRect, + type Rect, +} from '@xyflow/system'; import useViewportHelper from './useViewportHelper'; import { useStoreApi } from './useStore'; @@ -216,32 +223,26 @@ export function useReactFlow { - const isRect = isRectObject(nodeOrRect); - const node = isRect ? null : (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType); + const getNodeRect = useCallback((nodeOrRect: NodeType | { id: NodeType['id'] }): Rect | null => { + const node = + isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect) + ? nodeOrRect + : (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType); - if (!isRect && !node) { - return [null, null, isRect]; - } - - const nodeRect = isRect ? nodeOrRect : nodeToRect(node!); - - return [nodeRect, node, isRect]; - }, - [] - ); + return node ? nodeToRect(node) : null; + }, []); const getIntersectingNodes = useCallback>( (nodeOrRect, partially = true, nodes) => { - const [nodeRect, node, isRect] = getNodeRect(nodeOrRect); + const isRect = isRectObject(nodeOrRect); + const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect); if (!nodeRect) { return []; } return (nodes || store.getState().nodes).filter((n) => { - if (!isRect && (n.id === node!.id || !n.computed?.positionAbsolute)) { + if (!isRect && (n.id === nodeOrRect!.id || !n.computed?.positionAbsolute)) { return false; } @@ -257,7 +258,8 @@ export function useReactFlow>( (nodeOrRect, area, partially = true) => { - const [nodeRect] = getNodeRect(nodeOrRect); + const isRect = isRectObject(nodeOrRect); + const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect); if (!nodeRect) { return false; diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index 6f723237..302ffeee 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -16,14 +16,6 @@ import { import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.'; -// this is needed, to use generics + forwardRef -declare module 'react' { - // eslint-disable-next-line @typescript-eslint/ban-types - function forwardRef( - render: (props: P, ref: React.Ref) => React.ReactElement | null - ): (props: P & React.RefAttributes) => React.ReactElement | null; -} - export type OnNodesChange = (changes: NodeChange[]) => void; export type OnEdgesChange = (changes: EdgeChange[]) => void; diff --git a/packages/react/src/utils/general.ts b/packages/react/src/utils/general.ts index 2c2387fc..e497dd99 100644 --- a/packages/react/src/utils/general.ts +++ b/packages/react/src/utils/general.ts @@ -1,3 +1,4 @@ +import { ReactNode, Ref, RefAttributes, forwardRef } from 'react'; import { isNodeBase, isEdgeBase } from '@xyflow/system'; import type { Edge, Node } from '../types'; @@ -21,3 +22,11 @@ export const isNode = (element: unknown): element */ export const isEdge = (element: unknown): element is EdgeType => isEdgeBase(element); + +// eslint-disable-next-line @typescript-eslint/ban-types +export function fixedForwardRef( + render: (props: P, ref: Ref) => ReactNode +): (props: P & RefAttributes) => ReactNode { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return forwardRef(render) as any; +} diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index f784ff70..5ea7f58e 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # @xyflow/svelte +## 0.0.39 + +## ⚠️ Breaking changes + +- `on:nodedragstart`, `on:nodedrag` and `on:nodedragstop` are also getting fired if a selection bix is being dragged. We renamed the `node` param to `targetNode` and set it to `null` if a selection is being dragged. + +## Patch changes + +- export `EdgeLabel` component +- add "connectionindicator" class for `Handle` component +- address a11y warnings + ## 0.0.38 ## ⚠️ Breaking changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 02b23545..98f21569 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@xyflow/svelte", - "version": "0.0.38", + "version": "0.0.39", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.", "keywords": [ "svelte", @@ -43,33 +43,34 @@ "dependencies": { "@svelte-put/shortcut": "^3.1.0", "@xyflow/system": "workspace:*", - "classcat": "^5.0.4" + "classcat": "^5.0.4", + "svelte-preprocess": "^5.1.3" }, "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", - "@sveltejs/kit": "^2.5.2", - "@sveltejs/package": "^2.2.7", - "@typescript-eslint/eslint-plugin": "^5.60.0", - "@typescript-eslint/parser": "^5.60.0", - "autoprefixer": "^10.4.15", - "cssnano": "^6.0.1", - "dotenv": "^16.3.1", - "eslint": "^8.43.0", - "eslint-config-prettier": "^8.8.0", - "eslint-plugin-svelte": "^2.31.1", - "postcss": "^8.4.21", - "postcss-cli": "^10.1.0", + "@sveltejs/kit": "^2.5.4", + "@sveltejs/package": "^2.3.0", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "autoprefixer": "^10.4.18", + "cssnano": "^6.1.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.35.1", + "postcss": "^8.4.35", + "postcss-cli": "^11.0.0", "postcss-combine-duplicated-selectors": "^10.0.3", - "postcss-import": "^15.1.0", - "postcss-nested": "^6.0.0", + "postcss-import": "^16.0.1", + "postcss-nested": "^6.0.1", "postcss-rename": "^0.6.1", - "prettier": "^2.8.8", - "prettier-plugin-svelte": "^2.10.1", + "prettier": "^3.2.5", + "prettier-plugin-svelte": "^3.2.2", "svelte": "^4.2.12", - "svelte-check": "^3.6.6", - "svelte-eslint-parser": "^0.32.2", - "tslib": "^2.5.3", - "typescript": "5.1.3" + "svelte-check": "^3.6.7", + "svelte-eslint-parser": "^0.33.1", + "tslib": "^2.6.2", + "typescript": "5.4.2" }, "peerDependencies": { "svelte": "^3.0.0 || ^4.0.0" diff --git a/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte b/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte index bc12b604..9ab028c0 100644 --- a/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte +++ b/packages/svelte/src/lib/components/EdgeLabel/EdgeLabel.svelte @@ -20,6 +20,8 @@ style:transform="translate(-50%, -50%) translate({x}px,{y}px)" style={'pointer-events: all;' + style} role="button" + tabindex="-1" + on:keyup={() => {}} on:click={() => { if (id) handleEdgeSelect(id); }} diff --git a/packages/svelte/src/lib/components/Handle/Handle.svelte b/packages/svelte/src/lib/components/Handle/Handle.svelte index e35804fc..5217686c 100644 --- a/packages/svelte/src/lib/components/Handle/Handle.svelte +++ b/packages/svelte/src/lib/components/Handle/Handle.svelte @@ -8,7 +8,8 @@ isMouseEvent, type HandleConnection, areConnectionMapsEqual, - handleConnectionChange + handleConnectionChange, + ConnectionMode } from '@xyflow/system'; import { useStore } from '$lib/store'; @@ -23,6 +24,7 @@ export let isConnectable: $$Props['isConnectable'] = undefined; export let onconnect: $$Props['onconnect'] = undefined; export let ondisconnect: $$Props['ondisconnect'] = undefined; + // @todo implement connectablestart, connectableend // export let isConnectableStart: $$Props['isConnectableStart'] = undefined; // export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined; @@ -32,7 +34,7 @@ const isTarget = type === 'target'; const nodeId = getContext('svelteflow__node_id'); const connectable = getContext>('svelteflow__node_connectable'); - $: handleConnectable = isConnectable !== undefined ? isConnectable : $connectable; + $: isConnectable = isConnectable !== undefined ? isConnectable : $connectable; const handleId = id || null; @@ -99,7 +101,8 @@ onConnectEnd: (event) => { $onConnectEndAction?.(event); }, - getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom] + getTransform: () => [$viewport.x, $viewport.y, $viewport.zoom], + getConnectionStartHandle: () => $connection.startHandle }); } } @@ -124,6 +127,7 @@ prevConnections = connections ?? new Map(); } + $: connectionInProcess = !!$connection.startHandle; $: connectingFrom = $connection.startHandle?.nodeId === nodeId && $connection.startHandle?.type === type && @@ -132,9 +136,12 @@ $connection.endHandle?.nodeId === nodeId && $connection.endHandle?.type === type && $connection.endHandle?.handleId === handleId; + $: isPossibleEndHandle = + $connectionMode === ConnectionMode.Strict + ? $connection.startHandle?.type !== type + : nodeId !== $connection.startHandle?.nodeId || + handleId !== $connection.startHandle?.handleId; $: valid = connectingTo && $connection.status === 'valid'; - - // @todo implement connectablestart, connectableend