Merge pull request #4028 from xyflow/next

React Flow 12.0.0-next.12 & Svelte Flow 0.0.39
This commit is contained in:
Moritz Klack
2024-03-16 12:30:29 +01:00
committed by GitHub
27 changed files with 1119 additions and 591 deletions
+4 -4
View File
@@ -17,20 +17,20 @@ Powerful open source libraries for building node-based UIs with React or Svelte.
## The xyflow mono repo ## The xyflow mono repo
The xyflow repository is the home of four packages: The xyflow repository is the home of four packages:
* React Flow v11 `reactflow` [v11 branch](https://github.com/xyflow/xyflow/tree/v11) * React Flow 11 `reactflow` [v11 branch](https://github.com/xyflow/xyflow/tree/v11)
* React Flow v12 (not published yet) [packages/react](./packages/react) * React Flow 12 (beta) `@xyflow/svelte` [packages/react](./packages/react)
* Svelte Flow `@xyflow/svelte` [packages/svelte](./packages/svelte) * Svelte Flow `@xyflow/svelte` [packages/svelte](./packages/svelte)
* Shared helper library `@xyflow/system` [packages/system](./packages/system) * 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`. **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 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). **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: 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:
@@ -17,28 +17,34 @@ const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', n
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
const initialNodes: 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', id: '1',
type: 'input', type: 'input',
data: { label: 'Node 1' }, data: { label: 'Node 1' },
position: { x: 0, y: 0 }, position: { x: 0, y: 0 },
className: 'light', width: 200,
style: { height: 100,
width: 200,
height: 100,
},
}, },
{ {
id: '2', id: '2',
data: { label: 'Node 2' }, data: { label: 'Node 2' },
position: { x: 0, y: 150 }, position: { x: 0, y: 150 },
className: 'light',
}, },
{ {
id: '3', id: '3',
data: { label: 'Node 3' }, data: { label: 'Node 3' },
position: { x: 250, y: 0 }, position: { x: 250, y: 0 },
className: 'light',
}, },
{ {
id: '4', id: '4',
@@ -48,7 +54,6 @@ const initialNodes: Node[] = [
width: 50, width: 50,
height: 50, height: 50,
}, },
className: 'light',
}, },
]; ];
+8
View File
@@ -1,5 +1,13 @@
# @xyflow/react # @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 ## 12.0.0-next.11
## Patch changes ## Patch changes
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xyflow/react", "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.", "description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
"keywords": [ "keywords": [
"react", "react",
@@ -96,6 +96,7 @@ export function EdgeUpdateAnchors<EdgeType extends Edge = Edge>({
onEdgeUpdateEnd: _onEdgeUpdateEnd, onEdgeUpdateEnd: _onEdgeUpdateEnd,
updateConnection, updateConnection,
getTransform: () => store.getState().transform, getTransform: () => store.getState().transform,
getConnectionStartHandle: () => store.getState().connectionStartHandle,
}); });
}; };
@@ -4,7 +4,6 @@ import {
type TouchEvent as ReactTouchEvent, type TouchEvent as ReactTouchEvent,
type ForwardedRef, type ForwardedRef,
memo, memo,
forwardRef,
} from 'react'; } from 'react';
import cc from 'classcat'; import cc from 'classcat';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
@@ -24,6 +23,7 @@ import {
import { useStore, useStoreApi } from '../../hooks/useStore'; import { useStore, useStoreApi } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext'; import { useNodeId } from '../../contexts/NodeIdContext';
import { type ReactFlowState } from '../../types'; import { type ReactFlowState } from '../../types';
import { fixedForwardRef } from '../../utils';
export interface HandleComponentProps extends HandleProps, Omit<HTMLAttributes<HTMLDivElement>, 'id'> {} export interface HandleComponentProps extends HandleProps, Omit<HTMLAttributes<HTMLDivElement>, 'id'> {}
@@ -140,6 +140,7 @@ function HandleComponent(
onConnect: onConnectExtended, onConnect: onConnectExtended,
isValidConnection: isValidConnection || currentStore.isValidConnection, isValidConnection: isValidConnection || currentStore.isValidConnection,
getTransform: () => store.getState().transform, 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. * 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));
@@ -1,4 +1,4 @@
import { ForwardedRef, forwardRef, type CSSProperties } from 'react'; import { ForwardedRef, type CSSProperties } from 'react';
import cc from 'classcat'; import cc from 'classcat';
import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system'; import { ConnectionLineType, PanOnScrollMode, SelectionMode, infiniteExtent, isMacOs } from '@xyflow/system';
@@ -11,6 +11,7 @@ import { GraphView } from '../GraphView';
import { Wrapper } from './Wrapper'; import { Wrapper } from './Wrapper';
import type { Edge, Node, ReactFlowProps } from '../../types'; import type { Edge, Node, ReactFlowProps } from '../../types';
import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values'; import { defaultViewport as initViewport, defaultNodeOrigin } from './init-values';
import { fixedForwardRef } from '../../utils/general';
const wrapperStyle: CSSProperties = { const wrapperStyle: CSSProperties = {
width: '100%', width: '100%',
@@ -286,4 +287,4 @@ function ReactFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(
); );
} }
export default forwardRef(ReactFlow); export default fixedForwardRef(ReactFlow);
+20 -18
View File
@@ -1,5 +1,12 @@
import { useCallback, useMemo, useRef, useState } from 'react'; 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 useViewportHelper from './useViewportHelper';
import { useStoreApi } from './useStore'; import { useStoreApi } from './useStore';
@@ -216,32 +223,26 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
[] []
); );
const getNodeRect = useCallback( const getNodeRect = useCallback((nodeOrRect: NodeType | { id: NodeType['id'] }): Rect | null => {
(nodeOrRect: NodeType | { id: Node['id'] } | Rect): [Rect | null, NodeType | null | undefined, boolean] => { const node =
const isRect = isRectObject(nodeOrRect); isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect)
const node = isRect ? null : (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType); ? nodeOrRect
: (store.getState().nodeLookup.get(nodeOrRect.id) as NodeType);
if (!isRect && !node) { return node ? nodeToRect(node) : null;
return [null, null, isRect]; }, []);
}
const nodeRect = isRect ? nodeOrRect : nodeToRect(node!);
return [nodeRect, node, isRect];
},
[]
);
const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>( const getIntersectingNodes = useCallback<Instance.GetIntersectingNodes<NodeType>>(
(nodeOrRect, partially = true, nodes) => { (nodeOrRect, partially = true, nodes) => {
const [nodeRect, node, isRect] = getNodeRect(nodeOrRect); const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
if (!nodeRect) { if (!nodeRect) {
return []; return [];
} }
return (nodes || store.getState().nodes).filter((n) => { 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; return false;
} }
@@ -257,7 +258,8 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
const isNodeIntersecting = useCallback<Instance.IsNodeIntersecting<NodeType>>( const isNodeIntersecting = useCallback<Instance.IsNodeIntersecting<NodeType>>(
(nodeOrRect, area, partially = true) => { (nodeOrRect, area, partially = true) => {
const [nodeRect] = getNodeRect(nodeOrRect); const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
if (!nodeRect) { if (!nodeRect) {
return false; return false;
-8
View File
@@ -16,14 +16,6 @@ import {
import type { NodeChange, EdgeChange, Node, Edge, ReactFlowInstance, EdgeProps, NodeProps } from '.'; 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<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
): (props: P & React.RefAttributes<T>) => React.ReactElement | null;
}
export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void; export type OnNodesChange<NodeType extends Node = Node> = (changes: NodeChange<NodeType>[]) => void;
export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void; export type OnEdgesChange<EdgeType extends Edge = Edge> = (changes: EdgeChange<EdgeType>[]) => void;
+9
View File
@@ -1,3 +1,4 @@
import { ReactNode, Ref, RefAttributes, forwardRef } from 'react';
import { isNodeBase, isEdgeBase } from '@xyflow/system'; import { isNodeBase, isEdgeBase } from '@xyflow/system';
import type { Edge, Node } from '../types'; import type { Edge, Node } from '../types';
@@ -21,3 +22,11 @@ export const isNode = <NodeType extends Node = Node>(element: unknown): element
*/ */
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType => export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
isEdgeBase<EdgeType>(element); isEdgeBase<EdgeType>(element);
// eslint-disable-next-line @typescript-eslint/ban-types
export function fixedForwardRef<T, P = {}>(
render: (props: P, ref: Ref<T>) => ReactNode
): (props: P & RefAttributes<T>) => ReactNode {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return forwardRef(render) as any;
}
+12
View File
@@ -1,5 +1,17 @@
# @xyflow/svelte # @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 ## 0.0.38
## ⚠️ Breaking changes ## ⚠️ Breaking changes
+23 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xyflow/svelte", "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.", "description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.",
"keywords": [ "keywords": [
"svelte", "svelte",
@@ -43,33 +43,34 @@
"dependencies": { "dependencies": {
"@svelte-put/shortcut": "^3.1.0", "@svelte-put/shortcut": "^3.1.0",
"@xyflow/system": "workspace:*", "@xyflow/system": "workspace:*",
"classcat": "^5.0.4" "classcat": "^5.0.4",
"svelte-preprocess": "^5.1.3"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.1.1", "@sveltejs/adapter-auto": "^3.1.1",
"@sveltejs/kit": "^2.5.2", "@sveltejs/kit": "^2.5.4",
"@sveltejs/package": "^2.2.7", "@sveltejs/package": "^2.3.0",
"@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^5.60.0", "@typescript-eslint/parser": "^7.2.0",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.18",
"cssnano": "^6.0.1", "cssnano": "^6.1.0",
"dotenv": "^16.3.1", "dotenv": "^16.4.5",
"eslint": "^8.43.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.31.1", "eslint-plugin-svelte": "^2.35.1",
"postcss": "^8.4.21", "postcss": "^8.4.35",
"postcss-cli": "^10.1.0", "postcss-cli": "^11.0.0",
"postcss-combine-duplicated-selectors": "^10.0.3", "postcss-combine-duplicated-selectors": "^10.0.3",
"postcss-import": "^15.1.0", "postcss-import": "^16.0.1",
"postcss-nested": "^6.0.0", "postcss-nested": "^6.0.1",
"postcss-rename": "^0.6.1", "postcss-rename": "^0.6.1",
"prettier": "^2.8.8", "prettier": "^3.2.5",
"prettier-plugin-svelte": "^2.10.1", "prettier-plugin-svelte": "^3.2.2",
"svelte": "^4.2.12", "svelte": "^4.2.12",
"svelte-check": "^3.6.6", "svelte-check": "^3.6.7",
"svelte-eslint-parser": "^0.32.2", "svelte-eslint-parser": "^0.33.1",
"tslib": "^2.5.3", "tslib": "^2.6.2",
"typescript": "5.1.3" "typescript": "5.4.2"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^3.0.0 || ^4.0.0" "svelte": "^3.0.0 || ^4.0.0"
@@ -20,6 +20,8 @@
style:transform="translate(-50%, -50%) translate({x}px,{y}px)" style:transform="translate(-50%, -50%) translate({x}px,{y}px)"
style={'pointer-events: all;' + style} style={'pointer-events: all;' + style}
role="button" role="button"
tabindex="-1"
on:keyup={() => {}}
on:click={() => { on:click={() => {
if (id) handleEdgeSelect(id); if (id) handleEdgeSelect(id);
}} }}
@@ -8,7 +8,8 @@
isMouseEvent, isMouseEvent,
type HandleConnection, type HandleConnection,
areConnectionMapsEqual, areConnectionMapsEqual,
handleConnectionChange handleConnectionChange,
ConnectionMode
} from '@xyflow/system'; } from '@xyflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
@@ -23,6 +24,7 @@
export let isConnectable: $$Props['isConnectable'] = undefined; export let isConnectable: $$Props['isConnectable'] = undefined;
export let onconnect: $$Props['onconnect'] = undefined; export let onconnect: $$Props['onconnect'] = undefined;
export let ondisconnect: $$Props['ondisconnect'] = undefined; export let ondisconnect: $$Props['ondisconnect'] = undefined;
// @todo implement connectablestart, connectableend
// export let isConnectableStart: $$Props['isConnectableStart'] = undefined; // export let isConnectableStart: $$Props['isConnectableStart'] = undefined;
// export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined; // export let isConnectableEnd: $$Props['isConnectableEnd'] = undefined;
@@ -32,7 +34,7 @@
const isTarget = type === 'target'; const isTarget = type === 'target';
const nodeId = getContext<string>('svelteflow__node_id'); const nodeId = getContext<string>('svelteflow__node_id');
const connectable = getContext<Writable<boolean>>('svelteflow__node_connectable'); const connectable = getContext<Writable<boolean>>('svelteflow__node_connectable');
$: handleConnectable = isConnectable !== undefined ? isConnectable : $connectable; $: isConnectable = isConnectable !== undefined ? isConnectable : $connectable;
const handleId = id || null; const handleId = id || null;
@@ -99,7 +101,8 @@
onConnectEnd: (event) => { onConnectEnd: (event) => {
$onConnectEndAction?.(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(); prevConnections = connections ?? new Map();
} }
$: connectionInProcess = !!$connection.startHandle;
$: connectingFrom = $: connectingFrom =
$connection.startHandle?.nodeId === nodeId && $connection.startHandle?.nodeId === nodeId &&
$connection.startHandle?.type === type && $connection.startHandle?.type === type &&
@@ -132,9 +136,12 @@
$connection.endHandle?.nodeId === nodeId && $connection.endHandle?.nodeId === nodeId &&
$connection.endHandle?.type === type && $connection.endHandle?.type === type &&
$connection.endHandle?.handleId === handleId; $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'; $: valid = connectingTo && $connection.status === 'valid';
// @todo implement connectablestart, connectableend
</script> </script>
<!-- <!--
@@ -152,19 +159,17 @@ The Handle component is the part of a node that can be used to connect nodes.
'nodrag', 'nodrag',
'nopan', 'nopan',
position, position,
{
valid,
connectingto: connectingTo,
connectingfrom: connectingFrom
},
className className
])} ])}
class:valid
class:connectingto={connectingTo}
class:connectingfrom={connectingFrom}
class:source={!isTarget} class:source={!isTarget}
class:target={isTarget} class:target={isTarget}
class:connectablestart={handleConnectable} class:connectablestart={isConnectable}
class:connectableend={handleConnectable} class:connectableend={isConnectable}
class:connectable={handleConnectable} class:connectable={isConnectable}
class:connectionindicator={handleConnectable} class:connectionindicator={isConnectable && (!connectionInProcess || isPossibleEndHandle)}
on:mousedown={onPointerDown} on:mousedown={onPointerDown}
on:touchstart={onPointerDown} on:touchstart={onPointerDown}
{style} {style}
@@ -6,6 +6,7 @@
import { Selection } from '$lib/components/Selection'; import { Selection } from '$lib/components/Selection';
import drag from '$lib/actions/drag'; import drag from '$lib/actions/drag';
import type { Node } from '$lib/types'; import type { Node } from '$lib/types';
import { createNodeEventDispatcher } from '$lib';
const store = useStore(); const store = useStore();
const { selectionRectMode, nodes } = store; const { selectionRectMode, nodes } = store;
@@ -14,6 +15,7 @@
selectioncontextmenu: { nodes: Node[]; event: MouseEvent | TouchEvent }; selectioncontextmenu: { nodes: Node[]; event: MouseEvent | TouchEvent };
selectionclick: { nodes: Node[]; event: MouseEvent | TouchEvent }; selectionclick: { nodes: Node[]; event: MouseEvent | TouchEvent };
}>(); }>();
const dispatchNodeEvent = createNodeEventDispatcher();
$: selectedNodes = $nodes.filter((n) => n.selected); $: selectedNodes = $nodes.filter((n) => n.selected);
$: bounds = getNodesBounds(selectedNodes); $: bounds = getNodesBounds(selectedNodes);
@@ -31,9 +33,24 @@
<div <div
class="selection-wrapper nopan" class="selection-wrapper nopan"
style="width: {bounds.width}px; height: {bounds.height}px; transform: translate({bounds.x}px, {bounds.y}px)" style="width: {bounds.width}px; height: {bounds.height}px; transform: translate({bounds.x}px, {bounds.y}px)"
use:drag={{ disabled: false, store }} use:drag={{
disabled: false,
store,
onDrag: (event, _, __, nodes) => {
dispatchNodeEvent('nodedrag', { event, targetNode: null, nodes });
},
onDragStart: (event, _, __, nodes) => {
dispatchNodeEvent('nodedragstart', { event, targetNode: null, nodes });
},
onDragStop: (event, _, __, nodes) => {
dispatchNodeEvent('nodedragstop', { event, targetNode: null, nodes });
}
}}
on:contextmenu={onContextMenu} on:contextmenu={onContextMenu}
on:click={onClick} on:click={onClick}
role="button"
tabindex="-1"
on:keyup={() => {}}
> >
<Selection width="100%" height="100%" x={0} y={0} /> <Selection width="100%" height="100%" x={0} y={0} />
</div> </div>
@@ -12,6 +12,7 @@
import type { NodeWrapperProps } from './types'; import type { NodeWrapperProps } from './types';
import type { Node } from '$lib/types'; import type { Node } from '$lib/types';
import { getNodeInlineStyleDimensions } from './utils'; import { getNodeInlineStyleDimensions } from './utils';
import { createNodeEventDispatcher } from '$lib';
interface $$Props extends NodeWrapperProps {} interface $$Props extends NodeWrapperProps {}
@@ -66,16 +67,7 @@
} }
const nodeComponent = $nodeTypes[nodeType] || DefaultNode; const nodeComponent = $nodeTypes[nodeType] || DefaultNode;
const dispatch = createEventDispatcher<{ const dispatchNodeEvent = createNodeEventDispatcher();
nodeclick: { node: Node; event: MouseEvent | TouchEvent };
nodecontextmenu: { node: Node; event: MouseEvent | TouchEvent };
nodedrag: { node: Node; nodes: Node[]; event: MouseEvent | TouchEvent };
nodedragstart: { node: Node; nodes: Node[]; event: MouseEvent | TouchEvent };
nodedragstop: { node: Node; nodes: Node[]; event: MouseEvent | TouchEvent };
nodemouseenter: { node: Node; event: MouseEvent | TouchEvent };
nodemouseleave: { node: Node; event: MouseEvent | TouchEvent };
nodemousemove: { node: Node; event: MouseEvent | TouchEvent };
}>();
const connectableStore = writable(connectable); const connectableStore = writable(connectable);
let prevType: string | undefined = undefined; let prevType: string | undefined = undefined;
let prevSourcePosition: Position | undefined = undefined; let prevSourcePosition: Position | undefined = undefined;
@@ -151,7 +143,7 @@
handleNodeSelection(id); handleNodeSelection(id);
} }
dispatch('nodeclick', { node, event }); dispatchNodeEvent('nodeclick', { node, event });
} }
</script> </script>
@@ -166,14 +158,14 @@
handleSelector: dragHandle, handleSelector: dragHandle,
noDragClass: 'nodrag', noDragClass: 'nodrag',
onNodeMouseDown: handleNodeSelection, onNodeMouseDown: handleNodeSelection,
onDrag: (event, _, node, nodes) => { onDrag: (event, _, targetNode, nodes) => {
dispatch('nodedrag', { event, node, nodes }); dispatchNodeEvent('nodedrag', { event, targetNode, nodes });
}, },
onDragStart: (event, _, node, nodes) => { onDragStart: (event, _, targetNode, nodes) => {
dispatch('nodedragstart', { event, node, nodes }); dispatchNodeEvent('nodedragstart', { event, targetNode, nodes });
}, },
onDragStop: (event, _, node, nodes) => { onDragStop: (event, _, targetNode, nodes) => {
dispatch('nodedragstop', { event, node, nodes }); dispatchNodeEvent('nodedragstop', { event, targetNode, nodes });
}, },
store store
}} }}
@@ -192,10 +184,10 @@
style:visibility={initialized ? 'visible' : 'hidden'} style:visibility={initialized ? 'visible' : 'hidden'}
style="{style ?? ''};{inlineStyleDimensions.width}{inlineStyleDimensions.height}" style="{style ?? ''};{inlineStyleDimensions.width}{inlineStyleDimensions.height}"
on:click={onSelectNodeHandler} on:click={onSelectNodeHandler}
on:mouseenter={(event) => dispatch('nodemouseenter', { node, event })} on:mouseenter={(event) => dispatchNodeEvent('nodemouseenter', { node, event })}
on:mouseleave={(event) => dispatch('nodemouseleave', { node, event })} on:mouseleave={(event) => dispatchNodeEvent('nodemouseleave', { node, event })}
on:mousemove={(event) => dispatch('nodemousemove', { node, event })} on:mousemove={(event) => dispatchNodeEvent('nodemousemove', { node, event })}
on:contextmenu={(event) => dispatch('nodecontextmenu', { node, event })} on:contextmenu={(event) => dispatchNodeEvent('nodecontextmenu', { node, event })}
> >
<svelte:component <svelte:component
this={nodeComponent} this={nodeComponent}
@@ -242,7 +242,13 @@
on:nodedragstop on:nodedragstop
on:nodecontextmenu on:nodecontextmenu
/> />
<NodeSelection on:selectionclick on:selectioncontextmenu /> <NodeSelection
on:selectionclick
on:selectioncontextmenu
on:nodedragstart
on:nodedrag
on:nodedragstop
/>
</ViewportComponent> </ViewportComponent>
<UserSelection /> <UserSelection />
</Pane> </Pane>
+13 -17
View File
@@ -13,7 +13,8 @@ import {
type Rect, type Rect,
getViewportForBounds, getViewportForBounds,
getElementsToRemove, getElementsToRemove,
rendererPointToPoint rendererPointToPoint,
nodeHasDimensions
} from '@xyflow/system'; } from '@xyflow/system';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
@@ -242,19 +243,12 @@ export function useSvelteFlow(): {
edgeLookup edgeLookup
} = useStore(); } = useStore();
const getNodeRect = ( const getNodeRect = (nodeOrRect: Node | { id: Node['id'] }): Rect | null => {
nodeOrRect: Node | { id: Node['id'] } | Rect const node =
): [Rect | null, Node | null | undefined, boolean] => { isNode(nodeOrRect) && nodeHasDimensions(nodeOrRect)
const isRect = isRectObject(nodeOrRect); ? nodeOrRect
const node = isRect ? null : get(nodes).find((n) => n.id === nodeOrRect.id); : get(nodeLookup).get(nodeOrRect.id);
return node ? nodeToRect(node) : null;
if (!isRect && !node) {
return [null, null, isRect];
}
const nodeRect = isRect ? nodeOrRect : nodeToRect(node!);
return [nodeRect, node, isRect];
}; };
const updateNode = ( const updateNode = (
@@ -329,14 +323,15 @@ export function useSvelteFlow(): {
partially = true, partially = true,
nodesToIntersect?: Node[] nodesToIntersect?: Node[]
) => { ) => {
const [nodeRect, node, isRect] = getNodeRect(nodeOrRect); const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
if (!nodeRect) { if (!nodeRect) {
return []; return [];
} }
return (nodesToIntersect || get(nodes)).filter((n) => { return (nodesToIntersect || get(nodes)).filter((n) => {
if (!isRect && (n.id === node!.id || !n.computed?.positionAbsolute)) { if (!isRect && (n.id === nodeOrRect.id || !n.computed?.positionAbsolute)) {
return false; return false;
} }
@@ -352,7 +347,8 @@ export function useSvelteFlow(): {
area: Rect, area: Rect,
partially = true partially = true
) => { ) => {
const [nodeRect] = getNodeRect(nodeOrRect); const isRect = isRectObject(nodeOrRect);
const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
if (!nodeRect) { if (!nodeRect) {
return false; return false;
+1
View File
@@ -10,6 +10,7 @@ export * from '$lib/components/ViewportPortal';
export * from '$lib/components/BaseEdge'; export * from '$lib/components/BaseEdge';
export { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '$lib/components/edges'; export { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '$lib/components/edges';
export * from '$lib/components/Handle'; export * from '$lib/components/Handle';
export * from '$lib/components/EdgeLabel';
// plugins // plugins
export * from '$lib/plugins/Controls'; export * from '$lib/plugins/Controls';
+13
View File
@@ -1,3 +1,4 @@
import { createEventDispatcher } from 'svelte';
import { isNodeBase, isEdgeBase } from '@xyflow/system'; import { isNodeBase, isEdgeBase } from '@xyflow/system';
import type { Edge, Node } from '$lib/types'; import type { Edge, Node } from '$lib/types';
@@ -21,3 +22,15 @@ export const isNode = <NodeType extends Node = Node>(element: unknown): element
*/ */
export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType => export const isEdge = <EdgeType extends Edge = Edge>(element: unknown): element is EdgeType =>
isEdgeBase<EdgeType>(element); isEdgeBase<EdgeType>(element);
export const createNodeEventDispatcher = () =>
createEventDispatcher<{
nodeclick: { node: Node; event: MouseEvent | TouchEvent };
nodecontextmenu: { node: Node; event: MouseEvent | TouchEvent };
nodedrag: { targetNode: Node | null; nodes: Node[]; event: MouseEvent | TouchEvent };
nodedragstart: { targetNode: Node | null; nodes: Node[]; event: MouseEvent | TouchEvent };
nodedragstop: { targetNode: Node | null; nodes: Node[]; event: MouseEvent | TouchEvent };
nodemouseenter: { node: Node; event: MouseEvent | TouchEvent };
nodemouseleave: { node: Node; event: MouseEvent | TouchEvent };
nodemousemove: { node: Node; event: MouseEvent | TouchEvent };
}>();
+2
View File
@@ -1,7 +1,9 @@
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
preprocess: preprocess(),
kit: { kit: {
adapter: adapter() adapter: adapter()
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xyflow/system", "name": "@xyflow/system",
"version": "0.0.19", "version": "0.0.20",
"description": "xyflow core system that powers React Flow and Svelte Flow.", "description": "xyflow core system that powers React Flow and Svelte Flow.",
"keywords": [ "keywords": [
"node-based UI", "node-based UI",
+1 -1
View File
@@ -45,7 +45,7 @@ export const isEdgeBase = <EdgeType extends EdgeBase = EdgeBase>(element: any):
* @returns A boolean indicating whether the element is an Node * @returns A boolean indicating whether the element is an Node
*/ */
export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType => export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType =>
'id' in element && !('source' in element) && !('target' in element); 'id' in element && 'position' in element && !('source' in element) && !('target' in element);
/** /**
* Pass in a node, and get connected nodes where edge.source === node.id * Pass in a node, and get connected nodes where edge.source === node.id
@@ -2,7 +2,11 @@ import { NodeBase } from '../types';
type NodeData = Pick<NodeBase, 'id' | 'type' | 'data'>; type NodeData = Pick<NodeBase, 'id' | 'type' | 'data'>;
export function shallowNodeData(a: NodeData | NodeData[], b: NodeData | NodeData[]) { export function shallowNodeData(a: NodeData | NodeData[] | null, b: NodeData | NodeData[] | null) {
if (a === null || b === null) {
return false;
}
const _a = Array.isArray(a) ? a : [a]; const _a = Array.isArray(a) ? a : [a];
const _b = Array.isArray(b) ? b : [b]; const _b = Array.isArray(b) ? b : [b];
+6
View File
@@ -38,6 +38,7 @@ export type OnPointerDownParams = {
isValidConnection?: IsValidConnection; isValidConnection?: IsValidConnection;
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void; onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
getTransform: () => Transform; getTransform: () => Transform;
getConnectionStartHandle: () => ConnectingHandle | null;
}; };
export type IsValidParams = { export type IsValidParams = {
@@ -91,6 +92,7 @@ function onPointerDown(
onEdgeUpdateEnd, onEdgeUpdateEnd,
updateConnection, updateConnection,
getTransform, getTransform,
getConnectionStartHandle,
}: OnPointerDownParams }: OnPointerDownParams
) { ) {
// when xyflow is used inside a shadow root we can't use document // when xyflow is used inside a shadow root we can't use document
@@ -149,6 +151,10 @@ function onPointerDown(
onConnectStart?.(event, { nodeId, handleId, handleType }); onConnectStart?.(event, { nodeId, handleId, handleType });
function onPointerMove(event: MouseEvent | TouchEvent) { function onPointerMove(event: MouseEvent | TouchEvent) {
if (!getConnectionStartHandle()) {
onPointerUp(event);
}
const transform = getTransform(); const transform = getTransform();
connectionPosition = getEventPosition(event, containerBounds); connectionPosition = getEventPosition(event, containerBounds);
closestHandle = getClosestHandle( closestHandle = getClosestHandle(
+920 -468
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,6 +14,6 @@
"@rollup/plugin-typescript": "11.0.0", "@rollup/plugin-typescript": "11.0.0",
"rollup": "^3.23.0", "rollup": "^3.23.0",
"rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-peer-deps-external": "^2.2.4",
"typescript": "^4.9.4" "typescript": "^5.1.3"
} }
} }