Merge branch 'next' into enhance-connection-more
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { Transform, XYPosition, SnapGrid, Dimensions, NodeOrigin, Handle, Position } from '../types';
|
||||
import type { Transform, XYPosition, SnapGrid, Dimensions, Position, Handle } from '../types';
|
||||
import { snapPosition, pointToRendererPoint } from './general';
|
||||
|
||||
export type GetPointerPositionParams = {
|
||||
@@ -63,8 +63,7 @@ export const getHandleBounds = (
|
||||
nodeElement: HTMLDivElement,
|
||||
nodeBounds: DOMRect,
|
||||
zoom: number,
|
||||
nodeId: string,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
nodeId: string
|
||||
): Handle[] | null => {
|
||||
const handles = nodeElement.querySelectorAll(`.${type}`);
|
||||
|
||||
@@ -72,14 +71,7 @@ export const getHandleBounds = (
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlesArray = Array.from(handles) as HTMLDivElement[];
|
||||
|
||||
const nodeOffset = {
|
||||
x: nodeBounds.left + nodeBounds.width * nodeOrigin[0],
|
||||
y: nodeBounds.top + nodeBounds.height * nodeOrigin[1],
|
||||
};
|
||||
|
||||
return handlesArray.map((handle): Handle => {
|
||||
return Array.from(handles).map((handle): Handle => {
|
||||
const handleBounds = handle.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
@@ -87,9 +79,9 @@ export const getHandleBounds = (
|
||||
type,
|
||||
nodeId,
|
||||
position: handle.getAttribute('data-handlepos') as unknown as Position,
|
||||
x: (handleBounds.left - nodeOffset.x) / zoom,
|
||||
y: (handleBounds.top - nodeOffset.y) / zoom,
|
||||
...getDimensions(handle),
|
||||
x: (handleBounds.left - nodeBounds.left) / zoom,
|
||||
y: (handleBounds.top - nodeBounds.top) / zoom,
|
||||
...getDimensions(handle as HTMLDivElement),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
NodeLookup,
|
||||
} from '../types';
|
||||
import { type Viewport } from '../types';
|
||||
import { getNodePositionWithOrigin } from './graph';
|
||||
import { getNodePositionWithOrigin, isInternalNodeBase } from './graph';
|
||||
|
||||
export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max);
|
||||
|
||||
@@ -68,24 +68,28 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
|
||||
});
|
||||
|
||||
export const nodeToRect = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Rect => {
|
||||
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
|
||||
const { x, y } = isInternalNodeBase(node)
|
||||
? node.internals.positionAbsolute
|
||||
: getNodePositionWithOrigin(node, nodeOrigin);
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: node.measured?.width ?? node.width ?? 0,
|
||||
height: node.measured?.height ?? node.height ?? 0,
|
||||
width: node.measured?.width ?? node.width ?? node.initialWidth ?? 0,
|
||||
height: node.measured?.height ?? node.height ?? node.initialHeight ?? 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const nodeToBox = (node: InternalNodeBase | NodeBase, nodeOrigin: NodeOrigin = [0, 0]): Box => {
|
||||
const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute;
|
||||
const { x, y } = isInternalNodeBase(node)
|
||||
? node.internals.positionAbsolute
|
||||
: getNodePositionWithOrigin(node, nodeOrigin);
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
x2: x + (node.measured?.width ?? node.width ?? 0),
|
||||
y2: y + (node.measured?.height ?? node.height ?? 0),
|
||||
x2: x + (node.measured?.width ?? node.width ?? node.initialWidth ?? 0),
|
||||
y2: y + (node.measured?.height ?? node.height ?? node.initialHeight ?? 0),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -114,29 +118,6 @@ export const devWarn = (id: string, message: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getPositionWithOrigin = ({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
origin = [0, 0],
|
||||
}: {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
origin?: NodeOrigin;
|
||||
}): XYPosition => {
|
||||
if (!width || !height || origin[0] < 0 || origin[1] < 0 || origin[0] > 1 || origin[1] > 1) {
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
return {
|
||||
x: x - width * origin[0],
|
||||
y: y - height * origin[1],
|
||||
};
|
||||
};
|
||||
|
||||
export const snapPosition = (position: XYPosition, snapGrid: SnapGrid = [1, 1]): XYPosition => {
|
||||
return {
|
||||
x: snapGrid[0] * Math.round(position.x / snapGrid[0]),
|
||||
@@ -239,9 +220,10 @@ export function nodeHasDimensions<NodeType extends NodeBase = NodeBase>(node: No
|
||||
*/
|
||||
export function evaluateAbsolutePosition(
|
||||
position: XYPosition,
|
||||
dimensions: { width?: number; height?: number } = { width: 0, height: 0 },
|
||||
parentId: string,
|
||||
nodeLookup: NodeLookup,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
nodeOrigin: NodeOrigin
|
||||
): XYPosition {
|
||||
let nextParentId: string | undefined = parentId;
|
||||
const positionAbsolute = { ...position };
|
||||
@@ -252,10 +234,8 @@ export function evaluateAbsolutePosition(
|
||||
|
||||
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.position.x - xOffset;
|
||||
positionAbsolute.y += parent.position.y - yOffset;
|
||||
positionAbsolute.x += parent.internals.positionAbsolute.x - (dimensions.width ?? 0) * origin[0];
|
||||
positionAbsolute.y += parent.internals.positionAbsolute.y - (dimensions.height ?? 0) * origin[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,25 +106,15 @@ export const getIncomers = <NodeType extends NodeBase = NodeBase, EdgeType exten
|
||||
return nodes.filter((n) => incomersIds.has(n.id));
|
||||
};
|
||||
|
||||
export const getNodePositionWithOrigin = (
|
||||
node: InternalNodeBase | NodeBase,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
): { position: XYPosition; positionAbsolute: XYPosition } => {
|
||||
export const getNodePositionWithOrigin = (node: NodeBase, nodeOrigin: NodeOrigin = [0, 0]): XYPosition => {
|
||||
const { width, height } = getNodeDimensions(node);
|
||||
const positionAbsolute = 'internals' in node ? node.internals.positionAbsolute : node.position;
|
||||
const origin = node.origin || nodeOrigin;
|
||||
const origin = node.origin ?? nodeOrigin;
|
||||
const offsetX = width * origin[0];
|
||||
const offsetY = height * origin[1];
|
||||
|
||||
return {
|
||||
position: {
|
||||
x: node.position.x - offsetX,
|
||||
y: node.position.y - offsetY,
|
||||
},
|
||||
positionAbsolute: {
|
||||
x: positionAbsolute.x - offsetX,
|
||||
y: positionAbsolute.y - offsetY,
|
||||
},
|
||||
x: node.position.x - offsetX,
|
||||
y: node.position.y - offsetY,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -157,7 +147,6 @@ export const getNodesBounds = (nodes: NodeBase[], params: GetNodesBoundsParams =
|
||||
};
|
||||
|
||||
export type GetInternalNodesBoundsParams<NodeType> = {
|
||||
nodeOrigin?: NodeOrigin;
|
||||
useRelativePosition?: boolean;
|
||||
filter?: (node: NodeType) => boolean;
|
||||
};
|
||||
@@ -168,9 +157,7 @@ export type GetInternalNodesBoundsParams<NodeType> = {
|
||||
*/
|
||||
export const getInternalNodesBounds = <NodeType extends InternalNodeBase | NodeDragItem>(
|
||||
nodeLookup: Map<string, NodeType>,
|
||||
params: GetInternalNodesBoundsParams<NodeType> = {
|
||||
nodeOrigin: [0, 0],
|
||||
}
|
||||
params: GetInternalNodesBoundsParams<NodeType> = {}
|
||||
): Rect => {
|
||||
if (nodeLookup.size === 0) {
|
||||
return { x: 0, y: 0, width: 0, height: 0 };
|
||||
@@ -179,8 +166,8 @@ export const getInternalNodesBounds = <NodeType extends InternalNodeBase | NodeD
|
||||
let box = { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity };
|
||||
|
||||
nodeLookup.forEach((node) => {
|
||||
if (params.filter == undefined || params.filter(node)) {
|
||||
const nodeBox = nodeToBox(node as InternalNodeBase, params.nodeOrigin);
|
||||
if (params.filter === undefined || params.filter(node)) {
|
||||
const nodeBox = nodeToBox(node as InternalNodeBase);
|
||||
box = getBoundsOfBoxes(box, nodeBox);
|
||||
}
|
||||
});
|
||||
@@ -194,8 +181,7 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
|
||||
[tx, ty, tScale]: Transform = [0, 0, 1],
|
||||
partially = false,
|
||||
// set excludeNonSelectableNodes if you want to pay attention to the nodes "selectable" attribute
|
||||
excludeNonSelectableNodes = false,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
excludeNonSelectableNodes = false
|
||||
): InternalNodeBase<NodeType>[] => {
|
||||
const paneRect = {
|
||||
...pointToRendererPoint(rect, [tx, ty, tScale]),
|
||||
@@ -214,7 +200,7 @@ export const getNodesInside = <NodeType extends NodeBase = NodeBase>(
|
||||
continue;
|
||||
}
|
||||
|
||||
const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node, nodeOrigin));
|
||||
const overlappingArea = getOverlappingArea(paneRect, nodeToRect(node));
|
||||
const notInitialized = width === null || height === null;
|
||||
|
||||
const partiallyVisible = partially && overlappingArea > 0;
|
||||
@@ -248,22 +234,22 @@ export const getConnectedEdges = <NodeType extends NodeBase = NodeBase, EdgeType
|
||||
};
|
||||
|
||||
export function fitView<Params extends FitViewParamsBase<NodeBase>, Options extends FitViewOptionsBase<NodeBase>>(
|
||||
{ nodeLookup, width, height, panZoom, minZoom, maxZoom, nodeOrigin = [0, 0] }: Params,
|
||||
{ nodeLookup, width, height, panZoom, minZoom, maxZoom }: Params,
|
||||
options?: Options
|
||||
) {
|
||||
const filteredNodes: InternalNodeBase[] = [];
|
||||
const filteredNodes: Map<string, InternalNodeBase> = new Map();
|
||||
const optionNodeIds = options?.nodes ? new Set(options.nodes.map((node) => node.id)) : null;
|
||||
|
||||
nodeLookup.forEach((n) => {
|
||||
const isVisible = n.measured.width && n.measured.height && (options?.includeHiddenNodes || !n.hidden);
|
||||
|
||||
if (isVisible && (!optionNodeIds || optionNodeIds.has(n.id))) {
|
||||
filteredNodes.push(n);
|
||||
filteredNodes.set(n.id, n);
|
||||
}
|
||||
});
|
||||
|
||||
if (filteredNodes.length > 0) {
|
||||
const bounds = getNodesBounds(filteredNodes, { nodeOrigin });
|
||||
if (filteredNodes.size > 0) {
|
||||
const bounds = getInternalNodesBounds(filteredNodes);
|
||||
|
||||
const viewport = getViewportForBounds(
|
||||
bounds,
|
||||
@@ -323,9 +309,8 @@ export function calculateNodePosition<NodeType extends NodeBase>({
|
||||
}): { position: XYPosition; positionAbsolute: XYPosition } {
|
||||
const node = nodeLookup.get(nodeId)!;
|
||||
const parentNode = node.parentId ? nodeLookup.get(node.parentId) : undefined;
|
||||
const { x: parentX, y: parentY } = parentNode
|
||||
? getNodePositionWithOrigin(parentNode, parentNode.origin || nodeOrigin).positionAbsolute
|
||||
: { x: 0, y: 0 };
|
||||
const { x: parentX, y: parentY } = parentNode ? parentNode.internals.positionAbsolute : { x: 0, y: 0 };
|
||||
const origin = node.origin ?? nodeOrigin;
|
||||
|
||||
let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
|
||||
|
||||
@@ -339,13 +324,9 @@ export function calculateNodePosition<NodeType extends NodeBase>({
|
||||
const parentHeight = parentNode.measured.height;
|
||||
|
||||
if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
|
||||
const currNodeOrigin = node.origin || nodeOrigin;
|
||||
const extentX = parentX + nodeWidth * currNodeOrigin[0];
|
||||
const extentY = parentY + nodeHeight * currNodeOrigin[1];
|
||||
|
||||
currentExtent = [
|
||||
[extentX, extentY],
|
||||
[extentX + parentWidth - nodeWidth, extentY + parentHeight - nodeHeight],
|
||||
[parentX, parentY],
|
||||
[parentX + parentWidth - nodeWidth, parentY + parentHeight - nodeHeight],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -362,8 +343,9 @@ export function calculateNodePosition<NodeType extends NodeBase>({
|
||||
|
||||
return {
|
||||
position: {
|
||||
x: positionAbsolute.x - parentX,
|
||||
y: positionAbsolute.y - parentY,
|
||||
// TODO: is there a better way to do this?
|
||||
x: positionAbsolute.x - parentX + node.measured.width! * origin[0],
|
||||
y: positionAbsolute.y - parentY + node.measured.height! * origin[1],
|
||||
},
|
||||
positionAbsolute,
|
||||
};
|
||||
|
||||
+120
-103
@@ -6,7 +6,6 @@ import {
|
||||
PanZoomInstance,
|
||||
Transform,
|
||||
XYPosition,
|
||||
XYZPosition,
|
||||
ConnectionLookup,
|
||||
EdgeBase,
|
||||
EdgeLookup,
|
||||
@@ -22,48 +21,28 @@ import { getBoundsOfRects, getNodeDimensions, isNumeric, nodeToRect } from './ge
|
||||
import { getNodePositionWithOrigin } from './graph';
|
||||
import { ParentExpandChild } from './types';
|
||||
|
||||
const defaultOptions = {
|
||||
nodeOrigin: [0, 0] as NodeOrigin,
|
||||
elevateNodesOnSelect: true,
|
||||
defaults: {},
|
||||
};
|
||||
const adoptUserNodesDefaultOptions = {
|
||||
...defaultOptions,
|
||||
checkEquality: true,
|
||||
};
|
||||
|
||||
export function updateAbsolutePositions<NodeType extends NodeBase>(
|
||||
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||
options: UpdateNodesOptions<NodeType> = {
|
||||
nodeOrigin: [0, 0] as NodeOrigin,
|
||||
elevateNodesOnSelect: true,
|
||||
defaults: {},
|
||||
}
|
||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||
options?: UpdateNodesOptions<NodeType>
|
||||
) {
|
||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||
|
||||
for (const [, node] of nodeLookup) {
|
||||
const parentId = node.parentId;
|
||||
|
||||
if (!parentId) {
|
||||
const _options = { ...defaultOptions, ...options };
|
||||
for (const node of nodeLookup.values()) {
|
||||
if (!node.parentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!nodeLookup.has(parentId)) {
|
||||
throw new Error(`Parent node ${parentId} not found`);
|
||||
}
|
||||
|
||||
const parentNode = nodeLookup.get(parentId);
|
||||
const { x, y, z } = calculateXYZPosition(
|
||||
node,
|
||||
nodeLookup,
|
||||
{
|
||||
...node.position,
|
||||
z: (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0),
|
||||
},
|
||||
parentNode?.origin ?? options.nodeOrigin
|
||||
);
|
||||
|
||||
const currPosition = node.internals.positionAbsolute;
|
||||
const positionChanged = x !== currPosition.x || y !== currPosition.y;
|
||||
|
||||
if (positionChanged || z !== node.internals.z) {
|
||||
node.internals = {
|
||||
...node.internals,
|
||||
positionAbsolute: positionChanged ? { x, y } : currPosition,
|
||||
z,
|
||||
};
|
||||
}
|
||||
updateChildPosition(node, nodeLookup, parentLookup, _options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,38 +55,33 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
|
||||
|
||||
export function adoptUserNodes<NodeType extends NodeBase>(
|
||||
nodes: NodeType[],
|
||||
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||
parentLookup: Map<string, InternalNodeBase<NodeType>[]>,
|
||||
options: UpdateNodesOptions<NodeType> = {
|
||||
nodeOrigin: [0, 0] as NodeOrigin,
|
||||
elevateNodesOnSelect: true,
|
||||
defaults: {},
|
||||
checkEquality: true,
|
||||
}
|
||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||
options?: UpdateNodesOptions<NodeType>
|
||||
) {
|
||||
const _options = { ...adoptUserNodesDefaultOptions, ...options };
|
||||
const tmpLookup = new Map(nodeLookup);
|
||||
nodeLookup.clear();
|
||||
parentLookup.clear();
|
||||
|
||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||
|
||||
nodes.forEach((userNode) => {
|
||||
for (const userNode of nodes) {
|
||||
let internalNode = tmpLookup.get(userNode.id);
|
||||
|
||||
if (options.checkEquality && userNode === internalNode?.internals.userNode) {
|
||||
if (_options.checkEquality && userNode === internalNode?.internals.userNode) {
|
||||
nodeLookup.set(userNode.id, internalNode);
|
||||
} else {
|
||||
internalNode = {
|
||||
...options.defaults,
|
||||
..._options.defaults,
|
||||
...userNode,
|
||||
measured: {
|
||||
width: userNode.measured?.width,
|
||||
height: userNode.measured?.height,
|
||||
},
|
||||
internals: {
|
||||
positionAbsolute: userNode.position,
|
||||
positionAbsolute: getNodePositionWithOrigin(userNode, _options.nodeOrigin),
|
||||
handleBounds: internalNode?.internals.handleBounds,
|
||||
z: (isNumeric(userNode.zIndex) ? userNode.zIndex : 0) + (userNode.selected ? selectedNodeZ : 0),
|
||||
z: calculateZ(userNode, selectedNodeZ),
|
||||
userNode,
|
||||
},
|
||||
};
|
||||
@@ -115,50 +89,75 @@ export function adoptUserNodes<NodeType extends NodeBase>(
|
||||
}
|
||||
|
||||
if (userNode.parentId) {
|
||||
const childNodes = parentLookup.get(userNode.parentId);
|
||||
if (childNodes) {
|
||||
childNodes.push(internalNode);
|
||||
} else {
|
||||
parentLookup.set(userNode.parentId, [internalNode]);
|
||||
}
|
||||
updateChildPosition(internalNode, nodeLookup, parentLookup, options);
|
||||
}
|
||||
});
|
||||
|
||||
if (parentLookup.size > 0) {
|
||||
updateAbsolutePositions(nodeLookup, options);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateXYZPosition<NodeType extends NodeBase>(
|
||||
node: NodeType,
|
||||
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
|
||||
result: XYZPosition,
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
): XYZPosition {
|
||||
if (!node.parentId) {
|
||||
return result;
|
||||
function updateChildPosition<NodeType extends NodeBase>(
|
||||
node: InternalNodeBase<NodeType>,
|
||||
nodeLookup: NodeLookup<InternalNodeBase<NodeType>>,
|
||||
parentLookup: ParentLookup<InternalNodeBase<NodeType>>,
|
||||
options?: UpdateNodesOptions<NodeType>
|
||||
) {
|
||||
const _options = { ...defaultOptions, ...options };
|
||||
|
||||
const parentId = node.parentId!;
|
||||
const parentNode = nodeLookup.get(parentId);
|
||||
if (!parentNode) {
|
||||
throw new Error(`Parent node ${parentId} not found`);
|
||||
}
|
||||
|
||||
const parent = nodeLookup.get(node.parentId)!;
|
||||
const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin).position;
|
||||
// update the parentLookup
|
||||
const childNodes = parentLookup.get(parentId);
|
||||
if (childNodes) {
|
||||
childNodes.set(node.id, node);
|
||||
} else {
|
||||
parentLookup.set(parentId, new Map([[node.id, node]]));
|
||||
}
|
||||
|
||||
return calculateXYZPosition(
|
||||
parent,
|
||||
nodeLookup,
|
||||
{
|
||||
x: (result.x ?? 0) + parentPosition.x,
|
||||
y: (result.y ?? 0) + parentPosition.y,
|
||||
z: (parent.internals.z ?? 0) > (result.z ?? 0) ? parent.internals.z ?? 0 : result.z ?? 0,
|
||||
},
|
||||
parent.origin || nodeOrigin
|
||||
);
|
||||
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
|
||||
|
||||
const { x, y, z } = calculateChildXYZ(node, parentNode, _options.nodeOrigin!, selectedNodeZ);
|
||||
|
||||
const currPosition = node.internals.positionAbsolute;
|
||||
const positionChanged = x !== currPosition.x || y !== currPosition.y;
|
||||
|
||||
if (positionChanged || z !== node.internals.z) {
|
||||
node.internals = {
|
||||
...node.internals,
|
||||
positionAbsolute: positionChanged ? { x, y } : currPosition,
|
||||
z,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function calculateZ(node: NodeBase, selectedNodeZ: number) {
|
||||
return (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0);
|
||||
}
|
||||
|
||||
function calculateChildXYZ<NodeType extends NodeBase>(
|
||||
childNode: InternalNodeBase<NodeType>,
|
||||
parentNode: InternalNodeBase<NodeType>,
|
||||
nodeOrigin: NodeOrigin,
|
||||
selectedNodeZ: number
|
||||
) {
|
||||
const position = getNodePositionWithOrigin(childNode, nodeOrigin);
|
||||
const childZ = calculateZ(childNode, selectedNodeZ);
|
||||
const parentZ = parentNode.internals.z ?? 0;
|
||||
|
||||
return {
|
||||
x: parentNode.internals.positionAbsolute.x + position.x,
|
||||
y: parentNode.internals.positionAbsolute.y + position.y,
|
||||
z: parentZ > childZ ? parentZ : childZ,
|
||||
};
|
||||
}
|
||||
|
||||
export function handleExpandParent(
|
||||
children: ParentExpandChild[],
|
||||
nodeLookup: NodeLookup,
|
||||
parentLookup: ParentLookup,
|
||||
nodeOrigin?: NodeOrigin
|
||||
nodeOrigin: NodeOrigin = [0, 0]
|
||||
): (NodeDimensionChange | NodePositionChange)[] {
|
||||
const changes: (NodeDimensionChange | NodePositionChange)[] = [];
|
||||
const parentExpansions = new Map<string, { expandedRect: Rect; parent: InternalNodeBase }>();
|
||||
@@ -170,36 +169,45 @@ export function handleExpandParent(
|
||||
continue;
|
||||
}
|
||||
|
||||
const parentRect =
|
||||
parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent, parent.origin ?? nodeOrigin);
|
||||
const parentRect = parentExpansions.get(child.parentId)?.expandedRect ?? nodeToRect(parent);
|
||||
const expandedRect = getBoundsOfRects(parentRect, child.rect);
|
||||
|
||||
parentExpansions.set(child.parentId, { expandedRect, parent });
|
||||
}
|
||||
|
||||
if (parentExpansions.size > 0) {
|
||||
parentExpansions.forEach(({ expandedRect, parent }, parentId) => {
|
||||
// determine the position & dimensions of the parent
|
||||
const { position } = getNodePositionWithOrigin(parent, parent.origin);
|
||||
const positionAbsolute = parent.internals.positionAbsolute;
|
||||
const dimensions = getNodeDimensions(parent);
|
||||
const origin = parent.origin ?? nodeOrigin;
|
||||
|
||||
// determine how much the parent expands by moving the position
|
||||
const xChange = expandedRect.x < position.x ? Math.round(Math.abs(position.x - expandedRect.x)) : 0;
|
||||
const yChange = expandedRect.y < position.y ? Math.round(Math.abs(position.y - expandedRect.y)) : 0;
|
||||
// determine how much the parent expands in width and position
|
||||
const xChange =
|
||||
expandedRect.x < positionAbsolute.x ? Math.round(Math.abs(positionAbsolute.x - expandedRect.x)) : 0;
|
||||
const yChange =
|
||||
expandedRect.y < positionAbsolute.y ? Math.round(Math.abs(positionAbsolute.y - expandedRect.y)) : 0;
|
||||
|
||||
if (xChange > 0 || yChange > 0) {
|
||||
const newWidth = Math.max(dimensions.width, Math.round(expandedRect.width));
|
||||
const newHeight = Math.max(dimensions.height, Math.round(expandedRect.height));
|
||||
|
||||
const widthChange = (newWidth - dimensions.width) * origin[0];
|
||||
const heightChange = (newHeight - dimensions.height) * origin[1];
|
||||
|
||||
// We need to correct the position of the parent node if the origin is not [0,0]
|
||||
if (xChange > 0 || yChange > 0 || widthChange || heightChange) {
|
||||
changes.push({
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: position.x - xChange,
|
||||
y: position.y - yChange,
|
||||
x: parent.position.x - xChange + widthChange,
|
||||
y: parent.position.y - yChange + heightChange,
|
||||
},
|
||||
});
|
||||
|
||||
// We move all child nodes in the oppsite direction
|
||||
// so the x,y changes of the parent do not move the children
|
||||
const childNodes = parentLookup.get(parentId);
|
||||
childNodes?.forEach((childNode) => {
|
||||
parentLookup.get(parentId)?.forEach((childNode) => {
|
||||
if (!children.some((child) => child.id === childNode.id)) {
|
||||
changes.push({
|
||||
id: childNode.id,
|
||||
@@ -213,14 +221,15 @@ export function handleExpandParent(
|
||||
});
|
||||
}
|
||||
|
||||
if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height) {
|
||||
// We need to correct the dimensions of the parent node if the origin is not [0,0]
|
||||
if (dimensions.width < expandedRect.width || dimensions.height < expandedRect.height || xChange || yChange) {
|
||||
changes.push({
|
||||
id: parentId,
|
||||
type: 'dimensions',
|
||||
setAttributes: true,
|
||||
dimensions: {
|
||||
width: Math.max(dimensions.width, Math.round(expandedRect.width)),
|
||||
height: Math.max(dimensions.height, Math.round(expandedRect.height)),
|
||||
width: newWidth + (xChange ? origin[0] * xChange - widthChange : 0),
|
||||
height: newHeight + (yChange ? origin[1] * yChange - heightChange : 0),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -250,16 +259,19 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
||||
// in this array we collect nodes, that might trigger changes (like expanding parent)
|
||||
const parentExpandChildren: ParentExpandChild[] = [];
|
||||
|
||||
updates.forEach((update) => {
|
||||
for (const update of updates.values()) {
|
||||
const node = nodeLookup.get(update.id);
|
||||
if (!node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node?.hidden) {
|
||||
if (node.hidden) {
|
||||
node.internals = {
|
||||
...node.internals,
|
||||
handleBounds: undefined,
|
||||
};
|
||||
updatedInternals = true;
|
||||
} else if (node) {
|
||||
} else {
|
||||
const dimensions = getDimensions(update.nodeElement);
|
||||
const dimensionChanged = node.measured.width !== dimensions.width || node.measured.height !== dimensions.height;
|
||||
const doUpdate = !!(
|
||||
@@ -270,14 +282,19 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
||||
|
||||
if (doUpdate) {
|
||||
const nodeBounds = update.nodeElement.getBoundingClientRect();
|
||||
|
||||
node.measured = dimensions;
|
||||
node.internals = {
|
||||
...node.internals,
|
||||
positionAbsolute: getNodePositionWithOrigin(node, nodeOrigin),
|
||||
handleBounds: {
|
||||
source: getHandleBounds('source', update.nodeElement, nodeBounds, zoom, node.id, node.origin || nodeOrigin),
|
||||
target: getHandleBounds('target', update.nodeElement, nodeBounds, zoom, node.id, node.origin || nodeOrigin),
|
||||
source: getHandleBounds('source', update.nodeElement, nodeBounds, zoom, node.id),
|
||||
target: getHandleBounds('target', update.nodeElement, nodeBounds, zoom, node.id),
|
||||
},
|
||||
};
|
||||
if (node.parentId) {
|
||||
updateChildPosition(node, nodeLookup, parentLookup, { nodeOrigin });
|
||||
}
|
||||
|
||||
updatedInternals = true;
|
||||
|
||||
@@ -298,7 +315,7 @@ export function updateNodeInternals<NodeType extends InternalNodeBase>(
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (parentExpandChildren.length > 0) {
|
||||
const parentExpandChanges = handleExpandParent(parentExpandChildren, nodeLookup, parentLookup, nodeOrigin);
|
||||
|
||||
Reference in New Issue
Block a user