feat(react,svelte): add nodeDragThreshold
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
import { ReactFlow, addEdge, Node, Connection, Edge, useNodesState, useEdgesState } from '@xyflow/react';
|
||||
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const nodesA: Node[] = [
|
||||
{
|
||||
id: '1a',
|
||||
@@ -83,7 +80,10 @@ const edgesB: Edge[] = [
|
||||
{ id: 'e4b', source: 'inputb', target: '4b' },
|
||||
];
|
||||
|
||||
const onNodeDragStart = (_: MouseEvent, node: Node) => console.log('drag start', node);
|
||||
const onNodeDrag = (_: MouseEvent, node: Node) => console.log('drag', node.position);
|
||||
const onNodeDragStop = (_: MouseEvent, node: Node) => console.log('drag stop', node);
|
||||
const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node);
|
||||
|
||||
const BasicFlow = () => {
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(nodesA);
|
||||
@@ -99,8 +99,10 @@ const BasicFlow = () => {
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onConnect={onConnect}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onNodeDrag={onNodeDrag}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
nodeDragThreshold={10}
|
||||
>
|
||||
<div style={{ position: 'absolute', right: 10, top: 10, zIndex: 4 }}>
|
||||
<button
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
]);
|
||||
</script>
|
||||
|
||||
<SvelteFlow {nodes} {edges} fitView>
|
||||
<SvelteFlow {nodes} {edges} fitView nodeDragThreshold={2}>
|
||||
<Controls />
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<MiniMap />
|
||||
|
||||
@@ -56,6 +56,7 @@ type StoreUpdaterProps = Pick<
|
||||
| 'connectionRadius'
|
||||
| 'isValidConnection'
|
||||
| 'selectNodesOnDrag'
|
||||
| 'nodeDragThreshold'
|
||||
> & { rfId: string };
|
||||
|
||||
const selector = (s: ReactFlowState) => ({
|
||||
@@ -140,6 +141,7 @@ const StoreUpdater = ({
|
||||
connectionRadius,
|
||||
isValidConnection,
|
||||
selectNodesOnDrag,
|
||||
nodeDragThreshold,
|
||||
}: StoreUpdaterProps) => {
|
||||
const {
|
||||
setNodes,
|
||||
@@ -203,6 +205,7 @@ const StoreUpdater = ({
|
||||
useDirectStoreUpdater('connectionRadius', connectionRadius, store.setState);
|
||||
useDirectStoreUpdater('isValidConnection', isValidConnection, store.setState);
|
||||
useDirectStoreUpdater('selectNodesOnDrag', selectNodesOnDrag, store.setState);
|
||||
useDirectStoreUpdater('nodeDragThreshold', nodeDragThreshold, store.setState);
|
||||
|
||||
useStoreUpdater<Node[]>(nodes, setNodes);
|
||||
useStoreUpdater<Edge[]>(edges, setEdges);
|
||||
|
||||
@@ -163,6 +163,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
onError,
|
||||
style,
|
||||
id,
|
||||
nodeDragThreshold,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
@@ -292,6 +293,7 @@ const ReactFlow = forwardRef<ReactFlowRefType, ReactFlowProps>(
|
||||
connectionRadius={connectionRadius}
|
||||
isValidConnection={isValidConnection}
|
||||
selectNodesOnDrag={selectNodesOnDrag}
|
||||
nodeDragThreshold={nodeDragThreshold}
|
||||
/>
|
||||
<SelectionListener onSelectionChange={onSelectionChange} />
|
||||
{children}
|
||||
|
||||
@@ -28,6 +28,7 @@ const initialState: ReactFlowStore = {
|
||||
paneDragging: false,
|
||||
noPanClassName: 'nopan',
|
||||
nodeOrigin: [0, 0],
|
||||
nodeDragThreshold: 0,
|
||||
|
||||
snapGrid: [15, 15],
|
||||
snapToGrid: false,
|
||||
|
||||
@@ -148,6 +148,7 @@ export type ReactFlowProps = HTMLAttributes<HTMLDivElement> & {
|
||||
connectionRadius?: number;
|
||||
onError?: OnError;
|
||||
isValidConnection?: IsValidConnection;
|
||||
nodeDragThreshold?: number;
|
||||
};
|
||||
|
||||
export type ReactFlowRefType = HTMLDivElement;
|
||||
|
||||
@@ -61,6 +61,7 @@ export type ReactFlowStore = {
|
||||
translateExtent: CoordinateExtent;
|
||||
nodeExtent: CoordinateExtent;
|
||||
nodeOrigin: NodeOrigin;
|
||||
nodeDragThreshold: number;
|
||||
|
||||
nodesSelectionActive: boolean;
|
||||
userSelectionActive: boolean;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
## WIP
|
||||
## 0.0.22
|
||||
|
||||
- add `nodeDragThreshold` prop
|
||||
- add `fitViewOptions` prop
|
||||
- add `defaultEdgeOptions` prop
|
||||
- add `on:edgecontextmenu` event handler prop
|
||||
- add `connectionMode` prop
|
||||
- add `attributionPosition` prop
|
||||
|
||||
@@ -38,6 +38,7 @@ export default function drag(domNode: Element, params: UseDragParams) {
|
||||
autoPanOnNodeDrag: get(store.autoPanOnNodeDrag),
|
||||
nodesDraggable: get(store.nodesDraggable),
|
||||
selectNodesOnDrag: get(store.selectNodesOnDrag),
|
||||
nodeDragThreshold: get(store.nodeDragThreshold),
|
||||
unselectNodesAndEdges: store.unselectNodesAndEdges,
|
||||
updateNodePositions: store.updateNodePositions,
|
||||
panBy: store.panBy
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
export let panActivationKey: $$Props['panActivationKey'] = undefined;
|
||||
export let nodesDraggable: $$Props['nodesDraggable'] = undefined;
|
||||
export let nodesConnectable: $$Props['nodesConnectable'] = undefined;
|
||||
export let nodeDragThreshold: $$Props['nodeDragThreshold'] = undefined;
|
||||
export let elementsSelectable: $$Props['elementsSelectable'] = undefined;
|
||||
export let snapGrid: $$Props['snapGrid'] = undefined;
|
||||
export let deleteKey: $$Props['deleteKey'] = undefined;
|
||||
@@ -118,7 +119,8 @@
|
||||
autoPanOnConnect,
|
||||
autoPanOnNodeDrag,
|
||||
onError,
|
||||
connectionMode
|
||||
connectionMode,
|
||||
nodeDragThreshold
|
||||
};
|
||||
|
||||
updateStoreByKeys(store, updatableProps);
|
||||
|
||||
@@ -43,6 +43,7 @@ export type SvelteFlowProps = DOMAttributes<HTMLDivElement> & {
|
||||
fitView?: boolean;
|
||||
fitViewOptions?: FitViewOptions;
|
||||
nodeOrigin?: NodeOrigin;
|
||||
nodeDragThreshold?: number;
|
||||
minZoom?: number;
|
||||
maxZoom?: number;
|
||||
initialViewport?: Viewport;
|
||||
|
||||
@@ -63,6 +63,7 @@ export type UpdatableStoreProps = {
|
||||
autoPanOnNodeDrag?: UnwrapWritable<SvelteFlowStore['autoPanOnNodeDrag']>;
|
||||
connectionMode?: UnwrapWritable<SvelteFlowStore['connectionMode']>;
|
||||
onError?: UnwrapWritable<SvelteFlowStore['onError']>;
|
||||
nodeDragThreshold?: UnwrapWritable<SvelteFlowStore['nodeDragThreshold']>;
|
||||
};
|
||||
|
||||
export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) {
|
||||
|
||||
@@ -65,6 +65,7 @@ export const getInitialStore = () => ({
|
||||
minZoom: writable<number>(0.5),
|
||||
maxZoom: writable<number>(2),
|
||||
nodeOrigin: writable<NodeOrigin>([0, 0]),
|
||||
nodeDragThreshold: writable<number>(0),
|
||||
nodeExtent: writable<CoordinateExtent>(infiniteExtent),
|
||||
translateExtent: writable<CoordinateExtent>(infiniteExtent),
|
||||
autoPanOnNodeDrag: writable<boolean>(true),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xyflow/system",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "xyflow core system that powers React Flow and Svelte Flow.",
|
||||
"keywords": [
|
||||
"node-based UI",
|
||||
|
||||
@@ -44,6 +44,7 @@ type StoreItems = {
|
||||
autoPanOnNodeDrag: boolean;
|
||||
nodesDraggable: boolean;
|
||||
selectNodesOnDrag: boolean;
|
||||
nodeDragThreshold: number;
|
||||
panBy: PanBy;
|
||||
unselectNodesAndEdges: () => void;
|
||||
onError?: OnError;
|
||||
@@ -93,6 +94,7 @@ export function XYDrag({
|
||||
let mousePosition: XYPosition = { x: 0, y: 0 };
|
||||
let dragEvent: MouseEvent | null = null;
|
||||
let containerBounds: DOMRect | null = null;
|
||||
let dragStarted = false;
|
||||
|
||||
const d3Selection = select(domNode);
|
||||
|
||||
@@ -192,63 +194,84 @@ export function XYDrag({
|
||||
autoPanId = requestAnimationFrame(autoPan);
|
||||
}
|
||||
|
||||
function startDrag(event: UseDragEvent) {
|
||||
const {
|
||||
nodes,
|
||||
multiSelectionActive,
|
||||
nodesDraggable,
|
||||
transform,
|
||||
snapGrid,
|
||||
snapToGrid,
|
||||
selectNodesOnDrag,
|
||||
onNodeDragStart,
|
||||
onSelectionDragStart,
|
||||
unselectNodesAndEdges,
|
||||
} = getStoreItems();
|
||||
|
||||
dragStarted = true;
|
||||
|
||||
if ((!selectNodesOnDrag || !isSelectable) && !multiSelectionActive && nodeId) {
|
||||
if (!nodes.find((n) => n.id === nodeId)?.selected) {
|
||||
// we need to reset selected nodes when selectNodesOnDrag=false
|
||||
unselectNodesAndEdges();
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelectable && selectNodesOnDrag) {
|
||||
onNodeClick?.();
|
||||
}
|
||||
|
||||
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||
lastPos = pointerPos;
|
||||
dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId);
|
||||
|
||||
const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
|
||||
|
||||
if (dragItems) {
|
||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodes,
|
||||
});
|
||||
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||
}
|
||||
}
|
||||
|
||||
const d3DragInstance = drag()
|
||||
.on('start', (event: UseDragEvent) => {
|
||||
const {
|
||||
nodes,
|
||||
multiSelectionActive,
|
||||
domNode,
|
||||
nodesDraggable,
|
||||
transform,
|
||||
snapGrid,
|
||||
snapToGrid,
|
||||
selectNodesOnDrag,
|
||||
onNodeDragStart,
|
||||
onSelectionDragStart,
|
||||
unselectNodesAndEdges,
|
||||
} = getStoreItems();
|
||||
const { domNode, nodeDragThreshold, transform, snapGrid, snapToGrid } = getStoreItems();
|
||||
|
||||
if ((!selectNodesOnDrag || !isSelectable) && !multiSelectionActive && nodeId) {
|
||||
if (!nodes.find((n) => n.id === nodeId)?.selected) {
|
||||
// we need to reset selected nodes when selectNodesOnDrag=false
|
||||
unselectNodesAndEdges();
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelectable && selectNodesOnDrag) {
|
||||
onNodeClick?.();
|
||||
if (nodeDragThreshold === 0) {
|
||||
startDrag(event);
|
||||
}
|
||||
|
||||
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||
lastPos = pointerPos;
|
||||
dragItems = getDragItems(nodes, nodesDraggable, pointerPos, nodeId);
|
||||
|
||||
const onNodeOrSelectionDragStart = nodeId ? onNodeDragStart : wrapSelectionDragFunc(onSelectionDragStart);
|
||||
|
||||
if (dragItems) {
|
||||
const [currentNode, currentNodes] = getEventHandlerParams({
|
||||
nodeId,
|
||||
dragItems,
|
||||
nodes,
|
||||
});
|
||||
onDragStart?.(event.sourceEvent as MouseEvent, dragItems, currentNode, currentNodes);
|
||||
onNodeOrSelectionDragStart?.(event.sourceEvent as MouseEvent, currentNode, currentNodes);
|
||||
}
|
||||
|
||||
containerBounds = domNode?.getBoundingClientRect() || null;
|
||||
mousePosition = getEventPosition(event.sourceEvent, containerBounds!);
|
||||
})
|
||||
.on('drag', (event: UseDragEvent) => {
|
||||
const { autoPanOnNodeDrag, transform, snapGrid, snapToGrid } = getStoreItems();
|
||||
const { autoPanOnNodeDrag, transform, snapGrid, snapToGrid, nodeDragThreshold } = getStoreItems();
|
||||
const pointerPos = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid });
|
||||
|
||||
if (!autoPanStarted && autoPanOnNodeDrag) {
|
||||
if (!autoPanStarted && autoPanOnNodeDrag && dragStarted) {
|
||||
autoPanStarted = true;
|
||||
autoPan();
|
||||
}
|
||||
|
||||
if (!dragStarted) {
|
||||
const x = pointerPos.xSnapped - (lastPos.x ?? 0);
|
||||
const y = pointerPos.ySnapped - (lastPos.y ?? 0);
|
||||
const distance = Math.sqrt(x * x + y * y);
|
||||
|
||||
if (distance > nodeDragThreshold) {
|
||||
startDrag(event);
|
||||
}
|
||||
}
|
||||
|
||||
// skip events without movement
|
||||
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems) {
|
||||
if ((lastPos.x !== pointerPos.xSnapped || lastPos.y !== pointerPos.ySnapped) && dragItems && dragStarted) {
|
||||
dragEvent = event.sourceEvent as MouseEvent;
|
||||
mousePosition = getEventPosition(event.sourceEvent, containerBounds!);
|
||||
|
||||
@@ -256,7 +279,12 @@ export function XYDrag({
|
||||
}
|
||||
})
|
||||
.on('end', (event: UseDragEvent) => {
|
||||
if (!dragStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoPanStarted = false;
|
||||
dragStarted = false;
|
||||
cancelAnimationFrame(autoPanId);
|
||||
|
||||
if (dragItems) {
|
||||
|
||||
Reference in New Issue
Block a user