@xyflow/system 0.0.40 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -192,19 +192,31 @@ const getNodePositionWithOrigin = (node, nodeOrigin = [0, 0]) => {
192
192
  };
193
193
  };
194
194
  /**
195
- * Determines a bounding box that contains all given nodes in an array
195
+ * Internal function for determining a bounding box that contains all given nodes in an array.
196
196
  * @public
197
197
  * @remarks Useful when combined with {@link getViewportForBounds} to calculate the correct transform to fit the given nodes in a viewport.
198
198
  * @param nodes - Nodes to calculate the bounds for
199
199
  * @param params.nodeOrigin - Origin of the nodes: [0, 0] - top left, [0.5, 0.5] - center
200
200
  * @returns Bounding box enclosing all nodes
201
201
  */
202
- const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0] }) => {
202
+ const getNodesBounds = (nodes, params = { nodeOrigin: [0, 0], nodeLookup: undefined }) => {
203
+ if (process.env.NODE_ENV === 'development' && !params.nodeLookup) {
204
+ console.warn('Please use `getNodesBounds` from `useReactFlow`/`useSvelteFlow` hook to ensure correct values for sub flows. If not possible, you have to provide a nodeLookup to support sub flows.');
205
+ }
203
206
  if (nodes.length === 0) {
204
207
  return { x: 0, y: 0, width: 0, height: 0 };
205
208
  }
206
- const box = nodes.reduce((currBox, node) => {
207
- const nodeBox = nodeToBox(node, params.nodeOrigin);
209
+ const box = nodes.reduce((currBox, nodeOrId) => {
210
+ const isId = typeof nodeOrId === 'string';
211
+ let currentNode = !params.nodeLookup && !isId ? nodeOrId : undefined;
212
+ if (params.nodeLookup) {
213
+ currentNode = isId
214
+ ? params.nodeLookup.get(nodeOrId)
215
+ : !isInternalNodeBase(nodeOrId)
216
+ ? params.nodeLookup.get(nodeOrId.id)
217
+ : nodeOrId;
218
+ }
219
+ const nodeBox = currentNode ? nodeToBox(currentNode, params.nodeOrigin) : { x: 0, y: 0, x2: 0, y2: 0 };
208
220
  return getBoundsOfBoxes(currBox, nodeBox);
209
221
  }, { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity });
210
222
  return boxToRect(box);
@@ -286,20 +298,6 @@ async function fitView({ nodes, width, height, panZoom, minZoom, maxZoom }, opti
286
298
  await panZoom.setViewport(viewport, { duration: options?.duration });
287
299
  return Promise.resolve(true);
288
300
  }
289
- /**
290
- * This function clamps the passed extend by the node's width and height.
291
- * This is needed to prevent the node from being dragged outside of its extent.
292
- *
293
- * @param node
294
- * @param extent
295
- * @returns
296
- */
297
- function clampNodeExtent(node, extent) {
298
- if (!extent || extent === 'parent') {
299
- return extent;
300
- }
301
- return [extent[0], [extent[1][0] - (node.measured?.width ?? 0), extent[1][1] - (node.measured?.height ?? 0)]];
302
- }
303
301
  /**
304
302
  * This function calculates the next position of a node, taking into account the node's extent, parent node, and origin.
305
303
  *
@@ -311,36 +309,33 @@ function calculateNodePosition({ nodeId, nextPosition, nodeLookup, nodeOrigin =
311
309
  const parentNode = node.parentId ? nodeLookup.get(node.parentId) : undefined;
312
310
  const { x: parentX, y: parentY } = parentNode ? parentNode.internals.positionAbsolute : { x: 0, y: 0 };
313
311
  const origin = node.origin ?? nodeOrigin;
314
- let currentExtent = clampNodeExtent(node, node.extent || nodeExtent);
312
+ let extent = nodeExtent;
315
313
  if (node.extent === 'parent' && !node.expandParent) {
316
314
  if (!parentNode) {
317
315
  onError?.('005', errorMessages['error005']());
318
316
  }
319
317
  else {
320
- const nodeWidth = node.measured.width;
321
- const nodeHeight = node.measured.height;
322
318
  const parentWidth = parentNode.measured.width;
323
319
  const parentHeight = parentNode.measured.height;
324
- if (nodeWidth && nodeHeight && parentWidth && parentHeight) {
325
- currentExtent = [
320
+ if (parentWidth && parentHeight) {
321
+ extent = [
326
322
  [parentX, parentY],
327
- [parentX + parentWidth - nodeWidth, parentY + parentHeight - nodeHeight],
323
+ [parentX + parentWidth, parentY + parentHeight],
328
324
  ];
329
325
  }
330
326
  }
331
327
  }
332
328
  else if (parentNode && isCoordinateExtent(node.extent)) {
333
- currentExtent = [
329
+ extent = [
334
330
  [node.extent[0][0] + parentX, node.extent[0][1] + parentY],
335
331
  [node.extent[1][0] + parentX, node.extent[1][1] + parentY],
336
332
  ];
337
333
  }
338
- const positionAbsolute = isCoordinateExtent(currentExtent)
339
- ? clampPosition(nextPosition, currentExtent)
334
+ const positionAbsolute = isCoordinateExtent(extent)
335
+ ? clampPosition(nextPosition, extent, node.measured)
340
336
  : nextPosition;
341
337
  return {
342
338
  position: {
343
- // TODO: is there a better way to do this?
344
339
  x: positionAbsolute.x - parentX + node.measured.width * origin[0],
345
340
  y: positionAbsolute.y - parentY + node.measured.height * origin[1],
346
341
  },
@@ -397,10 +392,18 @@ async function getElementsToRemove({ nodesToRemove = [], edgesToRemove = [], nod
397
392
  }
398
393
 
399
394
  const clamp = (val, min = 0, max = 1) => Math.min(Math.max(val, min), max);
400
- const clampPosition = (position = { x: 0, y: 0 }, extent) => ({
401
- x: clamp(position.x, extent[0][0], extent[1][0]),
402
- y: clamp(position.y, extent[0][1], extent[1][1]),
395
+ const clampPosition = (position = { x: 0, y: 0 }, extent, dimensions) => ({
396
+ x: clamp(position.x, extent[0][0], extent[1][0] - (dimensions?.width ?? 0)),
397
+ y: clamp(position.y, extent[0][1], extent[1][1] - (dimensions?.height ?? 0)),
403
398
  });
399
+ function clampPositionToParent(childPosition, childDimensions, parent) {
400
+ const { width: parentWidth, height: parentHeight } = getNodeDimensions(parent);
401
+ const { x: parentX, y: parentY } = parent.internals.positionAbsolute;
402
+ return clampPosition(childPosition, [
403
+ [parentX, parentY],
404
+ [parentX + parentWidth, parentY + parentHeight],
405
+ ], childDimensions);
406
+ }
404
407
  /**
405
408
  * Calculates the velocity of panning when the mouse is close to the edge of the canvas
406
409
  * @internal
@@ -1182,6 +1185,7 @@ function getNodeToolbarTransform(nodeRect, viewport, position, offset, align) {
1182
1185
 
1183
1186
  const defaultOptions = {
1184
1187
  nodeOrigin: [0, 0],
1188
+ nodeExtent: infiniteExtent,
1185
1189
  elevateNodesOnSelect: true,
1186
1190
  defaults: {},
1187
1191
  };
@@ -1189,27 +1193,40 @@ const adoptUserNodesDefaultOptions = {
1189
1193
  ...defaultOptions,
1190
1194
  checkEquality: true,
1191
1195
  };
1196
+ function mergeObjects(base, incoming) {
1197
+ const result = { ...base };
1198
+ for (const key in incoming) {
1199
+ if (incoming[key] !== undefined) {
1200
+ // typecast is safe here, because we check for undefined
1201
+ result[key] = incoming[key];
1202
+ }
1203
+ }
1204
+ return result;
1205
+ }
1192
1206
  function updateAbsolutePositions(nodeLookup, parentLookup, options) {
1193
- const _options = { ...defaultOptions, ...options };
1207
+ const _options = mergeObjects(defaultOptions, options);
1194
1208
  for (const node of nodeLookup.values()) {
1195
1209
  if (!node.parentId) {
1196
1210
  continue;
1197
1211
  }
1198
- updateChildPosition(node, nodeLookup, parentLookup, _options);
1212
+ updateChildNode(node, nodeLookup, parentLookup, _options);
1199
1213
  }
1200
1214
  }
1201
1215
  function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
1202
- const _options = { ...adoptUserNodesDefaultOptions, ...options };
1216
+ const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
1203
1217
  const tmpLookup = new Map(nodeLookup);
1218
+ const selectedNodeZ = _options?.elevateNodesOnSelect ? 1000 : 0;
1204
1219
  nodeLookup.clear();
1205
1220
  parentLookup.clear();
1206
- const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1207
1221
  for (const userNode of nodes) {
1208
1222
  let internalNode = tmpLookup.get(userNode.id);
1209
1223
  if (_options.checkEquality && userNode === internalNode?.internals.userNode) {
1210
1224
  nodeLookup.set(userNode.id, internalNode);
1211
1225
  }
1212
1226
  else {
1227
+ const positionWithOrigin = getNodePositionWithOrigin(userNode, _options.nodeOrigin);
1228
+ const extent = isCoordinateExtent(userNode.extent) ? userNode.extent : _options.nodeExtent;
1229
+ const clampedPosition = clampPosition(positionWithOrigin, extent, getNodeDimensions(userNode));
1213
1230
  internalNode = {
1214
1231
  ..._options.defaults,
1215
1232
  ...userNode,
@@ -1218,7 +1235,7 @@ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
1218
1235
  height: userNode.measured?.height,
1219
1236
  },
1220
1237
  internals: {
1221
- positionAbsolute: getNodePositionWithOrigin(userNode, _options.nodeOrigin),
1238
+ positionAbsolute: clampedPosition,
1222
1239
  // if user re-initializes the node or removes `measured` for whatever reason, we reset the handleBounds so that the node gets re-measured
1223
1240
  handleBounds: !userNode.measured ? undefined : internalNode?.internals.handleBounds,
1224
1241
  z: calculateZ(userNode, selectedNodeZ),
@@ -1228,34 +1245,42 @@ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
1228
1245
  nodeLookup.set(userNode.id, internalNode);
1229
1246
  }
1230
1247
  if (userNode.parentId) {
1231
- updateChildPosition(internalNode, nodeLookup, parentLookup, options);
1248
+ updateChildNode(internalNode, nodeLookup, parentLookup, options);
1232
1249
  }
1233
1250
  }
1234
1251
  }
1235
- function updateChildPosition(node, nodeLookup, parentLookup, options) {
1236
- const _options = { ...defaultOptions, ...options };
1237
- const parentId = node.parentId;
1238
- const parentNode = nodeLookup.get(parentId);
1239
- if (!parentNode) {
1240
- console.warn(`Parent node ${parentId} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);
1252
+ function updateParentLookup(node, parentLookup) {
1253
+ if (!node.parentId) {
1241
1254
  return;
1242
1255
  }
1243
- // update the parentLookup
1244
- const childNodes = parentLookup.get(parentId);
1256
+ const childNodes = parentLookup.get(node.parentId);
1245
1257
  if (childNodes) {
1246
1258
  childNodes.set(node.id, node);
1247
1259
  }
1248
1260
  else {
1249
- parentLookup.set(parentId, new Map([[node.id, node]]));
1261
+ parentLookup.set(node.parentId, new Map([[node.id, node]]));
1262
+ }
1263
+ }
1264
+ /**
1265
+ * Updates positionAbsolute and zIndex of a child node and the parentLookup.
1266
+ */
1267
+ function updateChildNode(node, nodeLookup, parentLookup, options) {
1268
+ const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
1269
+ const parentId = node.parentId;
1270
+ const parentNode = nodeLookup.get(parentId);
1271
+ if (!parentNode) {
1272
+ console.warn(`Parent node ${parentId} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);
1273
+ return;
1250
1274
  }
1251
- const selectedNodeZ = options?.elevateNodesOnSelect ? 1000 : 0;
1252
- const { x, y, z } = calculateChildXYZ(node, parentNode, _options.nodeOrigin, selectedNodeZ);
1253
- const currPosition = node.internals.positionAbsolute;
1254
- const positionChanged = x !== currPosition.x || y !== currPosition.y;
1275
+ updateParentLookup(node, parentLookup);
1276
+ const selectedNodeZ = elevateNodesOnSelect ? 1000 : 0;
1277
+ const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ);
1278
+ const { positionAbsolute } = node.internals;
1279
+ const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
1255
1280
  if (positionChanged || z !== node.internals.z) {
1256
1281
  node.internals = {
1257
1282
  ...node.internals,
1258
- positionAbsolute: positionChanged ? { x, y } : currPosition,
1283
+ positionAbsolute: positionChanged ? { x, y } : positionAbsolute,
1259
1284
  z,
1260
1285
  };
1261
1286
  }
@@ -1263,13 +1288,22 @@ function updateChildPosition(node, nodeLookup, parentLookup, options) {
1263
1288
  function calculateZ(node, selectedNodeZ) {
1264
1289
  return (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? selectedNodeZ : 0);
1265
1290
  }
1266
- function calculateChildXYZ(childNode, parentNode, nodeOrigin, selectedNodeZ) {
1267
- const position = getNodePositionWithOrigin(childNode, nodeOrigin);
1291
+ function calculateChildXYZ(childNode, parentNode, nodeOrigin, nodeExtent, selectedNodeZ) {
1292
+ const { x: parentX, y: parentY } = parentNode.internals.positionAbsolute;
1293
+ const childDimensions = getNodeDimensions(childNode);
1294
+ const positionWithOrigin = getNodePositionWithOrigin(childNode, nodeOrigin);
1295
+ const clampedPosition = isCoordinateExtent(childNode.extent)
1296
+ ? clampPosition(positionWithOrigin, childNode.extent, childDimensions)
1297
+ : positionWithOrigin;
1298
+ let absolutePosition = clampPosition({ x: parentX + clampedPosition.x, y: parentY + clampedPosition.y }, nodeExtent, childDimensions);
1299
+ if (childNode.extent === 'parent') {
1300
+ absolutePosition = clampPositionToParent(absolutePosition, childDimensions, parentNode);
1301
+ }
1268
1302
  const childZ = calculateZ(childNode, selectedNodeZ);
1269
1303
  const parentZ = parentNode.internals.z ?? 0;
1270
1304
  return {
1271
- x: parentNode.internals.positionAbsolute.x + position.x,
1272
- y: parentNode.internals.positionAbsolute.y + position.y,
1305
+ x: absolutePosition.x,
1306
+ y: absolutePosition.y,
1273
1307
  z: parentZ > childZ ? parentZ : childZ,
1274
1308
  };
1275
1309
  }
@@ -1340,7 +1374,7 @@ function handleExpandParent(children, nodeLookup, parentLookup, nodeOrigin = [0,
1340
1374
  }
1341
1375
  return changes;
1342
1376
  }
1343
- function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin) {
1377
+ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOrigin, nodeExtent) {
1344
1378
  const viewportNode = domNode?.querySelector('.xyflow__viewport');
1345
1379
  let updatedInternals = false;
1346
1380
  if (!viewportNode) {
@@ -1371,17 +1405,25 @@ function updateNodeInternals(updates, nodeLookup, parentLookup, domNode, nodeOri
1371
1405
  (dimensionChanged || !node.internals.handleBounds || update.force));
1372
1406
  if (doUpdate) {
1373
1407
  const nodeBounds = update.nodeElement.getBoundingClientRect();
1408
+ const extent = isCoordinateExtent(node.extent) ? node.extent : nodeExtent;
1409
+ let { positionAbsolute } = node.internals;
1410
+ if (node.parentId && node.extent === 'parent') {
1411
+ positionAbsolute = clampPositionToParent(positionAbsolute, dimensions, nodeLookup.get(node.parentId));
1412
+ }
1413
+ else if (extent) {
1414
+ positionAbsolute = clampPosition(positionAbsolute, extent, dimensions);
1415
+ }
1374
1416
  node.measured = dimensions;
1375
1417
  node.internals = {
1376
1418
  ...node.internals,
1377
- positionAbsolute: getNodePositionWithOrigin(node, nodeOrigin),
1419
+ positionAbsolute,
1378
1420
  handleBounds: {
1379
1421
  source: getHandleBounds('source', update.nodeElement, nodeBounds, zoom, node.id),
1380
1422
  target: getHandleBounds('target', update.nodeElement, nodeBounds, zoom, node.id),
1381
1423
  },
1382
1424
  };
1383
1425
  if (node.parentId) {
1384
- updateChildPosition(node, nodeLookup, parentLookup, { nodeOrigin });
1426
+ updateChildNode(node, nodeLookup, parentLookup, { nodeOrigin });
1385
1427
  }
1386
1428
  updatedInternals = true;
1387
1429
  if (dimensionChanged) {
@@ -2287,7 +2329,7 @@ function createFilter({ zoomActivationKeyPressed, zoomOnScroll, zoomOnPinch, pan
2287
2329
  };
2288
2330
  }
2289
2331
 
2290
- function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExtent, viewport, onPanZoom, onPanZoomStart, onPanZoomEnd, onTransformChange, onDraggingChange, }) {
2332
+ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExtent, viewport, onPanZoom, onPanZoomStart, onPanZoomEnd, onDraggingChange, }) {
2291
2333
  const zoomPanValues = {
2292
2334
  isZoomingOrPanning: false,
2293
2335
  usedRightMouseButton: false,
@@ -2323,7 +2365,7 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
2323
2365
  return Promise.resolve(false);
2324
2366
  }
2325
2367
  // public functions
2326
- function update({ noWheelClassName, noPanClassName, onPaneContextMenu, userSelectionActive, panOnScroll, panOnDrag, panOnScrollMode, panOnScrollSpeed, preventScrolling, zoomOnPinch, zoomOnScroll, zoomOnDoubleClick, zoomActivationKeyPressed, lib, }) {
2368
+ function update({ noWheelClassName, noPanClassName, onPaneContextMenu, userSelectionActive, panOnScroll, panOnDrag, panOnScrollMode, panOnScrollSpeed, preventScrolling, zoomOnPinch, zoomOnScroll, zoomOnDoubleClick, zoomActivationKeyPressed, lib, onTransformChange, }) {
2327
2369
  if (userSelectionActive && !zoomPanValues.isZoomingOrPanning) {
2328
2370
  destroy();
2329
2371
  }
@@ -2874,4 +2916,4 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
2874
2916
  };
2875
2917
  }
2876
2918
 
2877
- export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, adoptUserNodes, areConnectionMapsEqual, boxToRect, calcAutoPan, calculateNodePosition, clamp, clampPosition, createMarkerIds, devWarn, elementSelectionKeys, errorMessages, evaluateAbsolutePosition, fitView, getBezierEdgeCenter, getBezierPath, getBoundsOfBoxes, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getDimensions, getEdgeCenter, getEdgePosition, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getFitViewNodes, getHandleBounds, getHandlePosition, getHostForElement, getIncomers, getInternalNodesBounds, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoers, getOverlappingArea, getPointerPosition, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, handleExpandParent, infiniteExtent, initialConnection, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isInternalNodeBase, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeHasDimensions, nodeToBox, nodeToRect, oppositePosition, panBy, pointToRendererPoint, reconnectEdge, rectToBox, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateNodeInternals };
2919
+ export { ConnectionLineType, ConnectionMode, MarkerType, PanOnScrollMode, Position, ResizeControlVariant, SelectionMode, XYDrag, XYHandle, XYMinimap, XYPanZoom, XYResizer, XY_RESIZER_HANDLE_POSITIONS, XY_RESIZER_LINE_POSITIONS, addEdge, adoptUserNodes, areConnectionMapsEqual, boxToRect, calcAutoPan, calculateNodePosition, clamp, clampPosition, clampPositionToParent, createMarkerIds, devWarn, elementSelectionKeys, errorMessages, evaluateAbsolutePosition, fitView, getBezierEdgeCenter, getBezierPath, getBoundsOfBoxes, getBoundsOfRects, getConnectedEdges, getConnectionStatus, getDimensions, getEdgeCenter, getEdgePosition, getElementsToRemove, getElevatedEdgeZIndex, getEventPosition, getFitViewNodes, getHandleBounds, getHandlePosition, getHostForElement, getIncomers, getInternalNodesBounds, getMarkerId, getNodeDimensions, getNodePositionWithOrigin, getNodeToolbarTransform, getNodesBounds, getNodesInside, getOutgoers, getOverlappingArea, getPointerPosition, getSmoothStepPath, getStraightPath, getViewportForBounds, handleConnectionChange, handleExpandParent, infiniteExtent, initialConnection, isCoordinateExtent, isEdgeBase, isEdgeVisible, isInputDOMNode, isInternalNodeBase, isMacOs, isMouseEvent, isNodeBase, isNumeric, isRectObject, nodeHasDimensions, nodeToBox, nodeToRect, oppositePosition, panBy, pointToRendererPoint, reconnectEdge, rectToBox, rendererPointToPoint, shallowNodeData, snapPosition, updateAbsolutePositions, updateConnectionLookup, updateNodeInternals };