@railtownai/railtracks-visualizer 0.0.33 → 0.0.35

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/cjs/index.js CHANGED
@@ -3358,27 +3358,25 @@ const getNodePositionWithOrigin = (node, nodeOrigin = [
3358
3358
  * Determines a bounding box that contains all given nodes in an array
3359
3359
  * @internal
3360
3360
  */ const getInternalNodesBounds = (nodeLookup, params = {})=>{
3361
- if (nodeLookup.size === 0) {
3362
- return {
3363
- x: 0,
3364
- y: 0,
3365
- width: 0,
3366
- height: 0
3367
- };
3368
- }
3369
3361
  let box = {
3370
3362
  x: Infinity,
3371
3363
  y: Infinity,
3372
3364
  x2: -Infinity,
3373
3365
  y2: -Infinity
3374
3366
  };
3367
+ let hasVisibleNodes = false;
3375
3368
  nodeLookup.forEach((node)=>{
3376
3369
  if (params.filter === undefined || params.filter(node)) {
3377
- const nodeBox = nodeToBox(node);
3378
- box = getBoundsOfBoxes(box, nodeBox);
3370
+ box = getBoundsOfBoxes(box, nodeToBox(node));
3371
+ hasVisibleNodes = true;
3379
3372
  }
3380
3373
  });
3381
- return boxToRect(box);
3374
+ return hasVisibleNodes ? boxToRect(box) : {
3375
+ x: 0,
3376
+ y: 0,
3377
+ width: 0,
3378
+ height: 0
3379
+ };
3382
3380
  };
3383
3381
  const getNodesInside = (nodes, rect, [tx, ty, tScale] = [
3384
3382
  0,
@@ -4123,7 +4121,7 @@ function getEdgeCenter({ sourceX, sourceY, targetX, targetY }) {
4123
4121
  return zIndex;
4124
4122
  }
4125
4123
  const edgeZ = elevateOnSelect && selected ? 1000 : 0;
4126
- const nodeZ = Math.max(sourceNode.parentId ? sourceNode.internals.z : 0, targetNode.parentId ? targetNode.internals.z : 0);
4124
+ const nodeZ = Math.max(sourceNode.parentId || elevateOnSelect && sourceNode.selected ? sourceNode.internals.z : 0, targetNode.parentId || elevateOnSelect && targetNode.selected ? targetNode.internals.z : 0);
4127
4125
  return edgeZ + nodeZ;
4128
4126
  }
4129
4127
  function isEdgeVisible({ sourceNode, targetNode, width, height, transform }) {
@@ -4639,6 +4637,8 @@ function createMarkerIds(edges, { id, defaultColor, defaultMarkerStart, defaultM
4639
4637
  return markers;
4640
4638
  }, []).sort((a, b)=>a.id.localeCompare(b.id));
4641
4639
  }
4640
+ const SELECTED_NODE_Z = 1000;
4641
+ const ROOT_PARENT_Z_INCREMENT = 10;
4642
4642
  const defaultOptions = {
4643
4643
  nodeOrigin: [
4644
4644
  0,
@@ -4677,11 +4677,42 @@ function updateAbsolutePositions(nodeLookup, parentLookup, options) {
4677
4677
  }
4678
4678
  }
4679
4679
  }
4680
+ function parseHandles(userNode, internalNode) {
4681
+ if (!userNode.handles) {
4682
+ return !userNode.measured ? undefined : internalNode?.internals.handleBounds;
4683
+ }
4684
+ const source = [];
4685
+ const target = [];
4686
+ for (const handle of userNode.handles){
4687
+ const handleBounds = {
4688
+ id: handle.id,
4689
+ width: handle.width ?? 1,
4690
+ height: handle.height ?? 1,
4691
+ nodeId: userNode.id,
4692
+ x: handle.x,
4693
+ y: handle.y,
4694
+ position: handle.position,
4695
+ type: handle.type
4696
+ };
4697
+ if (handle.type === 'source') {
4698
+ source.push(handleBounds);
4699
+ } else if (handle.type === 'target') {
4700
+ target.push(handleBounds);
4701
+ }
4702
+ }
4703
+ return {
4704
+ source,
4705
+ target
4706
+ };
4707
+ }
4680
4708
  function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
4681
4709
  const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
4710
+ let rootParentIndex = {
4711
+ i: -1
4712
+ };
4682
4713
  let nodesInitialized = nodes.length > 0;
4683
4714
  const tmpLookup = new Map(nodeLookup);
4684
- const selectedNodeZ = _options?.elevateNodesOnSelect ? 1000 : 0;
4715
+ const selectedNodeZ = _options?.elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
4685
4716
  nodeLookup.clear();
4686
4717
  parentLookup.clear();
4687
4718
  for (const userNode of nodes){
@@ -4702,7 +4733,7 @@ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
4702
4733
  internals: {
4703
4734
  positionAbsolute: clampedPosition,
4704
4735
  // if user re-initializes the node or removes `measured` for whatever reason, we reset the handleBounds so that the node gets re-measured
4705
- handleBounds: !userNode.measured ? undefined : internalNode?.internals.handleBounds,
4736
+ handleBounds: parseHandles(userNode, internalNode),
4706
4737
  z: calculateZ(userNode, selectedNodeZ),
4707
4738
  userNode
4708
4739
  }
@@ -4713,7 +4744,7 @@ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
4713
4744
  nodesInitialized = false;
4714
4745
  }
4715
4746
  if (userNode.parentId) {
4716
- updateChildNode(internalNode, nodeLookup, parentLookup, options);
4747
+ updateChildNode(internalNode, nodeLookup, parentLookup, options, rootParentIndex);
4717
4748
  }
4718
4749
  }
4719
4750
  return nodesInitialized;
@@ -4736,7 +4767,7 @@ function updateParentLookup(node, parentLookup) {
4736
4767
  }
4737
4768
  /**
4738
4769
  * Updates positionAbsolute and zIndex of a child node and the parentLookup.
4739
- */ function updateChildNode(node, nodeLookup, parentLookup, options) {
4770
+ */ function updateChildNode(node, nodeLookup, parentLookup, options, rootParentIndex) {
4740
4771
  const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
4741
4772
  const parentId = node.parentId;
4742
4773
  const parentNode = nodeLookup.get(parentId);
@@ -4745,7 +4776,16 @@ function updateParentLookup(node, parentLookup) {
4745
4776
  return;
4746
4777
  }
4747
4778
  updateParentLookup(node, parentLookup);
4748
- const selectedNodeZ = elevateNodesOnSelect ? 1000 : 0;
4779
+ // We just want to set the rootParentIndex for the first child
4780
+ if (rootParentIndex && !parentNode.parentId && parentNode.internals.rootParentIndex === undefined) {
4781
+ parentNode.internals.rootParentIndex = ++rootParentIndex.i;
4782
+ parentNode.internals.z = parentNode.internals.z + rootParentIndex.i * ROOT_PARENT_Z_INCREMENT;
4783
+ }
4784
+ // But we need to update rootParentIndex.i also when parent has not been updated
4785
+ if (rootParentIndex && parentNode.internals.rootParentIndex !== undefined) {
4786
+ rootParentIndex.i = parentNode.internals.rootParentIndex;
4787
+ }
4788
+ const selectedNodeZ = elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
4749
4789
  const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ);
4750
4790
  const { positionAbsolute } = node.internals;
4751
4791
  const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
@@ -5319,8 +5359,10 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
5319
5359
  autoPan();
5320
5360
  }
5321
5361
  if (!dragStarted) {
5322
- const x = pointerPos.xSnapped - (lastPos.x ?? 0);
5323
- const y = pointerPos.ySnapped - (lastPos.y ?? 0);
5362
+ // Calculate distance in client coordinates for consistent drag threshold behavior across zoom levels
5363
+ const currentMousePosition = getEventPosition(event.sourceEvent, containerBounds);
5364
+ const x = currentMousePosition.x - mousePosition.x;
5365
+ const y = currentMousePosition.y - mousePosition.y;
5324
5366
  const distance = Math.sqrt(x * x + y * y);
5325
5367
  if (distance > nodeDragThreshold) {
5326
5368
  startDrag(event);
@@ -5604,6 +5646,10 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
5604
5646
  previousConnection = newConnection;
5605
5647
  }
5606
5648
  function onPointerUp(event) {
5649
+ // Prevent multi-touch aborting connection
5650
+ if ('touches' in event && event.touches.length > 0) {
5651
+ return;
5652
+ }
5607
5653
  if (connectionStarted) {
5608
5654
  if ((closestHandle || resultHandleDomNode) && connection && isValid) {
5609
5655
  onConnect?.(connection);
@@ -5761,8 +5807,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
5761
5807
  pointer
5762
5808
  };
5763
5809
  }
5764
- /* eslint-disable @typescript-eslint/no-explicit-any */ const viewChanged = (prevViewport, eventViewport)=>prevViewport.x !== eventViewport.x || prevViewport.y !== eventViewport.y || prevViewport.zoom !== eventViewport.k;
5765
- const transformToViewport = (transform)=>({
5810
+ /* eslint-disable @typescript-eslint/no-explicit-any */ const transformToViewport = (transform)=>({
5766
5811
  x: transform.x,
5767
5812
  y: transform.y,
5768
5813
  zoom: transform.k
@@ -5786,6 +5831,9 @@ const wheelDelta = (event)=>{
5786
5831
  function createPanOnScrollHandler({ zoomPanValues, noWheelClassName, d3Selection, d3Zoom, panOnScrollMode, panOnScrollSpeed, zoomOnPinch, onPanZoomStart, onPanZoom, onPanZoomEnd }) {
5787
5832
  return (event)=>{
5788
5833
  if (isWrappedWithClass(event, noWheelClassName)) {
5834
+ if (event.ctrlKey) {
5835
+ event.preventDefault(); // stop native page zoom for pinch zooming
5836
+ }
5789
5837
  return false;
5790
5838
  }
5791
5839
  event.preventDefault();
@@ -5824,8 +5872,7 @@ function createPanOnScrollHandler({ zoomPanValues, noWheelClassName, d3Selection
5824
5872
  */ if (!zoomPanValues.isPanScrolling) {
5825
5873
  zoomPanValues.isPanScrolling = true;
5826
5874
  onPanZoomStart?.(event, nextViewport);
5827
- }
5828
- if (zoomPanValues.isPanScrolling) {
5875
+ } else {
5829
5876
  onPanZoom?.(event, nextViewport);
5830
5877
  zoomPanValues.panScrollTimeout = setTimeout(()=>{
5831
5878
  onPanZoomEnd?.(event, nextViewport);
@@ -5895,7 +5942,7 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5895
5942
  }
5896
5943
  zoomPanValues.usedRightMouseButton = false;
5897
5944
  onDraggingChange(false);
5898
- if (onPanZoomEnd && viewChanged(zoomPanValues.prevViewport, event.transform)) {
5945
+ if (onPanZoomEnd) {
5899
5946
  const viewport = transformToViewport(event.transform);
5900
5947
  zoomPanValues.prevViewport = viewport;
5901
5948
  clearTimeout(zoomPanValues.timerId);
@@ -5906,10 +5953,11 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5906
5953
  }
5907
5954
  };
5908
5955
  }
5909
- /* eslint-disable @typescript-eslint/no-explicit-any */ function createFilter({ zoomActivationKeyPressed, zoomOnScroll, zoomOnPinch, panOnDrag, panOnScroll, zoomOnDoubleClick, userSelectionActive, noWheelClassName, noPanClassName, lib }) {
5956
+ /* eslint-disable @typescript-eslint/no-explicit-any */ function createFilter({ zoomActivationKeyPressed, zoomOnScroll, zoomOnPinch, panOnDrag, panOnScroll, zoomOnDoubleClick, userSelectionActive, noWheelClassName, noPanClassName, lib, connectionInProgress }) {
5910
5957
  return (event)=>{
5911
5958
  const zoomScroll = zoomActivationKeyPressed || zoomOnScroll;
5912
5959
  const pinchZoom = zoomOnPinch && event.ctrlKey;
5960
+ const isWheelEvent = event.type === 'wheel';
5913
5961
  if (event.button === 1 && event.type === 'mousedown' && (isWrappedWithClass(event, `${lib}-flow__node`) || isWrappedWithClass(event, `${lib}-flow__edge`))) {
5914
5962
  return true;
5915
5963
  }
@@ -5921,15 +5969,19 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5921
5969
  if (userSelectionActive) {
5922
5970
  return false;
5923
5971
  }
5972
+ // we want to disable pinch-zooming while making a connection
5973
+ if (connectionInProgress && !isWheelEvent) {
5974
+ return false;
5975
+ }
5924
5976
  // if the target element is inside an element with the nowheel class, we prevent zooming
5925
- if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
5977
+ if (isWrappedWithClass(event, noWheelClassName) && isWheelEvent) {
5926
5978
  return false;
5927
5979
  }
5928
5980
  // if the target element is inside an element with the nopan class, we prevent panning
5929
- if (isWrappedWithClass(event, noPanClassName) && (event.type !== 'wheel' || panOnScroll && event.type === 'wheel' && !zoomActivationKeyPressed)) {
5981
+ if (isWrappedWithClass(event, noPanClassName) && (!isWheelEvent || panOnScroll && isWheelEvent && !zoomActivationKeyPressed)) {
5930
5982
  return false;
5931
5983
  }
5932
- if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
5984
+ if (!zoomOnPinch && event.ctrlKey && isWheelEvent) {
5933
5985
  return false;
5934
5986
  }
5935
5987
  if (!zoomOnPinch && event.type === 'touchstart' && event.touches?.length > 1) {
@@ -5937,7 +5989,7 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5937
5989
  return false;
5938
5990
  }
5939
5991
  // when there is no scroll handling enabled, we prevent all wheel events
5940
- if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
5992
+ if (!zoomScroll && !panOnScroll && !pinchZoom && isWheelEvent) {
5941
5993
  return false;
5942
5994
  }
5943
5995
  // if the pane is not movable, we prevent dragging it with mousestart or touchstart
@@ -5951,7 +6003,7 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5951
6003
  // We only allow right clicks if pan on drag is set to right click
5952
6004
  const buttonAllowed = Array.isArray(panOnDrag) && panOnDrag.includes(event.button) || !event.button || event.button <= 1;
5953
6005
  // default filter for d3-zoom
5954
- return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed;
6006
+ return (!event.ctrlKey || isWheelEvent) && buttonAllowed;
5955
6007
  };
5956
6008
  }
5957
6009
  function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExtent, viewport, onPanZoom, onPanZoomStart, onPanZoomEnd, onDraggingChange }) {
@@ -5959,10 +6011,7 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
5959
6011
  isZoomingOrPanning: false,
5960
6012
  usedRightMouseButton: false,
5961
6013
  prevViewport: {
5962
- x: 0,
5963
- y: 0,
5964
- zoom: 0
5965
- },
6014
+ },
5966
6015
  mouseButton: 0,
5967
6016
  timerId: undefined,
5968
6017
  panScrollTimeout: undefined,
@@ -6000,7 +6049,7 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
6000
6049
  return Promise.resolve(false);
6001
6050
  }
6002
6051
  // public functions
6003
- function update({ noWheelClassName, noPanClassName, onPaneContextMenu, userSelectionActive, panOnScroll, panOnDrag, panOnScrollMode, panOnScrollSpeed, preventScrolling, zoomOnPinch, zoomOnScroll, zoomOnDoubleClick, zoomActivationKeyPressed, lib, onTransformChange }) {
6052
+ function update({ noWheelClassName, noPanClassName, onPaneContextMenu, userSelectionActive, panOnScroll, panOnDrag, panOnScrollMode, panOnScrollSpeed, preventScrolling, zoomOnPinch, zoomOnScroll, zoomOnDoubleClick, zoomActivationKeyPressed, lib, onTransformChange, connectionInProgress }) {
6004
6053
  if (userSelectionActive && !zoomPanValues.isZoomingOrPanning) {
6005
6054
  destroy();
6006
6055
  }
@@ -6062,7 +6111,8 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
6062
6111
  userSelectionActive,
6063
6112
  noPanClassName,
6064
6113
  noWheelClassName,
6065
- lib
6114
+ lib,
6115
+ connectionInProgress
6066
6116
  });
6067
6117
  d3ZoomInstance.filter(filter);
6068
6118
  /*
@@ -6408,6 +6458,17 @@ function nodeToChildExtent(child, parent, nodeOrigin) {
6408
6458
  }
6409
6459
  function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6410
6460
  const selection = select(domNode);
6461
+ let params = {
6462
+ controlDirection: getControlDirection('bottom-right'),
6463
+ boundaries: {
6464
+ minWidth: 0,
6465
+ minHeight: 0,
6466
+ maxWidth: Number.MAX_VALUE,
6467
+ maxHeight: Number.MAX_VALUE
6468
+ },
6469
+ resizeDirection: undefined,
6470
+ keepAspectRatio: false
6471
+ };
6411
6472
  function update({ controlPosition, boundaries, keepAspectRatio, resizeDirection, onResizeStart, onResize, onResizeEnd, shouldResize }) {
6412
6473
  let prevValues = {
6413
6474
  ...initPrevValues$1
@@ -6415,13 +6476,20 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6415
6476
  let startValues = {
6416
6477
  ...initStartValues
6417
6478
  };
6418
- const controlDirection = getControlDirection(controlPosition);
6479
+ params = {
6480
+ boundaries,
6481
+ resizeDirection,
6482
+ keepAspectRatio,
6483
+ controlDirection: getControlDirection(controlPosition)
6484
+ };
6419
6485
  let node = undefined;
6420
6486
  let containerBounds = null;
6421
6487
  let childNodes = [];
6422
6488
  let parentNode = undefined; // Needed to fix expandParent
6423
6489
  let parentExtent = undefined;
6424
6490
  let childExtent = undefined;
6491
+ // we only want to trigger onResizeEnd if onResize was actually called
6492
+ let resizeDetected = false;
6425
6493
  const dragHandler = drag().on('start', (event)=>{
6426
6494
  const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, paneDomNode } = getStoreItems();
6427
6495
  node = nodeLookup.get(nodeId);
@@ -6503,7 +6571,7 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6503
6571
  const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
6504
6572
  const change = {};
6505
6573
  const nodeOrigin = node.origin ?? storeNodeOrigin;
6506
- const { width, height, x, y } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, parentExtent, childExtent);
6574
+ const { width, height, x, y } = getDimensionsAfterResize(startValues, params.controlDirection, pointerPosition, params.boundaries, params.keepAspectRatio, nodeOrigin, parentExtent, childExtent);
6507
6575
  const isWidthChange = width !== prevWidth;
6508
6576
  const isHeightChange = height !== prevHeight;
6509
6577
  const isXPosChange = x !== prevX && isWidthChange;
@@ -6532,8 +6600,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6532
6600
  }
6533
6601
  }
6534
6602
  if (isWidthChange || isHeightChange) {
6535
- change.width = isWidthChange && (!resizeDirection || resizeDirection === 'horizontal') ? width : prevValues.width;
6536
- change.height = isHeightChange && (!resizeDirection || resizeDirection === 'vertical') ? height : prevValues.height;
6603
+ change.width = isWidthChange && (!params.resizeDirection || params.resizeDirection === 'horizontal') ? width : prevValues.width;
6604
+ change.height = isHeightChange && (!params.resizeDirection || params.resizeDirection === 'vertical') ? height : prevValues.height;
6537
6605
  prevValues.width = change.width;
6538
6606
  prevValues.height = change.height;
6539
6607
  }
@@ -6555,8 +6623,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6555
6623
  prevWidth,
6556
6624
  height: prevValues.height,
6557
6625
  prevHeight,
6558
- affectsX: controlDirection.affectsX,
6559
- affectsY: controlDirection.affectsY
6626
+ affectsX: params.controlDirection.affectsX,
6627
+ affectsY: params.controlDirection.affectsY
6560
6628
  });
6561
6629
  const nextValues = {
6562
6630
  ...prevValues,
@@ -6566,15 +6634,20 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6566
6634
  if (callResize === false) {
6567
6635
  return;
6568
6636
  }
6637
+ resizeDetected = true;
6569
6638
  onResize?.(event, nextValues);
6570
6639
  onChange(change, childChanges);
6571
6640
  }).on('end', (event)=>{
6641
+ if (!resizeDetected) {
6642
+ return;
6643
+ }
6572
6644
  onResizeEnd?.(event, {
6573
6645
  ...prevValues
6574
6646
  });
6575
6647
  onEnd?.({
6576
6648
  ...prevValues
6577
6649
  });
6650
+ resizeDetected = false;
6578
6651
  });
6579
6652
  selection.call(dragHandler);
6580
6653
  }
@@ -8195,7 +8268,11 @@ const selector$k = (s)=>!!s.panZoom;
8195
8268
  deletedEdges: matchingEdges
8196
8269
  };
8197
8270
  },
8198
- getIntersectingNodes: (nodeOrRect, partially = true, nodes)=>{
8271
+ /**
8272
+ * Partial is defined as "the 2 nodes/areas are intersecting partially".
8273
+ * If a is contained in b or b is contained in a, they are both
8274
+ * considered fully intersecting.
8275
+ */ getIntersectingNodes: (nodeOrRect, partially = true, nodes)=>{
8199
8276
  const isRect = isRectObject(nodeOrRect);
8200
8277
  const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
8201
8278
  const hasNodesOption = nodes !== undefined;
@@ -8221,7 +8298,7 @@ const selector$k = (s)=>!!s.panZoom;
8221
8298
  }
8222
8299
  const overlappingArea = getOverlappingArea(nodeRect, area);
8223
8300
  const partiallyVisible = partially && overlappingArea > 0;
8224
- return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
8301
+ return partiallyVisible || overlappingArea >= area.width * area.height || overlappingArea >= nodeRect.width * nodeRect.height;
8225
8302
  },
8226
8303
  updateNode,
8227
8304
  updateNodeData: (id, dataUpdate, options = {
@@ -8340,7 +8417,7 @@ const win$1 = typeof window !== 'undefined' ? window : undefined;
8340
8417
  const store = useStoreApi();
8341
8418
  React.useEffect(()=>{
8342
8419
  const updateDimensions = ()=>{
8343
- if (!domNode.current) {
8420
+ if (!domNode.current || !(domNode.current.checkVisibility?.() ?? true)) {
8344
8421
  return false;
8345
8422
  }
8346
8423
  const size = getDimensions$1(domNode.current);
@@ -8375,12 +8452,13 @@ const containerStyle = {
8375
8452
  };
8376
8453
  const selector$j = (s)=>({
8377
8454
  userSelectionActive: s.userSelectionActive,
8378
- lib: s.lib
8455
+ lib: s.lib,
8456
+ connectionInProgress: s.connection.inProgress
8379
8457
  });
8380
8458
  function ZoomPane({ onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true, panOnScroll = false, panOnScrollSpeed = 0.5, panOnScrollMode = PanOnScrollMode.Free, zoomOnDoubleClick = true, panOnDrag = true, defaultViewport, translateExtent, minZoom, maxZoom, zoomActivationKeyCode, preventScrolling = true, children, noWheelClassName, noPanClassName, onViewportChange, isControlledViewport, paneClickDistance }) {
8381
8459
  const store = useStoreApi();
8382
8460
  const zoomPane = React.useRef(null);
8383
- const { userSelectionActive, lib } = useStore(selector$j, shallow$1);
8461
+ const { userSelectionActive, lib, connectionInProgress } = useStore(selector$j, shallow$1);
8384
8462
  const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
8385
8463
  const panZoom = React.useRef();
8386
8464
  useResizeHandler(zoomPane);
@@ -8458,7 +8536,8 @@ function ZoomPane({ onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true,
8458
8536
  userSelectionActive,
8459
8537
  noWheelClassName,
8460
8538
  lib,
8461
- onTransformChange
8539
+ onTransformChange,
8540
+ connectionInProgress
8462
8541
  });
8463
8542
  }, [
8464
8543
  onPaneContextMenu,
@@ -8475,7 +8554,8 @@ function ZoomPane({ onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true,
8475
8554
  userSelectionActive,
8476
8555
  noWheelClassName,
8477
8556
  lib,
8478
- onTransformChange
8557
+ onTransformChange,
8558
+ connectionInProgress
8479
8559
  ]);
8480
8560
  return jsxRuntime.jsx("div", {
8481
8561
  className: "react-flow__renderer",
@@ -8549,12 +8629,25 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
8549
8629
  onPaneContextMenu?.(event);
8550
8630
  };
8551
8631
  const onWheel = onPaneScroll ? (event)=>onPaneScroll(event) : undefined;
8552
- const onPointerDown = (event)=>{
8632
+ const onClickCapture = (event)=>{
8633
+ const isSelectionOnDragActive = selectionOnDrag && container.current === event.target || !selectionOnDrag || selectionKeyPressed;
8634
+ if (!isSelectionOnDragActive) {
8635
+ return;
8636
+ }
8637
+ event.stopPropagation();
8638
+ };
8639
+ // We are using capture here in order to prevent other pointer events
8640
+ // to be able to create a selection above a node or an edge
8641
+ const onPointerDownCapture = (event)=>{
8553
8642
  const { resetSelectedElements, domNode } = store.getState();
8554
8643
  containerBounds.current = domNode?.getBoundingClientRect();
8555
- if (!elementsSelectable || !isSelecting || event.button !== 0 || event.target !== container.current || !containerBounds.current) {
8644
+ const isNoKeyEvent = event.target !== container.current && !!event.target.closest('.nokey');
8645
+ const isSelectionActive = selectionOnDrag && container.current === event.target || !selectionOnDrag || selectionKeyPressed;
8646
+ if (!elementsSelectable || !isSelecting || event.button !== 0 || !containerBounds.current || isNoKeyEvent || !isSelectionActive || !event.isPrimary) {
8556
8647
  return;
8557
8648
  }
8649
+ event.stopPropagation();
8650
+ event.preventDefault();
8558
8651
  event.target?.setPointerCapture?.(event.pointerId);
8559
8652
  selectionStarted.current = true;
8560
8653
  selectionInProgress.current = false;
@@ -8658,9 +8751,10 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
8658
8751
  onContextMenu: wrapHandler(onContextMenu, container),
8659
8752
  onWheel: wrapHandler(onWheel, container),
8660
8753
  onPointerEnter: hasActiveSelection ? undefined : onPaneMouseEnter,
8661
- onPointerDown: hasActiveSelection ? onPointerDown : onPaneMouseMove,
8662
8754
  onPointerMove: hasActiveSelection ? onPointerMove : onPaneMouseMove,
8663
8755
  onPointerUp: hasActiveSelection ? onPointerUp : undefined,
8756
+ onPointerDownCapture: hasActiveSelection ? onPointerDownCapture : undefined,
8757
+ onClickCapture: hasActiveSelection ? onClickCapture : undefined,
8664
8758
  onPointerLeave: onPaneMouseLeave,
8665
8759
  ref: container,
8666
8760
  style: containerStyle,
@@ -9549,6 +9643,7 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
9549
9643
  })
9550
9644
  });
9551
9645
  }
9646
+ var NodeWrapper$1 = React.memo(NodeWrapper);
9552
9647
  const selector$b = (s)=>({
9553
9648
  nodesDraggable: s.nodesDraggable,
9554
9649
  nodesConnectable: s.nodesConnectable,
@@ -9588,7 +9683,7 @@ function NodeRendererComponent(props) {
9588
9683
  * moved into `NodeComponentWrapper`. This ensures they are
9589
9684
  * memorized – so if `NodeRenderer` *has* to rerender, it only
9590
9685
  * needs to regenerate the list of nodes, nothing else.
9591
- */ jsxRuntime.jsx(NodeWrapper, {
9686
+ */ jsxRuntime.jsx(NodeWrapper$1, {
9592
9687
  id: nodeId,
9593
9688
  nodeTypes: props.nodeTypes,
9594
9689
  nodeExtent: props.nodeExtent,
@@ -10582,6 +10677,7 @@ function EdgeWrapper({ id, edgesFocusable, edgesReconnectable, elementsSelectabl
10582
10677
  })
10583
10678
  });
10584
10679
  }
10680
+ var EdgeWrapper$1 = React.memo(EdgeWrapper);
10585
10681
  const selector$a = (s)=>({
10586
10682
  edgesFocusable: s.edgesFocusable,
10587
10683
  edgesReconnectable: s.edgesReconnectable,
@@ -10600,7 +10696,7 @@ function EdgeRendererComponent({ defaultMarkerColor, onlyRenderVisibleElements,
10600
10696
  rfId: rfId
10601
10697
  }),
10602
10698
  edgeIds.map((id)=>{
10603
- return jsxRuntime.jsx(EdgeWrapper, {
10699
+ return jsxRuntime.jsx(EdgeWrapper$1, {
10604
10700
  id: id,
10605
10701
  edgesFocusable: edgesFocusable,
10606
10702
  edgesReconnectable: edgesReconnectable,
@@ -10985,8 +11081,8 @@ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, heig
10985
11081
  }
10986
11082
  return {
10987
11083
  rfId: '1',
10988
- width: 0,
10989
- height: 0,
11084
+ width: width ?? 0,
11085
+ height: height ?? 0,
10990
11086
  transform,
10991
11087
  nodes: storeNodes,
10992
11088
  nodesInitialized,
@@ -12600,7 +12696,7 @@ var css_248z = "/* this gets exported as style.css and can be used for the defau
12600
12696
  styleInject(css_248z);
12601
12697
 
12602
12698
  /**
12603
- * @license lucide-react v0.542.0 - ISC
12699
+ * @license lucide-react v0.546.0 - ISC
12604
12700
  *
12605
12701
  * This source code is licensed under the ISC license.
12606
12702
  * See the LICENSE file in the root directory of this source tree.
@@ -12622,7 +12718,7 @@ const hasA11yProp = (props)=>{
12622
12718
  };
12623
12719
 
12624
12720
  /**
12625
- * @license lucide-react v0.542.0 - ISC
12721
+ * @license lucide-react v0.546.0 - ISC
12626
12722
  *
12627
12723
  * This source code is licensed under the ISC license.
12628
12724
  * See the LICENSE file in the root directory of this source tree.
@@ -14732,18 +14828,66 @@ function cn(...inputs) {
14732
14828
  return str.split(/[-_\s]+/).map((word)=>word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
14733
14829
  }
14734
14830
  /**
14735
- * Formats a currency value (USD dollars)
14831
+ * Formats a currency value (USD dollars) with dynamic precision
14736
14832
  * @param value - The dollar value to format
14737
- * @param precision - The number of decimal places to show. Defaults to 3.
14738
- * @returns The formatted dollar string (eg: $123.002)
14739
- */ function formatCurrency(value, precision = 3) {
14833
+ * @returns The formatted dollar string with appropriate precision
14834
+ */ function formatCurrency(value) {
14740
14835
  if (value === null || value === undefined) {
14741
- return "$0.000";
14836
+ return "$0.00";
14837
+ }
14838
+ const absValue = Math.abs(value);
14839
+ // Handle zero
14840
+ if (absValue === 0) {
14841
+ return "$0.00";
14842
+ }
14843
+ // Handle whole numbers - format with 3 decimal places
14844
+ if (Number.isInteger(absValue)) {
14845
+ const sign = value < 0 ? "-" : "";
14846
+ return `${sign}$${absValue.toFixed(3)}`;
14847
+ }
14848
+ // For decimal numbers, find the first non-zero decimal place
14849
+ const valueStr = absValue.toString();
14850
+ const decimalIndex = valueStr.indexOf(".");
14851
+ if (decimalIndex === -1) {
14852
+ // No decimal point, treat as whole number
14853
+ const sign = value < 0 ? "-" : "";
14854
+ return `${sign}$${absValue.toFixed(3)}`;
14855
+ }
14856
+ // Find the first non-zero digit after the decimal point
14857
+ let precision = 1;
14858
+ const decimalPart = valueStr.substring(decimalIndex + 1);
14859
+ for(let i = 0; i < decimalPart.length; i++){
14860
+ if (decimalPart[i] !== "0") {
14861
+ precision = i + 1;
14862
+ break;
14863
+ }
14864
+ }
14865
+ // For values >= 1, ensure we show at least 3 decimal places for readability
14866
+ if (absValue >= 1) {
14867
+ precision = Math.max(precision, 3);
14742
14868
  }
14743
- // Format the dollar value with sp ecified precision
14744
- const formatted = Math.abs(value).toFixed(precision);
14869
+ // Cap precision at 8 decimal places
14870
+ precision = Math.min(precision, 8);
14871
+ // Check if the value rounds to a whole number when rounded to the nearest integer
14872
+ // This handles cases like 123.999 -> 124.000, but only if the value is very close to a whole number
14873
+ const roundedToInteger = Math.round(absValue);
14874
+ if (roundedToInteger >= 1 && Number.isInteger(roundedToInteger) && Math.abs(absValue - roundedToInteger) < 0.01) {
14875
+ const sign = value < 0 ? "-" : "";
14876
+ return `${sign}$${roundedToInteger.toFixed(3)}`;
14877
+ }
14878
+ // Round to the determined precision
14879
+ const rounded = absValue.toFixed(precision);
14745
14880
  const sign = value < 0 ? "-" : "";
14746
- return `${sign}$${formatted}`;
14881
+ const roundedNum = parseFloat(rounded);
14882
+ // If the result is effectively zero (like $0.00000000), display as $0.00
14883
+ if (roundedNum === 0 || rounded === "0.00000000") {
14884
+ return "$0.00";
14885
+ }
14886
+ // If the rounded value is a whole number and >= 1, show 3 decimal places
14887
+ if (roundedNum >= 1 && Number.isInteger(roundedNum)) {
14888
+ return `${sign}$${roundedNum.toFixed(3)}`;
14889
+ }
14890
+ return `${sign}$${rounded}`;
14747
14891
  }
14748
14892
  /**
14749
14893
  * Formats latency in a human-readable format
@@ -16359,7 +16503,7 @@ const CountUpCurrency = ({ value, prefix = "", suffix = "" })=>{
16359
16503
  end: value,
16360
16504
  duration: DEFAULT_COUNTUP_DURATION,
16361
16505
  separator: ",",
16362
- decimals: 2,
16506
+ decimals: 3,
16363
16507
  decimal: ".",
16364
16508
  prefix: prefix,
16365
16509
  useEasing: true,
@@ -16986,8 +17130,8 @@ const AgentNode = ({ data, id, onInspect })=>{
16986
17130
  const nodeIcon = getNodeIcon(modelProvider);
16987
17131
  const modelName = data.details?.internals?.llm_details?.[data.details?.internals?.llm_details?.length - 1]?.model_name || "Unknown";
16988
17132
  const totalCost = sumTotalCost(data.details?.internals?.llm_details || []);
16989
- const totalCostFormatted = formatCurrency(totalCost, 2);
16990
- const totalCostTooltip = `Total cost: ${formatCurrency(totalCost, 5)} USD`;
17133
+ const totalCostFormatted = formatCurrency(totalCost);
17134
+ const totalCostTooltip = `Total cost: ${formatCurrency(totalCost)} USD`;
16991
17135
  const nodeDetails = getOverviewLlmDetails(data.details?.internals?.llm_details || []);
16992
17136
  const inputArray = nodeDetails?.input || [];
16993
17137
  const output = nodeDetails?.output || null;
@@ -19584,7 +19728,7 @@ var correctTargets = function(parent, targets) {
19584
19728
  };
19585
19729
 
19586
19730
  var DIALOG_NAME = "Dialog";
19587
- var [createDialogContext, createDialogScope] = createContextScope(DIALOG_NAME);
19731
+ var [createDialogContext] = createContextScope(DIALOG_NAME);
19588
19732
  var [DialogProvider, useDialogContext] = createDialogContext(DIALOG_NAME);
19589
19733
  var Dialog = (props)=>{
19590
19734
  const { __scopeDialog, children, open: openProp, defaultOpen, onOpenChange, modal = true } = props;
@@ -20199,7 +20343,7 @@ const nodeTypes = {
20199
20343
  const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", height = "100dvh", className = "", defaultZoom = 1, defaultPan = {
20200
20344
  x: 0,
20201
20345
  y: 0
20202
- }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect })=>{
20346
+ }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect, panToNodeId })=>{
20203
20347
  const { theme, isDarkMode } = useTheme();
20204
20348
  const themeColors = theme.colors;
20205
20349
  // Helper function to determine if we should apply minimum dimensions
@@ -20545,6 +20689,39 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", heigh
20545
20689
  defaultAutoFitDuration,
20546
20690
  defaultAutoFitDelay
20547
20691
  ]);
20692
+ // Pan to specific node when panToNodeId prop is provided
20693
+ React.useEffect(()=>{
20694
+ if (!panToNodeId || !reactFlowInstance || disableAutoFit || nodes.length === 0) {
20695
+ return;
20696
+ }
20697
+ // Check if the specified node ID exists in the current nodes array
20698
+ const targetNode = nodes.find((node)=>node.id === panToNodeId);
20699
+ if (!targetNode) {
20700
+ return;
20701
+ }
20702
+ // Pan to the specified node
20703
+ setTimeout(()=>{
20704
+ reactFlowInstance.fitView({
20705
+ nodes: [
20706
+ {
20707
+ id: panToNodeId
20708
+ }
20709
+ ],
20710
+ duration: defaultAutoFitDuration,
20711
+ padding: 0.2,
20712
+ minZoom: defaultZoom,
20713
+ maxZoom: defaultZoom
20714
+ });
20715
+ }, defaultAutoFitDelay);
20716
+ }, [
20717
+ panToNodeId,
20718
+ reactFlowInstance,
20719
+ disableAutoFit,
20720
+ nodes,
20721
+ defaultAutoFitDuration,
20722
+ defaultAutoFitDelay,
20723
+ defaultZoom
20724
+ ]);
20548
20725
  return /*#__PURE__*/ React.createElement("div", {
20549
20726
  ref: containerRef,
20550
20727
  style: {
@@ -23291,7 +23468,7 @@ var SELECTION_KEYS = [
23291
23468
  ];
23292
23469
  var SELECT_NAME = "Select";
23293
23470
  var [Collection, useCollection, createCollectionScope] = createCollection(SELECT_NAME);
23294
- var [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME, [
23471
+ var [createSelectContext] = createContextScope(SELECT_NAME, [
23295
23472
  createCollectionScope,
23296
23473
  createPopperScope
23297
23474
  ]);
@@ -24807,7 +24984,7 @@ const FileSelector = ({ files, currentFile, onFileSelect, onRefresh, loading = f
24807
24984
  };
24808
24985
 
24809
24986
  var CHECKBOX_NAME = "Checkbox";
24810
- var [createCheckboxContext, createCheckboxScope] = createContextScope(CHECKBOX_NAME);
24987
+ var [createCheckboxContext] = createContextScope(CHECKBOX_NAME);
24811
24988
  var [CheckboxProviderImpl, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME);
24812
24989
  function CheckboxProvider(props) {
24813
24990
  const { __scopeCheckbox, checked: checkedProp, children, defaultChecked, disabled, form, name, onCheckedChange, required, value = "on", // @ts-expect-error