diff --git a/.changeset/brown-shrimps-exist.md b/.changeset/brown-shrimps-exist.md new file mode 100644 index 0000000000..269cd85126 --- /dev/null +++ b/.changeset/brown-shrimps-exist.md @@ -0,0 +1,5 @@ +--- +'@xyflow/react': patch +--- + +Add `snapGrid` to `screenToFlowPosition` options diff --git a/.changeset/dull-cows-sneeze.md b/.changeset/dull-cows-sneeze.md new file mode 100644 index 0000000000..4ad1e4b186 --- /dev/null +++ b/.changeset/dull-cows-sneeze.md @@ -0,0 +1,6 @@ +--- +'@xyflow/react': patch +'@xyflow/svelte': patch +--- + +Pass options to useReactFlow/useSvelteFlow viewport helper functions correctly diff --git a/.changeset/fix-smoothstep-edge-rounding.md b/.changeset/fix-smoothstep-edge-rounding.md new file mode 100644 index 0000000000..d768cb1225 --- /dev/null +++ b/.changeset/fix-smoothstep-edge-rounding.md @@ -0,0 +1,5 @@ +--- +"@xyflow/system": patch +--- + +Fix missing border radius on `getSmoothStepPath` edge bends when using `stepPosition` diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000000..74930595dd --- /dev/null +++ b/.codespellrc @@ -0,0 +1,8 @@ +[codespell] +# Ref: https://github.com/codespell-project/codespell#using-a-config-file +skip = .git,.gitattributes,.gitignore,package-lock.json,*-lock.yaml,*.css,.codespellrc,.npm,.cache +check-hidden = true +# ned - person name "Ned Batchelder" in .coderabbit.yaml +# mistic - GitHub username @mistic in CHANGELOGs +# inout - legitimate "inout field" (input/output) term +ignore-words-list = ned,mistic,inout diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 0000000000..7c9eafb17b --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,23 @@ +# Codespell configuration is within .codespellrc +--- +name: Codespell + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Codespell + uses: codespell-project/actions-codespell@v2 diff --git a/.gitignore b/.gitignore index e567b884fd..7683bd05a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .cache +.npm node_modules examples/build cypress/videos diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ed47126ac2..1116f71462 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -35,4 +35,4 @@ Additionally, we ask that all community members uphold the standards laid out in ## 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). +This Code of Conduct is a direct descendant of the [Gleam Code of Conduct](https://github.com/gleam-lang/gleam/blob/f793b5d28a3102276a8b861c7e16a19c5231426e/CODE_OF_CONDUCT.md), which is itself a descendant of the [Contributor Covenant (v1.4)](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). diff --git a/examples/README.md b/examples/README.md index df8f167055..a5dcc09e0f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -10,4 +10,4 @@ Astro App used for SSR testing at [`examples/astro-xyflow`](./astro-xyflow) ## E2E -For furhter documentation of E2E tests have a look at [`/tests/playwright`](/tests/playwright). \ No newline at end of file +For further documentation of E2E tests have a look at [`/tests/playwright`](/tests/playwright). \ No newline at end of file diff --git a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte index 797f988459..bc82a2a411 100644 --- a/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte +++ b/examples/svelte/src/routes/examples/add-node-on-drop/Flow.svelte @@ -83,7 +83,7 @@ fitView fitViewOptions={{ padding: 2 }} onconnectstart={(_, { nodeId }) => { - // Memorize the nodeId you start draggin a connection line from a node + // Memorize the nodeId you start dragging a connection line from a node connectingNodeId = nodeId; }} onconnectend={handleConnectEnd} diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 7d6a2dfd18..96a4601715 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -752,7 +752,7 @@ There is more! Besides the new main features, we added some minor things that we ## 12.0.0-next.22 -- ⚠️ rename `updateEdge` to `reconnectEdge` and realted APIs [#4373](https://github.com/xyflow/xyflow/pull/4373) +- ⚠️ rename `updateEdge` to `reconnectEdge` and related APIs [#4373](https://github.com/xyflow/xyflow/pull/4373) - revise selection usability (capture while dragging out of the flow) - use correct end handle position when drawing a connection lines - determine correct end positions for connection lines @@ -1021,7 +1021,7 @@ There is more! Besides the new main features, we added some minor things that we - **`useConnection` hook:** This hook makes it possible to handle an ongoing connection. For example, you can use it for colorizing handles. - **`onDelete` handler**: We added a combined handler for `onDeleteNodes` and `onDeleteEdges` to make it easier to react to deletions. -- **`isValidConnection` prop:** This makes it possible to implement one validation function for all connections. It also gets called for programatically added edges. +- **`isValidConnection` prop:** This makes it possible to implement one validation function for all connections. It also gets called for programmatically added edges. - **Controlled `viewport`:** This is definitely an advanced feature. Possible use cases are to animate the viewport or round the transform for lower res screens for example. This features brings two new props: `viewport` and `onViewportChange`. - **`ViewportPortal` component:** This makes it possible to render elements in the viewport without the need to implement a custom node. - **Background component**: add `patternClassName` to be able to style the background pattern by using a class name. This is useful if you want to style the background pattern with Tailwind for example. diff --git a/packages/react/src/hooks/useHandleConnections.ts b/packages/react/src/hooks/useHandleConnections.ts index a53eb2c7c8..390d723fc7 100644 --- a/packages/react/src/hooks/useHandleConnections.ts +++ b/packages/react/src/hooks/useHandleConnections.ts @@ -52,7 +52,7 @@ export function useHandleConnections({ ); useEffect(() => { - // @todo dicuss if onConnect/onDisconnect should be called when the component mounts/unmounts + // @todo discuss if onConnect/onDisconnect should be called when the component mounts/unmounts if (prevConnections.current && prevConnections.current !== connections) { const _connections = connections ?? new Map(); handleConnectionChange(prevConnections.current, _connections, onDisconnect); diff --git a/packages/react/src/hooks/useViewportHelper.ts b/packages/react/src/hooks/useViewportHelper.ts index eee9748482..d419b649fa 100644 --- a/packages/react/src/hooks/useViewportHelper.ts +++ b/packages/react/src/hooks/useViewportHelper.ts @@ -24,17 +24,17 @@ const useViewportHelper = (): ViewportHelperFunctions => { zoomIn: (options) => { const { panZoom } = store.getState(); - return panZoom ? panZoom.scaleBy(1.2, { duration: options?.duration }) : Promise.resolve(false); + return panZoom ? panZoom.scaleBy(1.2, options) : Promise.resolve(false); }, zoomOut: (options) => { const { panZoom } = store.getState(); - return panZoom ? panZoom.scaleBy(1 / 1.2, { duration: options?.duration }) : Promise.resolve(false); + return panZoom ? panZoom.scaleBy(1 / 1.2, options) : Promise.resolve(false); }, zoomTo: (zoomLevel, options) => { const { panZoom } = store.getState(); - return panZoom ? panZoom.scaleTo(zoomLevel, { duration: options?.duration }) : Promise.resolve(false); + return panZoom ? panZoom.scaleTo(zoomLevel, options) : Promise.resolve(false); }, getZoom: () => store.getState().transform[2], setViewport: async (viewport, options) => { diff --git a/packages/react/src/store/index.ts b/packages/react/src/store/index.ts index 816059f008..5ca9bac2e3 100644 --- a/packages/react/src/store/index.ts +++ b/packages/react/src/store/index.ts @@ -142,7 +142,7 @@ const createStore = ({ } }, /* - * Every node gets registerd at a ResizeObserver. Whenever a node + * Every node gets registered at a ResizeObserver. Whenever a node * changes its dimensions, this function is called to measure the * new dimensions and update the nodes. */ diff --git a/packages/react/src/types/general.ts b/packages/react/src/types/general.ts index d13d11be48..000f8caa80 100644 --- a/packages/react/src/types/general.ts +++ b/packages/react/src/types/general.ts @@ -14,6 +14,7 @@ import { Connection, NodeChange, EdgeChange, + SnapGrid, } from '@xyflow/system'; import type { Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.'; @@ -203,7 +204,10 @@ export type ViewportHelperFunctions = { * @example * const flowPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY }) */ - screenToFlowPosition: (clientPosition: XYPosition, options?: { snapToGrid: boolean }) => XYPosition; + screenToFlowPosition: ( + clientPosition: XYPosition, + options?: { snapToGrid?: boolean; snapGrid?: SnapGrid } + ) => XYPosition; /** * Translate a position inside the flow's canvas to a screen pixel position. * diff --git a/packages/react/src/types/nodes.ts b/packages/react/src/types/nodes.ts index dbd321e247..4c85a54b76 100644 --- a/packages/react/src/types/nodes.ts +++ b/packages/react/src/types/nodes.ts @@ -83,7 +83,7 @@ export type NodeWrapperProps = { /** * The `BuiltInNode` type represents the built-in node types that are available in React Flow. - * You can use this type to extend your custom node type if you still want ot use the built-in ones. + * You can use this type to extend your custom node type if you still want to use the built-in ones. * * @public * @example diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index f9f3e6866a..0df39d06ed 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -790,7 +790,7 @@ Another huge update for Svelte Flow 🙏 Handling data flows will be way easier This is a huge update! We added a new `` component and a new `colorMode` ('light' | 'dark' | 'system') prop for toggling dark/light mode. -There are also some breaking changes again (sorry!) but we are very close to the final API for Svelte Flow 1.0.0. The biggest change is that we group node attriubutes (`width`, `height`, `positionAbsolute`) that are added by the library under `node.computed`. This makes it easier to understand, that this stuff comes from the library itself. `node.width` and `node.height` is still an optional node option and can be used to set certain dimensions for SSR or on the client. +There are also some breaking changes again (sorry!) but we are very close to the final API for Svelte Flow 1.0.0. The biggest change is that we group node attributes (`width`, `height`, `positionAbsolute`) that are added by the library under `node.computed`. This makes it easier to understand, that this stuff comes from the library itself. `node.width` and `node.height` is still an optional node option and can be used to set certain dimensions for SSR or on the client. - add `` component - add `on:selectionclick` and `on:selectioncontextmenu` event handlers diff --git a/packages/svelte/src/lib/container/SvelteFlow/types.ts b/packages/svelte/src/lib/container/SvelteFlow/types.ts index 8e5c6c8c52..1e54e2f26a 100644 --- a/packages/svelte/src/lib/container/SvelteFlow/types.ts +++ b/packages/svelte/src/lib/container/SvelteFlow/types.ts @@ -209,7 +209,7 @@ export type SvelteFlowProps< * @default 'strict' */ connectionMode?: ConnectionMode; - /** Provide a custom snippet to be used insted of the default connection line */ + /** Provide a custom snippet to be used instead of the default connection line */ connectionLineComponent?: Component; /** Styles to be applied to the connection line */ connectionLineStyle?: string; @@ -325,7 +325,7 @@ export type SvelteFlowProps< */ panOnScrollMode?: PanOnScrollMode; /** - * Enableing this prop allows users to pan the viewport by clicking and dragging. + * Enabling this prop allows users to pan the viewport by clicking and dragging. * You can also set this prop to an array of numbers to limit which mouse buttons can activate panning. * @default true * @example [0, 2] // allows panning with the left and right mouse buttons @@ -447,7 +447,7 @@ export type SvelteFlowProps< /** This event handler is called when the user stops panning or zooming the viewport */ onmoveend?: OnMoveEnd; /** - * Ocassionally something may happen that causes Svelte Flow to throw an error. + * Occasionally something may happen that causes Svelte Flow to throw an error. * Instead of exploding your application, we log a message to the console and then call this event handler. * You might use it for additional logging or to show a message to the user. */ diff --git a/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts index 19307f1fb6..ddf13e4a5b 100644 --- a/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts +++ b/packages/svelte/src/lib/hooks/useSvelteFlow.svelte.ts @@ -342,9 +342,7 @@ export function useSvelteFlow (ids === undefined ? store.edges : getElements(store.edgeLookup, ids)), setZoom: (zoomLevel, options) => { const panZoom = store.panZoom; - return panZoom - ? panZoom.scaleTo(zoomLevel, { duration: options?.duration }) - : Promise.resolve(false); + return panZoom ? panZoom.scaleTo(zoomLevel, options) : Promise.resolve(false); }, getZoom: () => store.viewport.zoom, setViewport: async (nextViewport, options) => { diff --git a/packages/svelte/src/lib/store/initial-store.svelte.ts b/packages/svelte/src/lib/store/initial-store.svelte.ts index fb7dfc9994..515d1dabc5 100644 --- a/packages/svelte/src/lib/store/initial-store.svelte.ts +++ b/packages/svelte/src/lib/store/initial-store.svelte.ts @@ -474,7 +474,7 @@ export function getInitialStore 0) { diff --git a/packages/system/CHANGELOG.md b/packages/system/CHANGELOG.md index 946177c651..a1172df4f4 100644 --- a/packages/system/CHANGELOG.md +++ b/packages/system/CHANGELOG.md @@ -356,7 +356,7 @@ - [#4464](https://github.com/xyflow/xyflow/pull/4464) [`89cd677b`](https://github.com/xyflow/xyflow/commit/89cd677b5668b78434e02e7b025c6ac58db91e58) Thanks [@moklick](https://github.com/moklick)! - fix(handles): reconnect for connectionMode=loose -- [#4467](https://github.com/xyflow/xyflow/pull/4467) [`c253c7c5`](https://github.com/xyflow/xyflow/commit/c253c7c59a2ccd2cb91ad44ce4acbe481d9d7fe1) Thanks [@moklick](https://github.com/moklick)! - chore(subflows): log warning instead of throwing an error when parent node cant be found +- [#4467](https://github.com/xyflow/xyflow/pull/4467) [`c253c7c5`](https://github.com/xyflow/xyflow/commit/c253c7c59a2ccd2cb91ad44ce4acbe481d9d7fe1) Thanks [@moklick](https://github.com/moklick)! - chore(subflows): log warning instead of throwing an error when parent node can't be found ## 0.0.36 diff --git a/packages/system/src/utils/edges/smoothstep-edge.ts b/packages/system/src/utils/edges/smoothstep-edge.ts index 5ca5c48978..ceb33456e8 100644 --- a/packages/system/src/utils/edges/smoothstep-edge.ts +++ b/packages/system/src/utils/edges/smoothstep-edge.ts @@ -107,12 +107,12 @@ function getPoints({ if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { if (dirAccessor === 'x') { // Primary direction is horizontal, so stepPosition affects X coordinate - centerX = center.x ?? (sourceGapped.x + (targetGapped.x - sourceGapped.x) * stepPosition); + centerX = center.x ?? sourceGapped.x + (targetGapped.x - sourceGapped.x) * stepPosition; centerY = center.y ?? (sourceGapped.y + targetGapped.y) / 2; } else { - // Primary direction is vertical, so stepPosition affects Y coordinate + // Primary direction is vertical, so stepPosition affects Y coordinate centerX = center.x ?? (sourceGapped.x + targetGapped.x) / 2; - centerY = center.y ?? (sourceGapped.y + (targetGapped.y - sourceGapped.y) * stepPosition); + centerY = center.y ?? sourceGapped.y + (targetGapped.y - sourceGapped.y) * stepPosition; } /* @@ -194,11 +194,17 @@ function getPoints({ } } + const gappedSource = { x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y }; + const gappedTarget = { x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y }; + const pathPoints = [ source, - { x: sourceGapped.x + sourceGapOffset.x, y: sourceGapped.y + sourceGapOffset.y }, + // we only want to add the gapped source/target if they are different from the first/last point to avoid duplicates which can cause issues with the bends + ...(gappedSource.x !== points[0].x || gappedSource.y !== points[0].y ? [gappedSource] : []), ...points, - { x: targetGapped.x + targetGapOffset.x, y: targetGapped.y + targetGapOffset.y }, + ...(gappedTarget.x !== points[points.length - 1].x || gappedTarget.y !== points[points.length - 1].y + ? [gappedTarget] + : []), target, ]; @@ -280,19 +286,13 @@ export function getSmoothStepPath({ stepPosition, }); - const path = points.reduce((res, p, i) => { - let segment = ''; + let path = `M${points[0].x} ${points[0].y}`; - if (i > 0 && i < points.length - 1) { - segment = getBend(points[i - 1], p, points[i + 1], borderRadius); - } else { - segment = `${i === 0 ? 'M' : 'L'}${p.x} ${p.y}`; - } - - res += segment; + for (let i = 1; i < points.length - 1; i++) { + path += getBend(points[i - 1], points[i], points[i + 1], borderRadius); + } - return res; - }, ''); + path += `L${points[points.length - 1].x} ${points[points.length - 1].y}`; return [path, labelX, labelY, offsetX, offsetY]; } diff --git a/packages/system/src/utils/general.ts b/packages/system/src/utils/general.ts index a57b25a237..3f92bb7184 100644 --- a/packages/system/src/utils/general.ts +++ b/packages/system/src/utils/general.ts @@ -360,7 +360,7 @@ export function nodeHasDimensions(node: No } /** - * Convert child position to aboslute position + * Convert child position to absolute position * * @internal * @param position diff --git a/packages/system/src/utils/store.ts b/packages/system/src/utils/store.ts index b499dfee3d..2bae20c85f 100644 --- a/packages/system/src/utils/store.ts +++ b/packages/system/src/utils/store.ts @@ -543,7 +543,7 @@ export async function panBy({ * @param connectionKey at which key the connection should be added * @param connectionLookup reference to the connection lookup * @param nodeId nodeId of the connection - * @param handleId handleId of the conneciton + * @param handleId handleId of the connection */ function addConnectionToLookup( type: 'source' | 'target', diff --git a/packages/system/src/xyhandle/XYHandle.ts b/packages/system/src/xyhandle/XYHandle.ts index b8edf7674f..f6d72dde08 100644 --- a/packages/system/src/xyhandle/XYHandle.ts +++ b/packages/system/src/xyhandle/XYHandle.ts @@ -245,7 +245,7 @@ function onPointerDown( doc.addEventListener('touchend', onPointerUp as EventListener); } -// checks if and returns connection in fom of an object { source: 123, target: 312 } +// checks if and returns connection in form of an object { source: 123, target: 312 } function isValidHandle( event: MouseEvent | TouchEvent, { diff --git a/packages/system/src/xypanzoom/eventhandler.ts b/packages/system/src/xypanzoom/eventhandler.ts index 38f90aa51b..0fde21a691 100644 --- a/packages/system/src/xypanzoom/eventhandler.ts +++ b/packages/system/src/xypanzoom/eventhandler.ts @@ -243,7 +243,7 @@ export function createPanZoomEndHandler({ () => { onPanZoomEnd?.(event.sourceEvent as MouseEvent | TouchEvent, viewport); }, - // we need a setTimeout for panOnScroll to supress multiple end events fired during scroll + // we need a setTimeout for panOnScroll to suppress multiple end events fired during scroll panOnScroll ? 150 : 0 ); } diff --git a/tests/playwright/README.md b/tests/playwright/README.md index 7b705968c8..5277454d1c 100644 --- a/tests/playwright/README.md +++ b/tests/playwright/README.md @@ -1,5 +1,5 @@ # End-to-End with Playwright -Here you can find our framework independant E2E tests written with [playwright](https://playwright.dev/). +Here you can find our framework independent E2E tests written with [playwright](https://playwright.dev/). ## Installation ```bash diff --git a/tests/playwright/e2e/edges.spec.ts b/tests/playwright/e2e/edges.spec.ts index 6e26c8ca3f..e81cdcbd03 100644 --- a/tests/playwright/e2e/edges.spec.ts +++ b/tests/playwright/e2e/edges.spec.ts @@ -7,7 +7,7 @@ test.describe('Edges', () => { // Go to the starting url before each test. await page.goto('/tests/generic/edges/general'); - // Timeout get's ignored and tests timeout after 200ms ??? + // Timeout gets ignored and tests timeout after 200ms ??? // page.waitForSelector('[data-id="edge-with-class"]', { timeout: 5000 }); });