@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/esm/index.js CHANGED
@@ -3338,27 +3338,25 @@ const getNodePositionWithOrigin = (node, nodeOrigin = [
3338
3338
  * Determines a bounding box that contains all given nodes in an array
3339
3339
  * @internal
3340
3340
  */ const getInternalNodesBounds = (nodeLookup, params = {})=>{
3341
- if (nodeLookup.size === 0) {
3342
- return {
3343
- x: 0,
3344
- y: 0,
3345
- width: 0,
3346
- height: 0
3347
- };
3348
- }
3349
3341
  let box = {
3350
3342
  x: Infinity,
3351
3343
  y: Infinity,
3352
3344
  x2: -Infinity,
3353
3345
  y2: -Infinity
3354
3346
  };
3347
+ let hasVisibleNodes = false;
3355
3348
  nodeLookup.forEach((node)=>{
3356
3349
  if (params.filter === undefined || params.filter(node)) {
3357
- const nodeBox = nodeToBox(node);
3358
- box = getBoundsOfBoxes(box, nodeBox);
3350
+ box = getBoundsOfBoxes(box, nodeToBox(node));
3351
+ hasVisibleNodes = true;
3359
3352
  }
3360
3353
  });
3361
- return boxToRect(box);
3354
+ return hasVisibleNodes ? boxToRect(box) : {
3355
+ x: 0,
3356
+ y: 0,
3357
+ width: 0,
3358
+ height: 0
3359
+ };
3362
3360
  };
3363
3361
  const getNodesInside = (nodes, rect, [tx, ty, tScale] = [
3364
3362
  0,
@@ -4103,7 +4101,7 @@ function getEdgeCenter({ sourceX, sourceY, targetX, targetY }) {
4103
4101
  return zIndex;
4104
4102
  }
4105
4103
  const edgeZ = elevateOnSelect && selected ? 1000 : 0;
4106
- const nodeZ = Math.max(sourceNode.parentId ? sourceNode.internals.z : 0, targetNode.parentId ? targetNode.internals.z : 0);
4104
+ const nodeZ = Math.max(sourceNode.parentId || elevateOnSelect && sourceNode.selected ? sourceNode.internals.z : 0, targetNode.parentId || elevateOnSelect && targetNode.selected ? targetNode.internals.z : 0);
4107
4105
  return edgeZ + nodeZ;
4108
4106
  }
4109
4107
  function isEdgeVisible({ sourceNode, targetNode, width, height, transform }) {
@@ -4619,6 +4617,8 @@ function createMarkerIds(edges, { id, defaultColor, defaultMarkerStart, defaultM
4619
4617
  return markers;
4620
4618
  }, []).sort((a, b)=>a.id.localeCompare(b.id));
4621
4619
  }
4620
+ const SELECTED_NODE_Z = 1000;
4621
+ const ROOT_PARENT_Z_INCREMENT = 10;
4622
4622
  const defaultOptions = {
4623
4623
  nodeOrigin: [
4624
4624
  0,
@@ -4657,11 +4657,42 @@ function updateAbsolutePositions(nodeLookup, parentLookup, options) {
4657
4657
  }
4658
4658
  }
4659
4659
  }
4660
+ function parseHandles(userNode, internalNode) {
4661
+ if (!userNode.handles) {
4662
+ return !userNode.measured ? undefined : internalNode?.internals.handleBounds;
4663
+ }
4664
+ const source = [];
4665
+ const target = [];
4666
+ for (const handle of userNode.handles){
4667
+ const handleBounds = {
4668
+ id: handle.id,
4669
+ width: handle.width ?? 1,
4670
+ height: handle.height ?? 1,
4671
+ nodeId: userNode.id,
4672
+ x: handle.x,
4673
+ y: handle.y,
4674
+ position: handle.position,
4675
+ type: handle.type
4676
+ };
4677
+ if (handle.type === 'source') {
4678
+ source.push(handleBounds);
4679
+ } else if (handle.type === 'target') {
4680
+ target.push(handleBounds);
4681
+ }
4682
+ }
4683
+ return {
4684
+ source,
4685
+ target
4686
+ };
4687
+ }
4660
4688
  function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
4661
4689
  const _options = mergeObjects(adoptUserNodesDefaultOptions, options);
4690
+ let rootParentIndex = {
4691
+ i: -1
4692
+ };
4662
4693
  let nodesInitialized = nodes.length > 0;
4663
4694
  const tmpLookup = new Map(nodeLookup);
4664
- const selectedNodeZ = _options?.elevateNodesOnSelect ? 1000 : 0;
4695
+ const selectedNodeZ = _options?.elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
4665
4696
  nodeLookup.clear();
4666
4697
  parentLookup.clear();
4667
4698
  for (const userNode of nodes){
@@ -4682,7 +4713,7 @@ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
4682
4713
  internals: {
4683
4714
  positionAbsolute: clampedPosition,
4684
4715
  // if user re-initializes the node or removes `measured` for whatever reason, we reset the handleBounds so that the node gets re-measured
4685
- handleBounds: !userNode.measured ? undefined : internalNode?.internals.handleBounds,
4716
+ handleBounds: parseHandles(userNode, internalNode),
4686
4717
  z: calculateZ(userNode, selectedNodeZ),
4687
4718
  userNode
4688
4719
  }
@@ -4693,7 +4724,7 @@ function adoptUserNodes(nodes, nodeLookup, parentLookup, options) {
4693
4724
  nodesInitialized = false;
4694
4725
  }
4695
4726
  if (userNode.parentId) {
4696
- updateChildNode(internalNode, nodeLookup, parentLookup, options);
4727
+ updateChildNode(internalNode, nodeLookup, parentLookup, options, rootParentIndex);
4697
4728
  }
4698
4729
  }
4699
4730
  return nodesInitialized;
@@ -4716,7 +4747,7 @@ function updateParentLookup(node, parentLookup) {
4716
4747
  }
4717
4748
  /**
4718
4749
  * Updates positionAbsolute and zIndex of a child node and the parentLookup.
4719
- */ function updateChildNode(node, nodeLookup, parentLookup, options) {
4750
+ */ function updateChildNode(node, nodeLookup, parentLookup, options, rootParentIndex) {
4720
4751
  const { elevateNodesOnSelect, nodeOrigin, nodeExtent } = mergeObjects(defaultOptions, options);
4721
4752
  const parentId = node.parentId;
4722
4753
  const parentNode = nodeLookup.get(parentId);
@@ -4725,7 +4756,16 @@ function updateParentLookup(node, parentLookup) {
4725
4756
  return;
4726
4757
  }
4727
4758
  updateParentLookup(node, parentLookup);
4728
- const selectedNodeZ = elevateNodesOnSelect ? 1000 : 0;
4759
+ // We just want to set the rootParentIndex for the first child
4760
+ if (rootParentIndex && !parentNode.parentId && parentNode.internals.rootParentIndex === undefined) {
4761
+ parentNode.internals.rootParentIndex = ++rootParentIndex.i;
4762
+ parentNode.internals.z = parentNode.internals.z + rootParentIndex.i * ROOT_PARENT_Z_INCREMENT;
4763
+ }
4764
+ // But we need to update rootParentIndex.i also when parent has not been updated
4765
+ if (rootParentIndex && parentNode.internals.rootParentIndex !== undefined) {
4766
+ rootParentIndex.i = parentNode.internals.rootParentIndex;
4767
+ }
4768
+ const selectedNodeZ = elevateNodesOnSelect ? SELECTED_NODE_Z : 0;
4729
4769
  const { x, y, z } = calculateChildXYZ(node, parentNode, nodeOrigin, nodeExtent, selectedNodeZ);
4730
4770
  const { positionAbsolute } = node.internals;
4731
4771
  const positionChanged = x !== positionAbsolute.x || y !== positionAbsolute.y;
@@ -5299,8 +5339,10 @@ function XYDrag({ onNodeMouseDown, getStoreItems, onDragStart, onDrag, onDragSto
5299
5339
  autoPan();
5300
5340
  }
5301
5341
  if (!dragStarted) {
5302
- const x = pointerPos.xSnapped - (lastPos.x ?? 0);
5303
- const y = pointerPos.ySnapped - (lastPos.y ?? 0);
5342
+ // Calculate distance in client coordinates for consistent drag threshold behavior across zoom levels
5343
+ const currentMousePosition = getEventPosition(event.sourceEvent, containerBounds);
5344
+ const x = currentMousePosition.x - mousePosition.x;
5345
+ const y = currentMousePosition.y - mousePosition.y;
5304
5346
  const distance = Math.sqrt(x * x + y * y);
5305
5347
  if (distance > nodeDragThreshold) {
5306
5348
  startDrag(event);
@@ -5584,6 +5626,10 @@ function onPointerDown(event, { connectionMode, connectionRadius, handleId, node
5584
5626
  previousConnection = newConnection;
5585
5627
  }
5586
5628
  function onPointerUp(event) {
5629
+ // Prevent multi-touch aborting connection
5630
+ if ('touches' in event && event.touches.length > 0) {
5631
+ return;
5632
+ }
5587
5633
  if (connectionStarted) {
5588
5634
  if ((closestHandle || resultHandleDomNode) && connection && isValid) {
5589
5635
  onConnect?.(connection);
@@ -5741,8 +5787,7 @@ function XYMinimap({ domNode, panZoom, getTransform, getViewScale }) {
5741
5787
  pointer
5742
5788
  };
5743
5789
  }
5744
- /* eslint-disable @typescript-eslint/no-explicit-any */ const viewChanged = (prevViewport, eventViewport)=>prevViewport.x !== eventViewport.x || prevViewport.y !== eventViewport.y || prevViewport.zoom !== eventViewport.k;
5745
- const transformToViewport = (transform)=>({
5790
+ /* eslint-disable @typescript-eslint/no-explicit-any */ const transformToViewport = (transform)=>({
5746
5791
  x: transform.x,
5747
5792
  y: transform.y,
5748
5793
  zoom: transform.k
@@ -5766,6 +5811,9 @@ const wheelDelta = (event)=>{
5766
5811
  function createPanOnScrollHandler({ zoomPanValues, noWheelClassName, d3Selection, d3Zoom, panOnScrollMode, panOnScrollSpeed, zoomOnPinch, onPanZoomStart, onPanZoom, onPanZoomEnd }) {
5767
5812
  return (event)=>{
5768
5813
  if (isWrappedWithClass(event, noWheelClassName)) {
5814
+ if (event.ctrlKey) {
5815
+ event.preventDefault(); // stop native page zoom for pinch zooming
5816
+ }
5769
5817
  return false;
5770
5818
  }
5771
5819
  event.preventDefault();
@@ -5804,8 +5852,7 @@ function createPanOnScrollHandler({ zoomPanValues, noWheelClassName, d3Selection
5804
5852
  */ if (!zoomPanValues.isPanScrolling) {
5805
5853
  zoomPanValues.isPanScrolling = true;
5806
5854
  onPanZoomStart?.(event, nextViewport);
5807
- }
5808
- if (zoomPanValues.isPanScrolling) {
5855
+ } else {
5809
5856
  onPanZoom?.(event, nextViewport);
5810
5857
  zoomPanValues.panScrollTimeout = setTimeout(()=>{
5811
5858
  onPanZoomEnd?.(event, nextViewport);
@@ -5875,7 +5922,7 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5875
5922
  }
5876
5923
  zoomPanValues.usedRightMouseButton = false;
5877
5924
  onDraggingChange(false);
5878
- if (onPanZoomEnd && viewChanged(zoomPanValues.prevViewport, event.transform)) {
5925
+ if (onPanZoomEnd) {
5879
5926
  const viewport = transformToViewport(event.transform);
5880
5927
  zoomPanValues.prevViewport = viewport;
5881
5928
  clearTimeout(zoomPanValues.timerId);
@@ -5886,10 +5933,11 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5886
5933
  }
5887
5934
  };
5888
5935
  }
5889
- /* eslint-disable @typescript-eslint/no-explicit-any */ function createFilter({ zoomActivationKeyPressed, zoomOnScroll, zoomOnPinch, panOnDrag, panOnScroll, zoomOnDoubleClick, userSelectionActive, noWheelClassName, noPanClassName, lib }) {
5936
+ /* eslint-disable @typescript-eslint/no-explicit-any */ function createFilter({ zoomActivationKeyPressed, zoomOnScroll, zoomOnPinch, panOnDrag, panOnScroll, zoomOnDoubleClick, userSelectionActive, noWheelClassName, noPanClassName, lib, connectionInProgress }) {
5890
5937
  return (event)=>{
5891
5938
  const zoomScroll = zoomActivationKeyPressed || zoomOnScroll;
5892
5939
  const pinchZoom = zoomOnPinch && event.ctrlKey;
5940
+ const isWheelEvent = event.type === 'wheel';
5893
5941
  if (event.button === 1 && event.type === 'mousedown' && (isWrappedWithClass(event, `${lib}-flow__node`) || isWrappedWithClass(event, `${lib}-flow__edge`))) {
5894
5942
  return true;
5895
5943
  }
@@ -5901,15 +5949,19 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5901
5949
  if (userSelectionActive) {
5902
5950
  return false;
5903
5951
  }
5952
+ // we want to disable pinch-zooming while making a connection
5953
+ if (connectionInProgress && !isWheelEvent) {
5954
+ return false;
5955
+ }
5904
5956
  // if the target element is inside an element with the nowheel class, we prevent zooming
5905
- if (isWrappedWithClass(event, noWheelClassName) && event.type === 'wheel') {
5957
+ if (isWrappedWithClass(event, noWheelClassName) && isWheelEvent) {
5906
5958
  return false;
5907
5959
  }
5908
5960
  // if the target element is inside an element with the nopan class, we prevent panning
5909
- if (isWrappedWithClass(event, noPanClassName) && (event.type !== 'wheel' || panOnScroll && event.type === 'wheel' && !zoomActivationKeyPressed)) {
5961
+ if (isWrappedWithClass(event, noPanClassName) && (!isWheelEvent || panOnScroll && isWheelEvent && !zoomActivationKeyPressed)) {
5910
5962
  return false;
5911
5963
  }
5912
- if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') {
5964
+ if (!zoomOnPinch && event.ctrlKey && isWheelEvent) {
5913
5965
  return false;
5914
5966
  }
5915
5967
  if (!zoomOnPinch && event.type === 'touchstart' && event.touches?.length > 1) {
@@ -5917,7 +5969,7 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5917
5969
  return false;
5918
5970
  }
5919
5971
  // when there is no scroll handling enabled, we prevent all wheel events
5920
- if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') {
5972
+ if (!zoomScroll && !panOnScroll && !pinchZoom && isWheelEvent) {
5921
5973
  return false;
5922
5974
  }
5923
5975
  // if the pane is not movable, we prevent dragging it with mousestart or touchstart
@@ -5931,7 +5983,7 @@ function createPanZoomEndHandler({ zoomPanValues, panOnDrag, panOnScroll, onDrag
5931
5983
  // We only allow right clicks if pan on drag is set to right click
5932
5984
  const buttonAllowed = Array.isArray(panOnDrag) && panOnDrag.includes(event.button) || !event.button || event.button <= 1;
5933
5985
  // default filter for d3-zoom
5934
- return (!event.ctrlKey || event.type === 'wheel') && buttonAllowed;
5986
+ return (!event.ctrlKey || isWheelEvent) && buttonAllowed;
5935
5987
  };
5936
5988
  }
5937
5989
  function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExtent, viewport, onPanZoom, onPanZoomStart, onPanZoomEnd, onDraggingChange }) {
@@ -5939,10 +5991,7 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
5939
5991
  isZoomingOrPanning: false,
5940
5992
  usedRightMouseButton: false,
5941
5993
  prevViewport: {
5942
- x: 0,
5943
- y: 0,
5944
- zoom: 0
5945
- },
5994
+ },
5946
5995
  mouseButton: 0,
5947
5996
  timerId: undefined,
5948
5997
  panScrollTimeout: undefined,
@@ -5980,7 +6029,7 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
5980
6029
  return Promise.resolve(false);
5981
6030
  }
5982
6031
  // public functions
5983
- function update({ noWheelClassName, noPanClassName, onPaneContextMenu, userSelectionActive, panOnScroll, panOnDrag, panOnScrollMode, panOnScrollSpeed, preventScrolling, zoomOnPinch, zoomOnScroll, zoomOnDoubleClick, zoomActivationKeyPressed, lib, onTransformChange }) {
6032
+ function update({ noWheelClassName, noPanClassName, onPaneContextMenu, userSelectionActive, panOnScroll, panOnDrag, panOnScrollMode, panOnScrollSpeed, preventScrolling, zoomOnPinch, zoomOnScroll, zoomOnDoubleClick, zoomActivationKeyPressed, lib, onTransformChange, connectionInProgress }) {
5984
6033
  if (userSelectionActive && !zoomPanValues.isZoomingOrPanning) {
5985
6034
  destroy();
5986
6035
  }
@@ -6042,7 +6091,8 @@ function XYPanZoom({ domNode, minZoom, maxZoom, paneClickDistance, translateExte
6042
6091
  userSelectionActive,
6043
6092
  noPanClassName,
6044
6093
  noWheelClassName,
6045
- lib
6094
+ lib,
6095
+ connectionInProgress
6046
6096
  });
6047
6097
  d3ZoomInstance.filter(filter);
6048
6098
  /*
@@ -6388,6 +6438,17 @@ function nodeToChildExtent(child, parent, nodeOrigin) {
6388
6438
  }
6389
6439
  function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6390
6440
  const selection = select(domNode);
6441
+ let params = {
6442
+ controlDirection: getControlDirection('bottom-right'),
6443
+ boundaries: {
6444
+ minWidth: 0,
6445
+ minHeight: 0,
6446
+ maxWidth: Number.MAX_VALUE,
6447
+ maxHeight: Number.MAX_VALUE
6448
+ },
6449
+ resizeDirection: undefined,
6450
+ keepAspectRatio: false
6451
+ };
6391
6452
  function update({ controlPosition, boundaries, keepAspectRatio, resizeDirection, onResizeStart, onResize, onResizeEnd, shouldResize }) {
6392
6453
  let prevValues = {
6393
6454
  ...initPrevValues$1
@@ -6395,13 +6456,20 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6395
6456
  let startValues = {
6396
6457
  ...initStartValues
6397
6458
  };
6398
- const controlDirection = getControlDirection(controlPosition);
6459
+ params = {
6460
+ boundaries,
6461
+ resizeDirection,
6462
+ keepAspectRatio,
6463
+ controlDirection: getControlDirection(controlPosition)
6464
+ };
6399
6465
  let node = undefined;
6400
6466
  let containerBounds = null;
6401
6467
  let childNodes = [];
6402
6468
  let parentNode = undefined; // Needed to fix expandParent
6403
6469
  let parentExtent = undefined;
6404
6470
  let childExtent = undefined;
6471
+ // we only want to trigger onResizeEnd if onResize was actually called
6472
+ let resizeDetected = false;
6405
6473
  const dragHandler = drag().on('start', (event)=>{
6406
6474
  const { nodeLookup, transform, snapGrid, snapToGrid, nodeOrigin, paneDomNode } = getStoreItems();
6407
6475
  node = nodeLookup.get(nodeId);
@@ -6483,7 +6551,7 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6483
6551
  const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues;
6484
6552
  const change = {};
6485
6553
  const nodeOrigin = node.origin ?? storeNodeOrigin;
6486
- const { width, height, x, y } = getDimensionsAfterResize(startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, nodeOrigin, parentExtent, childExtent);
6554
+ const { width, height, x, y } = getDimensionsAfterResize(startValues, params.controlDirection, pointerPosition, params.boundaries, params.keepAspectRatio, nodeOrigin, parentExtent, childExtent);
6487
6555
  const isWidthChange = width !== prevWidth;
6488
6556
  const isHeightChange = height !== prevHeight;
6489
6557
  const isXPosChange = x !== prevX && isWidthChange;
@@ -6512,8 +6580,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6512
6580
  }
6513
6581
  }
6514
6582
  if (isWidthChange || isHeightChange) {
6515
- change.width = isWidthChange && (!resizeDirection || resizeDirection === 'horizontal') ? width : prevValues.width;
6516
- change.height = isHeightChange && (!resizeDirection || resizeDirection === 'vertical') ? height : prevValues.height;
6583
+ change.width = isWidthChange && (!params.resizeDirection || params.resizeDirection === 'horizontal') ? width : prevValues.width;
6584
+ change.height = isHeightChange && (!params.resizeDirection || params.resizeDirection === 'vertical') ? height : prevValues.height;
6517
6585
  prevValues.width = change.width;
6518
6586
  prevValues.height = change.height;
6519
6587
  }
@@ -6535,8 +6603,8 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6535
6603
  prevWidth,
6536
6604
  height: prevValues.height,
6537
6605
  prevHeight,
6538
- affectsX: controlDirection.affectsX,
6539
- affectsY: controlDirection.affectsY
6606
+ affectsX: params.controlDirection.affectsX,
6607
+ affectsY: params.controlDirection.affectsY
6540
6608
  });
6541
6609
  const nextValues = {
6542
6610
  ...prevValues,
@@ -6546,15 +6614,20 @@ function XYResizer({ domNode, nodeId, getStoreItems, onChange, onEnd }) {
6546
6614
  if (callResize === false) {
6547
6615
  return;
6548
6616
  }
6617
+ resizeDetected = true;
6549
6618
  onResize?.(event, nextValues);
6550
6619
  onChange(change, childChanges);
6551
6620
  }).on('end', (event)=>{
6621
+ if (!resizeDetected) {
6622
+ return;
6623
+ }
6552
6624
  onResizeEnd?.(event, {
6553
6625
  ...prevValues
6554
6626
  });
6555
6627
  onEnd?.({
6556
6628
  ...prevValues
6557
6629
  });
6630
+ resizeDetected = false;
6558
6631
  });
6559
6632
  selection.call(dragHandler);
6560
6633
  }
@@ -8175,7 +8248,11 @@ const selector$k = (s)=>!!s.panZoom;
8175
8248
  deletedEdges: matchingEdges
8176
8249
  };
8177
8250
  },
8178
- getIntersectingNodes: (nodeOrRect, partially = true, nodes)=>{
8251
+ /**
8252
+ * Partial is defined as "the 2 nodes/areas are intersecting partially".
8253
+ * If a is contained in b or b is contained in a, they are both
8254
+ * considered fully intersecting.
8255
+ */ getIntersectingNodes: (nodeOrRect, partially = true, nodes)=>{
8179
8256
  const isRect = isRectObject(nodeOrRect);
8180
8257
  const nodeRect = isRect ? nodeOrRect : getNodeRect(nodeOrRect);
8181
8258
  const hasNodesOption = nodes !== undefined;
@@ -8201,7 +8278,7 @@ const selector$k = (s)=>!!s.panZoom;
8201
8278
  }
8202
8279
  const overlappingArea = getOverlappingArea(nodeRect, area);
8203
8280
  const partiallyVisible = partially && overlappingArea > 0;
8204
- return partiallyVisible || overlappingArea >= nodeRect.width * nodeRect.height;
8281
+ return partiallyVisible || overlappingArea >= area.width * area.height || overlappingArea >= nodeRect.width * nodeRect.height;
8205
8282
  },
8206
8283
  updateNode,
8207
8284
  updateNodeData: (id, dataUpdate, options = {
@@ -8320,7 +8397,7 @@ const win$1 = typeof window !== 'undefined' ? window : undefined;
8320
8397
  const store = useStoreApi();
8321
8398
  useEffect(()=>{
8322
8399
  const updateDimensions = ()=>{
8323
- if (!domNode.current) {
8400
+ if (!domNode.current || !(domNode.current.checkVisibility?.() ?? true)) {
8324
8401
  return false;
8325
8402
  }
8326
8403
  const size = getDimensions$1(domNode.current);
@@ -8355,12 +8432,13 @@ const containerStyle = {
8355
8432
  };
8356
8433
  const selector$j = (s)=>({
8357
8434
  userSelectionActive: s.userSelectionActive,
8358
- lib: s.lib
8435
+ lib: s.lib,
8436
+ connectionInProgress: s.connection.inProgress
8359
8437
  });
8360
8438
  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 }) {
8361
8439
  const store = useStoreApi();
8362
8440
  const zoomPane = useRef(null);
8363
- const { userSelectionActive, lib } = useStore(selector$j, shallow$1);
8441
+ const { userSelectionActive, lib, connectionInProgress } = useStore(selector$j, shallow$1);
8364
8442
  const zoomActivationKeyPressed = useKeyPress(zoomActivationKeyCode);
8365
8443
  const panZoom = useRef();
8366
8444
  useResizeHandler(zoomPane);
@@ -8438,7 +8516,8 @@ function ZoomPane({ onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true,
8438
8516
  userSelectionActive,
8439
8517
  noWheelClassName,
8440
8518
  lib,
8441
- onTransformChange
8519
+ onTransformChange,
8520
+ connectionInProgress
8442
8521
  });
8443
8522
  }, [
8444
8523
  onPaneContextMenu,
@@ -8455,7 +8534,8 @@ function ZoomPane({ onPaneContextMenu, zoomOnScroll = true, zoomOnPinch = true,
8455
8534
  userSelectionActive,
8456
8535
  noWheelClassName,
8457
8536
  lib,
8458
- onTransformChange
8537
+ onTransformChange,
8538
+ connectionInProgress
8459
8539
  ]);
8460
8540
  return jsx("div", {
8461
8541
  className: "react-flow__renderer",
@@ -8529,12 +8609,25 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
8529
8609
  onPaneContextMenu?.(event);
8530
8610
  };
8531
8611
  const onWheel = onPaneScroll ? (event)=>onPaneScroll(event) : undefined;
8532
- const onPointerDown = (event)=>{
8612
+ const onClickCapture = (event)=>{
8613
+ const isSelectionOnDragActive = selectionOnDrag && container.current === event.target || !selectionOnDrag || selectionKeyPressed;
8614
+ if (!isSelectionOnDragActive) {
8615
+ return;
8616
+ }
8617
+ event.stopPropagation();
8618
+ };
8619
+ // We are using capture here in order to prevent other pointer events
8620
+ // to be able to create a selection above a node or an edge
8621
+ const onPointerDownCapture = (event)=>{
8533
8622
  const { resetSelectedElements, domNode } = store.getState();
8534
8623
  containerBounds.current = domNode?.getBoundingClientRect();
8535
- if (!elementsSelectable || !isSelecting || event.button !== 0 || event.target !== container.current || !containerBounds.current) {
8624
+ const isNoKeyEvent = event.target !== container.current && !!event.target.closest('.nokey');
8625
+ const isSelectionActive = selectionOnDrag && container.current === event.target || !selectionOnDrag || selectionKeyPressed;
8626
+ if (!elementsSelectable || !isSelecting || event.button !== 0 || !containerBounds.current || isNoKeyEvent || !isSelectionActive || !event.isPrimary) {
8536
8627
  return;
8537
8628
  }
8629
+ event.stopPropagation();
8630
+ event.preventDefault();
8538
8631
  event.target?.setPointerCapture?.(event.pointerId);
8539
8632
  selectionStarted.current = true;
8540
8633
  selectionInProgress.current = false;
@@ -8638,9 +8731,10 @@ function Pane({ isSelecting, selectionKeyPressed, selectionMode = SelectionMode.
8638
8731
  onContextMenu: wrapHandler(onContextMenu, container),
8639
8732
  onWheel: wrapHandler(onWheel, container),
8640
8733
  onPointerEnter: hasActiveSelection ? undefined : onPaneMouseEnter,
8641
- onPointerDown: hasActiveSelection ? onPointerDown : onPaneMouseMove,
8642
8734
  onPointerMove: hasActiveSelection ? onPointerMove : onPaneMouseMove,
8643
8735
  onPointerUp: hasActiveSelection ? onPointerUp : undefined,
8736
+ onPointerDownCapture: hasActiveSelection ? onPointerDownCapture : undefined,
8737
+ onClickCapture: hasActiveSelection ? onClickCapture : undefined,
8644
8738
  onPointerLeave: onPaneMouseLeave,
8645
8739
  ref: container,
8646
8740
  style: containerStyle,
@@ -9529,6 +9623,7 @@ function NodeWrapper({ id, onClick, onMouseEnter, onMouseMove, onMouseLeave, onC
9529
9623
  })
9530
9624
  });
9531
9625
  }
9626
+ var NodeWrapper$1 = memo(NodeWrapper);
9532
9627
  const selector$b = (s)=>({
9533
9628
  nodesDraggable: s.nodesDraggable,
9534
9629
  nodesConnectable: s.nodesConnectable,
@@ -9568,7 +9663,7 @@ function NodeRendererComponent(props) {
9568
9663
  * moved into `NodeComponentWrapper`. This ensures they are
9569
9664
  * memorized – so if `NodeRenderer` *has* to rerender, it only
9570
9665
  * needs to regenerate the list of nodes, nothing else.
9571
- */ jsx(NodeWrapper, {
9666
+ */ jsx(NodeWrapper$1, {
9572
9667
  id: nodeId,
9573
9668
  nodeTypes: props.nodeTypes,
9574
9669
  nodeExtent: props.nodeExtent,
@@ -10562,6 +10657,7 @@ function EdgeWrapper({ id, edgesFocusable, edgesReconnectable, elementsSelectabl
10562
10657
  })
10563
10658
  });
10564
10659
  }
10660
+ var EdgeWrapper$1 = memo(EdgeWrapper);
10565
10661
  const selector$a = (s)=>({
10566
10662
  edgesFocusable: s.edgesFocusable,
10567
10663
  edgesReconnectable: s.edgesReconnectable,
@@ -10580,7 +10676,7 @@ function EdgeRendererComponent({ defaultMarkerColor, onlyRenderVisibleElements,
10580
10676
  rfId: rfId
10581
10677
  }),
10582
10678
  edgeIds.map((id)=>{
10583
- return jsx(EdgeWrapper, {
10679
+ return jsx(EdgeWrapper$1, {
10584
10680
  id: id,
10585
10681
  edgesFocusable: edgesFocusable,
10586
10682
  edgesReconnectable: edgesReconnectable,
@@ -10965,8 +11061,8 @@ const getInitialState = ({ nodes, edges, defaultNodes, defaultEdges, width, heig
10965
11061
  }
10966
11062
  return {
10967
11063
  rfId: '1',
10968
- width: 0,
10969
- height: 0,
11064
+ width: width ?? 0,
11065
+ height: height ?? 0,
10970
11066
  transform,
10971
11067
  nodes: storeNodes,
10972
11068
  nodesInitialized,
@@ -12580,7 +12676,7 @@ var css_248z = "/* this gets exported as style.css and can be used for the defau
12580
12676
  styleInject(css_248z);
12581
12677
 
12582
12678
  /**
12583
- * @license lucide-react v0.542.0 - ISC
12679
+ * @license lucide-react v0.546.0 - ISC
12584
12680
  *
12585
12681
  * This source code is licensed under the ISC license.
12586
12682
  * See the LICENSE file in the root directory of this source tree.
@@ -12602,7 +12698,7 @@ const hasA11yProp = (props)=>{
12602
12698
  };
12603
12699
 
12604
12700
  /**
12605
- * @license lucide-react v0.542.0 - ISC
12701
+ * @license lucide-react v0.546.0 - ISC
12606
12702
  *
12607
12703
  * This source code is licensed under the ISC license.
12608
12704
  * See the LICENSE file in the root directory of this source tree.
@@ -14712,18 +14808,66 @@ function cn(...inputs) {
14712
14808
  return str.split(/[-_\s]+/).map((word)=>word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
14713
14809
  }
14714
14810
  /**
14715
- * Formats a currency value (USD dollars)
14811
+ * Formats a currency value (USD dollars) with dynamic precision
14716
14812
  * @param value - The dollar value to format
14717
- * @param precision - The number of decimal places to show. Defaults to 3.
14718
- * @returns The formatted dollar string (eg: $123.002)
14719
- */ function formatCurrency(value, precision = 3) {
14813
+ * @returns The formatted dollar string with appropriate precision
14814
+ */ function formatCurrency(value) {
14720
14815
  if (value === null || value === undefined) {
14721
- return "$0.000";
14816
+ return "$0.00";
14817
+ }
14818
+ const absValue = Math.abs(value);
14819
+ // Handle zero
14820
+ if (absValue === 0) {
14821
+ return "$0.00";
14822
+ }
14823
+ // Handle whole numbers - format with 3 decimal places
14824
+ if (Number.isInteger(absValue)) {
14825
+ const sign = value < 0 ? "-" : "";
14826
+ return `${sign}$${absValue.toFixed(3)}`;
14827
+ }
14828
+ // For decimal numbers, find the first non-zero decimal place
14829
+ const valueStr = absValue.toString();
14830
+ const decimalIndex = valueStr.indexOf(".");
14831
+ if (decimalIndex === -1) {
14832
+ // No decimal point, treat as whole number
14833
+ const sign = value < 0 ? "-" : "";
14834
+ return `${sign}$${absValue.toFixed(3)}`;
14835
+ }
14836
+ // Find the first non-zero digit after the decimal point
14837
+ let precision = 1;
14838
+ const decimalPart = valueStr.substring(decimalIndex + 1);
14839
+ for(let i = 0; i < decimalPart.length; i++){
14840
+ if (decimalPart[i] !== "0") {
14841
+ precision = i + 1;
14842
+ break;
14843
+ }
14844
+ }
14845
+ // For values >= 1, ensure we show at least 3 decimal places for readability
14846
+ if (absValue >= 1) {
14847
+ precision = Math.max(precision, 3);
14722
14848
  }
14723
- // Format the dollar value with sp ecified precision
14724
- const formatted = Math.abs(value).toFixed(precision);
14849
+ // Cap precision at 8 decimal places
14850
+ precision = Math.min(precision, 8);
14851
+ // Check if the value rounds to a whole number when rounded to the nearest integer
14852
+ // This handles cases like 123.999 -> 124.000, but only if the value is very close to a whole number
14853
+ const roundedToInteger = Math.round(absValue);
14854
+ if (roundedToInteger >= 1 && Number.isInteger(roundedToInteger) && Math.abs(absValue - roundedToInteger) < 0.01) {
14855
+ const sign = value < 0 ? "-" : "";
14856
+ return `${sign}$${roundedToInteger.toFixed(3)}`;
14857
+ }
14858
+ // Round to the determined precision
14859
+ const rounded = absValue.toFixed(precision);
14725
14860
  const sign = value < 0 ? "-" : "";
14726
- return `${sign}$${formatted}`;
14861
+ const roundedNum = parseFloat(rounded);
14862
+ // If the result is effectively zero (like $0.00000000), display as $0.00
14863
+ if (roundedNum === 0 || rounded === "0.00000000") {
14864
+ return "$0.00";
14865
+ }
14866
+ // If the rounded value is a whole number and >= 1, show 3 decimal places
14867
+ if (roundedNum >= 1 && Number.isInteger(roundedNum)) {
14868
+ return `${sign}$${roundedNum.toFixed(3)}`;
14869
+ }
14870
+ return `${sign}$${rounded}`;
14727
14871
  }
14728
14872
  /**
14729
14873
  * Formats latency in a human-readable format
@@ -16339,7 +16483,7 @@ const CountUpCurrency = ({ value, prefix = "", suffix = "" })=>{
16339
16483
  end: value,
16340
16484
  duration: DEFAULT_COUNTUP_DURATION,
16341
16485
  separator: ",",
16342
- decimals: 2,
16486
+ decimals: 3,
16343
16487
  decimal: ".",
16344
16488
  prefix: prefix,
16345
16489
  useEasing: true,
@@ -16966,8 +17110,8 @@ const AgentNode = ({ data, id, onInspect })=>{
16966
17110
  const nodeIcon = getNodeIcon(modelProvider);
16967
17111
  const modelName = data.details?.internals?.llm_details?.[data.details?.internals?.llm_details?.length - 1]?.model_name || "Unknown";
16968
17112
  const totalCost = sumTotalCost(data.details?.internals?.llm_details || []);
16969
- const totalCostFormatted = formatCurrency(totalCost, 2);
16970
- const totalCostTooltip = `Total cost: ${formatCurrency(totalCost, 5)} USD`;
17113
+ const totalCostFormatted = formatCurrency(totalCost);
17114
+ const totalCostTooltip = `Total cost: ${formatCurrency(totalCost)} USD`;
16971
17115
  const nodeDetails = getOverviewLlmDetails(data.details?.internals?.llm_details || []);
16972
17116
  const inputArray = nodeDetails?.input || [];
16973
17117
  const output = nodeDetails?.output || null;
@@ -19564,7 +19708,7 @@ var correctTargets = function(parent, targets) {
19564
19708
  };
19565
19709
 
19566
19710
  var DIALOG_NAME = "Dialog";
19567
- var [createDialogContext, createDialogScope] = createContextScope(DIALOG_NAME);
19711
+ var [createDialogContext] = createContextScope(DIALOG_NAME);
19568
19712
  var [DialogProvider, useDialogContext] = createDialogContext(DIALOG_NAME);
19569
19713
  var Dialog = (props)=>{
19570
19714
  const { __scopeDialog, children, open: openProp, defaultOpen, onOpenChange, modal = true } = props;
@@ -20179,7 +20323,7 @@ const nodeTypes = {
20179
20323
  const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", height = "100dvh", className = "", defaultZoom = 1, defaultPan = {
20180
20324
  x: 0,
20181
20325
  y: 0
20182
- }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect })=>{
20326
+ }, disableAutoFit = true, defaultAutoFitDuration = 1000, defaultAutoFitDelay = 250, showTimeline = false, minNodeSpacing = 300, onInspect, panToNodeId })=>{
20183
20327
  const { theme, isDarkMode } = useTheme();
20184
20328
  const themeColors = theme.colors;
20185
20329
  // Helper function to determine if we should apply minimum dimensions
@@ -20525,6 +20669,39 @@ const AgenticFlowVisualizer = ({ flowData: propFlowData, width = "100dvw", heigh
20525
20669
  defaultAutoFitDuration,
20526
20670
  defaultAutoFitDelay
20527
20671
  ]);
20672
+ // Pan to specific node when panToNodeId prop is provided
20673
+ useEffect(()=>{
20674
+ if (!panToNodeId || !reactFlowInstance || disableAutoFit || nodes.length === 0) {
20675
+ return;
20676
+ }
20677
+ // Check if the specified node ID exists in the current nodes array
20678
+ const targetNode = nodes.find((node)=>node.id === panToNodeId);
20679
+ if (!targetNode) {
20680
+ return;
20681
+ }
20682
+ // Pan to the specified node
20683
+ setTimeout(()=>{
20684
+ reactFlowInstance.fitView({
20685
+ nodes: [
20686
+ {
20687
+ id: panToNodeId
20688
+ }
20689
+ ],
20690
+ duration: defaultAutoFitDuration,
20691
+ padding: 0.2,
20692
+ minZoom: defaultZoom,
20693
+ maxZoom: defaultZoom
20694
+ });
20695
+ }, defaultAutoFitDelay);
20696
+ }, [
20697
+ panToNodeId,
20698
+ reactFlowInstance,
20699
+ disableAutoFit,
20700
+ nodes,
20701
+ defaultAutoFitDuration,
20702
+ defaultAutoFitDelay,
20703
+ defaultZoom
20704
+ ]);
20528
20705
  return /*#__PURE__*/ React__default.createElement("div", {
20529
20706
  ref: containerRef,
20530
20707
  style: {
@@ -23271,7 +23448,7 @@ var SELECTION_KEYS = [
23271
23448
  ];
23272
23449
  var SELECT_NAME = "Select";
23273
23450
  var [Collection, useCollection, createCollectionScope] = createCollection(SELECT_NAME);
23274
- var [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME, [
23451
+ var [createSelectContext] = createContextScope(SELECT_NAME, [
23275
23452
  createCollectionScope,
23276
23453
  createPopperScope
23277
23454
  ]);
@@ -24787,7 +24964,7 @@ const FileSelector = ({ files, currentFile, onFileSelect, onRefresh, loading = f
24787
24964
  };
24788
24965
 
24789
24966
  var CHECKBOX_NAME = "Checkbox";
24790
- var [createCheckboxContext, createCheckboxScope] = createContextScope(CHECKBOX_NAME);
24967
+ var [createCheckboxContext] = createContextScope(CHECKBOX_NAME);
24791
24968
  var [CheckboxProviderImpl, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME);
24792
24969
  function CheckboxProvider(props) {
24793
24970
  const { __scopeCheckbox, checked: checkedProp, children, defaultChecked, disabled, form, name, onCheckedChange, required, value = "on", // @ts-expect-error