Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,31 @@ describe('AddNodeManager', () => {
});
});

it('preserves preview parent scope when replacing a loop child preview', () => {
const parentedPreviewNode: Node = {
...previewNode,
parentId: 'loop-1',
extent: 'parent',
};
mockNodes = [existingNode, parentedPreviewNode];
mockPreviewNodeReturn.mockReturnValue({
previewNode: parentedPreviewNode,
previewNodeConnectionInfo: connectionInfo,
});

render(<AddNodeManager customPanel={TestPanel} />);

screen.getByTestId('select-node').click();

const addedNode = mockNodes.find((n) => n.id === 'test-node-1234567890');
expect(addedNode).toMatchObject({
id: 'test-node-1234567890',
parentId: 'loop-1',
extent: 'parent',
position: parentedPreviewNode.position,
});
});

it('applies onBeforeNodeAdded transforms when node is added as source', () => {
const onBeforeNodeAdded = vi.fn((node: Node, edges: Edge[]) => ({
newNode: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ export const AddNodeManager: React.FC<AddNodeManagerProps> = ({
const nodeData = currentPreviewNode.data?.useSmartHandles
? { ...baseNodeData, useSmartHandles: true }
: baseNodeData;
const previewNodeScope = currentPreviewNode.parentId
? { parentId: currentPreviewNode.parentId, extent: currentPreviewNode.extent }
: {};

// Create new node at preview position
const newNode: Node = {
Expand All @@ -141,6 +144,7 @@ export const AddNodeManager: React.FC<AddNodeManagerProps> = ({
position: currentPreviewNode.position,
selected: true,
data: nodeData,
...previewNodeScope,
};
// Get the manifest for the new node type to find its default handles
const newNodeManifest = registry?.getManifest(nodeItem.data.type);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Position, type ReactFlowInstance } from '@uipath/apollo-react/canvas/xyflow/react';
Comment thread
SreedharAvvari marked this conversation as resolved.
import { applyPreviewToReactFlow, createPreviewNode } from '../../utils/createPreviewNode';
import { showPreviewGraph } from '../../utils/createPreviewGraph';

/**
* Creates a preview node and edge when a button handle is clicked.
Expand All @@ -19,20 +19,12 @@ export function createAddNodePreview(
sourceHandleType: 'source' | 'target' = 'source',
ignoredNodeTypes: string[] = []
): void {
// Use the unified preview creation utility
const preview = createPreviewNode(
showPreviewGraph({
sourceNodeId,
sourceHandleId,
reactFlowInstance,
undefined, // No drop position - use auto-placement
undefined, // No custom data
sourceHandleType,
undefined, // Use default preview node size
handlePosition,
ignoredNodeTypes
);

if (preview) {
applyPreviewToReactFlow(preview, reactFlowInstance);
}
ignoredNodeTypes,
});
}
17 changes: 6 additions & 11 deletions packages/apollo-react/src/canvas/components/BaseNode/BaseNode.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Node, NodeProps, ReactFlowState } from '@uipath/apollo-react/canvas/xyflow/react';
import type { Node, NodeProps } from '@uipath/apollo-react/canvas/xyflow/react';
import {
Position,
useReactFlow,
Expand Down Expand Up @@ -27,6 +27,7 @@ import type { HandleGroupManifest } from '../../schema/node-definition';
import { resolveAdornments } from '../../utils/adornment-resolver';
import { getIcon } from '../../utils/icon-registry';
import { resolveDisplay, resolveHandles } from '../../utils/manifest-resolver';
import { selectIsConnecting } from '../../utils/NodeUtils';
import { resolveToolbar } from '../../utils/toolbar-resolver';
import { useBaseCanvasMode } from '../BaseCanvas/BaseCanvasModeProvider';
import { useCanvasTheme } from '../BaseCanvas/CanvasThemeContext';
Expand All @@ -49,13 +50,6 @@ import { BaseInnerShape } from './BaseNodeInnerShape';
import { MissingManifestNode } from './BaseNodeMissingManifest';
import { NodeLabel } from './NodeLabel';

// Use `connection.inProgress` rather than `connectionClickStartHandle`.
// `connectionClickStartHandle` is set by click-to-connect and only cleared when
// the user clicks a second handle — clicking the pane does NOT clear it, so it
// can get stuck and cause all handles across all nodes to stay visible.
// `connection.inProgress` accurately reflects an active drag-to-connect gesture.
const selectIsConnecting = (state: ReactFlowState) => !!state.connection.inProgress;

const getContainerWidth = (shape: NodeShape | undefined, width: number | undefined) => {
const defaultWidth = shape === 'rectangle' ? DEFAULT_RECTANGLE_NODE_WIDTH : DEFAULT_NODE_SIZE;
if (width && width !== DEFAULT_NODE_SIZE && width !== DEFAULT_RECTANGLE_NODE_WIDTH) {
Expand Down Expand Up @@ -87,7 +81,7 @@ const getContainerHeight = (
};

const BaseNodeComponent = (props: NodeProps<Node<BaseNodeData>>) => {
const { type, data, selected, id, dragging, width, height } = props;
const { type, data, selected, id, dragging, width, height, parentId } = props;

// Read runtime configuration from context (provided by parent node components)
const {
Expand Down Expand Up @@ -260,6 +254,7 @@ const BaseNodeComponent = (props: NodeProps<Node<BaseNodeData>>) => {
// Compute height: max of base height (user-specified or measured) and handle minimum.
// baseHeightRef is updated above from external height changes; handle inflation
// is computed from the current handleConfigurations.
// biome-ignore lint/correctness/useExhaustiveDependencies: height updates baseHeightRef above and intentionally retriggers this memo.
const computedHeight = useMemo(() => {
const leftHandles = handleConfigurations
.filter((config) => config.position === Position.Left && config.visible !== false)
Expand All @@ -280,12 +275,11 @@ const BaseNodeComponent = (props: NodeProps<Node<BaseNodeData>>) => {
// Each handle gets a 2-grid-space lane (32px), plus 2-grid-space padding at top + bottom of node.
const minNodeHeight = (leftRightHandles * 2 + 2) * GRID_SPACING;
return Math.max(baseHeightRef.current, minNodeHeight);
// eslint-disable-next-line react-hooks/exhaustive-deps -- height is not read directly but triggers recalculation after baseHeightRef is updated above
}, [handleConfigurations, height]);

// biome-ignore lint/correctness/useExhaustiveDependencies: handle configuration changes require React Flow handle recalculation.
useEffect(() => {
updateNodeInternals(id);
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional when handle configurations change so it recalculates edge positions
}, [handleConfigurations, id, updateNodeInternals]);

// Sync computed height to node when it differs from React Flow's current value
Expand Down Expand Up @@ -493,6 +487,7 @@ const BaseNodeComponent = (props: NodeProps<Node<BaseNodeData>>) => {
nodeWidth: width,
nodeHeight: height,
shouldShowAddButtonFn,
portalActions: !!parentId,
});

// Generate SmartHandle elements from handle configurations (opt-in)
Expand Down
Loading
Loading