Merge branch 'next' into refactor/edge-rendering

This commit is contained in:
moklick
2023-12-17 11:04:32 +01:00
187 changed files with 5192 additions and 2186 deletions
+53
View File
@@ -0,0 +1,53 @@
import { Connection } from '../types';
/**
* @internal
*/
export function areConnectionMapsEqual(a?: Map<string, Connection>, b?: Map<string, Connection>) {
if (!a && !b) {
return true;
}
if (!a || !b || a.size !== b.size) {
return false;
}
if (!a.size && !b.size) {
return true;
}
for (const key of a.keys()) {
if (!b.has(key)) {
return false;
}
}
return true;
}
/**
* We call the callback for all connections in a that are not in b
*
* @internal
*/
export function handleConnectionChange(
a: Map<string, Connection>,
b: Map<string, Connection>,
cb?: (diff: Connection[]) => void
) {
if (!cb) {
return;
}
const diff: Connection[] = [];
a.forEach((connection, key) => {
if (!b?.has(key)) {
diff.push(connection);
}
});
if (diff.length) {
cb(diff);
}
}
+2
View File
@@ -73,6 +73,8 @@ 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],
+9 -1
View File
@@ -75,7 +75,7 @@ export function isEdgeVisible({ sourceNode, targetNode, width, height, transform
}
const getEdgeId = ({ source, sourceHandle, target, targetHandle }: Connection | EdgeBase): string =>
`xyflow__edge-${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
`xy-edge__${source}${sourceHandle || ''}-${target}${targetHandle || ''}`;
const connectionExists = (edge: EdgeBase, edges: EdgeBase[]) => {
return edges.some(
@@ -111,6 +111,14 @@ export const addEdgeBase = <EdgeType extends EdgeBase>(
return edges;
}
if (edge.sourceHandle === null) {
delete edge.sourceHandle;
}
if (edge.targetHandle === null) {
delete edge.targetHandle;
}
return edges.concat(edge);
};
+6 -6
View File
@@ -88,20 +88,20 @@ function toHandleBounds(handles?: NodeHandle[]) {
function getHandleDataByNode(node?: NodeBase): [Rect, NodeHandleBounds | null, boolean] {
const handleBounds = node?.[internalsSymbol]?.handleBounds || toHandleBounds(node?.handles) || null;
const nodeWidth = node?.width || node?.size?.width;
const nodeHeight = node?.height || node?.size?.height;
const nodeWidth = node?.computed?.width || node?.width;
const nodeHeight = node?.computed?.height || node?.height;
const isValid =
handleBounds &&
nodeWidth &&
nodeHeight &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
typeof node?.computed?.positionAbsolute?.x !== 'undefined' &&
typeof node?.computed?.positionAbsolute?.y !== 'undefined';
return [
{
x: node?.positionAbsolute?.x || 0,
y: node?.positionAbsolute?.y || 0,
x: node?.computed?.positionAbsolute?.x || 0,
y: node?.computed?.positionAbsolute?.y || 0,
width: nodeWidth || 0,
height: nodeHeight || 0,
},
+4 -4
View File
@@ -64,8 +64,8 @@ export const nodeToRect = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rec
return {
...positionAbsolute,
width: node.width || 0,
height: node.height || 0,
width: node.computed?.width ?? node.width ?? 0,
height: node.computed?.height ?? node.height ?? 0,
};
};
@@ -74,8 +74,8 @@ export const nodeToBox = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box
return {
...positionAbsolute,
x2: positionAbsolute.x + (node.width || 0),
y2: positionAbsolute.y + (node.height || 0),
x2: positionAbsolute.x + (node.computed?.width ?? node.width ?? 0),
y2: positionAbsolute.y + (node.computed?.height ?? node.height ?? 0),
};
};
+25 -24
View File
@@ -11,7 +11,6 @@ import {
getViewportForBounds,
} from './general';
import {
type Connection,
type Transform,
type XYPosition,
type Rect,
@@ -26,13 +25,11 @@ import {
} from '../types';
import { errorMessages } from '../constants';
export const isEdgeBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
element: NodeType | Connection | EdgeType
): element is EdgeType => 'id' in element && 'source' in element && 'target' in element;
export const isEdgeBase = <EdgeType extends EdgeBase = EdgeBase>(element: any): element is EdgeType =>
'id' in element && 'source' in element && 'target' in element;
export const isNodeBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
element: NodeType | Connection | EdgeType
): element is NodeType => 'id' in element && !('source' in element) && !('target' in element);
export const isNodeBase = <NodeType extends NodeBase = NodeBase>(element: any): element is NodeType =>
'id' in element && !('source' in element) && !('target' in element);
export const getOutgoersBase = <NodeType extends NodeBase = NodeBase, EdgeType extends EdgeBase = EdgeBase>(
node: NodeType | { id: string },
@@ -86,8 +83,8 @@ export const getNodePositionWithOrigin = (
};
}
const offsetX = (node.width ?? 0) * nodeOrigin[0];
const offsetY = (node.height ?? 0) * nodeOrigin[1];
const offsetX = (node.computed?.width ?? node.width ?? 0) * nodeOrigin[0];
const offsetY = (node.computed?.height ?? node.height ?? 0) * nodeOrigin[1];
const position: XYPosition = {
x: node.position.x - offsetX,
@@ -96,10 +93,10 @@ export const getNodePositionWithOrigin = (
return {
...position,
positionAbsolute: node.positionAbsolute
positionAbsolute: node.computed?.positionAbsolute
? {
x: node.positionAbsolute.x - offsetX,
y: node.positionAbsolute.y - offsetY,
x: node.computed.positionAbsolute.x - offsetX,
y: node.computed.positionAbsolute.y - offsetY,
}
: position,
};
@@ -118,8 +115,8 @@ export const getNodesBounds = (nodes: NodeBase[], nodeOrigin: NodeOrigin = [0, 0
rectToBox({
x,
y,
width: node.width || 0,
height: node.height || 0,
width: node.computed?.width ?? node.width ?? 0,
height: node.computed?.height ?? node.height ?? 0,
})
);
},
@@ -145,17 +142,19 @@ export const getNodesInside = <NodeType extends NodeBase>(
};
const visibleNodes = nodes.reduce<NodeType[]>((res, node) => {
const { width, height, selectable = true, hidden = false } = node;
const { computed, selectable = true, hidden = false } = node;
const width = computed?.width ?? node.width ?? null;
const height = computed?.height ?? node.height ?? null;
if ((excludeNonSelectableNodes && !selectable) || hidden) {
return res;
}
const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node, nodeOrigin));
const notInitialized = width === undefined || height === undefined || width === null || height === null;
const notInitialized = width === null || height === null;
const partiallyVisible = partially && overlappingArea > 0;
const area = (width || 0) * (height || 0);
const area = (width ?? 0) * (height ?? 0);
const isVisible = notInitialized || partiallyVisible || overlappingArea >= area;
if (isVisible || node.dragging) {
@@ -185,7 +184,7 @@ export function fitView<Params extends FitViewParamsBase<NodeBase>, Options exte
options?: Options
) {
const filteredNodes = nodes.filter((n) => {
const isVisible = n.width && n.height && (options?.includeHiddenNodes || !n.hidden);
const isVisible = n.computed?.width && n.computed?.height && (options?.includeHiddenNodes || !n.hidden);
if (options?.nodes?.length) {
return isVisible && options?.nodes.some((optionNode) => optionNode.id === n.id);
@@ -218,7 +217,7 @@ function clampNodeExtent(node: NodeDragItem | NodeBase, extent?: CoordinateExten
if (!extent || extent === 'parent') {
return extent;
}
return [extent[0], [extent[1][0] - (node.width || 0), extent[1][1] - (node.height || 0)]];
return [extent[0], [extent[1][0] - (node.computed?.width ?? 0), extent[1][1] - (node.computed?.height ?? 0)]];
}
export function calcNextPosition<NodeType extends NodeBase>(
@@ -242,16 +241,18 @@ export function calcNextPosition<NodeType extends NodeBase>(
}
if (node.extent === 'parent' && !node.expandParent) {
if (node.parentNode && node.width && node.height) {
const nodeWidth = node.computed?.width;
const nodeHeight = node.computed?.height;
if (node.parentNode && nodeWidth && nodeHeight) {
const currNodeOrigin = node.origin || nodeOrigin;
currentExtent =
parentNode && isNumeric(parentNode.width) && isNumeric(parentNode.height)
parentNode && isNumeric(parentNode.computed?.width) && isNumeric(parentNode.computed?.height)
? [
[parentPos.x + node.width * currNodeOrigin[0], parentPos.y + node.height * currNodeOrigin[1]],
[parentPos.x + nodeWidth * currNodeOrigin[0], parentPos.y + nodeHeight * currNodeOrigin[1]],
[
parentPos.x + parentNode.width - node.width + node.width * currNodeOrigin[0],
parentPos.y + parentNode.height - node.height + node.height * currNodeOrigin[1],
parentPos.x + (parentNode.computed?.width ?? 0) - nodeWidth + nodeWidth * currNodeOrigin[0],
parentPos.y + (parentNode.computed?.height ?? 0) - nodeHeight + nodeHeight * currNodeOrigin[1],
],
]
: currentExtent;
+3
View File
@@ -1,6 +1,9 @@
export * from './connections';
export * from './dom';
export * from './edges';
export * from './graph';
export * from './general';
export * from './marker';
export * from './node-toolbar';
export * from './store';
export * from './types';
+49
View File
@@ -0,0 +1,49 @@
import { Position, type Rect, type Viewport, type Align } from '../';
export function getNodeToolbarTransform(
nodeRect: Rect,
viewport: Viewport,
position: Position,
offset: number,
align: Align
): string {
let alignmentOffset = 0.5;
if (align === 'start') {
alignmentOffset = 0;
} else if (align === 'end') {
alignmentOffset = 1;
}
// position === Position.Top
// we set the x any y position of the toolbar based on the nodes position
let pos = [
(nodeRect.x + nodeRect.width * alignmentOffset) * viewport.zoom + viewport.x,
nodeRect.y * viewport.zoom + viewport.y - offset,
];
// and than shift it based on the alignment. The shift values are in %.
let shift = [-100 * alignmentOffset, -100];
switch (position) {
case Position.Right:
pos = [
(nodeRect.x + nodeRect.width) * viewport.zoom + viewport.x + offset,
(nodeRect.y + nodeRect.height * alignmentOffset) * viewport.zoom + viewport.y,
];
shift = [0, -100 * alignmentOffset];
break;
case Position.Bottom:
pos[1] = (nodeRect.y + nodeRect.height) * viewport.zoom + viewport.y + offset;
shift[1] = 0;
break;
case Position.Left:
pos = [
nodeRect.x * viewport.zoom + viewport.x - offset,
(nodeRect.y + nodeRect.height * alignmentOffset) * viewport.zoom + viewport.y,
];
shift = [-100, -100 * alignmentOffset];
break;
}
return `translate(${pos[0]}px, ${pos[1]}px) translate(${shift[0]}%, ${shift[1]}%)`;
}
+47 -12
View File
@@ -9,6 +9,8 @@ import {
Transform,
XYPosition,
XYZPosition,
ConnectionLookup,
EdgeBase,
} from '../types';
import { getDimensions, getHandleBounds } from './dom';
import { isNumeric } from './general';
@@ -40,7 +42,7 @@ export function updateAbsolutePositions<NodeType extends NodeBase>(
parentNode?.origin || nodeOrigin
);
node.positionAbsolute = {
node.computed!.positionAbsolute = {
x,
y,
};
@@ -62,7 +64,7 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
defaults?: Partial<NodeType>;
};
export function updateNodes<NodeType extends NodeBase>(
export function adoptUserProvidedNodes<NodeType extends NodeBase>(
nodes: NodeType[],
nodeLookup: Map<string, NodeType>,
options: UpdateNodesOptions<NodeType> = {
@@ -71,17 +73,26 @@ export function updateNodes<NodeType extends NodeBase>(
defaults: {},
}
): NodeType[] {
const tmpLookup = new Map(nodeLookup);
nodeLookup.clear();
const parentNodes: ParentNodes = {};
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
const nextNodes = nodes.map((n) => {
const currentStoreNode = nodeLookup.get(n.id);
const currentStoreNode = tmpLookup.get(n.id);
if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode) {
nodeLookup.set(n.id, currentStoreNode);
return currentStoreNode;
}
const node: NodeType = {
...options.defaults,
...n,
positionAbsolute: n.position,
width: n.width || currentStoreNode?.width,
height: n.height || currentStoreNode?.height,
computed: {
positionAbsolute: n.position,
width: n.computed?.width || currentStoreNode?.computed?.width,
height: n.computed?.height || currentStoreNode?.computed?.height,
},
};
const z = (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0);
const currInternals = n?.[internalsSymbol] || currentStoreNode?.[internalsSymbol];
@@ -95,6 +106,7 @@ export function updateNodes<NodeType extends NodeBase>(
value: {
handleBounds: currInternals?.handleBounds,
z,
userProvidedNode: n,
},
});
@@ -135,14 +147,14 @@ function calculateXYZPosition<NodeType extends NodeBase>(
);
}
export function updateNodeDimensions(
export function updateNodeDimensions<NodeType extends NodeBase>(
updates: Map<string, NodeDimensionUpdate>,
nodes: NodeBase[],
nodeLookup: Map<string, NodeBase>,
nodes: NodeType[],
nodeLookup: Map<string, NodeType>,
domNode: HTMLElement | null,
nodeOrigin?: NodeOrigin,
onUpdate?: (id: string, dimensions: Dimensions) => void
): NodeBase[] | null {
): NodeType[] | null {
const viewportNode = domNode?.querySelector('.xyflow__viewport');
if (!viewportNode) {
@@ -160,7 +172,7 @@ export function updateNodeDimensions(
const doUpdate = !!(
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width || node.height !== dimensions.height || update.forceUpdate)
(node.computed?.width !== dimensions.width || node.computed?.height !== dimensions.height || update.forceUpdate)
);
if (doUpdate) {
@@ -168,7 +180,10 @@ export function updateNodeDimensions(
const newNode = {
...node,
...dimensions,
computed: {
...node.computed,
...dimensions,
},
[internalsSymbol]: {
...node[internalsSymbol],
handleBounds: {
@@ -228,3 +243,23 @@ export function panBy({
return transformChanged;
}
export function updateConnectionLookup(lookup: ConnectionLookup, edges: EdgeBase[]) {
lookup.clear();
edges.forEach(({ source, target, sourceHandle = null, targetHandle = null }) => {
if (source && target) {
const sourceKey = `${source}-source-${sourceHandle}`;
const targetKey = `${target}-target-${targetHandle}`;
const prevSource = lookup.get(sourceKey) || new Map();
const prevTarget = lookup.get(targetKey) || new Map();
const connection = { source, target, sourceHandle, targetHandle };
lookup.set(sourceKey, prevSource.set(`${target}-${targetHandle}`, connection));
lookup.set(targetKey, prevTarget.set(`${source}-${sourceHandle}`, connection));
}
});
return lookup;
}