Merge pull request #4348 from xyflow/next
React Flow 12.0.0-next.20 and Svelte Flow 0.1.5
This commit is contained in:
@@ -28,10 +28,15 @@ for (let i = 0; i < 100; i++) {
|
||||
});
|
||||
}
|
||||
|
||||
const initEdges: Edge[] = [];
|
||||
const initEdges: Edge[] = initNodes.reduce<Edge[]>((res, node, index) => {
|
||||
if (index > 0) {
|
||||
res.push({ id: `${index - 1}-${index}`, source: (index - 1).toString(), target: index.toString() });
|
||||
}
|
||||
return res;
|
||||
}, []);
|
||||
|
||||
const CustomNodeFlow = () => {
|
||||
const { setNodes, updateNodeData } = useReactFlow();
|
||||
const { setNodes, updateNodeData, updateEdge } = useReactFlow();
|
||||
const [nodes, , onNodesChange] = useNodesState(initNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initEdges);
|
||||
|
||||
@@ -55,6 +60,10 @@ const CustomNodeFlow = () => {
|
||||
nodes.forEach((node) => updateNodeData(node.id, { label: 'node update' }));
|
||||
};
|
||||
|
||||
const multiUpdateEdges = () => {
|
||||
edges.forEach((edge) => updateEdge(edge.id, { label: 'edge update' }));
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
@@ -70,6 +79,7 @@ const CustomNodeFlow = () => {
|
||||
<Panel>
|
||||
<button onClick={multiSetNodes}>set nodes</button>
|
||||
<button onClick={multiUpdateNodes}>update nodes</button>
|
||||
<button onClick={multiUpdateEdges}>update edges</button>
|
||||
</Panel>
|
||||
</ReactFlow>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @xyflow/react
|
||||
|
||||
## 12.0.0-next.20
|
||||
|
||||
- add `updateEdge` and `updateEdgeData` helpers to `useReactFlow`
|
||||
- enable dynamic edge label updates
|
||||
- prevent zooming on mobile if zoomOnPinch is false
|
||||
- add straight edge to path built-in-types
|
||||
- abort drag when multiple touches are detected
|
||||
|
||||
## 12.0.0-next.19
|
||||
|
||||
- update internals on node resizer updates
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/react",
|
||||
"version": "12.0.0-next.19",
|
||||
"version": "12.0.0-next.20",
|
||||
"description": "React Flow - A highly customizable React library for building node-based editors and interactive flow charts.",
|
||||
"keywords": [
|
||||
"react",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useState, useCallback } from 'react';
|
||||
import { memo, useState, useEffect, useRef } from 'react';
|
||||
import cc from 'classcat';
|
||||
import type { Rect } from '@xyflow/system';
|
||||
|
||||
@@ -19,19 +19,20 @@ function EdgeTextComponent({
|
||||
}: EdgeTextProps) {
|
||||
const [edgeTextBbox, setEdgeTextBbox] = useState<Rect>({ x: 1, y: 0, width: 0, height: 0 });
|
||||
const edgeTextClasses = cc(['react-flow__edge-textwrapper', className]);
|
||||
const edgeTextRef = useRef<SVGTextElement | null>(null);
|
||||
|
||||
const onEdgeTextRefChange = useCallback((edgeRef: SVGTextElement) => {
|
||||
if (edgeRef === null) return;
|
||||
useEffect(() => {
|
||||
if (edgeTextRef.current) {
|
||||
const textBbox = edgeTextRef.current.getBBox();
|
||||
|
||||
const textBbox = edgeRef.getBBox();
|
||||
|
||||
setEdgeTextBbox({
|
||||
x: textBbox.x,
|
||||
y: textBbox.y,
|
||||
width: textBbox.width,
|
||||
height: textBbox.height,
|
||||
});
|
||||
}, []);
|
||||
setEdgeTextBbox({
|
||||
x: textBbox.x,
|
||||
y: textBbox.y,
|
||||
width: textBbox.width,
|
||||
height: textBbox.height,
|
||||
});
|
||||
}
|
||||
}, [label]);
|
||||
|
||||
if (typeof label === 'undefined' || !label) {
|
||||
return null;
|
||||
@@ -60,7 +61,7 @@ function EdgeTextComponent({
|
||||
className="react-flow__edge-text"
|
||||
y={edgeTextBbox.height / 2}
|
||||
dy="0.3em"
|
||||
ref={onEdgeTextRefChange}
|
||||
ref={edgeTextRef}
|
||||
style={labelStyle}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import useViewportHelper from './useViewportHelper';
|
||||
import { useStore, useStoreApi } from './useStore';
|
||||
import { useBatchContext } from '../components/BatchProvider';
|
||||
import { elementToRemoveChange, isNode } from '../utils';
|
||||
import { elementToRemoveChange, isEdge, isNode } from '../utils';
|
||||
import type { ReactFlowInstance, Node, Edge, InternalNode, ReactFlowState, GeneralHelpers } from '../types';
|
||||
|
||||
const selector = (s: ReactFlowState) => !!s.panZoom;
|
||||
@@ -41,6 +41,10 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
batchContext.nodeQueue.push(payload as NodeType[]);
|
||||
};
|
||||
|
||||
const setEdges: GeneralHelpers<NodeType, EdgeType>['setEdges'] = (payload) => {
|
||||
batchContext.edgeQueue.push(payload as EdgeType[]);
|
||||
};
|
||||
|
||||
const getNodeRect = (node: NodeType | { id: string }): Rect | null => {
|
||||
const { nodeLookup, nodeOrigin } = store.getState();
|
||||
|
||||
@@ -77,6 +81,23 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
);
|
||||
};
|
||||
|
||||
const updateEdge: GeneralHelpers<NodeType, EdgeType>['updateEdge'] = (
|
||||
id,
|
||||
edgeUpdate,
|
||||
options = { replace: false }
|
||||
) => {
|
||||
setEdges((prevEdges) =>
|
||||
prevEdges.map((edge) => {
|
||||
if (edge.id === id) {
|
||||
const nextEdge = typeof edgeUpdate === 'function' ? edgeUpdate(edge as EdgeType) : edgeUpdate;
|
||||
return options.replace && isEdge(nextEdge) ? (nextEdge as EdgeType) : { ...edge, ...nextEdge };
|
||||
}
|
||||
|
||||
return edge;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
getNodes: () => store.getState().nodes.map((n) => ({ ...n })) as NodeType[],
|
||||
getNode: (id) => getInternalNode(id)?.internals.userNode as NodeType,
|
||||
@@ -87,9 +108,7 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
},
|
||||
getEdge: (id) => store.getState().edgeLookup.get(id) as EdgeType,
|
||||
setNodes,
|
||||
setEdges: (payload) => {
|
||||
batchContext.edgeQueue.push(payload as EdgeType[]);
|
||||
},
|
||||
setEdges,
|
||||
addNodes: (payload) => {
|
||||
const newNodes = Array.isArray(payload) ? payload : [payload];
|
||||
batchContext.nodeQueue.push((nodes) => [...nodes, ...newNodes]);
|
||||
@@ -200,6 +219,17 @@ export function useReactFlow<NodeType extends Node = Node, EdgeType extends Edge
|
||||
options
|
||||
);
|
||||
},
|
||||
updateEdge,
|
||||
updateEdgeData: (id, dataUpdate, options = { replace: false }) => {
|
||||
updateEdge(
|
||||
id,
|
||||
(edge) => {
|
||||
const nextData = typeof dataUpdate === 'function' ? dataUpdate(edge) : dataUpdate;
|
||||
return options.replace ? { ...edge, data: nextData } : { ...edge, data: { ...edge.data, ...nextData } };
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ type StepEdge<EdgeData extends Record<string, unknown> = Record<string, unknown>
|
||||
pathOptions?: StepPathOptions;
|
||||
};
|
||||
|
||||
export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge;
|
||||
type StraightEdge<EdgeData extends Record<string, unknown> = Record<string, unknown>> = Edge<EdgeData, 'straight'>;
|
||||
|
||||
export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge | StraightEdge;
|
||||
|
||||
export type EdgeMouseHandler<EdgeType extends Edge = Edge> = (event: ReactMouseEvent, edge: EdgeType) => void;
|
||||
|
||||
|
||||
@@ -143,6 +143,36 @@ export type GeneralHelpers<NodeType extends Node = Node, EdgeType extends Edge =
|
||||
dataUpdate: Partial<NodeType['data']> | ((node: NodeType) => Partial<NodeType['data']>),
|
||||
options?: { replace: boolean }
|
||||
) => void;
|
||||
/**
|
||||
* Updates an edge.
|
||||
*
|
||||
* @param id - id of the edge to update
|
||||
* @param edgeUpdate - the edge update as an object or a function that receives the current edge and returns the edge update
|
||||
* @param options.replace - if true, the edge is replaced with the edge update, otherwise the changes get merged
|
||||
*
|
||||
* @example
|
||||
* updateEdge('edge-1', (edge) => ({ label: 'A new label' }));
|
||||
*/
|
||||
updateEdge: (
|
||||
id: string,
|
||||
edgeUpdate: Partial<EdgeType> | ((edge: EdgeType) => Partial<EdgeType>),
|
||||
options?: { replace: boolean }
|
||||
) => void;
|
||||
/**
|
||||
* Updates the data attribute of a edge.
|
||||
*
|
||||
* @param id - id of the edge to update
|
||||
* @param dataUpdate - the data update as an object or a function that receives the current data and returns the data update
|
||||
* @param options.replace - if true, the data is replaced with the data update, otherwise the changes get merged
|
||||
*
|
||||
* @example
|
||||
* updateEdgeData('edge-1', { label: 'A new label' });
|
||||
*/
|
||||
updateEdgeData: (
|
||||
id: string,
|
||||
dataUpdate: Partial<EdgeType['data']> | ((edge: EdgeType) => Partial<EdgeType['data']>),
|
||||
options?: { replace: boolean }
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type ReactFlowInstance<NodeType extends Node = Node, EdgeType extends Edge = Edge> = GeneralHelpers<
|
||||
|
||||
@@ -2,5 +2,8 @@
|
||||
"extends": "@xyflow/tsconfig/react.json",
|
||||
"display": "@xyflow/react",
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @xyflow/svelte
|
||||
|
||||
## 0.1.5
|
||||
|
||||
- prevent zooming on mobile if zoomOnPinch is false
|
||||
- add straight edge to path built-in-types
|
||||
- abort drag when multiple touches are detected
|
||||
- fix merge_styles error
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- add `selectable`, `deletable` and `draggable` to node and edge props
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/svelte",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "Svelte Flow - A highly customizable Svelte library for building node-based editors, workflow systems, diagrams and more.",
|
||||
"keywords": [
|
||||
"svelte",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div
|
||||
class={cc(['svelte-flow__panel', className, ...positionClasses])}
|
||||
{style}
|
||||
style:pointer-events={$selectionRectMode ? 'none' : undefined}
|
||||
style:pointer-events={$selectionRectMode ? 'none' : ''}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -44,7 +44,12 @@ type StepEdge<EdgeData extends Record<string, unknown> = Record<string, unknown>
|
||||
pathOptions?: StepPathOptions;
|
||||
};
|
||||
|
||||
export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge;
|
||||
type StraightEdge<EdgeData extends Record<string, unknown> = Record<string, unknown>> = Edge<
|
||||
EdgeData,
|
||||
'straight'
|
||||
>;
|
||||
|
||||
export type BuiltInEdge = SmoothStepEdge | BezierEdge | StepEdge | StraightEdge;
|
||||
|
||||
/**
|
||||
* Custom edge component props.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/system",
|
||||
"version": "0.0.27",
|
||||
"version": "0.0.28",
|
||||
"description": "xyflow core system that powers React Flow and Svelte Flow.",
|
||||
"keywords": [
|
||||
"node-based UI",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const getEventPosition = (event: MouseEvent | TouchEvent, bounds?: DOMRec
|
||||
export const getHandleBounds = (
|
||||
selector: string,
|
||||
nodeElement: HTMLDivElement,
|
||||
nodeBounds: DOMRect,
|
||||
zoom: number,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
): HandleElement[] | null => {
|
||||
@@ -72,11 +73,9 @@ export const getHandleBounds = (
|
||||
|
||||
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
||||
|
||||
// @todo can't we use the node dimensions here?
|
||||
const nodeBounds = nodeElement.getBoundingClientRect();
|
||||
const nodeOffset = {
|
||||
x: nodeBounds.width * nodeOrigin[0],
|
||||
y: nodeBounds.height * nodeOrigin[1],
|
||||
x: nodeBounds.left - nodeBounds.width * nodeOrigin[0],
|
||||
y: nodeBounds.top - nodeBounds.height * nodeOrigin[1],
|
||||
};
|
||||
|
||||
return handlesArray.map((handle): HandleElement => {
|
||||
@@ -85,8 +84,8 @@ export const getHandleBounds = (
|
||||
return {
|
||||
id: handle.getAttribute('data-handleid'),
|
||||
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
||||
x: (handleBounds.left - nodeBounds.left - nodeOffset.x) / zoom,
|
||||
y: (handleBounds.top - nodeBounds.top - nodeOffset.y) / zoom,
|
||||
x: (handleBounds.left - nodeOffset.x) / zoom,
|
||||
y: (handleBounds.top - nodeOffset.y) / zoom,
|
||||
...getDimensions(handle),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -269,12 +269,13 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
||||
);
|
||||
|
||||
if (doUpdate) {
|
||||
const nodeBounds = update.nodeElement.getBoundingClientRect();
|
||||
node.measured = dimensions;
|
||||
node.internals = {
|
||||
...node.internals,
|
||||
handleBounds: {
|
||||
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin),
|
||||
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin),
|
||||
source: getHandleBounds('.source', update.nodeElement, nodeBounds, zoom, node.origin || nodeOrigin),
|
||||
target: getHandleBounds('.target', update.nodeElement, nodeBounds, zoom, node.origin || nodeOrigin),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
let containerBounds: DOMRect | null = null;
|
||||
let dragStarted = false;
|
||||
let d3Selection: Selection<Element, unknown, null, undefined> | null = null;
|
||||
let abortDrag = false; // prevents unintentional dragging on multitouch
|
||||
|
||||
// public functions
|
||||
function update({ noDragClassName, handleSelector, domNode, isSelectable, nodeId }: DragUpdateParams) {
|
||||
@@ -264,6 +265,8 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
.on('start', (event: UseDragEvent) => {
|
||||
const { domNode, nodeDragThreshold, transform, snapGrid, snapToGrid } = getStoreItems();
|
||||
|
||||
abortDrag = false;
|
||||
|
||||
if (nodeDragThreshold === 0) {
|
||||
startDrag(event);
|
||||
}
|
||||
@@ -277,6 +280,14 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
const { autoPanOnNodeDrag, transform, snapGrid, snapToGrid, nodeDragThreshold } = getStoreItems();
|
||||
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||
|
||||
if (event.sourceEvent.type === 'touchmove' && event.sourceEvent.touches.length > 1) {
|
||||
abortDrag = true;
|
||||
}
|
||||
|
||||
if (abortDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!autoPanStarted && autoPanOnNodeDrag && dragStarted) {
|
||||
autoPanStarted = true;
|
||||
autoPan();
|
||||
@@ -301,7 +312,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
|
||||
}
|
||||
})
|
||||
.on('end', (event: UseDragEvent) => {
|
||||
if (!dragStarted) {
|
||||
if (!dragStarted || abortDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,11 @@ export function createFilter({
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!zoomOnPinch && event.type === 'touchstart' && event.touches?.length > 1) {
|
||||
event.preventDefault(); // if you manage to start with 2 touches, we prevent native zoom
|
||||
return false;
|
||||
}
|
||||
|
||||
// when there is no scroll handling enabled, we prevent all wheel events
|
||||
if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
|
||||
return false;
|
||||
|
||||
Generated
+7126
-5708
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user