Merge branch 'next' into absolute-origin

This commit is contained in:
Moritz Klack
2024-06-24 12:57:24 +02:00
committed by GitHub
91 changed files with 8822 additions and 7060 deletions
+6 -1
View File
@@ -1,5 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { D3DragEvent, Selection as D3Selection, SubjectPosition, ZoomBehavior } from 'd3';
import type { Selection as D3Selection } from 'd3-selection';
import type { D3DragEvent, SubjectPosition } from 'd3-drag';
import type { ZoomBehavior } from 'd3-zoom';
// this is needed for the Selection type to include the transition function :/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { Transition } from 'd3-transition';
import type { XYPosition, Rect } from './utils';
import type { InternalNodeBase, NodeBase, NodeDragItem, NodeOrigin } from './nodes';
+1
View File
@@ -16,6 +16,7 @@ export type ConnectingHandle = {
nodeId: string;
type: HandleType;
handleId?: string | null;
position?: Position | null;
};
export type ConnectionHandle = {
+12 -1
View File
@@ -88,7 +88,18 @@ export type InternalNodeBase<NodeType extends NodeBase = NodeBase> = NodeType &
*/
export type NodeProps<NodeType extends NodeBase> = Pick<
NodeType,
'id' | 'data' | 'width' | 'height' | 'sourcePosition' | 'targetPosition' | 'selected' | 'dragHandle'
| 'id'
| 'data'
| 'width'
| 'height'
| 'sourcePosition'
| 'targetPosition'
| 'selected'
| 'dragHandle'
| 'selectable'
| 'deletable'
| 'draggable'
| 'parentId'
> &
Required<Pick<NodeType, 'type' | 'dragging' | 'zIndex'>> & {
/** whether a node is connectable or not */
+5 -6
View File
@@ -71,6 +71,7 @@ export const getHandleBounds = (
selector: string,
node: InternalNodeBase,
nodeElement: HTMLDivElement,
nodeBounds: DOMRect,
zoom: number,
nodeOrigin: NodeOrigin = [0, 0]
): HandleElement[] | null => {
@@ -82,11 +83,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 => {
@@ -95,8 +94,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),
};
});
+4 -4
View File
@@ -132,23 +132,23 @@ export const addEdge = <EdgeType extends EdgeBase>(
return edges.concat(edge);
};
export type UpdateEdgeOptions = {
export type ReconnectEdgeOptions = {
shouldReplaceId?: boolean;
};
/**
* A handy utility to update an existing Edge with new properties
* A handy utility to reconnect an existing edge with new properties
* @param oldEdge - The edge you want to update
* @param newConnection - The new connection you want to update the edge with
* @param edges - The array of all current edges
* @param options.shouldReplaceId - should the id of the old edge be replaced with the new connection id
* @returns the updated edges array
*/
export const updateEdge = <EdgeType extends EdgeBase>(
export const reconnectEdge = <EdgeType extends EdgeBase>(
oldEdge: EdgeType,
newConnection: Connection,
edges: EdgeType[],
options: UpdateEdgeOptions = { shouldReplaceId: true }
options: ReconnectEdgeOptions = { shouldReplaceId: true }
): EdgeType[] => {
const { id: oldEdgeId, ...rest } = oldEdge;
+10 -5
View File
@@ -42,8 +42,6 @@ export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | n
: (targetHandleBounds?.target ?? []).concat(targetHandleBounds?.source ?? []),
params.targetHandle
);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
if (!sourceHandle || !targetHandle) {
params.onError?.(
@@ -58,8 +56,10 @@ export function getEdgePosition(params: GetEdgePositionParams): EdgePosition | n
return null;
}
const [sourceX, sourceY] = getHandlePosition(sourcePosition, sourceNode, sourceHandle);
const [targetX, targetY] = getHandlePosition(targetPosition, targetNode, targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
const [sourceX, sourceY] = getHandlePosition(sourceNode, sourceHandle, sourcePosition);
const [targetX, targetY] = getHandlePosition(targetNode, targetHandle, targetPosition);
return {
sourceX,
@@ -96,10 +96,15 @@ function toHandleBounds(handles?: NodeHandle[]) {
};
}
function getHandlePosition(position: Position, node: InternalNodeBase, handle: HandleElement | null = null): number[] {
export function getHandlePosition(
node: InternalNodeBase,
handle: HandleElement | null,
fallbackPosition: Position = Position.Left
): number[] {
const x = (handle?.x ?? 0) + node.internals.positionAbsolute.x;
const y = (handle?.y ?? 0) + node.internals.positionAbsolute.y;
const { width, height } = handle ?? getNodeDimensions(node);
const position = handle?.position ?? fallbackPosition;
switch (position) {
case Position.Top:
+11 -4
View File
@@ -250,10 +250,17 @@ export function evaluateAbsolutePosition(
): XYPosition {
const positionAbsolute = { ...position };
const parent = nodeLookup.get(parentId);
if (parent) {
positionAbsolute.x += parent.internals.positionAbsolute.x - dimensions.width * nodeOrigin[0];
positionAbsolute.y += parent.internals.positionAbsolute.y - dimensions.height * nodeOrigin[1];
while (nextParentId) {
const parent = nodeLookup.get(nextParentId);
nextParentId = parent?.parentId;
if (parent) {
const origin = parent.origin || nodeOrigin;
const xOffset = (parent.measured.width ?? 0) * origin[0];
const yOffset = (parent.measured.height ?? 0) * origin[1];
positionAbsolute.x += parent.internals.positionAbsolute.x - dimensions.width * nodeOrigin[0];
positionAbsolute.y += parent.internals.positionAbsolute.y - dimensions.height * nodeOrigin[1];
}
}
return positionAbsolute;
+5 -4
View File
@@ -282,14 +282,15 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
);
if (doUpdate) {
const positionAbsolute = getNodePositionWithOrigin(node, nodeOrigin);
const nodeBounds = update.nodeElement.getBoundingClientRect();
node.measured = dimensions;
node.internals = {
...node.internals,
positionAbsolute: getNodePositionWithOrigin(node, nodeOrigin),
positionAbsolute,
handleBounds: {
source: getHandleBounds('.source', node, update.nodeElement, zoom),
target: getHandleBounds('.target', node, update.nodeElement, zoom),
source: getHandleBounds('.source', update.nodeElement, nodeBounds, zoom),
target: getHandleBounds('.target', update.nodeElement, nodeBounds, zoom),
},
};
if (node.parentId) {
+12 -1
View File
@@ -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) {
@@ -263,6 +264,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);
}
@@ -276,6 +279,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();
@@ -300,7 +311,7 @@ export function XYDrag<OnNodeDrag extends (e: any, nodes: any, node: any) => voi
}
})
.on('end', (event: UseDragEvent) => {
if (!dragStarted) {
if (!dragStarted || abortDrag) {
return;
}
+12 -12
View File
@@ -13,6 +13,7 @@ import {
type IsValidConnection,
type ConnectionHandle,
NodeLookup,
Position,
} from '../types';
import { getClosestHandle, getConnectionStatus, getHandleLookup, getHandleType } from './utils';
@@ -36,7 +37,7 @@ export type OnPointerDownParams = {
onConnect?: OnConnect;
onConnectEnd?: OnConnectEnd;
isValidConnection?: IsValidConnection;
onEdgeUpdateEnd?: (evt: MouseEvent | TouchEvent) => void;
onReconnectEnd?: (evt: MouseEvent | TouchEvent) => void;
getTransform: () => Transform;
getConnectionStartHandle: () => ConnectingHandle | null;
};
@@ -89,7 +90,7 @@ function onPointerDown(
onConnect,
onConnectEnd,
isValidConnection = alwaysValid,
onEdgeUpdateEnd,
onReconnectEnd,
updateConnection,
getTransform,
getConnectionStartHandle,
@@ -138,12 +139,12 @@ function onPointerDown(
nodeId,
handleId,
type: handleType,
position: (clickedHandle?.getAttribute('data-handlepos') as Position) || Position.Top,
};
updateConnection({
connectionPosition,
connectionStatus: null,
// connectionNodeId etc will be removed in the next major in favor of connectionStartHandle
connectionStartHandle,
connectionEndHandle: null,
});
@@ -211,7 +212,7 @@ function onPointerDown(
onConnectEnd?.(event);
if (edgeUpdaterType) {
onEdgeUpdateEnd?.(event);
onReconnectEnd?.(event);
}
cancelConnection();
@@ -297,15 +298,14 @@ function isValidHandle(
? (isTarget && handleType === 'source') || (!isTarget && handleType === 'target')
: handleNodeId !== fromNodeId || handleId !== fromHandleId);
if (isValid) {
result.endHandle = {
nodeId: handleNodeId as string,
handleId,
type: handleType as HandleType,
};
result.isValid = isValid && isValidConnection(connection);
result.isValid = isValidConnection(connection);
}
result.endHandle = {
nodeId: handleNodeId as string,
handleId,
type: handleType as HandleType,
position: handleToCheck.getAttribute('data-handlepos') as Position,
};
}
return result;
+7 -5
View File
@@ -1,3 +1,4 @@
import { getHandlePosition } from '../utils';
import {
ConnectionStatus,
type HandleType,
@@ -16,14 +17,15 @@ export function getHandles(
type: HandleType,
currentHandle: string
): ConnectionHandle[] {
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, h) => {
if (`${node.id}-${h.id}-${type}` !== currentHandle) {
return (handleBounds[type] || []).reduce<ConnectionHandle[]>((res, handle) => {
if (`${node.id}-${handle.id}-${type}` !== currentHandle) {
const [x, y] = getHandlePosition(node, handle);
res.push({
id: h.id || null,
id: handle.id || null,
type,
nodeId: node.id,
x: node.internals.positionAbsolute.x + h.x + h.width / 2,
y: node.internals.positionAbsolute.y + h.y + h.height / 2,
x,
y,
});
}
return res;
+5
View File
@@ -38,6 +38,7 @@ export function XYMinimap({ domNode, panZoom, getTransform, getViewScale }: XYMi
zoomable = true,
inversePan = false,
}: XYMinimapUpdate) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const zoomHandler = (event: D3ZoomEvent<SVGSVGElement, any>) => {
const transform = getTransform();
@@ -55,6 +56,7 @@ export function XYMinimap({ domNode, panZoom, getTransform, getViewScale }: XYMi
};
let panStart = [0, 0];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const panStartHandler = (event: D3ZoomEvent<HTMLDivElement, any>) => {
if (event.sourceEvent.type === 'mousedown' || event.sourceEvent.type === 'touchstart') {
panStart = [
@@ -64,6 +66,7 @@ export function XYMinimap({ domNode, panZoom, getTransform, getViewScale }: XYMi
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const panHandler = (event: D3ZoomEvent<HTMLDivElement, any>) => {
const transform = getTransform();
@@ -101,8 +104,10 @@ export function XYMinimap({ domNode, panZoom, getTransform, getViewScale }: XYMi
const zoomAndPanHandler = zoom()
.on('start', panStartHandler)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.on('zoom', pannable ? panHandler : null)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.on('zoom.wheel', zoomable ? zoomHandler : null);
+5
View File
@@ -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;